mediatek: backport spi-mem based mtk spinor driver
This new driver has full quadspi and DMA support, providing way better reading performance. Signed-off-by: Chuanhong Guo <gch981213@gmail.com>
This commit is contained in:
		| @@ -336,6 +336,7 @@ CONFIG_SPI=y | ||||
| CONFIG_SPI_MASTER=y | ||||
| CONFIG_SPI_MEM=y | ||||
| # CONFIG_SPI_MT65XX is not set | ||||
| CONFIG_SPI_MTK_NOR=y | ||||
| CONFIG_SRCU=y | ||||
| CONFIG_STACKTRACE=y | ||||
| # CONFIG_SWAP is not set | ||||
|   | ||||
| @@ -0,0 +1,38 @@ | ||||
| From 671c3bf50ae498dc12aef6c70abe5cfa066b1348 Mon Sep 17 00:00:00 2001 | ||||
| From: Chuanhong Guo <gch981213@gmail.com> | ||||
| Date: Fri, 6 Mar 2020 16:50:49 +0800 | ||||
| Subject: [PATCH 1/2] spi: make spi-max-frequency optional | ||||
|  | ||||
| We only need a spi-max-frequency when we specifically request a | ||||
| spi frequency lower than the max speed of spi host. | ||||
| This property is already documented as optional property and current | ||||
| host drivers are implemented to operate at highest speed possible | ||||
| when spi->max_speed_hz is 0. | ||||
| This patch makes spi-max-frequency an optional property so that | ||||
| we could just omit it to use max controller speed. | ||||
|  | ||||
| Signed-off-by: Chuanhong Guo <gch981213@gmail.com> | ||||
| Link: https://lore.kernel.org/r/20200306085052.28258-2-gch981213@gmail.com | ||||
| Signed-off-by: Mark Brown <broonie@kernel.org> | ||||
| --- | ||||
|  drivers/spi/spi.c | 9 ++------- | ||||
|  1 file changed, 2 insertions(+), 7 deletions(-) | ||||
|  | ||||
| --- a/drivers/spi/spi.c | ||||
| +++ b/drivers/spi/spi.c | ||||
| @@ -1785,13 +1785,8 @@ static int of_spi_parse_dt(struct spi_co | ||||
|  		spi->mode |= SPI_CS_HIGH; | ||||
|   | ||||
|  	/* Device speed */ | ||||
| -	rc = of_property_read_u32(nc, "spi-max-frequency", &value); | ||||
| -	if (rc) { | ||||
| -		dev_err(&ctlr->dev, | ||||
| -			"%pOF has no valid 'spi-max-frequency' property (%d)\n", nc, rc); | ||||
| -		return rc; | ||||
| -	} | ||||
| -	spi->max_speed_hz = value; | ||||
| +	if (!of_property_read_u32(nc, "spi-max-frequency", &value)) | ||||
| +		spi->max_speed_hz = value; | ||||
|   | ||||
|  	return 0; | ||||
|  } | ||||
| @@ -0,0 +1,761 @@ | ||||
| From 881d1ee9fe81ff2be1b90809a07621be97404a57 Mon Sep 17 00:00:00 2001 | ||||
| From: Chuanhong Guo <gch981213@gmail.com> | ||||
| Date: Fri, 6 Mar 2020 16:50:50 +0800 | ||||
| Subject: [PATCH 2/2] spi: add support for mediatek spi-nor controller | ||||
|  | ||||
| This is a driver for mtk spi-nor controller using spi-mem interface. | ||||
| The same controller already has limited support provided by mtk-quadspi | ||||
| driver under spi-nor framework and this new driver is a replacement | ||||
| for the old one. | ||||
|  | ||||
| Comparing to the old driver, this driver has following advantages: | ||||
| 1. It can handle any full-duplex spi transfer up to 6 bytes, and | ||||
|    this is implemented using generic spi interface. | ||||
| 2. It take account into command opcode properly. The reading routine | ||||
|    in this controller can only use 0x03 or 0x0b as opcode on 1-1-1 | ||||
|    transfers, but old driver doesn't implement this properly. This | ||||
|    driver checks supported opcode explicitly and use (1) to perform | ||||
|    unmatched operations. | ||||
| 3. It properly handles SFDP reading. Old driver can't read SFDP | ||||
|    due to the bug mentioned in (2). | ||||
| 4. It can do 1-2-2 and 1-4-4 fast reading on spi-nor. These two ops | ||||
|    requires parsing SFDP, which isn't possible in old driver. And | ||||
|    the old driver is only flagged to support 1-1-2 mode. | ||||
| 5. It takes advantage of the DMA feature in this controller for | ||||
|    long reads and supports IRQ on DMA requests to free cpu cycles | ||||
|    from polling status registers on long DMA reading. It achieves | ||||
|    up to 17.5MB/s reading speed (1-4-4 mode) which is way faster | ||||
|    than the old one. IRQ is implemented as optional to maintain | ||||
|    backward compatibility. | ||||
|  | ||||
| Signed-off-by: Chuanhong Guo <gch981213@gmail.com> | ||||
| Link: https://lore.kernel.org/r/20200306085052.28258-3-gch981213@gmail.com | ||||
| Signed-off-by: Mark Brown <broonie@kernel.org> | ||||
| --- | ||||
|  drivers/spi/Kconfig       |  10 + | ||||
|  drivers/spi/Makefile      |   1 + | ||||
|  drivers/spi/spi-mtk-nor.c | 689 ++++++++++++++++++++++++++++++++++++++ | ||||
|  3 files changed, 700 insertions(+) | ||||
|  create mode 100644 drivers/spi/spi-mtk-nor.c | ||||
|  | ||||
| --- a/drivers/spi/Kconfig | ||||
| +++ b/drivers/spi/Kconfig | ||||
| @@ -433,6 +433,16 @@ config SPI_MT7621 | ||||
|  	help | ||||
|  	  This selects a driver for the MediaTek MT7621 SPI Controller. | ||||
|   | ||||
| +config SPI_MTK_NOR | ||||
| +	tristate "MediaTek SPI NOR controller" | ||||
| +	depends on ARCH_MEDIATEK || COMPILE_TEST | ||||
| +	help | ||||
| +	  This enables support for SPI NOR controller found on MediaTek | ||||
| +	  ARM SoCs. This is a controller specifically for SPI-NOR flash. | ||||
| +	  It can perform generic SPI transfers up to 6 bytes via generic | ||||
| +	  SPI interface as well as several SPI-NOR specific instructions | ||||
| +	  via SPI MEM interface. | ||||
| + | ||||
|  config SPI_NPCM_FIU | ||||
|  	tristate "Nuvoton NPCM FLASH Interface Unit" | ||||
|  	depends on ARCH_NPCM || COMPILE_TEST | ||||
| --- a/drivers/spi/Makefile | ||||
| +++ b/drivers/spi/Makefile | ||||
| @@ -61,6 +61,7 @@ obj-$(CONFIG_SPI_MPC52xx_PSC)		+= spi-mp | ||||
|  obj-$(CONFIG_SPI_MPC52xx)		+= spi-mpc52xx.o | ||||
|  obj-$(CONFIG_SPI_MT65XX)                += spi-mt65xx.o | ||||
|  obj-$(CONFIG_SPI_MT7621)		+= spi-mt7621.o | ||||
| +obj-$(CONFIG_SPI_MTK_NOR)		+= spi-mtk-nor.o | ||||
|  obj-$(CONFIG_SPI_MXIC)			+= spi-mxic.o | ||||
|  obj-$(CONFIG_SPI_MXS)			+= spi-mxs.o | ||||
|  obj-$(CONFIG_SPI_NPCM_FIU)		+= spi-npcm-fiu.o | ||||
| --- /dev/null | ||||
| +++ b/drivers/spi/spi-mtk-nor.c | ||||
| @@ -0,0 +1,689 @@ | ||||
| +// SPDX-License-Identifier: GPL-2.0 | ||||
| +// | ||||
| +// Mediatek SPI NOR controller driver | ||||
| +// | ||||
| +// Copyright (C) 2020 Chuanhong Guo <gch981213@gmail.com> | ||||
| + | ||||
| +#include <linux/bits.h> | ||||
| +#include <linux/clk.h> | ||||
| +#include <linux/completion.h> | ||||
| +#include <linux/dma-mapping.h> | ||||
| +#include <linux/interrupt.h> | ||||
| +#include <linux/io.h> | ||||
| +#include <linux/iopoll.h> | ||||
| +#include <linux/kernel.h> | ||||
| +#include <linux/module.h> | ||||
| +#include <linux/of_device.h> | ||||
| +#include <linux/spi/spi.h> | ||||
| +#include <linux/spi/spi-mem.h> | ||||
| +#include <linux/string.h> | ||||
| + | ||||
| +#define DRIVER_NAME "mtk-spi-nor" | ||||
| + | ||||
| +#define MTK_NOR_REG_CMD			0x00 | ||||
| +#define MTK_NOR_CMD_WRITE		BIT(4) | ||||
| +#define MTK_NOR_CMD_PROGRAM		BIT(2) | ||||
| +#define MTK_NOR_CMD_READ		BIT(0) | ||||
| +#define MTK_NOR_CMD_MASK		GENMASK(5, 0) | ||||
| + | ||||
| +#define MTK_NOR_REG_PRG_CNT		0x04 | ||||
| +#define MTK_NOR_REG_RDATA		0x0c | ||||
| + | ||||
| +#define MTK_NOR_REG_RADR0		0x10 | ||||
| +#define MTK_NOR_REG_RADR(n)		(MTK_NOR_REG_RADR0 + 4 * (n)) | ||||
| +#define MTK_NOR_REG_RADR3		0xc8 | ||||
| + | ||||
| +#define MTK_NOR_REG_WDATA		0x1c | ||||
| + | ||||
| +#define MTK_NOR_REG_PRGDATA0		0x20 | ||||
| +#define MTK_NOR_REG_PRGDATA(n)		(MTK_NOR_REG_PRGDATA0 + 4 * (n)) | ||||
| +#define MTK_NOR_REG_PRGDATA_MAX		5 | ||||
| + | ||||
| +#define MTK_NOR_REG_SHIFT0		0x38 | ||||
| +#define MTK_NOR_REG_SHIFT(n)		(MTK_NOR_REG_SHIFT0 + 4 * (n)) | ||||
| +#define MTK_NOR_REG_SHIFT_MAX		9 | ||||
| + | ||||
| +#define MTK_NOR_REG_CFG1		0x60 | ||||
| +#define MTK_NOR_FAST_READ		BIT(0) | ||||
| + | ||||
| +#define MTK_NOR_REG_CFG2		0x64 | ||||
| +#define MTK_NOR_WR_CUSTOM_OP_EN		BIT(4) | ||||
| +#define MTK_NOR_WR_BUF_EN		BIT(0) | ||||
| + | ||||
| +#define MTK_NOR_REG_PP_DATA		0x98 | ||||
| + | ||||
| +#define MTK_NOR_REG_IRQ_STAT		0xa8 | ||||
| +#define MTK_NOR_REG_IRQ_EN		0xac | ||||
| +#define MTK_NOR_IRQ_DMA			BIT(7) | ||||
| +#define MTK_NOR_IRQ_MASK		GENMASK(7, 0) | ||||
| + | ||||
| +#define MTK_NOR_REG_CFG3		0xb4 | ||||
| +#define MTK_NOR_DISABLE_WREN		BIT(7) | ||||
| +#define MTK_NOR_DISABLE_SR_POLL		BIT(5) | ||||
| + | ||||
| +#define MTK_NOR_REG_WP			0xc4 | ||||
| +#define MTK_NOR_ENABLE_SF_CMD		0x30 | ||||
| + | ||||
| +#define MTK_NOR_REG_BUSCFG		0xcc | ||||
| +#define MTK_NOR_4B_ADDR			BIT(4) | ||||
| +#define MTK_NOR_QUAD_ADDR		BIT(3) | ||||
| +#define MTK_NOR_QUAD_READ		BIT(2) | ||||
| +#define MTK_NOR_DUAL_ADDR		BIT(1) | ||||
| +#define MTK_NOR_DUAL_READ		BIT(0) | ||||
| +#define MTK_NOR_BUS_MODE_MASK		GENMASK(4, 0) | ||||
| + | ||||
| +#define MTK_NOR_REG_DMA_CTL		0x718 | ||||
| +#define MTK_NOR_DMA_START		BIT(0) | ||||
| + | ||||
| +#define MTK_NOR_REG_DMA_FADR		0x71c | ||||
| +#define MTK_NOR_REG_DMA_DADR		0x720 | ||||
| +#define MTK_NOR_REG_DMA_END_DADR	0x724 | ||||
| + | ||||
| +#define MTK_NOR_PRG_MAX_SIZE		6 | ||||
| +// Reading DMA src/dst addresses have to be 16-byte aligned | ||||
| +#define MTK_NOR_DMA_ALIGN		16 | ||||
| +#define MTK_NOR_DMA_ALIGN_MASK		(MTK_NOR_DMA_ALIGN - 1) | ||||
| +// and we allocate a bounce buffer if destination address isn't aligned. | ||||
| +#define MTK_NOR_BOUNCE_BUF_SIZE		PAGE_SIZE | ||||
| + | ||||
| +// Buffered page program can do one 128-byte transfer | ||||
| +#define MTK_NOR_PP_SIZE			128 | ||||
| + | ||||
| +#define CLK_TO_US(sp, clkcnt)		((clkcnt) * 1000000 / sp->spi_freq) | ||||
| + | ||||
| +struct mtk_nor { | ||||
| +	struct spi_controller *ctlr; | ||||
| +	struct device *dev; | ||||
| +	void __iomem *base; | ||||
| +	u8 *buffer; | ||||
| +	struct clk *spi_clk; | ||||
| +	struct clk *ctlr_clk; | ||||
| +	unsigned int spi_freq; | ||||
| +	bool wbuf_en; | ||||
| +	bool has_irq; | ||||
| +	struct completion op_done; | ||||
| +}; | ||||
| + | ||||
| +static inline void mtk_nor_rmw(struct mtk_nor *sp, u32 reg, u32 set, u32 clr) | ||||
| +{ | ||||
| +	u32 val = readl(sp->base + reg); | ||||
| + | ||||
| +	val &= ~clr; | ||||
| +	val |= set; | ||||
| +	writel(val, sp->base + reg); | ||||
| +} | ||||
| + | ||||
| +static inline int mtk_nor_cmd_exec(struct mtk_nor *sp, u32 cmd, ulong clk) | ||||
| +{ | ||||
| +	ulong delay = CLK_TO_US(sp, clk); | ||||
| +	u32 reg; | ||||
| +	int ret; | ||||
| + | ||||
| +	writel(cmd, sp->base + MTK_NOR_REG_CMD); | ||||
| +	ret = readl_poll_timeout(sp->base + MTK_NOR_REG_CMD, reg, !(reg & cmd), | ||||
| +				 delay / 3, (delay + 1) * 200); | ||||
| +	if (ret < 0) | ||||
| +		dev_err(sp->dev, "command %u timeout.\n", cmd); | ||||
| +	return ret; | ||||
| +} | ||||
| + | ||||
| +static void mtk_nor_set_addr(struct mtk_nor *sp, const struct spi_mem_op *op) | ||||
| +{ | ||||
| +	u32 addr = op->addr.val; | ||||
| +	int i; | ||||
| + | ||||
| +	for (i = 0; i < 3; i++) { | ||||
| +		writeb(addr & 0xff, sp->base + MTK_NOR_REG_RADR(i)); | ||||
| +		addr >>= 8; | ||||
| +	} | ||||
| +	if (op->addr.nbytes == 4) { | ||||
| +		writeb(addr & 0xff, sp->base + MTK_NOR_REG_RADR3); | ||||
| +		mtk_nor_rmw(sp, MTK_NOR_REG_BUSCFG, MTK_NOR_4B_ADDR, 0); | ||||
| +	} else { | ||||
| +		mtk_nor_rmw(sp, MTK_NOR_REG_BUSCFG, 0, MTK_NOR_4B_ADDR); | ||||
| +	} | ||||
| +} | ||||
| + | ||||
| +static bool mtk_nor_match_read(const struct spi_mem_op *op) | ||||
| +{ | ||||
| +	int dummy = 0; | ||||
| + | ||||
| +	if (op->dummy.buswidth) | ||||
| +		dummy = op->dummy.nbytes * BITS_PER_BYTE / op->dummy.buswidth; | ||||
| + | ||||
| +	if ((op->data.buswidth == 2) || (op->data.buswidth == 4)) { | ||||
| +		if (op->addr.buswidth == 1) | ||||
| +			return dummy == 8; | ||||
| +		else if (op->addr.buswidth == 2) | ||||
| +			return dummy == 4; | ||||
| +		else if (op->addr.buswidth == 4) | ||||
| +			return dummy == 6; | ||||
| +	} else if ((op->addr.buswidth == 1) && (op->data.buswidth == 1)) { | ||||
| +		if (op->cmd.opcode == 0x03) | ||||
| +			return dummy == 0; | ||||
| +		else if (op->cmd.opcode == 0x0b) | ||||
| +			return dummy == 8; | ||||
| +	} | ||||
| +	return false; | ||||
| +} | ||||
| + | ||||
| +static int mtk_nor_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op) | ||||
| +{ | ||||
| +	size_t len; | ||||
| + | ||||
| +	if (!op->data.nbytes) | ||||
| +		return 0; | ||||
| + | ||||
| +	if ((op->addr.nbytes == 3) || (op->addr.nbytes == 4)) { | ||||
| +		if ((op->data.dir == SPI_MEM_DATA_IN) && | ||||
| +		    mtk_nor_match_read(op)) { | ||||
| +			if ((op->addr.val & MTK_NOR_DMA_ALIGN_MASK) || | ||||
| +			    (op->data.nbytes < MTK_NOR_DMA_ALIGN)) | ||||
| +				op->data.nbytes = 1; | ||||
| +			else if (!((ulong)(op->data.buf.in) & | ||||
| +				   MTK_NOR_DMA_ALIGN_MASK)) | ||||
| +				op->data.nbytes &= ~MTK_NOR_DMA_ALIGN_MASK; | ||||
| +			else if (op->data.nbytes > MTK_NOR_BOUNCE_BUF_SIZE) | ||||
| +				op->data.nbytes = MTK_NOR_BOUNCE_BUF_SIZE; | ||||
| +			return 0; | ||||
| +		} else if (op->data.dir == SPI_MEM_DATA_OUT) { | ||||
| +			if (op->data.nbytes >= MTK_NOR_PP_SIZE) | ||||
| +				op->data.nbytes = MTK_NOR_PP_SIZE; | ||||
| +			else | ||||
| +				op->data.nbytes = 1; | ||||
| +			return 0; | ||||
| +		} | ||||
| +	} | ||||
| + | ||||
| +	len = MTK_NOR_PRG_MAX_SIZE - sizeof(op->cmd.opcode) - op->addr.nbytes - | ||||
| +	      op->dummy.nbytes; | ||||
| +	if (op->data.nbytes > len) | ||||
| +		op->data.nbytes = len; | ||||
| + | ||||
| +	return 0; | ||||
| +} | ||||
| + | ||||
| +static bool mtk_nor_supports_op(struct spi_mem *mem, | ||||
| +				const struct spi_mem_op *op) | ||||
| +{ | ||||
| +	size_t len; | ||||
| + | ||||
| +	if (op->cmd.buswidth != 1) | ||||
| +		return false; | ||||
| + | ||||
| +	if ((op->addr.nbytes == 3) || (op->addr.nbytes == 4)) { | ||||
| +		if ((op->data.dir == SPI_MEM_DATA_IN) && mtk_nor_match_read(op)) | ||||
| +			return true; | ||||
| +		else if (op->data.dir == SPI_MEM_DATA_OUT) | ||||
| +			return (op->addr.buswidth == 1) && | ||||
| +			       (op->dummy.buswidth == 0) && | ||||
| +			       (op->data.buswidth == 1); | ||||
| +	} | ||||
| +	len = sizeof(op->cmd.opcode) + op->addr.nbytes + op->dummy.nbytes; | ||||
| +	if ((len > MTK_NOR_PRG_MAX_SIZE) || | ||||
| +	    ((op->data.nbytes) && (len == MTK_NOR_PRG_MAX_SIZE))) | ||||
| +		return false; | ||||
| +	return true; | ||||
| +} | ||||
| + | ||||
| +static void mtk_nor_setup_bus(struct mtk_nor *sp, const struct spi_mem_op *op) | ||||
| +{ | ||||
| +	u32 reg = 0; | ||||
| + | ||||
| +	if (op->addr.nbytes == 4) | ||||
| +		reg |= MTK_NOR_4B_ADDR; | ||||
| + | ||||
| +	if (op->data.buswidth == 4) { | ||||
| +		reg |= MTK_NOR_QUAD_READ; | ||||
| +		writeb(op->cmd.opcode, sp->base + MTK_NOR_REG_PRGDATA(4)); | ||||
| +		if (op->addr.buswidth == 4) | ||||
| +			reg |= MTK_NOR_QUAD_ADDR; | ||||
| +	} else if (op->data.buswidth == 2) { | ||||
| +		reg |= MTK_NOR_DUAL_READ; | ||||
| +		writeb(op->cmd.opcode, sp->base + MTK_NOR_REG_PRGDATA(3)); | ||||
| +		if (op->addr.buswidth == 2) | ||||
| +			reg |= MTK_NOR_DUAL_ADDR; | ||||
| +	} else { | ||||
| +		if (op->cmd.opcode == 0x0b) | ||||
| +			mtk_nor_rmw(sp, MTK_NOR_REG_CFG1, MTK_NOR_FAST_READ, 0); | ||||
| +		else | ||||
| +			mtk_nor_rmw(sp, MTK_NOR_REG_CFG1, 0, MTK_NOR_FAST_READ); | ||||
| +	} | ||||
| +	mtk_nor_rmw(sp, MTK_NOR_REG_BUSCFG, reg, MTK_NOR_BUS_MODE_MASK); | ||||
| +} | ||||
| + | ||||
| +static int mtk_nor_read_dma(struct mtk_nor *sp, u32 from, unsigned int length, | ||||
| +			    u8 *buffer) | ||||
| +{ | ||||
| +	int ret = 0; | ||||
| +	ulong delay; | ||||
| +	u32 reg; | ||||
| +	dma_addr_t dma_addr; | ||||
| + | ||||
| +	dma_addr = dma_map_single(sp->dev, buffer, length, DMA_FROM_DEVICE); | ||||
| +	if (dma_mapping_error(sp->dev, dma_addr)) { | ||||
| +		dev_err(sp->dev, "failed to map dma buffer.\n"); | ||||
| +		return -EINVAL; | ||||
| +	} | ||||
| + | ||||
| +	writel(from, sp->base + MTK_NOR_REG_DMA_FADR); | ||||
| +	writel(dma_addr, sp->base + MTK_NOR_REG_DMA_DADR); | ||||
| +	writel(dma_addr + length, sp->base + MTK_NOR_REG_DMA_END_DADR); | ||||
| + | ||||
| +	if (sp->has_irq) { | ||||
| +		reinit_completion(&sp->op_done); | ||||
| +		mtk_nor_rmw(sp, MTK_NOR_REG_IRQ_EN, MTK_NOR_IRQ_DMA, 0); | ||||
| +	} | ||||
| + | ||||
| +	mtk_nor_rmw(sp, MTK_NOR_REG_DMA_CTL, MTK_NOR_DMA_START, 0); | ||||
| + | ||||
| +	delay = CLK_TO_US(sp, (length + 5) * BITS_PER_BYTE); | ||||
| + | ||||
| +	if (sp->has_irq) { | ||||
| +		if (!wait_for_completion_timeout(&sp->op_done, | ||||
| +						 (delay + 1) * 100)) | ||||
| +			ret = -ETIMEDOUT; | ||||
| +	} else { | ||||
| +		ret = readl_poll_timeout(sp->base + MTK_NOR_REG_DMA_CTL, reg, | ||||
| +					 !(reg & MTK_NOR_DMA_START), delay / 3, | ||||
| +					 (delay + 1) * 100); | ||||
| +	} | ||||
| + | ||||
| +	dma_unmap_single(sp->dev, dma_addr, length, DMA_FROM_DEVICE); | ||||
| +	if (ret < 0) | ||||
| +		dev_err(sp->dev, "dma read timeout.\n"); | ||||
| + | ||||
| +	return ret; | ||||
| +} | ||||
| + | ||||
| +static int mtk_nor_read_bounce(struct mtk_nor *sp, u32 from, | ||||
| +			       unsigned int length, u8 *buffer) | ||||
| +{ | ||||
| +	unsigned int rdlen; | ||||
| +	int ret; | ||||
| + | ||||
| +	if (length & MTK_NOR_DMA_ALIGN_MASK) | ||||
| +		rdlen = (length + MTK_NOR_DMA_ALIGN) & ~MTK_NOR_DMA_ALIGN_MASK; | ||||
| +	else | ||||
| +		rdlen = length; | ||||
| + | ||||
| +	ret = mtk_nor_read_dma(sp, from, rdlen, sp->buffer); | ||||
| +	if (ret) | ||||
| +		return ret; | ||||
| + | ||||
| +	memcpy(buffer, sp->buffer, length); | ||||
| +	return 0; | ||||
| +} | ||||
| + | ||||
| +static int mtk_nor_read_pio(struct mtk_nor *sp, const struct spi_mem_op *op) | ||||
| +{ | ||||
| +	u8 *buf = op->data.buf.in; | ||||
| +	int ret; | ||||
| + | ||||
| +	ret = mtk_nor_cmd_exec(sp, MTK_NOR_CMD_READ, 6 * BITS_PER_BYTE); | ||||
| +	if (!ret) | ||||
| +		buf[0] = readb(sp->base + MTK_NOR_REG_RDATA); | ||||
| +	return ret; | ||||
| +} | ||||
| + | ||||
| +static int mtk_nor_write_buffer_enable(struct mtk_nor *sp) | ||||
| +{ | ||||
| +	int ret; | ||||
| +	u32 val; | ||||
| + | ||||
| +	if (sp->wbuf_en) | ||||
| +		return 0; | ||||
| + | ||||
| +	val = readl(sp->base + MTK_NOR_REG_CFG2); | ||||
| +	writel(val | MTK_NOR_WR_BUF_EN, sp->base + MTK_NOR_REG_CFG2); | ||||
| +	ret = readl_poll_timeout(sp->base + MTK_NOR_REG_CFG2, val, | ||||
| +				 val & MTK_NOR_WR_BUF_EN, 0, 10000); | ||||
| +	if (!ret) | ||||
| +		sp->wbuf_en = true; | ||||
| +	return ret; | ||||
| +} | ||||
| + | ||||
| +static int mtk_nor_write_buffer_disable(struct mtk_nor *sp) | ||||
| +{ | ||||
| +	int ret; | ||||
| +	u32 val; | ||||
| + | ||||
| +	if (!sp->wbuf_en) | ||||
| +		return 0; | ||||
| +	val = readl(sp->base + MTK_NOR_REG_CFG2); | ||||
| +	writel(val & ~MTK_NOR_WR_BUF_EN, sp->base + MTK_NOR_REG_CFG2); | ||||
| +	ret = readl_poll_timeout(sp->base + MTK_NOR_REG_CFG2, val, | ||||
| +				 !(val & MTK_NOR_WR_BUF_EN), 0, 10000); | ||||
| +	if (!ret) | ||||
| +		sp->wbuf_en = false; | ||||
| +	return ret; | ||||
| +} | ||||
| + | ||||
| +static int mtk_nor_pp_buffered(struct mtk_nor *sp, const struct spi_mem_op *op) | ||||
| +{ | ||||
| +	const u8 *buf = op->data.buf.out; | ||||
| +	u32 val; | ||||
| +	int ret, i; | ||||
| + | ||||
| +	ret = mtk_nor_write_buffer_enable(sp); | ||||
| +	if (ret < 0) | ||||
| +		return ret; | ||||
| + | ||||
| +	for (i = 0; i < op->data.nbytes; i += 4) { | ||||
| +		val = buf[i + 3] << 24 | buf[i + 2] << 16 | buf[i + 1] << 8 | | ||||
| +		      buf[i]; | ||||
| +		writel(val, sp->base + MTK_NOR_REG_PP_DATA); | ||||
| +	} | ||||
| +	return mtk_nor_cmd_exec(sp, MTK_NOR_CMD_WRITE, | ||||
| +				(op->data.nbytes + 5) * BITS_PER_BYTE); | ||||
| +} | ||||
| + | ||||
| +static int mtk_nor_pp_unbuffered(struct mtk_nor *sp, | ||||
| +				 const struct spi_mem_op *op) | ||||
| +{ | ||||
| +	const u8 *buf = op->data.buf.out; | ||||
| +	int ret; | ||||
| + | ||||
| +	ret = mtk_nor_write_buffer_disable(sp); | ||||
| +	if (ret < 0) | ||||
| +		return ret; | ||||
| +	writeb(buf[0], sp->base + MTK_NOR_REG_WDATA); | ||||
| +	return mtk_nor_cmd_exec(sp, MTK_NOR_CMD_WRITE, 6 * BITS_PER_BYTE); | ||||
| +} | ||||
| + | ||||
| +int mtk_nor_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) | ||||
| +{ | ||||
| +	struct mtk_nor *sp = spi_controller_get_devdata(mem->spi->master); | ||||
| +	int ret; | ||||
| + | ||||
| +	if ((op->data.nbytes == 0) || | ||||
| +	    ((op->addr.nbytes != 3) && (op->addr.nbytes != 4))) | ||||
| +		return -ENOTSUPP; | ||||
| + | ||||
| +	if (op->data.dir == SPI_MEM_DATA_OUT) { | ||||
| +		mtk_nor_set_addr(sp, op); | ||||
| +		writeb(op->cmd.opcode, sp->base + MTK_NOR_REG_PRGDATA0); | ||||
| +		if (op->data.nbytes == MTK_NOR_PP_SIZE) | ||||
| +			return mtk_nor_pp_buffered(sp, op); | ||||
| +		return mtk_nor_pp_unbuffered(sp, op); | ||||
| +	} | ||||
| + | ||||
| +	if ((op->data.dir == SPI_MEM_DATA_IN) && mtk_nor_match_read(op)) { | ||||
| +		ret = mtk_nor_write_buffer_disable(sp); | ||||
| +		if (ret < 0) | ||||
| +			return ret; | ||||
| +		mtk_nor_setup_bus(sp, op); | ||||
| +		if (op->data.nbytes == 1) { | ||||
| +			mtk_nor_set_addr(sp, op); | ||||
| +			return mtk_nor_read_pio(sp, op); | ||||
| +		} else if (((ulong)(op->data.buf.in) & | ||||
| +			    MTK_NOR_DMA_ALIGN_MASK)) { | ||||
| +			return mtk_nor_read_bounce(sp, op->addr.val, | ||||
| +						   op->data.nbytes, | ||||
| +						   op->data.buf.in); | ||||
| +		} else { | ||||
| +			return mtk_nor_read_dma(sp, op->addr.val, | ||||
| +						op->data.nbytes, | ||||
| +						op->data.buf.in); | ||||
| +		} | ||||
| +	} | ||||
| + | ||||
| +	return -ENOTSUPP; | ||||
| +} | ||||
| + | ||||
| +static int mtk_nor_setup(struct spi_device *spi) | ||||
| +{ | ||||
| +	struct mtk_nor *sp = spi_controller_get_devdata(spi->master); | ||||
| + | ||||
| +	if (spi->max_speed_hz && (spi->max_speed_hz < sp->spi_freq)) { | ||||
| +		dev_err(&spi->dev, "spi clock should be %u Hz.\n", | ||||
| +			sp->spi_freq); | ||||
| +		return -EINVAL; | ||||
| +	} | ||||
| +	spi->max_speed_hz = sp->spi_freq; | ||||
| + | ||||
| +	return 0; | ||||
| +} | ||||
| + | ||||
| +static int mtk_nor_transfer_one_message(struct spi_controller *master, | ||||
| +					struct spi_message *m) | ||||
| +{ | ||||
| +	struct mtk_nor *sp = spi_controller_get_devdata(master); | ||||
| +	struct spi_transfer *t = NULL; | ||||
| +	unsigned long trx_len = 0; | ||||
| +	int stat = 0; | ||||
| +	int reg_offset = MTK_NOR_REG_PRGDATA_MAX; | ||||
| +	void __iomem *reg; | ||||
| +	const u8 *txbuf; | ||||
| +	u8 *rxbuf; | ||||
| +	int i; | ||||
| + | ||||
| +	list_for_each_entry(t, &m->transfers, transfer_list) { | ||||
| +		txbuf = t->tx_buf; | ||||
| +		for (i = 0; i < t->len; i++, reg_offset--) { | ||||
| +			reg = sp->base + MTK_NOR_REG_PRGDATA(reg_offset); | ||||
| +			if (txbuf) | ||||
| +				writeb(txbuf[i], reg); | ||||
| +			else | ||||
| +				writeb(0, reg); | ||||
| +		} | ||||
| +		trx_len += t->len; | ||||
| +	} | ||||
| + | ||||
| +	writel(trx_len * BITS_PER_BYTE, sp->base + MTK_NOR_REG_PRG_CNT); | ||||
| + | ||||
| +	stat = mtk_nor_cmd_exec(sp, MTK_NOR_CMD_PROGRAM, | ||||
| +				trx_len * BITS_PER_BYTE); | ||||
| +	if (stat < 0) | ||||
| +		goto msg_done; | ||||
| + | ||||
| +	reg_offset = trx_len - 1; | ||||
| +	list_for_each_entry(t, &m->transfers, transfer_list) { | ||||
| +		rxbuf = t->rx_buf; | ||||
| +		for (i = 0; i < t->len; i++, reg_offset--) { | ||||
| +			reg = sp->base + MTK_NOR_REG_SHIFT(reg_offset); | ||||
| +			if (rxbuf) | ||||
| +				rxbuf[i] = readb(reg); | ||||
| +		} | ||||
| +	} | ||||
| + | ||||
| +	m->actual_length = trx_len; | ||||
| +msg_done: | ||||
| +	m->status = stat; | ||||
| +	spi_finalize_current_message(master); | ||||
| + | ||||
| +	return 0; | ||||
| +} | ||||
| + | ||||
| +static void mtk_nor_disable_clk(struct mtk_nor *sp) | ||||
| +{ | ||||
| +	clk_disable_unprepare(sp->spi_clk); | ||||
| +	clk_disable_unprepare(sp->ctlr_clk); | ||||
| +} | ||||
| + | ||||
| +static int mtk_nor_enable_clk(struct mtk_nor *sp) | ||||
| +{ | ||||
| +	int ret; | ||||
| + | ||||
| +	ret = clk_prepare_enable(sp->spi_clk); | ||||
| +	if (ret) | ||||
| +		return ret; | ||||
| + | ||||
| +	ret = clk_prepare_enable(sp->ctlr_clk); | ||||
| +	if (ret) { | ||||
| +		clk_disable_unprepare(sp->spi_clk); | ||||
| +		return ret; | ||||
| +	} | ||||
| + | ||||
| +	return 0; | ||||
| +} | ||||
| + | ||||
| +static int mtk_nor_init(struct mtk_nor *sp) | ||||
| +{ | ||||
| +	int ret; | ||||
| + | ||||
| +	ret = mtk_nor_enable_clk(sp); | ||||
| +	if (ret) | ||||
| +		return ret; | ||||
| + | ||||
| +	sp->spi_freq = clk_get_rate(sp->spi_clk); | ||||
| + | ||||
| +	writel(MTK_NOR_ENABLE_SF_CMD, sp->base + MTK_NOR_REG_WP); | ||||
| +	mtk_nor_rmw(sp, MTK_NOR_REG_CFG2, MTK_NOR_WR_CUSTOM_OP_EN, 0); | ||||
| +	mtk_nor_rmw(sp, MTK_NOR_REG_CFG3, | ||||
| +		    MTK_NOR_DISABLE_WREN | MTK_NOR_DISABLE_SR_POLL, 0); | ||||
| + | ||||
| +	return ret; | ||||
| +} | ||||
| + | ||||
| +static irqreturn_t mtk_nor_irq_handler(int irq, void *data) | ||||
| +{ | ||||
| +	struct mtk_nor *sp = data; | ||||
| +	u32 irq_status, irq_enabled; | ||||
| + | ||||
| +	irq_status = readl(sp->base + MTK_NOR_REG_IRQ_STAT); | ||||
| +	irq_enabled = readl(sp->base + MTK_NOR_REG_IRQ_EN); | ||||
| +	// write status back to clear interrupt | ||||
| +	writel(irq_status, sp->base + MTK_NOR_REG_IRQ_STAT); | ||||
| + | ||||
| +	if (!(irq_status & irq_enabled)) | ||||
| +		return IRQ_NONE; | ||||
| + | ||||
| +	if (irq_status & MTK_NOR_IRQ_DMA) { | ||||
| +		complete(&sp->op_done); | ||||
| +		writel(0, sp->base + MTK_NOR_REG_IRQ_EN); | ||||
| +	} | ||||
| + | ||||
| +	return IRQ_HANDLED; | ||||
| +} | ||||
| + | ||||
| +static size_t mtk_max_msg_size(struct spi_device *spi) | ||||
| +{ | ||||
| +	return MTK_NOR_PRG_MAX_SIZE; | ||||
| +} | ||||
| + | ||||
| +static const struct spi_controller_mem_ops mtk_nor_mem_ops = { | ||||
| +	.adjust_op_size = mtk_nor_adjust_op_size, | ||||
| +	.supports_op = mtk_nor_supports_op, | ||||
| +	.exec_op = mtk_nor_exec_op | ||||
| +}; | ||||
| + | ||||
| +static const struct of_device_id mtk_nor_match[] = { | ||||
| +	{ .compatible = "mediatek,mt8173-nor" }, | ||||
| +	{ /* sentinel */ } | ||||
| +}; | ||||
| +MODULE_DEVICE_TABLE(of, mtk_nor_match); | ||||
| + | ||||
| +static int mtk_nor_probe(struct platform_device *pdev) | ||||
| +{ | ||||
| +	struct spi_controller *ctlr; | ||||
| +	struct mtk_nor *sp; | ||||
| +	void __iomem *base; | ||||
| +	u8 *buffer; | ||||
| +	struct clk *spi_clk, *ctlr_clk; | ||||
| +	int ret, irq; | ||||
| + | ||||
| +	base = devm_platform_ioremap_resource(pdev, 0); | ||||
| +	if (IS_ERR(base)) | ||||
| +		return PTR_ERR(base); | ||||
| + | ||||
| +	spi_clk = devm_clk_get(&pdev->dev, "spi"); | ||||
| +	if (IS_ERR(spi_clk)) | ||||
| +		return PTR_ERR(spi_clk); | ||||
| + | ||||
| +	ctlr_clk = devm_clk_get(&pdev->dev, "sf"); | ||||
| +	if (IS_ERR(ctlr_clk)) | ||||
| +		return PTR_ERR(ctlr_clk); | ||||
| + | ||||
| +	buffer = devm_kmalloc(&pdev->dev, | ||||
| +			      MTK_NOR_BOUNCE_BUF_SIZE + MTK_NOR_DMA_ALIGN, | ||||
| +			      GFP_KERNEL); | ||||
| +	if (!buffer) | ||||
| +		return -ENOMEM; | ||||
| + | ||||
| +	if ((ulong)buffer & MTK_NOR_DMA_ALIGN_MASK) | ||||
| +		buffer = (u8 *)(((ulong)buffer + MTK_NOR_DMA_ALIGN) & | ||||
| +				~MTK_NOR_DMA_ALIGN_MASK); | ||||
| + | ||||
| +	ctlr = spi_alloc_master(&pdev->dev, sizeof(*sp)); | ||||
| +	if (!ctlr) { | ||||
| +		dev_err(&pdev->dev, "failed to allocate spi controller\n"); | ||||
| +		return -ENOMEM; | ||||
| +	} | ||||
| + | ||||
| +	ctlr->bits_per_word_mask = SPI_BPW_MASK(8); | ||||
| +	ctlr->dev.of_node = pdev->dev.of_node; | ||||
| +	ctlr->max_message_size = mtk_max_msg_size; | ||||
| +	ctlr->mem_ops = &mtk_nor_mem_ops; | ||||
| +	ctlr->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD; | ||||
| +	ctlr->num_chipselect = 1; | ||||
| +	ctlr->setup = mtk_nor_setup; | ||||
| +	ctlr->transfer_one_message = mtk_nor_transfer_one_message; | ||||
| + | ||||
| +	dev_set_drvdata(&pdev->dev, ctlr); | ||||
| + | ||||
| +	sp = spi_controller_get_devdata(ctlr); | ||||
| +	sp->base = base; | ||||
| +	sp->buffer = buffer; | ||||
| +	sp->has_irq = false; | ||||
| +	sp->wbuf_en = false; | ||||
| +	sp->ctlr = ctlr; | ||||
| +	sp->dev = &pdev->dev; | ||||
| +	sp->spi_clk = spi_clk; | ||||
| +	sp->ctlr_clk = ctlr_clk; | ||||
| + | ||||
| +	irq = platform_get_irq_optional(pdev, 0); | ||||
| +	if (irq < 0) { | ||||
| +		dev_warn(sp->dev, "IRQ not available."); | ||||
| +	} else { | ||||
| +		writel(MTK_NOR_IRQ_MASK, base + MTK_NOR_REG_IRQ_STAT); | ||||
| +		writel(0, base + MTK_NOR_REG_IRQ_EN); | ||||
| +		ret = devm_request_irq(sp->dev, irq, mtk_nor_irq_handler, 0, | ||||
| +				       pdev->name, sp); | ||||
| +		if (ret < 0) { | ||||
| +			dev_warn(sp->dev, "failed to request IRQ."); | ||||
| +		} else { | ||||
| +			init_completion(&sp->op_done); | ||||
| +			sp->has_irq = true; | ||||
| +		} | ||||
| +	} | ||||
| + | ||||
| +	ret = mtk_nor_init(sp); | ||||
| +	if (ret < 0) { | ||||
| +		kfree(ctlr); | ||||
| +		return ret; | ||||
| +	} | ||||
| + | ||||
| +	dev_info(&pdev->dev, "spi frequency: %d Hz\n", sp->spi_freq); | ||||
| + | ||||
| +	return devm_spi_register_controller(&pdev->dev, ctlr); | ||||
| +} | ||||
| + | ||||
| +static int mtk_nor_remove(struct platform_device *pdev) | ||||
| +{ | ||||
| +	struct spi_controller *ctlr; | ||||
| +	struct mtk_nor *sp; | ||||
| + | ||||
| +	ctlr = dev_get_drvdata(&pdev->dev); | ||||
| +	sp = spi_controller_get_devdata(ctlr); | ||||
| + | ||||
| +	mtk_nor_disable_clk(sp); | ||||
| + | ||||
| +	return 0; | ||||
| +} | ||||
| + | ||||
| +static struct platform_driver mtk_nor_driver = { | ||||
| +	.driver = { | ||||
| +		.name = DRIVER_NAME, | ||||
| +		.of_match_table = mtk_nor_match, | ||||
| +	}, | ||||
| +	.probe = mtk_nor_probe, | ||||
| +	.remove = mtk_nor_remove, | ||||
| +}; | ||||
| + | ||||
| +module_platform_driver(mtk_nor_driver); | ||||
| + | ||||
| +MODULE_DESCRIPTION("Mediatek SPI NOR controller driver"); | ||||
| +MODULE_AUTHOR("Chuanhong Guo <gch981213@gmail.com>"); | ||||
| +MODULE_LICENSE("GPL v2"); | ||||
| +MODULE_ALIAS("platform:" DRIVER_NAME); | ||||
		Reference in New Issue
	
	Block a user
	 Chuanhong Guo
					Chuanhong Guo