This is a driver for sophgo spi-nor controller using spi-mem interface. Signed-off-by: Jingbao Qiu <qiujingbao.dlmu@xxxxxxxxx> --- drivers/spi/Kconfig | 9 + drivers/spi/Makefile | 1 + drivers/spi/spi-sophgo-cv1800.c | 370 ++++++++++++++++++++++++++++++++ 3 files changed, 380 insertions(+) create mode 100644 drivers/spi/spi-sophgo-cv1800.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index bc7021da2fe9..41ad7c0aaab8 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -971,6 +971,15 @@ config SPI_SN_F_OSPI for connecting an SPI Flash memory over up to 8-bit wide bus. It supports indirect access mode only. +config SPI_SOPHGO_CV1800 + tristate "Sophgo SPI NOR Controller" + depends on ARCH_SOPHGO || COMPILE_TEST + help + This enables support for the Sophgo SPI NOR controller, + which supports Dual/Qual read and write operations while + also supporting 3Byte address devices and 4Byte address + devices. + config SPI_SPRD tristate "Spreadtrum SPI controller" depends on ARCH_SPRD || COMPILE_TEST diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 4ff8d725ba5e..a25549155106 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -128,6 +128,7 @@ obj-$(CONFIG_SPI_SH_SCI) += spi-sh-sci.o obj-$(CONFIG_SPI_SIFIVE) += spi-sifive.o obj-$(CONFIG_SPI_SLAVE_MT27XX) += spi-slave-mt27xx.o obj-$(CONFIG_SPI_SN_F_OSPI) += spi-sn-f-ospi.o +obj-$(CONFIG_SPI_SOPHGO_CV1800) += spi-sophgo-cv1800.o obj-$(CONFIG_SPI_SPRD) += spi-sprd.o obj-$(CONFIG_SPI_SPRD_ADI) += spi-sprd-adi.o obj-$(CONFIG_SPI_STM32) += spi-stm32.o diff --git a/drivers/spi/spi-sophgo-cv1800.c b/drivers/spi/spi-sophgo-cv1800.c new file mode 100644 index 000000000000..2e453b7d45f0 --- /dev/null +++ b/drivers/spi/spi-sophgo-cv1800.c @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Sophgo SPI NOR controller driver +// +// Copyright (C) 2020 Jingbao Qiu <qiujingbao.dlmu@xxxxxxxxx> + +#include <linux/bitfield.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/spi/spi-mem.h> + +#define SOPHGO_SPI_CTRL 0x000 +#define SOPHGO_SPI_CE_CTRL 0x004 +#define SOPHGO_SPI_DLY_CTRL 0x008 +#define SOPHGO_SPI_DMMR 0x00C +#define SOPHGO_SPI_TRAN_CSR 0x010 +#define SOPHGO_SPI_TRAN_NUM 0x014 +#define SOPHGO_SPI_FIFO_PORT 0x018 +#define SOPHGO_SPI_FIFO_PT 0x020 +#define SOPHGO_SPI_INT_STS 0x028 + +#define SOPHGO_NOR_CTRL_SCK_DIV_MASK GENMASK(10, 0) +#define SOPHGO_NOR_CTRL_DEFAULT_DIV 4 +#define SOPHGO_NOR_DLY_CTRL_NEG_SAMPLE BIT(14) + +#define SOPHGO_NOR_CE_MANUAL BIT(0) +#define SOPHGO_NOR_CE_MANUAL_EN BIT(1) +#define SOPHGO_NOR_CE_ENABLE (SOPHGO_NOR_CE_MANUAL | SOPHGO_NOR_CE_MANUAL_EN) +#define SOPHGO_NOR_CE_DISABLE SOPHGO_NOR_CE_MANUAL_EN +#define SOPHGO_NOR_CE_HARDWARE 0 + +#define SOPHGO_NOR_TRAN_MODE_RX BIT(0) +#define SOPHGO_NOR_TRAN_MODE_TX BIT(1) +#define SOPHGO_NOR_TRAN_MODE_MASK GENMASK(1, 0) +#define SOPHGO_NOR_TRANS_FAST BIT(3) +#define SOPHGO_NOR_TRANS_BUS_WIDTH(n) (n << 4) +#define SOPHGO_NOR_TRANS_BUS_WIDTH_MASK GENMASK(5, 4) + +#define SOPHGO_NOR_TRANS_MIOS BIT(7) + +#define SOPHGO_NOR_TRAN_ADDR(n) (n << 8) +#define SOPHGO_NOR_TRANS_ADDR_MASK GENMASK(10, 8) +#define SOPHGO_NOR_TRANS_CMD BIT(11) +#define SOPHGO_NOR_TRAN_FIFO_MASK GENMASK(13, 12) +#define SOPHGO_NOR_TRAN_FIFO_8_BYTE GENMASK(13, 12) +#define SOPHGO_NOR_TRAN_GO_BUSY BIT(15) + +#define SOPHGO_NOR_TRANS_DMMR_EN BIT(20) +#define SOPHGO_NOR_TRANS_DMMR_CMD BIT(21) + +#define SOPHGO_NOR_TRANS_MMIO \ + (SOPHGO_NOR_TRANS_FAST | SOPHGO_NOR_TRANS_DMMR_EN | \ + SOPHGO_NOR_TRANS_DMMR_CMD | SOPHGO_NOR_TRANS_MIOS | \ + SOPHGO_NOR_TRAN_MODE_RX | SOPHGO_NOR_TRAN_FIFO_8_BYTE) + +#define SOPHGO_NOR_TRANS_PORT \ + (SOPHGO_NOR_TRAN_MODE_MASK | SOPHGO_NOR_TRANS_ADDR_MASK | \ + SOPHGO_NOR_TRAN_FIFO_MASK | SOPHGO_NOR_TRANS_BUS_WIDTH_MASK | \ + SOPHGO_NOR_TRANS_BUS_WIDTH_MASK) + +#define SOPHGO_NOR_FIFO_CAPACITY 8 +#define SOPHGO_NOR_FIFO_AVAI_MASK GENMASK(3, 0) + +#define SOPHGO_NOR_INT_TRAN_DONE BIT(0) +#define SOPHGO_NOR_INT_RD_FIFO BIT(1) +#define SOPHGO_NOR_INT_WR_FIFO BIT(2) + +struct sophgo_nor { + struct spi_controller *ctlr; + struct device *dev; + void __iomem *io_base; + uint32_t tran_csr_orig; + uint32_t sck_div_orig; + struct mutex lock; +}; + +static uint32_t sophgo_nor_clk_setup(struct sophgo_nor *spif, uint32_t sck_div) +{ + uint32_t reg; + uint32_t old_clk; + + reg = readl(spif->io_base + SOPHGO_SPI_DLY_CTRL); + + if (sck_div < SOPHGO_NOR_CTRL_DEFAULT_DIV) + reg |= SOPHGO_NOR_DLY_CTRL_NEG_SAMPLE; + + writel(reg, spif->io_base + SOPHGO_SPI_DLY_CTRL); + + reg = readl(spif->io_base + SOPHGO_SPI_CTRL); + old_clk = FIELD_GET(SOPHGO_NOR_CTRL_SCK_DIV_MASK, reg); + + reg &= ~SOPHGO_NOR_CTRL_SCK_DIV_MASK; + reg |= sck_div; + writel(reg, spif->io_base + SOPHGO_SPI_CTRL); + + return old_clk; +} + +static inline uint32_t sophgo_nor_trans_csr_config(struct sophgo_nor *spif, + const struct spi_mem_op *op) +{ + uint32_t tran_csr = 0; + + if (op->dummy.nbytes) + tran_csr |= (op->dummy.nbytes * 8) / op->dummy.buswidth << 16; + + tran_csr |= SOPHGO_NOR_TRANS_MMIO; + tran_csr |= SOPHGO_NOR_TRANS_BUS_WIDTH(op->data.buswidth / 2); + tran_csr |= SOPHGO_NOR_TRAN_ADDR(op->addr.nbytes); + + return tran_csr; +} + +static void sophgo_nor_config_mmio(struct sophgo_nor *spif, + const struct spi_mem_op *op, + uint32_t enabled) +{ + uint32_t ctrl, tran_csr; + + if (enabled) { + spif->tran_csr_orig = + readl(spif->io_base + SOPHGO_SPI_TRAN_CSR); + tran_csr = sophgo_nor_trans_csr_config(spif, op); + ctrl = SOPHGO_NOR_CE_HARDWARE; + } else { + tran_csr = spif->tran_csr_orig; + ctrl = SOPHGO_NOR_CE_ENABLE; + } + + writel(tran_csr, spif->io_base + SOPHGO_SPI_TRAN_CSR); + writel(ctrl, spif->io_base + SOPHGO_SPI_CE_CTRL); + writel(enabled, spif->io_base + SOPHGO_SPI_DMMR); +} + +static void sophgo_nor_config_port(struct sophgo_nor *spif, uint32_t enabled) +{ + uint32_t ctrl = SOPHGO_NOR_CE_ENABLE; + + if (enabled) { + ctrl = SOPHGO_NOR_CE_MANUAL_EN; + writel(!enabled, spif->io_base + SOPHGO_SPI_DMMR); + } + + writel(ctrl, spif->io_base + SOPHGO_SPI_CE_CTRL); +} + +static int sophgo_nor_xfer(struct sophgo_nor *spif, const uint8_t *dout, + uint8_t *din, uint32_t data_bytes, + uint32_t bus_width) +{ + uint32_t xfer_size, off; + uint32_t fifo_cnt; + uint32_t interrupt_mask = 0; + uint32_t stat, tran_csr = 0; + int ret = 0; + + writel(0, spif->io_base + SOPHGO_SPI_INT_STS); + writel(0, spif->io_base + SOPHGO_SPI_FIFO_PT); + + writew(data_bytes, spif->io_base + SOPHGO_SPI_TRAN_NUM); + + if (din && dout) + return -1; + else if (!din && !dout) + return -1; + + tran_csr = readw(spif->io_base + SOPHGO_SPI_TRAN_CSR); + + tran_csr &= ~SOPHGO_NOR_TRANS_PORT; + + tran_csr |= SOPHGO_NOR_TRAN_FIFO_8_BYTE; + tran_csr |= SOPHGO_NOR_TRAN_GO_BUSY; + tran_csr |= (bus_width / 2) << 4; + + interrupt_mask |= SOPHGO_NOR_INT_TRAN_DONE; + + if (din) { + tran_csr |= SOPHGO_NOR_TRAN_MODE_RX; + interrupt_mask |= SOPHGO_NOR_INT_RD_FIFO; + spif->sck_div_orig = + sophgo_nor_clk_setup(spif, SOPHGO_NOR_CTRL_DEFAULT_DIV); + } else if (dout) { + tran_csr |= SOPHGO_NOR_TRAN_MODE_TX; + interrupt_mask |= SOPHGO_NOR_INT_WR_FIFO; + } + + writew(tran_csr, spif->io_base + SOPHGO_SPI_TRAN_CSR); + + ret = readb_poll_timeout(spif->io_base + SOPHGO_SPI_INT_STS, stat, + stat & interrupt_mask, 10, 30); + if (ret) + dev_warn(spif->dev, "%s stat timedout\n", __func__); + + off = 0; + while (off < data_bytes) { + xfer_size = min_t(uint32_t, data_bytes - off, + SOPHGO_NOR_FIFO_CAPACITY); + + fifo_cnt = readl(spif->io_base + SOPHGO_SPI_FIFO_PT) & + SOPHGO_NOR_FIFO_AVAI_MASK; + + if (fifo_cnt > SOPHGO_NOR_FIFO_CAPACITY) + goto exit; + + if (din) + xfer_size = min(xfer_size, fifo_cnt); + else + xfer_size = min_t(uint32_t, xfer_size, + SOPHGO_NOR_FIFO_CAPACITY - fifo_cnt); + + while (xfer_size--) { + if (din) + *(din + off) = readb(spif->io_base + + SOPHGO_SPI_FIFO_PORT); + else + writeb(*(dout + off), + spif->io_base + SOPHGO_SPI_FIFO_PORT); + off++; + } + } + + ret = readb_poll_timeout(spif->io_base + SOPHGO_SPI_INT_STS, stat, + (stat & interrupt_mask), 10, 30); + if (ret) { + dev_warn(spif->dev, " %s command timed out %x\n", __func__, + stat); + } + +exit: + writeb(0, spif->io_base + SOPHGO_SPI_FIFO_PT); + stat = readb(spif->io_base + SOPHGO_SPI_INT_STS) & ~interrupt_mask; + writeb(stat, spif->io_base + SOPHGO_SPI_INT_STS); + + if (din) + sophgo_nor_clk_setup(spif, spif->sck_div_orig); + + return 0; +} + +static int sophgo_nor_port_trans(struct sophgo_nor *spif, + const struct spi_mem_op *op) +{ + const uint8_t *dout = NULL; + uint8_t *din = NULL; + uint32_t addr; + + sophgo_nor_config_port(spif, 1); + + if (op->cmd.nbytes) + sophgo_nor_xfer(spif, (uint8_t *)&op->cmd.opcode, NULL, + op->cmd.nbytes, op->cmd.buswidth); + + if (op->addr.nbytes) { + addr = cpu_to_be32(op->addr.val); + sophgo_nor_xfer(spif, (uint8_t *)&addr, NULL, op->addr.nbytes, + op->addr.buswidth); + } + + if (op->data.dir == SPI_MEM_DATA_IN) + din = op->data.buf.in; + else if (op->data.dir == SPI_MEM_DATA_OUT) + dout = op->data.buf.out; + + sophgo_nor_xfer(spif, dout, din, op->data.nbytes, op->data.buswidth); + + sophgo_nor_config_port(spif, 0); + + return 0; +} + +static void sophgo_nore_read_mmio(struct sophgo_nor *spif, + const struct spi_mem_op *op) +{ + sophgo_nor_config_mmio(spif, op, 1); + memcpy_fromio(op->data.buf.in, spif->io_base + op->addr.val, + op->data.nbytes); + sophgo_nor_config_mmio(spif, op, 0); +} + +static int sophgo_nor_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) +{ + struct sophgo_nor *spif; + + spif = spi_controller_get_devdata(mem->spi->controller); + + mutex_lock(&spif->lock); + if (op->data.dir == SPI_MEM_DATA_IN && op->data.nbytes && + op->addr.nbytes == 4) { + sophgo_nore_read_mmio(spif, op); + goto exit; + } + + sophgo_nor_port_trans(spif, op); + +exit: + mutex_unlock(&spif->lock); + return 0; +} + +static const struct spi_controller_mem_ops sophgo_nor_mem_ops = { + .exec_op = sophgo_nor_exec_op, +}; + +static const struct of_device_id sophgo_nor_match[] = { + { .compatible = "sophgo,cv1800b-nor" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sophgo_nor_match); + +static int sophgo_nor_probe(struct platform_device *pdev) +{ + struct spi_controller *ctlr; + struct sophgo_nor *sp; + void __iomem *base; + + ctlr = devm_spi_alloc_host(&pdev->dev, sizeof(*sp)); + if (!ctlr) + return -ENOMEM; + + sp = spi_controller_get_devdata(ctlr); + dev_set_drvdata(&pdev->dev, ctlr); + + sp->dev = &pdev->dev; + sp->ctlr = ctlr; + + sp->io_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + ctlr->num_chipselect = 1; + ctlr->dev.of_node = pdev->dev.of_node; + ctlr->bits_per_word_mask = SPI_BPW_MASK(8); + ctlr->auto_runtime_pm = false; + ctlr->mem_ops = &sophgo_nor_mem_ops; + ctlr->mode_bits = SPI_RX_DUAL | SPI_TX_DUAL | SPI_RX_QUAD | SPI_TX_QUAD; + + mutex_init(&sp->lock); + + sophgo_nor_config_port(sp, 1); + + return devm_spi_register_controller(&pdev->dev, ctlr); +} + +static int sophgo_nor_remove(struct platform_device *pdev) +{ + struct sophgo_nor *spif = platform_get_drvdata(pdev); + + mutex_destroy(&spif->lock); + return 0; +} + +static struct platform_driver sophgo_nor_driver = { + .driver = { + .name = "sophgo-spif", + .of_match_table = sophgo_nor_match, + }, + .probe = sophgo_nor_probe, + .remove = sophgo_nor_remove, +}; + +module_platform_driver(sophgo_nor_driver); + +MODULE_DESCRIPTION("Sophgo SPI NOR controller driver"); +MODULE_AUTHOR("Jingbao Qiu <qiujingbao.dlmu@xxxxxxxxx>"); +MODULE_LICENSE("GPL"); -- 2.25.1