123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897 |
- From 24884115a6029995dba2561b1ee810f28a34271a Mon Sep 17 00:00:00 2001
- From: "Ivan T. Ivanov" <[email protected]>
- Date: Thu, 13 Feb 2014 18:21:38 +0200
- Subject: [PATCH 065/182] spi: Add Qualcomm QUP SPI controller support
- Qualcomm Universal Peripheral (QUP) core is an AHB slave that
- provides a common data path (an output FIFO and an input FIFO)
- for serial peripheral interface (SPI) mini-core. SPI in master
- mode supports up to 50MHz, up to four chip selects, programmable
- data path from 4 bits to 32 bits and numerous protocol variants.
- Cc: Alok Chauhan <[email protected]>
- Cc: Gilad Avidov <[email protected]>
- Cc: Kiran Gunda <[email protected]>
- Cc: Sagar Dharia <[email protected]>
- Cc: [email protected]
- Signed-off-by: Ivan T. Ivanov <[email protected]>
- Signed-off-by: Mark Brown <[email protected]>
- ---
- drivers/spi/Kconfig | 13 +
- drivers/spi/Makefile | 1 +
- drivers/spi/spi-qup.c | 837 +++++++++++++++++++++++++++++++++++++++++++++++++
- 3 files changed, 851 insertions(+)
- create mode 100644 drivers/spi/spi-qup.c
- --- a/drivers/spi/Kconfig
- +++ b/drivers/spi/Kconfig
- @@ -390,6 +390,19 @@ config SPI_RSPI
- help
- SPI driver for Renesas RSPI and QSPI blocks.
-
- +config SPI_QUP
- + tristate "Qualcomm SPI controller with QUP interface"
- + depends on ARCH_MSM_DT
- + help
- + Qualcomm Universal Peripheral (QUP) core is an AHB slave that
- + provides a common data path (an output FIFO and an input FIFO)
- + for serial peripheral interface (SPI) mini-core. SPI in master
- + mode supports up to 50MHz, up to four chip selects, programmable
- + data path from 4 bits to 32 bits and numerous protocol variants.
- +
- + This driver can also be built as a module. If so, the module
- + will be called spi_qup.
- +
- config SPI_S3C24XX
- tristate "Samsung S3C24XX series SPI"
- depends on ARCH_S3C24XX
- --- a/drivers/spi/Makefile
- +++ b/drivers/spi/Makefile
- @@ -60,6 +60,7 @@ spi-pxa2xx-platform-$(CONFIG_SPI_PXA2XX_
- spi-pxa2xx-platform-$(CONFIG_SPI_PXA2XX_DMA) += spi-pxa2xx-dma.o
- obj-$(CONFIG_SPI_PXA2XX) += spi-pxa2xx-platform.o
- obj-$(CONFIG_SPI_PXA2XX_PCI) += spi-pxa2xx-pci.o
- +obj-$(CONFIG_SPI_QUP) += spi-qup.o
- obj-$(CONFIG_SPI_RSPI) += spi-rspi.o
- obj-$(CONFIG_SPI_S3C24XX) += spi-s3c24xx-hw.o
- spi-s3c24xx-hw-y := spi-s3c24xx.o
- --- /dev/null
- +++ b/drivers/spi/spi-qup.c
- @@ -0,0 +1,837 @@
- +/*
- + * Copyright (c) 2008-2014, The Linux foundation. All rights reserved.
- + *
- + * This program is free software; you can redistribute it and/or modify
- + * it under the terms of the GNU General Public License rev 2 and
- + * only rev 2 as published by the free Software foundation.
- + *
- + * This program is distributed in the hope that it will be useful,
- + * but WITHOUT ANY WARRANTY; without even the implied warranty of
- + * MERCHANTABILITY or fITNESS fOR A PARTICULAR PURPOSE. See the
- + * GNU General Public License for more details.
- + */
- +
- +#include <linux/clk.h>
- +#include <linux/delay.h>
- +#include <linux/err.h>
- +#include <linux/interrupt.h>
- +#include <linux/io.h>
- +#include <linux/list.h>
- +#include <linux/module.h>
- +#include <linux/of.h>
- +#include <linux/platform_device.h>
- +#include <linux/pm_runtime.h>
- +#include <linux/spi/spi.h>
- +
- +#define QUP_CONFIG 0x0000
- +#define QUP_STATE 0x0004
- +#define QUP_IO_M_MODES 0x0008
- +#define QUP_SW_RESET 0x000c
- +#define QUP_OPERATIONAL 0x0018
- +#define QUP_ERROR_FLAGS 0x001c
- +#define QUP_ERROR_FLAGS_EN 0x0020
- +#define QUP_OPERATIONAL_MASK 0x0028
- +#define QUP_HW_VERSION 0x0030
- +#define QUP_MX_OUTPUT_CNT 0x0100
- +#define QUP_OUTPUT_FIFO 0x0110
- +#define QUP_MX_WRITE_CNT 0x0150
- +#define QUP_MX_INPUT_CNT 0x0200
- +#define QUP_MX_READ_CNT 0x0208
- +#define QUP_INPUT_FIFO 0x0218
- +
- +#define SPI_CONFIG 0x0300
- +#define SPI_IO_CONTROL 0x0304
- +#define SPI_ERROR_FLAGS 0x0308
- +#define SPI_ERROR_FLAGS_EN 0x030c
- +
- +/* QUP_CONFIG fields */
- +#define QUP_CONFIG_SPI_MODE (1 << 8)
- +#define QUP_CONFIG_CLOCK_AUTO_GATE BIT(13)
- +#define QUP_CONFIG_NO_INPUT BIT(7)
- +#define QUP_CONFIG_NO_OUTPUT BIT(6)
- +#define QUP_CONFIG_N 0x001f
- +
- +/* QUP_STATE fields */
- +#define QUP_STATE_VALID BIT(2)
- +#define QUP_STATE_RESET 0
- +#define QUP_STATE_RUN 1
- +#define QUP_STATE_PAUSE 3
- +#define QUP_STATE_MASK 3
- +#define QUP_STATE_CLEAR 2
- +
- +#define QUP_HW_VERSION_2_1_1 0x20010001
- +
- +/* QUP_IO_M_MODES fields */
- +#define QUP_IO_M_PACK_EN BIT(15)
- +#define QUP_IO_M_UNPACK_EN BIT(14)
- +#define QUP_IO_M_INPUT_MODE_MASK_SHIFT 12
- +#define QUP_IO_M_OUTPUT_MODE_MASK_SHIFT 10
- +#define QUP_IO_M_INPUT_MODE_MASK (3 << QUP_IO_M_INPUT_MODE_MASK_SHIFT)
- +#define QUP_IO_M_OUTPUT_MODE_MASK (3 << QUP_IO_M_OUTPUT_MODE_MASK_SHIFT)
- +
- +#define QUP_IO_M_OUTPUT_BLOCK_SIZE(x) (((x) & (0x03 << 0)) >> 0)
- +#define QUP_IO_M_OUTPUT_FIFO_SIZE(x) (((x) & (0x07 << 2)) >> 2)
- +#define QUP_IO_M_INPUT_BLOCK_SIZE(x) (((x) & (0x03 << 5)) >> 5)
- +#define QUP_IO_M_INPUT_FIFO_SIZE(x) (((x) & (0x07 << 7)) >> 7)
- +
- +#define QUP_IO_M_MODE_FIFO 0
- +#define QUP_IO_M_MODE_BLOCK 1
- +#define QUP_IO_M_MODE_DMOV 2
- +#define QUP_IO_M_MODE_BAM 3
- +
- +/* QUP_OPERATIONAL fields */
- +#define QUP_OP_MAX_INPUT_DONE_FLAG BIT(11)
- +#define QUP_OP_MAX_OUTPUT_DONE_FLAG BIT(10)
- +#define QUP_OP_IN_SERVICE_FLAG BIT(9)
- +#define QUP_OP_OUT_SERVICE_FLAG BIT(8)
- +#define QUP_OP_IN_FIFO_FULL BIT(7)
- +#define QUP_OP_OUT_FIFO_FULL BIT(6)
- +#define QUP_OP_IN_FIFO_NOT_EMPTY BIT(5)
- +#define QUP_OP_OUT_FIFO_NOT_EMPTY BIT(4)
- +
- +/* QUP_ERROR_FLAGS and QUP_ERROR_FLAGS_EN fields */
- +#define QUP_ERROR_OUTPUT_OVER_RUN BIT(5)
- +#define QUP_ERROR_INPUT_UNDER_RUN BIT(4)
- +#define QUP_ERROR_OUTPUT_UNDER_RUN BIT(3)
- +#define QUP_ERROR_INPUT_OVER_RUN BIT(2)
- +
- +/* SPI_CONFIG fields */
- +#define SPI_CONFIG_HS_MODE BIT(10)
- +#define SPI_CONFIG_INPUT_FIRST BIT(9)
- +#define SPI_CONFIG_LOOPBACK BIT(8)
- +
- +/* SPI_IO_CONTROL fields */
- +#define SPI_IO_C_FORCE_CS BIT(11)
- +#define SPI_IO_C_CLK_IDLE_HIGH BIT(10)
- +#define SPI_IO_C_MX_CS_MODE BIT(8)
- +#define SPI_IO_C_CS_N_POLARITY_0 BIT(4)
- +#define SPI_IO_C_CS_SELECT(x) (((x) & 3) << 2)
- +#define SPI_IO_C_CS_SELECT_MASK 0x000c
- +#define SPI_IO_C_TRISTATE_CS BIT(1)
- +#define SPI_IO_C_NO_TRI_STATE BIT(0)
- +
- +/* SPI_ERROR_FLAGS and SPI_ERROR_FLAGS_EN fields */
- +#define SPI_ERROR_CLK_OVER_RUN BIT(1)
- +#define SPI_ERROR_CLK_UNDER_RUN BIT(0)
- +
- +#define SPI_NUM_CHIPSELECTS 4
- +
- +/* high speed mode is when bus rate is greater then 26MHz */
- +#define SPI_HS_MIN_RATE 26000000
- +#define SPI_MAX_RATE 50000000
- +
- +#define SPI_DELAY_THRESHOLD 1
- +#define SPI_DELAY_RETRY 10
- +
- +struct spi_qup_device {
- + int select;
- + u16 mode;
- +};
- +
- +struct spi_qup {
- + void __iomem *base;
- + struct device *dev;
- + struct clk *cclk; /* core clock */
- + struct clk *iclk; /* interface clock */
- + int irq;
- + u32 max_speed_hz;
- + spinlock_t lock;
- +
- + int in_fifo_sz;
- + int out_fifo_sz;
- + int in_blk_sz;
- + int out_blk_sz;
- +
- + struct spi_transfer *xfer;
- + struct completion done;
- + int error;
- + int w_size; /* bytes per SPI word */
- + int tx_bytes;
- + int rx_bytes;
- +};
- +
- +
- +static inline bool spi_qup_is_valid_state(struct spi_qup *controller)
- +{
- + u32 opstate = readl_relaxed(controller->base + QUP_STATE);
- +
- + return opstate & QUP_STATE_VALID;
- +}
- +
- +static int spi_qup_set_state(struct spi_qup *controller, u32 state)
- +{
- + unsigned long loop;
- + u32 cur_state;
- +
- + loop = 0;
- + while (!spi_qup_is_valid_state(controller)) {
- +
- + usleep_range(SPI_DELAY_THRESHOLD, SPI_DELAY_THRESHOLD * 2);
- +
- + if (++loop > SPI_DELAY_RETRY)
- + return -EIO;
- + }
- +
- + if (loop)
- + dev_dbg(controller->dev, "invalid state for %ld,us %d\n",
- + loop, state);
- +
- + cur_state = readl_relaxed(controller->base + QUP_STATE);
- + /*
- + * Per spec: for PAUSE_STATE to RESET_STATE, two writes
- + * of (b10) are required
- + */
- + if (((cur_state & QUP_STATE_MASK) == QUP_STATE_PAUSE) &&
- + (state == QUP_STATE_RESET)) {
- + writel_relaxed(QUP_STATE_CLEAR, controller->base + QUP_STATE);
- + writel_relaxed(QUP_STATE_CLEAR, controller->base + QUP_STATE);
- + } else {
- + cur_state &= ~QUP_STATE_MASK;
- + cur_state |= state;
- + writel_relaxed(cur_state, controller->base + QUP_STATE);
- + }
- +
- + loop = 0;
- + while (!spi_qup_is_valid_state(controller)) {
- +
- + usleep_range(SPI_DELAY_THRESHOLD, SPI_DELAY_THRESHOLD * 2);
- +
- + if (++loop > SPI_DELAY_RETRY)
- + return -EIO;
- + }
- +
- + return 0;
- +}
- +
- +
- +static void spi_qup_fifo_read(struct spi_qup *controller,
- + struct spi_transfer *xfer)
- +{
- + u8 *rx_buf = xfer->rx_buf;
- + u32 word, state;
- + int idx, shift, w_size;
- +
- + w_size = controller->w_size;
- +
- + while (controller->rx_bytes < xfer->len) {
- +
- + state = readl_relaxed(controller->base + QUP_OPERATIONAL);
- + if (0 == (state & QUP_OP_IN_FIFO_NOT_EMPTY))
- + break;
- +
- + word = readl_relaxed(controller->base + QUP_INPUT_FIFO);
- +
- + if (!rx_buf) {
- + controller->rx_bytes += w_size;
- + continue;
- + }
- +
- + for (idx = 0; idx < w_size; idx++, controller->rx_bytes++) {
- + /*
- + * The data format depends on bytes per SPI word:
- + * 4 bytes: 0x12345678
- + * 2 bytes: 0x00001234
- + * 1 byte : 0x00000012
- + */
- + shift = BITS_PER_BYTE;
- + shift *= (w_size - idx - 1);
- + rx_buf[controller->rx_bytes] = word >> shift;
- + }
- + }
- +}
- +
- +static void spi_qup_fifo_write(struct spi_qup *controller,
- + struct spi_transfer *xfer)
- +{
- + const u8 *tx_buf = xfer->tx_buf;
- + u32 word, state, data;
- + int idx, w_size;
- +
- + w_size = controller->w_size;
- +
- + while (controller->tx_bytes < xfer->len) {
- +
- + state = readl_relaxed(controller->base + QUP_OPERATIONAL);
- + if (state & QUP_OP_OUT_FIFO_FULL)
- + break;
- +
- + word = 0;
- + for (idx = 0; idx < w_size; idx++, controller->tx_bytes++) {
- +
- + if (!tx_buf) {
- + controller->tx_bytes += w_size;
- + break;
- + }
- +
- + data = tx_buf[controller->tx_bytes];
- + word |= data << (BITS_PER_BYTE * (3 - idx));
- + }
- +
- + writel_relaxed(word, controller->base + QUP_OUTPUT_FIFO);
- + }
- +}
- +
- +static irqreturn_t spi_qup_qup_irq(int irq, void *dev_id)
- +{
- + struct spi_qup *controller = dev_id;
- + struct spi_transfer *xfer;
- + u32 opflags, qup_err, spi_err;
- + unsigned long flags;
- + int error = 0;
- +
- + spin_lock_irqsave(&controller->lock, flags);
- + xfer = controller->xfer;
- + controller->xfer = NULL;
- + spin_unlock_irqrestore(&controller->lock, flags);
- +
- + qup_err = readl_relaxed(controller->base + QUP_ERROR_FLAGS);
- + spi_err = readl_relaxed(controller->base + SPI_ERROR_FLAGS);
- + opflags = readl_relaxed(controller->base + QUP_OPERATIONAL);
- +
- + writel_relaxed(qup_err, controller->base + QUP_ERROR_FLAGS);
- + writel_relaxed(spi_err, controller->base + SPI_ERROR_FLAGS);
- + writel_relaxed(opflags, controller->base + QUP_OPERATIONAL);
- +
- + if (!xfer) {
- + dev_err_ratelimited(controller->dev, "unexpected irq %x08 %x08 %x08\n",
- + qup_err, spi_err, opflags);
- + return IRQ_HANDLED;
- + }
- +
- + if (qup_err) {
- + if (qup_err & QUP_ERROR_OUTPUT_OVER_RUN)
- + dev_warn(controller->dev, "OUTPUT_OVER_RUN\n");
- + if (qup_err & QUP_ERROR_INPUT_UNDER_RUN)
- + dev_warn(controller->dev, "INPUT_UNDER_RUN\n");
- + if (qup_err & QUP_ERROR_OUTPUT_UNDER_RUN)
- + dev_warn(controller->dev, "OUTPUT_UNDER_RUN\n");
- + if (qup_err & QUP_ERROR_INPUT_OVER_RUN)
- + dev_warn(controller->dev, "INPUT_OVER_RUN\n");
- +
- + error = -EIO;
- + }
- +
- + if (spi_err) {
- + if (spi_err & SPI_ERROR_CLK_OVER_RUN)
- + dev_warn(controller->dev, "CLK_OVER_RUN\n");
- + if (spi_err & SPI_ERROR_CLK_UNDER_RUN)
- + dev_warn(controller->dev, "CLK_UNDER_RUN\n");
- +
- + error = -EIO;
- + }
- +
- + if (opflags & QUP_OP_IN_SERVICE_FLAG)
- + spi_qup_fifo_read(controller, xfer);
- +
- + if (opflags & QUP_OP_OUT_SERVICE_FLAG)
- + spi_qup_fifo_write(controller, xfer);
- +
- + spin_lock_irqsave(&controller->lock, flags);
- + controller->error = error;
- + controller->xfer = xfer;
- + spin_unlock_irqrestore(&controller->lock, flags);
- +
- + if (controller->rx_bytes == xfer->len || error)
- + complete(&controller->done);
- +
- + return IRQ_HANDLED;
- +}
- +
- +
- +/* set clock freq ... bits per word */
- +static int spi_qup_io_config(struct spi_qup *controller,
- + struct spi_qup_device *chip,
- + struct spi_transfer *xfer)
- +{
- + u32 config, iomode, mode;
- + int ret, n_words, w_size;
- +
- + if (chip->mode & SPI_LOOP && xfer->len > controller->in_fifo_sz) {
- + dev_err(controller->dev, "too big size for loopback %d > %d\n",
- + xfer->len, controller->in_fifo_sz);
- + return -EIO;
- + }
- +
- + ret = clk_set_rate(controller->cclk, xfer->speed_hz);
- + if (ret) {
- + dev_err(controller->dev, "fail to set frequency %d",
- + xfer->speed_hz);
- + return -EIO;
- + }
- +
- + if (spi_qup_set_state(controller, QUP_STATE_RESET)) {
- + dev_err(controller->dev, "cannot set RESET state\n");
- + return -EIO;
- + }
- +
- + w_size = 4;
- + if (xfer->bits_per_word <= 8)
- + w_size = 1;
- + else if (xfer->bits_per_word <= 16)
- + w_size = 2;
- +
- + n_words = xfer->len / w_size;
- + controller->w_size = w_size;
- +
- + if (n_words <= controller->in_fifo_sz) {
- + mode = QUP_IO_M_MODE_FIFO;
- + writel_relaxed(n_words, controller->base + QUP_MX_READ_CNT);
- + writel_relaxed(n_words, controller->base + QUP_MX_WRITE_CNT);
- + /* must be zero for FIFO */
- + writel_relaxed(0, controller->base + QUP_MX_INPUT_CNT);
- + writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT);
- + } else {
- + mode = QUP_IO_M_MODE_BLOCK;
- + writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT);
- + writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT);
- + /* must be zero for BLOCK and BAM */
- + writel_relaxed(0, controller->base + QUP_MX_READ_CNT);
- + writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT);
- + }
- +
- + iomode = readl_relaxed(controller->base + QUP_IO_M_MODES);
- + /* Set input and output transfer mode */
- + iomode &= ~(QUP_IO_M_INPUT_MODE_MASK | QUP_IO_M_OUTPUT_MODE_MASK);
- + iomode &= ~(QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN);
- + iomode |= (mode << QUP_IO_M_OUTPUT_MODE_MASK_SHIFT);
- + iomode |= (mode << QUP_IO_M_INPUT_MODE_MASK_SHIFT);
- +
- + writel_relaxed(iomode, controller->base + QUP_IO_M_MODES);
- +
- + config = readl_relaxed(controller->base + SPI_CONFIG);
- +
- + if (chip->mode & SPI_LOOP)
- + config |= SPI_CONFIG_LOOPBACK;
- + else
- + config &= ~SPI_CONFIG_LOOPBACK;
- +
- + if (chip->mode & SPI_CPHA)
- + config &= ~SPI_CONFIG_INPUT_FIRST;
- + else
- + config |= SPI_CONFIG_INPUT_FIRST;
- +
- + /*
- + * HS_MODE improves signal stability for spi-clk high rates,
- + * but is invalid in loop back mode.
- + */
- + if ((xfer->speed_hz >= SPI_HS_MIN_RATE) && !(chip->mode & SPI_LOOP))
- + config |= SPI_CONFIG_HS_MODE;
- + else
- + config &= ~SPI_CONFIG_HS_MODE;
- +
- + writel_relaxed(config, controller->base + SPI_CONFIG);
- +
- + config = readl_relaxed(controller->base + QUP_CONFIG);
- + config &= ~(QUP_CONFIG_NO_INPUT | QUP_CONFIG_NO_OUTPUT | QUP_CONFIG_N);
- + config |= xfer->bits_per_word - 1;
- + config |= QUP_CONFIG_SPI_MODE;
- + writel_relaxed(config, controller->base + QUP_CONFIG);
- +
- + writel_relaxed(0, controller->base + QUP_OPERATIONAL_MASK);
- + return 0;
- +}
- +
- +static void spi_qup_set_cs(struct spi_device *spi, bool enable)
- +{
- + struct spi_qup *controller = spi_master_get_devdata(spi->master);
- + struct spi_qup_device *chip = spi_get_ctldata(spi);
- +
- + u32 iocontol, mask;
- +
- + iocontol = readl_relaxed(controller->base + SPI_IO_CONTROL);
- +
- + /* Disable auto CS toggle and use manual */
- + iocontol &= ~SPI_IO_C_MX_CS_MODE;
- + iocontol |= SPI_IO_C_FORCE_CS;
- +
- + iocontol &= ~SPI_IO_C_CS_SELECT_MASK;
- + iocontol |= SPI_IO_C_CS_SELECT(chip->select);
- +
- + mask = SPI_IO_C_CS_N_POLARITY_0 << chip->select;
- +
- + if (enable)
- + iocontol |= mask;
- + else
- + iocontol &= ~mask;
- +
- + writel_relaxed(iocontol, controller->base + SPI_IO_CONTROL);
- +}
- +
- +static int spi_qup_transfer_one(struct spi_master *master,
- + struct spi_device *spi,
- + struct spi_transfer *xfer)
- +{
- + struct spi_qup *controller = spi_master_get_devdata(master);
- + struct spi_qup_device *chip = spi_get_ctldata(spi);
- + unsigned long timeout, flags;
- + int ret = -EIO;
- +
- + ret = spi_qup_io_config(controller, chip, xfer);
- + if (ret)
- + return ret;
- +
- + timeout = DIV_ROUND_UP(xfer->speed_hz, MSEC_PER_SEC);
- + timeout = DIV_ROUND_UP(xfer->len * 8, timeout);
- + timeout = 100 * msecs_to_jiffies(timeout);
- +
- + reinit_completion(&controller->done);
- +
- + spin_lock_irqsave(&controller->lock, flags);
- + controller->xfer = xfer;
- + controller->error = 0;
- + controller->rx_bytes = 0;
- + controller->tx_bytes = 0;
- + spin_unlock_irqrestore(&controller->lock, flags);
- +
- + if (spi_qup_set_state(controller, QUP_STATE_RUN)) {
- + dev_warn(controller->dev, "cannot set RUN state\n");
- + goto exit;
- + }
- +
- + if (spi_qup_set_state(controller, QUP_STATE_PAUSE)) {
- + dev_warn(controller->dev, "cannot set PAUSE state\n");
- + goto exit;
- + }
- +
- + spi_qup_fifo_write(controller, xfer);
- +
- + if (spi_qup_set_state(controller, QUP_STATE_RUN)) {
- + dev_warn(controller->dev, "cannot set EXECUTE state\n");
- + goto exit;
- + }
- +
- + if (!wait_for_completion_timeout(&controller->done, timeout))
- + ret = -ETIMEDOUT;
- +exit:
- + spi_qup_set_state(controller, QUP_STATE_RESET);
- + spin_lock_irqsave(&controller->lock, flags);
- + controller->xfer = NULL;
- + if (!ret)
- + ret = controller->error;
- + spin_unlock_irqrestore(&controller->lock, flags);
- + return ret;
- +}
- +
- +static int spi_qup_setup(struct spi_device *spi)
- +{
- + struct spi_qup *controller = spi_master_get_devdata(spi->master);
- + struct spi_qup_device *chip = spi_get_ctldata(spi);
- +
- + if (spi->chip_select >= spi->master->num_chipselect) {
- + dev_err(controller->dev, "invalid chip_select %d\n",
- + spi->chip_select);
- + return -EINVAL;
- + }
- +
- + if (spi->max_speed_hz > controller->max_speed_hz) {
- + dev_err(controller->dev, "invalid max_speed_hz %d\n",
- + spi->max_speed_hz);
- + return -EINVAL;
- + }
- +
- + if (!chip) {
- + /* First setup */
- + chip = kzalloc(sizeof(*chip), GFP_KERNEL);
- + if (!chip) {
- + dev_err(controller->dev, "no memory for chip data\n");
- + return -ENOMEM;
- + }
- +
- + chip->mode = spi->mode;
- + chip->select = spi->chip_select;
- + spi_set_ctldata(spi, chip);
- + }
- +
- + return 0;
- +}
- +
- +static void spi_qup_cleanup(struct spi_device *spi)
- +{
- + struct spi_qup_device *chip = spi_get_ctldata(spi);
- +
- + if (!chip)
- + return;
- +
- + spi_set_ctldata(spi, NULL);
- + kfree(chip);
- +}
- +
- +static int spi_qup_probe(struct platform_device *pdev)
- +{
- + struct spi_master *master;
- + struct clk *iclk, *cclk;
- + struct spi_qup *controller;
- + struct resource *res;
- + struct device *dev;
- + void __iomem *base;
- + u32 data, max_freq, iomode;
- + int ret, irq, size;
- +
- + dev = &pdev->dev;
- + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- + base = devm_ioremap_resource(dev, res);
- + if (IS_ERR(base))
- + return PTR_ERR(base);
- +
- + irq = platform_get_irq(pdev, 0);
- +
- + if (irq < 0)
- + return irq;
- +
- + cclk = devm_clk_get(dev, "core");
- + if (IS_ERR(cclk))
- + return PTR_ERR(cclk);
- +
- + iclk = devm_clk_get(dev, "iface");
- + if (IS_ERR(iclk))
- + return PTR_ERR(iclk);
- +
- + /* This is optional parameter */
- + if (of_property_read_u32(dev->of_node, "spi-max-frequency", &max_freq))
- + max_freq = SPI_MAX_RATE;
- +
- + if (!max_freq || max_freq > SPI_MAX_RATE) {
- + dev_err(dev, "invalid clock frequency %d\n", max_freq);
- + return -ENXIO;
- + }
- +
- + ret = clk_prepare_enable(cclk);
- + if (ret) {
- + dev_err(dev, "cannot enable core clock\n");
- + return ret;
- + }
- +
- + ret = clk_prepare_enable(iclk);
- + if (ret) {
- + clk_disable_unprepare(cclk);
- + dev_err(dev, "cannot enable iface clock\n");
- + return ret;
- + }
- +
- + data = readl_relaxed(base + QUP_HW_VERSION);
- +
- + if (data < QUP_HW_VERSION_2_1_1) {
- + clk_disable_unprepare(cclk);
- + clk_disable_unprepare(iclk);
- + dev_err(dev, "v.%08x is not supported\n", data);
- + return -ENXIO;
- + }
- +
- + master = spi_alloc_master(dev, sizeof(struct spi_qup));
- + if (!master) {
- + clk_disable_unprepare(cclk);
- + clk_disable_unprepare(iclk);
- + dev_err(dev, "cannot allocate master\n");
- + return -ENOMEM;
- + }
- +
- + master->bus_num = pdev->id;
- + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LOOP;
- + master->num_chipselect = SPI_NUM_CHIPSELECTS;
- + master->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32);
- + master->setup = spi_qup_setup;
- + master->cleanup = spi_qup_cleanup;
- + master->set_cs = spi_qup_set_cs;
- + master->transfer_one = spi_qup_transfer_one;
- + master->dev.of_node = pdev->dev.of_node;
- + master->auto_runtime_pm = true;
- +
- + platform_set_drvdata(pdev, master);
- +
- + controller = spi_master_get_devdata(master);
- +
- + controller->dev = dev;
- + controller->base = base;
- + controller->iclk = iclk;
- + controller->cclk = cclk;
- + controller->irq = irq;
- + controller->max_speed_hz = max_freq;
- +
- + spin_lock_init(&controller->lock);
- + init_completion(&controller->done);
- +
- + iomode = readl_relaxed(base + QUP_IO_M_MODES);
- +
- + size = QUP_IO_M_OUTPUT_BLOCK_SIZE(iomode);
- + if (size)
- + controller->out_blk_sz = size * 16;
- + else
- + controller->out_blk_sz = 4;
- +
- + size = QUP_IO_M_INPUT_BLOCK_SIZE(iomode);
- + if (size)
- + controller->in_blk_sz = size * 16;
- + else
- + controller->in_blk_sz = 4;
- +
- + size = QUP_IO_M_OUTPUT_FIFO_SIZE(iomode);
- + controller->out_fifo_sz = controller->out_blk_sz * (2 << size);
- +
- + size = QUP_IO_M_INPUT_FIFO_SIZE(iomode);
- + controller->in_fifo_sz = controller->in_blk_sz * (2 << size);
- +
- + dev_info(dev, "v.%08x IN:block:%d, fifo:%d, OUT:block:%d, fifo:%d\n",
- + data, controller->in_blk_sz, controller->in_fifo_sz,
- + controller->out_blk_sz, controller->out_fifo_sz);
- +
- + writel_relaxed(1, base + QUP_SW_RESET);
- +
- + ret = spi_qup_set_state(controller, QUP_STATE_RESET);
- + if (ret) {
- + dev_err(dev, "cannot set RESET state\n");
- + goto error;
- + }
- +
- + writel_relaxed(0, base + QUP_OPERATIONAL);
- + writel_relaxed(0, base + QUP_IO_M_MODES);
- + writel_relaxed(0, base + QUP_OPERATIONAL_MASK);
- + writel_relaxed(SPI_ERROR_CLK_UNDER_RUN | SPI_ERROR_CLK_OVER_RUN,
- + base + SPI_ERROR_FLAGS_EN);
- +
- + writel_relaxed(0, base + SPI_CONFIG);
- + writel_relaxed(SPI_IO_C_NO_TRI_STATE, base + SPI_IO_CONTROL);
- +
- + ret = devm_request_irq(dev, irq, spi_qup_qup_irq,
- + IRQF_TRIGGER_HIGH, pdev->name, controller);
- + if (ret)
- + goto error;
- +
- + ret = devm_spi_register_master(dev, master);
- + if (ret)
- + goto error;
- +
- + pm_runtime_set_autosuspend_delay(dev, MSEC_PER_SEC);
- + pm_runtime_use_autosuspend(dev);
- + pm_runtime_set_active(dev);
- + pm_runtime_enable(dev);
- + return 0;
- +
- +error:
- + clk_disable_unprepare(cclk);
- + clk_disable_unprepare(iclk);
- + spi_master_put(master);
- + return ret;
- +}
- +
- +#ifdef CONFIG_PM_RUNTIME
- +static int spi_qup_pm_suspend_runtime(struct device *device)
- +{
- + struct spi_master *master = dev_get_drvdata(device);
- + struct spi_qup *controller = spi_master_get_devdata(master);
- + u32 config;
- +
- + /* Enable clocks auto gaiting */
- + config = readl(controller->base + QUP_CONFIG);
- + config |= QUP_CLOCK_AUTO_GATE;
- + writel_relaxed(config, controller->base + QUP_CONFIG);
- + return 0;
- +}
- +
- +static int spi_qup_pm_resume_runtime(struct device *device)
- +{
- + struct spi_master *master = dev_get_drvdata(device);
- + struct spi_qup *controller = spi_master_get_devdata(master);
- + u32 config;
- +
- + /* Disable clocks auto gaiting */
- + config = readl_relaxed(controller->base + QUP_CONFIG);
- + config &= ~QUP_CLOCK_AUTO_GATE;
- + writel_relaxed(config, controller->base + QUP_CONFIG);
- + return 0;
- +}
- +#endif /* CONFIG_PM_RUNTIME */
- +
- +#ifdef CONFIG_PM_SLEEP
- +static int spi_qup_suspend(struct device *device)
- +{
- + struct spi_master *master = dev_get_drvdata(device);
- + struct spi_qup *controller = spi_master_get_devdata(master);
- + int ret;
- +
- + ret = spi_master_suspend(master);
- + if (ret)
- + return ret;
- +
- + ret = spi_qup_set_state(controller, QUP_STATE_RESET);
- + if (ret)
- + return ret;
- +
- + clk_disable_unprepare(controller->cclk);
- + clk_disable_unprepare(controller->iclk);
- + return 0;
- +}
- +
- +static int spi_qup_resume(struct device *device)
- +{
- + struct spi_master *master = dev_get_drvdata(device);
- + struct spi_qup *controller = spi_master_get_devdata(master);
- + int ret;
- +
- + ret = clk_prepare_enable(controller->iclk);
- + if (ret)
- + return ret;
- +
- + ret = clk_prepare_enable(controller->cclk);
- + if (ret)
- + return ret;
- +
- + ret = spi_qup_set_state(controller, QUP_STATE_RESET);
- + if (ret)
- + return ret;
- +
- + return spi_master_resume(master);
- +}
- +#endif /* CONFIG_PM_SLEEP */
- +
- +static int spi_qup_remove(struct platform_device *pdev)
- +{
- + struct spi_master *master = dev_get_drvdata(&pdev->dev);
- + struct spi_qup *controller = spi_master_get_devdata(master);
- + int ret;
- +
- + ret = pm_runtime_get_sync(&pdev->dev);
- + if (ret)
- + return ret;
- +
- + ret = spi_qup_set_state(controller, QUP_STATE_RESET);
- + if (ret)
- + return ret;
- +
- + clk_disable_unprepare(controller->cclk);
- + clk_disable_unprepare(controller->iclk);
- +
- + pm_runtime_put_noidle(&pdev->dev);
- + pm_runtime_disable(&pdev->dev);
- + spi_master_put(master);
- + return 0;
- +}
- +
- +static struct of_device_id spi_qup_dt_match[] = {
- + { .compatible = "qcom,spi-qup-v2.1.1", },
- + { .compatible = "qcom,spi-qup-v2.2.1", },
- + { }
- +};
- +MODULE_DEVICE_TABLE(of, spi_qup_dt_match);
- +
- +static const struct dev_pm_ops spi_qup_dev_pm_ops = {
- + SET_SYSTEM_SLEEP_PM_OPS(spi_qup_suspend, spi_qup_resume)
- + SET_RUNTIME_PM_OPS(spi_qup_pm_suspend_runtime,
- + spi_qup_pm_resume_runtime,
- + NULL)
- +};
- +
- +static struct platform_driver spi_qup_driver = {
- + .driver = {
- + .name = "spi_qup",
- + .owner = THIS_MODULE,
- + .pm = &spi_qup_dev_pm_ops,
- + .of_match_table = spi_qup_dt_match,
- + },
- + .probe = spi_qup_probe,
- + .remove = spi_qup_remove,
- +};
- +module_platform_driver(spi_qup_driver);
- +
- +MODULE_LICENSE("GPL v2");
- +MODULE_VERSION("0.4");
- +MODULE_ALIAS("platform:spi_qup");
|