diff options
author | Chuanhong Guo <gch981213@gmail.com> | 2020-03-20 19:26:45 +0800 |
---|---|---|
committer | Chuanhong Guo <gch981213@gmail.com> | 2020-03-21 12:58:29 +0800 |
commit | 2d2e9d29560478f7d1716cda8bc37370a0dcf520 (patch) | |
tree | aceecc5a8fe9ff228cda3dc78ee2c77db72371a6 /target/linux/mediatek | |
parent | a73ee0fe35fe1b29f475e3786a825065236ecccf (diff) | |
download | upstream-2d2e9d29560478f7d1716cda8bc37370a0dcf520.tar.gz upstream-2d2e9d29560478f7d1716cda8bc37370a0dcf520.tar.bz2 upstream-2d2e9d29560478f7d1716cda8bc37370a0dcf520.zip |
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>
Diffstat (limited to 'target/linux/mediatek')
3 files changed, 800 insertions, 0 deletions
diff --git a/target/linux/mediatek/mt7629/config-5.4 b/target/linux/mediatek/mt7629/config-5.4 index 295b9e8c06..44cb3a1752 100644 --- a/target/linux/mediatek/mt7629/config-5.4 +++ b/target/linux/mediatek/mt7629/config-5.4 @@ -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 diff --git a/target/linux/mediatek/patches-5.4/0001-v5.7-spi-make-spi-max-frequency-optional.patch b/target/linux/mediatek/patches-5.4/0001-v5.7-spi-make-spi-max-frequency-optional.patch new file mode 100644 index 0000000000..d29afc0636 --- /dev/null +++ b/target/linux/mediatek/patches-5.4/0001-v5.7-spi-make-spi-max-frequency-optional.patch @@ -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; + } diff --git a/target/linux/mediatek/patches-5.4/0002-v5.7-spi-add-support-for-mediatek-spi-nor-controller.patch b/target/linux/mediatek/patches-5.4/0002-v5.7-spi-add-support-for-mediatek-spi-nor-controller.patch new file mode 100644 index 0000000000..0a63bdddaf --- /dev/null +++ b/target/linux/mediatek/patches-5.4/0002-v5.7-spi-add-support-for-mediatek-spi-nor-controller.patch @@ -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); |