lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1530827202-9997-2-git-send-email-girishm@codeaurora.org>
Date:   Thu,  5 Jul 2018 15:46:42 -0600
From:   Girish Mahadevan <girishm@...eaurora.org>
To:     broonie@...nel.org, linux-spi@...r.kernel.org,
        linux-kernel@...r.kernel.org
Cc:     Girish Mahadevan <girishm@...eaurora.org>, dianders@...omium.org,
        swboyd@...omium.org, linux-arm-msm@...r.kernel.org,
        sdharia@...eaurora.org, kramasub@...eaurora.org,
        boris.brezillon@...tlin.com
Subject: [PATCH 2/2] spi: Introduce new driver for Qualcomm QuadSPI controller

New driver for Qualcomm QuadSPI(QSPI) controller that is used to
communicate with slaves such flash memory devices. The QSPI controller
can operate in 2 or 4 wire mode but only supports SPI Mode 0 and SPI
Mode 3. The controller can also operate in Single or Dual data rate modes.

Signed-off-by: Girish Mahadevan <girishm@...eaurora.org>
---
 drivers/spi/Kconfig         |   7 +
 drivers/spi/Makefile        |   1 +
 drivers/spi/spi-qcom-qspi.c | 728 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 736 insertions(+)
 create mode 100644 drivers/spi/spi-qcom-qspi.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 97e1fa9..bc71cd3 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -571,6 +571,13 @@ config SPI_QCOM_GENI
 	  This driver can also be built as a module.  If so, the module
 	  will be called spi-geni-qcom.
 
+config SPI_QCOM_QSPI
+	tristate "QTI QPSPI controller"
+	depends on ARCH_QCOM
+	help
+	  QSPI(Quad SPI) driver for Qualcomm QSPI controller.
+
 config SPI_QUP
 	tristate "Qualcomm SPI controller with QUP interface"
 	depends on ARCH_QCOM || (ARM && COMPILE_TEST)
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 5a0ce66..93cea68 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -113,3 +113,4 @@ obj-$(CONFIG_SPI_ZYNQMP_GQSPI)		+= spi-zynqmp-gqspi.o
 # SPI slave protocol handlers
 obj-$(CONFIG_SPI_SLAVE_TIME)		+= spi-slave-time.o
 obj-$(CONFIG_SPI_SLAVE_SYSTEM_CONTROL)	+= spi-slave-system-control.o
+obj-$(CONFIG_SPI_QCOM_QSPI)		+= spi-qcom-qspi.o
diff --git a/drivers/spi/spi-qcom-qspi.c b/drivers/spi/spi-qcom-qspi.c
new file mode 100644
index 0000000..6a86605
--- /dev/null
+++ b/drivers/spi/spi-qcom-qspi.c
@@ -0,0 +1,728 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2017-2018, The Linux foundation. All rights reserved.
+
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi-mem.h>
+
+#define AHB_MIN_HZ		9600000UL
+#define QSPI_NUM_CS		2
+#define QSPI_BYTES_PER_WORD	4
+#define MSTR_CONFIG		0x0000
+#define AHB_MASTER_CFG		0x0004
+#define MSTR_INT_EN		0x000C
+#define MSTR_INT_STATUS		0x0010
+#define PIO_XFER_CTRL		0x0014
+#define PIO_XFER_CFG		0x0018
+#define PIO_XFER_STATUS		0x001c
+#define PIO_DATAOUT_1B		0x0020
+#define PIO_DATAOUT_4B		0x0024
+#define RD_FIFO_CFG		0x0028
+#define RD_FIFO_STATUS		0x002c
+#define RD_FIFO_RESET		0x0030
+#define CUR_MEM_ADDR		0x0048
+#define HW_VERSION		0x004c
+#define RD_FIFOk		0x0050
+#define SAMPLING_CLK_CFG	0x0090
+#define SAMPLING_CLK_STATUS	0x0094
+
+/* Macros to help set/get fields in MSTR_CONFIG register */
+#define	FULL_CYCLE_MODE	BIT(3)
+#define	FB_CLK_EN	BIT(4)
+#define	PIN_HOLDN	BIT(6)
+#define	PIN_WPN		(BIT(7))
+#define	DMA_ENABLE		(BIT(8))
+#define	BIG_ENDIAN_MODE		(BIT(9))
+#define	SPI_MODE_MSK		(0xc00)
+#define	SPI_MODE_SHFT		(10)
+#define	CHIP_SELECT_NUM		BIT(12)
+#define	SBL_EN			BIT(13)
+#define	LPA_BASE_MSK		(0x3c000)
+#define	LPA_BASE_SHFT		(14)
+#define	TX_DATA_DELAY_MSK	(0xc0000)
+#define	TX_DATA_DELAY_SHFT	(18)
+#define	TX_CLK_DELAY_MSK	(0x300000)
+#define	TX_CLK_DELAY_SHFT	(20)
+#define	TX_CS_N_DELAY_MSK	(0xc00000)
+#define	TX_CS_N_DELAY_SHFT	(22)
+#define	TX_DATA_OE_DELAY_MSK	(0x3000000)
+#define	TX_DATA_OE_DELAY_SHFT	(24)
+
+/* Macros to help set/get fields in AHB_MSTR_CFG register */
+#define	HMEM_TYPE_START_MID_TRANS_MSK	(0x7)
+#define	HMEM_TYPE_START_MID_TRANS_SHFT	(0)
+#define	HMEM_TYPE_LAST_TRANS_MSK	(0x38)
+#define	HMEM_TYPE_LAST_TRANS_SHFT	(3)
+#define	USE_HMEMTYPE_LAST_ON_DESC_OR_CHAIN_MSK	(0xc0)
+#define	USE_HMEMTYPE_LAST_ON_DESC_OR_CHAIN_SHFT	(6)
+#define	HMEMTYPE_READ_TRANS_MSK		(0x700)
+#define	HMEMTYPE_READ_TRANS_SHFT	(8)
+#define	HSHARED		(BIT(11))
+#define	HINNERSHARED	(BIT(12))
+
+/* Macros to help set/get fields in MSTR_INT_EN/MSTR_INT_STATUS registers */
+#define	RESP_FIFO_UNDERRUN	(BIT(0))
+#define	RESP_FIFO_NOT_EMPTY	(BIT(1))
+#define	RESP_FIFO_RDY	(BIT(2))
+#define	HRESP_FROM_NOC_ERR	(BIT(3))
+#define	WR_FIFO_EMPTY	(BIT(9))
+#define	WR_FIFO_FULL	(BIT(10))
+#define	WR_FIFO_OVERRUN	(BIT(11))
+#define	TRANSACTION_DONE	(BIT(16))
+#define	ENABLE_ALL_IRQ	(RESP_FIFO_UNDERRUN | RESP_FIFO_RDY | \
+		HRESP_FROM_NOC_ERR | WR_FIFO_EMPTY | WR_FIFO_FULL |\
+		WR_FIFO_OVERRUN | TRANSACTION_DONE)
+
+/* Macros to help set/get fields in RD_FIFO_CONFIG register */
+#define	CONTINUOUS_MODE	(BIT(0))
+
+/* Macros to help set/get fields in RD_FIFO_RESET register */
+#define	RESET_FIFO	(BIT(0))
+
+/* Macros to help set/get fields in PIO_TRANSFER_CONFIG register */
+#define	TRANSFER_DIRECTION	(BIT(0))
+#define	MULTI_IO_MODE_MSK	(0xe)
+#define	MULTI_IO_MODE_SHFT	(1)
+#define	TRANSFER_FRAGMENT	(BIT(8))
+
+/* Macros to help set/get fields in PIO_TRANSFER_CONTROL register */
+#define	REQUEST_COUNT_MSK	(0xffff)
+
+/* Macros to help set/get fields in PIO_TRANSFER_STATUS register */
+#define	WR_FIFO_BYTES_MSK	(0xffff0000)
+#define	WR_FIFO_BYTES_SHFT	(16)
+
+/* Macros to help set/get fields in RD_FIFO_STATUS register */
+#define	FIFO_EMPTY	(BIT(11))
+#define	WR_CNTS_MSK	(0x7f0)
+#define	WR_CNTS_SHFT	(4)
+#define	RDY_64BYTE	(BIT(3))
+#define	RDY_32BYTE	(BIT(2))
+#define	RDY_16BYTE	(BIT(1))
+#define	FIFO_RDY	(BIT(0))
+
+/*
+ * The Mode transfer macros, the values are programmed to the HW registers
+ * when doing PIO mode of transfers.
+ */
+#define	SDR_1BIT	(1)
+#define	SDR_2BIT	(2)
+#define	SDR_4BIT	(3)
+#define	DDR_1BIT	(5)
+#define	DDR_2BIT	(6)
+#define	DDR_4BIT	(7)
+
+/* The Mode transfer macros when setting up DMA descriptors */
+#define	DMA_DESC_SINGLE_SPI	(1)
+#define	DMA_DESC_DUAL_SPI	(2)
+#define	DMA_DESC_QUAD_SPI	(3)
+
+enum qspi_dir {
+	QSPI_READ,
+	QSPI_WRITE,
+};
+
+struct qspi_xfer {
+	struct spi_transfer *xfer;
+	const void *tx_buf;
+	void *rx_buf;
+	u32 rem_bytes;
+	int mode;
+	enum qspi_dir dir;
+	bool is_last;
+};
+
+struct qcom_qspi {
+	void __iomem *base;
+	struct device *dev;
+	struct clk *core_clk;
+	struct clk *iface_clk;
+	int irq;
+	struct qspi_xfer xfer;
+	struct completion transfer_complete;
+};
+
+static void qcom_qspi_unset_clks(struct qcom_qspi *ctrl)
+{
+
+	if (!ctrl)
+		return;
+
+	clk_disable_unprepare(ctrl->core_clk);
+	clk_disable_unprepare(ctrl->iface_clk);
+
+}
+
+static int qcom_qspi_set_clks(struct qcom_qspi *ctrl, u32 core_speed)
+{
+	int ret;
+
+	ret = clk_set_rate(ctrl->core_clk, core_speed);
+	if (ret) {
+		dev_err(ctrl->dev, "%s: Failed to set core clk %d\n",
+							 __func__, ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(ctrl->core_clk);
+	if (ret) {
+		dev_err(ctrl->dev, "%s: Failed to prepare core clk %d\n",
+							 __func__, ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(ctrl->iface_clk);
+	if (ret) {
+		dev_err(ctrl->dev, "%s: Failed to prepare iface clk %d\n",
+							 __func__, ret);
+		goto exit_set_core_clk;
+	}
+
+	return ret;
+exit_set_core_clk:
+	clk_disable_unprepare(ctrl->iface_clk);
+	return ret;
+}
+
+static int qcom_qspi_pio_xfer_cfg(struct qcom_qspi *ctrl)
+{
+	u32 pio_xfer_cfg = 0;
+	struct qspi_xfer *xfer;
+
+	if (!ctrl)
+		return -ENODEV;
+
+	xfer = &ctrl->xfer;
+	pio_xfer_cfg = readl_relaxed((ctrl->base + PIO_XFER_CFG));
+	pio_xfer_cfg &= ~TRANSFER_DIRECTION;
+	pio_xfer_cfg |= xfer->dir;
+	if (xfer->is_last)
+		pio_xfer_cfg &= ~TRANSFER_FRAGMENT;
+	else
+		pio_xfer_cfg |= TRANSFER_FRAGMENT;
+	pio_xfer_cfg &= ~MULTI_IO_MODE_MSK;
+	pio_xfer_cfg |= (xfer->mode << MULTI_IO_MODE_SHFT);
+
+	writel_relaxed(pio_xfer_cfg, (ctrl->base + PIO_XFER_CFG));
+	return 0;
+}
+
+static int qcom_qspi_pio_xfer_ctrl(struct qcom_qspi *ctrl)
+{
+	u32 pio_xfer_ctrl;
+
+	if (!ctrl)
+		return -ENODEV;
+
+	pio_xfer_ctrl = readl_relaxed(ctrl->base + PIO_XFER_CTRL);
+	pio_xfer_ctrl &= ~REQUEST_COUNT_MSK;
+	pio_xfer_ctrl |= ctrl->xfer.rem_bytes;
+	writel_relaxed(pio_xfer_ctrl, ctrl->base + PIO_XFER_CTRL);
+	return 0;
+}
+
+static int qcom_qspi_pio_xfer(struct qcom_qspi *ctrl)
+{
+	int ret;
+	u32 mstr_cfg;
+	int int_status = ENABLE_ALL_IRQ;
+
+	mstr_cfg = readl_relaxed(ctrl->base + MSTR_CONFIG);
+	mstr_cfg &= ~DMA_ENABLE;
+	writel_relaxed(mstr_cfg, ctrl->base + MSTR_CONFIG);
+
+	ret = qcom_qspi_pio_xfer_cfg(ctrl);
+	if  (ret) {
+		dev_err(ctrl->dev, "%s: Failed pio_xfer_cfg %d\n",
+				__func__, ret);
+		return ret;
+	}
+
+	ret = qcom_qspi_pio_xfer_ctrl(ctrl);
+	if  (ret) {
+		dev_err(ctrl->dev, "%s: Failed pio_xfer_ctl %d\n",
+				__func__, ret);
+		return ret;
+	}
+	writel_relaxed(int_status, ctrl->base + MSTR_INT_EN);
+	writel_relaxed(int_status, ctrl->base + MSTR_INT_STATUS);
+	return 0;
+}
+
+static int wait_for_xfer(struct qcom_qspi *ctrl)
+{
+	if (!wait_for_completion_timeout(&ctrl->transfer_complete, HZ))
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static int process_opcode(const struct spi_mem_op *op, struct qcom_qspi *ctrl)
+{
+	int ret;
+
+	ctrl->xfer.dir = QSPI_WRITE;
+	ctrl->xfer.mode = op->cmd.buswidth;
+	if (op->addr.nbytes || op->data.nbytes)
+		ctrl->xfer.is_last = false;
+	else
+		ctrl->xfer.is_last = true;
+	ctrl->xfer.rem_bytes = 1;
+	ctrl->xfer.tx_buf = &op->cmd.opcode;
+	ret = qcom_qspi_pio_xfer(ctrl);
+	if (ret)
+		return ret;
+	ret = wait_for_xfer(ctrl);
+	return ret;
+}
+
+static int process_addr(const struct spi_mem_op *op, struct qcom_qspi *ctrl)
+{
+	int ret;
+
+	ctrl->xfer.dir = QSPI_WRITE;
+	ctrl->xfer.mode = op->addr.buswidth;
+	if (op->data.nbytes)
+		ctrl->xfer.is_last = false;
+	else
+		ctrl->xfer.is_last = true;
+	ctrl->xfer.rem_bytes = op->addr.nbytes;
+	ctrl->xfer.tx_buf = &op->addr.val;
+	ret = qcom_qspi_pio_xfer(ctrl);
+	if (ret)
+		return ret;
+	ret = wait_for_xfer(ctrl);
+	return ret;
+}
+
+static int process_dummy(const struct spi_mem_op *op, struct qcom_qspi *ctrl)
+{
+	int ret = 0;
+	unsigned char *buf;
+
+	ctrl->xfer.dir = QSPI_WRITE;
+	ctrl->xfer.mode = DDR_4BIT;
+	ctrl->xfer.is_last = false;
+	ctrl->xfer.rem_bytes = op->dummy.nbytes;
+	buf = (unsigned char *)kmalloc_array(op->dummy.nbytes,
+				sizeof(unsigned char), GFP_KERNEL);
+	memset(buf, 0xff, op->dummy.nbytes);
+	ctrl->xfer.tx_buf = buf;
+	ret = qcom_qspi_pio_xfer(ctrl);
+	if (ret)
+		goto exit_process_dummy;
+	ret = wait_for_xfer(ctrl);
+exit_process_dummy:
+	kfree(buf);
+	return ret;
+}
+
+static int process_data(const struct spi_mem_op *op, struct qcom_qspi *ctrl)
+{
+	int ret;
+
+	ctrl->xfer.dir = (op->data.dir == SPI_MEM_DATA_IN) ?
+					QSPI_READ : QSPI_WRITE;
+	ctrl->xfer.mode = op->data.buswidth;
+	ctrl->xfer.is_last = true;
+	ctrl->xfer.rem_bytes = op->data.nbytes;
+
+	if (ctrl->xfer.dir == QSPI_WRITE)
+		ctrl->xfer.tx_buf = op->data.buf.out;
+	else
+		ctrl->xfer.rx_buf = op->data.buf.in;
+	ret = qcom_qspi_pio_xfer(ctrl);
+	if (ret)
+		return ret;
+	ret = wait_for_xfer(ctrl);
+	return ret;
+}
+
+static int qcom_qspi_exec_mem_op(struct spi_mem *mem,
+				const struct spi_mem_op *op)
+{
+	struct qcom_qspi *ctrl = spi_master_get_devdata(mem->spi->master);
+	struct spi_device *dev = mem->spi;
+	int ret;
+
+	if (!ctrl)
+		return -ENODEV;
+
+	if (dev->max_speed_hz) {
+		ret = qcom_qspi_set_clks(ctrl, dev->max_speed_hz);
+		if (ret)
+			return ret;
+	}
+
+	ret = process_opcode(op, ctrl);
+	if (ret)
+		return ret;
+
+	if (op->addr.nbytes) {
+		ret = process_addr(op, ctrl);
+		if (ret)
+			return ret;
+	}
+
+	if (op->dummy.nbytes) {
+		ret = process_dummy(op, ctrl);
+		if (ret)
+			return ret;
+	}
+
+	if (op->data.nbytes) {
+		ret = process_data(op, ctrl);
+		if (ret)
+			return ret;
+	}
+	return ret;
+}
+
+static int qcom_qspi_setup(struct spi_device *spi)
+{
+	int ret;
+	u32 mstr_cfg = 0;
+	struct qcom_qspi *ctrl;
+	int tx_data_oe_delay = 1;
+	int tx_data_delay = 1;
+
+	ctrl = spi_master_get_devdata(spi->master);
+
+	ret = pm_runtime_get_sync(ctrl->dev);
+	if (ret < 0) {
+		dev_err(ctrl->dev, "%s: RPM resume failed %d",
+						__func__, ret);
+		return ret;
+	}
+
+	mstr_cfg = readl_relaxed(ctrl->base + MSTR_CONFIG);
+	mstr_cfg &= ~CHIP_SELECT_NUM;
+	if (spi->chip_select)
+		mstr_cfg |= CHIP_SELECT_NUM;
+
+	mstr_cfg = (mstr_cfg & ~SPI_MODE_MSK) | (spi->mode << SPI_MODE_SHFT);
+	mstr_cfg |= FB_CLK_EN | PIN_WPN | PIN_HOLDN | SBL_EN |
+				FULL_CYCLE_MODE | DMA_ENABLE;
+	mstr_cfg |= (mstr_cfg & ~TX_DATA_OE_DELAY_MSK) |
+				(tx_data_oe_delay << TX_DATA_OE_DELAY_SHFT);
+	mstr_cfg |= (mstr_cfg & ~TX_DATA_DELAY_MSK) |
+				(tx_data_delay << TX_DATA_DELAY_SHFT);
+
+	writel_relaxed(mstr_cfg, (ctrl->base + MSTR_CONFIG));
+	/*
+	 * Ensure that the configuration goes through by reading back
+	 * a register from the IO space.
+	 */
+	mb();
+	mstr_cfg = readl_relaxed((ctrl->base + MSTR_CONFIG));
+	pm_runtime_put_sync(ctrl->dev);
+	return ret;
+}
+
+static irqreturn_t pio_read(struct qcom_qspi *ctrl)
+{
+	u32 rd_fifo_status;
+	u32 wr_cnts;
+	bool fifo_rdy;
+	u32 rd_fifo;
+	int bytes_to_read;
+	int words_to_read;
+	int i;
+
+	if (!ctrl)
+		return IRQ_NONE;
+	rd_fifo_status = readl_relaxed(ctrl->base + RD_FIFO_STATUS);
+	wr_cnts = (rd_fifo_status & WR_CNTS_MSK) >> WR_CNTS_SHFT;
+	fifo_rdy = (rd_fifo_status & FIFO_RDY) ? true : false;
+
+	if (!fifo_rdy) {
+		dev_dbg(ctrl->dev, "%s: Spurious IRQ 0x%x",
+			__func__, rd_fifo_status);
+		return IRQ_NONE;
+	}
+
+	words_to_read = wr_cnts / QSPI_BYTES_PER_WORD;
+	bytes_to_read = wr_cnts % QSPI_BYTES_PER_WORD;
+	if (!ctrl->xfer.rx_buf)
+		return IRQ_NONE;
+
+	if (words_to_read) {
+		u32 *w_buf = ctrl->xfer.rx_buf;
+
+		ctrl->xfer.rem_bytes -=
+				(words_to_read * QSPI_BYTES_PER_WORD);
+		for (i = 0; i < words_to_read; i++) {
+			rd_fifo = readl_relaxed(ctrl->base + RD_FIFOk);
+			*w_buf++ = rd_fifo;
+		}
+		ctrl->xfer.rx_buf = w_buf;
+	}
+
+	if (bytes_to_read) {
+		u8 *byte_buf = ctrl->xfer.xfer->rx_buf;
+
+		rd_fifo = readl_relaxed(ctrl->base + RD_FIFOk);
+		ctrl->xfer.rem_bytes -= bytes_to_read;
+		for (i = 0; i < bytes_to_read; i++)
+			*byte_buf++ = (rd_fifo >> (i * BITS_PER_BYTE));
+		ctrl->xfer.xfer->rx_buf = byte_buf;
+
+	}
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t pio_write(struct qcom_qspi *ctrl)
+{
+	const int *word_buf;
+	const char *byte_buf;
+	const void *xfer_buf;
+	u32 wr_fifo_bytes;
+	int wr_size;
+
+	if (!ctrl)
+		return IRQ_NONE;
+	wr_fifo_bytes =
+	readl_relaxed(ctrl->base + PIO_XFER_STATUS) >> WR_FIFO_BYTES_SHFT;
+
+	xfer_buf = ctrl->xfer.tx_buf;
+
+	if (!wr_fifo_bytes || !xfer_buf) {
+		dev_err(ctrl->dev, "%s wr %d xfer_buf %p", __func__,
+						wr_fifo_bytes, xfer_buf);
+		return IRQ_NONE;
+
+	}
+	if (ctrl->xfer.rem_bytes < QSPI_BYTES_PER_WORD) {
+		byte_buf = xfer_buf;
+		wr_size = min(wr_fifo_bytes, ctrl->xfer.rem_bytes);
+		ctrl->xfer.rem_bytes -= wr_size;
+		while (wr_size--)
+			writel_relaxed(*byte_buf++,
+				(ctrl->base + PIO_DATAOUT_1B));
+		ctrl->xfer.tx_buf = byte_buf;
+	} else {
+		int rem_words =
+		ctrl->xfer.rem_bytes / QSPI_BYTES_PER_WORD;
+		int wr_fifo_words = wr_fifo_bytes / QSPI_BYTES_PER_WORD;
+
+		wr_size = min(rem_words, wr_fifo_words);
+		ctrl->xfer.rem_bytes -=
+				(wr_size * QSPI_BYTES_PER_WORD);
+		word_buf = xfer_buf;
+
+		while (wr_size--)
+			writel_relaxed(*word_buf++,
+				(ctrl->base + PIO_DATAOUT_4B));
+		ctrl->xfer.tx_buf = word_buf;
+
+	}
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t qcom_qspi_irq(int irq, void *dev_id)
+{
+	u32 int_status;
+	struct qcom_qspi *ctrl = dev_id;
+	irqreturn_t ret = IRQ_HANDLED;
+
+	int_status = readl_relaxed(ctrl->base + MSTR_INT_STATUS);
+	writel_relaxed(int_status, ctrl->base + MSTR_INT_STATUS);
+
+	if (int_status & WR_FIFO_EMPTY)
+		ret = pio_write(ctrl);
+
+	if (int_status & RESP_FIFO_RDY)
+		ret = pio_read(ctrl);
+
+	if (!ctrl->xfer.rem_bytes)
+		complete(&ctrl->transfer_complete);
+
+	return ret;
+}
+
+static bool qcom_qspi_supports_op(struct spi_mem *mem,
+				const struct spi_mem_op *op)
+{
+	return true;
+}
+
+static int qcom_qspi_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
+{
+	return 0;
+}
+
+static const struct spi_controller_mem_ops qcom_qspi_mem_ops = {
+	.exec_op = qcom_qspi_exec_mem_op,
+	.supports_op = qcom_qspi_supports_op,
+	.adjust_op_size = qcom_qspi_adjust_op_size,
+};
+
+static int qcom_qspi_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	struct device *dev;
+	struct resource *res;
+	struct spi_master *master;
+	struct qcom_qspi *ctrl;
+
+	dev = &pdev->dev;
+
+	master = spi_alloc_master(dev, sizeof(struct qcom_qspi));
+	if (!master) {
+		dev_err(dev, "%s: Failed to alloc spi master", __func__);
+		return -ENOMEM;
+	}
+
+	ctrl = spi_master_get_devdata(master);
+
+	ctrl->dev = dev;
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	ctrl->base = devm_ioremap(dev, res->start, resource_size(res));
+	if (IS_ERR(ctrl->base)) {
+		ret = PTR_ERR(ctrl->base);
+		dev_err(dev, "%s: Failed to get base addr %d", __func__, ret);
+		goto exit_probe_master_put;
+	}
+
+	ctrl->irq = platform_get_irq(pdev, 0);
+	if (ctrl->irq < 0) {
+		ret = PTR_ERR(ctrl->base);
+		dev_err(dev, "%s: Failed to get irq %d", __func__, ret);
+		goto exit_probe_master_put;
+	}
+
+	ret = devm_request_irq(dev, ctrl->irq, qcom_qspi_irq,
+			IRQF_TRIGGER_HIGH, dev_name(dev), ctrl);
+	if (ret) {
+		dev_err(dev, "%s: Failed to request irq %d", __func__, ret);
+		goto exit_probe_master_put;
+	}
+
+	ctrl->core_clk = devm_clk_get(dev, "core");
+	if (IS_ERR(ctrl->core_clk)) {
+		ret = PTR_ERR(ctrl->core_clk);
+		dev_err(dev, "%s: Failed to get core clk %d", __func__, ret);
+		goto exit_probe_master_put;
+	}
+
+	ctrl->iface_clk = devm_clk_get(dev, "iface");
+	if (IS_ERR(ctrl->iface_clk)) {
+		ret = PTR_ERR(ctrl->iface_clk);
+		dev_err(dev, "%s: Failed to get iface clk %d", __func__, ret);
+		goto exit_probe_master_put;
+	}
+
+	master->max_speed_hz = 300000000;
+	master->num_chipselect = QSPI_NUM_CS;
+	master->bus_num = pdev->id;
+	master->dev.of_node = pdev->dev.of_node;
+	master->mode_bits = SPI_MODE_0|SPI_MODE_3|SPI_TX_QUAD|SPI_RX_QUAD;
+	master->auto_runtime_pm = true;
+	master->setup = qcom_qspi_setup;
+	master->mem_ops = &qcom_qspi_mem_ops;
+
+	pm_runtime_enable(dev);
+	init_completion(&ctrl->transfer_complete);
+	platform_set_drvdata(pdev, master);
+	ret = devm_spi_register_master(dev, master);
+	if (!ret)
+		return 0;
+	pm_runtime_disable(&pdev->dev);
+exit_probe_master_put:
+	spi_master_put(master);
+	return ret;
+}
+
+static int qcom_qspi_remove(struct platform_device *pdev)
+{
+	struct spi_master *master = platform_get_drvdata(pdev);
+	struct qcom_qspi *qspi = spi_master_get_devdata(master);
+
+	spi_unregister_master(master);
+	qcom_qspi_unset_clks(qspi);
+	pm_runtime_put_noidle(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+	return 0;
+}
+
+static int qcom_qspi_resume_runtime(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct spi_master *mas = platform_get_drvdata(pdev);
+	struct qcom_qspi *ctrl;
+	int ret;
+
+	ctrl = spi_master_get_devdata(mas);
+	ret = qcom_qspi_set_clks(ctrl, mas->max_speed_hz);
+	if (ret)
+		dev_err(dev, "%s: Err failed to set clks %d", __func__, ret);
+	return ret;
+}
+
+static int qcom_qspi_suspend_runtime(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct spi_master *mas = platform_get_drvdata(pdev);
+	struct qcom_qspi *ctrl;
+
+	ctrl = spi_master_get_devdata(mas);
+	qcom_qspi_unset_clks(ctrl);
+	return 0;
+}
+
+static int qcom_qspi_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct spi_master *mas = platform_get_drvdata(pdev);
+
+	return spi_master_resume(mas);
+}
+
+static int qcom_qspi_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct spi_master *mas = platform_get_drvdata(pdev);
+	int ret;
+
+	if (pm_runtime_enabled(dev))
+		return -EBUSY;
+
+	ret = spi_master_suspend(mas);
+	if (ret)
+		return ret;
+	return 0;
+}
+
+static const struct dev_pm_ops qcom_qspi_dev_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(qcom_qspi_suspend, qcom_qspi_resume)
+	SET_RUNTIME_PM_OPS(qcom_qspi_suspend_runtime,
+			   qcom_qspi_resume_runtime,
+			   NULL)
+};
+
+static const struct of_device_id qcom_qspi_dt_match[] = {
+	{ .compatible = "qcom,qspi-v1", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, qcom_qspi_dt_match);
+
+static struct platform_driver qcom_qspi_driver = {
+	.driver = {
+		.name		= "qcom_qspi",
+		.owner		= THIS_MODULE,
+		.pm		= &qcom_qspi_dev_pm_ops,
+		.of_match_table = qcom_qspi_dt_match,
+	},
+	.probe = qcom_qspi_probe,
+	.remove = qcom_qspi_remove,
+};
+module_platform_driver(qcom_qspi_driver);
+
+MODULE_DESCRIPTION("SPI driver for QSPI cores");
+MODULE_LICENSE("GPL v2");
-- 
1.9.1

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ