Il 23/04/24 12:16, Lorenzo Bianconi ha scritto:
Introduce support for SPI-NAND driver of the Airoha NAND Flash Interface
found on Airoha ARM SoCs.
Tested-by: Rajeev Kumar <Rajeev.Kumar@xxxxxxxxxx>
Signed-off-by: Lorenzo Bianconi <lorenzo@xxxxxxxxxx>
---
MAINTAINERS | 9 +
drivers/spi/Kconfig | 10 +
drivers/spi/Makefile | 1 +
drivers/spi/spi-airoha-snfi.c | 1156 +++++++++++++++++++++++++++++++++
4 files changed, 1176 insertions(+)
create mode 100644 drivers/spi/spi-airoha-snfi.c
diff --git a/MAINTAINERS b/MAINTAINERS
index aa3b947fb080..ce9fac46f741 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -653,6 +653,15 @@ S: Supported
F: fs/aio.c
F: include/linux/*aio*.h
+AIROHA SPI SNFI DRIVER
+M: Lorenzo Bianconi <lorenzo@xxxxxxxxxx>
+M: Ray Liu <ray.liu@xxxxxxxxxx>
+L: linux-arm-kernel@xxxxxxxxxxxxxxxxxxx (moderated for non-subscribers)
+L: linux-spi@xxxxxxxxxxxxxxx
+S: Maintained
+F: Documentation/devicetree/bindings/spi/airoha,en7581-snand.yaml
+F: drivers/spi/spi-airoha.c
+
AIRSPY MEDIA DRIVER
L: linux-media@xxxxxxxxxxxxxxx
S: Orphan
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index bc7021da2fe9..6fa91775f334 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -57,6 +57,16 @@ config SPI_MEM
comment "SPI Master Controller Drivers"
+config SPI_AIROHA_SNFI
+ tristate "Airoha SPI NAND Flash Interface"
+ depends on ARCH_AIROHA || COMPILE_TEST
+ depends on SPI_MASTER
+ select REGMAP_MMIO
+ help
+ This enables support for SPI-NAND mode on the Airoha NAND
+ Flash Interface found on Airoha ARM SoCs. This controller
+ is implemented as a SPI-MEM controller.
+
config SPI_ALTERA
tristate "Altera SPI Controller platform driver"
select SPI_ALTERA_CORE
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 4ff8d725ba5e..e694254dec04 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_SPI_SPIDEV) += spidev.o
obj-$(CONFIG_SPI_LOOPBACK_TEST) += spi-loopback-test.o
# SPI master controller drivers (bus)
+obj-$(CONFIG_SPI_AIROHA_SNFI) += spi-airoha-snfi.o
obj-$(CONFIG_SPI_ALTERA) += spi-altera-platform.o
obj-$(CONFIG_SPI_ALTERA_CORE) += spi-altera-core.o
obj-$(CONFIG_SPI_ALTERA_DFL) += spi-altera-dfl.o
diff --git a/drivers/spi/spi-airoha-snfi.c b/drivers/spi/spi-airoha-snfi.c
new file mode 100644
index 000000000000..eacb32d6e28e
--- /dev/null
+++ b/drivers/spi/spi-airoha-snfi.c
@@ -0,0 +1,1156 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2024 AIROHA Inc
+ * Author: Lorenzo Bianconi <lorenzo@xxxxxxxxxx>
+ * Author: Ray Liu <ray.liu@xxxxxxxxxx>
+ */
+
..snip...
+static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc,
+ u64 offs, size_t len, void *buf)
+{
+ struct spi_device *spi = desc->mem->spi;
+ struct airoha_snand_dev *as_dev = spi_get_ctldata(spi);
+ struct spi_mem_op *op = &desc->info.op_tmpl;
+ struct airoha_snand_ctrl *as_ctrl;
+ u32 val, rd_mode;
+ int err;
+
+ if (!as_dev->data_need_update)
+ return len;
+
+ as_dev->data_need_update = false;
+
+ switch (op->cmd.opcode) {
+ case SPI_NAND_OP_READ_FROM_CACHE_DUAL:
+ rd_mode = 1;
+ break;
+ case SPI_NAND_OP_READ_FROM_CACHE_QUAD:
+ rd_mode = 2;
+ break;
+ default:
+ rd_mode = 0;
+ break;
+ }
+
+ as_ctrl = spi_controller_get_devdata(spi->controller);
+ err = airoha_snand_set_mode(as_ctrl, SPI_MODE_DMA);
+ if (err < 0)
+ return err;
+
+ err = airoha_snand_nfi_config(as_ctrl);
+ if (err)
+ return err;
+
+ dma_sync_single_for_device(as_ctrl->dev, as_dev->dma_addr,
+ as_dev->buf_len, DMA_BIDIRECTIONAL);
+ mb();
Are you sure you need this memory barrier here?
P.S.: Just in case... any other write or read will anyway add a barrier ;-)
+
+ /* set dma addr */
+ err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_STRADDR,
+ as_dev->dma_addr);
+ if (err)
+ return err;
+
..snip..
+
+static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc,
+ u64 offs, size_t len, const void *buf)
+{
+ struct spi_device *spi = desc->mem->spi;
+ struct airoha_snand_dev *as_dev = spi_get_ctldata(spi);
+ struct spi_mem_op *op = &desc->info.op_tmpl;
+ struct airoha_snand_ctrl *as_ctrl;
+ u32 wr_mode, val;
+ int err;
+
+ as_ctrl = spi_controller_get_devdata(spi->controller);
+ err = airoha_snand_set_mode(as_ctrl, SPI_MODE_MANUAL);
+ if (err < 0)
+ return err;
+
+ dma_sync_single_for_cpu(as_ctrl->dev, as_dev->dma_addr,
+ as_dev->buf_len, DMA_BIDIRECTIONAL);
+ memcpy(as_dev->txrx_buf + offs, buf, len);
+ dma_sync_single_for_device(as_ctrl->dev, as_dev->dma_addr,
+ as_dev->buf_len, DMA_BIDIRECTIONAL);
+ mb();
+
+ err = airoha_snand_set_mode(as_ctrl, SPI_MODE_DMA);
+ if (err < 0)
+ return err;
+
+ err = airoha_snand_nfi_config(as_ctrl);
+ if (err)
+ return err;
+
+ if (op->cmd.opcode == SPI_NAND_OP_PROGRAM_LOAD_QUAD ||
+ op->cmd.opcode == SPI_NAND_OP_PROGRAM_LOAD_RAMDON_QUAD)
+ wr_mode = 2;
I'm mostly sure that this '2' is supposed to be BIT(1) instead
+ else
+ wr_mode = 0;
+
+ err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_STRADDR,
+ as_dev->dma_addr);
+ if (err)
+ return err;
+
+ val = as_ctrl->nfi_cfg.sec_size * as_ctrl->nfi_cfg.sec_num;
+ val = FIELD_PREP(SPI_NFI_PROG_LOAD_BYTE_NUM, val);
val = FIELD_PREP(SPI_NFI_PROG_LOAD_BYTE_NUM,
as_ctrl->nfi_cfg.sec_size * as_ctrl->nfi_cfg.sec_num);
Being this 100 cols eventually, you wouldn't even need two lines - but
if you don't want to go 100, it's still more readable (imo) like this,
even if in two lines.
+ err = regmap_update_bits(as_ctrl->regmap_nfi,
+ REG_SPI_NFI_SNF_MISC_CTL2,
+ SPI_NFI_PROG_LOAD_BYTE_NUM, val);
+ if (err)
+ return err;
+
+ err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_PG_CTL1,
+ FIELD_PREP(SPI_NFI_PG_LOAD_CMD,
+ op->cmd.opcode));
+ if (err)
+ return err;
+
+ err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_SNF_MISC_CTL,
+ FIELD_PREP(SPI_NFI_DATA_READ_WR_MODE, wr_mode));
+ if (err)
+ return err;
+
..snip..
+
+#define SPI_NAND_CACHE_SIZE (4096 + 256)
#include <linux/sizes.h> <--- at the beginning
#define SPI_NAND_CACHE_SIZE (SZ_4K + SZ_256)
...there are macros for that, might as well use them, right? :-)
+
+static int airoha_snand_setup(struct spi_device *spi)
+{
+ struct airoha_snand_dev *as_dev = spi_get_ctldata(spi);
+ struct airoha_snand_ctrl *as_ctrl;
+
+ as_dev = kzalloc(sizeof(*as_dev), GFP_KERNEL);
+ if (!as_dev)
+ return -ENOMEM;
+
+ spi_set_ctldata(spi, as_dev);
+ as_dev->data_need_update = true;
+
+ /* prepare device buffer */
+ as_dev->buf_len = SPI_NAND_CACHE_SIZE;
+ as_dev->txrx_buf = kzalloc(as_dev->buf_len, GFP_KERNEL);
+ if (!as_dev->txrx_buf)
+ goto error_dev_free;
+
+ as_ctrl = spi_controller_get_devdata(spi->controller);
+ as_dev->dma_addr = dma_map_single(as_ctrl->dev, as_dev->txrx_buf,
+ as_dev->buf_len, DMA_BIDIRECTIONAL);
+ if (dma_mapping_error(as_ctrl->dev, as_dev->dma_addr))
+ goto error_buf_free;
+
+ return 0;
+
+error_buf_free:
+ kfree(as_dev->txrx_buf);
+error_dev_free:
+ kfree(as_dev);
+
+ return -EINVAL;
+}
+
+static void airoha_snand_cleanup(struct spi_device *spi)
+{
+ struct airoha_snand_dev *as_dev = spi_get_ctldata(spi);
+ struct airoha_snand_ctrl *as_ctrl;
+
+ as_ctrl = spi_controller_get_devdata(spi->controller);
+ dma_unmap_single(as_ctrl->dev, as_dev->dma_addr,
+ as_dev->buf_len, DMA_BIDIRECTIONAL);
+ kfree(as_dev->txrx_buf);
+
+ spi_set_ctldata(spi, NULL);
+}
+
+static int airoha_snand_nfi_setup(struct airoha_snand_ctrl *as_ctrl)
+{
+ u32 val, sec_size, sec_num;
+ int err;
+
+ err = regmap_read(as_ctrl->regmap_nfi, REG_SPI_NFI_CON, &val);
+ if (err)
+ return err;
+
+ sec_num = FIELD_GET(SPI_NFI_SEC_NUM, val);
+
+ err = regmap_read(as_ctrl->regmap_nfi,
+ REG_SPI_NFI_SECCUS_SIZE, &val);
+ if (err)
+ return err;
+
+ sec_size = FIELD_GET(SPI_NFI_CUS_SEC_SIZE, val);
+
+ /* init default value */
+ as_ctrl->nfi_cfg.sec_size = sec_size;
+ as_ctrl->nfi_cfg.sec_num = sec_num;
+ as_ctrl->nfi_cfg.page_size = rounddown(sec_size * sec_num, 1024);
+ as_ctrl->nfi_cfg.spare_size = 16;
+
+ err = airoha_snand_nfi_init(as_ctrl);
+ if (err)
+ return err;
+
+ return airoha_snand_nfi_config(as_ctrl);
+}
+
+static const struct regmap_config spi_ctrl_regmap_config = {
+ .name = "ctrl",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = REG_SPI_CTRL_NFI2SPI_EN,
+};
+
+static const struct regmap_config spi_nfi_regmap_config = {
+ .name = "nfi",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = REG_SPI_NFI_SNF_NFI_CNFG,
+};
+
+static const struct of_device_id airoha_snand_ids[] = {
+ { .compatible = "airoha,en7581-snand" },
+ { }
{ /* sentinel */ }
...please!
+};
+MODULE_DEVICE_TABLE(of, airoha_snand_ids);
+
Cheers,
Angelo