From: Miquel Raynal <miquel.raynal@bootlin.com>
To: Xiangsheng Hou <xiangsheng.hou@mediatek.com>
Cc: <broonie@kernel.org>, <benliang.zhao@mediatek.com>,
<dandan.he@mediatek.com>, <guochun.mao@mediatek.com>,
<bin.zhang@mediatek.com>, <sanny.chen@mediatek.com>,
<mao.zhong@mediatek.com>, <yingjoe.chen@mediatek.com>,
<donghunt@amazon.com>, <rdlee@amazon.com>,
<linux-mtd@lists.infradead.org>,
<linux-mediatek@lists.infradead.org>,
<srv_heupstream@mediatek.com>
Subject: Re: [RFC,v4,3/5] spi: mtk: Add mediatek SPI Nand Flash interface driver
Date: Thu, 9 Dec 2021 11:20:30 +0100 [thread overview]
Message-ID: <20211209112030.5cdcd07c@xps13> (raw)
In-Reply-To: <20211130083202.14228-4-xiangsheng.hou@mediatek.com>
Hi Xiangsheng,
xiangsheng.hou@mediatek.com wrote on Tue, 30 Nov 2021 16:32:00 +0800:
> The SPI Nand Flash interface driver cowork with Mediatek pipelined
> HW ECC engine.
>
> Signed-off-by: Xiangsheng Hou <xiangsheng.hou@mediatek.com>
> ---
> drivers/spi/Kconfig | 11 +
> drivers/spi/Makefile | 1 +
> drivers/spi/spi-mtk-snfi.c | 1117 ++++++++++++++++++++++++++++++++++++
> 3 files changed, 1129 insertions(+)
> create mode 100644 drivers/spi/spi-mtk-snfi.c
>
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index 596705d24400..9cb6a173b1ef 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -535,6 +535,17 @@ config SPI_MT65XX
> say Y or M here.If you are not sure, say N.
> SPI drivers for Mediatek MT65XX and MT81XX series ARM SoCs.
>
> +config SPI_MTK_SNFI
> + tristate "MediaTek SPI NAND interface"
> + depends on MTD
> + select MTD_SPI_NAND
> + select MTD_NAND_ECC_MTK
> + help
> + This selects the SPI NAND FLASH interface(SNFI),
> + which could be found on MediaTek Soc.
> + Say Y or M here.If you are not sure, say N.
> + Note Parallel Nand and SPI NAND is alternative on MediaTek SoCs.
> +
> config SPI_MT7621
> tristate "MediaTek MT7621 SPI Controller"
> depends on RALINK || COMPILE_TEST
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index dd7393a6046f..57d11eecf662 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -73,6 +73,7 @@ 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_MTK_SNFI) += spi-mtk-snfi.o
> obj-$(CONFIG_SPI_MXIC) += spi-mxic.o
> obj-$(CONFIG_SPI_MXS) += spi-mxs.o
> obj-$(CONFIG_SPI_NPCM_FIU) += spi-npcm-fiu.o
> diff --git a/drivers/spi/spi-mtk-snfi.c b/drivers/spi/spi-mtk-snfi.c
> new file mode 100644
> index 000000000000..b4dce6d78176
> --- /dev/null
> +++ b/drivers/spi/spi-mtk-snfi.c
> @@ -0,0 +1,1117 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Driver for MediaTek SPI Nand Flash interface
> + *
> + * Copyright (C) 2021 MediaTek Inc.
> + * Authors: Xiangsheng Hou <xiangsheng.hou@mediatek.com>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/interrupt.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/mtd/nand.h>
> +#include <linux/mtd/nand-ecc-mtk.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/spi/spi.h>
> +#include <linux/spi/spi-mem.h>
> +
> +/* Registers used by the driver */
> +#define NFI_CNFG (0x00)
> +#define CNFG_DMA BIT(0)
> +#define CNFG_READ_EN BIT(1)
> +#define CNFG_DMA_BURST_EN BIT(2)
> +#define CNFG_HW_ECC_EN BIT(8)
> +#define CNFG_AUTO_FMT_EN BIT(9)
> +#define CNFG_OP_CUST GENMASK(14, 13)
> +#define NFI_PAGEFMT (0x04)
> +#define PAGEFMT_512_2K (0)
> +#define PAGEFMT_2K_4K (1)
> +#define PAGEFMT_4K_8K (2)
> +#define PAGEFMT_8K_16K (3)
> +#define PAGEFMT_PAGE_MASK GENMASK(1, 0)
> +#define PAGEFMT_SEC_SEL_512 BIT(2)
> +#define PAGEFMT_FDM_SHIFT (8)
> +#define PAGEFMT_FDM_ECC_SHIFT (12)
> +#define PAGEFMT_SPARE_SHIFT (16)
> +#define PAGEFMT_SPARE_MASK GENMASK(21, 16)
> +#define NFI_CON (0x08)
> +#define CON_FIFO_FLUSH BIT(0)
> +#define CON_NFI_RST BIT(1)
> +#define CON_BRD BIT(8)
> +#define CON_BWR BIT(9)
> +#define CON_SEC_SHIFT (12)
> +#define CON_SEC_MASK GENMASK(16, 12)
> +#define NFI_INTR_EN (0x10)
> +#define INTR_CUS_PROG_EN BIT(7)
> +#define INTR_CUS_READ_EN BIT(8)
> +#define INTR_IRQ_EN BIT(31)
> +#define NFI_INTR_STA (0x14)
> +#define NFI_CMD (0x20)
> +#define CMD_DUMMY (0x00)
> +#define NFI_STRDATA (0x40)
> +#define STAR_EN BIT(0)
> +#define NFI_STA (0x60)
> +#define NFI_FSM_MASK GENMASK(19, 16)
> +#define STA_EMP_PAGE BIT(12)
> +#define NFI_ADDRCNTR (0x70)
> +#define CNTR_MASK GENMASK(16, 12)
> +#define ADDRCNTR_SEC_SHIFT (12)
> +#define ADDRCNTR_SEC(val) \
> + (((val) & CNTR_MASK) >> ADDRCNTR_SEC_SHIFT)
> +#define NFI_STRADDR (0x80)
> +#define NFI_BYTELEN (0x84)
> +#define NFI_FDML(x) (0xA0 + (x) * sizeof(u32) * 2)
> +#define NFI_FDMM(x) (0xA4 + (x) * sizeof(u32) * 2)
> +#define NFI_MASTERSTA (0x224)
> +#define AHB_BUS_BUSY GENMASK(1, 0)
> +#define SNFI_MAC_CTL (0x500)
> +#define MAC_WIP BIT(0)
> +#define MAC_WIP_READY BIT(1)
> +#define MAC_TRIG BIT(2)
> +#define MAC_EN BIT(3)
> +#define MAC_SIO_SEL BIT(4)
> +#define SNFI_MAC_OUTL (0x504)
> +#define SNFI_MAC_INL (0x508)
> +#define SNFI_RD_CTL2 (0x510)
> +#define RD_CMD_MASK GENMASK(7, 0)
> +#define RD_DUMMY_SHIFT (8)
> +#define SNFI_RD_CTL3 (0x514)
> +#define RD_ADDR_MASK GENMASK(16, 0)
> +#define SNFI_PG_CTL1 (0x524)
> +#define WR_LOAD_CMD_MASK GENMASK(15, 8)
> +#define WR_LOAD_CMD_SHIFT (8)
> +#define SNFI_PG_CTL2 (0x528)
> +#define WR_LOAD_ADDR_MASK GENMASK(15, 0)
> +#define SNFI_MISC_CTL (0x538)
> +#define RD_CUSTOM_EN BIT(6)
> +#define WR_CUSTOM_EN BIT(7)
> +#define LATCH_LAT_SHIFT (8)
> +#define LATCH_LAT_MASK GENMASK(9, 8)
> +#define RD_MODE_X2 BIT(16)
> +#define RD_MODE_X4 BIT(17)
> +#define RD_MODE_DQUAL BIT(18)
> +#define RD_MODE_MASK GENMASK(18, 16)
> +#define WR_X4_EN BIT(20)
> +#define SW_RST BIT(28)
> +#define SNFI_MISC_CTL2 (0x53c)
> +#define WR_LEN_SHIFT (16)
> +#define SNFI_DLY_CTL3 (0x548)
> +#define SAM_DLY_MASK GENMASK(5, 0)
> +#define SNFI_STA_CTL1 (0x550)
> +#define SPI_STATE GENMASK(3, 0)
> +#define CUS_READ_DONE BIT(27)
> +#define CUS_PROG_DONE BIT(28)
> +#define SNFI_CNFG (0x55c)
> +#define SNFI_MODE_EN BIT(0)
> +#define SNFI_GPRAM_DATA (0x800)
> +#define SNFI_GPRAM_MAX_LEN (160)
> +
> +#define MTK_SNFI_TIMEOUT (500000)
> +#define MTK_SNFI_RESET_TIMEOUT (1000000)
> +#define MTK_SNFI_AUTOSUSPEND_DELAY (1000)
> +#define KB(x) ((x) * 1024UL)
> +
> +struct mtk_snfi_caps {
> + u8 pageformat_spare_shift;
> +};
> +
> +struct mtk_snfi {
> + struct device *dev;
> + struct completion done;
> + void __iomem *regs;
> + const struct mtk_snfi_caps *caps;
> +
> + struct clk *nfi_clk;
> + struct clk *snfi_clk;
> + struct clk *hclk;
> +
> + struct nand_ecc_engine *engine;
> +
> + u32 sample_delay;
> + u32 read_latency;
> +
> + void *tx_buf;
> + dma_addr_t dma_addr;
> +};
> +
> +static struct mtk_ecc_engine *mtk_snfi_to_ecc_engine(struct mtk_snfi *snfi)
> +{
> + return snfi->engine->priv;
> +}
> +
> +static void mtk_snfi_mac_enable(struct mtk_snfi *snfi)
> +{
> + u32 val;
> +
> + val = readl(snfi->regs + SNFI_MAC_CTL);
> + val &= ~MAC_SIO_SEL;
> + val |= MAC_EN;
> +
> + writel(val, snfi->regs + SNFI_MAC_CTL);
> +}
> +
> +static int mtk_snfi_mac_trigger(struct mtk_snfi *snfi)
> +{
> + int ret;
> + u32 val;
> +
> + val = readl(snfi->regs + SNFI_MAC_CTL);
> + val |= MAC_TRIG;
> + writel(val, snfi->regs + SNFI_MAC_CTL);
> +
> + ret = readl_poll_timeout_atomic(snfi->regs + SNFI_MAC_CTL,
> + val, val & MAC_WIP_READY,
> + 0, MTK_SNFI_TIMEOUT);
> + if (ret < 0) {
> + dev_err(snfi->dev, "wait for wip ready timeout\n");
> + return -EIO;
> + }
> +
> + ret = readl_poll_timeout_atomic(snfi->regs + SNFI_MAC_CTL,
> + val, !(val & MAC_WIP), 0,
> + MTK_SNFI_TIMEOUT);
> + if (ret < 0) {
> + dev_err(snfi->dev, "command write timeout\n");
> + return -EIO;
> + }
> +
> + return 0;
> +}
> +
> +static void mtk_snfi_mac_disable(struct mtk_snfi *snfi)
> +{
> + u32 val;
> +
> + val = readl(snfi->regs + SNFI_MAC_CTL);
> + val &= ~(MAC_TRIG | MAC_EN);
> + writel(val, snfi->regs + SNFI_MAC_CTL);
> +}
> +
> +static int mtk_snfi_mac_op(struct mtk_snfi *snfi)
> +{
> + int ret;
> +
> + mtk_snfi_mac_enable(snfi);
> + ret = mtk_snfi_mac_trigger(snfi);
> + mtk_snfi_mac_disable(snfi);
> +
> + return ret;
> +}
> +
> +static inline void mtk_snfi_read_oob_free(struct mtk_snfi *snfi,
> + const struct spi_mem_op *op)
> +{
> + struct mtk_ecc_engine *eng = mtk_snfi_to_ecc_engine(snfi);
> + u8 *oobptr = op->data.buf.in;
> + u32 vall, valm;
> + int i, j;
> +
> + oobptr += eng->section_size * eng->nsteps;
> + for (i = 0; i < eng->nsteps; i++) {
> + vall = readl(snfi->regs + NFI_FDML(i));
> + valm = readl(snfi->regs + NFI_FDMM(i));
> +
> + for (j = 0; j < eng->oob_free; j++)
> + oobptr[j] = (j >= 4 ? valm : vall) >> ((j % 4) * 8);
> +
> + oobptr += eng->oob_free;
> + }
> +}
> +
> +static inline void mtk_snfi_write_oob_free(struct mtk_snfi *snfi,
> + const struct spi_mem_op *op)
> +{
> + struct mtk_ecc_engine *eng = mtk_snfi_to_ecc_engine(snfi);
> + const u8 *oobptr = op->data.buf.out;
> + u32 vall, valm;
> + int i, j;
> +
> + oobptr += eng->section_size * eng->nsteps;
> + for (i = 0; i < eng->nsteps; i++) {
> + vall = 0;
> + valm = 0;
> + for (j = 0; j < 8; j++) {
> + if (j < 4)
> + vall |= (j < eng->oob_free ? oobptr[j] : 0xff)
> + << (j * 8);
> + else
> + valm |= (j < eng->oob_free ? oobptr[j] : 0xff)
> + << ((j - 4) * 8);
> + }
> +
> + writel(vall, snfi->regs + NFI_FDML(i));
> + writel(valm, snfi->regs + NFI_FDMM(i));
> + oobptr += eng->oob_free;
> + }
> +}
> +
> +static irqreturn_t mtk_snfi_irq(int irq, void *id)
> +{
> + struct mtk_snfi *snfi = id;
> + u32 sta, ien;
> +
> + sta = readl(snfi->regs + NFI_INTR_STA);
> + ien = readl(snfi->regs + NFI_INTR_EN);
> +
> + if (!(sta & ien))
> + return IRQ_NONE;
> +
> + writel(0, snfi->regs + NFI_INTR_EN);
> + complete(&snfi->done);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int mtk_snfi_enable_clk(struct device *dev, struct mtk_snfi *snfi)
> +{
> + int ret;
> +
> + ret = clk_prepare_enable(snfi->nfi_clk);
> + if (ret) {
> + dev_err(dev, "failed to enable nfi clk\n");
> + return ret;
> + }
> +
> + ret = clk_prepare_enable(snfi->snfi_clk);
> + if (ret) {
> + dev_err(dev, "failed to enable snfi clk\n");
> + clk_disable_unprepare(snfi->nfi_clk);
> + return ret;
> + }
> +
> + ret = clk_prepare_enable(snfi->hclk);
> + if (ret) {
> + dev_err(dev, "failed to enable hclk\n");
> + clk_disable_unprepare(snfi->nfi_clk);
> + clk_disable_unprepare(snfi->snfi_clk);
definitely deserves goto statements :)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void mtk_snfi_disable_clk(struct mtk_snfi *snfi)
> +{
> + clk_disable_unprepare(snfi->nfi_clk);
> + clk_disable_unprepare(snfi->snfi_clk);
> + clk_disable_unprepare(snfi->hclk);
> +}
> +
> +static int mtk_snfi_reset(struct mtk_snfi *snfi)
> +{
> + u32 val;
> + int ret;
> +
> + val = readl(snfi->regs + SNFI_MISC_CTL) | SW_RST;
> + writel(val, snfi->regs + SNFI_MISC_CTL);
> +
> + ret = readw_poll_timeout(snfi->regs + SNFI_STA_CTL1, val,
> + !(val & SPI_STATE), 0,
> + MTK_SNFI_RESET_TIMEOUT);
> + if (ret) {
> + dev_warn(snfi->dev, "wait spi idle timeout 0x%x\n", val);
> + return ret;
> + }
> +
> + val = readl(snfi->regs + SNFI_MISC_CTL);
> + val &= ~SW_RST;
> + writel(val, snfi->regs + SNFI_MISC_CTL);
> +
> + writew(CON_FIFO_FLUSH | CON_NFI_RST, snfi->regs + NFI_CON);
> + ret = readw_poll_timeout(snfi->regs + NFI_STA, val,
> + !(val & NFI_FSM_MASK), 0,
> + MTK_SNFI_RESET_TIMEOUT);
> + if (ret) {
> + dev_warn(snfi->dev, "wait nfi fsm idle timeout 0x%x\n", val);
> + return ret;
> + }
> +
> + val = readl(snfi->regs + NFI_STRDATA);
> + val &= ~STAR_EN;
> + writew(val, snfi->regs + NFI_STRDATA);
> +
> + return 0;
> +}
> +
> +static int mtk_snfi_init(struct mtk_snfi *snfi)
> +{
> + int ret;
> + u32 val;
> +
> + ret = mtk_snfi_reset(snfi);
> + if (ret)
> + return ret;
> +
> + writel(SNFI_MODE_EN, snfi->regs + SNFI_CNFG);
> +
> + if (snfi->sample_delay) {
> + val = readl(snfi->regs + SNFI_DLY_CTL3);
> + val &= ~SAM_DLY_MASK;
> + val |= snfi->sample_delay;
> + writel(val, snfi->regs + SNFI_DLY_CTL3);
> + }
> +
> + if (snfi->read_latency) {
> + val = readl(snfi->regs + SNFI_MISC_CTL);
> + val &= ~LATCH_LAT_MASK;
> + val |= (snfi->read_latency << LATCH_LAT_SHIFT);
> + writel(val, snfi->regs + SNFI_MISC_CTL);
> + }
> +
> + return 0;
> +}
> +
> +static void mtk_snfi_prepare_for_tx(struct mtk_snfi *snfi,
> + const struct spi_mem_op *op)
> +{
> + struct mtk_ecc_engine *eng = mtk_snfi_to_ecc_engine(snfi);
> + u32 val;
> +
> + val = readl(snfi->regs + SNFI_PG_CTL1);
> + val &= ~WR_LOAD_CMD_MASK;
> + val |= op->cmd.opcode << WR_LOAD_CMD_SHIFT;
> + writel(val, snfi->regs + SNFI_PG_CTL1);
> +
> + writel(op->addr.val & WR_LOAD_ADDR_MASK,
> + snfi->regs + SNFI_PG_CTL2);
> +
> + val = readl(snfi->regs + SNFI_MISC_CTL);
> + val |= WR_CUSTOM_EN;
> + if (op->data.buswidth == 4)
> + val |= WR_X4_EN;
> + writel(val, snfi->regs + SNFI_MISC_CTL);
> +
> + val = eng->nsteps * (eng->oob_per_section + eng->section_size);
> + writel(val << WR_LEN_SHIFT, snfi->regs + SNFI_MISC_CTL2);
> +
> + writel(INTR_CUS_PROG_EN | INTR_IRQ_EN, snfi->regs + NFI_INTR_EN);
> +}
> +
> +static void mtk_snfi_prepare_for_rx(struct mtk_snfi *snfi,
> + const struct spi_mem_op *op)
> +{
> + struct mtk_ecc_engine *eng = mtk_snfi_to_ecc_engine(snfi);
> + u32 val, dummy_cycle;
> +
> + dummy_cycle = (op->dummy.nbytes << 3) >>
> + (ffs(op->dummy.buswidth) - 1);
> + val = (op->cmd.opcode & RD_CMD_MASK) |
> + (dummy_cycle << RD_DUMMY_SHIFT);
> + writel(val, snfi->regs + SNFI_RD_CTL2);
> +
> + writel(op->addr.val & RD_ADDR_MASK,
> + snfi->regs + SNFI_RD_CTL3);
> +
> + val = readl(snfi->regs + SNFI_MISC_CTL);
> + val |= RD_CUSTOM_EN;
> + val &= ~RD_MODE_MASK;
> + if (op->data.buswidth == 4)
> + val |= RD_MODE_X4;
> + else if (op->data.buswidth == 2)
> + val |= RD_MODE_X2;
> +
> + if (op->addr.buswidth != 1)
> + val |= RD_MODE_DQUAL;
> +
> + writel(val, snfi->regs + SNFI_MISC_CTL);
> +
> + val = eng->nsteps * (eng->oob_per_section + eng->section_size);
> + writel(val, snfi->regs + SNFI_MISC_CTL2);
> +
> + writel(INTR_CUS_READ_EN | INTR_IRQ_EN, snfi->regs + NFI_INTR_EN);
> +}
> +
> +static int mtk_snfi_prepare(struct mtk_snfi *snfi,
> + const struct spi_mem_op *op, bool rx)
> +{
> + struct mtk_ecc_engine *eng = mtk_snfi_to_ecc_engine(snfi);
> + dma_addr_t addr;
> + int ret;
> + u32 val;
> +
> + addr = dma_map_single(snfi->dev,
> + op->data.buf.in, op->data.nbytes,
> + rx ? DMA_FROM_DEVICE : DMA_TO_DEVICE);
> + ret = dma_mapping_error(snfi->dev, addr);
> + if (ret) {
> + dev_err(snfi->dev, "dma mapping error\n");
> + return -EINVAL;
> + }
> +
> + snfi->dma_addr = addr;
> + writel(lower_32_bits(addr), snfi->regs + NFI_STRADDR);
> +
> + if (op->ecc_en && !rx)
> + mtk_snfi_write_oob_free(snfi, op);
> +
> + val = readw(snfi->regs + NFI_CNFG);
> + val |= CNFG_DMA | CNFG_DMA_BURST_EN | CNFG_OP_CUST;
> + val |= rx ? CNFG_READ_EN : 0;
> +
> + if (op->ecc_en)
> + val |= CNFG_HW_ECC_EN | CNFG_AUTO_FMT_EN;
> +
> + writew(val, snfi->regs + NFI_CNFG);
> +
> + writel(eng->nsteps << CON_SEC_SHIFT, snfi->regs + NFI_CON);
> +
> + init_completion(&snfi->done);
> +
> + /* trigger state machine to custom op mode */
> + writel(CMD_DUMMY, snfi->regs + NFI_CMD);
> +
> + if (rx)
> + mtk_snfi_prepare_for_rx(snfi, op);
> + else
> + mtk_snfi_prepare_for_tx(snfi, op);
> +
> + return 0;
> +}
> +
> +static void mtk_snfi_trigger(struct mtk_snfi *snfi,
> + const struct spi_mem_op *op, bool rx)
> +{
> + u32 val;
> +
> + val = readl(snfi->regs + NFI_CON);
> + val |= rx ? CON_BRD : CON_BWR;
> + writew(val, snfi->regs + NFI_CON);
> +
> + writew(STAR_EN, snfi->regs + NFI_STRDATA);
> +}
> +
> +static int mtk_snfi_wait_done(struct mtk_snfi *snfi,
> + const struct spi_mem_op *op, bool rx)
> +{
> + struct mtk_ecc_engine *eng = mtk_snfi_to_ecc_engine(snfi);
> + struct device *dev = snfi->dev;
> + u32 val;
> + int ret;
> +
> + ret = wait_for_completion_timeout(&snfi->done, msecs_to_jiffies(500));
> + if (!ret) {
> + dev_err(dev, "wait for %d completion done timeout\n", rx);
> + return -ETIMEDOUT;
> + }
> +
> + if (rx) {
> + ret = readl_poll_timeout_atomic(snfi->regs + NFI_BYTELEN,
> + val,
> + ADDRCNTR_SEC(val) >=
> + eng->nsteps,
> + 0, MTK_SNFI_TIMEOUT);
> + if (ret) {
> + dev_err(dev, "wait for rx section count timeout\n");
> + return -ETIMEDOUT;
> + }
> +
> + ret = readl_poll_timeout_atomic(snfi->regs + NFI_MASTERSTA,
> + val,
> + !(val & AHB_BUS_BUSY),
> + 0, MTK_SNFI_TIMEOUT);
> + if (ret) {
> + dev_err(dev, "wait for bus busy timeout\n");
> + return -ETIMEDOUT;
> + }
> + } else {
> + ret = readl_poll_timeout_atomic(snfi->regs + NFI_ADDRCNTR,
> + val,
> + ADDRCNTR_SEC(val) >=
> + eng->nsteps,
> + 0, MTK_SNFI_TIMEOUT);
> + if (ret) {
> + dev_err(dev, "wait for tx section count timeout\n");
> + return -ETIMEDOUT;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static void mtk_snfi_complete(struct mtk_snfi *snfi,
> + const struct spi_mem_op *op, bool rx)
> +{
> + u32 val;
> +
> + dma_unmap_single(snfi->dev,
> + snfi->dma_addr, op->data.nbytes,
> + rx ? DMA_FROM_DEVICE : DMA_TO_DEVICE);
> +
> + if (op->ecc_en && rx)
> + mtk_snfi_read_oob_free(snfi, op);
> +
> + val = readl(snfi->regs + SNFI_MISC_CTL);
> + val &= rx ? ~RD_CUSTOM_EN : ~WR_CUSTOM_EN;
> + writel(val, snfi->regs + SNFI_MISC_CTL);
> +
> + val = readl(snfi->regs + SNFI_STA_CTL1);
> + val |= rx ? CUS_READ_DONE : CUS_PROG_DONE;
> + writew(val, snfi->regs + SNFI_STA_CTL1);
> + val &= rx ? ~CUS_READ_DONE : ~CUS_PROG_DONE;
> + writew(val, snfi->regs + SNFI_STA_CTL1);
> +
> + /* Disable interrupt */
> + val = readl(snfi->regs + NFI_INTR_EN);
> + val &= rx ? ~INTR_CUS_READ_EN : ~INTR_CUS_PROG_EN;
> + writew(val, snfi->regs + NFI_INTR_EN);
> +
> + writew(0, snfi->regs + NFI_CNFG);
> + writew(0, snfi->regs + NFI_CON);
> +}
> +
> +static int mtk_snfi_transfer_dma(struct mtk_snfi *snfi,
> + const struct spi_mem_op *op, bool rx)
> +{
> + int ret;
> +
> + ret = mtk_snfi_prepare(snfi, op, rx);
> + if (ret)
> + return ret;
> +
> + mtk_snfi_trigger(snfi, op, rx);
> +
> + ret = mtk_snfi_wait_done(snfi, op, rx);
> +
> + mtk_snfi_complete(snfi, op, rx);
> +
> + return ret;
> +}
> +
> +static int mtk_snfi_transfer_mac(struct mtk_snfi *snfi,
> + const u8 *txbuf, u8 *rxbuf,
> + const u32 txlen, const u32 rxlen)
> +{
> + u32 i, j, val, tmp;
> + u8 *p_tmp = (u8 *)(&tmp);
> + u32 offset = 0;
> + int ret = 0;
> +
> + /* Move tx data to gpram in snfi mac mode */
> + for (i = 0; i < txlen; ) {
> + for (j = 0, tmp = 0; i < txlen && j < 4; i++, j++)
> + p_tmp[j] = txbuf[i];
> +
> + writel(tmp, snfi->regs + SNFI_GPRAM_DATA + offset);
> + offset += 4;
> + }
> +
> + writel(txlen, snfi->regs + SNFI_MAC_OUTL);
> + writel(rxlen, snfi->regs + SNFI_MAC_INL);
> +
> + ret = mtk_snfi_mac_op(snfi);
> + if (ret) {
> + dev_warn(snfi->dev, "snfi mac operation fail\n");
> + return ret;
> + }
> +
> + /* Get tx data from gpram in snfi mac mode */
> + if (rxlen)
> + for (i = 0, offset = rounddown(txlen, 4); i < rxlen; ) {
> + val = readl(snfi->regs +
> + SNFI_GPRAM_DATA + offset);
> + for (j = 0; i < rxlen && j < 4; i++, j++, rxbuf++) {
> + if (i == 0)
> + j = txlen % 4;
> + *rxbuf = (val >> (j * 8)) & 0xff;
> + }
> + offset += 4;
> + }
> +
> + return ret;
> +}
> +
> +static int mtk_snfi_exec_op(struct spi_mem *mem,
> + const struct spi_mem_op *op)
> +{
> + struct mtk_snfi *snfi = spi_controller_get_devdata(mem->spi->master);
> + u8 *buf, *txbuf = snfi->tx_buf, *rxbuf = NULL;
> + u32 txlen = 0, rxlen = 0;
> + int i, ret = 0;
> + bool rx;
> +
> + rx = op->data.dir == SPI_MEM_DATA_IN;
> +
> + ret = mtk_snfi_reset(snfi);
> + if (ret) {
> + dev_warn(snfi->dev, "snfi reset fail\n");
> + return ret;
> + }
> +
> + /*
> + * If tx/rx data buswidth is not 0/1, use snfi DMA mode.
> + * Otherwise, use snfi mac mode.
> + */
> + if (op->data.buswidth != 1 && op->data.buswidth != 0) {
> + ret = mtk_snfi_transfer_dma(snfi, op, rx);
> + if (ret)
> + dev_warn(snfi->dev, "snfi dma transfer %d fail %d\n",
> + rx, ret);
> + return ret;
> + }
> +
> + txbuf[txlen++] = op->cmd.opcode;
> +
> + if (op->addr.nbytes)
> + for (i = 0; i < op->addr.nbytes; i++)
> + txbuf[txlen++] = op->addr.val >>
> + (8 * (op->addr.nbytes - i - 1));
> +
> + txlen += op->dummy.nbytes;
> +
> + if (op->data.dir == SPI_MEM_DATA_OUT) {
> + buf = (u8 *)op->data.buf.out;
> + for (i = 0; i < op->data.nbytes; i++)
> + txbuf[txlen++] = buf[i];
> + }
> +
> + if (op->data.dir == SPI_MEM_DATA_IN) {
> + rxbuf = (u8 *)op->data.buf.in;
> + rxlen = op->data.nbytes;
> + }
> +
> + ret = mtk_snfi_transfer_mac(snfi, txbuf, rxbuf, txlen, rxlen);
> + if (ret)
> + dev_warn(snfi->dev, "snfi mac transfer %d fail %d\n",
> + op->data.dir, ret);
> +
> + return ret;
> +}
> +
> +static int mtk_snfi_check_buswidth(u8 width)
> +{
> + switch (width) {
> + case 1:
> + case 2:
> + case 4:
> + return 0;
> +
> + default:
> + break;
> + }
> +
> + return -EOPNOTSUPP;
> +}
> +
> +static bool mtk_snfi_supports_op(struct spi_mem *mem,
> + const struct spi_mem_op *op)
> +{
> + int ret = 0;
> +
> + if (!spi_mem_default_supports_op(mem, op))
With the integration properly set, this call would always return false
when ecc_en = true. You should switch to
spi_mem_generic_supports_op(mem, op, false, true);
> + return false;
> +
> + if (op->cmd.buswidth != 1)
> + return false;
> +
> + /*
> + * For one operation will use snfi mac mode when data
> + * buswidth is 0/1. However, the HW ECC engine can not
> + * be used in mac mode.
> + */
> + if (op->ecc_en && op->data.buswidth == 1 &&
> + op->data.nbytes >= SNFI_GPRAM_MAX_LEN)
> + return false;
> +
> + switch (op->data.dir) {
> + /* For spi mem data in, can support 1/2/4 buswidth */
> + case SPI_MEM_DATA_IN:
> + if (op->addr.nbytes)
> + ret |= mtk_snfi_check_buswidth(op->addr.buswidth);
> +
> + if (op->dummy.nbytes)
> + ret |= mtk_snfi_check_buswidth(op->dummy.buswidth);
> +
> + if (op->data.nbytes)
> + ret |= mtk_snfi_check_buswidth(op->data.buswidth);
> +
> + if (ret)
> + return false;
> +
> + break;
> + case SPI_MEM_DATA_OUT:
> + /*
> + * For spi mem data out, can support 0/1 buswidth
> + * for addr/dummy and 1/4 buswidth for data.
> + */
> + if (op->addr.buswidth != 0 && op->addr.buswidth != 1)
> + return false;
> +
> + if (op->dummy.buswidth != 0 && op->dummy.buswidth != 1)
> + return false;
> +
> + if (op->data.buswidth != 1 && op->data.buswidth != 4)
> + return false;
> +
> + break;
> + default:
> + break;
> + }
> +
> + return true;
> +}
> +
> +static int mtk_snfi_adjust_op_size(struct spi_mem *mem,
> + struct spi_mem_op *op)
> +{
> + u32 len, max_len;
> +
> + /*
> + * The op size only support SNFI_GPRAM_MAX_LEN which will
> + * use the snfi mac mode when data buswidth is 0/1.
> + * Otherwise, the snfi can max support 16KB.
> + */
> + if (op->data.buswidth == 1 || op->data.buswidth == 0)
> + max_len = SNFI_GPRAM_MAX_LEN;
> + else
> + max_len = KB(16);
> +
> + len = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes;
> + if (len > max_len)
> + return -EOPNOTSUPP;
> +
> + if ((len + op->data.nbytes) > max_len)
> + op->data.nbytes = max_len - len;
> +
> + return 0;
> +}
> +
> +static const struct mtk_snfi_caps mtk_snfi_caps_mt7622 = {
> + .pageformat_spare_shift = 16,
> +};
> +
> +static const struct spi_controller_mem_ops mtk_snfi_ops = {
> + .adjust_op_size = mtk_snfi_adjust_op_size,
> + .supports_op = mtk_snfi_supports_op,
> + .exec_op = mtk_snfi_exec_op,
> +};
> +
> +static const struct of_device_id mtk_snfi_id_table[] = {
> + { .compatible = "mediatek,mt7622-snfi",
> + .data = &mtk_snfi_caps_mt7622,
> + },
> + { /* sentinel */ }
> +};
> +
> +/* ECC wrapper */
> +static struct mtk_snfi *mtk_nand_to_spi(struct nand_device *nand)
> +{
> + struct device *dev = nand->ecc.engine->dev;
> + struct spi_master *master = dev_get_drvdata(dev);
> + struct mtk_snfi *snfi = spi_master_get_devdata(master);
> +
> + return snfi;
> +}
> +
> +static int mtk_snfi_config(struct nand_device *nand,
> + struct mtk_snfi *snfi)
> +{
> + struct mtk_ecc_engine *eng = mtk_snfi_to_ecc_engine(snfi);
> + u32 val;
> +
> + switch (nanddev_page_size(nand)) {
> + case 512:
> + val = PAGEFMT_512_2K | PAGEFMT_SEC_SEL_512;
> + break;
> + case KB(2):
> + if (eng->section_size == 512)
> + val = PAGEFMT_2K_4K | PAGEFMT_SEC_SEL_512;
> + else
> + val = PAGEFMT_512_2K;
> + break;
> + case KB(4):
> + if (eng->section_size == 512)
> + val = PAGEFMT_4K_8K | PAGEFMT_SEC_SEL_512;
> + else
> + val = PAGEFMT_2K_4K;
> + break;
> + case KB(8):
> + if (eng->section_size == 512)
> + val = PAGEFMT_8K_16K | PAGEFMT_SEC_SEL_512;
> + else
> + val = PAGEFMT_4K_8K;
> + break;
> + case KB(16):
> + val = PAGEFMT_8K_16K;
> + break;
> + default:
> + dev_err(snfi->dev, "invalid page len: %d\n",
> + nanddev_page_size(nand));
> + return -EINVAL;
> + }
> +
> + val |= eng->oob_per_section_idx << PAGEFMT_SPARE_SHIFT;
> + val |= eng->oob_free << PAGEFMT_FDM_SHIFT;
> + val |= eng->oob_free_protected << PAGEFMT_FDM_ECC_SHIFT;
> + writel(val, snfi->regs + NFI_PAGEFMT);
Shouldn't this be calculated only once?
> +
> + return 0;
> +}
> +
> +static int mtk_snfi_ecc_init_ctx(struct nand_device *nand)
> +{
> + struct nand_ecc_engine_ops *ops = mtk_ecc_get_pipelined_ops();
> +
> + return ops->init_ctx(nand);
> +}
> +
> +static void mtk_snfi_ecc_cleanup_ctx(struct nand_device *nand)
> +{
> + struct nand_ecc_engine_ops *ops = mtk_ecc_get_pipelined_ops();
> +
> + ops->cleanup_ctx(nand);
> +}
> +
> +static int mtk_snfi_ecc_prepare_io_req(struct nand_device *nand,
> + struct nand_page_io_req *req)
> +{
> + struct nand_ecc_engine_ops *ops = mtk_ecc_get_pipelined_ops();
> + struct mtk_snfi *snfi = mtk_nand_to_spi(nand);
> + int ret;
> +
> + ret = mtk_snfi_config(nand, snfi);
> + if (ret)
> + return ret;
> +
> + return ops->prepare_io_req(nand, req);
> +}
> +
> +static int mtk_snfi_ecc_finish_io_req(struct nand_device *nand,
> + struct nand_page_io_req *req)
> +{
> + struct nand_ecc_engine_ops *ops = mtk_ecc_get_pipelined_ops();
> + struct mtk_ecc_engine *eng = nand_to_ecc_ctx(nand);
> + struct mtk_snfi *snfi = mtk_nand_to_spi(nand);
> +
> + if (req->mode != MTD_OPS_RAW)
> + eng->read_empty = readl(snfi->regs + NFI_STA) & STA_EMP_PAGE;
> +
> + return ops->finish_io_req(nand, req);
> +}
> +
> +static struct nand_ecc_engine_ops mtk_snfi_ecc_engine_pipelined_ops = {
> + .init_ctx = mtk_snfi_ecc_init_ctx,
> + .cleanup_ctx = mtk_snfi_ecc_cleanup_ctx,
> + .prepare_io_req = mtk_snfi_ecc_prepare_io_req,
> + .finish_io_req = mtk_snfi_ecc_finish_io_req,
> +};
> +
> +static int mtk_snfi_ecc_probe(struct platform_device *pdev,
> + struct mtk_snfi *snfi)
> +{
> + struct nand_ecc_engine *ecceng;
> +
> + if (!mtk_ecc_get_pipelined_ops())
> + return -EOPNOTSUPP;
> +
> + ecceng = devm_kzalloc(&pdev->dev, sizeof(*ecceng), GFP_KERNEL);
> + if (!ecceng)
> + return -ENOMEM;
> +
> + ecceng->dev = &pdev->dev;
> + ecceng->ops = &mtk_snfi_ecc_engine_pipelined_ops;
You need to tell the core that this is a pipelined engine (look at the
integration entry).
> +
> + nand_ecc_register_on_host_hw_engine(ecceng);
> +
> + snfi->engine = ecceng;
> +
> + return 0;
> +}
> +
> +static int mtk_snfi_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct spi_controller *ctlr;
> + struct mtk_snfi *snfi;
> + struct resource *res;
> + int ret, irq;
> + u32 val = 0;
> +
> + ctlr = spi_alloc_master(&pdev->dev, sizeof(*snfi));
> + if (!ctlr)
> + return -ENOMEM;
> +
> + snfi = spi_controller_get_devdata(ctlr);
> + snfi->dev = &pdev->dev;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + snfi->regs = devm_ioremap_resource(snfi->dev, res);
> + if (IS_ERR(snfi->regs)) {
> + ret = PTR_ERR(snfi->regs);
> + goto err_put_master;
> + }
> +
> + ret = of_property_read_u32(np, "sample-delay", &val);
> + if (!ret)
> + snfi->sample_delay = val;
> +
> + ret = of_property_read_u32(np, "read-latency", &val);
> + if (!ret)
> + snfi->read_latency = val;
> +
> + snfi->nfi_clk = devm_clk_get(snfi->dev, "nfi_clk");
> + if (IS_ERR(snfi->nfi_clk)) {
> + dev_err(snfi->dev, "not found nfi clk\n");
> + ret = PTR_ERR(snfi->nfi_clk);
> + goto err_put_master;
> + }
> +
> + snfi->snfi_clk = devm_clk_get(snfi->dev, "snfi_clk");
> + if (IS_ERR(snfi->snfi_clk)) {
> + dev_err(snfi->dev, "not found snfi clk\n");
> + ret = PTR_ERR(snfi->snfi_clk);
> + goto err_put_master;
> + }
> +
> + snfi->hclk = devm_clk_get(snfi->dev, "hclk");
> + if (IS_ERR(snfi->hclk)) {
> + dev_err(snfi->dev, "not found hclk\n");
> + ret = PTR_ERR(snfi->hclk);
> + goto err_put_master;
> + }
> +
> + ret = mtk_snfi_enable_clk(snfi->dev, snfi);
> + if (ret)
> + goto err_put_master;
> +
> + snfi->caps = of_device_get_match_data(snfi->dev);
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0) {
> + dev_err(snfi->dev, "not found snfi irq resource\n");
> + ret = -EINVAL;
> + goto clk_disable;
> + }
> +
> + ret = devm_request_irq(snfi->dev, irq, mtk_snfi_irq,
> + 0, "mtk-snfi", snfi);
> + if (ret) {
> + dev_err(snfi->dev, "failed to request snfi irq\n");
> + goto clk_disable;
> + }
> +
> + ret = dma_set_mask(snfi->dev, DMA_BIT_MASK(32));
> + if (ret) {
> + dev_err(snfi->dev, "failed to set dma mask\n");
> + goto clk_disable;
> + }
> +
> + snfi->tx_buf = kzalloc(SNFI_GPRAM_MAX_LEN, GFP_KERNEL);
> + if (!snfi->tx_buf) {
> + ret = -ENOMEM;
> + goto clk_disable;
> + }
> +
> + ctlr->dev.of_node = np;
> + ctlr->mem_ops = &mtk_snfi_ops;
> + ctlr->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_QUAD;
> + ctlr->auto_runtime_pm = true;
> +
> + dev_set_drvdata(snfi->dev, ctlr);
> +
> + ret = mtk_snfi_init(snfi);
> + if (ret) {
> + dev_err(snfi->dev, "failed to init snfi\n");
> + goto free_buf;
> + }
> +
> + ret = mtk_snfi_ecc_probe(pdev, snfi);
> + if (ret) {
> + dev_warn(snfi->dev, "ECC engine not available\n");
> + goto free_buf;
> + }
> +
> + pm_runtime_enable(snfi->dev);
> +
> + ret = devm_spi_register_master(snfi->dev, ctlr);
> + if (ret) {
> + dev_err(snfi->dev, "failed to register spi master\n");
> + goto disable_pm_runtime;
> + }
> +
> + return 0;
> +
> +disable_pm_runtime:
> + pm_runtime_disable(snfi->dev);
> +
> +free_buf:
> + kfree(snfi->tx_buf);
> +
> +clk_disable:
> + mtk_snfi_disable_clk(snfi);
> +
> +err_put_master:
> + spi_master_put(ctlr);
> +
> + return ret;
> +}
> +
> +static int mtk_snfi_remove(struct platform_device *pdev)
> +{
> + struct spi_controller *ctlr = dev_get_drvdata(&pdev->dev);
> + struct mtk_snfi *snfi = spi_controller_get_devdata(ctlr);
> + struct nand_ecc_engine *eng = snfi->engine;
> +
> + pm_runtime_disable(snfi->dev);
> + nand_ecc_unregister_on_host_hw_engine(eng);
> + kfree(snfi->tx_buf);
> + spi_master_put(ctlr);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int mtk_snfi_runtime_suspend(struct device *dev)
> +{
> + struct spi_controller *ctlr = dev_get_drvdata(dev);
> + struct mtk_snfi *snfi = spi_controller_get_devdata(ctlr);
> +
> + mtk_snfi_disable_clk(snfi);
> +
> + return 0;
> +}
> +
> +static int mtk_snfi_runtime_resume(struct device *dev)
> +{
> + struct spi_controller *ctlr = dev_get_drvdata(dev);
> + struct mtk_snfi *snfi = spi_controller_get_devdata(ctlr);
> + int ret;
> +
> + ret = mtk_snfi_enable_clk(dev, snfi);
> + if (ret)
> + return ret;
> +
> + ret = mtk_snfi_init(snfi);
> + if (ret)
> + dev_err(dev, "failed to init snfi\n");
> +
> + return ret;
> +}
> +#endif /* CONFIG_PM */
> +
> +static const struct dev_pm_ops mtk_snfi_pm_ops = {
> + SET_RUNTIME_PM_OPS(mtk_snfi_runtime_suspend,
> + mtk_snfi_runtime_resume, NULL)
> + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> + pm_runtime_force_resume)
> +};
> +
> +static struct platform_driver mtk_snfi_driver = {
> + .driver = {
> + .name = "mtk-snfi",
> + .of_match_table = mtk_snfi_id_table,
> + .pm = &mtk_snfi_pm_ops,
> + },
> + .probe = mtk_snfi_probe,
> + .remove = mtk_snfi_remove,
> +};
> +
> +module_platform_driver(mtk_snfi_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Xiangsheng Hou <xiangsheng.hou@mediatek.com>");
> +MODULE_DESCRIPTION("Mediatek SPI Nand Flash interface driver");
Otherwise looks good, I believe you can drop the RFC prefix now.
Thanks,
Miquèl
_______________________________________________
Linux-mediatek mailing list
Linux-mediatek@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-mediatek
WARNING: multiple messages have this Message-ID (diff)
From: Miquel Raynal <miquel.raynal@bootlin.com>
To: Xiangsheng Hou <xiangsheng.hou@mediatek.com>
Cc: <broonie@kernel.org>, <benliang.zhao@mediatek.com>,
<dandan.he@mediatek.com>, <guochun.mao@mediatek.com>,
<bin.zhang@mediatek.com>, <sanny.chen@mediatek.com>,
<mao.zhong@mediatek.com>, <yingjoe.chen@mediatek.com>,
<donghunt@amazon.com>, <rdlee@amazon.com>,
<linux-mtd@lists.infradead.org>,
<linux-mediatek@lists.infradead.org>,
<srv_heupstream@mediatek.com>
Subject: Re: [RFC,v4,3/5] spi: mtk: Add mediatek SPI Nand Flash interface driver
Date: Thu, 9 Dec 2021 11:20:30 +0100 [thread overview]
Message-ID: <20211209112030.5cdcd07c@xps13> (raw)
In-Reply-To: <20211130083202.14228-4-xiangsheng.hou@mediatek.com>
Hi Xiangsheng,
xiangsheng.hou@mediatek.com wrote on Tue, 30 Nov 2021 16:32:00 +0800:
> The SPI Nand Flash interface driver cowork with Mediatek pipelined
> HW ECC engine.
>
> Signed-off-by: Xiangsheng Hou <xiangsheng.hou@mediatek.com>
> ---
> drivers/spi/Kconfig | 11 +
> drivers/spi/Makefile | 1 +
> drivers/spi/spi-mtk-snfi.c | 1117 ++++++++++++++++++++++++++++++++++++
> 3 files changed, 1129 insertions(+)
> create mode 100644 drivers/spi/spi-mtk-snfi.c
>
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index 596705d24400..9cb6a173b1ef 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -535,6 +535,17 @@ config SPI_MT65XX
> say Y or M here.If you are not sure, say N.
> SPI drivers for Mediatek MT65XX and MT81XX series ARM SoCs.
>
> +config SPI_MTK_SNFI
> + tristate "MediaTek SPI NAND interface"
> + depends on MTD
> + select MTD_SPI_NAND
> + select MTD_NAND_ECC_MTK
> + help
> + This selects the SPI NAND FLASH interface(SNFI),
> + which could be found on MediaTek Soc.
> + Say Y or M here.If you are not sure, say N.
> + Note Parallel Nand and SPI NAND is alternative on MediaTek SoCs.
> +
> config SPI_MT7621
> tristate "MediaTek MT7621 SPI Controller"
> depends on RALINK || COMPILE_TEST
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index dd7393a6046f..57d11eecf662 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -73,6 +73,7 @@ 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_MTK_SNFI) += spi-mtk-snfi.o
> obj-$(CONFIG_SPI_MXIC) += spi-mxic.o
> obj-$(CONFIG_SPI_MXS) += spi-mxs.o
> obj-$(CONFIG_SPI_NPCM_FIU) += spi-npcm-fiu.o
> diff --git a/drivers/spi/spi-mtk-snfi.c b/drivers/spi/spi-mtk-snfi.c
> new file mode 100644
> index 000000000000..b4dce6d78176
> --- /dev/null
> +++ b/drivers/spi/spi-mtk-snfi.c
> @@ -0,0 +1,1117 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Driver for MediaTek SPI Nand Flash interface
> + *
> + * Copyright (C) 2021 MediaTek Inc.
> + * Authors: Xiangsheng Hou <xiangsheng.hou@mediatek.com>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/interrupt.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/mtd/nand.h>
> +#include <linux/mtd/nand-ecc-mtk.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/spi/spi.h>
> +#include <linux/spi/spi-mem.h>
> +
> +/* Registers used by the driver */
> +#define NFI_CNFG (0x00)
> +#define CNFG_DMA BIT(0)
> +#define CNFG_READ_EN BIT(1)
> +#define CNFG_DMA_BURST_EN BIT(2)
> +#define CNFG_HW_ECC_EN BIT(8)
> +#define CNFG_AUTO_FMT_EN BIT(9)
> +#define CNFG_OP_CUST GENMASK(14, 13)
> +#define NFI_PAGEFMT (0x04)
> +#define PAGEFMT_512_2K (0)
> +#define PAGEFMT_2K_4K (1)
> +#define PAGEFMT_4K_8K (2)
> +#define PAGEFMT_8K_16K (3)
> +#define PAGEFMT_PAGE_MASK GENMASK(1, 0)
> +#define PAGEFMT_SEC_SEL_512 BIT(2)
> +#define PAGEFMT_FDM_SHIFT (8)
> +#define PAGEFMT_FDM_ECC_SHIFT (12)
> +#define PAGEFMT_SPARE_SHIFT (16)
> +#define PAGEFMT_SPARE_MASK GENMASK(21, 16)
> +#define NFI_CON (0x08)
> +#define CON_FIFO_FLUSH BIT(0)
> +#define CON_NFI_RST BIT(1)
> +#define CON_BRD BIT(8)
> +#define CON_BWR BIT(9)
> +#define CON_SEC_SHIFT (12)
> +#define CON_SEC_MASK GENMASK(16, 12)
> +#define NFI_INTR_EN (0x10)
> +#define INTR_CUS_PROG_EN BIT(7)
> +#define INTR_CUS_READ_EN BIT(8)
> +#define INTR_IRQ_EN BIT(31)
> +#define NFI_INTR_STA (0x14)
> +#define NFI_CMD (0x20)
> +#define CMD_DUMMY (0x00)
> +#define NFI_STRDATA (0x40)
> +#define STAR_EN BIT(0)
> +#define NFI_STA (0x60)
> +#define NFI_FSM_MASK GENMASK(19, 16)
> +#define STA_EMP_PAGE BIT(12)
> +#define NFI_ADDRCNTR (0x70)
> +#define CNTR_MASK GENMASK(16, 12)
> +#define ADDRCNTR_SEC_SHIFT (12)
> +#define ADDRCNTR_SEC(val) \
> + (((val) & CNTR_MASK) >> ADDRCNTR_SEC_SHIFT)
> +#define NFI_STRADDR (0x80)
> +#define NFI_BYTELEN (0x84)
> +#define NFI_FDML(x) (0xA0 + (x) * sizeof(u32) * 2)
> +#define NFI_FDMM(x) (0xA4 + (x) * sizeof(u32) * 2)
> +#define NFI_MASTERSTA (0x224)
> +#define AHB_BUS_BUSY GENMASK(1, 0)
> +#define SNFI_MAC_CTL (0x500)
> +#define MAC_WIP BIT(0)
> +#define MAC_WIP_READY BIT(1)
> +#define MAC_TRIG BIT(2)
> +#define MAC_EN BIT(3)
> +#define MAC_SIO_SEL BIT(4)
> +#define SNFI_MAC_OUTL (0x504)
> +#define SNFI_MAC_INL (0x508)
> +#define SNFI_RD_CTL2 (0x510)
> +#define RD_CMD_MASK GENMASK(7, 0)
> +#define RD_DUMMY_SHIFT (8)
> +#define SNFI_RD_CTL3 (0x514)
> +#define RD_ADDR_MASK GENMASK(16, 0)
> +#define SNFI_PG_CTL1 (0x524)
> +#define WR_LOAD_CMD_MASK GENMASK(15, 8)
> +#define WR_LOAD_CMD_SHIFT (8)
> +#define SNFI_PG_CTL2 (0x528)
> +#define WR_LOAD_ADDR_MASK GENMASK(15, 0)
> +#define SNFI_MISC_CTL (0x538)
> +#define RD_CUSTOM_EN BIT(6)
> +#define WR_CUSTOM_EN BIT(7)
> +#define LATCH_LAT_SHIFT (8)
> +#define LATCH_LAT_MASK GENMASK(9, 8)
> +#define RD_MODE_X2 BIT(16)
> +#define RD_MODE_X4 BIT(17)
> +#define RD_MODE_DQUAL BIT(18)
> +#define RD_MODE_MASK GENMASK(18, 16)
> +#define WR_X4_EN BIT(20)
> +#define SW_RST BIT(28)
> +#define SNFI_MISC_CTL2 (0x53c)
> +#define WR_LEN_SHIFT (16)
> +#define SNFI_DLY_CTL3 (0x548)
> +#define SAM_DLY_MASK GENMASK(5, 0)
> +#define SNFI_STA_CTL1 (0x550)
> +#define SPI_STATE GENMASK(3, 0)
> +#define CUS_READ_DONE BIT(27)
> +#define CUS_PROG_DONE BIT(28)
> +#define SNFI_CNFG (0x55c)
> +#define SNFI_MODE_EN BIT(0)
> +#define SNFI_GPRAM_DATA (0x800)
> +#define SNFI_GPRAM_MAX_LEN (160)
> +
> +#define MTK_SNFI_TIMEOUT (500000)
> +#define MTK_SNFI_RESET_TIMEOUT (1000000)
> +#define MTK_SNFI_AUTOSUSPEND_DELAY (1000)
> +#define KB(x) ((x) * 1024UL)
> +
> +struct mtk_snfi_caps {
> + u8 pageformat_spare_shift;
> +};
> +
> +struct mtk_snfi {
> + struct device *dev;
> + struct completion done;
> + void __iomem *regs;
> + const struct mtk_snfi_caps *caps;
> +
> + struct clk *nfi_clk;
> + struct clk *snfi_clk;
> + struct clk *hclk;
> +
> + struct nand_ecc_engine *engine;
> +
> + u32 sample_delay;
> + u32 read_latency;
> +
> + void *tx_buf;
> + dma_addr_t dma_addr;
> +};
> +
> +static struct mtk_ecc_engine *mtk_snfi_to_ecc_engine(struct mtk_snfi *snfi)
> +{
> + return snfi->engine->priv;
> +}
> +
> +static void mtk_snfi_mac_enable(struct mtk_snfi *snfi)
> +{
> + u32 val;
> +
> + val = readl(snfi->regs + SNFI_MAC_CTL);
> + val &= ~MAC_SIO_SEL;
> + val |= MAC_EN;
> +
> + writel(val, snfi->regs + SNFI_MAC_CTL);
> +}
> +
> +static int mtk_snfi_mac_trigger(struct mtk_snfi *snfi)
> +{
> + int ret;
> + u32 val;
> +
> + val = readl(snfi->regs + SNFI_MAC_CTL);
> + val |= MAC_TRIG;
> + writel(val, snfi->regs + SNFI_MAC_CTL);
> +
> + ret = readl_poll_timeout_atomic(snfi->regs + SNFI_MAC_CTL,
> + val, val & MAC_WIP_READY,
> + 0, MTK_SNFI_TIMEOUT);
> + if (ret < 0) {
> + dev_err(snfi->dev, "wait for wip ready timeout\n");
> + return -EIO;
> + }
> +
> + ret = readl_poll_timeout_atomic(snfi->regs + SNFI_MAC_CTL,
> + val, !(val & MAC_WIP), 0,
> + MTK_SNFI_TIMEOUT);
> + if (ret < 0) {
> + dev_err(snfi->dev, "command write timeout\n");
> + return -EIO;
> + }
> +
> + return 0;
> +}
> +
> +static void mtk_snfi_mac_disable(struct mtk_snfi *snfi)
> +{
> + u32 val;
> +
> + val = readl(snfi->regs + SNFI_MAC_CTL);
> + val &= ~(MAC_TRIG | MAC_EN);
> + writel(val, snfi->regs + SNFI_MAC_CTL);
> +}
> +
> +static int mtk_snfi_mac_op(struct mtk_snfi *snfi)
> +{
> + int ret;
> +
> + mtk_snfi_mac_enable(snfi);
> + ret = mtk_snfi_mac_trigger(snfi);
> + mtk_snfi_mac_disable(snfi);
> +
> + return ret;
> +}
> +
> +static inline void mtk_snfi_read_oob_free(struct mtk_snfi *snfi,
> + const struct spi_mem_op *op)
> +{
> + struct mtk_ecc_engine *eng = mtk_snfi_to_ecc_engine(snfi);
> + u8 *oobptr = op->data.buf.in;
> + u32 vall, valm;
> + int i, j;
> +
> + oobptr += eng->section_size * eng->nsteps;
> + for (i = 0; i < eng->nsteps; i++) {
> + vall = readl(snfi->regs + NFI_FDML(i));
> + valm = readl(snfi->regs + NFI_FDMM(i));
> +
> + for (j = 0; j < eng->oob_free; j++)
> + oobptr[j] = (j >= 4 ? valm : vall) >> ((j % 4) * 8);
> +
> + oobptr += eng->oob_free;
> + }
> +}
> +
> +static inline void mtk_snfi_write_oob_free(struct mtk_snfi *snfi,
> + const struct spi_mem_op *op)
> +{
> + struct mtk_ecc_engine *eng = mtk_snfi_to_ecc_engine(snfi);
> + const u8 *oobptr = op->data.buf.out;
> + u32 vall, valm;
> + int i, j;
> +
> + oobptr += eng->section_size * eng->nsteps;
> + for (i = 0; i < eng->nsteps; i++) {
> + vall = 0;
> + valm = 0;
> + for (j = 0; j < 8; j++) {
> + if (j < 4)
> + vall |= (j < eng->oob_free ? oobptr[j] : 0xff)
> + << (j * 8);
> + else
> + valm |= (j < eng->oob_free ? oobptr[j] : 0xff)
> + << ((j - 4) * 8);
> + }
> +
> + writel(vall, snfi->regs + NFI_FDML(i));
> + writel(valm, snfi->regs + NFI_FDMM(i));
> + oobptr += eng->oob_free;
> + }
> +}
> +
> +static irqreturn_t mtk_snfi_irq(int irq, void *id)
> +{
> + struct mtk_snfi *snfi = id;
> + u32 sta, ien;
> +
> + sta = readl(snfi->regs + NFI_INTR_STA);
> + ien = readl(snfi->regs + NFI_INTR_EN);
> +
> + if (!(sta & ien))
> + return IRQ_NONE;
> +
> + writel(0, snfi->regs + NFI_INTR_EN);
> + complete(&snfi->done);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int mtk_snfi_enable_clk(struct device *dev, struct mtk_snfi *snfi)
> +{
> + int ret;
> +
> + ret = clk_prepare_enable(snfi->nfi_clk);
> + if (ret) {
> + dev_err(dev, "failed to enable nfi clk\n");
> + return ret;
> + }
> +
> + ret = clk_prepare_enable(snfi->snfi_clk);
> + if (ret) {
> + dev_err(dev, "failed to enable snfi clk\n");
> + clk_disable_unprepare(snfi->nfi_clk);
> + return ret;
> + }
> +
> + ret = clk_prepare_enable(snfi->hclk);
> + if (ret) {
> + dev_err(dev, "failed to enable hclk\n");
> + clk_disable_unprepare(snfi->nfi_clk);
> + clk_disable_unprepare(snfi->snfi_clk);
definitely deserves goto statements :)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void mtk_snfi_disable_clk(struct mtk_snfi *snfi)
> +{
> + clk_disable_unprepare(snfi->nfi_clk);
> + clk_disable_unprepare(snfi->snfi_clk);
> + clk_disable_unprepare(snfi->hclk);
> +}
> +
> +static int mtk_snfi_reset(struct mtk_snfi *snfi)
> +{
> + u32 val;
> + int ret;
> +
> + val = readl(snfi->regs + SNFI_MISC_CTL) | SW_RST;
> + writel(val, snfi->regs + SNFI_MISC_CTL);
> +
> + ret = readw_poll_timeout(snfi->regs + SNFI_STA_CTL1, val,
> + !(val & SPI_STATE), 0,
> + MTK_SNFI_RESET_TIMEOUT);
> + if (ret) {
> + dev_warn(snfi->dev, "wait spi idle timeout 0x%x\n", val);
> + return ret;
> + }
> +
> + val = readl(snfi->regs + SNFI_MISC_CTL);
> + val &= ~SW_RST;
> + writel(val, snfi->regs + SNFI_MISC_CTL);
> +
> + writew(CON_FIFO_FLUSH | CON_NFI_RST, snfi->regs + NFI_CON);
> + ret = readw_poll_timeout(snfi->regs + NFI_STA, val,
> + !(val & NFI_FSM_MASK), 0,
> + MTK_SNFI_RESET_TIMEOUT);
> + if (ret) {
> + dev_warn(snfi->dev, "wait nfi fsm idle timeout 0x%x\n", val);
> + return ret;
> + }
> +
> + val = readl(snfi->regs + NFI_STRDATA);
> + val &= ~STAR_EN;
> + writew(val, snfi->regs + NFI_STRDATA);
> +
> + return 0;
> +}
> +
> +static int mtk_snfi_init(struct mtk_snfi *snfi)
> +{
> + int ret;
> + u32 val;
> +
> + ret = mtk_snfi_reset(snfi);
> + if (ret)
> + return ret;
> +
> + writel(SNFI_MODE_EN, snfi->regs + SNFI_CNFG);
> +
> + if (snfi->sample_delay) {
> + val = readl(snfi->regs + SNFI_DLY_CTL3);
> + val &= ~SAM_DLY_MASK;
> + val |= snfi->sample_delay;
> + writel(val, snfi->regs + SNFI_DLY_CTL3);
> + }
> +
> + if (snfi->read_latency) {
> + val = readl(snfi->regs + SNFI_MISC_CTL);
> + val &= ~LATCH_LAT_MASK;
> + val |= (snfi->read_latency << LATCH_LAT_SHIFT);
> + writel(val, snfi->regs + SNFI_MISC_CTL);
> + }
> +
> + return 0;
> +}
> +
> +static void mtk_snfi_prepare_for_tx(struct mtk_snfi *snfi,
> + const struct spi_mem_op *op)
> +{
> + struct mtk_ecc_engine *eng = mtk_snfi_to_ecc_engine(snfi);
> + u32 val;
> +
> + val = readl(snfi->regs + SNFI_PG_CTL1);
> + val &= ~WR_LOAD_CMD_MASK;
> + val |= op->cmd.opcode << WR_LOAD_CMD_SHIFT;
> + writel(val, snfi->regs + SNFI_PG_CTL1);
> +
> + writel(op->addr.val & WR_LOAD_ADDR_MASK,
> + snfi->regs + SNFI_PG_CTL2);
> +
> + val = readl(snfi->regs + SNFI_MISC_CTL);
> + val |= WR_CUSTOM_EN;
> + if (op->data.buswidth == 4)
> + val |= WR_X4_EN;
> + writel(val, snfi->regs + SNFI_MISC_CTL);
> +
> + val = eng->nsteps * (eng->oob_per_section + eng->section_size);
> + writel(val << WR_LEN_SHIFT, snfi->regs + SNFI_MISC_CTL2);
> +
> + writel(INTR_CUS_PROG_EN | INTR_IRQ_EN, snfi->regs + NFI_INTR_EN);
> +}
> +
> +static void mtk_snfi_prepare_for_rx(struct mtk_snfi *snfi,
> + const struct spi_mem_op *op)
> +{
> + struct mtk_ecc_engine *eng = mtk_snfi_to_ecc_engine(snfi);
> + u32 val, dummy_cycle;
> +
> + dummy_cycle = (op->dummy.nbytes << 3) >>
> + (ffs(op->dummy.buswidth) - 1);
> + val = (op->cmd.opcode & RD_CMD_MASK) |
> + (dummy_cycle << RD_DUMMY_SHIFT);
> + writel(val, snfi->regs + SNFI_RD_CTL2);
> +
> + writel(op->addr.val & RD_ADDR_MASK,
> + snfi->regs + SNFI_RD_CTL3);
> +
> + val = readl(snfi->regs + SNFI_MISC_CTL);
> + val |= RD_CUSTOM_EN;
> + val &= ~RD_MODE_MASK;
> + if (op->data.buswidth == 4)
> + val |= RD_MODE_X4;
> + else if (op->data.buswidth == 2)
> + val |= RD_MODE_X2;
> +
> + if (op->addr.buswidth != 1)
> + val |= RD_MODE_DQUAL;
> +
> + writel(val, snfi->regs + SNFI_MISC_CTL);
> +
> + val = eng->nsteps * (eng->oob_per_section + eng->section_size);
> + writel(val, snfi->regs + SNFI_MISC_CTL2);
> +
> + writel(INTR_CUS_READ_EN | INTR_IRQ_EN, snfi->regs + NFI_INTR_EN);
> +}
> +
> +static int mtk_snfi_prepare(struct mtk_snfi *snfi,
> + const struct spi_mem_op *op, bool rx)
> +{
> + struct mtk_ecc_engine *eng = mtk_snfi_to_ecc_engine(snfi);
> + dma_addr_t addr;
> + int ret;
> + u32 val;
> +
> + addr = dma_map_single(snfi->dev,
> + op->data.buf.in, op->data.nbytes,
> + rx ? DMA_FROM_DEVICE : DMA_TO_DEVICE);
> + ret = dma_mapping_error(snfi->dev, addr);
> + if (ret) {
> + dev_err(snfi->dev, "dma mapping error\n");
> + return -EINVAL;
> + }
> +
> + snfi->dma_addr = addr;
> + writel(lower_32_bits(addr), snfi->regs + NFI_STRADDR);
> +
> + if (op->ecc_en && !rx)
> + mtk_snfi_write_oob_free(snfi, op);
> +
> + val = readw(snfi->regs + NFI_CNFG);
> + val |= CNFG_DMA | CNFG_DMA_BURST_EN | CNFG_OP_CUST;
> + val |= rx ? CNFG_READ_EN : 0;
> +
> + if (op->ecc_en)
> + val |= CNFG_HW_ECC_EN | CNFG_AUTO_FMT_EN;
> +
> + writew(val, snfi->regs + NFI_CNFG);
> +
> + writel(eng->nsteps << CON_SEC_SHIFT, snfi->regs + NFI_CON);
> +
> + init_completion(&snfi->done);
> +
> + /* trigger state machine to custom op mode */
> + writel(CMD_DUMMY, snfi->regs + NFI_CMD);
> +
> + if (rx)
> + mtk_snfi_prepare_for_rx(snfi, op);
> + else
> + mtk_snfi_prepare_for_tx(snfi, op);
> +
> + return 0;
> +}
> +
> +static void mtk_snfi_trigger(struct mtk_snfi *snfi,
> + const struct spi_mem_op *op, bool rx)
> +{
> + u32 val;
> +
> + val = readl(snfi->regs + NFI_CON);
> + val |= rx ? CON_BRD : CON_BWR;
> + writew(val, snfi->regs + NFI_CON);
> +
> + writew(STAR_EN, snfi->regs + NFI_STRDATA);
> +}
> +
> +static int mtk_snfi_wait_done(struct mtk_snfi *snfi,
> + const struct spi_mem_op *op, bool rx)
> +{
> + struct mtk_ecc_engine *eng = mtk_snfi_to_ecc_engine(snfi);
> + struct device *dev = snfi->dev;
> + u32 val;
> + int ret;
> +
> + ret = wait_for_completion_timeout(&snfi->done, msecs_to_jiffies(500));
> + if (!ret) {
> + dev_err(dev, "wait for %d completion done timeout\n", rx);
> + return -ETIMEDOUT;
> + }
> +
> + if (rx) {
> + ret = readl_poll_timeout_atomic(snfi->regs + NFI_BYTELEN,
> + val,
> + ADDRCNTR_SEC(val) >=
> + eng->nsteps,
> + 0, MTK_SNFI_TIMEOUT);
> + if (ret) {
> + dev_err(dev, "wait for rx section count timeout\n");
> + return -ETIMEDOUT;
> + }
> +
> + ret = readl_poll_timeout_atomic(snfi->regs + NFI_MASTERSTA,
> + val,
> + !(val & AHB_BUS_BUSY),
> + 0, MTK_SNFI_TIMEOUT);
> + if (ret) {
> + dev_err(dev, "wait for bus busy timeout\n");
> + return -ETIMEDOUT;
> + }
> + } else {
> + ret = readl_poll_timeout_atomic(snfi->regs + NFI_ADDRCNTR,
> + val,
> + ADDRCNTR_SEC(val) >=
> + eng->nsteps,
> + 0, MTK_SNFI_TIMEOUT);
> + if (ret) {
> + dev_err(dev, "wait for tx section count timeout\n");
> + return -ETIMEDOUT;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static void mtk_snfi_complete(struct mtk_snfi *snfi,
> + const struct spi_mem_op *op, bool rx)
> +{
> + u32 val;
> +
> + dma_unmap_single(snfi->dev,
> + snfi->dma_addr, op->data.nbytes,
> + rx ? DMA_FROM_DEVICE : DMA_TO_DEVICE);
> +
> + if (op->ecc_en && rx)
> + mtk_snfi_read_oob_free(snfi, op);
> +
> + val = readl(snfi->regs + SNFI_MISC_CTL);
> + val &= rx ? ~RD_CUSTOM_EN : ~WR_CUSTOM_EN;
> + writel(val, snfi->regs + SNFI_MISC_CTL);
> +
> + val = readl(snfi->regs + SNFI_STA_CTL1);
> + val |= rx ? CUS_READ_DONE : CUS_PROG_DONE;
> + writew(val, snfi->regs + SNFI_STA_CTL1);
> + val &= rx ? ~CUS_READ_DONE : ~CUS_PROG_DONE;
> + writew(val, snfi->regs + SNFI_STA_CTL1);
> +
> + /* Disable interrupt */
> + val = readl(snfi->regs + NFI_INTR_EN);
> + val &= rx ? ~INTR_CUS_READ_EN : ~INTR_CUS_PROG_EN;
> + writew(val, snfi->regs + NFI_INTR_EN);
> +
> + writew(0, snfi->regs + NFI_CNFG);
> + writew(0, snfi->regs + NFI_CON);
> +}
> +
> +static int mtk_snfi_transfer_dma(struct mtk_snfi *snfi,
> + const struct spi_mem_op *op, bool rx)
> +{
> + int ret;
> +
> + ret = mtk_snfi_prepare(snfi, op, rx);
> + if (ret)
> + return ret;
> +
> + mtk_snfi_trigger(snfi, op, rx);
> +
> + ret = mtk_snfi_wait_done(snfi, op, rx);
> +
> + mtk_snfi_complete(snfi, op, rx);
> +
> + return ret;
> +}
> +
> +static int mtk_snfi_transfer_mac(struct mtk_snfi *snfi,
> + const u8 *txbuf, u8 *rxbuf,
> + const u32 txlen, const u32 rxlen)
> +{
> + u32 i, j, val, tmp;
> + u8 *p_tmp = (u8 *)(&tmp);
> + u32 offset = 0;
> + int ret = 0;
> +
> + /* Move tx data to gpram in snfi mac mode */
> + for (i = 0; i < txlen; ) {
> + for (j = 0, tmp = 0; i < txlen && j < 4; i++, j++)
> + p_tmp[j] = txbuf[i];
> +
> + writel(tmp, snfi->regs + SNFI_GPRAM_DATA + offset);
> + offset += 4;
> + }
> +
> + writel(txlen, snfi->regs + SNFI_MAC_OUTL);
> + writel(rxlen, snfi->regs + SNFI_MAC_INL);
> +
> + ret = mtk_snfi_mac_op(snfi);
> + if (ret) {
> + dev_warn(snfi->dev, "snfi mac operation fail\n");
> + return ret;
> + }
> +
> + /* Get tx data from gpram in snfi mac mode */
> + if (rxlen)
> + for (i = 0, offset = rounddown(txlen, 4); i < rxlen; ) {
> + val = readl(snfi->regs +
> + SNFI_GPRAM_DATA + offset);
> + for (j = 0; i < rxlen && j < 4; i++, j++, rxbuf++) {
> + if (i == 0)
> + j = txlen % 4;
> + *rxbuf = (val >> (j * 8)) & 0xff;
> + }
> + offset += 4;
> + }
> +
> + return ret;
> +}
> +
> +static int mtk_snfi_exec_op(struct spi_mem *mem,
> + const struct spi_mem_op *op)
> +{
> + struct mtk_snfi *snfi = spi_controller_get_devdata(mem->spi->master);
> + u8 *buf, *txbuf = snfi->tx_buf, *rxbuf = NULL;
> + u32 txlen = 0, rxlen = 0;
> + int i, ret = 0;
> + bool rx;
> +
> + rx = op->data.dir == SPI_MEM_DATA_IN;
> +
> + ret = mtk_snfi_reset(snfi);
> + if (ret) {
> + dev_warn(snfi->dev, "snfi reset fail\n");
> + return ret;
> + }
> +
> + /*
> + * If tx/rx data buswidth is not 0/1, use snfi DMA mode.
> + * Otherwise, use snfi mac mode.
> + */
> + if (op->data.buswidth != 1 && op->data.buswidth != 0) {
> + ret = mtk_snfi_transfer_dma(snfi, op, rx);
> + if (ret)
> + dev_warn(snfi->dev, "snfi dma transfer %d fail %d\n",
> + rx, ret);
> + return ret;
> + }
> +
> + txbuf[txlen++] = op->cmd.opcode;
> +
> + if (op->addr.nbytes)
> + for (i = 0; i < op->addr.nbytes; i++)
> + txbuf[txlen++] = op->addr.val >>
> + (8 * (op->addr.nbytes - i - 1));
> +
> + txlen += op->dummy.nbytes;
> +
> + if (op->data.dir == SPI_MEM_DATA_OUT) {
> + buf = (u8 *)op->data.buf.out;
> + for (i = 0; i < op->data.nbytes; i++)
> + txbuf[txlen++] = buf[i];
> + }
> +
> + if (op->data.dir == SPI_MEM_DATA_IN) {
> + rxbuf = (u8 *)op->data.buf.in;
> + rxlen = op->data.nbytes;
> + }
> +
> + ret = mtk_snfi_transfer_mac(snfi, txbuf, rxbuf, txlen, rxlen);
> + if (ret)
> + dev_warn(snfi->dev, "snfi mac transfer %d fail %d\n",
> + op->data.dir, ret);
> +
> + return ret;
> +}
> +
> +static int mtk_snfi_check_buswidth(u8 width)
> +{
> + switch (width) {
> + case 1:
> + case 2:
> + case 4:
> + return 0;
> +
> + default:
> + break;
> + }
> +
> + return -EOPNOTSUPP;
> +}
> +
> +static bool mtk_snfi_supports_op(struct spi_mem *mem,
> + const struct spi_mem_op *op)
> +{
> + int ret = 0;
> +
> + if (!spi_mem_default_supports_op(mem, op))
With the integration properly set, this call would always return false
when ecc_en = true. You should switch to
spi_mem_generic_supports_op(mem, op, false, true);
> + return false;
> +
> + if (op->cmd.buswidth != 1)
> + return false;
> +
> + /*
> + * For one operation will use snfi mac mode when data
> + * buswidth is 0/1. However, the HW ECC engine can not
> + * be used in mac mode.
> + */
> + if (op->ecc_en && op->data.buswidth == 1 &&
> + op->data.nbytes >= SNFI_GPRAM_MAX_LEN)
> + return false;
> +
> + switch (op->data.dir) {
> + /* For spi mem data in, can support 1/2/4 buswidth */
> + case SPI_MEM_DATA_IN:
> + if (op->addr.nbytes)
> + ret |= mtk_snfi_check_buswidth(op->addr.buswidth);
> +
> + if (op->dummy.nbytes)
> + ret |= mtk_snfi_check_buswidth(op->dummy.buswidth);
> +
> + if (op->data.nbytes)
> + ret |= mtk_snfi_check_buswidth(op->data.buswidth);
> +
> + if (ret)
> + return false;
> +
> + break;
> + case SPI_MEM_DATA_OUT:
> + /*
> + * For spi mem data out, can support 0/1 buswidth
> + * for addr/dummy and 1/4 buswidth for data.
> + */
> + if (op->addr.buswidth != 0 && op->addr.buswidth != 1)
> + return false;
> +
> + if (op->dummy.buswidth != 0 && op->dummy.buswidth != 1)
> + return false;
> +
> + if (op->data.buswidth != 1 && op->data.buswidth != 4)
> + return false;
> +
> + break;
> + default:
> + break;
> + }
> +
> + return true;
> +}
> +
> +static int mtk_snfi_adjust_op_size(struct spi_mem *mem,
> + struct spi_mem_op *op)
> +{
> + u32 len, max_len;
> +
> + /*
> + * The op size only support SNFI_GPRAM_MAX_LEN which will
> + * use the snfi mac mode when data buswidth is 0/1.
> + * Otherwise, the snfi can max support 16KB.
> + */
> + if (op->data.buswidth == 1 || op->data.buswidth == 0)
> + max_len = SNFI_GPRAM_MAX_LEN;
> + else
> + max_len = KB(16);
> +
> + len = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes;
> + if (len > max_len)
> + return -EOPNOTSUPP;
> +
> + if ((len + op->data.nbytes) > max_len)
> + op->data.nbytes = max_len - len;
> +
> + return 0;
> +}
> +
> +static const struct mtk_snfi_caps mtk_snfi_caps_mt7622 = {
> + .pageformat_spare_shift = 16,
> +};
> +
> +static const struct spi_controller_mem_ops mtk_snfi_ops = {
> + .adjust_op_size = mtk_snfi_adjust_op_size,
> + .supports_op = mtk_snfi_supports_op,
> + .exec_op = mtk_snfi_exec_op,
> +};
> +
> +static const struct of_device_id mtk_snfi_id_table[] = {
> + { .compatible = "mediatek,mt7622-snfi",
> + .data = &mtk_snfi_caps_mt7622,
> + },
> + { /* sentinel */ }
> +};
> +
> +/* ECC wrapper */
> +static struct mtk_snfi *mtk_nand_to_spi(struct nand_device *nand)
> +{
> + struct device *dev = nand->ecc.engine->dev;
> + struct spi_master *master = dev_get_drvdata(dev);
> + struct mtk_snfi *snfi = spi_master_get_devdata(master);
> +
> + return snfi;
> +}
> +
> +static int mtk_snfi_config(struct nand_device *nand,
> + struct mtk_snfi *snfi)
> +{
> + struct mtk_ecc_engine *eng = mtk_snfi_to_ecc_engine(snfi);
> + u32 val;
> +
> + switch (nanddev_page_size(nand)) {
> + case 512:
> + val = PAGEFMT_512_2K | PAGEFMT_SEC_SEL_512;
> + break;
> + case KB(2):
> + if (eng->section_size == 512)
> + val = PAGEFMT_2K_4K | PAGEFMT_SEC_SEL_512;
> + else
> + val = PAGEFMT_512_2K;
> + break;
> + case KB(4):
> + if (eng->section_size == 512)
> + val = PAGEFMT_4K_8K | PAGEFMT_SEC_SEL_512;
> + else
> + val = PAGEFMT_2K_4K;
> + break;
> + case KB(8):
> + if (eng->section_size == 512)
> + val = PAGEFMT_8K_16K | PAGEFMT_SEC_SEL_512;
> + else
> + val = PAGEFMT_4K_8K;
> + break;
> + case KB(16):
> + val = PAGEFMT_8K_16K;
> + break;
> + default:
> + dev_err(snfi->dev, "invalid page len: %d\n",
> + nanddev_page_size(nand));
> + return -EINVAL;
> + }
> +
> + val |= eng->oob_per_section_idx << PAGEFMT_SPARE_SHIFT;
> + val |= eng->oob_free << PAGEFMT_FDM_SHIFT;
> + val |= eng->oob_free_protected << PAGEFMT_FDM_ECC_SHIFT;
> + writel(val, snfi->regs + NFI_PAGEFMT);
Shouldn't this be calculated only once?
> +
> + return 0;
> +}
> +
> +static int mtk_snfi_ecc_init_ctx(struct nand_device *nand)
> +{
> + struct nand_ecc_engine_ops *ops = mtk_ecc_get_pipelined_ops();
> +
> + return ops->init_ctx(nand);
> +}
> +
> +static void mtk_snfi_ecc_cleanup_ctx(struct nand_device *nand)
> +{
> + struct nand_ecc_engine_ops *ops = mtk_ecc_get_pipelined_ops();
> +
> + ops->cleanup_ctx(nand);
> +}
> +
> +static int mtk_snfi_ecc_prepare_io_req(struct nand_device *nand,
> + struct nand_page_io_req *req)
> +{
> + struct nand_ecc_engine_ops *ops = mtk_ecc_get_pipelined_ops();
> + struct mtk_snfi *snfi = mtk_nand_to_spi(nand);
> + int ret;
> +
> + ret = mtk_snfi_config(nand, snfi);
> + if (ret)
> + return ret;
> +
> + return ops->prepare_io_req(nand, req);
> +}
> +
> +static int mtk_snfi_ecc_finish_io_req(struct nand_device *nand,
> + struct nand_page_io_req *req)
> +{
> + struct nand_ecc_engine_ops *ops = mtk_ecc_get_pipelined_ops();
> + struct mtk_ecc_engine *eng = nand_to_ecc_ctx(nand);
> + struct mtk_snfi *snfi = mtk_nand_to_spi(nand);
> +
> + if (req->mode != MTD_OPS_RAW)
> + eng->read_empty = readl(snfi->regs + NFI_STA) & STA_EMP_PAGE;
> +
> + return ops->finish_io_req(nand, req);
> +}
> +
> +static struct nand_ecc_engine_ops mtk_snfi_ecc_engine_pipelined_ops = {
> + .init_ctx = mtk_snfi_ecc_init_ctx,
> + .cleanup_ctx = mtk_snfi_ecc_cleanup_ctx,
> + .prepare_io_req = mtk_snfi_ecc_prepare_io_req,
> + .finish_io_req = mtk_snfi_ecc_finish_io_req,
> +};
> +
> +static int mtk_snfi_ecc_probe(struct platform_device *pdev,
> + struct mtk_snfi *snfi)
> +{
> + struct nand_ecc_engine *ecceng;
> +
> + if (!mtk_ecc_get_pipelined_ops())
> + return -EOPNOTSUPP;
> +
> + ecceng = devm_kzalloc(&pdev->dev, sizeof(*ecceng), GFP_KERNEL);
> + if (!ecceng)
> + return -ENOMEM;
> +
> + ecceng->dev = &pdev->dev;
> + ecceng->ops = &mtk_snfi_ecc_engine_pipelined_ops;
You need to tell the core that this is a pipelined engine (look at the
integration entry).
> +
> + nand_ecc_register_on_host_hw_engine(ecceng);
> +
> + snfi->engine = ecceng;
> +
> + return 0;
> +}
> +
> +static int mtk_snfi_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct spi_controller *ctlr;
> + struct mtk_snfi *snfi;
> + struct resource *res;
> + int ret, irq;
> + u32 val = 0;
> +
> + ctlr = spi_alloc_master(&pdev->dev, sizeof(*snfi));
> + if (!ctlr)
> + return -ENOMEM;
> +
> + snfi = spi_controller_get_devdata(ctlr);
> + snfi->dev = &pdev->dev;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + snfi->regs = devm_ioremap_resource(snfi->dev, res);
> + if (IS_ERR(snfi->regs)) {
> + ret = PTR_ERR(snfi->regs);
> + goto err_put_master;
> + }
> +
> + ret = of_property_read_u32(np, "sample-delay", &val);
> + if (!ret)
> + snfi->sample_delay = val;
> +
> + ret = of_property_read_u32(np, "read-latency", &val);
> + if (!ret)
> + snfi->read_latency = val;
> +
> + snfi->nfi_clk = devm_clk_get(snfi->dev, "nfi_clk");
> + if (IS_ERR(snfi->nfi_clk)) {
> + dev_err(snfi->dev, "not found nfi clk\n");
> + ret = PTR_ERR(snfi->nfi_clk);
> + goto err_put_master;
> + }
> +
> + snfi->snfi_clk = devm_clk_get(snfi->dev, "snfi_clk");
> + if (IS_ERR(snfi->snfi_clk)) {
> + dev_err(snfi->dev, "not found snfi clk\n");
> + ret = PTR_ERR(snfi->snfi_clk);
> + goto err_put_master;
> + }
> +
> + snfi->hclk = devm_clk_get(snfi->dev, "hclk");
> + if (IS_ERR(snfi->hclk)) {
> + dev_err(snfi->dev, "not found hclk\n");
> + ret = PTR_ERR(snfi->hclk);
> + goto err_put_master;
> + }
> +
> + ret = mtk_snfi_enable_clk(snfi->dev, snfi);
> + if (ret)
> + goto err_put_master;
> +
> + snfi->caps = of_device_get_match_data(snfi->dev);
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0) {
> + dev_err(snfi->dev, "not found snfi irq resource\n");
> + ret = -EINVAL;
> + goto clk_disable;
> + }
> +
> + ret = devm_request_irq(snfi->dev, irq, mtk_snfi_irq,
> + 0, "mtk-snfi", snfi);
> + if (ret) {
> + dev_err(snfi->dev, "failed to request snfi irq\n");
> + goto clk_disable;
> + }
> +
> + ret = dma_set_mask(snfi->dev, DMA_BIT_MASK(32));
> + if (ret) {
> + dev_err(snfi->dev, "failed to set dma mask\n");
> + goto clk_disable;
> + }
> +
> + snfi->tx_buf = kzalloc(SNFI_GPRAM_MAX_LEN, GFP_KERNEL);
> + if (!snfi->tx_buf) {
> + ret = -ENOMEM;
> + goto clk_disable;
> + }
> +
> + ctlr->dev.of_node = np;
> + ctlr->mem_ops = &mtk_snfi_ops;
> + ctlr->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_QUAD;
> + ctlr->auto_runtime_pm = true;
> +
> + dev_set_drvdata(snfi->dev, ctlr);
> +
> + ret = mtk_snfi_init(snfi);
> + if (ret) {
> + dev_err(snfi->dev, "failed to init snfi\n");
> + goto free_buf;
> + }
> +
> + ret = mtk_snfi_ecc_probe(pdev, snfi);
> + if (ret) {
> + dev_warn(snfi->dev, "ECC engine not available\n");
> + goto free_buf;
> + }
> +
> + pm_runtime_enable(snfi->dev);
> +
> + ret = devm_spi_register_master(snfi->dev, ctlr);
> + if (ret) {
> + dev_err(snfi->dev, "failed to register spi master\n");
> + goto disable_pm_runtime;
> + }
> +
> + return 0;
> +
> +disable_pm_runtime:
> + pm_runtime_disable(snfi->dev);
> +
> +free_buf:
> + kfree(snfi->tx_buf);
> +
> +clk_disable:
> + mtk_snfi_disable_clk(snfi);
> +
> +err_put_master:
> + spi_master_put(ctlr);
> +
> + return ret;
> +}
> +
> +static int mtk_snfi_remove(struct platform_device *pdev)
> +{
> + struct spi_controller *ctlr = dev_get_drvdata(&pdev->dev);
> + struct mtk_snfi *snfi = spi_controller_get_devdata(ctlr);
> + struct nand_ecc_engine *eng = snfi->engine;
> +
> + pm_runtime_disable(snfi->dev);
> + nand_ecc_unregister_on_host_hw_engine(eng);
> + kfree(snfi->tx_buf);
> + spi_master_put(ctlr);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int mtk_snfi_runtime_suspend(struct device *dev)
> +{
> + struct spi_controller *ctlr = dev_get_drvdata(dev);
> + struct mtk_snfi *snfi = spi_controller_get_devdata(ctlr);
> +
> + mtk_snfi_disable_clk(snfi);
> +
> + return 0;
> +}
> +
> +static int mtk_snfi_runtime_resume(struct device *dev)
> +{
> + struct spi_controller *ctlr = dev_get_drvdata(dev);
> + struct mtk_snfi *snfi = spi_controller_get_devdata(ctlr);
> + int ret;
> +
> + ret = mtk_snfi_enable_clk(dev, snfi);
> + if (ret)
> + return ret;
> +
> + ret = mtk_snfi_init(snfi);
> + if (ret)
> + dev_err(dev, "failed to init snfi\n");
> +
> + return ret;
> +}
> +#endif /* CONFIG_PM */
> +
> +static const struct dev_pm_ops mtk_snfi_pm_ops = {
> + SET_RUNTIME_PM_OPS(mtk_snfi_runtime_suspend,
> + mtk_snfi_runtime_resume, NULL)
> + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> + pm_runtime_force_resume)
> +};
> +
> +static struct platform_driver mtk_snfi_driver = {
> + .driver = {
> + .name = "mtk-snfi",
> + .of_match_table = mtk_snfi_id_table,
> + .pm = &mtk_snfi_pm_ops,
> + },
> + .probe = mtk_snfi_probe,
> + .remove = mtk_snfi_remove,
> +};
> +
> +module_platform_driver(mtk_snfi_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Xiangsheng Hou <xiangsheng.hou@mediatek.com>");
> +MODULE_DESCRIPTION("Mediatek SPI Nand Flash interface driver");
Otherwise looks good, I believe you can drop the RFC prefix now.
Thanks,
Miquèl
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
next prev parent reply other threads:[~2021-12-09 10:20 UTC|newest]
Thread overview: 36+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-11-30 8:31 [RFC,v4,0/5] Add Mediatek SPI Nand controller and convert ECC driver Xiangsheng Hou
2021-11-30 8:31 ` Xiangsheng Hou
2021-11-30 8:31 ` [RFC,v4,1/5] mtd: nand: ecc: Move mediatek " Xiangsheng Hou
2021-11-30 8:31 ` Xiangsheng Hou
2021-11-30 8:31 ` [RFC,v4,2/5] mtd: nand: ecc: mtk: Convert to the ECC infrastructure Xiangsheng Hou
2021-11-30 8:31 ` Xiangsheng Hou
2021-12-09 10:32 ` Miquel Raynal
2021-12-09 10:32 ` Miquel Raynal
2021-12-10 9:09 ` xiangsheng.hou
2021-12-10 9:09 ` xiangsheng.hou
2021-12-10 9:34 ` Miquel Raynal
2021-12-10 9:34 ` Miquel Raynal
2021-12-11 3:25 ` xiangsheng.hou
2021-12-11 3:25 ` xiangsheng.hou
2021-12-13 9:29 ` Miquel Raynal
2021-12-13 9:29 ` Miquel Raynal
2021-12-14 3:32 ` xiangsheng.hou
2021-12-14 3:32 ` xiangsheng.hou
2021-12-14 9:47 ` Miquel Raynal
2021-12-14 9:47 ` Miquel Raynal
2021-11-30 8:32 ` [RFC,v4,3/5] spi: mtk: Add mediatek SPI Nand Flash interface driver Xiangsheng Hou
2021-11-30 8:32 ` Xiangsheng Hou
2021-12-09 10:20 ` Miquel Raynal [this message]
2021-12-09 10:20 ` Miquel Raynal
2021-12-10 9:09 ` xiangsheng.hou
2021-12-10 9:09 ` xiangsheng.hou
2021-12-10 9:40 ` Miquel Raynal
2021-12-10 9:40 ` Miquel Raynal
2021-11-30 8:32 ` [RFC, v4, 4/5] mtd: spinand: Move set/get OOB databytes to each ECC engines Xiangsheng Hou
2021-11-30 8:32 ` Xiangsheng Hou
2021-12-14 11:41 ` [RFC,v4,4/5] " Miquel Raynal
2021-12-14 11:41 ` Miquel Raynal
2021-12-20 7:37 ` xiangsheng.hou
2021-12-20 7:37 ` xiangsheng.hou
2021-11-30 8:32 ` [RFC,v4,5/5] arm64: dts: mtk: Add snfi node Xiangsheng Hou
2021-11-30 8:32 ` Xiangsheng Hou
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20211209112030.5cdcd07c@xps13 \
--to=miquel.raynal@bootlin.com \
--cc=benliang.zhao@mediatek.com \
--cc=bin.zhang@mediatek.com \
--cc=broonie@kernel.org \
--cc=dandan.he@mediatek.com \
--cc=donghunt@amazon.com \
--cc=guochun.mao@mediatek.com \
--cc=linux-mediatek@lists.infradead.org \
--cc=linux-mtd@lists.infradead.org \
--cc=mao.zhong@mediatek.com \
--cc=rdlee@amazon.com \
--cc=sanny.chen@mediatek.com \
--cc=srv_heupstream@mediatek.com \
--cc=xiangsheng.hou@mediatek.com \
--cc=yingjoe.chen@mediatek.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.