* [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
@ 2022-07-22 16:48 ` 周琰杰 (Zhou Yanjie)
0 siblings, 0 replies; 78+ messages in thread
From: 周琰杰 (Zhou Yanjie) @ 2022-07-22 16:48 UTC (permalink / raw)
To: tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, broonie, robh+dt, krzysztof.kozlowski+dt
Cc: linux-mtd, linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, paul, dongsheng.qiu, aric.pzqi,
rick.tyliu, jinghui.liu, sernia.zhou, reimu
Add SFC support for the X1000 SoC, the X1600 SoC, and the X2000 SoC
from Ingenic.
Signed-off-by: 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
---
drivers/spi/Kconfig | 9 +
drivers/spi/Makefile | 1 +
drivers/spi/spi-ingenic-sfc.c | 662 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 672 insertions(+)
create mode 100644 drivers/spi/spi-ingenic-sfc.c
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 3b1044e..1077bd3 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -437,6 +437,15 @@ config SPI_INGENIC
To compile this driver as a module, choose M here: the module
will be called spi-ingenic.
+config SPI_INGENIC_SFC
+ tristate "Ingenic SoCs SPI Flash Controller"
+ depends on MACH_INGENIC || COMPILE_TEST
+ help
+ This enables support for the Ingenic SoCs SPI flash controller.
+
+ To compile this driver as a module, choose M here: the module
+ will be called ingenic-sfc.
+
config SPI_INTEL
tristate
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 0f44eb6..f3e42c0 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -62,6 +62,7 @@ obj-$(CONFIG_SPI_HISI_SFC_V3XX) += spi-hisi-sfc-v3xx.o
obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o
obj-$(CONFIG_SPI_IMX) += spi-imx.o
obj-$(CONFIG_SPI_INGENIC) += spi-ingenic.o
+obj-$(CONFIG_SPI_INGENIC_SFC) += spi-ingenic-sfc.o
obj-$(CONFIG_SPI_INTEL) += spi-intel.o
obj-$(CONFIG_SPI_INTEL_PCI) += spi-intel-pci.o
obj-$(CONFIG_SPI_INTEL_PLATFORM) += spi-intel-platform.o
diff --git a/drivers/spi/spi-ingenic-sfc.c b/drivers/spi/spi-ingenic-sfc.c
new file mode 100644
index 00000000..a565546
--- /dev/null
+++ b/drivers/spi/spi-ingenic-sfc.c
@@ -0,0 +1,662 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/*
+ * Ingenic SoCs SPI Flash Controller Driver
+ * Copyright (c) 2022 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi-mem.h>
+
+/* SFC register offsets */
+#define SFC_REG_GLB 0x0000
+#define SFC_REG_DEV_CONF 0x0004
+#define SFC_REG_DEV_STA_EXP 0x0008
+#define SFC_REG_DEV_STA_RT 0x000c
+#define SFC_REG_DEV_STA_MSK 0x0010
+#define SFC_REG_TRAN_CONF(n) (0x0014 + n * 4)
+#define SFC_REG_TRAN_CFG0(n) (0x0014 + n * 4)
+#define SFC_REG_TRAN_LEN 0x002c
+#define SFC_REG_DEV_ADDR(n) (0x0030 + n * 4)
+#define SFC_REG_DEV_ADDR_PLUS(n) (0x0048 + n * 4)
+#define SFC_REG_MEM_ADDR 0x0060
+#define SFC_REG_TRIG 0x0064
+#define SFC_REG_SR 0x0068
+#define SFC_REG_SCR 0x006c
+#define SFC_REG_INTC 0x0070
+#define SFC_REG_FSM 0x0074
+#define SFC_REG_CGE 0x0078
+#define SFC_REG_TRAN_CFG1(n) (0x009c + n * 4)
+#define SFC_REG_DR 0x1000
+
+/* bits within the GLB register */
+#define GLB_TRAN_DIR_MASK GENMASK(13, 13)
+#define GLB_TRAN_DIR_WRITE 0x1
+#define GLB_TRAN_DIR_READ 0x0
+#define GLB_THRESHOLD_MASK GENMASK(12, 7)
+#define GLB_OP_MODE_MASK GENMASK(6, 6)
+#define GLB_OP_MODE_DMA 0x1
+#define GLB_OP_MODE_SLAVE 0x0
+#define GLB_PHASE_NUM_MASK GENMASK(5, 3)
+#define GLB_WP_EN BIT(2)
+#define GLB_BURST_MD_MASK GENMASK(1, 0)
+#define GLB_BURST_MD_INCR32 0x3
+#define GLB_BURST_MD_INCR16 0x2
+#define GLB_BURST_MD_INCR8 0x1
+#define GLB_BURST_MD_INCR4 0x0
+
+/* bits within the DEV_CONF register */
+#define DEV_CONF_SMP_DELAY_MASK GENMASK(20, 16)
+#define DEV_CONF_SMP_DELAY_180DEG 0x4
+#define DEV_CONF_SMP_DELAY_HALF_CYCLE 0x1
+#define DEV_CONF_CMD_TYPE_MASK GENMASK(15, 15)
+#define DEV_CONF_CMD_TYPE_16BIT 0x1
+#define DEV_CONF_CMD_TYPE_8BIT 0x0
+#define DEV_CONF_STA_TYPE_MASK GENMASK(14, 13)
+#define DEV_CONF_THOLD_MASK GENMASK(12, 11)
+#define DEV_CONF_TSETUP_MASK GENMASK(10, 9)
+#define DEV_CONF_TSH_MASK GENMASK(8, 5)
+#define DEV_CONF_CPHA BIT(4)
+#define DEV_CONF_CPOL BIT(3)
+#define DEV_CONF_CE_DL BIT(2)
+#define DEV_CONF_HOLD_DL BIT(1)
+#define DEV_CONF_WP_DL BIT(0)
+
+/* bits within the TRAN_CONF(n) register */
+#define TRAN_CONF_TRAN_MODE_MASK GENMASK(31, 29)
+#define TRAN_CONF_ADDR_WIDTH_MASK GENMASK(28, 26)
+#define TRAN_CONF_POLL_EN BIT(25)
+#define TRAN_CONF_CMD_EN BIT(24)
+#define TRAN_CONF_PHASE_FORMAT_MASK GENMASK(23, 23)
+#define TRAN_CONF_DMY_BITS_MASK GENMASK(22, 17)
+#define TRAN_CONF_DATA_EN BIT(16)
+#define TRAN_CONF_CMD_MASK GENMASK(15, 0)
+
+/* bits within the TRIG register */
+#define TRIG_FLUSH BIT(2)
+#define TRIG_STOP BIT(1)
+#define TRIG_START BIT(0)
+
+/* bits within the SR register */
+#define SR_FIFO_NUM_MASK GENMASK(22, 16)
+#define SR_END BIT(4)
+#define SR_TRAN_REQ BIT(3)
+#define SR_RECE_REQ BIT(2)
+#define SR_OVER BIT(1)
+#define SR_UNDER BIT(0)
+
+/* bits within the SCR register */
+#define SCR_CLR_END BIT(4)
+#define SCR_CLR_TREQ BIT(3)
+#define SCR_CLR_RREQ BIT(2)
+#define SCR_CLR_OVER BIT(1)
+#define SCR_CLR_UNDER BIT(0)
+
+/* bits within the INTC register */
+#define INTC_MASK_END BIT(4)
+#define INTC_MASK_TREQ BIT(3)
+#define INTC_MASK_RREQ BIT(2)
+#define INTC_MASK_OVER BIT(1)
+#define INTC_MASK_UNDER BIT(0)
+
+/* bits within the TRAN_CFG1(n) register */
+#define TRAN_CFG1_TRAN_MODE_MASK GENMASK(7, 4)
+
+#define TRAN_MODE_STANDARD 0
+#define TRAN_MODE_DUAL_DATA 1
+#define TRAN_MODE_DUAL_IO 2
+#define TRAN_MODE_DUAL_FULL 3
+#define TRAN_MODE_QUAD_DATA 5
+#define TRAN_MODE_QUAD_IO 6
+#define TRAN_MODE_QUAD_FULL 7
+#define TRAN_MODE_OCTAL_DATA 9
+#define TRAN_MODE_OCTAL_IO 10
+#define TRAN_MODE_OCTAL_FULL 11
+
+#define INGENIC_SFC_FIFO_SIZE (64 * 4)
+
+#define INGENIC_SFC_TRANSFER_TIMEOUT 1000
+
+enum ingenic_sfc_version {
+ ID_X1000,
+ ID_X1600,
+ ID_X2000,
+};
+
+struct ingenic_soc_info {
+ enum ingenic_sfc_version version;
+
+ unsigned int max_bus_width;
+
+ const u32 tran_mode_mask;
+};
+
+struct ingenic_sfc {
+ const struct ingenic_soc_info *soc_info;
+
+ void __iomem *base;
+ struct device *dev;
+ struct clk *clk;
+ int irq;
+
+ struct completion completion;
+};
+
+static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
+{
+ struct ingenic_sfc *sfc = data;
+
+ writel(0x1f, sfc->base + SFC_REG_INTC);
+
+ complete(&sfc->completion);
+
+ return IRQ_HANDLED;
+}
+
+static int ingenic_sfc_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
+{
+ uintptr_t addr = (uintptr_t)op->data.buf.in;
+
+ if (op->data.nbytes > INGENIC_SFC_FIFO_SIZE && !IS_ALIGNED(addr, 4))
+ op->data.nbytes = INGENIC_SFC_FIFO_SIZE;
+
+ return 0;
+}
+
+static bool ingenic_sfc_supports_op(struct spi_mem *mem, const struct spi_mem_op *op)
+{
+ struct spi_device *spi = mem->spi;
+ struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
+ uintptr_t addr = (uintptr_t)op->data.buf.in;
+
+ /* The controller only supports Standard SPI mode, Duall mode, Quad mode and Octal mode. */
+ if (op->cmd.buswidth > sfc->soc_info->max_bus_width ||
+ op->addr.buswidth > sfc->soc_info->max_bus_width ||
+ op->dummy.buswidth > sfc->soc_info->max_bus_width ||
+ op->data.buswidth > sfc->soc_info->max_bus_width)
+ return false;
+
+ /* Max 32 dummy clock cycles supported */
+ if (op->dummy.nbytes && op->dummy.nbytes * 8 / op->dummy.buswidth > 32)
+ return false;
+
+ /* Max rx data length, check controller limits and alignment */
+ if (op->data.dir == SPI_MEM_DATA_IN &&
+ op->data.nbytes > INGENIC_SFC_FIFO_SIZE && !IS_ALIGNED(addr, 4))
+ return false;
+
+ /* Max 6 bytes address width supported */
+ if (op->addr.nbytes > 6)
+ return false;
+
+ return spi_mem_default_supports_op(mem, op);
+}
+
+static void ingenic_sfc_set_transfer_mode(struct ingenic_sfc *sfc, const struct spi_mem_op *op)
+{
+ int val;
+
+ val = readl(sfc->base + (sfc->soc_info->version >= ID_X1600 ?
+ SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
+ val &= ~sfc->soc_info->tran_mode_mask;
+ if (op->cmd.buswidth == 8)
+ val |= (TRAN_MODE_OCTAL_FULL << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
+ sfc->soc_info->tran_mode_mask;
+ else if (op->cmd.buswidth == 4)
+ val |= (TRAN_MODE_QUAD_FULL << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
+ sfc->soc_info->tran_mode_mask;
+ else if (op->cmd.buswidth == 2)
+ val |= (TRAN_MODE_DUAL_FULL << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
+ sfc->soc_info->tran_mode_mask;
+ else if (op->addr.buswidth == 8)
+ val |= (TRAN_MODE_OCTAL_IO << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
+ sfc->soc_info->tran_mode_mask;
+ else if (op->addr.buswidth == 4)
+ val |= (TRAN_MODE_QUAD_IO << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
+ sfc->soc_info->tran_mode_mask;
+ else if (op->addr.buswidth == 2)
+ val |= (TRAN_MODE_DUAL_IO << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
+ sfc->soc_info->tran_mode_mask;
+ else if (op->data.buswidth == 8)
+ val |= (TRAN_MODE_OCTAL_DATA << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
+ sfc->soc_info->tran_mode_mask;
+ else if (op->data.buswidth == 4)
+ val |= (TRAN_MODE_QUAD_DATA << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
+ sfc->soc_info->tran_mode_mask;
+ else if (op->data.buswidth == 2)
+ val |= (TRAN_MODE_DUAL_DATA << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
+ sfc->soc_info->tran_mode_mask;
+ else
+ val |= (TRAN_MODE_STANDARD << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
+ sfc->soc_info->tran_mode_mask;
+ writel(val, sfc->base + (sfc->soc_info->version >= ID_X1600 ?
+ SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
+}
+
+/*
+ * We only need PIO mode to handle the SPI_MEM_NO_DATA transfers,
+ * and the unaligned accesses in SPI_MEM_DATA_IN transfers.
+ */
+static void ingenic_sfc_read_rxfifo(struct ingenic_sfc *sfc, u8 *to, unsigned int len)
+{
+ void __iomem *from;
+
+ from = sfc->base + SFC_REG_DR;
+
+ for (; len >= 4; len -= 4, to += 4) {
+ u32 val = __raw_readl(from);
+ memcpy(to, &val, 4);
+ }
+
+ if (len) {
+ u32 val = __raw_readl(from);
+ memcpy(to, &val, len);
+ }
+}
+
+static int ingenic_sfc_exec_op_pio(struct ingenic_sfc *sfc, const struct spi_mem_op *op)
+{
+ int ret, val;
+
+ val = readl(sfc->base + SFC_REG_GLB);
+ u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
+ u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
+ writel(val, sfc->base + SFC_REG_GLB);
+
+ val = TRAN_CONF_CMD_EN | op->cmd.opcode;
+
+ if (op->addr.nbytes > 0) {
+ val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK, op->addr.nbytes);
+
+ writel(op->addr.val & 0xffffffff, sfc->base + SFC_REG_DEV_ADDR(0));
+ writel(op->addr.val >> 32, sfc->base + SFC_REG_DEV_ADDR_PLUS(0));
+ }
+
+ if (op->dummy.nbytes > 0)
+ val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
+ op->dummy.nbytes * 8 / op->dummy.buswidth);
+
+ if (op->data.nbytes > 0)
+ val |= TRAN_CONF_DATA_EN;
+
+ writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
+ writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
+
+ ingenic_sfc_set_transfer_mode(sfc, op);
+
+ writel(0x1f, sfc->base + SFC_REG_SCR);
+ writel(~(INTC_MASK_END | INTC_MASK_RREQ), sfc->base + SFC_REG_INTC);
+
+ writel(0, sfc->base + SFC_REG_MEM_ADDR);
+
+ writel(TRIG_FLUSH, sfc->base + SFC_REG_TRIG);
+ writel(TRIG_START, sfc->base + SFC_REG_TRIG);
+
+ ret = wait_for_completion_timeout(&sfc->completion,
+ msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
+ if (!ret) {
+ writel(0x1f, sfc->base + SFC_REG_INTC);
+ writel(0x1f, sfc->base + SFC_REG_SCR);
+ dev_err(sfc->dev, "line:%d Timeout for ACK from SFC device\n", __LINE__);
+ return -ETIMEDOUT;
+ }
+
+ ingenic_sfc_read_rxfifo(sfc, op->data.buf.in, op->data.nbytes);
+ readl_poll_timeout(sfc->base + SFC_REG_SR, val, val & SR_END, 10, 0);
+
+ writel(INTC_MASK_END | INTC_MASK_RREQ, sfc->base + SFC_REG_SCR);
+ writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
+
+ return 0;
+}
+
+static int ingenic_sfc_exec_op_dma(struct ingenic_sfc *sfc, const struct spi_mem_op *op)
+{
+ dma_addr_t addr;
+ int ret, val;
+
+ val = readl(sfc->base + SFC_REG_GLB);
+ u32p_replace_bits(&val, op->data.dir == SPI_MEM_DATA_IN ?
+ GLB_TRAN_DIR_READ : GLB_TRAN_DIR_WRITE, GLB_TRAN_DIR_MASK);
+ u32p_replace_bits(&val, GLB_OP_MODE_DMA, GLB_OP_MODE_MASK);
+ writel(val, sfc->base + SFC_REG_GLB);
+
+ val = TRAN_CONF_CMD_EN | op->cmd.opcode;
+
+ if (op->addr.nbytes > 0) {
+ val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK, op->addr.nbytes);
+ writel(op->addr.val & 0xffffffff, sfc->base + SFC_REG_DEV_ADDR(0));
+ writel(op->addr.val >> 32, sfc->base + SFC_REG_DEV_ADDR_PLUS(0));
+ }
+
+ if (op->dummy.nbytes > 0)
+ val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
+ op->dummy.nbytes * 8 / op->dummy.buswidth);
+
+ if (op->data.nbytes > 0)
+ val |= TRAN_CONF_DATA_EN;
+
+ writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
+ writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
+
+ ingenic_sfc_set_transfer_mode(sfc, op);
+
+ writel(0x1f, sfc->base + SFC_REG_SCR);
+ writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
+
+ switch (op->data.dir) {
+ case SPI_MEM_DATA_IN:
+ addr = dma_map_single(sfc->dev, op->data.buf.in, op->data.nbytes, DMA_FROM_DEVICE);
+ if (dma_mapping_error(sfc->dev, addr)) {
+ dev_err(sfc->dev, "RX DMA memory not mapped\n");
+ return -ENOMEM;
+ }
+
+ writel(addr, sfc->base + SFC_REG_MEM_ADDR);
+ break;
+
+ case SPI_MEM_DATA_OUT:
+ addr = dma_map_single(sfc->dev, (void *)op->data.buf.out,
+ op->data.nbytes, DMA_TO_DEVICE);
+ if (dma_mapping_error(sfc->dev, addr)) {
+ dev_err(sfc->dev, "TX DMA memory not mapped\n");
+ return -ENOMEM;
+ }
+
+ writel(addr, sfc->base + SFC_REG_MEM_ADDR);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ writel(TRIG_START, sfc->base + SFC_REG_TRIG);
+
+ ret = wait_for_completion_timeout(&sfc->completion,
+ msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
+ if (!ret) {
+ writel(0x1f, sfc->base + SFC_REG_INTC);
+ writel(0x1f, sfc->base + SFC_REG_SCR);
+ dev_err(sfc->dev, "line:%d Timeout for ACK from SFC device\n", __LINE__);
+ return -ETIMEDOUT;
+ }
+
+ dma_unmap_single(sfc->dev, addr, op->data.nbytes,
+ op->data.dir == SPI_MEM_DATA_IN ? DMA_FROM_DEVICE : DMA_TO_DEVICE);
+
+ writel(INTC_MASK_END, sfc->base + SFC_REG_SCR);
+ writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
+
+ return 0;
+}
+
+static int ingenic_sfc_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
+{
+ struct spi_device *spi = mem->spi;
+ struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
+ uintptr_t addr = (uintptr_t)op->data.buf.in;
+
+ init_completion(&sfc->completion);
+
+ switch (op->data.dir) {
+ case SPI_MEM_DATA_IN:
+ if (sfc->soc_info->version >= ID_X1600 || IS_ALIGNED(addr, 4))
+ break;
+
+ fallthrough;
+
+ case SPI_MEM_NO_DATA:
+ return ingenic_sfc_exec_op_pio(sfc, op);
+
+ default:
+ break;
+ }
+
+ return ingenic_sfc_exec_op_dma(sfc, op);
+}
+
+static int ingenic_sfc_poll_status(struct spi_mem *mem, const struct spi_mem_op *op,
+ u16 mask, u16 match, unsigned long initial_delay_us,
+ unsigned long polling_delay_us, unsigned long timeout_ms)
+{
+ struct spi_device *spi = mem->spi;
+ struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
+ int ret, val;
+
+ init_completion(&sfc->completion);
+
+ val = readl(sfc->base + SFC_REG_GLB);
+ u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
+ u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
+ writel(val, sfc->base + SFC_REG_GLB);
+
+ writel(match, sfc->base + SFC_REG_DEV_STA_EXP);
+ writel(mask, sfc->base + SFC_REG_DEV_STA_MSK);
+
+ val = TRAN_CONF_POLL_EN | TRAN_CONF_CMD_EN | op->cmd.opcode;
+
+ if (op->addr.nbytes > 0) {
+ val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK, op->addr.nbytes);
+
+ writel(op->addr.val & 0xffffffff, sfc->base + SFC_REG_DEV_ADDR(0));
+ writel(op->addr.val >> 32, sfc->base + SFC_REG_DEV_ADDR_PLUS(0));
+ }
+
+ if (op->dummy.nbytes > 0)
+ val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
+ op->dummy.nbytes * 8 / op->dummy.buswidth);
+
+ if (op->data.nbytes > 0)
+ val |= TRAN_CONF_DATA_EN;
+
+ writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
+ writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
+
+ ingenic_sfc_set_transfer_mode(sfc, op);
+
+ writel(0x1f, sfc->base + SFC_REG_SCR);
+ writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
+
+ writel(0, sfc->base + SFC_REG_MEM_ADDR);
+
+ writel(TRIG_START, sfc->base + SFC_REG_TRIG);
+
+ ret = wait_for_completion_timeout(&sfc->completion,
+ msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
+ if (!ret) {
+ writel(0x1f, sfc->base + SFC_REG_INTC);
+ writel(0x1f, sfc->base + SFC_REG_SCR);
+ dev_err(sfc->dev, "line:%d Timeout for ACK from SFC device\n", __LINE__);
+ return -ETIMEDOUT;
+ }
+
+ writel(SCR_CLR_END, sfc->base + SFC_REG_SCR);
+ writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
+
+ return 0;
+}
+
+static const struct spi_controller_mem_ops ingenic_sfc_mem_ops = {
+ .adjust_op_size = ingenic_sfc_adjust_op_size,
+ .supports_op = ingenic_sfc_supports_op,
+ .exec_op = ingenic_sfc_exec_op,
+ .poll_status = ingenic_sfc_poll_status,
+};
+
+static int ingenic_sfc_setup(struct spi_device *spi)
+{
+ struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
+ unsigned long rate;
+ int ret, val;
+
+ if (!spi->max_speed_hz)
+ return -EINVAL;
+
+ ret = clk_set_rate(sfc->clk, spi->max_speed_hz * 2);
+ if (ret)
+ return -EINVAL;
+
+ writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
+ writel(0, sfc->base + SFC_REG_DEV_CONF);
+ writel(0, sfc->base + SFC_REG_CGE);
+
+ val = readl(sfc->base + SFC_REG_GLB);
+ u32p_replace_bits(&val, 64 - 1, GLB_THRESHOLD_MASK);
+ writel(val, sfc->base + SFC_REG_GLB);
+
+ val = readl(sfc->base + SFC_REG_DEV_CONF);
+
+ /* cpha bit:0 , cpol bit:0 */
+ val &= ~(DEV_CONF_CPHA | DEV_CONF_CPOL);
+ val |= spi->mode & SPI_CPHA ? DEV_CONF_CPHA : 0;
+ val |= spi->mode & SPI_CPOL ? DEV_CONF_CPOL : 0;
+
+ /* ce_dl bit:1, hold bit:1, wp bit:1 */
+ val |= (DEV_CONF_CE_DL | DEV_CONF_HOLD_DL | DEV_CONF_WP_DL);
+
+ writel(val, sfc->base + SFC_REG_DEV_CONF);
+
+ val = readl(sfc->base + SFC_REG_GLB);
+ u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
+ writel(val, sfc->base + SFC_REG_GLB);
+
+ rate = clk_get_rate(sfc->clk);
+ val = readl(sfc->base + SFC_REG_DEV_CONF);
+ if (sfc->soc_info->version >= ID_X1600 && rate >= 200000000)
+ u32p_replace_bits(&val, DEV_CONF_SMP_DELAY_180DEG, DEV_CONF_SMP_DELAY_MASK);
+ else if (sfc->soc_info->version == ID_X1000 && rate >= 100000000)
+ u32p_replace_bits(&val, DEV_CONF_SMP_DELAY_HALF_CYCLE, DEV_CONF_SMP_DELAY_MASK);
+ writel(val, sfc->base + SFC_REG_DEV_CONF);
+
+ return 0;
+}
+
+static int ingenic_sfc_probe(struct platform_device *pdev)
+{
+ struct ingenic_sfc *sfc;
+ struct spi_controller *ctlr;
+ int ret;
+
+ ctlr = spi_alloc_master(&pdev->dev, sizeof(*sfc));
+ if (!ctlr)
+ return -ENOMEM;
+
+ sfc = spi_controller_get_devdata(ctlr);
+
+ sfc->soc_info = of_device_get_match_data(&pdev->dev);
+ if (!sfc->soc_info) {
+ dev_err(&pdev->dev, "No of match data provided\n");
+ ret = -ENODEV;
+ goto err_put_master;
+ }
+
+ sfc->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(sfc->base)) {
+ ret = PTR_ERR(sfc->base);
+ goto err_put_master;
+ }
+
+ sfc->clk = devm_clk_get(&pdev->dev, "sfc");
+ if (IS_ERR(sfc->clk)) {
+ ret = IS_ERR(sfc->clk);
+ goto err_put_master;
+ }
+
+ ret = clk_prepare_enable(sfc->clk);
+ if (ret)
+ goto err_put_master;
+
+ sfc->irq = platform_get_irq(pdev, 0);
+ if (sfc->irq < 0) {
+ ret = sfc->irq;
+ goto err_put_master;
+ }
+
+ sfc->dev = &pdev->dev;
+
+ platform_set_drvdata(pdev, sfc);
+
+ ret = devm_request_irq(&pdev->dev, sfc->irq, ingenic_sfc_irq_handler, 0,
+ dev_name(&pdev->dev), sfc);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request irq%d, ret = %d\n", sfc->irq, ret);
+ goto err_put_master;
+ }
+
+ ctlr->bus_num = -1;
+ ctlr->num_chipselect = 1;
+ ctlr->mem_ops = &ingenic_sfc_mem_ops;
+ ctlr->dev.of_node = pdev->dev.of_node;
+ ctlr->setup = ingenic_sfc_setup;
+ ctlr->mode_bits = SPI_CPHA | SPI_CPOL |
+ SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
+ if (sfc->soc_info->version >= ID_X2000)
+ ctlr->mode_bits |= SPI_RX_OCTAL | SPI_TX_OCTAL;
+
+ ret = devm_spi_register_controller(&pdev->dev, ctlr);
+ if (ret)
+ goto err_put_master;
+
+ return 0;
+
+err_put_master:
+ spi_master_put(ctlr);
+
+ return ret;
+}
+
+static const struct ingenic_soc_info x1000_soc_info = {
+ .version = ID_X1000,
+
+ .max_bus_width = 4,
+
+ .tran_mode_mask = TRAN_CONF_TRAN_MODE_MASK,
+};
+
+static const struct ingenic_soc_info x1600_soc_info = {
+ .version = ID_X1600,
+
+ .max_bus_width = 4,
+
+ .tran_mode_mask = TRAN_CONF_TRAN_MODE_MASK,
+};
+
+static const struct ingenic_soc_info x2000_soc_info = {
+ .version = ID_X2000,
+
+ .max_bus_width = 8,
+
+ .tran_mode_mask = TRAN_CFG1_TRAN_MODE_MASK,
+};
+
+static const struct of_device_id ingenic_sfc_of_matches[] = {
+ { .compatible = "ingenic,x1000-sfc", .data = &x1000_soc_info },
+ { .compatible = "ingenic,x1600-sfc", .data = &x1600_soc_info },
+ { .compatible = "ingenic,x2000-sfc", .data = &x2000_soc_info },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ingenic_sfc_of_matches);
+
+static struct platform_driver ingenic_sfc_driver = {
+ .driver = {
+ .name = "ingenic-sfc",
+ .of_match_table = ingenic_sfc_of_matches,
+ },
+ .probe = ingenic_sfc_probe,
+};
+module_platform_driver(ingenic_sfc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>");
+MODULE_DESCRIPTION("Ingenic SoCs SPI Flash Controller Driver");
--
2.7.4
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
2022-07-22 16:48 ` 周琰杰 (Zhou Yanjie)
@ 2022-07-22 18:07 ` Krzysztof Kozlowski
-1 siblings, 0 replies; 78+ messages in thread
From: Krzysztof Kozlowski @ 2022-07-22 18:07 UTC (permalink / raw)
To: 周琰杰 (Zhou Yanjie),
tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, broonie, robh+dt, krzysztof.kozlowski+dt
Cc: linux-mtd, linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, paul, dongsheng.qiu, aric.pzqi,
rick.tyliu, jinghui.liu, sernia.zhou, reimu
On 22/07/2022 18:48, 周琰杰 (Zhou Yanjie) wrote:
> Add SFC support for the X1000 SoC, the X1600 SoC, and the X2000 SoC
> from Ingenic.
>
> Signed-off-by: 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
> ---
> drivers/spi/Kconfig | 9 +
> drivers/spi/Makefile | 1 +
> drivers/spi/spi-ingenic-sfc.c | 662 ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 672 insertions(+)
> create mode 100644 drivers/spi/spi-ingenic-sfc.c
>
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index 3b1044e..1077bd3 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -437,6 +437,15 @@ config SPI_INGENIC
> To compile this driver as a module, choose M here: the module
> will be called spi-ingenic.
>
> +config SPI_INGENIC_SFC
> + tristate "Ingenic SoCs SPI Flash Controller"
> + depends on MACH_INGENIC || COMPILE_TEST
> + help
> + This enables support for the Ingenic SoCs SPI flash controller.
> +
> + To compile this driver as a module, choose M here: the module
> + will be called ingenic-sfc.
> +
> config SPI_INTEL
> tristate
>
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index 0f44eb6..f3e42c0 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -62,6 +62,7 @@ obj-$(CONFIG_SPI_HISI_SFC_V3XX) += spi-hisi-sfc-v3xx.o
> obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o
> obj-$(CONFIG_SPI_IMX) += spi-imx.o
> obj-$(CONFIG_SPI_INGENIC) += spi-ingenic.o
> +obj-$(CONFIG_SPI_INGENIC_SFC) += spi-ingenic-sfc.o
> obj-$(CONFIG_SPI_INTEL) += spi-intel.o
> obj-$(CONFIG_SPI_INTEL_PCI) += spi-intel-pci.o
> obj-$(CONFIG_SPI_INTEL_PLATFORM) += spi-intel-platform.o
> diff --git a/drivers/spi/spi-ingenic-sfc.c b/drivers/spi/spi-ingenic-sfc.c
> new file mode 100644
> index 00000000..a565546
> --- /dev/null
> +++ b/drivers/spi/spi-ingenic-sfc.c
> @@ -0,0 +1,662 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +/*
> + * Ingenic SoCs SPI Flash Controller Driver
> + * Copyright (c) 2022 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/interrupt.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/mtd/mtd.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/spi/spi.h>
> +#include <linux/spi/spi-mem.h>
> +
> +/* SFC register offsets */
> +#define SFC_REG_GLB 0x0000
> +#define SFC_REG_DEV_CONF 0x0004
> +#define SFC_REG_DEV_STA_EXP 0x0008
> +#define SFC_REG_DEV_STA_RT 0x000c
> +#define SFC_REG_DEV_STA_MSK 0x0010
> +#define SFC_REG_TRAN_CONF(n) (0x0014 + n * 4)
> +#define SFC_REG_TRAN_CFG0(n) (0x0014 + n * 4)
> +#define SFC_REG_TRAN_LEN 0x002c
> +#define SFC_REG_DEV_ADDR(n) (0x0030 + n * 4)
> +#define SFC_REG_DEV_ADDR_PLUS(n) (0x0048 + n * 4)
> +#define SFC_REG_MEM_ADDR 0x0060
> +#define SFC_REG_TRIG 0x0064
> +#define SFC_REG_SR 0x0068
> +#define SFC_REG_SCR 0x006c
> +#define SFC_REG_INTC 0x0070
> +#define SFC_REG_FSM 0x0074
> +#define SFC_REG_CGE 0x0078
> +#define SFC_REG_TRAN_CFG1(n) (0x009c + n * 4)
> +#define SFC_REG_DR 0x1000
> +
> +/* bits within the GLB register */
> +#define GLB_TRAN_DIR_MASK GENMASK(13, 13)
> +#define GLB_TRAN_DIR_WRITE 0x1
> +#define GLB_TRAN_DIR_READ 0x0
> +#define GLB_THRESHOLD_MASK GENMASK(12, 7)
> +#define GLB_OP_MODE_MASK GENMASK(6, 6)
> +#define GLB_OP_MODE_DMA 0x1
> +#define GLB_OP_MODE_SLAVE 0x0
> +#define GLB_PHASE_NUM_MASK GENMASK(5, 3)
> +#define GLB_WP_EN BIT(2)
> +#define GLB_BURST_MD_MASK GENMASK(1, 0)
> +#define GLB_BURST_MD_INCR32 0x3
> +#define GLB_BURST_MD_INCR16 0x2
> +#define GLB_BURST_MD_INCR8 0x1
> +#define GLB_BURST_MD_INCR4 0x0
> +
> +/* bits within the DEV_CONF register */
> +#define DEV_CONF_SMP_DELAY_MASK GENMASK(20, 16)
> +#define DEV_CONF_SMP_DELAY_180DEG 0x4
> +#define DEV_CONF_SMP_DELAY_HALF_CYCLE 0x1
> +#define DEV_CONF_CMD_TYPE_MASK GENMASK(15, 15)
> +#define DEV_CONF_CMD_TYPE_16BIT 0x1
> +#define DEV_CONF_CMD_TYPE_8BIT 0x0
> +#define DEV_CONF_STA_TYPE_MASK GENMASK(14, 13)
> +#define DEV_CONF_THOLD_MASK GENMASK(12, 11)
> +#define DEV_CONF_TSETUP_MASK GENMASK(10, 9)
> +#define DEV_CONF_TSH_MASK GENMASK(8, 5)
> +#define DEV_CONF_CPHA BIT(4)
> +#define DEV_CONF_CPOL BIT(3)
> +#define DEV_CONF_CE_DL BIT(2)
> +#define DEV_CONF_HOLD_DL BIT(1)
> +#define DEV_CONF_WP_DL BIT(0)
> +
> +/* bits within the TRAN_CONF(n) register */
> +#define TRAN_CONF_TRAN_MODE_MASK GENMASK(31, 29)
> +#define TRAN_CONF_ADDR_WIDTH_MASK GENMASK(28, 26)
> +#define TRAN_CONF_POLL_EN BIT(25)
> +#define TRAN_CONF_CMD_EN BIT(24)
> +#define TRAN_CONF_PHASE_FORMAT_MASK GENMASK(23, 23)
> +#define TRAN_CONF_DMY_BITS_MASK GENMASK(22, 17)
> +#define TRAN_CONF_DATA_EN BIT(16)
> +#define TRAN_CONF_CMD_MASK GENMASK(15, 0)
> +
> +/* bits within the TRIG register */
> +#define TRIG_FLUSH BIT(2)
> +#define TRIG_STOP BIT(1)
> +#define TRIG_START BIT(0)
> +
> +/* bits within the SR register */
> +#define SR_FIFO_NUM_MASK GENMASK(22, 16)
> +#define SR_END BIT(4)
> +#define SR_TRAN_REQ BIT(3)
> +#define SR_RECE_REQ BIT(2)
> +#define SR_OVER BIT(1)
> +#define SR_UNDER BIT(0)
> +
> +/* bits within the SCR register */
> +#define SCR_CLR_END BIT(4)
> +#define SCR_CLR_TREQ BIT(3)
> +#define SCR_CLR_RREQ BIT(2)
> +#define SCR_CLR_OVER BIT(1)
> +#define SCR_CLR_UNDER BIT(0)
> +
> +/* bits within the INTC register */
> +#define INTC_MASK_END BIT(4)
> +#define INTC_MASK_TREQ BIT(3)
> +#define INTC_MASK_RREQ BIT(2)
> +#define INTC_MASK_OVER BIT(1)
> +#define INTC_MASK_UNDER BIT(0)
> +
> +/* bits within the TRAN_CFG1(n) register */
> +#define TRAN_CFG1_TRAN_MODE_MASK GENMASK(7, 4)
> +
> +#define TRAN_MODE_STANDARD 0
> +#define TRAN_MODE_DUAL_DATA 1
> +#define TRAN_MODE_DUAL_IO 2
> +#define TRAN_MODE_DUAL_FULL 3
> +#define TRAN_MODE_QUAD_DATA 5
> +#define TRAN_MODE_QUAD_IO 6
> +#define TRAN_MODE_QUAD_FULL 7
> +#define TRAN_MODE_OCTAL_DATA 9
> +#define TRAN_MODE_OCTAL_IO 10
> +#define TRAN_MODE_OCTAL_FULL 11
> +
> +#define INGENIC_SFC_FIFO_SIZE (64 * 4)
> +
> +#define INGENIC_SFC_TRANSFER_TIMEOUT 1000
> +
> +enum ingenic_sfc_version {
> + ID_X1000,
> + ID_X1600,
> + ID_X2000,
> +};
> +
> +struct ingenic_soc_info {
> + enum ingenic_sfc_version version;
> +
No need for blank line.
> + unsigned int max_bus_width;
> +
Remove as well.
> + const u32 tran_mode_mask;
> +};
> +
> +struct ingenic_sfc {
> + const struct ingenic_soc_info *soc_info;
> +
> + void __iomem *base;
> + struct device *dev;
> + struct clk *clk;
> + int irq;
> +
> + struct completion completion;
> +};
> +
> +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
> +{
> + struct ingenic_sfc *sfc = data;
> +
> + writel(0x1f, sfc->base + SFC_REG_INTC);
> +
> + complete(&sfc->completion);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int ingenic_sfc_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
> +{
> + uintptr_t addr = (uintptr_t)op->data.buf.in;
> +
> + if (op->data.nbytes > INGENIC_SFC_FIFO_SIZE && !IS_ALIGNED(addr, 4))
> + op->data.nbytes = INGENIC_SFC_FIFO_SIZE;
> +
> + return 0;
> +}
> +
> +static bool ingenic_sfc_supports_op(struct spi_mem *mem, const struct spi_mem_op *op)
> +{
> + struct spi_device *spi = mem->spi;
> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
> + uintptr_t addr = (uintptr_t)op->data.buf.in;
> +
> + /* The controller only supports Standard SPI mode, Duall mode, Quad mode and Octal mode. */
> + if (op->cmd.buswidth > sfc->soc_info->max_bus_width ||
> + op->addr.buswidth > sfc->soc_info->max_bus_width ||
> + op->dummy.buswidth > sfc->soc_info->max_bus_width ||
> + op->data.buswidth > sfc->soc_info->max_bus_width)
> + return false;
> +
> + /* Max 32 dummy clock cycles supported */
> + if (op->dummy.nbytes && op->dummy.nbytes * 8 / op->dummy.buswidth > 32)
> + return false;
> +
> + /* Max rx data length, check controller limits and alignment */
> + if (op->data.dir == SPI_MEM_DATA_IN &&
> + op->data.nbytes > INGENIC_SFC_FIFO_SIZE && !IS_ALIGNED(addr, 4))
> + return false;
> +
> + /* Max 6 bytes address width supported */
> + if (op->addr.nbytes > 6)
> + return false;
> +
> + return spi_mem_default_supports_op(mem, op);
> +}
> +
> +static void ingenic_sfc_set_transfer_mode(struct ingenic_sfc *sfc, const struct spi_mem_op *op)
> +{
> + int val;
> +
> + val = readl(sfc->base + (sfc->soc_info->version >= ID_X1600 ?
> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
This is no really readable.
> + val &= ~sfc->soc_info->tran_mode_mask;
> + if (op->cmd.buswidth == 8)
> + val |= (TRAN_MODE_OCTAL_FULL << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->cmd.buswidth == 4)
> + val |= (TRAN_MODE_QUAD_FULL << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->cmd.buswidth == 2)
> + val |= (TRAN_MODE_DUAL_FULL << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->addr.buswidth == 8)
> + val |= (TRAN_MODE_OCTAL_IO << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->addr.buswidth == 4)
> + val |= (TRAN_MODE_QUAD_IO << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->addr.buswidth == 2)
> + val |= (TRAN_MODE_DUAL_IO << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->data.buswidth == 8)
> + val |= (TRAN_MODE_OCTAL_DATA << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->data.buswidth == 4)
> + val |= (TRAN_MODE_QUAD_DATA << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->data.buswidth == 2)
> + val |= (TRAN_MODE_DUAL_DATA << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else
> + val |= (TRAN_MODE_STANDARD << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + writel(val, sfc->base + (sfc->soc_info->version >= ID_X1600 ?
> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
Not readable.
> +}
> +
> +/*
> + * We only need PIO mode to handle the SPI_MEM_NO_DATA transfers,
> + * and the unaligned accesses in SPI_MEM_DATA_IN transfers.
> + */
(...)
> + sfc->dev = &pdev->dev;
> +
> + platform_set_drvdata(pdev, sfc);
> +
> + ret = devm_request_irq(&pdev->dev, sfc->irq, ingenic_sfc_irq_handler, 0,
> + dev_name(&pdev->dev), sfc);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to request irq%d, ret = %d\n", sfc->irq, ret);
> + goto err_put_master;
> + }
> +
> + ctlr->bus_num = -1;
> + ctlr->num_chipselect = 1;
> + ctlr->mem_ops = &ingenic_sfc_mem_ops;
> + ctlr->dev.of_node = pdev->dev.of_node;
> + ctlr->setup = ingenic_sfc_setup;
> + ctlr->mode_bits = SPI_CPHA | SPI_CPOL |
> + SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
> + if (sfc->soc_info->version >= ID_X2000)
> + ctlr->mode_bits |= SPI_RX_OCTAL | SPI_TX_OCTAL;
> +
> + ret = devm_spi_register_controller(&pdev->dev, ctlr);
> + if (ret)
> + goto err_put_master;
> +
> + return 0;
> +
> +err_put_master:
> + spi_master_put(ctlr);
What about unpreparing clocks? Is it part of spi_master_put()?
> +
> + return ret;
> +}
> +
> +static const struct ingenic_soc_info x1000_soc_info = {
> + .version = ID_X1000,
> +
No need for blank lines after each field.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
@ 2022-07-22 18:07 ` Krzysztof Kozlowski
0 siblings, 0 replies; 78+ messages in thread
From: Krzysztof Kozlowski @ 2022-07-22 18:07 UTC (permalink / raw)
To: 周琰杰 (Zhou Yanjie),
tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, broonie, robh+dt, krzysztof.kozlowski+dt
Cc: linux-mtd, linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, paul, dongsheng.qiu, aric.pzqi,
rick.tyliu, jinghui.liu, sernia.zhou, reimu
On 22/07/2022 18:48, 周琰杰 (Zhou Yanjie) wrote:
> Add SFC support for the X1000 SoC, the X1600 SoC, and the X2000 SoC
> from Ingenic.
>
> Signed-off-by: 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
> ---
> drivers/spi/Kconfig | 9 +
> drivers/spi/Makefile | 1 +
> drivers/spi/spi-ingenic-sfc.c | 662 ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 672 insertions(+)
> create mode 100644 drivers/spi/spi-ingenic-sfc.c
>
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index 3b1044e..1077bd3 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -437,6 +437,15 @@ config SPI_INGENIC
> To compile this driver as a module, choose M here: the module
> will be called spi-ingenic.
>
> +config SPI_INGENIC_SFC
> + tristate "Ingenic SoCs SPI Flash Controller"
> + depends on MACH_INGENIC || COMPILE_TEST
> + help
> + This enables support for the Ingenic SoCs SPI flash controller.
> +
> + To compile this driver as a module, choose M here: the module
> + will be called ingenic-sfc.
> +
> config SPI_INTEL
> tristate
>
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index 0f44eb6..f3e42c0 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -62,6 +62,7 @@ obj-$(CONFIG_SPI_HISI_SFC_V3XX) += spi-hisi-sfc-v3xx.o
> obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o
> obj-$(CONFIG_SPI_IMX) += spi-imx.o
> obj-$(CONFIG_SPI_INGENIC) += spi-ingenic.o
> +obj-$(CONFIG_SPI_INGENIC_SFC) += spi-ingenic-sfc.o
> obj-$(CONFIG_SPI_INTEL) += spi-intel.o
> obj-$(CONFIG_SPI_INTEL_PCI) += spi-intel-pci.o
> obj-$(CONFIG_SPI_INTEL_PLATFORM) += spi-intel-platform.o
> diff --git a/drivers/spi/spi-ingenic-sfc.c b/drivers/spi/spi-ingenic-sfc.c
> new file mode 100644
> index 00000000..a565546
> --- /dev/null
> +++ b/drivers/spi/spi-ingenic-sfc.c
> @@ -0,0 +1,662 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +/*
> + * Ingenic SoCs SPI Flash Controller Driver
> + * Copyright (c) 2022 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/interrupt.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/mtd/mtd.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/spi/spi.h>
> +#include <linux/spi/spi-mem.h>
> +
> +/* SFC register offsets */
> +#define SFC_REG_GLB 0x0000
> +#define SFC_REG_DEV_CONF 0x0004
> +#define SFC_REG_DEV_STA_EXP 0x0008
> +#define SFC_REG_DEV_STA_RT 0x000c
> +#define SFC_REG_DEV_STA_MSK 0x0010
> +#define SFC_REG_TRAN_CONF(n) (0x0014 + n * 4)
> +#define SFC_REG_TRAN_CFG0(n) (0x0014 + n * 4)
> +#define SFC_REG_TRAN_LEN 0x002c
> +#define SFC_REG_DEV_ADDR(n) (0x0030 + n * 4)
> +#define SFC_REG_DEV_ADDR_PLUS(n) (0x0048 + n * 4)
> +#define SFC_REG_MEM_ADDR 0x0060
> +#define SFC_REG_TRIG 0x0064
> +#define SFC_REG_SR 0x0068
> +#define SFC_REG_SCR 0x006c
> +#define SFC_REG_INTC 0x0070
> +#define SFC_REG_FSM 0x0074
> +#define SFC_REG_CGE 0x0078
> +#define SFC_REG_TRAN_CFG1(n) (0x009c + n * 4)
> +#define SFC_REG_DR 0x1000
> +
> +/* bits within the GLB register */
> +#define GLB_TRAN_DIR_MASK GENMASK(13, 13)
> +#define GLB_TRAN_DIR_WRITE 0x1
> +#define GLB_TRAN_DIR_READ 0x0
> +#define GLB_THRESHOLD_MASK GENMASK(12, 7)
> +#define GLB_OP_MODE_MASK GENMASK(6, 6)
> +#define GLB_OP_MODE_DMA 0x1
> +#define GLB_OP_MODE_SLAVE 0x0
> +#define GLB_PHASE_NUM_MASK GENMASK(5, 3)
> +#define GLB_WP_EN BIT(2)
> +#define GLB_BURST_MD_MASK GENMASK(1, 0)
> +#define GLB_BURST_MD_INCR32 0x3
> +#define GLB_BURST_MD_INCR16 0x2
> +#define GLB_BURST_MD_INCR8 0x1
> +#define GLB_BURST_MD_INCR4 0x0
> +
> +/* bits within the DEV_CONF register */
> +#define DEV_CONF_SMP_DELAY_MASK GENMASK(20, 16)
> +#define DEV_CONF_SMP_DELAY_180DEG 0x4
> +#define DEV_CONF_SMP_DELAY_HALF_CYCLE 0x1
> +#define DEV_CONF_CMD_TYPE_MASK GENMASK(15, 15)
> +#define DEV_CONF_CMD_TYPE_16BIT 0x1
> +#define DEV_CONF_CMD_TYPE_8BIT 0x0
> +#define DEV_CONF_STA_TYPE_MASK GENMASK(14, 13)
> +#define DEV_CONF_THOLD_MASK GENMASK(12, 11)
> +#define DEV_CONF_TSETUP_MASK GENMASK(10, 9)
> +#define DEV_CONF_TSH_MASK GENMASK(8, 5)
> +#define DEV_CONF_CPHA BIT(4)
> +#define DEV_CONF_CPOL BIT(3)
> +#define DEV_CONF_CE_DL BIT(2)
> +#define DEV_CONF_HOLD_DL BIT(1)
> +#define DEV_CONF_WP_DL BIT(0)
> +
> +/* bits within the TRAN_CONF(n) register */
> +#define TRAN_CONF_TRAN_MODE_MASK GENMASK(31, 29)
> +#define TRAN_CONF_ADDR_WIDTH_MASK GENMASK(28, 26)
> +#define TRAN_CONF_POLL_EN BIT(25)
> +#define TRAN_CONF_CMD_EN BIT(24)
> +#define TRAN_CONF_PHASE_FORMAT_MASK GENMASK(23, 23)
> +#define TRAN_CONF_DMY_BITS_MASK GENMASK(22, 17)
> +#define TRAN_CONF_DATA_EN BIT(16)
> +#define TRAN_CONF_CMD_MASK GENMASK(15, 0)
> +
> +/* bits within the TRIG register */
> +#define TRIG_FLUSH BIT(2)
> +#define TRIG_STOP BIT(1)
> +#define TRIG_START BIT(0)
> +
> +/* bits within the SR register */
> +#define SR_FIFO_NUM_MASK GENMASK(22, 16)
> +#define SR_END BIT(4)
> +#define SR_TRAN_REQ BIT(3)
> +#define SR_RECE_REQ BIT(2)
> +#define SR_OVER BIT(1)
> +#define SR_UNDER BIT(0)
> +
> +/* bits within the SCR register */
> +#define SCR_CLR_END BIT(4)
> +#define SCR_CLR_TREQ BIT(3)
> +#define SCR_CLR_RREQ BIT(2)
> +#define SCR_CLR_OVER BIT(1)
> +#define SCR_CLR_UNDER BIT(0)
> +
> +/* bits within the INTC register */
> +#define INTC_MASK_END BIT(4)
> +#define INTC_MASK_TREQ BIT(3)
> +#define INTC_MASK_RREQ BIT(2)
> +#define INTC_MASK_OVER BIT(1)
> +#define INTC_MASK_UNDER BIT(0)
> +
> +/* bits within the TRAN_CFG1(n) register */
> +#define TRAN_CFG1_TRAN_MODE_MASK GENMASK(7, 4)
> +
> +#define TRAN_MODE_STANDARD 0
> +#define TRAN_MODE_DUAL_DATA 1
> +#define TRAN_MODE_DUAL_IO 2
> +#define TRAN_MODE_DUAL_FULL 3
> +#define TRAN_MODE_QUAD_DATA 5
> +#define TRAN_MODE_QUAD_IO 6
> +#define TRAN_MODE_QUAD_FULL 7
> +#define TRAN_MODE_OCTAL_DATA 9
> +#define TRAN_MODE_OCTAL_IO 10
> +#define TRAN_MODE_OCTAL_FULL 11
> +
> +#define INGENIC_SFC_FIFO_SIZE (64 * 4)
> +
> +#define INGENIC_SFC_TRANSFER_TIMEOUT 1000
> +
> +enum ingenic_sfc_version {
> + ID_X1000,
> + ID_X1600,
> + ID_X2000,
> +};
> +
> +struct ingenic_soc_info {
> + enum ingenic_sfc_version version;
> +
No need for blank line.
> + unsigned int max_bus_width;
> +
Remove as well.
> + const u32 tran_mode_mask;
> +};
> +
> +struct ingenic_sfc {
> + const struct ingenic_soc_info *soc_info;
> +
> + void __iomem *base;
> + struct device *dev;
> + struct clk *clk;
> + int irq;
> +
> + struct completion completion;
> +};
> +
> +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
> +{
> + struct ingenic_sfc *sfc = data;
> +
> + writel(0x1f, sfc->base + SFC_REG_INTC);
> +
> + complete(&sfc->completion);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int ingenic_sfc_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
> +{
> + uintptr_t addr = (uintptr_t)op->data.buf.in;
> +
> + if (op->data.nbytes > INGENIC_SFC_FIFO_SIZE && !IS_ALIGNED(addr, 4))
> + op->data.nbytes = INGENIC_SFC_FIFO_SIZE;
> +
> + return 0;
> +}
> +
> +static bool ingenic_sfc_supports_op(struct spi_mem *mem, const struct spi_mem_op *op)
> +{
> + struct spi_device *spi = mem->spi;
> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
> + uintptr_t addr = (uintptr_t)op->data.buf.in;
> +
> + /* The controller only supports Standard SPI mode, Duall mode, Quad mode and Octal mode. */
> + if (op->cmd.buswidth > sfc->soc_info->max_bus_width ||
> + op->addr.buswidth > sfc->soc_info->max_bus_width ||
> + op->dummy.buswidth > sfc->soc_info->max_bus_width ||
> + op->data.buswidth > sfc->soc_info->max_bus_width)
> + return false;
> +
> + /* Max 32 dummy clock cycles supported */
> + if (op->dummy.nbytes && op->dummy.nbytes * 8 / op->dummy.buswidth > 32)
> + return false;
> +
> + /* Max rx data length, check controller limits and alignment */
> + if (op->data.dir == SPI_MEM_DATA_IN &&
> + op->data.nbytes > INGENIC_SFC_FIFO_SIZE && !IS_ALIGNED(addr, 4))
> + return false;
> +
> + /* Max 6 bytes address width supported */
> + if (op->addr.nbytes > 6)
> + return false;
> +
> + return spi_mem_default_supports_op(mem, op);
> +}
> +
> +static void ingenic_sfc_set_transfer_mode(struct ingenic_sfc *sfc, const struct spi_mem_op *op)
> +{
> + int val;
> +
> + val = readl(sfc->base + (sfc->soc_info->version >= ID_X1600 ?
> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
This is no really readable.
> + val &= ~sfc->soc_info->tran_mode_mask;
> + if (op->cmd.buswidth == 8)
> + val |= (TRAN_MODE_OCTAL_FULL << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->cmd.buswidth == 4)
> + val |= (TRAN_MODE_QUAD_FULL << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->cmd.buswidth == 2)
> + val |= (TRAN_MODE_DUAL_FULL << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->addr.buswidth == 8)
> + val |= (TRAN_MODE_OCTAL_IO << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->addr.buswidth == 4)
> + val |= (TRAN_MODE_QUAD_IO << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->addr.buswidth == 2)
> + val |= (TRAN_MODE_DUAL_IO << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->data.buswidth == 8)
> + val |= (TRAN_MODE_OCTAL_DATA << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->data.buswidth == 4)
> + val |= (TRAN_MODE_QUAD_DATA << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->data.buswidth == 2)
> + val |= (TRAN_MODE_DUAL_DATA << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else
> + val |= (TRAN_MODE_STANDARD << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + writel(val, sfc->base + (sfc->soc_info->version >= ID_X1600 ?
> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
Not readable.
> +}
> +
> +/*
> + * We only need PIO mode to handle the SPI_MEM_NO_DATA transfers,
> + * and the unaligned accesses in SPI_MEM_DATA_IN transfers.
> + */
(...)
> + sfc->dev = &pdev->dev;
> +
> + platform_set_drvdata(pdev, sfc);
> +
> + ret = devm_request_irq(&pdev->dev, sfc->irq, ingenic_sfc_irq_handler, 0,
> + dev_name(&pdev->dev), sfc);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to request irq%d, ret = %d\n", sfc->irq, ret);
> + goto err_put_master;
> + }
> +
> + ctlr->bus_num = -1;
> + ctlr->num_chipselect = 1;
> + ctlr->mem_ops = &ingenic_sfc_mem_ops;
> + ctlr->dev.of_node = pdev->dev.of_node;
> + ctlr->setup = ingenic_sfc_setup;
> + ctlr->mode_bits = SPI_CPHA | SPI_CPOL |
> + SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
> + if (sfc->soc_info->version >= ID_X2000)
> + ctlr->mode_bits |= SPI_RX_OCTAL | SPI_TX_OCTAL;
> +
> + ret = devm_spi_register_controller(&pdev->dev, ctlr);
> + if (ret)
> + goto err_put_master;
> +
> + return 0;
> +
> +err_put_master:
> + spi_master_put(ctlr);
What about unpreparing clocks? Is it part of spi_master_put()?
> +
> + return ret;
> +}
> +
> +static const struct ingenic_soc_info x1000_soc_info = {
> + .version = ID_X1000,
> +
No need for blank lines after each field.
Best regards,
Krzysztof
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
2022-07-22 18:07 ` Krzysztof Kozlowski
@ 2022-07-23 16:53 ` Zhou Yanjie
-1 siblings, 0 replies; 78+ messages in thread
From: Zhou Yanjie @ 2022-07-23 16:53 UTC (permalink / raw)
To: Krzysztof Kozlowski, tudor.ambarus, p.yadav, michael,
miquel.raynal, richard, vigneshr, broonie, robh+dt,
krzysztof.kozlowski+dt
Cc: linux-mtd, linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, paul, dongsheng.qiu, aric.pzqi,
rick.tyliu, jinghui.liu, sernia.zhou, reimu
Hi Krzysztof,
On 2022/7/23 上午2:07, Krzysztof Kozlowski wrote:
> On 22/07/2022 18:48, 周琰杰 (Zhou Yanjie) wrote:
>> Add SFC support for the X1000 SoC, the X1600 SoC, and the X2000 SoC
>> from Ingenic.
>>
>> Signed-off-by: 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
>> ---
>> drivers/spi/Kconfig | 9 +
>> drivers/spi/Makefile | 1 +
>> drivers/spi/spi-ingenic-sfc.c | 662 ++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 672 insertions(+)
>> create mode 100644 drivers/spi/spi-ingenic-sfc.c
>>
>> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
>> index 3b1044e..1077bd3 100644
>> --- a/drivers/spi/Kconfig
>> +++ b/drivers/spi/Kconfig
>> @@ -437,6 +437,15 @@ config SPI_INGENIC
>> To compile this driver as a module, choose M here: the module
>> will be called spi-ingenic.
>>
>> +config SPI_INGENIC_SFC
>> + tristate "Ingenic SoCs SPI Flash Controller"
>> + depends on MACH_INGENIC || COMPILE_TEST
>> + help
>> + This enables support for the Ingenic SoCs SPI flash controller.
>> +
>> + To compile this driver as a module, choose M here: the module
>> + will be called ingenic-sfc.
>> +
>> config SPI_INTEL
>> tristate
>>
>> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
>> index 0f44eb6..f3e42c0 100644
>> --- a/drivers/spi/Makefile
>> +++ b/drivers/spi/Makefile
>> @@ -62,6 +62,7 @@ obj-$(CONFIG_SPI_HISI_SFC_V3XX) += spi-hisi-sfc-v3xx.o
>> obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o
>> obj-$(CONFIG_SPI_IMX) += spi-imx.o
>> obj-$(CONFIG_SPI_INGENIC) += spi-ingenic.o
>> +obj-$(CONFIG_SPI_INGENIC_SFC) += spi-ingenic-sfc.o
>> obj-$(CONFIG_SPI_INTEL) += spi-intel.o
>> obj-$(CONFIG_SPI_INTEL_PCI) += spi-intel-pci.o
>> obj-$(CONFIG_SPI_INTEL_PLATFORM) += spi-intel-platform.o
>> diff --git a/drivers/spi/spi-ingenic-sfc.c b/drivers/spi/spi-ingenic-sfc.c
>> new file mode 100644
>> index 00000000..a565546
>> --- /dev/null
>> +++ b/drivers/spi/spi-ingenic-sfc.c
>> @@ -0,0 +1,662 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +/*
>> + * Ingenic SoCs SPI Flash Controller Driver
>> + * Copyright (c) 2022 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
>> + */
>> +
>> +#include <linux/bitfield.h>
>> +#include <linux/bitops.h>
>> +#include <linux/clk.h>
>> +#include <linux/completion.h>
>> +#include <linux/dma-mapping.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/iopoll.h>
>> +#include <linux/module.h>
>> +#include <linux/mtd/mtd.h>
>> +#include <linux/of_device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/slab.h>
>> +#include <linux/spi/spi.h>
>> +#include <linux/spi/spi-mem.h>
>> +
>> +/* SFC register offsets */
>> +#define SFC_REG_GLB 0x0000
>> +#define SFC_REG_DEV_CONF 0x0004
>> +#define SFC_REG_DEV_STA_EXP 0x0008
>> +#define SFC_REG_DEV_STA_RT 0x000c
>> +#define SFC_REG_DEV_STA_MSK 0x0010
>> +#define SFC_REG_TRAN_CONF(n) (0x0014 + n * 4)
>> +#define SFC_REG_TRAN_CFG0(n) (0x0014 + n * 4)
>> +#define SFC_REG_TRAN_LEN 0x002c
>> +#define SFC_REG_DEV_ADDR(n) (0x0030 + n * 4)
>> +#define SFC_REG_DEV_ADDR_PLUS(n) (0x0048 + n * 4)
>> +#define SFC_REG_MEM_ADDR 0x0060
>> +#define SFC_REG_TRIG 0x0064
>> +#define SFC_REG_SR 0x0068
>> +#define SFC_REG_SCR 0x006c
>> +#define SFC_REG_INTC 0x0070
>> +#define SFC_REG_FSM 0x0074
>> +#define SFC_REG_CGE 0x0078
>> +#define SFC_REG_TRAN_CFG1(n) (0x009c + n * 4)
>> +#define SFC_REG_DR 0x1000
>> +
>> +/* bits within the GLB register */
>> +#define GLB_TRAN_DIR_MASK GENMASK(13, 13)
>> +#define GLB_TRAN_DIR_WRITE 0x1
>> +#define GLB_TRAN_DIR_READ 0x0
>> +#define GLB_THRESHOLD_MASK GENMASK(12, 7)
>> +#define GLB_OP_MODE_MASK GENMASK(6, 6)
>> +#define GLB_OP_MODE_DMA 0x1
>> +#define GLB_OP_MODE_SLAVE 0x0
>> +#define GLB_PHASE_NUM_MASK GENMASK(5, 3)
>> +#define GLB_WP_EN BIT(2)
>> +#define GLB_BURST_MD_MASK GENMASK(1, 0)
>> +#define GLB_BURST_MD_INCR32 0x3
>> +#define GLB_BURST_MD_INCR16 0x2
>> +#define GLB_BURST_MD_INCR8 0x1
>> +#define GLB_BURST_MD_INCR4 0x0
>> +
>> +/* bits within the DEV_CONF register */
>> +#define DEV_CONF_SMP_DELAY_MASK GENMASK(20, 16)
>> +#define DEV_CONF_SMP_DELAY_180DEG 0x4
>> +#define DEV_CONF_SMP_DELAY_HALF_CYCLE 0x1
>> +#define DEV_CONF_CMD_TYPE_MASK GENMASK(15, 15)
>> +#define DEV_CONF_CMD_TYPE_16BIT 0x1
>> +#define DEV_CONF_CMD_TYPE_8BIT 0x0
>> +#define DEV_CONF_STA_TYPE_MASK GENMASK(14, 13)
>> +#define DEV_CONF_THOLD_MASK GENMASK(12, 11)
>> +#define DEV_CONF_TSETUP_MASK GENMASK(10, 9)
>> +#define DEV_CONF_TSH_MASK GENMASK(8, 5)
>> +#define DEV_CONF_CPHA BIT(4)
>> +#define DEV_CONF_CPOL BIT(3)
>> +#define DEV_CONF_CE_DL BIT(2)
>> +#define DEV_CONF_HOLD_DL BIT(1)
>> +#define DEV_CONF_WP_DL BIT(0)
>> +
>> +/* bits within the TRAN_CONF(n) register */
>> +#define TRAN_CONF_TRAN_MODE_MASK GENMASK(31, 29)
>> +#define TRAN_CONF_ADDR_WIDTH_MASK GENMASK(28, 26)
>> +#define TRAN_CONF_POLL_EN BIT(25)
>> +#define TRAN_CONF_CMD_EN BIT(24)
>> +#define TRAN_CONF_PHASE_FORMAT_MASK GENMASK(23, 23)
>> +#define TRAN_CONF_DMY_BITS_MASK GENMASK(22, 17)
>> +#define TRAN_CONF_DATA_EN BIT(16)
>> +#define TRAN_CONF_CMD_MASK GENMASK(15, 0)
>> +
>> +/* bits within the TRIG register */
>> +#define TRIG_FLUSH BIT(2)
>> +#define TRIG_STOP BIT(1)
>> +#define TRIG_START BIT(0)
>> +
>> +/* bits within the SR register */
>> +#define SR_FIFO_NUM_MASK GENMASK(22, 16)
>> +#define SR_END BIT(4)
>> +#define SR_TRAN_REQ BIT(3)
>> +#define SR_RECE_REQ BIT(2)
>> +#define SR_OVER BIT(1)
>> +#define SR_UNDER BIT(0)
>> +
>> +/* bits within the SCR register */
>> +#define SCR_CLR_END BIT(4)
>> +#define SCR_CLR_TREQ BIT(3)
>> +#define SCR_CLR_RREQ BIT(2)
>> +#define SCR_CLR_OVER BIT(1)
>> +#define SCR_CLR_UNDER BIT(0)
>> +
>> +/* bits within the INTC register */
>> +#define INTC_MASK_END BIT(4)
>> +#define INTC_MASK_TREQ BIT(3)
>> +#define INTC_MASK_RREQ BIT(2)
>> +#define INTC_MASK_OVER BIT(1)
>> +#define INTC_MASK_UNDER BIT(0)
>> +
>> +/* bits within the TRAN_CFG1(n) register */
>> +#define TRAN_CFG1_TRAN_MODE_MASK GENMASK(7, 4)
>> +
>> +#define TRAN_MODE_STANDARD 0
>> +#define TRAN_MODE_DUAL_DATA 1
>> +#define TRAN_MODE_DUAL_IO 2
>> +#define TRAN_MODE_DUAL_FULL 3
>> +#define TRAN_MODE_QUAD_DATA 5
>> +#define TRAN_MODE_QUAD_IO 6
>> +#define TRAN_MODE_QUAD_FULL 7
>> +#define TRAN_MODE_OCTAL_DATA 9
>> +#define TRAN_MODE_OCTAL_IO 10
>> +#define TRAN_MODE_OCTAL_FULL 11
>> +
>> +#define INGENIC_SFC_FIFO_SIZE (64 * 4)
>> +
>> +#define INGENIC_SFC_TRANSFER_TIMEOUT 1000
>> +
>> +enum ingenic_sfc_version {
>> + ID_X1000,
>> + ID_X1600,
>> + ID_X2000,
>> +};
>> +
>> +struct ingenic_soc_info {
>> + enum ingenic_sfc_version version;
>> +
> No need for blank line.
>
>> + unsigned int max_bus_width;
>> +
> Remove as well.
>
>> + const u32 tran_mode_mask;
>> +};
>> +
>> +struct ingenic_sfc {
>> + const struct ingenic_soc_info *soc_info;
>> +
>> + void __iomem *base;
>> + struct device *dev;
>> + struct clk *clk;
>> + int irq;
>> +
>> + struct completion completion;
>> +};
>> +
>> +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
>> +{
>> + struct ingenic_sfc *sfc = data;
>> +
>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>> +
>> + complete(&sfc->completion);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static int ingenic_sfc_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
>> +{
>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>> +
>> + if (op->data.nbytes > INGENIC_SFC_FIFO_SIZE && !IS_ALIGNED(addr, 4))
>> + op->data.nbytes = INGENIC_SFC_FIFO_SIZE;
>> +
>> + return 0;
>> +}
>> +
>> +static bool ingenic_sfc_supports_op(struct spi_mem *mem, const struct spi_mem_op *op)
>> +{
>> + struct spi_device *spi = mem->spi;
>> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>> +
>> + /* The controller only supports Standard SPI mode, Duall mode, Quad mode and Octal mode. */
>> + if (op->cmd.buswidth > sfc->soc_info->max_bus_width ||
>> + op->addr.buswidth > sfc->soc_info->max_bus_width ||
>> + op->dummy.buswidth > sfc->soc_info->max_bus_width ||
>> + op->data.buswidth > sfc->soc_info->max_bus_width)
>> + return false;
>> +
>> + /* Max 32 dummy clock cycles supported */
>> + if (op->dummy.nbytes && op->dummy.nbytes * 8 / op->dummy.buswidth > 32)
>> + return false;
>> +
>> + /* Max rx data length, check controller limits and alignment */
>> + if (op->data.dir == SPI_MEM_DATA_IN &&
>> + op->data.nbytes > INGENIC_SFC_FIFO_SIZE && !IS_ALIGNED(addr, 4))
>> + return false;
>> +
>> + /* Max 6 bytes address width supported */
>> + if (op->addr.nbytes > 6)
>> + return false;
>> +
>> + return spi_mem_default_supports_op(mem, op);
>> +}
>> +
>> +static void ingenic_sfc_set_transfer_mode(struct ingenic_sfc *sfc, const struct spi_mem_op *op)
>> +{
>> + int val;
>> +
>> + val = readl(sfc->base + (sfc->soc_info->version >= ID_X1600 ?
>> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
> This is no really readable.
Sure, I will change it in the next version.
>> + val &= ~sfc->soc_info->tran_mode_mask;
>> + if (op->cmd.buswidth == 8)
>> + val |= (TRAN_MODE_OCTAL_FULL << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->cmd.buswidth == 4)
>> + val |= (TRAN_MODE_QUAD_FULL << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->cmd.buswidth == 2)
>> + val |= (TRAN_MODE_DUAL_FULL << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->addr.buswidth == 8)
>> + val |= (TRAN_MODE_OCTAL_IO << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->addr.buswidth == 4)
>> + val |= (TRAN_MODE_QUAD_IO << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->addr.buswidth == 2)
>> + val |= (TRAN_MODE_DUAL_IO << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->data.buswidth == 8)
>> + val |= (TRAN_MODE_OCTAL_DATA << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->data.buswidth == 4)
>> + val |= (TRAN_MODE_QUAD_DATA << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->data.buswidth == 2)
>> + val |= (TRAN_MODE_DUAL_DATA << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else
>> + val |= (TRAN_MODE_STANDARD << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + writel(val, sfc->base + (sfc->soc_info->version >= ID_X1600 ?
>> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
> Not readable.
Will change it.
>
>> +}
>> +
>> +/*
>> + * We only need PIO mode to handle the SPI_MEM_NO_DATA transfers,
>> + * and the unaligned accesses in SPI_MEM_DATA_IN transfers.
>> + */
> (...)
>
>> + sfc->dev = &pdev->dev;
>> +
>> + platform_set_drvdata(pdev, sfc);
>> +
>> + ret = devm_request_irq(&pdev->dev, sfc->irq, ingenic_sfc_irq_handler, 0,
>> + dev_name(&pdev->dev), sfc);
>> + if (ret) {
>> + dev_err(&pdev->dev, "Failed to request irq%d, ret = %d\n", sfc->irq, ret);
>> + goto err_put_master;
>> + }
>> +
>> + ctlr->bus_num = -1;
>> + ctlr->num_chipselect = 1;
>> + ctlr->mem_ops = &ingenic_sfc_mem_ops;
>> + ctlr->dev.of_node = pdev->dev.of_node;
>> + ctlr->setup = ingenic_sfc_setup;
>> + ctlr->mode_bits = SPI_CPHA | SPI_CPOL |
>> + SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
>> + if (sfc->soc_info->version >= ID_X2000)
>> + ctlr->mode_bits |= SPI_RX_OCTAL | SPI_TX_OCTAL;
>> +
>> + ret = devm_spi_register_controller(&pdev->dev, ctlr);
>> + if (ret)
>> + goto err_put_master;
>> +
>> + return 0;
>> +
>> +err_put_master:
>> + spi_master_put(ctlr);
> What about unpreparing clocks? Is it part of spi_master_put()?
Will add it in the next version.
Thanks and beset regards!
>> +
>> + return ret;
>> +}
>> +
>> +static const struct ingenic_soc_info x1000_soc_info = {
>> + .version = ID_X1000,
>> +
> No need for blank lines after each field.
>
>
> Best regards,
> Krzysztof
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
@ 2022-07-23 16:53 ` Zhou Yanjie
0 siblings, 0 replies; 78+ messages in thread
From: Zhou Yanjie @ 2022-07-23 16:53 UTC (permalink / raw)
To: Krzysztof Kozlowski, tudor.ambarus, p.yadav, michael,
miquel.raynal, richard, vigneshr, broonie, robh+dt,
krzysztof.kozlowski+dt
Cc: linux-mtd, linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, paul, dongsheng.qiu, aric.pzqi,
rick.tyliu, jinghui.liu, sernia.zhou, reimu
Hi Krzysztof,
On 2022/7/23 上午2:07, Krzysztof Kozlowski wrote:
> On 22/07/2022 18:48, 周琰杰 (Zhou Yanjie) wrote:
>> Add SFC support for the X1000 SoC, the X1600 SoC, and the X2000 SoC
>> from Ingenic.
>>
>> Signed-off-by: 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
>> ---
>> drivers/spi/Kconfig | 9 +
>> drivers/spi/Makefile | 1 +
>> drivers/spi/spi-ingenic-sfc.c | 662 ++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 672 insertions(+)
>> create mode 100644 drivers/spi/spi-ingenic-sfc.c
>>
>> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
>> index 3b1044e..1077bd3 100644
>> --- a/drivers/spi/Kconfig
>> +++ b/drivers/spi/Kconfig
>> @@ -437,6 +437,15 @@ config SPI_INGENIC
>> To compile this driver as a module, choose M here: the module
>> will be called spi-ingenic.
>>
>> +config SPI_INGENIC_SFC
>> + tristate "Ingenic SoCs SPI Flash Controller"
>> + depends on MACH_INGENIC || COMPILE_TEST
>> + help
>> + This enables support for the Ingenic SoCs SPI flash controller.
>> +
>> + To compile this driver as a module, choose M here: the module
>> + will be called ingenic-sfc.
>> +
>> config SPI_INTEL
>> tristate
>>
>> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
>> index 0f44eb6..f3e42c0 100644
>> --- a/drivers/spi/Makefile
>> +++ b/drivers/spi/Makefile
>> @@ -62,6 +62,7 @@ obj-$(CONFIG_SPI_HISI_SFC_V3XX) += spi-hisi-sfc-v3xx.o
>> obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o
>> obj-$(CONFIG_SPI_IMX) += spi-imx.o
>> obj-$(CONFIG_SPI_INGENIC) += spi-ingenic.o
>> +obj-$(CONFIG_SPI_INGENIC_SFC) += spi-ingenic-sfc.o
>> obj-$(CONFIG_SPI_INTEL) += spi-intel.o
>> obj-$(CONFIG_SPI_INTEL_PCI) += spi-intel-pci.o
>> obj-$(CONFIG_SPI_INTEL_PLATFORM) += spi-intel-platform.o
>> diff --git a/drivers/spi/spi-ingenic-sfc.c b/drivers/spi/spi-ingenic-sfc.c
>> new file mode 100644
>> index 00000000..a565546
>> --- /dev/null
>> +++ b/drivers/spi/spi-ingenic-sfc.c
>> @@ -0,0 +1,662 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +/*
>> + * Ingenic SoCs SPI Flash Controller Driver
>> + * Copyright (c) 2022 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
>> + */
>> +
>> +#include <linux/bitfield.h>
>> +#include <linux/bitops.h>
>> +#include <linux/clk.h>
>> +#include <linux/completion.h>
>> +#include <linux/dma-mapping.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/iopoll.h>
>> +#include <linux/module.h>
>> +#include <linux/mtd/mtd.h>
>> +#include <linux/of_device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/slab.h>
>> +#include <linux/spi/spi.h>
>> +#include <linux/spi/spi-mem.h>
>> +
>> +/* SFC register offsets */
>> +#define SFC_REG_GLB 0x0000
>> +#define SFC_REG_DEV_CONF 0x0004
>> +#define SFC_REG_DEV_STA_EXP 0x0008
>> +#define SFC_REG_DEV_STA_RT 0x000c
>> +#define SFC_REG_DEV_STA_MSK 0x0010
>> +#define SFC_REG_TRAN_CONF(n) (0x0014 + n * 4)
>> +#define SFC_REG_TRAN_CFG0(n) (0x0014 + n * 4)
>> +#define SFC_REG_TRAN_LEN 0x002c
>> +#define SFC_REG_DEV_ADDR(n) (0x0030 + n * 4)
>> +#define SFC_REG_DEV_ADDR_PLUS(n) (0x0048 + n * 4)
>> +#define SFC_REG_MEM_ADDR 0x0060
>> +#define SFC_REG_TRIG 0x0064
>> +#define SFC_REG_SR 0x0068
>> +#define SFC_REG_SCR 0x006c
>> +#define SFC_REG_INTC 0x0070
>> +#define SFC_REG_FSM 0x0074
>> +#define SFC_REG_CGE 0x0078
>> +#define SFC_REG_TRAN_CFG1(n) (0x009c + n * 4)
>> +#define SFC_REG_DR 0x1000
>> +
>> +/* bits within the GLB register */
>> +#define GLB_TRAN_DIR_MASK GENMASK(13, 13)
>> +#define GLB_TRAN_DIR_WRITE 0x1
>> +#define GLB_TRAN_DIR_READ 0x0
>> +#define GLB_THRESHOLD_MASK GENMASK(12, 7)
>> +#define GLB_OP_MODE_MASK GENMASK(6, 6)
>> +#define GLB_OP_MODE_DMA 0x1
>> +#define GLB_OP_MODE_SLAVE 0x0
>> +#define GLB_PHASE_NUM_MASK GENMASK(5, 3)
>> +#define GLB_WP_EN BIT(2)
>> +#define GLB_BURST_MD_MASK GENMASK(1, 0)
>> +#define GLB_BURST_MD_INCR32 0x3
>> +#define GLB_BURST_MD_INCR16 0x2
>> +#define GLB_BURST_MD_INCR8 0x1
>> +#define GLB_BURST_MD_INCR4 0x0
>> +
>> +/* bits within the DEV_CONF register */
>> +#define DEV_CONF_SMP_DELAY_MASK GENMASK(20, 16)
>> +#define DEV_CONF_SMP_DELAY_180DEG 0x4
>> +#define DEV_CONF_SMP_DELAY_HALF_CYCLE 0x1
>> +#define DEV_CONF_CMD_TYPE_MASK GENMASK(15, 15)
>> +#define DEV_CONF_CMD_TYPE_16BIT 0x1
>> +#define DEV_CONF_CMD_TYPE_8BIT 0x0
>> +#define DEV_CONF_STA_TYPE_MASK GENMASK(14, 13)
>> +#define DEV_CONF_THOLD_MASK GENMASK(12, 11)
>> +#define DEV_CONF_TSETUP_MASK GENMASK(10, 9)
>> +#define DEV_CONF_TSH_MASK GENMASK(8, 5)
>> +#define DEV_CONF_CPHA BIT(4)
>> +#define DEV_CONF_CPOL BIT(3)
>> +#define DEV_CONF_CE_DL BIT(2)
>> +#define DEV_CONF_HOLD_DL BIT(1)
>> +#define DEV_CONF_WP_DL BIT(0)
>> +
>> +/* bits within the TRAN_CONF(n) register */
>> +#define TRAN_CONF_TRAN_MODE_MASK GENMASK(31, 29)
>> +#define TRAN_CONF_ADDR_WIDTH_MASK GENMASK(28, 26)
>> +#define TRAN_CONF_POLL_EN BIT(25)
>> +#define TRAN_CONF_CMD_EN BIT(24)
>> +#define TRAN_CONF_PHASE_FORMAT_MASK GENMASK(23, 23)
>> +#define TRAN_CONF_DMY_BITS_MASK GENMASK(22, 17)
>> +#define TRAN_CONF_DATA_EN BIT(16)
>> +#define TRAN_CONF_CMD_MASK GENMASK(15, 0)
>> +
>> +/* bits within the TRIG register */
>> +#define TRIG_FLUSH BIT(2)
>> +#define TRIG_STOP BIT(1)
>> +#define TRIG_START BIT(0)
>> +
>> +/* bits within the SR register */
>> +#define SR_FIFO_NUM_MASK GENMASK(22, 16)
>> +#define SR_END BIT(4)
>> +#define SR_TRAN_REQ BIT(3)
>> +#define SR_RECE_REQ BIT(2)
>> +#define SR_OVER BIT(1)
>> +#define SR_UNDER BIT(0)
>> +
>> +/* bits within the SCR register */
>> +#define SCR_CLR_END BIT(4)
>> +#define SCR_CLR_TREQ BIT(3)
>> +#define SCR_CLR_RREQ BIT(2)
>> +#define SCR_CLR_OVER BIT(1)
>> +#define SCR_CLR_UNDER BIT(0)
>> +
>> +/* bits within the INTC register */
>> +#define INTC_MASK_END BIT(4)
>> +#define INTC_MASK_TREQ BIT(3)
>> +#define INTC_MASK_RREQ BIT(2)
>> +#define INTC_MASK_OVER BIT(1)
>> +#define INTC_MASK_UNDER BIT(0)
>> +
>> +/* bits within the TRAN_CFG1(n) register */
>> +#define TRAN_CFG1_TRAN_MODE_MASK GENMASK(7, 4)
>> +
>> +#define TRAN_MODE_STANDARD 0
>> +#define TRAN_MODE_DUAL_DATA 1
>> +#define TRAN_MODE_DUAL_IO 2
>> +#define TRAN_MODE_DUAL_FULL 3
>> +#define TRAN_MODE_QUAD_DATA 5
>> +#define TRAN_MODE_QUAD_IO 6
>> +#define TRAN_MODE_QUAD_FULL 7
>> +#define TRAN_MODE_OCTAL_DATA 9
>> +#define TRAN_MODE_OCTAL_IO 10
>> +#define TRAN_MODE_OCTAL_FULL 11
>> +
>> +#define INGENIC_SFC_FIFO_SIZE (64 * 4)
>> +
>> +#define INGENIC_SFC_TRANSFER_TIMEOUT 1000
>> +
>> +enum ingenic_sfc_version {
>> + ID_X1000,
>> + ID_X1600,
>> + ID_X2000,
>> +};
>> +
>> +struct ingenic_soc_info {
>> + enum ingenic_sfc_version version;
>> +
> No need for blank line.
>
>> + unsigned int max_bus_width;
>> +
> Remove as well.
>
>> + const u32 tran_mode_mask;
>> +};
>> +
>> +struct ingenic_sfc {
>> + const struct ingenic_soc_info *soc_info;
>> +
>> + void __iomem *base;
>> + struct device *dev;
>> + struct clk *clk;
>> + int irq;
>> +
>> + struct completion completion;
>> +};
>> +
>> +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
>> +{
>> + struct ingenic_sfc *sfc = data;
>> +
>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>> +
>> + complete(&sfc->completion);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static int ingenic_sfc_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
>> +{
>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>> +
>> + if (op->data.nbytes > INGENIC_SFC_FIFO_SIZE && !IS_ALIGNED(addr, 4))
>> + op->data.nbytes = INGENIC_SFC_FIFO_SIZE;
>> +
>> + return 0;
>> +}
>> +
>> +static bool ingenic_sfc_supports_op(struct spi_mem *mem, const struct spi_mem_op *op)
>> +{
>> + struct spi_device *spi = mem->spi;
>> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>> +
>> + /* The controller only supports Standard SPI mode, Duall mode, Quad mode and Octal mode. */
>> + if (op->cmd.buswidth > sfc->soc_info->max_bus_width ||
>> + op->addr.buswidth > sfc->soc_info->max_bus_width ||
>> + op->dummy.buswidth > sfc->soc_info->max_bus_width ||
>> + op->data.buswidth > sfc->soc_info->max_bus_width)
>> + return false;
>> +
>> + /* Max 32 dummy clock cycles supported */
>> + if (op->dummy.nbytes && op->dummy.nbytes * 8 / op->dummy.buswidth > 32)
>> + return false;
>> +
>> + /* Max rx data length, check controller limits and alignment */
>> + if (op->data.dir == SPI_MEM_DATA_IN &&
>> + op->data.nbytes > INGENIC_SFC_FIFO_SIZE && !IS_ALIGNED(addr, 4))
>> + return false;
>> +
>> + /* Max 6 bytes address width supported */
>> + if (op->addr.nbytes > 6)
>> + return false;
>> +
>> + return spi_mem_default_supports_op(mem, op);
>> +}
>> +
>> +static void ingenic_sfc_set_transfer_mode(struct ingenic_sfc *sfc, const struct spi_mem_op *op)
>> +{
>> + int val;
>> +
>> + val = readl(sfc->base + (sfc->soc_info->version >= ID_X1600 ?
>> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
> This is no really readable.
Sure, I will change it in the next version.
>> + val &= ~sfc->soc_info->tran_mode_mask;
>> + if (op->cmd.buswidth == 8)
>> + val |= (TRAN_MODE_OCTAL_FULL << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->cmd.buswidth == 4)
>> + val |= (TRAN_MODE_QUAD_FULL << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->cmd.buswidth == 2)
>> + val |= (TRAN_MODE_DUAL_FULL << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->addr.buswidth == 8)
>> + val |= (TRAN_MODE_OCTAL_IO << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->addr.buswidth == 4)
>> + val |= (TRAN_MODE_QUAD_IO << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->addr.buswidth == 2)
>> + val |= (TRAN_MODE_DUAL_IO << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->data.buswidth == 8)
>> + val |= (TRAN_MODE_OCTAL_DATA << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->data.buswidth == 4)
>> + val |= (TRAN_MODE_QUAD_DATA << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->data.buswidth == 2)
>> + val |= (TRAN_MODE_DUAL_DATA << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else
>> + val |= (TRAN_MODE_STANDARD << (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + writel(val, sfc->base + (sfc->soc_info->version >= ID_X1600 ?
>> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
> Not readable.
Will change it.
>
>> +}
>> +
>> +/*
>> + * We only need PIO mode to handle the SPI_MEM_NO_DATA transfers,
>> + * and the unaligned accesses in SPI_MEM_DATA_IN transfers.
>> + */
> (...)
>
>> + sfc->dev = &pdev->dev;
>> +
>> + platform_set_drvdata(pdev, sfc);
>> +
>> + ret = devm_request_irq(&pdev->dev, sfc->irq, ingenic_sfc_irq_handler, 0,
>> + dev_name(&pdev->dev), sfc);
>> + if (ret) {
>> + dev_err(&pdev->dev, "Failed to request irq%d, ret = %d\n", sfc->irq, ret);
>> + goto err_put_master;
>> + }
>> +
>> + ctlr->bus_num = -1;
>> + ctlr->num_chipselect = 1;
>> + ctlr->mem_ops = &ingenic_sfc_mem_ops;
>> + ctlr->dev.of_node = pdev->dev.of_node;
>> + ctlr->setup = ingenic_sfc_setup;
>> + ctlr->mode_bits = SPI_CPHA | SPI_CPOL |
>> + SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
>> + if (sfc->soc_info->version >= ID_X2000)
>> + ctlr->mode_bits |= SPI_RX_OCTAL | SPI_TX_OCTAL;
>> +
>> + ret = devm_spi_register_controller(&pdev->dev, ctlr);
>> + if (ret)
>> + goto err_put_master;
>> +
>> + return 0;
>> +
>> +err_put_master:
>> + spi_master_put(ctlr);
> What about unpreparing clocks? Is it part of spi_master_put()?
Will add it in the next version.
Thanks and beset regards!
>> +
>> + return ret;
>> +}
>> +
>> +static const struct ingenic_soc_info x1000_soc_info = {
>> + .version = ID_X1000,
>> +
> No need for blank lines after each field.
>
>
> Best regards,
> Krzysztof
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
2022-07-22 16:48 ` 周琰杰 (Zhou Yanjie)
@ 2022-07-22 18:38 ` Mark Brown
-1 siblings, 0 replies; 78+ messages in thread
From: Mark Brown @ 2022-07-22 18:38 UTC (permalink / raw)
To: 周琰杰 (Zhou Yanjie)
Cc: tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, robh+dt, krzysztof.kozlowski+dt, linux-mtd, linux-spi,
linux-mips, linux-kernel, devicetree, aidanmacdonald.0x0, tmn505,
paul, dongsheng.qiu, aric.pzqi, rick.tyliu, jinghui.liu,
sernia.zhou, reimu
[-- Attachment #1: Type: text/plain, Size: 2329 bytes --]
On Sat, Jul 23, 2022 at 12:48:30AM +0800, 周琰杰 (Zhou Yanjie) wrote:
This looks mostly good, a few small issues though:
> +++ b/drivers/spi/spi-ingenic-sfc.c
> @@ -0,0 +1,662 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +/*
> + * Ingenic SoCs SPI Flash Controller Driver
Please make the entire comment a C++ one so things look more
intentional.
> +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
> +{
> + struct ingenic_sfc *sfc = data;
> +
> + writel(0x1f, sfc->base + SFC_REG_INTC);
> +
> + complete(&sfc->completion);
> +
> + return IRQ_HANDLED;
> +}
This doesn't pay any attention to any status registers in the chip so
won't work if the interrupt is shared and won't notice any error reports
from the device...
> +static int ingenic_sfc_setup(struct spi_device *spi)
> +{
> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
> + unsigned long rate;
> + int ret, val;
> +
> + if (!spi->max_speed_hz)
> + return -EINVAL;
> +
> + ret = clk_set_rate(sfc->clk, spi->max_speed_hz * 2);
> + if (ret)
> + return -EINVAL;
The setup() operation should be safe for use on one device while another
device is active. It's not going to be a problem until there's a
version of the IP with more than one chip select, but that could happen
some time (and someone might decide to make a board using GPIO chip
selects...) but this should really go into the data path.
> + ret = clk_prepare_enable(sfc->clk);
> + if (ret)
> + goto err_put_master;
Nothing ever disables this clock. It might also be nice to enable the
clock only when the controller is in use, that bit is not super
important though.
> + ret = devm_request_irq(&pdev->dev, sfc->irq, ingenic_sfc_irq_handler, 0,
> + dev_name(&pdev->dev), sfc);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to request irq%d, ret = %d\n", sfc->irq, ret);
> + goto err_put_master;
> + }
It's not safe to use devm here...
> + ret = devm_spi_register_controller(&pdev->dev, ctlr);
> + if (ret)
> + goto err_put_master;
...unregistering the controller may free the driver data structure and
the interrupt handler uses it so we could attempt to use freed data in
the window between the controller being unregistered and the interrupt
being freed.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
@ 2022-07-22 18:38 ` Mark Brown
0 siblings, 0 replies; 78+ messages in thread
From: Mark Brown @ 2022-07-22 18:38 UTC (permalink / raw)
To: 周琰杰 (Zhou Yanjie)
Cc: tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, robh+dt, krzysztof.kozlowski+dt, linux-mtd, linux-spi,
linux-mips, linux-kernel, devicetree, aidanmacdonald.0x0, tmn505,
paul, dongsheng.qiu, aric.pzqi, rick.tyliu, jinghui.liu,
sernia.zhou, reimu
[-- Attachment #1.1: Type: text/plain, Size: 2329 bytes --]
On Sat, Jul 23, 2022 at 12:48:30AM +0800, 周琰杰 (Zhou Yanjie) wrote:
This looks mostly good, a few small issues though:
> +++ b/drivers/spi/spi-ingenic-sfc.c
> @@ -0,0 +1,662 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +/*
> + * Ingenic SoCs SPI Flash Controller Driver
Please make the entire comment a C++ one so things look more
intentional.
> +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
> +{
> + struct ingenic_sfc *sfc = data;
> +
> + writel(0x1f, sfc->base + SFC_REG_INTC);
> +
> + complete(&sfc->completion);
> +
> + return IRQ_HANDLED;
> +}
This doesn't pay any attention to any status registers in the chip so
won't work if the interrupt is shared and won't notice any error reports
from the device...
> +static int ingenic_sfc_setup(struct spi_device *spi)
> +{
> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
> + unsigned long rate;
> + int ret, val;
> +
> + if (!spi->max_speed_hz)
> + return -EINVAL;
> +
> + ret = clk_set_rate(sfc->clk, spi->max_speed_hz * 2);
> + if (ret)
> + return -EINVAL;
The setup() operation should be safe for use on one device while another
device is active. It's not going to be a problem until there's a
version of the IP with more than one chip select, but that could happen
some time (and someone might decide to make a board using GPIO chip
selects...) but this should really go into the data path.
> + ret = clk_prepare_enable(sfc->clk);
> + if (ret)
> + goto err_put_master;
Nothing ever disables this clock. It might also be nice to enable the
clock only when the controller is in use, that bit is not super
important though.
> + ret = devm_request_irq(&pdev->dev, sfc->irq, ingenic_sfc_irq_handler, 0,
> + dev_name(&pdev->dev), sfc);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to request irq%d, ret = %d\n", sfc->irq, ret);
> + goto err_put_master;
> + }
It's not safe to use devm here...
> + ret = devm_spi_register_controller(&pdev->dev, ctlr);
> + if (ret)
> + goto err_put_master;
...unregistering the controller may free the driver data structure and
the interrupt handler uses it so we could attempt to use freed data in
the window between the controller being unregistered and the interrupt
being freed.
[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
[-- Attachment #2: Type: text/plain, Size: 144 bytes --]
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
2022-07-22 18:38 ` Mark Brown
@ 2022-07-23 17:06 ` Zhou Yanjie
-1 siblings, 0 replies; 78+ messages in thread
From: Zhou Yanjie @ 2022-07-23 17:06 UTC (permalink / raw)
To: Mark Brown
Cc: tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, robh+dt, krzysztof.kozlowski+dt, linux-mtd, linux-spi,
linux-mips, linux-kernel, devicetree, aidanmacdonald.0x0, tmn505,
paul, dongsheng.qiu, aric.pzqi, rick.tyliu, jinghui.liu,
sernia.zhou, reimu
Hi Mark,
On 2022/7/23 上午2:38, Mark Brown wrote:
> On Sat, Jul 23, 2022 at 12:48:30AM +0800, 周琰杰 (Zhou Yanjie) wrote:
>
> This looks mostly good, a few small issues though:
>
>> +++ b/drivers/spi/spi-ingenic-sfc.c
>> @@ -0,0 +1,662 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +/*
>> + * Ingenic SoCs SPI Flash Controller Driver
> Please make the entire comment a C++ one so things look more
> intentional.
I'm sorry, I didn't understand well what you meant :(
Could you please explain a little more detail?
>
>> +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
>> +{
>> + struct ingenic_sfc *sfc = data;
>> +
>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>> +
>> + complete(&sfc->completion);
>> +
>> + return IRQ_HANDLED;
>> +}
> This doesn't pay any attention to any status registers in the chip so
> won't work if the interrupt is shared and won't notice any error reports
> from the device...
This interrupt is exclusively owned by SFC, do we still
need to perform the operation you said? I haven't done
these operations before because I want to minimize the
overhead and avoid affecting performance.
>
>> +static int ingenic_sfc_setup(struct spi_device *spi)
>> +{
>> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
>> + unsigned long rate;
>> + int ret, val;
>> +
>> + if (!spi->max_speed_hz)
>> + return -EINVAL;
>> +
>> + ret = clk_set_rate(sfc->clk, spi->max_speed_hz * 2);
>> + if (ret)
>> + return -EINVAL;
> The setup() operation should be safe for use on one device while another
> device is active. It's not going to be a problem until there's a
> version of the IP with more than one chip select, but that could happen
> some time (and someone might decide to make a board using GPIO chip
> selects...) but this should really go into the data path.
Sure, I will change it in the next version.
>> + ret = clk_prepare_enable(sfc->clk);
>> + if (ret)
>> + goto err_put_master;
> Nothing ever disables this clock. It might also be nice to enable the
> clock only when the controller is in use, that bit is not super
> important though.
Sure, will add it.
>
>> + ret = devm_request_irq(&pdev->dev, sfc->irq, ingenic_sfc_irq_handler, 0,
>> + dev_name(&pdev->dev), sfc);
>> + if (ret) {
>> + dev_err(&pdev->dev, "Failed to request irq%d, ret = %d\n", sfc->irq, ret);
>> + goto err_put_master;
>> + }
> It's not safe to use devm here...
Sure, will fix it in the next version.
>
>> + ret = devm_spi_register_controller(&pdev->dev, ctlr);
>> + if (ret)
>> + goto err_put_master;
> ...unregistering the controller may free the driver data structure and
> the interrupt handler uses it so we could attempt to use freed data in
> the window between the controller being unregistered and the interrupt
> being freed.
Sure.
Thanks and best regards!
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
@ 2022-07-23 17:06 ` Zhou Yanjie
0 siblings, 0 replies; 78+ messages in thread
From: Zhou Yanjie @ 2022-07-23 17:06 UTC (permalink / raw)
To: Mark Brown
Cc: tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, robh+dt, krzysztof.kozlowski+dt, linux-mtd, linux-spi,
linux-mips, linux-kernel, devicetree, aidanmacdonald.0x0, tmn505,
paul, dongsheng.qiu, aric.pzqi, rick.tyliu, jinghui.liu,
sernia.zhou, reimu
Hi Mark,
On 2022/7/23 上午2:38, Mark Brown wrote:
> On Sat, Jul 23, 2022 at 12:48:30AM +0800, 周琰杰 (Zhou Yanjie) wrote:
>
> This looks mostly good, a few small issues though:
>
>> +++ b/drivers/spi/spi-ingenic-sfc.c
>> @@ -0,0 +1,662 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +/*
>> + * Ingenic SoCs SPI Flash Controller Driver
> Please make the entire comment a C++ one so things look more
> intentional.
I'm sorry, I didn't understand well what you meant :(
Could you please explain a little more detail?
>
>> +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
>> +{
>> + struct ingenic_sfc *sfc = data;
>> +
>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>> +
>> + complete(&sfc->completion);
>> +
>> + return IRQ_HANDLED;
>> +}
> This doesn't pay any attention to any status registers in the chip so
> won't work if the interrupt is shared and won't notice any error reports
> from the device...
This interrupt is exclusively owned by SFC, do we still
need to perform the operation you said? I haven't done
these operations before because I want to minimize the
overhead and avoid affecting performance.
>
>> +static int ingenic_sfc_setup(struct spi_device *spi)
>> +{
>> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
>> + unsigned long rate;
>> + int ret, val;
>> +
>> + if (!spi->max_speed_hz)
>> + return -EINVAL;
>> +
>> + ret = clk_set_rate(sfc->clk, spi->max_speed_hz * 2);
>> + if (ret)
>> + return -EINVAL;
> The setup() operation should be safe for use on one device while another
> device is active. It's not going to be a problem until there's a
> version of the IP with more than one chip select, but that could happen
> some time (and someone might decide to make a board using GPIO chip
> selects...) but this should really go into the data path.
Sure, I will change it in the next version.
>> + ret = clk_prepare_enable(sfc->clk);
>> + if (ret)
>> + goto err_put_master;
> Nothing ever disables this clock. It might also be nice to enable the
> clock only when the controller is in use, that bit is not super
> important though.
Sure, will add it.
>
>> + ret = devm_request_irq(&pdev->dev, sfc->irq, ingenic_sfc_irq_handler, 0,
>> + dev_name(&pdev->dev), sfc);
>> + if (ret) {
>> + dev_err(&pdev->dev, "Failed to request irq%d, ret = %d\n", sfc->irq, ret);
>> + goto err_put_master;
>> + }
> It's not safe to use devm here...
Sure, will fix it in the next version.
>
>> + ret = devm_spi_register_controller(&pdev->dev, ctlr);
>> + if (ret)
>> + goto err_put_master;
> ...unregistering the controller may free the driver data structure and
> the interrupt handler uses it so we could attempt to use freed data in
> the window between the controller being unregistered and the interrupt
> being freed.
Sure.
Thanks and best regards!
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
2022-07-23 17:06 ` Zhou Yanjie
@ 2022-07-23 19:32 ` Mark Brown
-1 siblings, 0 replies; 78+ messages in thread
From: Mark Brown @ 2022-07-23 19:32 UTC (permalink / raw)
To: Zhou Yanjie
Cc: tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, robh+dt, krzysztof.kozlowski+dt, linux-mtd, linux-spi,
linux-mips, linux-kernel, devicetree, aidanmacdonald.0x0, tmn505,
paul, dongsheng.qiu, aric.pzqi, rick.tyliu, jinghui.liu,
sernia.zhou, reimu
[-- Attachment #1: Type: text/plain, Size: 1640 bytes --]
On Sun, Jul 24, 2022 at 01:06:16AM +0800, Zhou Yanjie wrote:
> On 2022/7/23 上午2:38, Mark Brown wrote:
> > > +++ b/drivers/spi/spi-ingenic-sfc.c
> > > @@ -0,0 +1,662 @@
> > > +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > > +/*
> > > + * Ingenic SoCs SPI Flash Controller Driver
> > Please make the entire comment a C++ one so things look more
> > intentional.
> I'm sorry, I didn't understand well what you meant :(
> Could you please explain a little more detail?
The above comment block uses both C /* */ and C++ // style comments,
please make it just use the C++ style.
> > > +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
> > > +{
> > > + struct ingenic_sfc *sfc = data;
> > > +
> > > + writel(0x1f, sfc->base + SFC_REG_INTC);
> > > +
> > > + complete(&sfc->completion);
> > > +
> > > + return IRQ_HANDLED;
> > > +}
> > This doesn't pay any attention to any status registers in the chip so
> > won't work if the interrupt is shared and won't notice any error reports
> > from the device...
> This interrupt is exclusively owned by SFC, do we still
> need to perform the operation you said? I haven't done
> these operations before because I want to minimize the
> overhead and avoid affecting performance.
Even if the device is not shared is there no possibility that the
device can report an unexpected interrupt status? It's not just
the sharing case, it's also the fact that it looks like there's a
status being reported but we're not checking it so if anything
goes wrong then we're less likely to notice. I'd worry about
data corruption.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
@ 2022-07-23 19:32 ` Mark Brown
0 siblings, 0 replies; 78+ messages in thread
From: Mark Brown @ 2022-07-23 19:32 UTC (permalink / raw)
To: Zhou Yanjie
Cc: tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, robh+dt, krzysztof.kozlowski+dt, linux-mtd, linux-spi,
linux-mips, linux-kernel, devicetree, aidanmacdonald.0x0, tmn505,
paul, dongsheng.qiu, aric.pzqi, rick.tyliu, jinghui.liu,
sernia.zhou, reimu
[-- Attachment #1.1: Type: text/plain, Size: 1640 bytes --]
On Sun, Jul 24, 2022 at 01:06:16AM +0800, Zhou Yanjie wrote:
> On 2022/7/23 上午2:38, Mark Brown wrote:
> > > +++ b/drivers/spi/spi-ingenic-sfc.c
> > > @@ -0,0 +1,662 @@
> > > +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > > +/*
> > > + * Ingenic SoCs SPI Flash Controller Driver
> > Please make the entire comment a C++ one so things look more
> > intentional.
> I'm sorry, I didn't understand well what you meant :(
> Could you please explain a little more detail?
The above comment block uses both C /* */ and C++ // style comments,
please make it just use the C++ style.
> > > +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
> > > +{
> > > + struct ingenic_sfc *sfc = data;
> > > +
> > > + writel(0x1f, sfc->base + SFC_REG_INTC);
> > > +
> > > + complete(&sfc->completion);
> > > +
> > > + return IRQ_HANDLED;
> > > +}
> > This doesn't pay any attention to any status registers in the chip so
> > won't work if the interrupt is shared and won't notice any error reports
> > from the device...
> This interrupt is exclusively owned by SFC, do we still
> need to perform the operation you said? I haven't done
> these operations before because I want to minimize the
> overhead and avoid affecting performance.
Even if the device is not shared is there no possibility that the
device can report an unexpected interrupt status? It's not just
the sharing case, it's also the fact that it looks like there's a
status being reported but we're not checking it so if anything
goes wrong then we're less likely to notice. I'd worry about
data corruption.
[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
[-- Attachment #2: Type: text/plain, Size: 144 bytes --]
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
2022-07-23 19:32 ` Mark Brown
@ 2022-07-24 1:24 ` Zhou Yanjie
-1 siblings, 0 replies; 78+ messages in thread
From: Zhou Yanjie @ 2022-07-24 1:24 UTC (permalink / raw)
To: Mark Brown
Cc: tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, robh+dt, krzysztof.kozlowski+dt, linux-mtd, linux-spi,
linux-mips, linux-kernel, devicetree, aidanmacdonald.0x0, tmn505,
paul, dongsheng.qiu, aric.pzqi, rick.tyliu, jinghui.liu,
sernia.zhou, reimu
Hi Mark,
On 2022/7/24 上午3:32, Mark Brown wrote:
> On Sun, Jul 24, 2022 at 01:06:16AM +0800, Zhou Yanjie wrote:
>> On 2022/7/23 上午2:38, Mark Brown wrote:
>>>> +++ b/drivers/spi/spi-ingenic-sfc.c
>>>> @@ -0,0 +1,662 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>>> +/*
>>>> + * Ingenic SoCs SPI Flash Controller Driver
>>> Please make the entire comment a C++ one so things look more
>>> intentional.
>> I'm sorry, I didn't understand well what you meant :(
>> Could you please explain a little more detail?
> The above comment block uses both C /* */ and C++ // style comments,
> please make it just use the C++ style.
Sure, will do in the next version.
>>>> +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
>>>> +{
>>>> + struct ingenic_sfc *sfc = data;
>>>> +
>>>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>>>> +
>>>> + complete(&sfc->completion);
>>>> +
>>>> + return IRQ_HANDLED;
>>>> +}
>>> This doesn't pay any attention to any status registers in the chip so
>>> won't work if the interrupt is shared and won't notice any error reports
>>> from the device...
>> This interrupt is exclusively owned by SFC, do we still
>> need to perform the operation you said? I haven't done
>> these operations before because I want to minimize the
>> overhead and avoid affecting performance.
> Even if the device is not shared is there no possibility that the
> device can report an unexpected interrupt status? It's not just
> the sharing case, it's also the fact that it looks like there's a
> status being reported but we're not checking it so if anything
> goes wrong then we're less likely to notice. I'd worry about
> data corruption.
Sure, I will change this in the next version.
Thanks and best regards!
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
@ 2022-07-24 1:24 ` Zhou Yanjie
0 siblings, 0 replies; 78+ messages in thread
From: Zhou Yanjie @ 2022-07-24 1:24 UTC (permalink / raw)
To: Mark Brown
Cc: tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, robh+dt, krzysztof.kozlowski+dt, linux-mtd, linux-spi,
linux-mips, linux-kernel, devicetree, aidanmacdonald.0x0, tmn505,
paul, dongsheng.qiu, aric.pzqi, rick.tyliu, jinghui.liu,
sernia.zhou, reimu
Hi Mark,
On 2022/7/24 上午3:32, Mark Brown wrote:
> On Sun, Jul 24, 2022 at 01:06:16AM +0800, Zhou Yanjie wrote:
>> On 2022/7/23 上午2:38, Mark Brown wrote:
>>>> +++ b/drivers/spi/spi-ingenic-sfc.c
>>>> @@ -0,0 +1,662 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>>> +/*
>>>> + * Ingenic SoCs SPI Flash Controller Driver
>>> Please make the entire comment a C++ one so things look more
>>> intentional.
>> I'm sorry, I didn't understand well what you meant :(
>> Could you please explain a little more detail?
> The above comment block uses both C /* */ and C++ // style comments,
> please make it just use the C++ style.
Sure, will do in the next version.
>>>> +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
>>>> +{
>>>> + struct ingenic_sfc *sfc = data;
>>>> +
>>>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>>>> +
>>>> + complete(&sfc->completion);
>>>> +
>>>> + return IRQ_HANDLED;
>>>> +}
>>> This doesn't pay any attention to any status registers in the chip so
>>> won't work if the interrupt is shared and won't notice any error reports
>>> from the device...
>> This interrupt is exclusively owned by SFC, do we still
>> need to perform the operation you said? I haven't done
>> these operations before because I want to minimize the
>> overhead and avoid affecting performance.
> Even if the device is not shared is there no possibility that the
> device can report an unexpected interrupt status? It's not just
> the sharing case, it's also the fact that it looks like there's a
> status being reported but we're not checking it so if anything
> goes wrong then we're less likely to notice. I'd worry about
> data corruption.
Sure, I will change this in the next version.
Thanks and best regards!
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
2022-07-22 16:48 ` 周琰杰 (Zhou Yanjie)
@ 2022-07-22 20:03 ` Paul Cercueil
-1 siblings, 0 replies; 78+ messages in thread
From: Paul Cercueil @ 2022-07-22 20:03 UTC (permalink / raw)
To: 周琰杰
Cc: tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, broonie, robh+dt, krzysztof.kozlowski+dt, linux-mtd,
linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, dongsheng.qiu, aric.pzqi, rick.tyliu,
jinghui.liu, sernia.zhou, reimu
Hi Zhou,
Le sam., juil. 23 2022 at 00:48:30 +0800, 周琰杰 (Zhou Yanjie)
<zhouyanjie@wanyeetech.com> a écrit :
> Add SFC support for the X1000 SoC, the X1600 SoC, and the X2000 SoC
> from Ingenic.
>
> Signed-off-by: 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
> ---
> drivers/spi/Kconfig | 9 +
> drivers/spi/Makefile | 1 +
> drivers/spi/spi-ingenic-sfc.c | 662
> ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 672 insertions(+)
> create mode 100644 drivers/spi/spi-ingenic-sfc.c
>
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index 3b1044e..1077bd3 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -437,6 +437,15 @@ config SPI_INGENIC
> To compile this driver as a module, choose M here: the module
> will be called spi-ingenic.
>
> +config SPI_INGENIC_SFC
> + tristate "Ingenic SoCs SPI Flash Controller"
> + depends on MACH_INGENIC || COMPILE_TEST
> + help
> + This enables support for the Ingenic SoCs SPI flash controller.
> +
> + To compile this driver as a module, choose M here: the module
> + will be called ingenic-sfc.
> +
> config SPI_INTEL
> tristate
>
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index 0f44eb6..f3e42c0 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -62,6 +62,7 @@ obj-$(CONFIG_SPI_HISI_SFC_V3XX) +=
> spi-hisi-sfc-v3xx.o
> obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o
> obj-$(CONFIG_SPI_IMX) += spi-imx.o
> obj-$(CONFIG_SPI_INGENIC) += spi-ingenic.o
> +obj-$(CONFIG_SPI_INGENIC_SFC) += spi-ingenic-sfc.o
> obj-$(CONFIG_SPI_INTEL) += spi-intel.o
> obj-$(CONFIG_SPI_INTEL_PCI) += spi-intel-pci.o
> obj-$(CONFIG_SPI_INTEL_PLATFORM) += spi-intel-platform.o
> diff --git a/drivers/spi/spi-ingenic-sfc.c
> b/drivers/spi/spi-ingenic-sfc.c
> new file mode 100644
> index 00000000..a565546
> --- /dev/null
> +++ b/drivers/spi/spi-ingenic-sfc.c
> @@ -0,0 +1,662 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
Dual-license driver? That's not what MODULE_LICENSE() says.
> +/*
> + * Ingenic SoCs SPI Flash Controller Driver
> + * Copyright (c) 2022 周琰杰 (Zhou Yanjie)
> <zhouyanjie@wanyeetech.com>
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/interrupt.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/mtd/mtd.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/spi/spi.h>
> +#include <linux/spi/spi-mem.h>
> +
> +/* SFC register offsets */
> +#define SFC_REG_GLB 0x0000
> +#define SFC_REG_DEV_CONF 0x0004
> +#define SFC_REG_DEV_STA_EXP 0x0008
> +#define SFC_REG_DEV_STA_RT 0x000c
> +#define SFC_REG_DEV_STA_MSK 0x0010
> +#define SFC_REG_TRAN_CONF(n) (0x0014 + n * 4)
> +#define SFC_REG_TRAN_CFG0(n) (0x0014 + n * 4)
You should protect the macro parameter. If you do for instance
SFC_REG_TRAN_CONF(x + 1) it would resolve to (0x0014 + x + 1 * 4) which
is not what you'd want.
Also - looks like SFC_REG_TRAN_CONF() and SFC_REG_TRAN_CFG0() are the
same thing, that's on purpose?
> +#define SFC_REG_TRAN_LEN 0x002c
> +#define SFC_REG_DEV_ADDR(n) (0x0030 + n * 4)
> +#define SFC_REG_DEV_ADDR_PLUS(n) (0x0048 + n * 4)
> +#define SFC_REG_MEM_ADDR 0x0060
> +#define SFC_REG_TRIG 0x0064
> +#define SFC_REG_SR 0x0068
> +#define SFC_REG_SCR 0x006c
> +#define SFC_REG_INTC 0x0070
> +#define SFC_REG_FSM 0x0074
> +#define SFC_REG_CGE 0x0078
> +#define SFC_REG_TRAN_CFG1(n) (0x009c + n * 4)
> +#define SFC_REG_DR 0x1000
> +
> +/* bits within the GLB register */
> +#define GLB_TRAN_DIR_MASK GENMASK(13, 13)
> +#define GLB_TRAN_DIR_WRITE 0x1
> +#define GLB_TRAN_DIR_READ 0x0
When it's a single bit - just use BIT().
> +#define GLB_THRESHOLD_MASK GENMASK(12, 7)
> +#define GLB_OP_MODE_MASK GENMASK(6, 6)
Same here, and I see it a few times below as well.
> +#define GLB_OP_MODE_DMA 0x1
> +#define GLB_OP_MODE_SLAVE 0x0
> +#define GLB_PHASE_NUM_MASK GENMASK(5, 3)
> +#define GLB_WP_EN BIT(2)
> +#define GLB_BURST_MD_MASK GENMASK(1, 0)
> +#define GLB_BURST_MD_INCR32 0x3
> +#define GLB_BURST_MD_INCR16 0x2
> +#define GLB_BURST_MD_INCR8 0x1
> +#define GLB_BURST_MD_INCR4 0x0
> +
> +/* bits within the DEV_CONF register */
> +#define DEV_CONF_SMP_DELAY_MASK GENMASK(20, 16)
> +#define DEV_CONF_SMP_DELAY_180DEG 0x4
> +#define DEV_CONF_SMP_DELAY_HALF_CYCLE 0x1
> +#define DEV_CONF_CMD_TYPE_MASK GENMASK(15, 15)
> +#define DEV_CONF_CMD_TYPE_16BIT 0x1
> +#define DEV_CONF_CMD_TYPE_8BIT 0x0
> +#define DEV_CONF_STA_TYPE_MASK GENMASK(14, 13)
> +#define DEV_CONF_THOLD_MASK GENMASK(12, 11)
> +#define DEV_CONF_TSETUP_MASK GENMASK(10, 9)
> +#define DEV_CONF_TSH_MASK GENMASK(8, 5)
> +#define DEV_CONF_CPHA BIT(4)
> +#define DEV_CONF_CPOL BIT(3)
> +#define DEV_CONF_CE_DL BIT(2)
> +#define DEV_CONF_HOLD_DL BIT(1)
> +#define DEV_CONF_WP_DL BIT(0)
> +
> +/* bits within the TRAN_CONF(n) register */
> +#define TRAN_CONF_TRAN_MODE_MASK GENMASK(31, 29)
> +#define TRAN_CONF_ADDR_WIDTH_MASK GENMASK(28, 26)
> +#define TRAN_CONF_POLL_EN BIT(25)
> +#define TRAN_CONF_CMD_EN BIT(24)
> +#define TRAN_CONF_PHASE_FORMAT_MASK GENMASK(23, 23)
> +#define TRAN_CONF_DMY_BITS_MASK GENMASK(22, 17)
> +#define TRAN_CONF_DATA_EN BIT(16)
> +#define TRAN_CONF_CMD_MASK GENMASK(15, 0)
> +
> +/* bits within the TRIG register */
> +#define TRIG_FLUSH BIT(2)
> +#define TRIG_STOP BIT(1)
> +#define TRIG_START BIT(0)
> +
> +/* bits within the SR register */
> +#define SR_FIFO_NUM_MASK GENMASK(22, 16)
> +#define SR_END BIT(4)
> +#define SR_TRAN_REQ BIT(3)
> +#define SR_RECE_REQ BIT(2)
> +#define SR_OVER BIT(1)
> +#define SR_UNDER BIT(0)
> +
> +/* bits within the SCR register */
> +#define SCR_CLR_END BIT(4)
> +#define SCR_CLR_TREQ BIT(3)
> +#define SCR_CLR_RREQ BIT(2)
> +#define SCR_CLR_OVER BIT(1)
> +#define SCR_CLR_UNDER BIT(0)
> +
> +/* bits within the INTC register */
> +#define INTC_MASK_END BIT(4)
> +#define INTC_MASK_TREQ BIT(3)
> +#define INTC_MASK_RREQ BIT(2)
> +#define INTC_MASK_OVER BIT(1)
> +#define INTC_MASK_UNDER BIT(0)
> +
> +/* bits within the TRAN_CFG1(n) register */
> +#define TRAN_CFG1_TRAN_MODE_MASK GENMASK(7, 4)
> +
> +#define TRAN_MODE_STANDARD 0
> +#define TRAN_MODE_DUAL_DATA 1
> +#define TRAN_MODE_DUAL_IO 2
> +#define TRAN_MODE_DUAL_FULL 3
> +#define TRAN_MODE_QUAD_DATA 5
> +#define TRAN_MODE_QUAD_IO 6
> +#define TRAN_MODE_QUAD_FULL 7
> +#define TRAN_MODE_OCTAL_DATA 9
> +#define TRAN_MODE_OCTAL_IO 10
> +#define TRAN_MODE_OCTAL_FULL 11
> +
> +#define INGENIC_SFC_FIFO_SIZE (64 * 4)
> +
> +#define INGENIC_SFC_TRANSFER_TIMEOUT 1000
Maybe add the unit name in the macro as well -
INGENIC_SFC_TRANSFER_TIMEOUT_MS.
> +
> +enum ingenic_sfc_version {
> + ID_X1000,
> + ID_X1600,
> + ID_X2000,
> +};
> +
> +struct ingenic_soc_info {
> + enum ingenic_sfc_version version;
> +
> + unsigned int max_bus_width;
> +
> + const u32 tran_mode_mask;
> +};
> +
> +struct ingenic_sfc {
> + const struct ingenic_soc_info *soc_info;
> +
> + void __iomem *base;
> + struct device *dev;
> + struct clk *clk;
> + int irq;
> +
> + struct completion completion;
> +};
> +
> +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
> +{
> + struct ingenic_sfc *sfc = data;
> +
> + writel(0x1f, sfc->base + SFC_REG_INTC);
> +
> + complete(&sfc->completion);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int ingenic_sfc_adjust_op_size(struct spi_mem *mem, struct
> spi_mem_op *op)
> +{
> + uintptr_t addr = (uintptr_t)op->data.buf.in;
> +
> + if (op->data.nbytes > INGENIC_SFC_FIFO_SIZE && !IS_ALIGNED(addr, 4))
> + op->data.nbytes = INGENIC_SFC_FIFO_SIZE;
> +
> + return 0;
> +}
> +
> +static bool ingenic_sfc_supports_op(struct spi_mem *mem, const
> struct spi_mem_op *op)
> +{
> + struct spi_device *spi = mem->spi;
> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
> + uintptr_t addr = (uintptr_t)op->data.buf.in;
> +
> + /* The controller only supports Standard SPI mode, Duall mode, Quad
> mode and Octal mode. */
Dual*
> + if (op->cmd.buswidth > sfc->soc_info->max_bus_width ||
> + op->addr.buswidth > sfc->soc_info->max_bus_width ||
> + op->dummy.buswidth > sfc->soc_info->max_bus_width ||
> + op->data.buswidth > sfc->soc_info->max_bus_width)
> + return false;
> +
> + /* Max 32 dummy clock cycles supported */
> + if (op->dummy.nbytes && op->dummy.nbytes * 8 / op->dummy.buswidth >
> 32)
> + return false;
> +
> + /* Max rx data length, check controller limits and alignment */
> + if (op->data.dir == SPI_MEM_DATA_IN &&
> + op->data.nbytes > INGENIC_SFC_FIFO_SIZE && !IS_ALIGNED(addr, 4))
This does the same check than in ingenic_sfc_adjust_op_size(), maybe
move it to a new inline function?
> + return false;
> +
> + /* Max 6 bytes address width supported */
> + if (op->addr.nbytes > 6)
> + return false;
> +
> + return spi_mem_default_supports_op(mem, op);
> +}
> +
> +static void ingenic_sfc_set_transfer_mode(struct ingenic_sfc *sfc,
> const struct spi_mem_op *op)
> +{
> + int val;
> +
> + val = readl(sfc->base + (sfc->soc_info->version >= ID_X1600 ?
> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
As Krzysztof said - ugh.
Also, instead of having a "version" enum in your soc_info, why not just
have a "reg_conf" field that gives you directly the right register?
> + val &= ~sfc->soc_info->tran_mode_mask;
> + if (op->cmd.buswidth == 8)
> + val |= (TRAN_MODE_OCTAL_FULL <<
> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
Looks like you're really trying to reinvent the wheel.
val |= FIELD_PREP(sfc->soc_info->tran_mode_mask, TRAN_MODE_OCTAL_FULL);
using <linux/bitfield.h>.
Also, just define a 'mode' variable and set it in your if/else blocks,
that would look much better. Then you can set val |= FIELD_PREP(...,
mode) at the end.
> + else if (op->cmd.buswidth == 4)
> + val |= (TRAN_MODE_QUAD_FULL << (ffs(sfc->soc_info->tran_mode_mask)
> - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->cmd.buswidth == 2)
> + val |= (TRAN_MODE_DUAL_FULL << (ffs(sfc->soc_info->tran_mode_mask)
> - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->addr.buswidth == 8)
> + val |= (TRAN_MODE_OCTAL_IO << (ffs(sfc->soc_info->tran_mode_mask)
> - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->addr.buswidth == 4)
> + val |= (TRAN_MODE_QUAD_IO << (ffs(sfc->soc_info->tran_mode_mask) -
> 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->addr.buswidth == 2)
> + val |= (TRAN_MODE_DUAL_IO << (ffs(sfc->soc_info->tran_mode_mask) -
> 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->data.buswidth == 8)
> + val |= (TRAN_MODE_OCTAL_DATA <<
> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->data.buswidth == 4)
> + val |= (TRAN_MODE_QUAD_DATA << (ffs(sfc->soc_info->tran_mode_mask)
> - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->data.buswidth == 2)
> + val |= (TRAN_MODE_DUAL_DATA << (ffs(sfc->soc_info->tran_mode_mask)
> - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else
> + val |= (TRAN_MODE_STANDARD << (ffs(sfc->soc_info->tran_mode_mask)
> - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + writel(val, sfc->base + (sfc->soc_info->version >= ID_X1600 ?
> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
> +}
> +
> +/*
> + * We only need PIO mode to handle the SPI_MEM_NO_DATA transfers,
> + * and the unaligned accesses in SPI_MEM_DATA_IN transfers.
> + */
> +static void ingenic_sfc_read_rxfifo(struct ingenic_sfc *sfc, u8 *to,
> unsigned int len)
> +{
> + void __iomem *from;
> +
> + from = sfc->base + SFC_REG_DR;
> +
> + for (; len >= 4; len -= 4, to += 4) {
> + u32 val = __raw_readl(from);
> + memcpy(to, &val, 4);
No need to use memcpy for 4 bytes. You can do: put_unaligned(val, (u32
*)to);
> + }
> +
> + if (len) {
> + u32 val = __raw_readl(from);
> + memcpy(to, &val, len);
Hmm, I'm not sure that is endian-safe. I would prefer if you copied
byte by byte.
> + }
> +}
> +
> +static int ingenic_sfc_exec_op_pio(struct ingenic_sfc *sfc, const
> struct spi_mem_op *op)
> +{
> + int ret, val;
> +
> + val = readl(sfc->base + SFC_REG_GLB);
> + u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
> + writel(val, sfc->base + SFC_REG_GLB);
By the way, have you considered using regmap?
It would give you things like regmap_update_bits() for this kind of
things, and regmap_field() to handle your conf register being at a
different address across SoCs.
> +
> + val = TRAN_CONF_CMD_EN | op->cmd.opcode;
> +
> + if (op->addr.nbytes > 0) {
> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK, op->addr.nbytes);
> +
> + writel(op->addr.val & 0xffffffff, sfc->base + SFC_REG_DEV_ADDR(0));
> + writel(op->addr.val >> 32, sfc->base + SFC_REG_DEV_ADDR_PLUS(0));
> + }
> +
> + if (op->dummy.nbytes > 0)
> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
> + op->dummy.nbytes * 8 / op->dummy.buswidth);
> +
> + if (op->data.nbytes > 0)
> + val |= TRAN_CONF_DATA_EN;
> +
> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
> +
> + ingenic_sfc_set_transfer_mode(sfc, op);
> +
> + writel(0x1f, sfc->base + SFC_REG_SCR);
Random 0x1f value here, maybe use a macro?
> + writel(~(INTC_MASK_END | INTC_MASK_RREQ), sfc->base + SFC_REG_INTC);
> +
> + writel(0, sfc->base + SFC_REG_MEM_ADDR);
> +
> + writel(TRIG_FLUSH, sfc->base + SFC_REG_TRIG);
> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
> +
> + ret = wait_for_completion_timeout(&sfc->completion,
> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
> + if (!ret) {
> + writel(0x1f, sfc->base + SFC_REG_INTC);
> + writel(0x1f, sfc->base + SFC_REG_SCR);
> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC device\n",
> __LINE__);
> + return -ETIMEDOUT;
> + }
> +
> + ingenic_sfc_read_rxfifo(sfc, op->data.buf.in, op->data.nbytes);
> + readl_poll_timeout(sfc->base + SFC_REG_SR, val, val & SR_END, 10,
> 0);
Infinite timeout? Is that very wise?
> +
> + writel(INTC_MASK_END | INTC_MASK_RREQ, sfc->base + SFC_REG_SCR);
> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
> +
> + return 0;
> +}
> +
> +static int ingenic_sfc_exec_op_dma(struct ingenic_sfc *sfc, const
> struct spi_mem_op *op)
> +{
> + dma_addr_t addr;
> + int ret, val;
> +
> + val = readl(sfc->base + SFC_REG_GLB);
> + u32p_replace_bits(&val, op->data.dir == SPI_MEM_DATA_IN ?
> + GLB_TRAN_DIR_READ : GLB_TRAN_DIR_WRITE, GLB_TRAN_DIR_MASK);
> + u32p_replace_bits(&val, GLB_OP_MODE_DMA, GLB_OP_MODE_MASK);
> + writel(val, sfc->base + SFC_REG_GLB);
> +
> + val = TRAN_CONF_CMD_EN | op->cmd.opcode;
> +
> + if (op->addr.nbytes > 0) {
> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK, op->addr.nbytes);
> + writel(op->addr.val & 0xffffffff, sfc->base + SFC_REG_DEV_ADDR(0));
> + writel(op->addr.val >> 32, sfc->base + SFC_REG_DEV_ADDR_PLUS(0));
> + }
> +
> + if (op->dummy.nbytes > 0)
> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
> + op->dummy.nbytes * 8 / op->dummy.buswidth);
> +
> + if (op->data.nbytes > 0)
> + val |= TRAN_CONF_DATA_EN;
There's a lot of code duplication here with ingenic_sfc_exec_op_pio().
A lot can be factorized.
> +
> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
> +
> + ingenic_sfc_set_transfer_mode(sfc, op);
> +
> + writel(0x1f, sfc->base + SFC_REG_SCR);
> + writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
> +
> + switch (op->data.dir) {
> + case SPI_MEM_DATA_IN:
> + addr = dma_map_single(sfc->dev, op->data.buf.in, op->data.nbytes,
> DMA_FROM_DEVICE);
> + if (dma_mapping_error(sfc->dev, addr)) {
> + dev_err(sfc->dev, "RX DMA memory not mapped\n");
> + return -ENOMEM;
> + }
> +
> + writel(addr, sfc->base + SFC_REG_MEM_ADDR);
> + break;
> +
> + case SPI_MEM_DATA_OUT:
> + addr = dma_map_single(sfc->dev, (void *)op->data.buf.out,
> + op->data.nbytes, DMA_TO_DEVICE);
> + if (dma_mapping_error(sfc->dev, addr)) {
> + dev_err(sfc->dev, "TX DMA memory not mapped\n");
> + return -ENOMEM;
> + }
> +
> + writel(addr, sfc->base + SFC_REG_MEM_ADDR);
> + break;
> +
> + default:
> + return -EINVAL;
> + }
> +
> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
> +
> + ret = wait_for_completion_timeout(&sfc->completion,
> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
> + if (!ret) {
> + writel(0x1f, sfc->base + SFC_REG_INTC);
> + writel(0x1f, sfc->base + SFC_REG_SCR);
> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC device\n",
> __LINE__);
> + return -ETIMEDOUT;
> + }
> +
> + dma_unmap_single(sfc->dev, addr, op->data.nbytes,
> + op->data.dir == SPI_MEM_DATA_IN ? DMA_FROM_DEVICE :
> DMA_TO_DEVICE);
Use a small inline function for that too. My personal rule is that ?:
is fine if the line fits in 80 characters, but if you have to break,
then you really need to move it somewhere else.
> +
> + writel(INTC_MASK_END, sfc->base + SFC_REG_SCR);
> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
> +
> + return 0;
> +}
> +
> +static int ingenic_sfc_exec_op(struct spi_mem *mem, const struct
> spi_mem_op *op)
> +{
> + struct spi_device *spi = mem->spi;
> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
> + uintptr_t addr = (uintptr_t)op->data.buf.in;
> +
> + init_completion(&sfc->completion);
> +
> + switch (op->data.dir) {
> + case SPI_MEM_DATA_IN:
> + if (sfc->soc_info->version >= ID_X1600 || IS_ALIGNED(addr, 4))
> + break;
> +
> + fallthrough;
> +
> + case SPI_MEM_NO_DATA:
> + return ingenic_sfc_exec_op_pio(sfc, op);
> +
> + default:
> + break;
> + }
> +
> + return ingenic_sfc_exec_op_dma(sfc, op);
> +}
> +
> +static int ingenic_sfc_poll_status(struct spi_mem *mem, const struct
> spi_mem_op *op,
> + u16 mask, u16 match, unsigned long initial_delay_us,
> + unsigned long polling_delay_us, unsigned long timeout_ms)
> +{
> + struct spi_device *spi = mem->spi;
> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
> + int ret, val;
> +
> + init_completion(&sfc->completion);
> +
> + val = readl(sfc->base + SFC_REG_GLB);
> + u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
> + writel(val, sfc->base + SFC_REG_GLB);
> +
> + writel(match, sfc->base + SFC_REG_DEV_STA_EXP);
> + writel(mask, sfc->base + SFC_REG_DEV_STA_MSK);
> +
> + val = TRAN_CONF_POLL_EN | TRAN_CONF_CMD_EN | op->cmd.opcode;
> +
> + if (op->addr.nbytes > 0) {
> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK, op->addr.nbytes);
> +
> + writel(op->addr.val & 0xffffffff, sfc->base + SFC_REG_DEV_ADDR(0));
> + writel(op->addr.val >> 32, sfc->base + SFC_REG_DEV_ADDR_PLUS(0));
> + }
> +
> + if (op->dummy.nbytes > 0)
> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
> + op->dummy.nbytes * 8 / op->dummy.buswidth);
> +
> + if (op->data.nbytes > 0)
> + val |= TRAN_CONF_DATA_EN;
> +
> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
> +
> + ingenic_sfc_set_transfer_mode(sfc, op);
> +
> + writel(0x1f, sfc->base + SFC_REG_SCR);
> + writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
> +
> + writel(0, sfc->base + SFC_REG_MEM_ADDR);
> +
> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
> +
> + ret = wait_for_completion_timeout(&sfc->completion,
> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
> + if (!ret) {
> + writel(0x1f, sfc->base + SFC_REG_INTC);
> + writel(0x1f, sfc->base + SFC_REG_SCR);
> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC device\n",
> __LINE__);
> + return -ETIMEDOUT;
> + }
> +
> + writel(SCR_CLR_END, sfc->base + SFC_REG_SCR);
> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
> +
> + return 0;
> +}
> +
> +static const struct spi_controller_mem_ops ingenic_sfc_mem_ops = {
> + .adjust_op_size = ingenic_sfc_adjust_op_size,
> + .supports_op = ingenic_sfc_supports_op,
> + .exec_op = ingenic_sfc_exec_op,
> + .poll_status = ingenic_sfc_poll_status,
> +};
> +
> +static int ingenic_sfc_setup(struct spi_device *spi)
> +{
> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
> + unsigned long rate;
> + int ret, val;
> +
> + if (!spi->max_speed_hz)
> + return -EINVAL;
Maybe set a sane default?
> +
> + ret = clk_set_rate(sfc->clk, spi->max_speed_hz * 2);
> + if (ret)
> + return -EINVAL;
> +
> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
> + writel(0, sfc->base + SFC_REG_DEV_CONF);
> + writel(0, sfc->base + SFC_REG_CGE);
> +
> + val = readl(sfc->base + SFC_REG_GLB);
> + u32p_replace_bits(&val, 64 - 1, GLB_THRESHOLD_MASK);
> + writel(val, sfc->base + SFC_REG_GLB);
> +
> + val = readl(sfc->base + SFC_REG_DEV_CONF);
> +
> + /* cpha bit:0 , cpol bit:0 */
> + val &= ~(DEV_CONF_CPHA | DEV_CONF_CPOL);
> + val |= spi->mode & SPI_CPHA ? DEV_CONF_CPHA : 0;
> + val |= spi->mode & SPI_CPOL ? DEV_CONF_CPOL : 0;
> +
> + /* ce_dl bit:1, hold bit:1, wp bit:1 */
> + val |= (DEV_CONF_CE_DL | DEV_CONF_HOLD_DL | DEV_CONF_WP_DL);
> +
> + writel(val, sfc->base + SFC_REG_DEV_CONF);
> +
> + val = readl(sfc->base + SFC_REG_GLB);
> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
> + writel(val, sfc->base + SFC_REG_GLB);
> +
> + rate = clk_get_rate(sfc->clk);
I'd suggest using clk_round_rate() before clk_set_rate() because then
you know what frequency it's going to be, and you don't have to call
clk_get_rate() afterwards.
Cheers,
-Paul
> + val = readl(sfc->base + SFC_REG_DEV_CONF);
> + if (sfc->soc_info->version >= ID_X1600 && rate >= 200000000)
> + u32p_replace_bits(&val, DEV_CONF_SMP_DELAY_180DEG,
> DEV_CONF_SMP_DELAY_MASK);
> + else if (sfc->soc_info->version == ID_X1000 && rate >= 100000000)
> + u32p_replace_bits(&val, DEV_CONF_SMP_DELAY_HALF_CYCLE,
> DEV_CONF_SMP_DELAY_MASK);
> + writel(val, sfc->base + SFC_REG_DEV_CONF);
> +
> + return 0;
> +}
> +
> +static int ingenic_sfc_probe(struct platform_device *pdev)
> +{
> + struct ingenic_sfc *sfc;
> + struct spi_controller *ctlr;
> + int ret;
> +
> + ctlr = spi_alloc_master(&pdev->dev, sizeof(*sfc));
> + if (!ctlr)
> + return -ENOMEM;
> +
> + sfc = spi_controller_get_devdata(ctlr);
> +
> + sfc->soc_info = of_device_get_match_data(&pdev->dev);
> + if (!sfc->soc_info) {
> + dev_err(&pdev->dev, "No of match data provided\n");
> + ret = -ENODEV;
> + goto err_put_master;
> + }
> +
> + sfc->base = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(sfc->base)) {
> + ret = PTR_ERR(sfc->base);
> + goto err_put_master;
> + }
> +
> + sfc->clk = devm_clk_get(&pdev->dev, "sfc");
> + if (IS_ERR(sfc->clk)) {
> + ret = IS_ERR(sfc->clk);
> + goto err_put_master;
> + }
> +
> + ret = clk_prepare_enable(sfc->clk);
> + if (ret)
> + goto err_put_master;
> +
> + sfc->irq = platform_get_irq(pdev, 0);
> + if (sfc->irq < 0) {
> + ret = sfc->irq;
> + goto err_put_master;
> + }
> +
> + sfc->dev = &pdev->dev;
> +
> + platform_set_drvdata(pdev, sfc);
> +
> + ret = devm_request_irq(&pdev->dev, sfc->irq,
> ingenic_sfc_irq_handler, 0,
> + dev_name(&pdev->dev), sfc);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to request irq%d, ret = %d\n",
> sfc->irq, ret);
> + goto err_put_master;
> + }
> +
> + ctlr->bus_num = -1;
> + ctlr->num_chipselect = 1;
> + ctlr->mem_ops = &ingenic_sfc_mem_ops;
> + ctlr->dev.of_node = pdev->dev.of_node;
> + ctlr->setup = ingenic_sfc_setup;
> + ctlr->mode_bits = SPI_CPHA | SPI_CPOL |
> + SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
> + if (sfc->soc_info->version >= ID_X2000)
> + ctlr->mode_bits |= SPI_RX_OCTAL | SPI_TX_OCTAL;
> +
> + ret = devm_spi_register_controller(&pdev->dev, ctlr);
> + if (ret)
> + goto err_put_master;
> +
> + return 0;
> +
> +err_put_master:
> + spi_master_put(ctlr);
> +
> + return ret;
> +}
> +
> +static const struct ingenic_soc_info x1000_soc_info = {
> + .version = ID_X1000,
> +
> + .max_bus_width = 4,
> +
> + .tran_mode_mask = TRAN_CONF_TRAN_MODE_MASK,
> +};
> +
> +static const struct ingenic_soc_info x1600_soc_info = {
> + .version = ID_X1600,
> +
> + .max_bus_width = 4,
> +
> + .tran_mode_mask = TRAN_CONF_TRAN_MODE_MASK,
> +};
> +
> +static const struct ingenic_soc_info x2000_soc_info = {
> + .version = ID_X2000,
> +
> + .max_bus_width = 8,
> +
> + .tran_mode_mask = TRAN_CFG1_TRAN_MODE_MASK,
> +};
> +
> +static const struct of_device_id ingenic_sfc_of_matches[] = {
> + { .compatible = "ingenic,x1000-sfc", .data = &x1000_soc_info },
> + { .compatible = "ingenic,x1600-sfc", .data = &x1600_soc_info },
> + { .compatible = "ingenic,x2000-sfc", .data = &x2000_soc_info },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, ingenic_sfc_of_matches);
> +
> +static struct platform_driver ingenic_sfc_driver = {
> + .driver = {
> + .name = "ingenic-sfc",
> + .of_match_table = ingenic_sfc_of_matches,
> + },
> + .probe = ingenic_sfc_probe,
> +};
> +module_platform_driver(ingenic_sfc_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>");
> +MODULE_DESCRIPTION("Ingenic SoCs SPI Flash Controller Driver");
> --
> 2.7.4
>
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
@ 2022-07-22 20:03 ` Paul Cercueil
0 siblings, 0 replies; 78+ messages in thread
From: Paul Cercueil @ 2022-07-22 20:03 UTC (permalink / raw)
To: 周琰杰
Cc: tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, broonie, robh+dt, krzysztof.kozlowski+dt, linux-mtd,
linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, dongsheng.qiu, aric.pzqi, rick.tyliu,
jinghui.liu, sernia.zhou, reimu
Hi Zhou,
Le sam., juil. 23 2022 at 00:48:30 +0800, 周琰杰 (Zhou Yanjie)
<zhouyanjie@wanyeetech.com> a écrit :
> Add SFC support for the X1000 SoC, the X1600 SoC, and the X2000 SoC
> from Ingenic.
>
> Signed-off-by: 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
> ---
> drivers/spi/Kconfig | 9 +
> drivers/spi/Makefile | 1 +
> drivers/spi/spi-ingenic-sfc.c | 662
> ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 672 insertions(+)
> create mode 100644 drivers/spi/spi-ingenic-sfc.c
>
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index 3b1044e..1077bd3 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -437,6 +437,15 @@ config SPI_INGENIC
> To compile this driver as a module, choose M here: the module
> will be called spi-ingenic.
>
> +config SPI_INGENIC_SFC
> + tristate "Ingenic SoCs SPI Flash Controller"
> + depends on MACH_INGENIC || COMPILE_TEST
> + help
> + This enables support for the Ingenic SoCs SPI flash controller.
> +
> + To compile this driver as a module, choose M here: the module
> + will be called ingenic-sfc.
> +
> config SPI_INTEL
> tristate
>
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index 0f44eb6..f3e42c0 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -62,6 +62,7 @@ obj-$(CONFIG_SPI_HISI_SFC_V3XX) +=
> spi-hisi-sfc-v3xx.o
> obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o
> obj-$(CONFIG_SPI_IMX) += spi-imx.o
> obj-$(CONFIG_SPI_INGENIC) += spi-ingenic.o
> +obj-$(CONFIG_SPI_INGENIC_SFC) += spi-ingenic-sfc.o
> obj-$(CONFIG_SPI_INTEL) += spi-intel.o
> obj-$(CONFIG_SPI_INTEL_PCI) += spi-intel-pci.o
> obj-$(CONFIG_SPI_INTEL_PLATFORM) += spi-intel-platform.o
> diff --git a/drivers/spi/spi-ingenic-sfc.c
> b/drivers/spi/spi-ingenic-sfc.c
> new file mode 100644
> index 00000000..a565546
> --- /dev/null
> +++ b/drivers/spi/spi-ingenic-sfc.c
> @@ -0,0 +1,662 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
Dual-license driver? That's not what MODULE_LICENSE() says.
> +/*
> + * Ingenic SoCs SPI Flash Controller Driver
> + * Copyright (c) 2022 周琰杰 (Zhou Yanjie)
> <zhouyanjie@wanyeetech.com>
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/interrupt.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/mtd/mtd.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/spi/spi.h>
> +#include <linux/spi/spi-mem.h>
> +
> +/* SFC register offsets */
> +#define SFC_REG_GLB 0x0000
> +#define SFC_REG_DEV_CONF 0x0004
> +#define SFC_REG_DEV_STA_EXP 0x0008
> +#define SFC_REG_DEV_STA_RT 0x000c
> +#define SFC_REG_DEV_STA_MSK 0x0010
> +#define SFC_REG_TRAN_CONF(n) (0x0014 + n * 4)
> +#define SFC_REG_TRAN_CFG0(n) (0x0014 + n * 4)
You should protect the macro parameter. If you do for instance
SFC_REG_TRAN_CONF(x + 1) it would resolve to (0x0014 + x + 1 * 4) which
is not what you'd want.
Also - looks like SFC_REG_TRAN_CONF() and SFC_REG_TRAN_CFG0() are the
same thing, that's on purpose?
> +#define SFC_REG_TRAN_LEN 0x002c
> +#define SFC_REG_DEV_ADDR(n) (0x0030 + n * 4)
> +#define SFC_REG_DEV_ADDR_PLUS(n) (0x0048 + n * 4)
> +#define SFC_REG_MEM_ADDR 0x0060
> +#define SFC_REG_TRIG 0x0064
> +#define SFC_REG_SR 0x0068
> +#define SFC_REG_SCR 0x006c
> +#define SFC_REG_INTC 0x0070
> +#define SFC_REG_FSM 0x0074
> +#define SFC_REG_CGE 0x0078
> +#define SFC_REG_TRAN_CFG1(n) (0x009c + n * 4)
> +#define SFC_REG_DR 0x1000
> +
> +/* bits within the GLB register */
> +#define GLB_TRAN_DIR_MASK GENMASK(13, 13)
> +#define GLB_TRAN_DIR_WRITE 0x1
> +#define GLB_TRAN_DIR_READ 0x0
When it's a single bit - just use BIT().
> +#define GLB_THRESHOLD_MASK GENMASK(12, 7)
> +#define GLB_OP_MODE_MASK GENMASK(6, 6)
Same here, and I see it a few times below as well.
> +#define GLB_OP_MODE_DMA 0x1
> +#define GLB_OP_MODE_SLAVE 0x0
> +#define GLB_PHASE_NUM_MASK GENMASK(5, 3)
> +#define GLB_WP_EN BIT(2)
> +#define GLB_BURST_MD_MASK GENMASK(1, 0)
> +#define GLB_BURST_MD_INCR32 0x3
> +#define GLB_BURST_MD_INCR16 0x2
> +#define GLB_BURST_MD_INCR8 0x1
> +#define GLB_BURST_MD_INCR4 0x0
> +
> +/* bits within the DEV_CONF register */
> +#define DEV_CONF_SMP_DELAY_MASK GENMASK(20, 16)
> +#define DEV_CONF_SMP_DELAY_180DEG 0x4
> +#define DEV_CONF_SMP_DELAY_HALF_CYCLE 0x1
> +#define DEV_CONF_CMD_TYPE_MASK GENMASK(15, 15)
> +#define DEV_CONF_CMD_TYPE_16BIT 0x1
> +#define DEV_CONF_CMD_TYPE_8BIT 0x0
> +#define DEV_CONF_STA_TYPE_MASK GENMASK(14, 13)
> +#define DEV_CONF_THOLD_MASK GENMASK(12, 11)
> +#define DEV_CONF_TSETUP_MASK GENMASK(10, 9)
> +#define DEV_CONF_TSH_MASK GENMASK(8, 5)
> +#define DEV_CONF_CPHA BIT(4)
> +#define DEV_CONF_CPOL BIT(3)
> +#define DEV_CONF_CE_DL BIT(2)
> +#define DEV_CONF_HOLD_DL BIT(1)
> +#define DEV_CONF_WP_DL BIT(0)
> +
> +/* bits within the TRAN_CONF(n) register */
> +#define TRAN_CONF_TRAN_MODE_MASK GENMASK(31, 29)
> +#define TRAN_CONF_ADDR_WIDTH_MASK GENMASK(28, 26)
> +#define TRAN_CONF_POLL_EN BIT(25)
> +#define TRAN_CONF_CMD_EN BIT(24)
> +#define TRAN_CONF_PHASE_FORMAT_MASK GENMASK(23, 23)
> +#define TRAN_CONF_DMY_BITS_MASK GENMASK(22, 17)
> +#define TRAN_CONF_DATA_EN BIT(16)
> +#define TRAN_CONF_CMD_MASK GENMASK(15, 0)
> +
> +/* bits within the TRIG register */
> +#define TRIG_FLUSH BIT(2)
> +#define TRIG_STOP BIT(1)
> +#define TRIG_START BIT(0)
> +
> +/* bits within the SR register */
> +#define SR_FIFO_NUM_MASK GENMASK(22, 16)
> +#define SR_END BIT(4)
> +#define SR_TRAN_REQ BIT(3)
> +#define SR_RECE_REQ BIT(2)
> +#define SR_OVER BIT(1)
> +#define SR_UNDER BIT(0)
> +
> +/* bits within the SCR register */
> +#define SCR_CLR_END BIT(4)
> +#define SCR_CLR_TREQ BIT(3)
> +#define SCR_CLR_RREQ BIT(2)
> +#define SCR_CLR_OVER BIT(1)
> +#define SCR_CLR_UNDER BIT(0)
> +
> +/* bits within the INTC register */
> +#define INTC_MASK_END BIT(4)
> +#define INTC_MASK_TREQ BIT(3)
> +#define INTC_MASK_RREQ BIT(2)
> +#define INTC_MASK_OVER BIT(1)
> +#define INTC_MASK_UNDER BIT(0)
> +
> +/* bits within the TRAN_CFG1(n) register */
> +#define TRAN_CFG1_TRAN_MODE_MASK GENMASK(7, 4)
> +
> +#define TRAN_MODE_STANDARD 0
> +#define TRAN_MODE_DUAL_DATA 1
> +#define TRAN_MODE_DUAL_IO 2
> +#define TRAN_MODE_DUAL_FULL 3
> +#define TRAN_MODE_QUAD_DATA 5
> +#define TRAN_MODE_QUAD_IO 6
> +#define TRAN_MODE_QUAD_FULL 7
> +#define TRAN_MODE_OCTAL_DATA 9
> +#define TRAN_MODE_OCTAL_IO 10
> +#define TRAN_MODE_OCTAL_FULL 11
> +
> +#define INGENIC_SFC_FIFO_SIZE (64 * 4)
> +
> +#define INGENIC_SFC_TRANSFER_TIMEOUT 1000
Maybe add the unit name in the macro as well -
INGENIC_SFC_TRANSFER_TIMEOUT_MS.
> +
> +enum ingenic_sfc_version {
> + ID_X1000,
> + ID_X1600,
> + ID_X2000,
> +};
> +
> +struct ingenic_soc_info {
> + enum ingenic_sfc_version version;
> +
> + unsigned int max_bus_width;
> +
> + const u32 tran_mode_mask;
> +};
> +
> +struct ingenic_sfc {
> + const struct ingenic_soc_info *soc_info;
> +
> + void __iomem *base;
> + struct device *dev;
> + struct clk *clk;
> + int irq;
> +
> + struct completion completion;
> +};
> +
> +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
> +{
> + struct ingenic_sfc *sfc = data;
> +
> + writel(0x1f, sfc->base + SFC_REG_INTC);
> +
> + complete(&sfc->completion);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int ingenic_sfc_adjust_op_size(struct spi_mem *mem, struct
> spi_mem_op *op)
> +{
> + uintptr_t addr = (uintptr_t)op->data.buf.in;
> +
> + if (op->data.nbytes > INGENIC_SFC_FIFO_SIZE && !IS_ALIGNED(addr, 4))
> + op->data.nbytes = INGENIC_SFC_FIFO_SIZE;
> +
> + return 0;
> +}
> +
> +static bool ingenic_sfc_supports_op(struct spi_mem *mem, const
> struct spi_mem_op *op)
> +{
> + struct spi_device *spi = mem->spi;
> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
> + uintptr_t addr = (uintptr_t)op->data.buf.in;
> +
> + /* The controller only supports Standard SPI mode, Duall mode, Quad
> mode and Octal mode. */
Dual*
> + if (op->cmd.buswidth > sfc->soc_info->max_bus_width ||
> + op->addr.buswidth > sfc->soc_info->max_bus_width ||
> + op->dummy.buswidth > sfc->soc_info->max_bus_width ||
> + op->data.buswidth > sfc->soc_info->max_bus_width)
> + return false;
> +
> + /* Max 32 dummy clock cycles supported */
> + if (op->dummy.nbytes && op->dummy.nbytes * 8 / op->dummy.buswidth >
> 32)
> + return false;
> +
> + /* Max rx data length, check controller limits and alignment */
> + if (op->data.dir == SPI_MEM_DATA_IN &&
> + op->data.nbytes > INGENIC_SFC_FIFO_SIZE && !IS_ALIGNED(addr, 4))
This does the same check than in ingenic_sfc_adjust_op_size(), maybe
move it to a new inline function?
> + return false;
> +
> + /* Max 6 bytes address width supported */
> + if (op->addr.nbytes > 6)
> + return false;
> +
> + return spi_mem_default_supports_op(mem, op);
> +}
> +
> +static void ingenic_sfc_set_transfer_mode(struct ingenic_sfc *sfc,
> const struct spi_mem_op *op)
> +{
> + int val;
> +
> + val = readl(sfc->base + (sfc->soc_info->version >= ID_X1600 ?
> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
As Krzysztof said - ugh.
Also, instead of having a "version" enum in your soc_info, why not just
have a "reg_conf" field that gives you directly the right register?
> + val &= ~sfc->soc_info->tran_mode_mask;
> + if (op->cmd.buswidth == 8)
> + val |= (TRAN_MODE_OCTAL_FULL <<
> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
Looks like you're really trying to reinvent the wheel.
val |= FIELD_PREP(sfc->soc_info->tran_mode_mask, TRAN_MODE_OCTAL_FULL);
using <linux/bitfield.h>.
Also, just define a 'mode' variable and set it in your if/else blocks,
that would look much better. Then you can set val |= FIELD_PREP(...,
mode) at the end.
> + else if (op->cmd.buswidth == 4)
> + val |= (TRAN_MODE_QUAD_FULL << (ffs(sfc->soc_info->tran_mode_mask)
> - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->cmd.buswidth == 2)
> + val |= (TRAN_MODE_DUAL_FULL << (ffs(sfc->soc_info->tran_mode_mask)
> - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->addr.buswidth == 8)
> + val |= (TRAN_MODE_OCTAL_IO << (ffs(sfc->soc_info->tran_mode_mask)
> - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->addr.buswidth == 4)
> + val |= (TRAN_MODE_QUAD_IO << (ffs(sfc->soc_info->tran_mode_mask) -
> 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->addr.buswidth == 2)
> + val |= (TRAN_MODE_DUAL_IO << (ffs(sfc->soc_info->tran_mode_mask) -
> 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->data.buswidth == 8)
> + val |= (TRAN_MODE_OCTAL_DATA <<
> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->data.buswidth == 4)
> + val |= (TRAN_MODE_QUAD_DATA << (ffs(sfc->soc_info->tran_mode_mask)
> - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else if (op->data.buswidth == 2)
> + val |= (TRAN_MODE_DUAL_DATA << (ffs(sfc->soc_info->tran_mode_mask)
> - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + else
> + val |= (TRAN_MODE_STANDARD << (ffs(sfc->soc_info->tran_mode_mask)
> - 1)) &
> + sfc->soc_info->tran_mode_mask;
> + writel(val, sfc->base + (sfc->soc_info->version >= ID_X1600 ?
> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
> +}
> +
> +/*
> + * We only need PIO mode to handle the SPI_MEM_NO_DATA transfers,
> + * and the unaligned accesses in SPI_MEM_DATA_IN transfers.
> + */
> +static void ingenic_sfc_read_rxfifo(struct ingenic_sfc *sfc, u8 *to,
> unsigned int len)
> +{
> + void __iomem *from;
> +
> + from = sfc->base + SFC_REG_DR;
> +
> + for (; len >= 4; len -= 4, to += 4) {
> + u32 val = __raw_readl(from);
> + memcpy(to, &val, 4);
No need to use memcpy for 4 bytes. You can do: put_unaligned(val, (u32
*)to);
> + }
> +
> + if (len) {
> + u32 val = __raw_readl(from);
> + memcpy(to, &val, len);
Hmm, I'm not sure that is endian-safe. I would prefer if you copied
byte by byte.
> + }
> +}
> +
> +static int ingenic_sfc_exec_op_pio(struct ingenic_sfc *sfc, const
> struct spi_mem_op *op)
> +{
> + int ret, val;
> +
> + val = readl(sfc->base + SFC_REG_GLB);
> + u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
> + writel(val, sfc->base + SFC_REG_GLB);
By the way, have you considered using regmap?
It would give you things like regmap_update_bits() for this kind of
things, and regmap_field() to handle your conf register being at a
different address across SoCs.
> +
> + val = TRAN_CONF_CMD_EN | op->cmd.opcode;
> +
> + if (op->addr.nbytes > 0) {
> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK, op->addr.nbytes);
> +
> + writel(op->addr.val & 0xffffffff, sfc->base + SFC_REG_DEV_ADDR(0));
> + writel(op->addr.val >> 32, sfc->base + SFC_REG_DEV_ADDR_PLUS(0));
> + }
> +
> + if (op->dummy.nbytes > 0)
> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
> + op->dummy.nbytes * 8 / op->dummy.buswidth);
> +
> + if (op->data.nbytes > 0)
> + val |= TRAN_CONF_DATA_EN;
> +
> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
> +
> + ingenic_sfc_set_transfer_mode(sfc, op);
> +
> + writel(0x1f, sfc->base + SFC_REG_SCR);
Random 0x1f value here, maybe use a macro?
> + writel(~(INTC_MASK_END | INTC_MASK_RREQ), sfc->base + SFC_REG_INTC);
> +
> + writel(0, sfc->base + SFC_REG_MEM_ADDR);
> +
> + writel(TRIG_FLUSH, sfc->base + SFC_REG_TRIG);
> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
> +
> + ret = wait_for_completion_timeout(&sfc->completion,
> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
> + if (!ret) {
> + writel(0x1f, sfc->base + SFC_REG_INTC);
> + writel(0x1f, sfc->base + SFC_REG_SCR);
> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC device\n",
> __LINE__);
> + return -ETIMEDOUT;
> + }
> +
> + ingenic_sfc_read_rxfifo(sfc, op->data.buf.in, op->data.nbytes);
> + readl_poll_timeout(sfc->base + SFC_REG_SR, val, val & SR_END, 10,
> 0);
Infinite timeout? Is that very wise?
> +
> + writel(INTC_MASK_END | INTC_MASK_RREQ, sfc->base + SFC_REG_SCR);
> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
> +
> + return 0;
> +}
> +
> +static int ingenic_sfc_exec_op_dma(struct ingenic_sfc *sfc, const
> struct spi_mem_op *op)
> +{
> + dma_addr_t addr;
> + int ret, val;
> +
> + val = readl(sfc->base + SFC_REG_GLB);
> + u32p_replace_bits(&val, op->data.dir == SPI_MEM_DATA_IN ?
> + GLB_TRAN_DIR_READ : GLB_TRAN_DIR_WRITE, GLB_TRAN_DIR_MASK);
> + u32p_replace_bits(&val, GLB_OP_MODE_DMA, GLB_OP_MODE_MASK);
> + writel(val, sfc->base + SFC_REG_GLB);
> +
> + val = TRAN_CONF_CMD_EN | op->cmd.opcode;
> +
> + if (op->addr.nbytes > 0) {
> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK, op->addr.nbytes);
> + writel(op->addr.val & 0xffffffff, sfc->base + SFC_REG_DEV_ADDR(0));
> + writel(op->addr.val >> 32, sfc->base + SFC_REG_DEV_ADDR_PLUS(0));
> + }
> +
> + if (op->dummy.nbytes > 0)
> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
> + op->dummy.nbytes * 8 / op->dummy.buswidth);
> +
> + if (op->data.nbytes > 0)
> + val |= TRAN_CONF_DATA_EN;
There's a lot of code duplication here with ingenic_sfc_exec_op_pio().
A lot can be factorized.
> +
> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
> +
> + ingenic_sfc_set_transfer_mode(sfc, op);
> +
> + writel(0x1f, sfc->base + SFC_REG_SCR);
> + writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
> +
> + switch (op->data.dir) {
> + case SPI_MEM_DATA_IN:
> + addr = dma_map_single(sfc->dev, op->data.buf.in, op->data.nbytes,
> DMA_FROM_DEVICE);
> + if (dma_mapping_error(sfc->dev, addr)) {
> + dev_err(sfc->dev, "RX DMA memory not mapped\n");
> + return -ENOMEM;
> + }
> +
> + writel(addr, sfc->base + SFC_REG_MEM_ADDR);
> + break;
> +
> + case SPI_MEM_DATA_OUT:
> + addr = dma_map_single(sfc->dev, (void *)op->data.buf.out,
> + op->data.nbytes, DMA_TO_DEVICE);
> + if (dma_mapping_error(sfc->dev, addr)) {
> + dev_err(sfc->dev, "TX DMA memory not mapped\n");
> + return -ENOMEM;
> + }
> +
> + writel(addr, sfc->base + SFC_REG_MEM_ADDR);
> + break;
> +
> + default:
> + return -EINVAL;
> + }
> +
> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
> +
> + ret = wait_for_completion_timeout(&sfc->completion,
> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
> + if (!ret) {
> + writel(0x1f, sfc->base + SFC_REG_INTC);
> + writel(0x1f, sfc->base + SFC_REG_SCR);
> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC device\n",
> __LINE__);
> + return -ETIMEDOUT;
> + }
> +
> + dma_unmap_single(sfc->dev, addr, op->data.nbytes,
> + op->data.dir == SPI_MEM_DATA_IN ? DMA_FROM_DEVICE :
> DMA_TO_DEVICE);
Use a small inline function for that too. My personal rule is that ?:
is fine if the line fits in 80 characters, but if you have to break,
then you really need to move it somewhere else.
> +
> + writel(INTC_MASK_END, sfc->base + SFC_REG_SCR);
> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
> +
> + return 0;
> +}
> +
> +static int ingenic_sfc_exec_op(struct spi_mem *mem, const struct
> spi_mem_op *op)
> +{
> + struct spi_device *spi = mem->spi;
> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
> + uintptr_t addr = (uintptr_t)op->data.buf.in;
> +
> + init_completion(&sfc->completion);
> +
> + switch (op->data.dir) {
> + case SPI_MEM_DATA_IN:
> + if (sfc->soc_info->version >= ID_X1600 || IS_ALIGNED(addr, 4))
> + break;
> +
> + fallthrough;
> +
> + case SPI_MEM_NO_DATA:
> + return ingenic_sfc_exec_op_pio(sfc, op);
> +
> + default:
> + break;
> + }
> +
> + return ingenic_sfc_exec_op_dma(sfc, op);
> +}
> +
> +static int ingenic_sfc_poll_status(struct spi_mem *mem, const struct
> spi_mem_op *op,
> + u16 mask, u16 match, unsigned long initial_delay_us,
> + unsigned long polling_delay_us, unsigned long timeout_ms)
> +{
> + struct spi_device *spi = mem->spi;
> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
> + int ret, val;
> +
> + init_completion(&sfc->completion);
> +
> + val = readl(sfc->base + SFC_REG_GLB);
> + u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
> + writel(val, sfc->base + SFC_REG_GLB);
> +
> + writel(match, sfc->base + SFC_REG_DEV_STA_EXP);
> + writel(mask, sfc->base + SFC_REG_DEV_STA_MSK);
> +
> + val = TRAN_CONF_POLL_EN | TRAN_CONF_CMD_EN | op->cmd.opcode;
> +
> + if (op->addr.nbytes > 0) {
> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK, op->addr.nbytes);
> +
> + writel(op->addr.val & 0xffffffff, sfc->base + SFC_REG_DEV_ADDR(0));
> + writel(op->addr.val >> 32, sfc->base + SFC_REG_DEV_ADDR_PLUS(0));
> + }
> +
> + if (op->dummy.nbytes > 0)
> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
> + op->dummy.nbytes * 8 / op->dummy.buswidth);
> +
> + if (op->data.nbytes > 0)
> + val |= TRAN_CONF_DATA_EN;
> +
> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
> +
> + ingenic_sfc_set_transfer_mode(sfc, op);
> +
> + writel(0x1f, sfc->base + SFC_REG_SCR);
> + writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
> +
> + writel(0, sfc->base + SFC_REG_MEM_ADDR);
> +
> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
> +
> + ret = wait_for_completion_timeout(&sfc->completion,
> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
> + if (!ret) {
> + writel(0x1f, sfc->base + SFC_REG_INTC);
> + writel(0x1f, sfc->base + SFC_REG_SCR);
> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC device\n",
> __LINE__);
> + return -ETIMEDOUT;
> + }
> +
> + writel(SCR_CLR_END, sfc->base + SFC_REG_SCR);
> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
> +
> + return 0;
> +}
> +
> +static const struct spi_controller_mem_ops ingenic_sfc_mem_ops = {
> + .adjust_op_size = ingenic_sfc_adjust_op_size,
> + .supports_op = ingenic_sfc_supports_op,
> + .exec_op = ingenic_sfc_exec_op,
> + .poll_status = ingenic_sfc_poll_status,
> +};
> +
> +static int ingenic_sfc_setup(struct spi_device *spi)
> +{
> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
> + unsigned long rate;
> + int ret, val;
> +
> + if (!spi->max_speed_hz)
> + return -EINVAL;
Maybe set a sane default?
> +
> + ret = clk_set_rate(sfc->clk, spi->max_speed_hz * 2);
> + if (ret)
> + return -EINVAL;
> +
> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
> + writel(0, sfc->base + SFC_REG_DEV_CONF);
> + writel(0, sfc->base + SFC_REG_CGE);
> +
> + val = readl(sfc->base + SFC_REG_GLB);
> + u32p_replace_bits(&val, 64 - 1, GLB_THRESHOLD_MASK);
> + writel(val, sfc->base + SFC_REG_GLB);
> +
> + val = readl(sfc->base + SFC_REG_DEV_CONF);
> +
> + /* cpha bit:0 , cpol bit:0 */
> + val &= ~(DEV_CONF_CPHA | DEV_CONF_CPOL);
> + val |= spi->mode & SPI_CPHA ? DEV_CONF_CPHA : 0;
> + val |= spi->mode & SPI_CPOL ? DEV_CONF_CPOL : 0;
> +
> + /* ce_dl bit:1, hold bit:1, wp bit:1 */
> + val |= (DEV_CONF_CE_DL | DEV_CONF_HOLD_DL | DEV_CONF_WP_DL);
> +
> + writel(val, sfc->base + SFC_REG_DEV_CONF);
> +
> + val = readl(sfc->base + SFC_REG_GLB);
> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
> + writel(val, sfc->base + SFC_REG_GLB);
> +
> + rate = clk_get_rate(sfc->clk);
I'd suggest using clk_round_rate() before clk_set_rate() because then
you know what frequency it's going to be, and you don't have to call
clk_get_rate() afterwards.
Cheers,
-Paul
> + val = readl(sfc->base + SFC_REG_DEV_CONF);
> + if (sfc->soc_info->version >= ID_X1600 && rate >= 200000000)
> + u32p_replace_bits(&val, DEV_CONF_SMP_DELAY_180DEG,
> DEV_CONF_SMP_DELAY_MASK);
> + else if (sfc->soc_info->version == ID_X1000 && rate >= 100000000)
> + u32p_replace_bits(&val, DEV_CONF_SMP_DELAY_HALF_CYCLE,
> DEV_CONF_SMP_DELAY_MASK);
> + writel(val, sfc->base + SFC_REG_DEV_CONF);
> +
> + return 0;
> +}
> +
> +static int ingenic_sfc_probe(struct platform_device *pdev)
> +{
> + struct ingenic_sfc *sfc;
> + struct spi_controller *ctlr;
> + int ret;
> +
> + ctlr = spi_alloc_master(&pdev->dev, sizeof(*sfc));
> + if (!ctlr)
> + return -ENOMEM;
> +
> + sfc = spi_controller_get_devdata(ctlr);
> +
> + sfc->soc_info = of_device_get_match_data(&pdev->dev);
> + if (!sfc->soc_info) {
> + dev_err(&pdev->dev, "No of match data provided\n");
> + ret = -ENODEV;
> + goto err_put_master;
> + }
> +
> + sfc->base = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(sfc->base)) {
> + ret = PTR_ERR(sfc->base);
> + goto err_put_master;
> + }
> +
> + sfc->clk = devm_clk_get(&pdev->dev, "sfc");
> + if (IS_ERR(sfc->clk)) {
> + ret = IS_ERR(sfc->clk);
> + goto err_put_master;
> + }
> +
> + ret = clk_prepare_enable(sfc->clk);
> + if (ret)
> + goto err_put_master;
> +
> + sfc->irq = platform_get_irq(pdev, 0);
> + if (sfc->irq < 0) {
> + ret = sfc->irq;
> + goto err_put_master;
> + }
> +
> + sfc->dev = &pdev->dev;
> +
> + platform_set_drvdata(pdev, sfc);
> +
> + ret = devm_request_irq(&pdev->dev, sfc->irq,
> ingenic_sfc_irq_handler, 0,
> + dev_name(&pdev->dev), sfc);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to request irq%d, ret = %d\n",
> sfc->irq, ret);
> + goto err_put_master;
> + }
> +
> + ctlr->bus_num = -1;
> + ctlr->num_chipselect = 1;
> + ctlr->mem_ops = &ingenic_sfc_mem_ops;
> + ctlr->dev.of_node = pdev->dev.of_node;
> + ctlr->setup = ingenic_sfc_setup;
> + ctlr->mode_bits = SPI_CPHA | SPI_CPOL |
> + SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
> + if (sfc->soc_info->version >= ID_X2000)
> + ctlr->mode_bits |= SPI_RX_OCTAL | SPI_TX_OCTAL;
> +
> + ret = devm_spi_register_controller(&pdev->dev, ctlr);
> + if (ret)
> + goto err_put_master;
> +
> + return 0;
> +
> +err_put_master:
> + spi_master_put(ctlr);
> +
> + return ret;
> +}
> +
> +static const struct ingenic_soc_info x1000_soc_info = {
> + .version = ID_X1000,
> +
> + .max_bus_width = 4,
> +
> + .tran_mode_mask = TRAN_CONF_TRAN_MODE_MASK,
> +};
> +
> +static const struct ingenic_soc_info x1600_soc_info = {
> + .version = ID_X1600,
> +
> + .max_bus_width = 4,
> +
> + .tran_mode_mask = TRAN_CONF_TRAN_MODE_MASK,
> +};
> +
> +static const struct ingenic_soc_info x2000_soc_info = {
> + .version = ID_X2000,
> +
> + .max_bus_width = 8,
> +
> + .tran_mode_mask = TRAN_CFG1_TRAN_MODE_MASK,
> +};
> +
> +static const struct of_device_id ingenic_sfc_of_matches[] = {
> + { .compatible = "ingenic,x1000-sfc", .data = &x1000_soc_info },
> + { .compatible = "ingenic,x1600-sfc", .data = &x1600_soc_info },
> + { .compatible = "ingenic,x2000-sfc", .data = &x2000_soc_info },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, ingenic_sfc_of_matches);
> +
> +static struct platform_driver ingenic_sfc_driver = {
> + .driver = {
> + .name = "ingenic-sfc",
> + .of_match_table = ingenic_sfc_of_matches,
> + },
> + .probe = ingenic_sfc_probe,
> +};
> +module_platform_driver(ingenic_sfc_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>");
> +MODULE_DESCRIPTION("Ingenic SoCs SPI Flash Controller Driver");
> --
> 2.7.4
>
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
2022-07-22 20:03 ` Paul Cercueil
@ 2022-07-23 17:26 ` Zhou Yanjie
-1 siblings, 0 replies; 78+ messages in thread
From: Zhou Yanjie @ 2022-07-23 17:26 UTC (permalink / raw)
To: Paul Cercueil
Cc: tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, broonie, robh+dt, krzysztof.kozlowski+dt, linux-mtd,
linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, dongsheng.qiu, aric.pzqi, rick.tyliu,
jinghui.liu, sernia.zhou, reimu
Hi Paul,
On 2022/7/23 上午4:03, Paul Cercueil wrote:
> Hi Zhou,
>
>
> Le sam., juil. 23 2022 at 00:48:30 +0800, 周琰杰 (Zhou Yanjie)
> <zhouyanjie@wanyeetech.com> a écrit :
>> Add SFC support for the X1000 SoC, the X1600 SoC, and the X2000 SoC
>> from Ingenic.
>>
>> Signed-off-by: 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
>> ---
>> drivers/spi/Kconfig | 9 +
>> drivers/spi/Makefile | 1 +
>> drivers/spi/spi-ingenic-sfc.c | 662
>> ++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 672 insertions(+)
>> create mode 100644 drivers/spi/spi-ingenic-sfc.c
>>
>> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
>> index 3b1044e..1077bd3 100644
>> --- a/drivers/spi/Kconfig
>> +++ b/drivers/spi/Kconfig
>> @@ -437,6 +437,15 @@ config SPI_INGENIC
>> To compile this driver as a module, choose M here: the module
>> will be called spi-ingenic.
>>
>> +config SPI_INGENIC_SFC
>> + tristate "Ingenic SoCs SPI Flash Controller"
>> + depends on MACH_INGENIC || COMPILE_TEST
>> + help
>> + This enables support for the Ingenic SoCs SPI flash controller.
>> +
>> + To compile this driver as a module, choose M here: the module
>> + will be called ingenic-sfc.
>> +
>> config SPI_INTEL
>> tristate
>>
>> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
>> index 0f44eb6..f3e42c0 100644
>> --- a/drivers/spi/Makefile
>> +++ b/drivers/spi/Makefile
>> @@ -62,6 +62,7 @@ obj-$(CONFIG_SPI_HISI_SFC_V3XX) +=
>> spi-hisi-sfc-v3xx.o
>> obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o
>> obj-$(CONFIG_SPI_IMX) += spi-imx.o
>> obj-$(CONFIG_SPI_INGENIC) += spi-ingenic.o
>> +obj-$(CONFIG_SPI_INGENIC_SFC) += spi-ingenic-sfc.o
>> obj-$(CONFIG_SPI_INTEL) += spi-intel.o
>> obj-$(CONFIG_SPI_INTEL_PCI) += spi-intel-pci.o
>> obj-$(CONFIG_SPI_INTEL_PLATFORM) += spi-intel-platform.o
>> diff --git a/drivers/spi/spi-ingenic-sfc.c
>> b/drivers/spi/spi-ingenic-sfc.c
>> new file mode 100644
>> index 00000000..a565546
>> --- /dev/null
>> +++ b/drivers/spi/spi-ingenic-sfc.c
>> @@ -0,0 +1,662 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>
> Dual-license driver? That's not what MODULE_LICENSE() says.
>
My fault, forgot to modify MODULE_LICENSE(), will fix it in the next
version.
>> +/*
>> + * Ingenic SoCs SPI Flash Controller Driver
>> + * Copyright (c) 2022 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
>> + */
>> +
>> +#include <linux/bitfield.h>
>> +#include <linux/bitops.h>
>> +#include <linux/clk.h>
>> +#include <linux/completion.h>
>> +#include <linux/dma-mapping.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/iopoll.h>
>> +#include <linux/module.h>
>> +#include <linux/mtd/mtd.h>
>> +#include <linux/of_device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/slab.h>
>> +#include <linux/spi/spi.h>
>> +#include <linux/spi/spi-mem.h>
>> +
>> +/* SFC register offsets */
>> +#define SFC_REG_GLB 0x0000
>> +#define SFC_REG_DEV_CONF 0x0004
>> +#define SFC_REG_DEV_STA_EXP 0x0008
>> +#define SFC_REG_DEV_STA_RT 0x000c
>> +#define SFC_REG_DEV_STA_MSK 0x0010
>> +#define SFC_REG_TRAN_CONF(n) (0x0014 + n * 4)
>> +#define SFC_REG_TRAN_CFG0(n) (0x0014 + n * 4)
>
> You should protect the macro parameter. If you do for instance
> SFC_REG_TRAN_CONF(x + 1) it would resolve to (0x0014 + x + 1 * 4)
> which is not what you'd want.
>
Sure, will fix it.
> Also - looks like SFC_REG_TRAN_CONF() and SFC_REG_TRAN_CFG0() are the
> same thing, that's on purpose?
>
>> +#define SFC_REG_TRAN_LEN 0x002c
>> +#define SFC_REG_DEV_ADDR(n) (0x0030 + n * 4)
>> +#define SFC_REG_DEV_ADDR_PLUS(n) (0x0048 + n * 4)
>> +#define SFC_REG_MEM_ADDR 0x0060
>> +#define SFC_REG_TRIG 0x0064
>> +#define SFC_REG_SR 0x0068
>> +#define SFC_REG_SCR 0x006c
>> +#define SFC_REG_INTC 0x0070
>> +#define SFC_REG_FSM 0x0074
>> +#define SFC_REG_CGE 0x0078
>> +#define SFC_REG_TRAN_CFG1(n) (0x009c + n * 4)
>> +#define SFC_REG_DR 0x1000
>> +
>> +/* bits within the GLB register */
>> +#define GLB_TRAN_DIR_MASK GENMASK(13, 13)
>> +#define GLB_TRAN_DIR_WRITE 0x1
>> +#define GLB_TRAN_DIR_READ 0x0
>
> When it's a single bit - just use BIT().
Sure.
>
>> +#define GLB_THRESHOLD_MASK GENMASK(12, 7)
>> +#define GLB_OP_MODE_MASK GENMASK(6, 6)
>
> Same here, and I see it a few times below as well.
>
Sure.
>> +#define GLB_OP_MODE_DMA 0x1
>> +#define GLB_OP_MODE_SLAVE 0x0
>> +#define GLB_PHASE_NUM_MASK GENMASK(5, 3)
>> +#define GLB_WP_EN BIT(2)
>> +#define GLB_BURST_MD_MASK GENMASK(1, 0)
>> +#define GLB_BURST_MD_INCR32 0x3
>> +#define GLB_BURST_MD_INCR16 0x2
>> +#define GLB_BURST_MD_INCR8 0x1
>> +#define GLB_BURST_MD_INCR4 0x0
>> +
>> +/* bits within the DEV_CONF register */
>> +#define DEV_CONF_SMP_DELAY_MASK GENMASK(20, 16)
>> +#define DEV_CONF_SMP_DELAY_180DEG 0x4
>> +#define DEV_CONF_SMP_DELAY_HALF_CYCLE 0x1
>> +#define DEV_CONF_CMD_TYPE_MASK GENMASK(15, 15)
>> +#define DEV_CONF_CMD_TYPE_16BIT 0x1
>> +#define DEV_CONF_CMD_TYPE_8BIT 0x0
>> +#define DEV_CONF_STA_TYPE_MASK GENMASK(14, 13)
>> +#define DEV_CONF_THOLD_MASK GENMASK(12, 11)
>> +#define DEV_CONF_TSETUP_MASK GENMASK(10, 9)
>> +#define DEV_CONF_TSH_MASK GENMASK(8, 5)
>> +#define DEV_CONF_CPHA BIT(4)
>> +#define DEV_CONF_CPOL BIT(3)
>> +#define DEV_CONF_CE_DL BIT(2)
>> +#define DEV_CONF_HOLD_DL BIT(1)
>> +#define DEV_CONF_WP_DL BIT(0)
>> +
>> +/* bits within the TRAN_CONF(n) register */
>> +#define TRAN_CONF_TRAN_MODE_MASK GENMASK(31, 29)
>> +#define TRAN_CONF_ADDR_WIDTH_MASK GENMASK(28, 26)
>> +#define TRAN_CONF_POLL_EN BIT(25)
>> +#define TRAN_CONF_CMD_EN BIT(24)
>> +#define TRAN_CONF_PHASE_FORMAT_MASK GENMASK(23, 23)
>> +#define TRAN_CONF_DMY_BITS_MASK GENMASK(22, 17)
>> +#define TRAN_CONF_DATA_EN BIT(16)
>> +#define TRAN_CONF_CMD_MASK GENMASK(15, 0)
>> +
>> +/* bits within the TRIG register */
>> +#define TRIG_FLUSH BIT(2)
>> +#define TRIG_STOP BIT(1)
>> +#define TRIG_START BIT(0)
>> +
>> +/* bits within the SR register */
>> +#define SR_FIFO_NUM_MASK GENMASK(22, 16)
>> +#define SR_END BIT(4)
>> +#define SR_TRAN_REQ BIT(3)
>> +#define SR_RECE_REQ BIT(2)
>> +#define SR_OVER BIT(1)
>> +#define SR_UNDER BIT(0)
>> +
>> +/* bits within the SCR register */
>> +#define SCR_CLR_END BIT(4)
>> +#define SCR_CLR_TREQ BIT(3)
>> +#define SCR_CLR_RREQ BIT(2)
>> +#define SCR_CLR_OVER BIT(1)
>> +#define SCR_CLR_UNDER BIT(0)
>> +
>> +/* bits within the INTC register */
>> +#define INTC_MASK_END BIT(4)
>> +#define INTC_MASK_TREQ BIT(3)
>> +#define INTC_MASK_RREQ BIT(2)
>> +#define INTC_MASK_OVER BIT(1)
>> +#define INTC_MASK_UNDER BIT(0)
>> +
>> +/* bits within the TRAN_CFG1(n) register */
>> +#define TRAN_CFG1_TRAN_MODE_MASK GENMASK(7, 4)
>> +
>> +#define TRAN_MODE_STANDARD 0
>> +#define TRAN_MODE_DUAL_DATA 1
>> +#define TRAN_MODE_DUAL_IO 2
>> +#define TRAN_MODE_DUAL_FULL 3
>> +#define TRAN_MODE_QUAD_DATA 5
>> +#define TRAN_MODE_QUAD_IO 6
>> +#define TRAN_MODE_QUAD_FULL 7
>> +#define TRAN_MODE_OCTAL_DATA 9
>> +#define TRAN_MODE_OCTAL_IO 10
>> +#define TRAN_MODE_OCTAL_FULL 11
>> +
>> +#define INGENIC_SFC_FIFO_SIZE (64 * 4)
>> +
>> +#define INGENIC_SFC_TRANSFER_TIMEOUT 1000
>
> Maybe add the unit name in the macro as well -
> INGENIC_SFC_TRANSFER_TIMEOUT_MS.
Sure.
>
>> +
>> +enum ingenic_sfc_version {
>> + ID_X1000,
>> + ID_X1600,
>> + ID_X2000,
>> +};
>> +
>> +struct ingenic_soc_info {
>> + enum ingenic_sfc_version version;
>> +
>> + unsigned int max_bus_width;
>> +
>> + const u32 tran_mode_mask;
>> +};
>> +
>> +struct ingenic_sfc {
>> + const struct ingenic_soc_info *soc_info;
>> +
>> + void __iomem *base;
>> + struct device *dev;
>> + struct clk *clk;
>> + int irq;
>> +
>> + struct completion completion;
>> +};
>> +
>> +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
>> +{
>> + struct ingenic_sfc *sfc = data;
>> +
>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>> +
>> + complete(&sfc->completion);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static int ingenic_sfc_adjust_op_size(struct spi_mem *mem, struct
>> spi_mem_op *op)
>> +{
>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>> +
>> + if (op->data.nbytes > INGENIC_SFC_FIFO_SIZE && !IS_ALIGNED(addr,
>> 4))
>> + op->data.nbytes = INGENIC_SFC_FIFO_SIZE;
>> +
>> + return 0;
>> +}
>> +
>> +static bool ingenic_sfc_supports_op(struct spi_mem *mem, const
>> struct spi_mem_op *op)
>> +{
>> + struct spi_device *spi = mem->spi;
>> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>> +
>> + /* The controller only supports Standard SPI mode, Duall mode,
>> Quad mode and Octal mode. */
>
> Dual*
Oops, will fix it.
>
>> + if (op->cmd.buswidth > sfc->soc_info->max_bus_width ||
>> + op->addr.buswidth > sfc->soc_info->max_bus_width ||
>> + op->dummy.buswidth > sfc->soc_info->max_bus_width ||
>> + op->data.buswidth > sfc->soc_info->max_bus_width)
>> + return false;
>> +
>> + /* Max 32 dummy clock cycles supported */
>> + if (op->dummy.nbytes && op->dummy.nbytes * 8 /
>> op->dummy.buswidth > 32)
>> + return false;
>> +
>> + /* Max rx data length, check controller limits and alignment */
>> + if (op->data.dir == SPI_MEM_DATA_IN &&
>> + op->data.nbytes > INGENIC_SFC_FIFO_SIZE && !IS_ALIGNED(addr,
>> 4))
>
> This does the same check than in ingenic_sfc_adjust_op_size(), maybe
> move it to a new inline function?
>
Sure.
>> + return false;
>> +
>> + /* Max 6 bytes address width supported */
>> + if (op->addr.nbytes > 6)
>> + return false;
>> +
>> + return spi_mem_default_supports_op(mem, op);
>> +}
>> +
>> +static void ingenic_sfc_set_transfer_mode(struct ingenic_sfc *sfc,
>> const struct spi_mem_op *op)
>> +{
>> + int val;
>> +
>> + val = readl(sfc->base + (sfc->soc_info->version >= ID_X1600 ?
>> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
>
> As Krzysztof said - ugh.
>
Will fix it.
> Also, instead of having a "version" enum in your soc_info, why not
> just have a "reg_conf" field that gives you directly the right register?
>
We still need a version to distinguish the SFC before X1600 SoC and
the SFC after X1600 SoC, because in addition to the difference in the
cfg register, another difference is that the SFC before X1600 SoC does
not support address unaligned RX DMA transfer, while SFC in X1600 and
later SoCs can support this feature.
>
>> + val &= ~sfc->soc_info->tran_mode_mask;
>> + if (op->cmd.buswidth == 8)
>> + val |= (TRAN_MODE_OCTAL_FULL <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>
> Looks like you're really trying to reinvent the wheel.
>
> val |= FIELD_PREP(sfc->soc_info->tran_mode_mask, TRAN_MODE_OCTAL_FULL);
>
> using <linux/bitfield.h>.
>
> Also, just define a 'mode' variable and set it in your if/else blocks,
> that would look much better. Then you can set val |= FIELD_PREP(...,
> mode) at the end.
Sure, will change this in the next version.
>
>> + else if (op->cmd.buswidth == 4)
>> + val |= (TRAN_MODE_QUAD_FULL <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->cmd.buswidth == 2)
>> + val |= (TRAN_MODE_DUAL_FULL <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->addr.buswidth == 8)
>> + val |= (TRAN_MODE_OCTAL_IO <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->addr.buswidth == 4)
>> + val |= (TRAN_MODE_QUAD_IO <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->addr.buswidth == 2)
>> + val |= (TRAN_MODE_DUAL_IO <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->data.buswidth == 8)
>> + val |= (TRAN_MODE_OCTAL_DATA <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->data.buswidth == 4)
>> + val |= (TRAN_MODE_QUAD_DATA <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->data.buswidth == 2)
>> + val |= (TRAN_MODE_DUAL_DATA <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else
>> + val |= (TRAN_MODE_STANDARD <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + writel(val, sfc->base + (sfc->soc_info->version >= ID_X1600 ?
>> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
>> +}
>> +
>> +/*
>> + * We only need PIO mode to handle the SPI_MEM_NO_DATA transfers,
>> + * and the unaligned accesses in SPI_MEM_DATA_IN transfers.
>> + */
>> +static void ingenic_sfc_read_rxfifo(struct ingenic_sfc *sfc, u8 *to,
>> unsigned int len)
>> +{
>> + void __iomem *from;
>> +
>> + from = sfc->base + SFC_REG_DR;
>> +
>> + for (; len >= 4; len -= 4, to += 4) {
>> + u32 val = __raw_readl(from);
>> + memcpy(to, &val, 4);
>
> No need to use memcpy for 4 bytes. You can do: put_unaligned(val, (u32
> *)to);
>
Sure.
>> + }
>> +
>> + if (len) {
>> + u32 val = __raw_readl(from);
>> + memcpy(to, &val, len);
>
> Hmm, I'm not sure that is endian-safe. I would prefer if you copied
> byte by byte.
>
Sure.
>> + }
>> +}
>> +
>> +static int ingenic_sfc_exec_op_pio(struct ingenic_sfc *sfc, const
>> struct spi_mem_op *op)
>> +{
>> + int ret, val;
>> +
>> + val = readl(sfc->base + SFC_REG_GLB);
>> + u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
>> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
>> + writel(val, sfc->base + SFC_REG_GLB);
>
> By the way, have you considered using regmap?
>
> It would give you things like regmap_update_bits() for this kind of
> things, and regmap_field() to handle your conf register being at a
> different address across SoCs.
>
It seems that the overhead of using regmap will be greater than using
readl and writel,
the purpose of using readl and writel is to minimize overhead and
maximize performance. :)
>> +
>> + val = TRAN_CONF_CMD_EN | op->cmd.opcode;
>> +
>> + if (op->addr.nbytes > 0) {
>> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK, op->addr.nbytes);
>> +
>> + writel(op->addr.val & 0xffffffff, sfc->base +
>> SFC_REG_DEV_ADDR(0));
>> + writel(op->addr.val >> 32, sfc->base +
>> SFC_REG_DEV_ADDR_PLUS(0));
>> + }
>> +
>> + if (op->dummy.nbytes > 0)
>> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
>> + op->dummy.nbytes * 8 / op->dummy.buswidth);
>> +
>> + if (op->data.nbytes > 0)
>> + val |= TRAN_CONF_DATA_EN;
>> +
>> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
>> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
>> +
>> + ingenic_sfc_set_transfer_mode(sfc, op);
>> +
>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>
> Random 0x1f value here, maybe use a macro?
Sure.
>
>> + writel(~(INTC_MASK_END | INTC_MASK_RREQ), sfc->base +
>> SFC_REG_INTC);
>> +
>> + writel(0, sfc->base + SFC_REG_MEM_ADDR);
>> +
>> + writel(TRIG_FLUSH, sfc->base + SFC_REG_TRIG);
>> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
>> +
>> + ret = wait_for_completion_timeout(&sfc->completion,
>> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
>> + if (!ret) {
>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC
>> device\n", __LINE__);
>> + return -ETIMEDOUT;
>> + }
>> +
>> + ingenic_sfc_read_rxfifo(sfc, op->data.buf.in, op->data.nbytes);
>> + readl_poll_timeout(sfc->base + SFC_REG_SR, val, val & SR_END,
>> 10, 0);
>
> Infinite timeout? Is that very wise?
Will fix it in the next version.
>
>> +
>> + writel(INTC_MASK_END | INTC_MASK_RREQ, sfc->base + SFC_REG_SCR);
>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>> +
>> + return 0;
>> +}
>> +
>> +static int ingenic_sfc_exec_op_dma(struct ingenic_sfc *sfc, const
>> struct spi_mem_op *op)
>> +{
>> + dma_addr_t addr;
>> + int ret, val;
>> +
>> + val = readl(sfc->base + SFC_REG_GLB);
>> + u32p_replace_bits(&val, op->data.dir == SPI_MEM_DATA_IN ?
>> + GLB_TRAN_DIR_READ : GLB_TRAN_DIR_WRITE, GLB_TRAN_DIR_MASK);
>> + u32p_replace_bits(&val, GLB_OP_MODE_DMA, GLB_OP_MODE_MASK);
>> + writel(val, sfc->base + SFC_REG_GLB);
>> +
>> + val = TRAN_CONF_CMD_EN | op->cmd.opcode;
>> +
>> + if (op->addr.nbytes > 0) {
>> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK, op->addr.nbytes);
>> + writel(op->addr.val & 0xffffffff, sfc->base +
>> SFC_REG_DEV_ADDR(0));
>> + writel(op->addr.val >> 32, sfc->base +
>> SFC_REG_DEV_ADDR_PLUS(0));
>> + }
>> +
>> + if (op->dummy.nbytes > 0)
>> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
>> + op->dummy.nbytes * 8 / op->dummy.buswidth);
>> +
>> + if (op->data.nbytes > 0)
>> + val |= TRAN_CONF_DATA_EN;
>
> There's a lot of code duplication here with ingenic_sfc_exec_op_pio().
> A lot can be factorized.
Sure, I will try to do it.
>
>> +
>> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
>> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
>> +
>> + ingenic_sfc_set_transfer_mode(sfc, op);
>> +
>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>> + writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
>> +
>> + switch (op->data.dir) {
>> + case SPI_MEM_DATA_IN:
>> + addr = dma_map_single(sfc->dev, op->data.buf.in,
>> op->data.nbytes, DMA_FROM_DEVICE);
>> + if (dma_mapping_error(sfc->dev, addr)) {
>> + dev_err(sfc->dev, "RX DMA memory not mapped\n");
>> + return -ENOMEM;
>> + }
>> +
>> + writel(addr, sfc->base + SFC_REG_MEM_ADDR);
>> + break;
>> +
>> + case SPI_MEM_DATA_OUT:
>> + addr = dma_map_single(sfc->dev, (void *)op->data.buf.out,
>> + op->data.nbytes, DMA_TO_DEVICE);
>> + if (dma_mapping_error(sfc->dev, addr)) {
>> + dev_err(sfc->dev, "TX DMA memory not mapped\n");
>> + return -ENOMEM;
>> + }
>> +
>> + writel(addr, sfc->base + SFC_REG_MEM_ADDR);
>> + break;
>> +
>> + default:
>> + return -EINVAL;
>> + }
>> +
>> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
>> +
>> + ret = wait_for_completion_timeout(&sfc->completion,
>> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
>> + if (!ret) {
>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC
>> device\n", __LINE__);
>> + return -ETIMEDOUT;
>> + }
>> +
>> + dma_unmap_single(sfc->dev, addr, op->data.nbytes,
>> + op->data.dir == SPI_MEM_DATA_IN ? DMA_FROM_DEVICE :
>> DMA_TO_DEVICE);
>
> Use a small inline function for that too. My personal rule is that ?:
> is fine if the line fits in 80 characters, but if you have to break,
> then you really need to move it somewhere else.
>
Sure.
>> +
>> + writel(INTC_MASK_END, sfc->base + SFC_REG_SCR);
>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>> +
>> + return 0;
>> +}
>> +
>> +static int ingenic_sfc_exec_op(struct spi_mem *mem, const struct
>> spi_mem_op *op)
>> +{
>> + struct spi_device *spi = mem->spi;
>> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>> +
>> + init_completion(&sfc->completion);
>> +
>> + switch (op->data.dir) {
>> + case SPI_MEM_DATA_IN:
>> + if (sfc->soc_info->version >= ID_X1600 || IS_ALIGNED(addr, 4))
>> + break;
>> +
>> + fallthrough;
>> +
>> + case SPI_MEM_NO_DATA:
>> + return ingenic_sfc_exec_op_pio(sfc, op);
>> +
>> + default:
>> + break;
>> + }
>> +
>> + return ingenic_sfc_exec_op_dma(sfc, op);
>> +}
>> +
>> +static int ingenic_sfc_poll_status(struct spi_mem *mem, const struct
>> spi_mem_op *op,
>> + u16 mask, u16 match, unsigned long initial_delay_us,
>> + unsigned long polling_delay_us, unsigned long timeout_ms)
>> +{
>> + struct spi_device *spi = mem->spi;
>> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
>> + int ret, val;
>> +
>> + init_completion(&sfc->completion);
>> +
>> + val = readl(sfc->base + SFC_REG_GLB);
>> + u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
>> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
>> + writel(val, sfc->base + SFC_REG_GLB);
>> +
>> + writel(match, sfc->base + SFC_REG_DEV_STA_EXP);
>> + writel(mask, sfc->base + SFC_REG_DEV_STA_MSK);
>> +
>> + val = TRAN_CONF_POLL_EN | TRAN_CONF_CMD_EN | op->cmd.opcode;
>> +
>> + if (op->addr.nbytes > 0) {
>> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK, op->addr.nbytes);
>> +
>> + writel(op->addr.val & 0xffffffff, sfc->base +
>> SFC_REG_DEV_ADDR(0));
>> + writel(op->addr.val >> 32, sfc->base +
>> SFC_REG_DEV_ADDR_PLUS(0));
>> + }
>> +
>> + if (op->dummy.nbytes > 0)
>> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
>> + op->dummy.nbytes * 8 / op->dummy.buswidth);
>> +
>> + if (op->data.nbytes > 0)
>> + val |= TRAN_CONF_DATA_EN;
>> +
>> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
>> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
>> +
>> + ingenic_sfc_set_transfer_mode(sfc, op);
>> +
>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>> + writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
>> +
>> + writel(0, sfc->base + SFC_REG_MEM_ADDR);
>> +
>> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
>> +
>> + ret = wait_for_completion_timeout(&sfc->completion,
>> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
>> + if (!ret) {
>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC
>> device\n", __LINE__);
>> + return -ETIMEDOUT;
>> + }
>> +
>> + writel(SCR_CLR_END, sfc->base + SFC_REG_SCR);
>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>> +
>> + return 0;
>> +}
>> +
>> +static const struct spi_controller_mem_ops ingenic_sfc_mem_ops = {
>> + .adjust_op_size = ingenic_sfc_adjust_op_size,
>> + .supports_op = ingenic_sfc_supports_op,
>> + .exec_op = ingenic_sfc_exec_op,
>> + .poll_status = ingenic_sfc_poll_status,
>> +};
>> +
>> +static int ingenic_sfc_setup(struct spi_device *spi)
>> +{
>> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
>> + unsigned long rate;
>> + int ret, val;
>> +
>> + if (!spi->max_speed_hz)
>> + return -EINVAL;
>
> Maybe set a sane default?
Sure.
>
>> +
>> + ret = clk_set_rate(sfc->clk, spi->max_speed_hz * 2);
>> + if (ret)
>> + return -EINVAL;
>> +
>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>> + writel(0, sfc->base + SFC_REG_DEV_CONF);
>> + writel(0, sfc->base + SFC_REG_CGE);
>> +
>> + val = readl(sfc->base + SFC_REG_GLB);
>> + u32p_replace_bits(&val, 64 - 1, GLB_THRESHOLD_MASK);
>> + writel(val, sfc->base + SFC_REG_GLB);
>> +
>> + val = readl(sfc->base + SFC_REG_DEV_CONF);
>> +
>> + /* cpha bit:0 , cpol bit:0 */
>> + val &= ~(DEV_CONF_CPHA | DEV_CONF_CPOL);
>> + val |= spi->mode & SPI_CPHA ? DEV_CONF_CPHA : 0;
>> + val |= spi->mode & SPI_CPOL ? DEV_CONF_CPOL : 0;
>> +
>> + /* ce_dl bit:1, hold bit:1, wp bit:1 */
>> + val |= (DEV_CONF_CE_DL | DEV_CONF_HOLD_DL | DEV_CONF_WP_DL);
>> +
>> + writel(val, sfc->base + SFC_REG_DEV_CONF);
>> +
>> + val = readl(sfc->base + SFC_REG_GLB);
>> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
>> + writel(val, sfc->base + SFC_REG_GLB);
>> +
>> + rate = clk_get_rate(sfc->clk);
>
> I'd suggest using clk_round_rate() before clk_set_rate() because then
> you know what frequency it's going to be, and you don't have to call
> clk_get_rate() afterwards.
>
Sure, will try.
Thanks and best regards!
> Cheers,
> -Paul
>
>> + val = readl(sfc->base + SFC_REG_DEV_CONF);
>> + if (sfc->soc_info->version >= ID_X1600 && rate >= 200000000)
>> + u32p_replace_bits(&val, DEV_CONF_SMP_DELAY_180DEG,
>> DEV_CONF_SMP_DELAY_MASK);
>> + else if (sfc->soc_info->version == ID_X1000 && rate >= 100000000)
>> + u32p_replace_bits(&val, DEV_CONF_SMP_DELAY_HALF_CYCLE,
>> DEV_CONF_SMP_DELAY_MASK);
>> + writel(val, sfc->base + SFC_REG_DEV_CONF);
>> +
>> + return 0;
>> +}
>> +
>> +static int ingenic_sfc_probe(struct platform_device *pdev)
>> +{
>> + struct ingenic_sfc *sfc;
>> + struct spi_controller *ctlr;
>> + int ret;
>> +
>> + ctlr = spi_alloc_master(&pdev->dev, sizeof(*sfc));
>> + if (!ctlr)
>> + return -ENOMEM;
>> +
>> + sfc = spi_controller_get_devdata(ctlr);
>> +
>> + sfc->soc_info = of_device_get_match_data(&pdev->dev);
>> + if (!sfc->soc_info) {
>> + dev_err(&pdev->dev, "No of match data provided\n");
>> + ret = -ENODEV;
>> + goto err_put_master;
>> + }
>> +
>> + sfc->base = devm_platform_ioremap_resource(pdev, 0);
>> + if (IS_ERR(sfc->base)) {
>> + ret = PTR_ERR(sfc->base);
>> + goto err_put_master;
>> + }
>> +
>> + sfc->clk = devm_clk_get(&pdev->dev, "sfc");
>> + if (IS_ERR(sfc->clk)) {
>> + ret = IS_ERR(sfc->clk);
>> + goto err_put_master;
>> + }
>> +
>> + ret = clk_prepare_enable(sfc->clk);
>> + if (ret)
>> + goto err_put_master;
>> +
>> + sfc->irq = platform_get_irq(pdev, 0);
>> + if (sfc->irq < 0) {
>> + ret = sfc->irq;
>> + goto err_put_master;
>> + }
>> +
>> + sfc->dev = &pdev->dev;
>> +
>> + platform_set_drvdata(pdev, sfc);
>> +
>> + ret = devm_request_irq(&pdev->dev, sfc->irq,
>> ingenic_sfc_irq_handler, 0,
>> + dev_name(&pdev->dev), sfc);
>> + if (ret) {
>> + dev_err(&pdev->dev, "Failed to request irq%d, ret = %d\n",
>> sfc->irq, ret);
>> + goto err_put_master;
>> + }
>> +
>> + ctlr->bus_num = -1;
>> + ctlr->num_chipselect = 1;
>> + ctlr->mem_ops = &ingenic_sfc_mem_ops;
>> + ctlr->dev.of_node = pdev->dev.of_node;
>> + ctlr->setup = ingenic_sfc_setup;
>> + ctlr->mode_bits = SPI_CPHA | SPI_CPOL |
>> + SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
>> + if (sfc->soc_info->version >= ID_X2000)
>> + ctlr->mode_bits |= SPI_RX_OCTAL | SPI_TX_OCTAL;
>> +
>> + ret = devm_spi_register_controller(&pdev->dev, ctlr);
>> + if (ret)
>> + goto err_put_master;
>> +
>> + return 0;
>> +
>> +err_put_master:
>> + spi_master_put(ctlr);
>> +
>> + return ret;
>> +}
>> +
>> +static const struct ingenic_soc_info x1000_soc_info = {
>> + .version = ID_X1000,
>> +
>> + .max_bus_width = 4,
>> +
>> + .tran_mode_mask = TRAN_CONF_TRAN_MODE_MASK,
>> +};
>> +
>> +static const struct ingenic_soc_info x1600_soc_info = {
>> + .version = ID_X1600,
>> +
>> + .max_bus_width = 4,
>> +
>> + .tran_mode_mask = TRAN_CONF_TRAN_MODE_MASK,
>> +};
>> +
>> +static const struct ingenic_soc_info x2000_soc_info = {
>> + .version = ID_X2000,
>> +
>> + .max_bus_width = 8,
>> +
>> + .tran_mode_mask = TRAN_CFG1_TRAN_MODE_MASK,
>> +};
>> +
>> +static const struct of_device_id ingenic_sfc_of_matches[] = {
>> + { .compatible = "ingenic,x1000-sfc", .data = &x1000_soc_info },
>> + { .compatible = "ingenic,x1600-sfc", .data = &x1600_soc_info },
>> + { .compatible = "ingenic,x2000-sfc", .data = &x2000_soc_info },
>> + { /* sentinel */ }
>> +};
>> +MODULE_DEVICE_TABLE(of, ingenic_sfc_of_matches);
>> +
>> +static struct platform_driver ingenic_sfc_driver = {
>> + .driver = {
>> + .name = "ingenic-sfc",
>> + .of_match_table = ingenic_sfc_of_matches,
>> + },
>> + .probe = ingenic_sfc_probe,
>> +};
>> +module_platform_driver(ingenic_sfc_driver);
>> +
>> +MODULE_LICENSE("GPL");
>> +MODULE_AUTHOR("周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>");
>> +MODULE_DESCRIPTION("Ingenic SoCs SPI Flash Controller Driver");
>> --
>> 2.7.4
>>
>
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
@ 2022-07-23 17:26 ` Zhou Yanjie
0 siblings, 0 replies; 78+ messages in thread
From: Zhou Yanjie @ 2022-07-23 17:26 UTC (permalink / raw)
To: Paul Cercueil
Cc: tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, broonie, robh+dt, krzysztof.kozlowski+dt, linux-mtd,
linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, dongsheng.qiu, aric.pzqi, rick.tyliu,
jinghui.liu, sernia.zhou, reimu
Hi Paul,
On 2022/7/23 上午4:03, Paul Cercueil wrote:
> Hi Zhou,
>
>
> Le sam., juil. 23 2022 at 00:48:30 +0800, 周琰杰 (Zhou Yanjie)
> <zhouyanjie@wanyeetech.com> a écrit :
>> Add SFC support for the X1000 SoC, the X1600 SoC, and the X2000 SoC
>> from Ingenic.
>>
>> Signed-off-by: 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
>> ---
>> drivers/spi/Kconfig | 9 +
>> drivers/spi/Makefile | 1 +
>> drivers/spi/spi-ingenic-sfc.c | 662
>> ++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 672 insertions(+)
>> create mode 100644 drivers/spi/spi-ingenic-sfc.c
>>
>> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
>> index 3b1044e..1077bd3 100644
>> --- a/drivers/spi/Kconfig
>> +++ b/drivers/spi/Kconfig
>> @@ -437,6 +437,15 @@ config SPI_INGENIC
>> To compile this driver as a module, choose M here: the module
>> will be called spi-ingenic.
>>
>> +config SPI_INGENIC_SFC
>> + tristate "Ingenic SoCs SPI Flash Controller"
>> + depends on MACH_INGENIC || COMPILE_TEST
>> + help
>> + This enables support for the Ingenic SoCs SPI flash controller.
>> +
>> + To compile this driver as a module, choose M here: the module
>> + will be called ingenic-sfc.
>> +
>> config SPI_INTEL
>> tristate
>>
>> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
>> index 0f44eb6..f3e42c0 100644
>> --- a/drivers/spi/Makefile
>> +++ b/drivers/spi/Makefile
>> @@ -62,6 +62,7 @@ obj-$(CONFIG_SPI_HISI_SFC_V3XX) +=
>> spi-hisi-sfc-v3xx.o
>> obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o
>> obj-$(CONFIG_SPI_IMX) += spi-imx.o
>> obj-$(CONFIG_SPI_INGENIC) += spi-ingenic.o
>> +obj-$(CONFIG_SPI_INGENIC_SFC) += spi-ingenic-sfc.o
>> obj-$(CONFIG_SPI_INTEL) += spi-intel.o
>> obj-$(CONFIG_SPI_INTEL_PCI) += spi-intel-pci.o
>> obj-$(CONFIG_SPI_INTEL_PLATFORM) += spi-intel-platform.o
>> diff --git a/drivers/spi/spi-ingenic-sfc.c
>> b/drivers/spi/spi-ingenic-sfc.c
>> new file mode 100644
>> index 00000000..a565546
>> --- /dev/null
>> +++ b/drivers/spi/spi-ingenic-sfc.c
>> @@ -0,0 +1,662 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>
> Dual-license driver? That's not what MODULE_LICENSE() says.
>
My fault, forgot to modify MODULE_LICENSE(), will fix it in the next
version.
>> +/*
>> + * Ingenic SoCs SPI Flash Controller Driver
>> + * Copyright (c) 2022 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
>> + */
>> +
>> +#include <linux/bitfield.h>
>> +#include <linux/bitops.h>
>> +#include <linux/clk.h>
>> +#include <linux/completion.h>
>> +#include <linux/dma-mapping.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/iopoll.h>
>> +#include <linux/module.h>
>> +#include <linux/mtd/mtd.h>
>> +#include <linux/of_device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/slab.h>
>> +#include <linux/spi/spi.h>
>> +#include <linux/spi/spi-mem.h>
>> +
>> +/* SFC register offsets */
>> +#define SFC_REG_GLB 0x0000
>> +#define SFC_REG_DEV_CONF 0x0004
>> +#define SFC_REG_DEV_STA_EXP 0x0008
>> +#define SFC_REG_DEV_STA_RT 0x000c
>> +#define SFC_REG_DEV_STA_MSK 0x0010
>> +#define SFC_REG_TRAN_CONF(n) (0x0014 + n * 4)
>> +#define SFC_REG_TRAN_CFG0(n) (0x0014 + n * 4)
>
> You should protect the macro parameter. If you do for instance
> SFC_REG_TRAN_CONF(x + 1) it would resolve to (0x0014 + x + 1 * 4)
> which is not what you'd want.
>
Sure, will fix it.
> Also - looks like SFC_REG_TRAN_CONF() and SFC_REG_TRAN_CFG0() are the
> same thing, that's on purpose?
>
>> +#define SFC_REG_TRAN_LEN 0x002c
>> +#define SFC_REG_DEV_ADDR(n) (0x0030 + n * 4)
>> +#define SFC_REG_DEV_ADDR_PLUS(n) (0x0048 + n * 4)
>> +#define SFC_REG_MEM_ADDR 0x0060
>> +#define SFC_REG_TRIG 0x0064
>> +#define SFC_REG_SR 0x0068
>> +#define SFC_REG_SCR 0x006c
>> +#define SFC_REG_INTC 0x0070
>> +#define SFC_REG_FSM 0x0074
>> +#define SFC_REG_CGE 0x0078
>> +#define SFC_REG_TRAN_CFG1(n) (0x009c + n * 4)
>> +#define SFC_REG_DR 0x1000
>> +
>> +/* bits within the GLB register */
>> +#define GLB_TRAN_DIR_MASK GENMASK(13, 13)
>> +#define GLB_TRAN_DIR_WRITE 0x1
>> +#define GLB_TRAN_DIR_READ 0x0
>
> When it's a single bit - just use BIT().
Sure.
>
>> +#define GLB_THRESHOLD_MASK GENMASK(12, 7)
>> +#define GLB_OP_MODE_MASK GENMASK(6, 6)
>
> Same here, and I see it a few times below as well.
>
Sure.
>> +#define GLB_OP_MODE_DMA 0x1
>> +#define GLB_OP_MODE_SLAVE 0x0
>> +#define GLB_PHASE_NUM_MASK GENMASK(5, 3)
>> +#define GLB_WP_EN BIT(2)
>> +#define GLB_BURST_MD_MASK GENMASK(1, 0)
>> +#define GLB_BURST_MD_INCR32 0x3
>> +#define GLB_BURST_MD_INCR16 0x2
>> +#define GLB_BURST_MD_INCR8 0x1
>> +#define GLB_BURST_MD_INCR4 0x0
>> +
>> +/* bits within the DEV_CONF register */
>> +#define DEV_CONF_SMP_DELAY_MASK GENMASK(20, 16)
>> +#define DEV_CONF_SMP_DELAY_180DEG 0x4
>> +#define DEV_CONF_SMP_DELAY_HALF_CYCLE 0x1
>> +#define DEV_CONF_CMD_TYPE_MASK GENMASK(15, 15)
>> +#define DEV_CONF_CMD_TYPE_16BIT 0x1
>> +#define DEV_CONF_CMD_TYPE_8BIT 0x0
>> +#define DEV_CONF_STA_TYPE_MASK GENMASK(14, 13)
>> +#define DEV_CONF_THOLD_MASK GENMASK(12, 11)
>> +#define DEV_CONF_TSETUP_MASK GENMASK(10, 9)
>> +#define DEV_CONF_TSH_MASK GENMASK(8, 5)
>> +#define DEV_CONF_CPHA BIT(4)
>> +#define DEV_CONF_CPOL BIT(3)
>> +#define DEV_CONF_CE_DL BIT(2)
>> +#define DEV_CONF_HOLD_DL BIT(1)
>> +#define DEV_CONF_WP_DL BIT(0)
>> +
>> +/* bits within the TRAN_CONF(n) register */
>> +#define TRAN_CONF_TRAN_MODE_MASK GENMASK(31, 29)
>> +#define TRAN_CONF_ADDR_WIDTH_MASK GENMASK(28, 26)
>> +#define TRAN_CONF_POLL_EN BIT(25)
>> +#define TRAN_CONF_CMD_EN BIT(24)
>> +#define TRAN_CONF_PHASE_FORMAT_MASK GENMASK(23, 23)
>> +#define TRAN_CONF_DMY_BITS_MASK GENMASK(22, 17)
>> +#define TRAN_CONF_DATA_EN BIT(16)
>> +#define TRAN_CONF_CMD_MASK GENMASK(15, 0)
>> +
>> +/* bits within the TRIG register */
>> +#define TRIG_FLUSH BIT(2)
>> +#define TRIG_STOP BIT(1)
>> +#define TRIG_START BIT(0)
>> +
>> +/* bits within the SR register */
>> +#define SR_FIFO_NUM_MASK GENMASK(22, 16)
>> +#define SR_END BIT(4)
>> +#define SR_TRAN_REQ BIT(3)
>> +#define SR_RECE_REQ BIT(2)
>> +#define SR_OVER BIT(1)
>> +#define SR_UNDER BIT(0)
>> +
>> +/* bits within the SCR register */
>> +#define SCR_CLR_END BIT(4)
>> +#define SCR_CLR_TREQ BIT(3)
>> +#define SCR_CLR_RREQ BIT(2)
>> +#define SCR_CLR_OVER BIT(1)
>> +#define SCR_CLR_UNDER BIT(0)
>> +
>> +/* bits within the INTC register */
>> +#define INTC_MASK_END BIT(4)
>> +#define INTC_MASK_TREQ BIT(3)
>> +#define INTC_MASK_RREQ BIT(2)
>> +#define INTC_MASK_OVER BIT(1)
>> +#define INTC_MASK_UNDER BIT(0)
>> +
>> +/* bits within the TRAN_CFG1(n) register */
>> +#define TRAN_CFG1_TRAN_MODE_MASK GENMASK(7, 4)
>> +
>> +#define TRAN_MODE_STANDARD 0
>> +#define TRAN_MODE_DUAL_DATA 1
>> +#define TRAN_MODE_DUAL_IO 2
>> +#define TRAN_MODE_DUAL_FULL 3
>> +#define TRAN_MODE_QUAD_DATA 5
>> +#define TRAN_MODE_QUAD_IO 6
>> +#define TRAN_MODE_QUAD_FULL 7
>> +#define TRAN_MODE_OCTAL_DATA 9
>> +#define TRAN_MODE_OCTAL_IO 10
>> +#define TRAN_MODE_OCTAL_FULL 11
>> +
>> +#define INGENIC_SFC_FIFO_SIZE (64 * 4)
>> +
>> +#define INGENIC_SFC_TRANSFER_TIMEOUT 1000
>
> Maybe add the unit name in the macro as well -
> INGENIC_SFC_TRANSFER_TIMEOUT_MS.
Sure.
>
>> +
>> +enum ingenic_sfc_version {
>> + ID_X1000,
>> + ID_X1600,
>> + ID_X2000,
>> +};
>> +
>> +struct ingenic_soc_info {
>> + enum ingenic_sfc_version version;
>> +
>> + unsigned int max_bus_width;
>> +
>> + const u32 tran_mode_mask;
>> +};
>> +
>> +struct ingenic_sfc {
>> + const struct ingenic_soc_info *soc_info;
>> +
>> + void __iomem *base;
>> + struct device *dev;
>> + struct clk *clk;
>> + int irq;
>> +
>> + struct completion completion;
>> +};
>> +
>> +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
>> +{
>> + struct ingenic_sfc *sfc = data;
>> +
>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>> +
>> + complete(&sfc->completion);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static int ingenic_sfc_adjust_op_size(struct spi_mem *mem, struct
>> spi_mem_op *op)
>> +{
>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>> +
>> + if (op->data.nbytes > INGENIC_SFC_FIFO_SIZE && !IS_ALIGNED(addr,
>> 4))
>> + op->data.nbytes = INGENIC_SFC_FIFO_SIZE;
>> +
>> + return 0;
>> +}
>> +
>> +static bool ingenic_sfc_supports_op(struct spi_mem *mem, const
>> struct spi_mem_op *op)
>> +{
>> + struct spi_device *spi = mem->spi;
>> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>> +
>> + /* The controller only supports Standard SPI mode, Duall mode,
>> Quad mode and Octal mode. */
>
> Dual*
Oops, will fix it.
>
>> + if (op->cmd.buswidth > sfc->soc_info->max_bus_width ||
>> + op->addr.buswidth > sfc->soc_info->max_bus_width ||
>> + op->dummy.buswidth > sfc->soc_info->max_bus_width ||
>> + op->data.buswidth > sfc->soc_info->max_bus_width)
>> + return false;
>> +
>> + /* Max 32 dummy clock cycles supported */
>> + if (op->dummy.nbytes && op->dummy.nbytes * 8 /
>> op->dummy.buswidth > 32)
>> + return false;
>> +
>> + /* Max rx data length, check controller limits and alignment */
>> + if (op->data.dir == SPI_MEM_DATA_IN &&
>> + op->data.nbytes > INGENIC_SFC_FIFO_SIZE && !IS_ALIGNED(addr,
>> 4))
>
> This does the same check than in ingenic_sfc_adjust_op_size(), maybe
> move it to a new inline function?
>
Sure.
>> + return false;
>> +
>> + /* Max 6 bytes address width supported */
>> + if (op->addr.nbytes > 6)
>> + return false;
>> +
>> + return spi_mem_default_supports_op(mem, op);
>> +}
>> +
>> +static void ingenic_sfc_set_transfer_mode(struct ingenic_sfc *sfc,
>> const struct spi_mem_op *op)
>> +{
>> + int val;
>> +
>> + val = readl(sfc->base + (sfc->soc_info->version >= ID_X1600 ?
>> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
>
> As Krzysztof said - ugh.
>
Will fix it.
> Also, instead of having a "version" enum in your soc_info, why not
> just have a "reg_conf" field that gives you directly the right register?
>
We still need a version to distinguish the SFC before X1600 SoC and
the SFC after X1600 SoC, because in addition to the difference in the
cfg register, another difference is that the SFC before X1600 SoC does
not support address unaligned RX DMA transfer, while SFC in X1600 and
later SoCs can support this feature.
>
>> + val &= ~sfc->soc_info->tran_mode_mask;
>> + if (op->cmd.buswidth == 8)
>> + val |= (TRAN_MODE_OCTAL_FULL <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>
> Looks like you're really trying to reinvent the wheel.
>
> val |= FIELD_PREP(sfc->soc_info->tran_mode_mask, TRAN_MODE_OCTAL_FULL);
>
> using <linux/bitfield.h>.
>
> Also, just define a 'mode' variable and set it in your if/else blocks,
> that would look much better. Then you can set val |= FIELD_PREP(...,
> mode) at the end.
Sure, will change this in the next version.
>
>> + else if (op->cmd.buswidth == 4)
>> + val |= (TRAN_MODE_QUAD_FULL <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->cmd.buswidth == 2)
>> + val |= (TRAN_MODE_DUAL_FULL <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->addr.buswidth == 8)
>> + val |= (TRAN_MODE_OCTAL_IO <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->addr.buswidth == 4)
>> + val |= (TRAN_MODE_QUAD_IO <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->addr.buswidth == 2)
>> + val |= (TRAN_MODE_DUAL_IO <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->data.buswidth == 8)
>> + val |= (TRAN_MODE_OCTAL_DATA <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->data.buswidth == 4)
>> + val |= (TRAN_MODE_QUAD_DATA <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else if (op->data.buswidth == 2)
>> + val |= (TRAN_MODE_DUAL_DATA <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + else
>> + val |= (TRAN_MODE_STANDARD <<
>> (ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>> + sfc->soc_info->tran_mode_mask;
>> + writel(val, sfc->base + (sfc->soc_info->version >= ID_X1600 ?
>> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
>> +}
>> +
>> +/*
>> + * We only need PIO mode to handle the SPI_MEM_NO_DATA transfers,
>> + * and the unaligned accesses in SPI_MEM_DATA_IN transfers.
>> + */
>> +static void ingenic_sfc_read_rxfifo(struct ingenic_sfc *sfc, u8 *to,
>> unsigned int len)
>> +{
>> + void __iomem *from;
>> +
>> + from = sfc->base + SFC_REG_DR;
>> +
>> + for (; len >= 4; len -= 4, to += 4) {
>> + u32 val = __raw_readl(from);
>> + memcpy(to, &val, 4);
>
> No need to use memcpy for 4 bytes. You can do: put_unaligned(val, (u32
> *)to);
>
Sure.
>> + }
>> +
>> + if (len) {
>> + u32 val = __raw_readl(from);
>> + memcpy(to, &val, len);
>
> Hmm, I'm not sure that is endian-safe. I would prefer if you copied
> byte by byte.
>
Sure.
>> + }
>> +}
>> +
>> +static int ingenic_sfc_exec_op_pio(struct ingenic_sfc *sfc, const
>> struct spi_mem_op *op)
>> +{
>> + int ret, val;
>> +
>> + val = readl(sfc->base + SFC_REG_GLB);
>> + u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
>> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
>> + writel(val, sfc->base + SFC_REG_GLB);
>
> By the way, have you considered using regmap?
>
> It would give you things like regmap_update_bits() for this kind of
> things, and regmap_field() to handle your conf register being at a
> different address across SoCs.
>
It seems that the overhead of using regmap will be greater than using
readl and writel,
the purpose of using readl and writel is to minimize overhead and
maximize performance. :)
>> +
>> + val = TRAN_CONF_CMD_EN | op->cmd.opcode;
>> +
>> + if (op->addr.nbytes > 0) {
>> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK, op->addr.nbytes);
>> +
>> + writel(op->addr.val & 0xffffffff, sfc->base +
>> SFC_REG_DEV_ADDR(0));
>> + writel(op->addr.val >> 32, sfc->base +
>> SFC_REG_DEV_ADDR_PLUS(0));
>> + }
>> +
>> + if (op->dummy.nbytes > 0)
>> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
>> + op->dummy.nbytes * 8 / op->dummy.buswidth);
>> +
>> + if (op->data.nbytes > 0)
>> + val |= TRAN_CONF_DATA_EN;
>> +
>> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
>> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
>> +
>> + ingenic_sfc_set_transfer_mode(sfc, op);
>> +
>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>
> Random 0x1f value here, maybe use a macro?
Sure.
>
>> + writel(~(INTC_MASK_END | INTC_MASK_RREQ), sfc->base +
>> SFC_REG_INTC);
>> +
>> + writel(0, sfc->base + SFC_REG_MEM_ADDR);
>> +
>> + writel(TRIG_FLUSH, sfc->base + SFC_REG_TRIG);
>> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
>> +
>> + ret = wait_for_completion_timeout(&sfc->completion,
>> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
>> + if (!ret) {
>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC
>> device\n", __LINE__);
>> + return -ETIMEDOUT;
>> + }
>> +
>> + ingenic_sfc_read_rxfifo(sfc, op->data.buf.in, op->data.nbytes);
>> + readl_poll_timeout(sfc->base + SFC_REG_SR, val, val & SR_END,
>> 10, 0);
>
> Infinite timeout? Is that very wise?
Will fix it in the next version.
>
>> +
>> + writel(INTC_MASK_END | INTC_MASK_RREQ, sfc->base + SFC_REG_SCR);
>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>> +
>> + return 0;
>> +}
>> +
>> +static int ingenic_sfc_exec_op_dma(struct ingenic_sfc *sfc, const
>> struct spi_mem_op *op)
>> +{
>> + dma_addr_t addr;
>> + int ret, val;
>> +
>> + val = readl(sfc->base + SFC_REG_GLB);
>> + u32p_replace_bits(&val, op->data.dir == SPI_MEM_DATA_IN ?
>> + GLB_TRAN_DIR_READ : GLB_TRAN_DIR_WRITE, GLB_TRAN_DIR_MASK);
>> + u32p_replace_bits(&val, GLB_OP_MODE_DMA, GLB_OP_MODE_MASK);
>> + writel(val, sfc->base + SFC_REG_GLB);
>> +
>> + val = TRAN_CONF_CMD_EN | op->cmd.opcode;
>> +
>> + if (op->addr.nbytes > 0) {
>> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK, op->addr.nbytes);
>> + writel(op->addr.val & 0xffffffff, sfc->base +
>> SFC_REG_DEV_ADDR(0));
>> + writel(op->addr.val >> 32, sfc->base +
>> SFC_REG_DEV_ADDR_PLUS(0));
>> + }
>> +
>> + if (op->dummy.nbytes > 0)
>> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
>> + op->dummy.nbytes * 8 / op->dummy.buswidth);
>> +
>> + if (op->data.nbytes > 0)
>> + val |= TRAN_CONF_DATA_EN;
>
> There's a lot of code duplication here with ingenic_sfc_exec_op_pio().
> A lot can be factorized.
Sure, I will try to do it.
>
>> +
>> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
>> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
>> +
>> + ingenic_sfc_set_transfer_mode(sfc, op);
>> +
>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>> + writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
>> +
>> + switch (op->data.dir) {
>> + case SPI_MEM_DATA_IN:
>> + addr = dma_map_single(sfc->dev, op->data.buf.in,
>> op->data.nbytes, DMA_FROM_DEVICE);
>> + if (dma_mapping_error(sfc->dev, addr)) {
>> + dev_err(sfc->dev, "RX DMA memory not mapped\n");
>> + return -ENOMEM;
>> + }
>> +
>> + writel(addr, sfc->base + SFC_REG_MEM_ADDR);
>> + break;
>> +
>> + case SPI_MEM_DATA_OUT:
>> + addr = dma_map_single(sfc->dev, (void *)op->data.buf.out,
>> + op->data.nbytes, DMA_TO_DEVICE);
>> + if (dma_mapping_error(sfc->dev, addr)) {
>> + dev_err(sfc->dev, "TX DMA memory not mapped\n");
>> + return -ENOMEM;
>> + }
>> +
>> + writel(addr, sfc->base + SFC_REG_MEM_ADDR);
>> + break;
>> +
>> + default:
>> + return -EINVAL;
>> + }
>> +
>> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
>> +
>> + ret = wait_for_completion_timeout(&sfc->completion,
>> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
>> + if (!ret) {
>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC
>> device\n", __LINE__);
>> + return -ETIMEDOUT;
>> + }
>> +
>> + dma_unmap_single(sfc->dev, addr, op->data.nbytes,
>> + op->data.dir == SPI_MEM_DATA_IN ? DMA_FROM_DEVICE :
>> DMA_TO_DEVICE);
>
> Use a small inline function for that too. My personal rule is that ?:
> is fine if the line fits in 80 characters, but if you have to break,
> then you really need to move it somewhere else.
>
Sure.
>> +
>> + writel(INTC_MASK_END, sfc->base + SFC_REG_SCR);
>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>> +
>> + return 0;
>> +}
>> +
>> +static int ingenic_sfc_exec_op(struct spi_mem *mem, const struct
>> spi_mem_op *op)
>> +{
>> + struct spi_device *spi = mem->spi;
>> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>> +
>> + init_completion(&sfc->completion);
>> +
>> + switch (op->data.dir) {
>> + case SPI_MEM_DATA_IN:
>> + if (sfc->soc_info->version >= ID_X1600 || IS_ALIGNED(addr, 4))
>> + break;
>> +
>> + fallthrough;
>> +
>> + case SPI_MEM_NO_DATA:
>> + return ingenic_sfc_exec_op_pio(sfc, op);
>> +
>> + default:
>> + break;
>> + }
>> +
>> + return ingenic_sfc_exec_op_dma(sfc, op);
>> +}
>> +
>> +static int ingenic_sfc_poll_status(struct spi_mem *mem, const struct
>> spi_mem_op *op,
>> + u16 mask, u16 match, unsigned long initial_delay_us,
>> + unsigned long polling_delay_us, unsigned long timeout_ms)
>> +{
>> + struct spi_device *spi = mem->spi;
>> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
>> + int ret, val;
>> +
>> + init_completion(&sfc->completion);
>> +
>> + val = readl(sfc->base + SFC_REG_GLB);
>> + u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
>> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
>> + writel(val, sfc->base + SFC_REG_GLB);
>> +
>> + writel(match, sfc->base + SFC_REG_DEV_STA_EXP);
>> + writel(mask, sfc->base + SFC_REG_DEV_STA_MSK);
>> +
>> + val = TRAN_CONF_POLL_EN | TRAN_CONF_CMD_EN | op->cmd.opcode;
>> +
>> + if (op->addr.nbytes > 0) {
>> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK, op->addr.nbytes);
>> +
>> + writel(op->addr.val & 0xffffffff, sfc->base +
>> SFC_REG_DEV_ADDR(0));
>> + writel(op->addr.val >> 32, sfc->base +
>> SFC_REG_DEV_ADDR_PLUS(0));
>> + }
>> +
>> + if (op->dummy.nbytes > 0)
>> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
>> + op->dummy.nbytes * 8 / op->dummy.buswidth);
>> +
>> + if (op->data.nbytes > 0)
>> + val |= TRAN_CONF_DATA_EN;
>> +
>> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
>> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
>> +
>> + ingenic_sfc_set_transfer_mode(sfc, op);
>> +
>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>> + writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
>> +
>> + writel(0, sfc->base + SFC_REG_MEM_ADDR);
>> +
>> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
>> +
>> + ret = wait_for_completion_timeout(&sfc->completion,
>> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
>> + if (!ret) {
>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC
>> device\n", __LINE__);
>> + return -ETIMEDOUT;
>> + }
>> +
>> + writel(SCR_CLR_END, sfc->base + SFC_REG_SCR);
>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>> +
>> + return 0;
>> +}
>> +
>> +static const struct spi_controller_mem_ops ingenic_sfc_mem_ops = {
>> + .adjust_op_size = ingenic_sfc_adjust_op_size,
>> + .supports_op = ingenic_sfc_supports_op,
>> + .exec_op = ingenic_sfc_exec_op,
>> + .poll_status = ingenic_sfc_poll_status,
>> +};
>> +
>> +static int ingenic_sfc_setup(struct spi_device *spi)
>> +{
>> + struct ingenic_sfc *sfc = spi_controller_get_devdata(spi->master);
>> + unsigned long rate;
>> + int ret, val;
>> +
>> + if (!spi->max_speed_hz)
>> + return -EINVAL;
>
> Maybe set a sane default?
Sure.
>
>> +
>> + ret = clk_set_rate(sfc->clk, spi->max_speed_hz * 2);
>> + if (ret)
>> + return -EINVAL;
>> +
>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>> + writel(0, sfc->base + SFC_REG_DEV_CONF);
>> + writel(0, sfc->base + SFC_REG_CGE);
>> +
>> + val = readl(sfc->base + SFC_REG_GLB);
>> + u32p_replace_bits(&val, 64 - 1, GLB_THRESHOLD_MASK);
>> + writel(val, sfc->base + SFC_REG_GLB);
>> +
>> + val = readl(sfc->base + SFC_REG_DEV_CONF);
>> +
>> + /* cpha bit:0 , cpol bit:0 */
>> + val &= ~(DEV_CONF_CPHA | DEV_CONF_CPOL);
>> + val |= spi->mode & SPI_CPHA ? DEV_CONF_CPHA : 0;
>> + val |= spi->mode & SPI_CPOL ? DEV_CONF_CPOL : 0;
>> +
>> + /* ce_dl bit:1, hold bit:1, wp bit:1 */
>> + val |= (DEV_CONF_CE_DL | DEV_CONF_HOLD_DL | DEV_CONF_WP_DL);
>> +
>> + writel(val, sfc->base + SFC_REG_DEV_CONF);
>> +
>> + val = readl(sfc->base + SFC_REG_GLB);
>> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
>> + writel(val, sfc->base + SFC_REG_GLB);
>> +
>> + rate = clk_get_rate(sfc->clk);
>
> I'd suggest using clk_round_rate() before clk_set_rate() because then
> you know what frequency it's going to be, and you don't have to call
> clk_get_rate() afterwards.
>
Sure, will try.
Thanks and best regards!
> Cheers,
> -Paul
>
>> + val = readl(sfc->base + SFC_REG_DEV_CONF);
>> + if (sfc->soc_info->version >= ID_X1600 && rate >= 200000000)
>> + u32p_replace_bits(&val, DEV_CONF_SMP_DELAY_180DEG,
>> DEV_CONF_SMP_DELAY_MASK);
>> + else if (sfc->soc_info->version == ID_X1000 && rate >= 100000000)
>> + u32p_replace_bits(&val, DEV_CONF_SMP_DELAY_HALF_CYCLE,
>> DEV_CONF_SMP_DELAY_MASK);
>> + writel(val, sfc->base + SFC_REG_DEV_CONF);
>> +
>> + return 0;
>> +}
>> +
>> +static int ingenic_sfc_probe(struct platform_device *pdev)
>> +{
>> + struct ingenic_sfc *sfc;
>> + struct spi_controller *ctlr;
>> + int ret;
>> +
>> + ctlr = spi_alloc_master(&pdev->dev, sizeof(*sfc));
>> + if (!ctlr)
>> + return -ENOMEM;
>> +
>> + sfc = spi_controller_get_devdata(ctlr);
>> +
>> + sfc->soc_info = of_device_get_match_data(&pdev->dev);
>> + if (!sfc->soc_info) {
>> + dev_err(&pdev->dev, "No of match data provided\n");
>> + ret = -ENODEV;
>> + goto err_put_master;
>> + }
>> +
>> + sfc->base = devm_platform_ioremap_resource(pdev, 0);
>> + if (IS_ERR(sfc->base)) {
>> + ret = PTR_ERR(sfc->base);
>> + goto err_put_master;
>> + }
>> +
>> + sfc->clk = devm_clk_get(&pdev->dev, "sfc");
>> + if (IS_ERR(sfc->clk)) {
>> + ret = IS_ERR(sfc->clk);
>> + goto err_put_master;
>> + }
>> +
>> + ret = clk_prepare_enable(sfc->clk);
>> + if (ret)
>> + goto err_put_master;
>> +
>> + sfc->irq = platform_get_irq(pdev, 0);
>> + if (sfc->irq < 0) {
>> + ret = sfc->irq;
>> + goto err_put_master;
>> + }
>> +
>> + sfc->dev = &pdev->dev;
>> +
>> + platform_set_drvdata(pdev, sfc);
>> +
>> + ret = devm_request_irq(&pdev->dev, sfc->irq,
>> ingenic_sfc_irq_handler, 0,
>> + dev_name(&pdev->dev), sfc);
>> + if (ret) {
>> + dev_err(&pdev->dev, "Failed to request irq%d, ret = %d\n",
>> sfc->irq, ret);
>> + goto err_put_master;
>> + }
>> +
>> + ctlr->bus_num = -1;
>> + ctlr->num_chipselect = 1;
>> + ctlr->mem_ops = &ingenic_sfc_mem_ops;
>> + ctlr->dev.of_node = pdev->dev.of_node;
>> + ctlr->setup = ingenic_sfc_setup;
>> + ctlr->mode_bits = SPI_CPHA | SPI_CPOL |
>> + SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
>> + if (sfc->soc_info->version >= ID_X2000)
>> + ctlr->mode_bits |= SPI_RX_OCTAL | SPI_TX_OCTAL;
>> +
>> + ret = devm_spi_register_controller(&pdev->dev, ctlr);
>> + if (ret)
>> + goto err_put_master;
>> +
>> + return 0;
>> +
>> +err_put_master:
>> + spi_master_put(ctlr);
>> +
>> + return ret;
>> +}
>> +
>> +static const struct ingenic_soc_info x1000_soc_info = {
>> + .version = ID_X1000,
>> +
>> + .max_bus_width = 4,
>> +
>> + .tran_mode_mask = TRAN_CONF_TRAN_MODE_MASK,
>> +};
>> +
>> +static const struct ingenic_soc_info x1600_soc_info = {
>> + .version = ID_X1600,
>> +
>> + .max_bus_width = 4,
>> +
>> + .tran_mode_mask = TRAN_CONF_TRAN_MODE_MASK,
>> +};
>> +
>> +static const struct ingenic_soc_info x2000_soc_info = {
>> + .version = ID_X2000,
>> +
>> + .max_bus_width = 8,
>> +
>> + .tran_mode_mask = TRAN_CFG1_TRAN_MODE_MASK,
>> +};
>> +
>> +static const struct of_device_id ingenic_sfc_of_matches[] = {
>> + { .compatible = "ingenic,x1000-sfc", .data = &x1000_soc_info },
>> + { .compatible = "ingenic,x1600-sfc", .data = &x1600_soc_info },
>> + { .compatible = "ingenic,x2000-sfc", .data = &x2000_soc_info },
>> + { /* sentinel */ }
>> +};
>> +MODULE_DEVICE_TABLE(of, ingenic_sfc_of_matches);
>> +
>> +static struct platform_driver ingenic_sfc_driver = {
>> + .driver = {
>> + .name = "ingenic-sfc",
>> + .of_match_table = ingenic_sfc_of_matches,
>> + },
>> + .probe = ingenic_sfc_probe,
>> +};
>> +module_platform_driver(ingenic_sfc_driver);
>> +
>> +MODULE_LICENSE("GPL");
>> +MODULE_AUTHOR("周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>");
>> +MODULE_DESCRIPTION("Ingenic SoCs SPI Flash Controller Driver");
>> --
>> 2.7.4
>>
>
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
2022-07-23 17:26 ` Zhou Yanjie
@ 2022-07-23 20:24 ` Paul Cercueil
-1 siblings, 0 replies; 78+ messages in thread
From: Paul Cercueil @ 2022-07-23 20:24 UTC (permalink / raw)
To: Zhou Yanjie
Cc: tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, broonie, robh+dt, krzysztof.kozlowski+dt, linux-mtd,
linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, dongsheng.qiu, aric.pzqi, rick.tyliu,
jinghui.liu, sernia.zhou, reimu
Hi Zhou,
Le dim., juil. 24 2022 at 01:26:56 +0800, Zhou Yanjie
<zhouyanjie@wanyeetech.com> a écrit :
> Hi Paul,
>
> On 2022/7/23 上午4:03, Paul Cercueil wrote:
>> Hi Zhou,
>>
>>
>> Le sam., juil. 23 2022 at 00:48:30 +0800, 周琰杰 (Zhou Yanjie)
>> \x7f<zhouyanjie@wanyeetech.com> a écrit :
>>> Add SFC support for the X1000 SoC, the X1600 SoC, and the X2000 SoC
>>> from Ingenic.
>>>
>>> Signed-off-by: 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
>>> ---
>>> drivers/spi/Kconfig | 9 +
>>> drivers/spi/Makefile | 1 +
>>> drivers/spi/spi-ingenic-sfc.c | 662
>>> \x7f\x7f++++++++++++++++++++++++++++++++++++++++++
>>> 3 files changed, 672 insertions(+)
>>> create mode 100644 drivers/spi/spi-ingenic-sfc.c
>>>
>>> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
>>> index 3b1044e..1077bd3 100644
>>> --- a/drivers/spi/Kconfig
>>> +++ b/drivers/spi/Kconfig
>>> @@ -437,6 +437,15 @@ config SPI_INGENIC
>>> To compile this driver as a module, choose M here: the module
>>> will be called spi-ingenic.
>>>
>>> +config SPI_INGENIC_SFC
>>> + tristate "Ingenic SoCs SPI Flash Controller"
>>> + depends on MACH_INGENIC || COMPILE_TEST
>>> + help
>>> + This enables support for the Ingenic SoCs SPI flash
>>> controller.
>>> +
>>> + To compile this driver as a module, choose M here: the module
>>> + will be called ingenic-sfc.
>>> +
>>> config SPI_INTEL
>>> tristate
>>>
>>> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
>>> index 0f44eb6..f3e42c0 100644
>>> --- a/drivers/spi/Makefile
>>> +++ b/drivers/spi/Makefile
>>> @@ -62,6 +62,7 @@ obj-$(CONFIG_SPI_HISI_SFC_V3XX) +=
>>> \x7f\x7fspi-hisi-sfc-v3xx.o
>>> obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o
>>> obj-$(CONFIG_SPI_IMX) += spi-imx.o
>>> obj-$(CONFIG_SPI_INGENIC) += spi-ingenic.o
>>> +obj-$(CONFIG_SPI_INGENIC_SFC) += spi-ingenic-sfc.o
>>> obj-$(CONFIG_SPI_INTEL) += spi-intel.o
>>> obj-$(CONFIG_SPI_INTEL_PCI) += spi-intel-pci.o
>>> obj-$(CONFIG_SPI_INTEL_PLATFORM) += spi-intel-platform.o
>>> diff --git a/drivers/spi/spi-ingenic-sfc.c
>>> \x7f\x7fb/drivers/spi/spi-ingenic-sfc.c
>>> new file mode 100644
>>> index 00000000..a565546
>>> --- /dev/null
>>> +++ b/drivers/spi/spi-ingenic-sfc.c
>>> @@ -0,0 +1,662 @@
>>> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>
>> Dual-license driver? That's not what MODULE_LICENSE() says.
>>
>
> My fault, forgot to modify MODULE_LICENSE(), will fix it in the next
> version.
>
>
>>> +/*
>>> + * Ingenic SoCs SPI Flash Controller Driver
>>> + * Copyright (c) 2022 周琰杰 (Zhou Yanjie)
>>> <zhouyanjie@wanyeetech.com>
>>> + */
>>> +
>>> +#include <linux/bitfield.h>
>>> +#include <linux/bitops.h>
>>> +#include <linux/clk.h>
>>> +#include <linux/completion.h>
>>> +#include <linux/dma-mapping.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/iopoll.h>
>>> +#include <linux/module.h>
>>> +#include <linux/mtd/mtd.h>
>>> +#include <linux/of_device.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/spi/spi.h>
>>> +#include <linux/spi/spi-mem.h>
>>> +
>>> +/* SFC register offsets */
>>> +#define SFC_REG_GLB 0x0000
>>> +#define SFC_REG_DEV_CONF 0x0004
>>> +#define SFC_REG_DEV_STA_EXP 0x0008
>>> +#define SFC_REG_DEV_STA_RT 0x000c
>>> +#define SFC_REG_DEV_STA_MSK 0x0010
>>> +#define SFC_REG_TRAN_CONF(n) (0x0014 + n * 4)
>>> +#define SFC_REG_TRAN_CFG0(n) (0x0014 + n * 4)
>>
>> You should protect the macro parameter. If you do for instance
>> \x7fSFC_REG_TRAN_CONF(x + 1) it would resolve to (0x0014 + x + 1 * 4)
>> \x7fwhich is not what you'd want.
>>
>
> Sure, will fix it.
>
>
>> Also - looks like SFC_REG_TRAN_CONF() and SFC_REG_TRAN_CFG0() are
>> the \x7fsame thing, that's on purpose?
>>
>>> +#define SFC_REG_TRAN_LEN 0x002c
>>> +#define SFC_REG_DEV_ADDR(n) (0x0030 + n * 4)
>>> +#define SFC_REG_DEV_ADDR_PLUS(n) (0x0048 + n * 4)
>>> +#define SFC_REG_MEM_ADDR 0x0060
>>> +#define SFC_REG_TRIG 0x0064
>>> +#define SFC_REG_SR 0x0068
>>> +#define SFC_REG_SCR 0x006c
>>> +#define SFC_REG_INTC 0x0070
>>> +#define SFC_REG_FSM 0x0074
>>> +#define SFC_REG_CGE 0x0078
>>> +#define SFC_REG_TRAN_CFG1(n) (0x009c + n * 4)
>>> +#define SFC_REG_DR 0x1000
>>> +
>>> +/* bits within the GLB register */
>>> +#define GLB_TRAN_DIR_MASK GENMASK(13, 13)
>>> +#define GLB_TRAN_DIR_WRITE 0x1
>>> +#define GLB_TRAN_DIR_READ 0x0
>>
>> When it's a single bit - just use BIT().
>
>
> Sure.
>
>
>>
>>> +#define GLB_THRESHOLD_MASK GENMASK(12, 7)
>>> +#define GLB_OP_MODE_MASK GENMASK(6, 6)
>>
>> Same here, and I see it a few times below as well.
>>
>
> Sure.
>
>
>>> +#define GLB_OP_MODE_DMA 0x1
>>> +#define GLB_OP_MODE_SLAVE 0x0
>>> +#define GLB_PHASE_NUM_MASK GENMASK(5, 3)
>>> +#define GLB_WP_EN BIT(2)
>>> +#define GLB_BURST_MD_MASK GENMASK(1, 0)
>>> +#define GLB_BURST_MD_INCR32 0x3
>>> +#define GLB_BURST_MD_INCR16 0x2
>>> +#define GLB_BURST_MD_INCR8 0x1
>>> +#define GLB_BURST_MD_INCR4 0x0
>>> +
>>> +/* bits within the DEV_CONF register */
>>> +#define DEV_CONF_SMP_DELAY_MASK GENMASK(20, 16)
>>> +#define DEV_CONF_SMP_DELAY_180DEG 0x4
>>> +#define DEV_CONF_SMP_DELAY_HALF_CYCLE 0x1
>>> +#define DEV_CONF_CMD_TYPE_MASK GENMASK(15, 15)
>>> +#define DEV_CONF_CMD_TYPE_16BIT 0x1
>>> +#define DEV_CONF_CMD_TYPE_8BIT 0x0
>>> +#define DEV_CONF_STA_TYPE_MASK GENMASK(14, 13)
>>> +#define DEV_CONF_THOLD_MASK GENMASK(12, 11)
>>> +#define DEV_CONF_TSETUP_MASK GENMASK(10, 9)
>>> +#define DEV_CONF_TSH_MASK GENMASK(8, 5)
>>> +#define DEV_CONF_CPHA BIT(4)
>>> +#define DEV_CONF_CPOL BIT(3)
>>> +#define DEV_CONF_CE_DL BIT(2)
>>> +#define DEV_CONF_HOLD_DL BIT(1)
>>> +#define DEV_CONF_WP_DL BIT(0)
>>> +
>>> +/* bits within the TRAN_CONF(n) register */
>>> +#define TRAN_CONF_TRAN_MODE_MASK GENMASK(31, 29)
>>> +#define TRAN_CONF_ADDR_WIDTH_MASK GENMASK(28, 26)
>>> +#define TRAN_CONF_POLL_EN BIT(25)
>>> +#define TRAN_CONF_CMD_EN BIT(24)
>>> +#define TRAN_CONF_PHASE_FORMAT_MASK GENMASK(23, 23)
>>> +#define TRAN_CONF_DMY_BITS_MASK GENMASK(22, 17)
>>> +#define TRAN_CONF_DATA_EN BIT(16)
>>> +#define TRAN_CONF_CMD_MASK GENMASK(15, 0)
>>> +
>>> +/* bits within the TRIG register */
>>> +#define TRIG_FLUSH BIT(2)
>>> +#define TRIG_STOP BIT(1)
>>> +#define TRIG_START BIT(0)
>>> +
>>> +/* bits within the SR register */
>>> +#define SR_FIFO_NUM_MASK GENMASK(22, 16)
>>> +#define SR_END BIT(4)
>>> +#define SR_TRAN_REQ BIT(3)
>>> +#define SR_RECE_REQ BIT(2)
>>> +#define SR_OVER BIT(1)
>>> +#define SR_UNDER BIT(0)
>>> +
>>> +/* bits within the SCR register */
>>> +#define SCR_CLR_END BIT(4)
>>> +#define SCR_CLR_TREQ BIT(3)
>>> +#define SCR_CLR_RREQ BIT(2)
>>> +#define SCR_CLR_OVER BIT(1)
>>> +#define SCR_CLR_UNDER BIT(0)
>>> +
>>> +/* bits within the INTC register */
>>> +#define INTC_MASK_END BIT(4)
>>> +#define INTC_MASK_TREQ BIT(3)
>>> +#define INTC_MASK_RREQ BIT(2)
>>> +#define INTC_MASK_OVER BIT(1)
>>> +#define INTC_MASK_UNDER BIT(0)
>>> +
>>> +/* bits within the TRAN_CFG1(n) register */
>>> +#define TRAN_CFG1_TRAN_MODE_MASK GENMASK(7, 4)
>>> +
>>> +#define TRAN_MODE_STANDARD 0
>>> +#define TRAN_MODE_DUAL_DATA 1
>>> +#define TRAN_MODE_DUAL_IO 2
>>> +#define TRAN_MODE_DUAL_FULL 3
>>> +#define TRAN_MODE_QUAD_DATA 5
>>> +#define TRAN_MODE_QUAD_IO 6
>>> +#define TRAN_MODE_QUAD_FULL 7
>>> +#define TRAN_MODE_OCTAL_DATA 9
>>> +#define TRAN_MODE_OCTAL_IO 10
>>> +#define TRAN_MODE_OCTAL_FULL 11
>>> +
>>> +#define INGENIC_SFC_FIFO_SIZE (64 * 4)
>>> +
>>> +#define INGENIC_SFC_TRANSFER_TIMEOUT 1000
>>
>> Maybe add the unit name in the macro as well -
>> \x7fINGENIC_SFC_TRANSFER_TIMEOUT_MS.
>
>
> Sure.
>
>
>>
>>> +
>>> +enum ingenic_sfc_version {
>>> + ID_X1000,
>>> + ID_X1600,
>>> + ID_X2000,
>>> +};
>>> +
>>> +struct ingenic_soc_info {
>>> + enum ingenic_sfc_version version;
>>> +
>>> + unsigned int max_bus_width;
>>> +
>>> + const u32 tran_mode_mask;
>>> +};
>>> +
>>> +struct ingenic_sfc {
>>> + const struct ingenic_soc_info *soc_info;
>>> +
>>> + void __iomem *base;
>>> + struct device *dev;
>>> + struct clk *clk;
>>> + int irq;
>>> +
>>> + struct completion completion;
>>> +};
>>> +
>>> +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
>>> +{
>>> + struct ingenic_sfc *sfc = data;
>>> +
>>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>>> +
>>> + complete(&sfc->completion);
>>> +
>>> + return IRQ_HANDLED;
>>> +}
>>> +
>>> +static int ingenic_sfc_adjust_op_size(struct spi_mem *mem, struct
>>> \x7f\x7fspi_mem_op *op)
>>> +{
>>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>>> +
>>> + if (op->data.nbytes > INGENIC_SFC_FIFO_SIZE &&
>>> !IS_ALIGNED(addr, \x7f\x7f4))
>>> + op->data.nbytes = INGENIC_SFC_FIFO_SIZE;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static bool ingenic_sfc_supports_op(struct spi_mem *mem, const
>>> \x7f\x7fstruct spi_mem_op *op)
>>> +{
>>> + struct spi_device *spi = mem->spi;
>>> + struct ingenic_sfc *sfc =
>>> spi_controller_get_devdata(spi->master);
>>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>>> +
>>> + /* The controller only supports Standard SPI mode, Duall mode,
>>> \x7f\x7fQuad mode and Octal mode. */
>>
>> Dual*
>
>
> Oops, will fix it.
>
>
>>
>>> + if (op->cmd.buswidth > sfc->soc_info->max_bus_width ||
>>> + op->addr.buswidth > sfc->soc_info->max_bus_width ||
>>> + op->dummy.buswidth > sfc->soc_info->max_bus_width ||
>>> + op->data.buswidth > sfc->soc_info->max_bus_width)
>>> + return false;
>>> +
>>> + /* Max 32 dummy clock cycles supported */
>>> + if (op->dummy.nbytes && op->dummy.nbytes * 8 /
>>> \x7f\x7fop->dummy.buswidth > 32)
>>> + return false;
>>> +
>>> + /* Max rx data length, check controller limits and alignment */
>>> + if (op->data.dir == SPI_MEM_DATA_IN &&
>>> + op->data.nbytes > INGENIC_SFC_FIFO_SIZE &&
>>> !IS_ALIGNED(addr, \x7f\x7f4))
>>
>> This does the same check than in ingenic_sfc_adjust_op_size(), maybe
>> \x7fmove it to a new inline function?
>>
>
> Sure.
>
>
>>> + return false;
>>> +
>>> + /* Max 6 bytes address width supported */
>>> + if (op->addr.nbytes > 6)
>>> + return false;
>>> +
>>> + return spi_mem_default_supports_op(mem, op);
>>> +}
>>> +
>>> +static void ingenic_sfc_set_transfer_mode(struct ingenic_sfc *sfc,
>>> \x7f\x7fconst struct spi_mem_op *op)
>>> +{
>>> + int val;
>>> +
>>> + val = readl(sfc->base + (sfc->soc_info->version >= ID_X1600 ?
>>> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
>>
>> As Krzysztof said - ugh.
>>
>
> Will fix it.
>
>
>> Also, instead of having a "version" enum in your soc_info, why not
>> \x7fjust have a "reg_conf" field that gives you directly the right
>> register?
>>
>
> We still need a version to distinguish the SFC before X1600 SoC and
> the SFC after X1600 SoC, because in addition to the difference in the
> cfg register, another difference is that the SFC before X1600 SoC does
> not support address unaligned RX DMA transfer, while SFC in X1600 and
> later SoCs can support this feature.
Then add a .dma_supports_unaligned_address field.
>
>
>>
>>> + val &= ~sfc->soc_info->tran_mode_mask;
>>> + if (op->cmd.buswidth == 8)
>>> + val |= (TRAN_MODE_OCTAL_FULL <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>
>> Looks like you're really trying to reinvent the wheel.
>>
>> val |= FIELD_PREP(sfc->soc_info->tran_mode_mask,
>> TRAN_MODE_OCTAL_FULL);
>>
>> using <linux/bitfield.h>.
>>
>> Also, just define a 'mode' variable and set it in your if/else
>> blocks, \x7fthat would look much better. Then you can set val |=
>> FIELD_PREP(..., \x7fmode) at the end.
>
>
> Sure, will change this in the next version.
>
>
>>
>>> + else if (op->cmd.buswidth == 4)
>>> + val |= (TRAN_MODE_QUAD_FULL <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>> + else if (op->cmd.buswidth == 2)
>>> + val |= (TRAN_MODE_DUAL_FULL <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>> + else if (op->addr.buswidth == 8)
>>> + val |= (TRAN_MODE_OCTAL_IO <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>> + else if (op->addr.buswidth == 4)
>>> + val |= (TRAN_MODE_QUAD_IO <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>> + else if (op->addr.buswidth == 2)
>>> + val |= (TRAN_MODE_DUAL_IO <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>> + else if (op->data.buswidth == 8)
>>> + val |= (TRAN_MODE_OCTAL_DATA <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>> + else if (op->data.buswidth == 4)
>>> + val |= (TRAN_MODE_QUAD_DATA <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>> + else if (op->data.buswidth == 2)
>>> + val |= (TRAN_MODE_DUAL_DATA <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>> + else
>>> + val |= (TRAN_MODE_STANDARD <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>> + writel(val, sfc->base + (sfc->soc_info->version >= ID_X1600 ?
>>> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
>>> +}
>>> +
>>> +/*
>>> + * We only need PIO mode to handle the SPI_MEM_NO_DATA transfers,
>>> + * and the unaligned accesses in SPI_MEM_DATA_IN transfers.
>>> + */
>>> +static void ingenic_sfc_read_rxfifo(struct ingenic_sfc *sfc, u8
>>> *to, \x7f\x7funsigned int len)
>>> +{
>>> + void __iomem *from;
>>> +
>>> + from = sfc->base + SFC_REG_DR;
>>> +
>>> + for (; len >= 4; len -= 4, to += 4) {
>>> + u32 val = __raw_readl(from);
>>> + memcpy(to, &val, 4);
>>
>> No need to use memcpy for 4 bytes. You can do: put_unaligned(val,
>> (u32 \x7f*)to);
>>
>
> Sure.
>
>
>>> + }
>>> +
>>> + if (len) {
>>> + u32 val = __raw_readl(from);
>>> + memcpy(to, &val, len);
>>
>> Hmm, I'm not sure that is endian-safe. I would prefer if you copied
>> \x7fbyte by byte.
>>
>
> Sure.
>
>
>>> + }
>>> +}
>>> +
>>> +static int ingenic_sfc_exec_op_pio(struct ingenic_sfc *sfc, const
>>> \x7f\x7fstruct spi_mem_op *op)
>>> +{
>>> + int ret, val;
>>> +
>>> + val = readl(sfc->base + SFC_REG_GLB);
>>> + u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
>>> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
>>> + writel(val, sfc->base + SFC_REG_GLB);
>>
>> By the way, have you considered using regmap?
>>
>> It would give you things like regmap_update_bits() for this kind of
>> \x7fthings, and regmap_field() to handle your conf register being at a
>> \x7fdifferent address across SoCs.
>>
>
> It seems that the overhead of using regmap will be greater than using
> readl and writel,
> the purpose of using readl and writel is to minimize overhead and
> maximize performance. :)
If you really think that this is a valid reason not to use regmap, then
prove it with a benchmark, because I'm *very* doubtful about your claim.
Cheers,
-Paul
>>> +
>>> + val = TRAN_CONF_CMD_EN | op->cmd.opcode;
>>> +
>>> + if (op->addr.nbytes > 0) {
>>> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK,
>>> op->addr.nbytes);
>>> +
>>> + writel(op->addr.val & 0xffffffff, sfc->base +
>>> \x7f\x7fSFC_REG_DEV_ADDR(0));
>>> + writel(op->addr.val >> 32, sfc->base +
>>> \x7f\x7fSFC_REG_DEV_ADDR_PLUS(0));
>>> + }
>>> +
>>> + if (op->dummy.nbytes > 0)
>>> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
>>> + op->dummy.nbytes * 8 / op->dummy.buswidth);
>>> +
>>> + if (op->data.nbytes > 0)
>>> + val |= TRAN_CONF_DATA_EN;
>>> +
>>> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
>>> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
>>> +
>>> + ingenic_sfc_set_transfer_mode(sfc, op);
>>> +
>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>
>> Random 0x1f value here, maybe use a macro?
>
>
> Sure.
>
>
>>
>>> + writel(~(INTC_MASK_END | INTC_MASK_RREQ), sfc->base +
>>> \x7f\x7fSFC_REG_INTC);
>>> +
>>> + writel(0, sfc->base + SFC_REG_MEM_ADDR);
>>> +
>>> + writel(TRIG_FLUSH, sfc->base + SFC_REG_TRIG);
>>> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
>>> +
>>> + ret = wait_for_completion_timeout(&sfc->completion,
>>> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
>>> + if (!ret) {
>>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC
>>> \x7f\x7fdevice\n", __LINE__);
>>> + return -ETIMEDOUT;
>>> + }
>>> +
>>> + ingenic_sfc_read_rxfifo(sfc, op->data.buf.in, op->data.nbytes);
>>> + readl_poll_timeout(sfc->base + SFC_REG_SR, val, val & SR_END,
>>> \x7f\x7f10, 0);
>>
>> Infinite timeout? Is that very wise?
>
>
> Will fix it in the next version.
>
>
>>
>>> +
>>> + writel(INTC_MASK_END | INTC_MASK_RREQ, sfc->base +
>>> SFC_REG_SCR);
>>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int ingenic_sfc_exec_op_dma(struct ingenic_sfc *sfc, const
>>> \x7f\x7fstruct spi_mem_op *op)
>>> +{
>>> + dma_addr_t addr;
>>> + int ret, val;
>>> +
>>> + val = readl(sfc->base + SFC_REG_GLB);
>>> + u32p_replace_bits(&val, op->data.dir == SPI_MEM_DATA_IN ?
>>> + GLB_TRAN_DIR_READ : GLB_TRAN_DIR_WRITE,
>>> GLB_TRAN_DIR_MASK);
>>> + u32p_replace_bits(&val, GLB_OP_MODE_DMA, GLB_OP_MODE_MASK);
>>> + writel(val, sfc->base + SFC_REG_GLB);
>>> +
>>> + val = TRAN_CONF_CMD_EN | op->cmd.opcode;
>>> +
>>> + if (op->addr.nbytes > 0) {
>>> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK,
>>> op->addr.nbytes);
>>> + writel(op->addr.val & 0xffffffff, sfc->base +
>>> \x7f\x7fSFC_REG_DEV_ADDR(0));
>>> + writel(op->addr.val >> 32, sfc->base +
>>> \x7f\x7fSFC_REG_DEV_ADDR_PLUS(0));
>>> + }
>>> +
>>> + if (op->dummy.nbytes > 0)
>>> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
>>> + op->dummy.nbytes * 8 / op->dummy.buswidth);
>>> +
>>> + if (op->data.nbytes > 0)
>>> + val |= TRAN_CONF_DATA_EN;
>>
>> There's a lot of code duplication here with
>> ingenic_sfc_exec_op_pio(). \x7fA lot can be factorized.
>
>
> Sure, I will try to do it.
>
>
>>
>>> +
>>> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
>>> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
>>> +
>>> + ingenic_sfc_set_transfer_mode(sfc, op);
>>> +
>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>> + writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
>>> +
>>> + switch (op->data.dir) {
>>> + case SPI_MEM_DATA_IN:
>>> + addr = dma_map_single(sfc->dev, op->data.buf.in,
>>> \x7f\x7fop->data.nbytes, DMA_FROM_DEVICE);
>>> + if (dma_mapping_error(sfc->dev, addr)) {
>>> + dev_err(sfc->dev, "RX DMA memory not mapped\n");
>>> + return -ENOMEM;
>>> + }
>>> +
>>> + writel(addr, sfc->base + SFC_REG_MEM_ADDR);
>>> + break;
>>> +
>>> + case SPI_MEM_DATA_OUT:
>>> + addr = dma_map_single(sfc->dev, (void *)op->data.buf.out,
>>> + op->data.nbytes, DMA_TO_DEVICE);
>>> + if (dma_mapping_error(sfc->dev, addr)) {
>>> + dev_err(sfc->dev, "TX DMA memory not mapped\n");
>>> + return -ENOMEM;
>>> + }
>>> +
>>> + writel(addr, sfc->base + SFC_REG_MEM_ADDR);
>>> + break;
>>> +
>>> + default:
>>> + return -EINVAL;
>>> + }
>>> +
>>> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
>>> +
>>> + ret = wait_for_completion_timeout(&sfc->completion,
>>> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
>>> + if (!ret) {
>>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC
>>> \x7f\x7fdevice\n", __LINE__);
>>> + return -ETIMEDOUT;
>>> + }
>>> +
>>> + dma_unmap_single(sfc->dev, addr, op->data.nbytes,
>>> + op->data.dir == SPI_MEM_DATA_IN ? DMA_FROM_DEVICE :
>>> \x7f\x7fDMA_TO_DEVICE);
>>
>> Use a small inline function for that too. My personal rule is that
>> ?: \x7fis fine if the line fits in 80 characters, but if you have to
>> break, \x7fthen you really need to move it somewhere else.
>>
>
> Sure.
>
>
>>> +
>>> + writel(INTC_MASK_END, sfc->base + SFC_REG_SCR);
>>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int ingenic_sfc_exec_op(struct spi_mem *mem, const struct
>>> \x7f\x7fspi_mem_op *op)
>>> +{
>>> + struct spi_device *spi = mem->spi;
>>> + struct ingenic_sfc *sfc =
>>> spi_controller_get_devdata(spi->master);
>>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>>> +
>>> + init_completion(&sfc->completion);
>>> +
>>> + switch (op->data.dir) {
>>> + case SPI_MEM_DATA_IN:
>>> + if (sfc->soc_info->version >= ID_X1600 || IS_ALIGNED(addr,
>>> 4))
>>> + break;
>>> +
>>> + fallthrough;
>>> +
>>> + case SPI_MEM_NO_DATA:
>>> + return ingenic_sfc_exec_op_pio(sfc, op);
>>> +
>>> + default:
>>> + break;
>>> + }
>>> +
>>> + return ingenic_sfc_exec_op_dma(sfc, op);
>>> +}
>>> +
>>> +static int ingenic_sfc_poll_status(struct spi_mem *mem, const
>>> struct \x7f\x7fspi_mem_op *op,
>>> + u16 mask, u16 match, unsigned long initial_delay_us,
>>> + unsigned long polling_delay_us, unsigned long
>>> timeout_ms)
>>> +{
>>> + struct spi_device *spi = mem->spi;
>>> + struct ingenic_sfc *sfc =
>>> spi_controller_get_devdata(spi->master);
>>> + int ret, val;
>>> +
>>> + init_completion(&sfc->completion);
>>> +
>>> + val = readl(sfc->base + SFC_REG_GLB);
>>> + u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
>>> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
>>> + writel(val, sfc->base + SFC_REG_GLB);
>>> +
>>> + writel(match, sfc->base + SFC_REG_DEV_STA_EXP);
>>> + writel(mask, sfc->base + SFC_REG_DEV_STA_MSK);
>>> +
>>> + val = TRAN_CONF_POLL_EN | TRAN_CONF_CMD_EN | op->cmd.opcode;
>>> +
>>> + if (op->addr.nbytes > 0) {
>>> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK,
>>> op->addr.nbytes);
>>> +
>>> + writel(op->addr.val & 0xffffffff, sfc->base +
>>> \x7f\x7fSFC_REG_DEV_ADDR(0));
>>> + writel(op->addr.val >> 32, sfc->base +
>>> \x7f\x7fSFC_REG_DEV_ADDR_PLUS(0));
>>> + }
>>> +
>>> + if (op->dummy.nbytes > 0)
>>> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
>>> + op->dummy.nbytes * 8 / op->dummy.buswidth);
>>> +
>>> + if (op->data.nbytes > 0)
>>> + val |= TRAN_CONF_DATA_EN;
>>> +
>>> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
>>> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
>>> +
>>> + ingenic_sfc_set_transfer_mode(sfc, op);
>>> +
>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>> + writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
>>> +
>>> + writel(0, sfc->base + SFC_REG_MEM_ADDR);
>>> +
>>> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
>>> +
>>> + ret = wait_for_completion_timeout(&sfc->completion,
>>> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
>>> + if (!ret) {
>>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC
>>> \x7f\x7fdevice\n", __LINE__);
>>> + return -ETIMEDOUT;
>>> + }
>>> +
>>> + writel(SCR_CLR_END, sfc->base + SFC_REG_SCR);
>>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static const struct spi_controller_mem_ops ingenic_sfc_mem_ops = {
>>> + .adjust_op_size = ingenic_sfc_adjust_op_size,
>>> + .supports_op = ingenic_sfc_supports_op,
>>> + .exec_op = ingenic_sfc_exec_op,
>>> + .poll_status = ingenic_sfc_poll_status,
>>> +};
>>> +
>>> +static int ingenic_sfc_setup(struct spi_device *spi)
>>> +{
>>> + struct ingenic_sfc *sfc =
>>> spi_controller_get_devdata(spi->master);
>>> + unsigned long rate;
>>> + int ret, val;
>>> +
>>> + if (!spi->max_speed_hz)
>>> + return -EINVAL;
>>
>> Maybe set a sane default?
>
>
> Sure.
>
>
>>
>>> +
>>> + ret = clk_set_rate(sfc->clk, spi->max_speed_hz * 2);
>>> + if (ret)
>>> + return -EINVAL;
>>> +
>>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>>> + writel(0, sfc->base + SFC_REG_DEV_CONF);
>>> + writel(0, sfc->base + SFC_REG_CGE);
>>> +
>>> + val = readl(sfc->base + SFC_REG_GLB);
>>> + u32p_replace_bits(&val, 64 - 1, GLB_THRESHOLD_MASK);
>>> + writel(val, sfc->base + SFC_REG_GLB);
>>> +
>>> + val = readl(sfc->base + SFC_REG_DEV_CONF);
>>> +
>>> + /* cpha bit:0 , cpol bit:0 */
>>> + val &= ~(DEV_CONF_CPHA | DEV_CONF_CPOL);
>>> + val |= spi->mode & SPI_CPHA ? DEV_CONF_CPHA : 0;
>>> + val |= spi->mode & SPI_CPOL ? DEV_CONF_CPOL : 0;
>>> +
>>> + /* ce_dl bit:1, hold bit:1, wp bit:1 */
>>> + val |= (DEV_CONF_CE_DL | DEV_CONF_HOLD_DL | DEV_CONF_WP_DL);
>>> +
>>> + writel(val, sfc->base + SFC_REG_DEV_CONF);
>>> +
>>> + val = readl(sfc->base + SFC_REG_GLB);
>>> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
>>> + writel(val, sfc->base + SFC_REG_GLB);
>>> +
>>> + rate = clk_get_rate(sfc->clk);
>>
>> I'd suggest using clk_round_rate() before clk_set_rate() because
>> then \x7fyou know what frequency it's going to be, and you don't have
>> to call \x7fclk_get_rate() afterwards.
>>
>
> Sure, will try.
>
>
> Thanks and best regards!
>
>
>> Cheers,
>> -Paul
>>
>>> + val = readl(sfc->base + SFC_REG_DEV_CONF);
>>> + if (sfc->soc_info->version >= ID_X1600 && rate >= 200000000)
>>> + u32p_replace_bits(&val, DEV_CONF_SMP_DELAY_180DEG,
>>> \x7f\x7fDEV_CONF_SMP_DELAY_MASK);
>>> + else if (sfc->soc_info->version == ID_X1000 && rate >=
>>> 100000000)
>>> + u32p_replace_bits(&val, DEV_CONF_SMP_DELAY_HALF_CYCLE,
>>> \x7f\x7fDEV_CONF_SMP_DELAY_MASK);
>>> + writel(val, sfc->base + SFC_REG_DEV_CONF);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int ingenic_sfc_probe(struct platform_device *pdev)
>>> +{
>>> + struct ingenic_sfc *sfc;
>>> + struct spi_controller *ctlr;
>>> + int ret;
>>> +
>>> + ctlr = spi_alloc_master(&pdev->dev, sizeof(*sfc));
>>> + if (!ctlr)
>>> + return -ENOMEM;
>>> +
>>> + sfc = spi_controller_get_devdata(ctlr);
>>> +
>>> + sfc->soc_info = of_device_get_match_data(&pdev->dev);
>>> + if (!sfc->soc_info) {
>>> + dev_err(&pdev->dev, "No of match data provided\n");
>>> + ret = -ENODEV;
>>> + goto err_put_master;
>>> + }
>>> +
>>> + sfc->base = devm_platform_ioremap_resource(pdev, 0);
>>> + if (IS_ERR(sfc->base)) {
>>> + ret = PTR_ERR(sfc->base);
>>> + goto err_put_master;
>>> + }
>>> +
>>> + sfc->clk = devm_clk_get(&pdev->dev, "sfc");
>>> + if (IS_ERR(sfc->clk)) {
>>> + ret = IS_ERR(sfc->clk);
>>> + goto err_put_master;
>>> + }
>>> +
>>> + ret = clk_prepare_enable(sfc->clk);
>>> + if (ret)
>>> + goto err_put_master;
>>> +
>>> + sfc->irq = platform_get_irq(pdev, 0);
>>> + if (sfc->irq < 0) {
>>> + ret = sfc->irq;
>>> + goto err_put_master;
>>> + }
>>> +
>>> + sfc->dev = &pdev->dev;
>>> +
>>> + platform_set_drvdata(pdev, sfc);
>>> +
>>> + ret = devm_request_irq(&pdev->dev, sfc->irq,
>>> \x7f\x7fingenic_sfc_irq_handler, 0,
>>> + dev_name(&pdev->dev), sfc);
>>> + if (ret) {
>>> + dev_err(&pdev->dev, "Failed to request irq%d, ret = %d\n",
>>> \x7f\x7fsfc->irq, ret);
>>> + goto err_put_master;
>>> + }
>>> +
>>> + ctlr->bus_num = -1;
>>> + ctlr->num_chipselect = 1;
>>> + ctlr->mem_ops = &ingenic_sfc_mem_ops;
>>> + ctlr->dev.of_node = pdev->dev.of_node;
>>> + ctlr->setup = ingenic_sfc_setup;
>>> + ctlr->mode_bits = SPI_CPHA | SPI_CPOL |
>>> + SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
>>> + if (sfc->soc_info->version >= ID_X2000)
>>> + ctlr->mode_bits |= SPI_RX_OCTAL | SPI_TX_OCTAL;
>>> +
>>> + ret = devm_spi_register_controller(&pdev->dev, ctlr);
>>> + if (ret)
>>> + goto err_put_master;
>>> +
>>> + return 0;
>>> +
>>> +err_put_master:
>>> + spi_master_put(ctlr);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static const struct ingenic_soc_info x1000_soc_info = {
>>> + .version = ID_X1000,
>>> +
>>> + .max_bus_width = 4,
>>> +
>>> + .tran_mode_mask = TRAN_CONF_TRAN_MODE_MASK,
>>> +};
>>> +
>>> +static const struct ingenic_soc_info x1600_soc_info = {
>>> + .version = ID_X1600,
>>> +
>>> + .max_bus_width = 4,
>>> +
>>> + .tran_mode_mask = TRAN_CONF_TRAN_MODE_MASK,
>>> +};
>>> +
>>> +static const struct ingenic_soc_info x2000_soc_info = {
>>> + .version = ID_X2000,
>>> +
>>> + .max_bus_width = 8,
>>> +
>>> + .tran_mode_mask = TRAN_CFG1_TRAN_MODE_MASK,
>>> +};
>>> +
>>> +static const struct of_device_id ingenic_sfc_of_matches[] = {
>>> + { .compatible = "ingenic,x1000-sfc", .data = &x1000_soc_info },
>>> + { .compatible = "ingenic,x1600-sfc", .data = &x1600_soc_info },
>>> + { .compatible = "ingenic,x2000-sfc", .data = &x2000_soc_info },
>>> + { /* sentinel */ }
>>> +};
>>> +MODULE_DEVICE_TABLE(of, ingenic_sfc_of_matches);
>>> +
>>> +static struct platform_driver ingenic_sfc_driver = {
>>> + .driver = {
>>> + .name = "ingenic-sfc",
>>> + .of_match_table = ingenic_sfc_of_matches,
>>> + },
>>> + .probe = ingenic_sfc_probe,
>>> +};
>>> +module_platform_driver(ingenic_sfc_driver);
>>> +
>>> +MODULE_LICENSE("GPL");
>>> +MODULE_AUTHOR("周琰杰 (Zhou Yanjie)
>>> <zhouyanjie@wanyeetech.com>");
>>> +MODULE_DESCRIPTION("Ingenic SoCs SPI Flash Controller Driver");
>>> --
>>> 2.7.4
>>>
>>
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
@ 2022-07-23 20:24 ` Paul Cercueil
0 siblings, 0 replies; 78+ messages in thread
From: Paul Cercueil @ 2022-07-23 20:24 UTC (permalink / raw)
To: Zhou Yanjie
Cc: tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, broonie, robh+dt, krzysztof.kozlowski+dt, linux-mtd,
linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, dongsheng.qiu, aric.pzqi, rick.tyliu,
jinghui.liu, sernia.zhou, reimu
Hi Zhou,
Le dim., juil. 24 2022 at 01:26:56 +0800, Zhou Yanjie
<zhouyanjie@wanyeetech.com> a écrit :
> Hi Paul,
>
> On 2022/7/23 上午4:03, Paul Cercueil wrote:
>> Hi Zhou,
>>
>>
>> Le sam., juil. 23 2022 at 00:48:30 +0800, 周琰杰 (Zhou Yanjie)
>> \x7f<zhouyanjie@wanyeetech.com> a écrit :
>>> Add SFC support for the X1000 SoC, the X1600 SoC, and the X2000 SoC
>>> from Ingenic.
>>>
>>> Signed-off-by: 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
>>> ---
>>> drivers/spi/Kconfig | 9 +
>>> drivers/spi/Makefile | 1 +
>>> drivers/spi/spi-ingenic-sfc.c | 662
>>> \x7f\x7f++++++++++++++++++++++++++++++++++++++++++
>>> 3 files changed, 672 insertions(+)
>>> create mode 100644 drivers/spi/spi-ingenic-sfc.c
>>>
>>> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
>>> index 3b1044e..1077bd3 100644
>>> --- a/drivers/spi/Kconfig
>>> +++ b/drivers/spi/Kconfig
>>> @@ -437,6 +437,15 @@ config SPI_INGENIC
>>> To compile this driver as a module, choose M here: the module
>>> will be called spi-ingenic.
>>>
>>> +config SPI_INGENIC_SFC
>>> + tristate "Ingenic SoCs SPI Flash Controller"
>>> + depends on MACH_INGENIC || COMPILE_TEST
>>> + help
>>> + This enables support for the Ingenic SoCs SPI flash
>>> controller.
>>> +
>>> + To compile this driver as a module, choose M here: the module
>>> + will be called ingenic-sfc.
>>> +
>>> config SPI_INTEL
>>> tristate
>>>
>>> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
>>> index 0f44eb6..f3e42c0 100644
>>> --- a/drivers/spi/Makefile
>>> +++ b/drivers/spi/Makefile
>>> @@ -62,6 +62,7 @@ obj-$(CONFIG_SPI_HISI_SFC_V3XX) +=
>>> \x7f\x7fspi-hisi-sfc-v3xx.o
>>> obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o
>>> obj-$(CONFIG_SPI_IMX) += spi-imx.o
>>> obj-$(CONFIG_SPI_INGENIC) += spi-ingenic.o
>>> +obj-$(CONFIG_SPI_INGENIC_SFC) += spi-ingenic-sfc.o
>>> obj-$(CONFIG_SPI_INTEL) += spi-intel.o
>>> obj-$(CONFIG_SPI_INTEL_PCI) += spi-intel-pci.o
>>> obj-$(CONFIG_SPI_INTEL_PLATFORM) += spi-intel-platform.o
>>> diff --git a/drivers/spi/spi-ingenic-sfc.c
>>> \x7f\x7fb/drivers/spi/spi-ingenic-sfc.c
>>> new file mode 100644
>>> index 00000000..a565546
>>> --- /dev/null
>>> +++ b/drivers/spi/spi-ingenic-sfc.c
>>> @@ -0,0 +1,662 @@
>>> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>
>> Dual-license driver? That's not what MODULE_LICENSE() says.
>>
>
> My fault, forgot to modify MODULE_LICENSE(), will fix it in the next
> version.
>
>
>>> +/*
>>> + * Ingenic SoCs SPI Flash Controller Driver
>>> + * Copyright (c) 2022 周琰杰 (Zhou Yanjie)
>>> <zhouyanjie@wanyeetech.com>
>>> + */
>>> +
>>> +#include <linux/bitfield.h>
>>> +#include <linux/bitops.h>
>>> +#include <linux/clk.h>
>>> +#include <linux/completion.h>
>>> +#include <linux/dma-mapping.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/iopoll.h>
>>> +#include <linux/module.h>
>>> +#include <linux/mtd/mtd.h>
>>> +#include <linux/of_device.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/spi/spi.h>
>>> +#include <linux/spi/spi-mem.h>
>>> +
>>> +/* SFC register offsets */
>>> +#define SFC_REG_GLB 0x0000
>>> +#define SFC_REG_DEV_CONF 0x0004
>>> +#define SFC_REG_DEV_STA_EXP 0x0008
>>> +#define SFC_REG_DEV_STA_RT 0x000c
>>> +#define SFC_REG_DEV_STA_MSK 0x0010
>>> +#define SFC_REG_TRAN_CONF(n) (0x0014 + n * 4)
>>> +#define SFC_REG_TRAN_CFG0(n) (0x0014 + n * 4)
>>
>> You should protect the macro parameter. If you do for instance
>> \x7fSFC_REG_TRAN_CONF(x + 1) it would resolve to (0x0014 + x + 1 * 4)
>> \x7fwhich is not what you'd want.
>>
>
> Sure, will fix it.
>
>
>> Also - looks like SFC_REG_TRAN_CONF() and SFC_REG_TRAN_CFG0() are
>> the \x7fsame thing, that's on purpose?
>>
>>> +#define SFC_REG_TRAN_LEN 0x002c
>>> +#define SFC_REG_DEV_ADDR(n) (0x0030 + n * 4)
>>> +#define SFC_REG_DEV_ADDR_PLUS(n) (0x0048 + n * 4)
>>> +#define SFC_REG_MEM_ADDR 0x0060
>>> +#define SFC_REG_TRIG 0x0064
>>> +#define SFC_REG_SR 0x0068
>>> +#define SFC_REG_SCR 0x006c
>>> +#define SFC_REG_INTC 0x0070
>>> +#define SFC_REG_FSM 0x0074
>>> +#define SFC_REG_CGE 0x0078
>>> +#define SFC_REG_TRAN_CFG1(n) (0x009c + n * 4)
>>> +#define SFC_REG_DR 0x1000
>>> +
>>> +/* bits within the GLB register */
>>> +#define GLB_TRAN_DIR_MASK GENMASK(13, 13)
>>> +#define GLB_TRAN_DIR_WRITE 0x1
>>> +#define GLB_TRAN_DIR_READ 0x0
>>
>> When it's a single bit - just use BIT().
>
>
> Sure.
>
>
>>
>>> +#define GLB_THRESHOLD_MASK GENMASK(12, 7)
>>> +#define GLB_OP_MODE_MASK GENMASK(6, 6)
>>
>> Same here, and I see it a few times below as well.
>>
>
> Sure.
>
>
>>> +#define GLB_OP_MODE_DMA 0x1
>>> +#define GLB_OP_MODE_SLAVE 0x0
>>> +#define GLB_PHASE_NUM_MASK GENMASK(5, 3)
>>> +#define GLB_WP_EN BIT(2)
>>> +#define GLB_BURST_MD_MASK GENMASK(1, 0)
>>> +#define GLB_BURST_MD_INCR32 0x3
>>> +#define GLB_BURST_MD_INCR16 0x2
>>> +#define GLB_BURST_MD_INCR8 0x1
>>> +#define GLB_BURST_MD_INCR4 0x0
>>> +
>>> +/* bits within the DEV_CONF register */
>>> +#define DEV_CONF_SMP_DELAY_MASK GENMASK(20, 16)
>>> +#define DEV_CONF_SMP_DELAY_180DEG 0x4
>>> +#define DEV_CONF_SMP_DELAY_HALF_CYCLE 0x1
>>> +#define DEV_CONF_CMD_TYPE_MASK GENMASK(15, 15)
>>> +#define DEV_CONF_CMD_TYPE_16BIT 0x1
>>> +#define DEV_CONF_CMD_TYPE_8BIT 0x0
>>> +#define DEV_CONF_STA_TYPE_MASK GENMASK(14, 13)
>>> +#define DEV_CONF_THOLD_MASK GENMASK(12, 11)
>>> +#define DEV_CONF_TSETUP_MASK GENMASK(10, 9)
>>> +#define DEV_CONF_TSH_MASK GENMASK(8, 5)
>>> +#define DEV_CONF_CPHA BIT(4)
>>> +#define DEV_CONF_CPOL BIT(3)
>>> +#define DEV_CONF_CE_DL BIT(2)
>>> +#define DEV_CONF_HOLD_DL BIT(1)
>>> +#define DEV_CONF_WP_DL BIT(0)
>>> +
>>> +/* bits within the TRAN_CONF(n) register */
>>> +#define TRAN_CONF_TRAN_MODE_MASK GENMASK(31, 29)
>>> +#define TRAN_CONF_ADDR_WIDTH_MASK GENMASK(28, 26)
>>> +#define TRAN_CONF_POLL_EN BIT(25)
>>> +#define TRAN_CONF_CMD_EN BIT(24)
>>> +#define TRAN_CONF_PHASE_FORMAT_MASK GENMASK(23, 23)
>>> +#define TRAN_CONF_DMY_BITS_MASK GENMASK(22, 17)
>>> +#define TRAN_CONF_DATA_EN BIT(16)
>>> +#define TRAN_CONF_CMD_MASK GENMASK(15, 0)
>>> +
>>> +/* bits within the TRIG register */
>>> +#define TRIG_FLUSH BIT(2)
>>> +#define TRIG_STOP BIT(1)
>>> +#define TRIG_START BIT(0)
>>> +
>>> +/* bits within the SR register */
>>> +#define SR_FIFO_NUM_MASK GENMASK(22, 16)
>>> +#define SR_END BIT(4)
>>> +#define SR_TRAN_REQ BIT(3)
>>> +#define SR_RECE_REQ BIT(2)
>>> +#define SR_OVER BIT(1)
>>> +#define SR_UNDER BIT(0)
>>> +
>>> +/* bits within the SCR register */
>>> +#define SCR_CLR_END BIT(4)
>>> +#define SCR_CLR_TREQ BIT(3)
>>> +#define SCR_CLR_RREQ BIT(2)
>>> +#define SCR_CLR_OVER BIT(1)
>>> +#define SCR_CLR_UNDER BIT(0)
>>> +
>>> +/* bits within the INTC register */
>>> +#define INTC_MASK_END BIT(4)
>>> +#define INTC_MASK_TREQ BIT(3)
>>> +#define INTC_MASK_RREQ BIT(2)
>>> +#define INTC_MASK_OVER BIT(1)
>>> +#define INTC_MASK_UNDER BIT(0)
>>> +
>>> +/* bits within the TRAN_CFG1(n) register */
>>> +#define TRAN_CFG1_TRAN_MODE_MASK GENMASK(7, 4)
>>> +
>>> +#define TRAN_MODE_STANDARD 0
>>> +#define TRAN_MODE_DUAL_DATA 1
>>> +#define TRAN_MODE_DUAL_IO 2
>>> +#define TRAN_MODE_DUAL_FULL 3
>>> +#define TRAN_MODE_QUAD_DATA 5
>>> +#define TRAN_MODE_QUAD_IO 6
>>> +#define TRAN_MODE_QUAD_FULL 7
>>> +#define TRAN_MODE_OCTAL_DATA 9
>>> +#define TRAN_MODE_OCTAL_IO 10
>>> +#define TRAN_MODE_OCTAL_FULL 11
>>> +
>>> +#define INGENIC_SFC_FIFO_SIZE (64 * 4)
>>> +
>>> +#define INGENIC_SFC_TRANSFER_TIMEOUT 1000
>>
>> Maybe add the unit name in the macro as well -
>> \x7fINGENIC_SFC_TRANSFER_TIMEOUT_MS.
>
>
> Sure.
>
>
>>
>>> +
>>> +enum ingenic_sfc_version {
>>> + ID_X1000,
>>> + ID_X1600,
>>> + ID_X2000,
>>> +};
>>> +
>>> +struct ingenic_soc_info {
>>> + enum ingenic_sfc_version version;
>>> +
>>> + unsigned int max_bus_width;
>>> +
>>> + const u32 tran_mode_mask;
>>> +};
>>> +
>>> +struct ingenic_sfc {
>>> + const struct ingenic_soc_info *soc_info;
>>> +
>>> + void __iomem *base;
>>> + struct device *dev;
>>> + struct clk *clk;
>>> + int irq;
>>> +
>>> + struct completion completion;
>>> +};
>>> +
>>> +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
>>> +{
>>> + struct ingenic_sfc *sfc = data;
>>> +
>>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>>> +
>>> + complete(&sfc->completion);
>>> +
>>> + return IRQ_HANDLED;
>>> +}
>>> +
>>> +static int ingenic_sfc_adjust_op_size(struct spi_mem *mem, struct
>>> \x7f\x7fspi_mem_op *op)
>>> +{
>>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>>> +
>>> + if (op->data.nbytes > INGENIC_SFC_FIFO_SIZE &&
>>> !IS_ALIGNED(addr, \x7f\x7f4))
>>> + op->data.nbytes = INGENIC_SFC_FIFO_SIZE;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static bool ingenic_sfc_supports_op(struct spi_mem *mem, const
>>> \x7f\x7fstruct spi_mem_op *op)
>>> +{
>>> + struct spi_device *spi = mem->spi;
>>> + struct ingenic_sfc *sfc =
>>> spi_controller_get_devdata(spi->master);
>>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>>> +
>>> + /* The controller only supports Standard SPI mode, Duall mode,
>>> \x7f\x7fQuad mode and Octal mode. */
>>
>> Dual*
>
>
> Oops, will fix it.
>
>
>>
>>> + if (op->cmd.buswidth > sfc->soc_info->max_bus_width ||
>>> + op->addr.buswidth > sfc->soc_info->max_bus_width ||
>>> + op->dummy.buswidth > sfc->soc_info->max_bus_width ||
>>> + op->data.buswidth > sfc->soc_info->max_bus_width)
>>> + return false;
>>> +
>>> + /* Max 32 dummy clock cycles supported */
>>> + if (op->dummy.nbytes && op->dummy.nbytes * 8 /
>>> \x7f\x7fop->dummy.buswidth > 32)
>>> + return false;
>>> +
>>> + /* Max rx data length, check controller limits and alignment */
>>> + if (op->data.dir == SPI_MEM_DATA_IN &&
>>> + op->data.nbytes > INGENIC_SFC_FIFO_SIZE &&
>>> !IS_ALIGNED(addr, \x7f\x7f4))
>>
>> This does the same check than in ingenic_sfc_adjust_op_size(), maybe
>> \x7fmove it to a new inline function?
>>
>
> Sure.
>
>
>>> + return false;
>>> +
>>> + /* Max 6 bytes address width supported */
>>> + if (op->addr.nbytes > 6)
>>> + return false;
>>> +
>>> + return spi_mem_default_supports_op(mem, op);
>>> +}
>>> +
>>> +static void ingenic_sfc_set_transfer_mode(struct ingenic_sfc *sfc,
>>> \x7f\x7fconst struct spi_mem_op *op)
>>> +{
>>> + int val;
>>> +
>>> + val = readl(sfc->base + (sfc->soc_info->version >= ID_X1600 ?
>>> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
>>
>> As Krzysztof said - ugh.
>>
>
> Will fix it.
>
>
>> Also, instead of having a "version" enum in your soc_info, why not
>> \x7fjust have a "reg_conf" field that gives you directly the right
>> register?
>>
>
> We still need a version to distinguish the SFC before X1600 SoC and
> the SFC after X1600 SoC, because in addition to the difference in the
> cfg register, another difference is that the SFC before X1600 SoC does
> not support address unaligned RX DMA transfer, while SFC in X1600 and
> later SoCs can support this feature.
Then add a .dma_supports_unaligned_address field.
>
>
>>
>>> + val &= ~sfc->soc_info->tran_mode_mask;
>>> + if (op->cmd.buswidth == 8)
>>> + val |= (TRAN_MODE_OCTAL_FULL <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>
>> Looks like you're really trying to reinvent the wheel.
>>
>> val |= FIELD_PREP(sfc->soc_info->tran_mode_mask,
>> TRAN_MODE_OCTAL_FULL);
>>
>> using <linux/bitfield.h>.
>>
>> Also, just define a 'mode' variable and set it in your if/else
>> blocks, \x7fthat would look much better. Then you can set val |=
>> FIELD_PREP(..., \x7fmode) at the end.
>
>
> Sure, will change this in the next version.
>
>
>>
>>> + else if (op->cmd.buswidth == 4)
>>> + val |= (TRAN_MODE_QUAD_FULL <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>> + else if (op->cmd.buswidth == 2)
>>> + val |= (TRAN_MODE_DUAL_FULL <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>> + else if (op->addr.buswidth == 8)
>>> + val |= (TRAN_MODE_OCTAL_IO <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>> + else if (op->addr.buswidth == 4)
>>> + val |= (TRAN_MODE_QUAD_IO <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>> + else if (op->addr.buswidth == 2)
>>> + val |= (TRAN_MODE_DUAL_IO <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>> + else if (op->data.buswidth == 8)
>>> + val |= (TRAN_MODE_OCTAL_DATA <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>> + else if (op->data.buswidth == 4)
>>> + val |= (TRAN_MODE_QUAD_DATA <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>> + else if (op->data.buswidth == 2)
>>> + val |= (TRAN_MODE_DUAL_DATA <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>> + else
>>> + val |= (TRAN_MODE_STANDARD <<
>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>> + sfc->soc_info->tran_mode_mask;
>>> + writel(val, sfc->base + (sfc->soc_info->version >= ID_X1600 ?
>>> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
>>> +}
>>> +
>>> +/*
>>> + * We only need PIO mode to handle the SPI_MEM_NO_DATA transfers,
>>> + * and the unaligned accesses in SPI_MEM_DATA_IN transfers.
>>> + */
>>> +static void ingenic_sfc_read_rxfifo(struct ingenic_sfc *sfc, u8
>>> *to, \x7f\x7funsigned int len)
>>> +{
>>> + void __iomem *from;
>>> +
>>> + from = sfc->base + SFC_REG_DR;
>>> +
>>> + for (; len >= 4; len -= 4, to += 4) {
>>> + u32 val = __raw_readl(from);
>>> + memcpy(to, &val, 4);
>>
>> No need to use memcpy for 4 bytes. You can do: put_unaligned(val,
>> (u32 \x7f*)to);
>>
>
> Sure.
>
>
>>> + }
>>> +
>>> + if (len) {
>>> + u32 val = __raw_readl(from);
>>> + memcpy(to, &val, len);
>>
>> Hmm, I'm not sure that is endian-safe. I would prefer if you copied
>> \x7fbyte by byte.
>>
>
> Sure.
>
>
>>> + }
>>> +}
>>> +
>>> +static int ingenic_sfc_exec_op_pio(struct ingenic_sfc *sfc, const
>>> \x7f\x7fstruct spi_mem_op *op)
>>> +{
>>> + int ret, val;
>>> +
>>> + val = readl(sfc->base + SFC_REG_GLB);
>>> + u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
>>> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
>>> + writel(val, sfc->base + SFC_REG_GLB);
>>
>> By the way, have you considered using regmap?
>>
>> It would give you things like regmap_update_bits() for this kind of
>> \x7fthings, and regmap_field() to handle your conf register being at a
>> \x7fdifferent address across SoCs.
>>
>
> It seems that the overhead of using regmap will be greater than using
> readl and writel,
> the purpose of using readl and writel is to minimize overhead and
> maximize performance. :)
If you really think that this is a valid reason not to use regmap, then
prove it with a benchmark, because I'm *very* doubtful about your claim.
Cheers,
-Paul
>>> +
>>> + val = TRAN_CONF_CMD_EN | op->cmd.opcode;
>>> +
>>> + if (op->addr.nbytes > 0) {
>>> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK,
>>> op->addr.nbytes);
>>> +
>>> + writel(op->addr.val & 0xffffffff, sfc->base +
>>> \x7f\x7fSFC_REG_DEV_ADDR(0));
>>> + writel(op->addr.val >> 32, sfc->base +
>>> \x7f\x7fSFC_REG_DEV_ADDR_PLUS(0));
>>> + }
>>> +
>>> + if (op->dummy.nbytes > 0)
>>> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
>>> + op->dummy.nbytes * 8 / op->dummy.buswidth);
>>> +
>>> + if (op->data.nbytes > 0)
>>> + val |= TRAN_CONF_DATA_EN;
>>> +
>>> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
>>> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
>>> +
>>> + ingenic_sfc_set_transfer_mode(sfc, op);
>>> +
>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>
>> Random 0x1f value here, maybe use a macro?
>
>
> Sure.
>
>
>>
>>> + writel(~(INTC_MASK_END | INTC_MASK_RREQ), sfc->base +
>>> \x7f\x7fSFC_REG_INTC);
>>> +
>>> + writel(0, sfc->base + SFC_REG_MEM_ADDR);
>>> +
>>> + writel(TRIG_FLUSH, sfc->base + SFC_REG_TRIG);
>>> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
>>> +
>>> + ret = wait_for_completion_timeout(&sfc->completion,
>>> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
>>> + if (!ret) {
>>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC
>>> \x7f\x7fdevice\n", __LINE__);
>>> + return -ETIMEDOUT;
>>> + }
>>> +
>>> + ingenic_sfc_read_rxfifo(sfc, op->data.buf.in, op->data.nbytes);
>>> + readl_poll_timeout(sfc->base + SFC_REG_SR, val, val & SR_END,
>>> \x7f\x7f10, 0);
>>
>> Infinite timeout? Is that very wise?
>
>
> Will fix it in the next version.
>
>
>>
>>> +
>>> + writel(INTC_MASK_END | INTC_MASK_RREQ, sfc->base +
>>> SFC_REG_SCR);
>>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int ingenic_sfc_exec_op_dma(struct ingenic_sfc *sfc, const
>>> \x7f\x7fstruct spi_mem_op *op)
>>> +{
>>> + dma_addr_t addr;
>>> + int ret, val;
>>> +
>>> + val = readl(sfc->base + SFC_REG_GLB);
>>> + u32p_replace_bits(&val, op->data.dir == SPI_MEM_DATA_IN ?
>>> + GLB_TRAN_DIR_READ : GLB_TRAN_DIR_WRITE,
>>> GLB_TRAN_DIR_MASK);
>>> + u32p_replace_bits(&val, GLB_OP_MODE_DMA, GLB_OP_MODE_MASK);
>>> + writel(val, sfc->base + SFC_REG_GLB);
>>> +
>>> + val = TRAN_CONF_CMD_EN | op->cmd.opcode;
>>> +
>>> + if (op->addr.nbytes > 0) {
>>> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK,
>>> op->addr.nbytes);
>>> + writel(op->addr.val & 0xffffffff, sfc->base +
>>> \x7f\x7fSFC_REG_DEV_ADDR(0));
>>> + writel(op->addr.val >> 32, sfc->base +
>>> \x7f\x7fSFC_REG_DEV_ADDR_PLUS(0));
>>> + }
>>> +
>>> + if (op->dummy.nbytes > 0)
>>> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
>>> + op->dummy.nbytes * 8 / op->dummy.buswidth);
>>> +
>>> + if (op->data.nbytes > 0)
>>> + val |= TRAN_CONF_DATA_EN;
>>
>> There's a lot of code duplication here with
>> ingenic_sfc_exec_op_pio(). \x7fA lot can be factorized.
>
>
> Sure, I will try to do it.
>
>
>>
>>> +
>>> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
>>> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
>>> +
>>> + ingenic_sfc_set_transfer_mode(sfc, op);
>>> +
>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>> + writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
>>> +
>>> + switch (op->data.dir) {
>>> + case SPI_MEM_DATA_IN:
>>> + addr = dma_map_single(sfc->dev, op->data.buf.in,
>>> \x7f\x7fop->data.nbytes, DMA_FROM_DEVICE);
>>> + if (dma_mapping_error(sfc->dev, addr)) {
>>> + dev_err(sfc->dev, "RX DMA memory not mapped\n");
>>> + return -ENOMEM;
>>> + }
>>> +
>>> + writel(addr, sfc->base + SFC_REG_MEM_ADDR);
>>> + break;
>>> +
>>> + case SPI_MEM_DATA_OUT:
>>> + addr = dma_map_single(sfc->dev, (void *)op->data.buf.out,
>>> + op->data.nbytes, DMA_TO_DEVICE);
>>> + if (dma_mapping_error(sfc->dev, addr)) {
>>> + dev_err(sfc->dev, "TX DMA memory not mapped\n");
>>> + return -ENOMEM;
>>> + }
>>> +
>>> + writel(addr, sfc->base + SFC_REG_MEM_ADDR);
>>> + break;
>>> +
>>> + default:
>>> + return -EINVAL;
>>> + }
>>> +
>>> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
>>> +
>>> + ret = wait_for_completion_timeout(&sfc->completion,
>>> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
>>> + if (!ret) {
>>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC
>>> \x7f\x7fdevice\n", __LINE__);
>>> + return -ETIMEDOUT;
>>> + }
>>> +
>>> + dma_unmap_single(sfc->dev, addr, op->data.nbytes,
>>> + op->data.dir == SPI_MEM_DATA_IN ? DMA_FROM_DEVICE :
>>> \x7f\x7fDMA_TO_DEVICE);
>>
>> Use a small inline function for that too. My personal rule is that
>> ?: \x7fis fine if the line fits in 80 characters, but if you have to
>> break, \x7fthen you really need to move it somewhere else.
>>
>
> Sure.
>
>
>>> +
>>> + writel(INTC_MASK_END, sfc->base + SFC_REG_SCR);
>>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int ingenic_sfc_exec_op(struct spi_mem *mem, const struct
>>> \x7f\x7fspi_mem_op *op)
>>> +{
>>> + struct spi_device *spi = mem->spi;
>>> + struct ingenic_sfc *sfc =
>>> spi_controller_get_devdata(spi->master);
>>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>>> +
>>> + init_completion(&sfc->completion);
>>> +
>>> + switch (op->data.dir) {
>>> + case SPI_MEM_DATA_IN:
>>> + if (sfc->soc_info->version >= ID_X1600 || IS_ALIGNED(addr,
>>> 4))
>>> + break;
>>> +
>>> + fallthrough;
>>> +
>>> + case SPI_MEM_NO_DATA:
>>> + return ingenic_sfc_exec_op_pio(sfc, op);
>>> +
>>> + default:
>>> + break;
>>> + }
>>> +
>>> + return ingenic_sfc_exec_op_dma(sfc, op);
>>> +}
>>> +
>>> +static int ingenic_sfc_poll_status(struct spi_mem *mem, const
>>> struct \x7f\x7fspi_mem_op *op,
>>> + u16 mask, u16 match, unsigned long initial_delay_us,
>>> + unsigned long polling_delay_us, unsigned long
>>> timeout_ms)
>>> +{
>>> + struct spi_device *spi = mem->spi;
>>> + struct ingenic_sfc *sfc =
>>> spi_controller_get_devdata(spi->master);
>>> + int ret, val;
>>> +
>>> + init_completion(&sfc->completion);
>>> +
>>> + val = readl(sfc->base + SFC_REG_GLB);
>>> + u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
>>> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
>>> + writel(val, sfc->base + SFC_REG_GLB);
>>> +
>>> + writel(match, sfc->base + SFC_REG_DEV_STA_EXP);
>>> + writel(mask, sfc->base + SFC_REG_DEV_STA_MSK);
>>> +
>>> + val = TRAN_CONF_POLL_EN | TRAN_CONF_CMD_EN | op->cmd.opcode;
>>> +
>>> + if (op->addr.nbytes > 0) {
>>> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK,
>>> op->addr.nbytes);
>>> +
>>> + writel(op->addr.val & 0xffffffff, sfc->base +
>>> \x7f\x7fSFC_REG_DEV_ADDR(0));
>>> + writel(op->addr.val >> 32, sfc->base +
>>> \x7f\x7fSFC_REG_DEV_ADDR_PLUS(0));
>>> + }
>>> +
>>> + if (op->dummy.nbytes > 0)
>>> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
>>> + op->dummy.nbytes * 8 / op->dummy.buswidth);
>>> +
>>> + if (op->data.nbytes > 0)
>>> + val |= TRAN_CONF_DATA_EN;
>>> +
>>> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
>>> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
>>> +
>>> + ingenic_sfc_set_transfer_mode(sfc, op);
>>> +
>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>> + writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
>>> +
>>> + writel(0, sfc->base + SFC_REG_MEM_ADDR);
>>> +
>>> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
>>> +
>>> + ret = wait_for_completion_timeout(&sfc->completion,
>>> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
>>> + if (!ret) {
>>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC
>>> \x7f\x7fdevice\n", __LINE__);
>>> + return -ETIMEDOUT;
>>> + }
>>> +
>>> + writel(SCR_CLR_END, sfc->base + SFC_REG_SCR);
>>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static const struct spi_controller_mem_ops ingenic_sfc_mem_ops = {
>>> + .adjust_op_size = ingenic_sfc_adjust_op_size,
>>> + .supports_op = ingenic_sfc_supports_op,
>>> + .exec_op = ingenic_sfc_exec_op,
>>> + .poll_status = ingenic_sfc_poll_status,
>>> +};
>>> +
>>> +static int ingenic_sfc_setup(struct spi_device *spi)
>>> +{
>>> + struct ingenic_sfc *sfc =
>>> spi_controller_get_devdata(spi->master);
>>> + unsigned long rate;
>>> + int ret, val;
>>> +
>>> + if (!spi->max_speed_hz)
>>> + return -EINVAL;
>>
>> Maybe set a sane default?
>
>
> Sure.
>
>
>>
>>> +
>>> + ret = clk_set_rate(sfc->clk, spi->max_speed_hz * 2);
>>> + if (ret)
>>> + return -EINVAL;
>>> +
>>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>>> + writel(0, sfc->base + SFC_REG_DEV_CONF);
>>> + writel(0, sfc->base + SFC_REG_CGE);
>>> +
>>> + val = readl(sfc->base + SFC_REG_GLB);
>>> + u32p_replace_bits(&val, 64 - 1, GLB_THRESHOLD_MASK);
>>> + writel(val, sfc->base + SFC_REG_GLB);
>>> +
>>> + val = readl(sfc->base + SFC_REG_DEV_CONF);
>>> +
>>> + /* cpha bit:0 , cpol bit:0 */
>>> + val &= ~(DEV_CONF_CPHA | DEV_CONF_CPOL);
>>> + val |= spi->mode & SPI_CPHA ? DEV_CONF_CPHA : 0;
>>> + val |= spi->mode & SPI_CPOL ? DEV_CONF_CPOL : 0;
>>> +
>>> + /* ce_dl bit:1, hold bit:1, wp bit:1 */
>>> + val |= (DEV_CONF_CE_DL | DEV_CONF_HOLD_DL | DEV_CONF_WP_DL);
>>> +
>>> + writel(val, sfc->base + SFC_REG_DEV_CONF);
>>> +
>>> + val = readl(sfc->base + SFC_REG_GLB);
>>> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
>>> + writel(val, sfc->base + SFC_REG_GLB);
>>> +
>>> + rate = clk_get_rate(sfc->clk);
>>
>> I'd suggest using clk_round_rate() before clk_set_rate() because
>> then \x7fyou know what frequency it's going to be, and you don't have
>> to call \x7fclk_get_rate() afterwards.
>>
>
> Sure, will try.
>
>
> Thanks and best regards!
>
>
>> Cheers,
>> -Paul
>>
>>> + val = readl(sfc->base + SFC_REG_DEV_CONF);
>>> + if (sfc->soc_info->version >= ID_X1600 && rate >= 200000000)
>>> + u32p_replace_bits(&val, DEV_CONF_SMP_DELAY_180DEG,
>>> \x7f\x7fDEV_CONF_SMP_DELAY_MASK);
>>> + else if (sfc->soc_info->version == ID_X1000 && rate >=
>>> 100000000)
>>> + u32p_replace_bits(&val, DEV_CONF_SMP_DELAY_HALF_CYCLE,
>>> \x7f\x7fDEV_CONF_SMP_DELAY_MASK);
>>> + writel(val, sfc->base + SFC_REG_DEV_CONF);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int ingenic_sfc_probe(struct platform_device *pdev)
>>> +{
>>> + struct ingenic_sfc *sfc;
>>> + struct spi_controller *ctlr;
>>> + int ret;
>>> +
>>> + ctlr = spi_alloc_master(&pdev->dev, sizeof(*sfc));
>>> + if (!ctlr)
>>> + return -ENOMEM;
>>> +
>>> + sfc = spi_controller_get_devdata(ctlr);
>>> +
>>> + sfc->soc_info = of_device_get_match_data(&pdev->dev);
>>> + if (!sfc->soc_info) {
>>> + dev_err(&pdev->dev, "No of match data provided\n");
>>> + ret = -ENODEV;
>>> + goto err_put_master;
>>> + }
>>> +
>>> + sfc->base = devm_platform_ioremap_resource(pdev, 0);
>>> + if (IS_ERR(sfc->base)) {
>>> + ret = PTR_ERR(sfc->base);
>>> + goto err_put_master;
>>> + }
>>> +
>>> + sfc->clk = devm_clk_get(&pdev->dev, "sfc");
>>> + if (IS_ERR(sfc->clk)) {
>>> + ret = IS_ERR(sfc->clk);
>>> + goto err_put_master;
>>> + }
>>> +
>>> + ret = clk_prepare_enable(sfc->clk);
>>> + if (ret)
>>> + goto err_put_master;
>>> +
>>> + sfc->irq = platform_get_irq(pdev, 0);
>>> + if (sfc->irq < 0) {
>>> + ret = sfc->irq;
>>> + goto err_put_master;
>>> + }
>>> +
>>> + sfc->dev = &pdev->dev;
>>> +
>>> + platform_set_drvdata(pdev, sfc);
>>> +
>>> + ret = devm_request_irq(&pdev->dev, sfc->irq,
>>> \x7f\x7fingenic_sfc_irq_handler, 0,
>>> + dev_name(&pdev->dev), sfc);
>>> + if (ret) {
>>> + dev_err(&pdev->dev, "Failed to request irq%d, ret = %d\n",
>>> \x7f\x7fsfc->irq, ret);
>>> + goto err_put_master;
>>> + }
>>> +
>>> + ctlr->bus_num = -1;
>>> + ctlr->num_chipselect = 1;
>>> + ctlr->mem_ops = &ingenic_sfc_mem_ops;
>>> + ctlr->dev.of_node = pdev->dev.of_node;
>>> + ctlr->setup = ingenic_sfc_setup;
>>> + ctlr->mode_bits = SPI_CPHA | SPI_CPOL |
>>> + SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
>>> + if (sfc->soc_info->version >= ID_X2000)
>>> + ctlr->mode_bits |= SPI_RX_OCTAL | SPI_TX_OCTAL;
>>> +
>>> + ret = devm_spi_register_controller(&pdev->dev, ctlr);
>>> + if (ret)
>>> + goto err_put_master;
>>> +
>>> + return 0;
>>> +
>>> +err_put_master:
>>> + spi_master_put(ctlr);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static const struct ingenic_soc_info x1000_soc_info = {
>>> + .version = ID_X1000,
>>> +
>>> + .max_bus_width = 4,
>>> +
>>> + .tran_mode_mask = TRAN_CONF_TRAN_MODE_MASK,
>>> +};
>>> +
>>> +static const struct ingenic_soc_info x1600_soc_info = {
>>> + .version = ID_X1600,
>>> +
>>> + .max_bus_width = 4,
>>> +
>>> + .tran_mode_mask = TRAN_CONF_TRAN_MODE_MASK,
>>> +};
>>> +
>>> +static const struct ingenic_soc_info x2000_soc_info = {
>>> + .version = ID_X2000,
>>> +
>>> + .max_bus_width = 8,
>>> +
>>> + .tran_mode_mask = TRAN_CFG1_TRAN_MODE_MASK,
>>> +};
>>> +
>>> +static const struct of_device_id ingenic_sfc_of_matches[] = {
>>> + { .compatible = "ingenic,x1000-sfc", .data = &x1000_soc_info },
>>> + { .compatible = "ingenic,x1600-sfc", .data = &x1600_soc_info },
>>> + { .compatible = "ingenic,x2000-sfc", .data = &x2000_soc_info },
>>> + { /* sentinel */ }
>>> +};
>>> +MODULE_DEVICE_TABLE(of, ingenic_sfc_of_matches);
>>> +
>>> +static struct platform_driver ingenic_sfc_driver = {
>>> + .driver = {
>>> + .name = "ingenic-sfc",
>>> + .of_match_table = ingenic_sfc_of_matches,
>>> + },
>>> + .probe = ingenic_sfc_probe,
>>> +};
>>> +module_platform_driver(ingenic_sfc_driver);
>>> +
>>> +MODULE_LICENSE("GPL");
>>> +MODULE_AUTHOR("周琰杰 (Zhou Yanjie)
>>> <zhouyanjie@wanyeetech.com>");
>>> +MODULE_DESCRIPTION("Ingenic SoCs SPI Flash Controller Driver");
>>> --
>>> 2.7.4
>>>
>>
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
2022-07-23 20:24 ` Paul Cercueil
@ 2022-07-24 15:29 ` Zhou Yanjie
-1 siblings, 0 replies; 78+ messages in thread
From: Zhou Yanjie @ 2022-07-24 15:29 UTC (permalink / raw)
To: Paul Cercueil
Cc: tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, broonie, robh+dt, krzysztof.kozlowski+dt, linux-mtd,
linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, dongsheng.qiu, aric.pzqi, rick.tyliu,
jinghui.liu, sernia.zhou, reimu
Hi Paul,
On 2022/7/24 上午4:24, Paul Cercueil wrote:
> Hi Zhou,
>
> Le dim., juil. 24 2022 at 01:26:56 +0800, Zhou Yanjie
> <zhouyanjie@wanyeetech.com> a écrit :
>> Hi Paul,
>>
>> On 2022/7/23 上午4:03, Paul Cercueil wrote:
>>> Hi Zhou,
>>>
>>>
>>> Le sam., juil. 23 2022 at 00:48:30 +0800, 周琰杰 (Zhou Yanjie)
>>> \x7f<zhouyanjie@wanyeetech.com> a écrit :
>>>> Add SFC support for the X1000 SoC, the X1600 SoC, and the X2000 SoC
>>>> from Ingenic.
>>>>
>>>> Signed-off-by: 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
>>>> ---
>>>> drivers/spi/Kconfig | 9 +
>>>> drivers/spi/Makefile | 1 +
>>>> drivers/spi/spi-ingenic-sfc.c | 662
>>>> \x7f\x7f++++++++++++++++++++++++++++++++++++++++++
>>>> 3 files changed, 672 insertions(+)
>>>> create mode 100644 drivers/spi/spi-ingenic-sfc.c
>>>>
>>>> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
>>>> index 3b1044e..1077bd3 100644
>>>> --- a/drivers/spi/Kconfig
>>>> +++ b/drivers/spi/Kconfig
>>>> @@ -437,6 +437,15 @@ config SPI_INGENIC
>>>> To compile this driver as a module, choose M here: the module
>>>> will be called spi-ingenic.
>>>>
>>>> +config SPI_INGENIC_SFC
>>>> + tristate "Ingenic SoCs SPI Flash Controller"
>>>> + depends on MACH_INGENIC || COMPILE_TEST
>>>> + help
>>>> + This enables support for the Ingenic SoCs SPI flash controller.
>>>> +
>>>> + To compile this driver as a module, choose M here: the module
>>>> + will be called ingenic-sfc.
>>>> +
>>>> config SPI_INTEL
>>>> tristate
>>>>
>>>> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
>>>> index 0f44eb6..f3e42c0 100644
>>>> --- a/drivers/spi/Makefile
>>>> +++ b/drivers/spi/Makefile
>>>> @@ -62,6 +62,7 @@ obj-$(CONFIG_SPI_HISI_SFC_V3XX) +=
>>>> \x7f\x7fspi-hisi-sfc-v3xx.o
>>>> obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o
>>>> obj-$(CONFIG_SPI_IMX) += spi-imx.o
>>>> obj-$(CONFIG_SPI_INGENIC) += spi-ingenic.o
>>>> +obj-$(CONFIG_SPI_INGENIC_SFC) += spi-ingenic-sfc.o
>>>> obj-$(CONFIG_SPI_INTEL) += spi-intel.o
>>>> obj-$(CONFIG_SPI_INTEL_PCI) += spi-intel-pci.o
>>>> obj-$(CONFIG_SPI_INTEL_PLATFORM) += spi-intel-platform.o
>>>> diff --git a/drivers/spi/spi-ingenic-sfc.c
>>>> \x7f\x7fb/drivers/spi/spi-ingenic-sfc.c
>>>> new file mode 100644
>>>> index 00000000..a565546
>>>> --- /dev/null
>>>> +++ b/drivers/spi/spi-ingenic-sfc.c
>>>> @@ -0,0 +1,662 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>>
>>> Dual-license driver? That's not what MODULE_LICENSE() says.
>>>
>>
>> My fault, forgot to modify MODULE_LICENSE(), will fix it in the next
>> version.
>>
>>
>>>> +/*
>>>> + * Ingenic SoCs SPI Flash Controller Driver
>>>> + * Copyright (c) 2022 周琰杰 (Zhou Yanjie)
>>>> <zhouyanjie@wanyeetech.com>
>>>> + */
>>>> +
>>>> +#include <linux/bitfield.h>
>>>> +#include <linux/bitops.h>
>>>> +#include <linux/clk.h>
>>>> +#include <linux/completion.h>
>>>> +#include <linux/dma-mapping.h>
>>>> +#include <linux/interrupt.h>
>>>> +#include <linux/iopoll.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/mtd/mtd.h>
>>>> +#include <linux/of_device.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/slab.h>
>>>> +#include <linux/spi/spi.h>
>>>> +#include <linux/spi/spi-mem.h>
>>>> +
>>>> +/* SFC register offsets */
>>>> +#define SFC_REG_GLB 0x0000
>>>> +#define SFC_REG_DEV_CONF 0x0004
>>>> +#define SFC_REG_DEV_STA_EXP 0x0008
>>>> +#define SFC_REG_DEV_STA_RT 0x000c
>>>> +#define SFC_REG_DEV_STA_MSK 0x0010
>>>> +#define SFC_REG_TRAN_CONF(n) (0x0014 + n * 4)
>>>> +#define SFC_REG_TRAN_CFG0(n) (0x0014 + n * 4)
>>>
>>> You should protect the macro parameter. If you do for instance
>>> \x7fSFC_REG_TRAN_CONF(x + 1) it would resolve to (0x0014 + x + 1 * 4)
>>> \x7fwhich is not what you'd want.
>>>
>>
>> Sure, will fix it.
>>
>>
>>> Also - looks like SFC_REG_TRAN_CONF() and SFC_REG_TRAN_CFG0() are
>>> the \x7fsame thing, that's on purpose?
>>>
>>>> +#define SFC_REG_TRAN_LEN 0x002c
>>>> +#define SFC_REG_DEV_ADDR(n) (0x0030 + n * 4)
>>>> +#define SFC_REG_DEV_ADDR_PLUS(n) (0x0048 + n * 4)
>>>> +#define SFC_REG_MEM_ADDR 0x0060
>>>> +#define SFC_REG_TRIG 0x0064
>>>> +#define SFC_REG_SR 0x0068
>>>> +#define SFC_REG_SCR 0x006c
>>>> +#define SFC_REG_INTC 0x0070
>>>> +#define SFC_REG_FSM 0x0074
>>>> +#define SFC_REG_CGE 0x0078
>>>> +#define SFC_REG_TRAN_CFG1(n) (0x009c + n * 4)
>>>> +#define SFC_REG_DR 0x1000
>>>> +
>>>> +/* bits within the GLB register */
>>>> +#define GLB_TRAN_DIR_MASK GENMASK(13, 13)
>>>> +#define GLB_TRAN_DIR_WRITE 0x1
>>>> +#define GLB_TRAN_DIR_READ 0x0
>>>
>>> When it's a single bit - just use BIT().
>>
>>
>> Sure.
>>
>>
>>>
>>>> +#define GLB_THRESHOLD_MASK GENMASK(12, 7)
>>>> +#define GLB_OP_MODE_MASK GENMASK(6, 6)
>>>
>>> Same here, and I see it a few times below as well.
>>>
>>
>> Sure.
>>
>>
>>>> +#define GLB_OP_MODE_DMA 0x1
>>>> +#define GLB_OP_MODE_SLAVE 0x0
>>>> +#define GLB_PHASE_NUM_MASK GENMASK(5, 3)
>>>> +#define GLB_WP_EN BIT(2)
>>>> +#define GLB_BURST_MD_MASK GENMASK(1, 0)
>>>> +#define GLB_BURST_MD_INCR32 0x3
>>>> +#define GLB_BURST_MD_INCR16 0x2
>>>> +#define GLB_BURST_MD_INCR8 0x1
>>>> +#define GLB_BURST_MD_INCR4 0x0
>>>> +
>>>> +/* bits within the DEV_CONF register */
>>>> +#define DEV_CONF_SMP_DELAY_MASK GENMASK(20, 16)
>>>> +#define DEV_CONF_SMP_DELAY_180DEG 0x4
>>>> +#define DEV_CONF_SMP_DELAY_HALF_CYCLE 0x1
>>>> +#define DEV_CONF_CMD_TYPE_MASK GENMASK(15, 15)
>>>> +#define DEV_CONF_CMD_TYPE_16BIT 0x1
>>>> +#define DEV_CONF_CMD_TYPE_8BIT 0x0
>>>> +#define DEV_CONF_STA_TYPE_MASK GENMASK(14, 13)
>>>> +#define DEV_CONF_THOLD_MASK GENMASK(12, 11)
>>>> +#define DEV_CONF_TSETUP_MASK GENMASK(10, 9)
>>>> +#define DEV_CONF_TSH_MASK GENMASK(8, 5)
>>>> +#define DEV_CONF_CPHA BIT(4)
>>>> +#define DEV_CONF_CPOL BIT(3)
>>>> +#define DEV_CONF_CE_DL BIT(2)
>>>> +#define DEV_CONF_HOLD_DL BIT(1)
>>>> +#define DEV_CONF_WP_DL BIT(0)
>>>> +
>>>> +/* bits within the TRAN_CONF(n) register */
>>>> +#define TRAN_CONF_TRAN_MODE_MASK GENMASK(31, 29)
>>>> +#define TRAN_CONF_ADDR_WIDTH_MASK GENMASK(28, 26)
>>>> +#define TRAN_CONF_POLL_EN BIT(25)
>>>> +#define TRAN_CONF_CMD_EN BIT(24)
>>>> +#define TRAN_CONF_PHASE_FORMAT_MASK GENMASK(23, 23)
>>>> +#define TRAN_CONF_DMY_BITS_MASK GENMASK(22, 17)
>>>> +#define TRAN_CONF_DATA_EN BIT(16)
>>>> +#define TRAN_CONF_CMD_MASK GENMASK(15, 0)
>>>> +
>>>> +/* bits within the TRIG register */
>>>> +#define TRIG_FLUSH BIT(2)
>>>> +#define TRIG_STOP BIT(1)
>>>> +#define TRIG_START BIT(0)
>>>> +
>>>> +/* bits within the SR register */
>>>> +#define SR_FIFO_NUM_MASK GENMASK(22, 16)
>>>> +#define SR_END BIT(4)
>>>> +#define SR_TRAN_REQ BIT(3)
>>>> +#define SR_RECE_REQ BIT(2)
>>>> +#define SR_OVER BIT(1)
>>>> +#define SR_UNDER BIT(0)
>>>> +
>>>> +/* bits within the SCR register */
>>>> +#define SCR_CLR_END BIT(4)
>>>> +#define SCR_CLR_TREQ BIT(3)
>>>> +#define SCR_CLR_RREQ BIT(2)
>>>> +#define SCR_CLR_OVER BIT(1)
>>>> +#define SCR_CLR_UNDER BIT(0)
>>>> +
>>>> +/* bits within the INTC register */
>>>> +#define INTC_MASK_END BIT(4)
>>>> +#define INTC_MASK_TREQ BIT(3)
>>>> +#define INTC_MASK_RREQ BIT(2)
>>>> +#define INTC_MASK_OVER BIT(1)
>>>> +#define INTC_MASK_UNDER BIT(0)
>>>> +
>>>> +/* bits within the TRAN_CFG1(n) register */
>>>> +#define TRAN_CFG1_TRAN_MODE_MASK GENMASK(7, 4)
>>>> +
>>>> +#define TRAN_MODE_STANDARD 0
>>>> +#define TRAN_MODE_DUAL_DATA 1
>>>> +#define TRAN_MODE_DUAL_IO 2
>>>> +#define TRAN_MODE_DUAL_FULL 3
>>>> +#define TRAN_MODE_QUAD_DATA 5
>>>> +#define TRAN_MODE_QUAD_IO 6
>>>> +#define TRAN_MODE_QUAD_FULL 7
>>>> +#define TRAN_MODE_OCTAL_DATA 9
>>>> +#define TRAN_MODE_OCTAL_IO 10
>>>> +#define TRAN_MODE_OCTAL_FULL 11
>>>> +
>>>> +#define INGENIC_SFC_FIFO_SIZE (64 * 4)
>>>> +
>>>> +#define INGENIC_SFC_TRANSFER_TIMEOUT 1000
>>>
>>> Maybe add the unit name in the macro as well -
>>> \x7fINGENIC_SFC_TRANSFER_TIMEOUT_MS.
>>
>>
>> Sure.
>>
>>
>>>
>>>> +
>>>> +enum ingenic_sfc_version {
>>>> + ID_X1000,
>>>> + ID_X1600,
>>>> + ID_X2000,
>>>> +};
>>>> +
>>>> +struct ingenic_soc_info {
>>>> + enum ingenic_sfc_version version;
>>>> +
>>>> + unsigned int max_bus_width;
>>>> +
>>>> + const u32 tran_mode_mask;
>>>> +};
>>>> +
>>>> +struct ingenic_sfc {
>>>> + const struct ingenic_soc_info *soc_info;
>>>> +
>>>> + void __iomem *base;
>>>> + struct device *dev;
>>>> + struct clk *clk;
>>>> + int irq;
>>>> +
>>>> + struct completion completion;
>>>> +};
>>>> +
>>>> +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
>>>> +{
>>>> + struct ingenic_sfc *sfc = data;
>>>> +
>>>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>>>> +
>>>> + complete(&sfc->completion);
>>>> +
>>>> + return IRQ_HANDLED;
>>>> +}
>>>> +
>>>> +static int ingenic_sfc_adjust_op_size(struct spi_mem *mem, struct
>>>> \x7f\x7fspi_mem_op *op)
>>>> +{
>>>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>>>> +
>>>> + if (op->data.nbytes > INGENIC_SFC_FIFO_SIZE &&
>>>> !IS_ALIGNED(addr, \x7f\x7f4))
>>>> + op->data.nbytes = INGENIC_SFC_FIFO_SIZE;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static bool ingenic_sfc_supports_op(struct spi_mem *mem, const
>>>> \x7f\x7fstruct spi_mem_op *op)
>>>> +{
>>>> + struct spi_device *spi = mem->spi;
>>>> + struct ingenic_sfc *sfc =
>>>> spi_controller_get_devdata(spi->master);
>>>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>>>> +
>>>> + /* The controller only supports Standard SPI mode, Duall mode,
>>>> \x7f\x7fQuad mode and Octal mode. */
>>>
>>> Dual*
>>
>>
>> Oops, will fix it.
>>
>>
>>>
>>>> + if (op->cmd.buswidth > sfc->soc_info->max_bus_width ||
>>>> + op->addr.buswidth > sfc->soc_info->max_bus_width ||
>>>> + op->dummy.buswidth > sfc->soc_info->max_bus_width ||
>>>> + op->data.buswidth > sfc->soc_info->max_bus_width)
>>>> + return false;
>>>> +
>>>> + /* Max 32 dummy clock cycles supported */
>>>> + if (op->dummy.nbytes && op->dummy.nbytes * 8 /
>>>> \x7f\x7fop->dummy.buswidth > 32)
>>>> + return false;
>>>> +
>>>> + /* Max rx data length, check controller limits and alignment */
>>>> + if (op->data.dir == SPI_MEM_DATA_IN &&
>>>> + op->data.nbytes > INGENIC_SFC_FIFO_SIZE &&
>>>> !IS_ALIGNED(addr, \x7f\x7f4))
>>>
>>> This does the same check than in ingenic_sfc_adjust_op_size(), maybe
>>> \x7fmove it to a new inline function?
>>>
>>
>> Sure.
>>
>>
>>>> + return false;
>>>> +
>>>> + /* Max 6 bytes address width supported */
>>>> + if (op->addr.nbytes > 6)
>>>> + return false;
>>>> +
>>>> + return spi_mem_default_supports_op(mem, op);
>>>> +}
>>>> +
>>>> +static void ingenic_sfc_set_transfer_mode(struct ingenic_sfc *sfc,
>>>> \x7f\x7fconst struct spi_mem_op *op)
>>>> +{
>>>> + int val;
>>>> +
>>>> + val = readl(sfc->base + (sfc->soc_info->version >= ID_X1600 ?
>>>> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
>>>
>>> As Krzysztof said - ugh.
>>>
>>
>> Will fix it.
>>
>>
>>> Also, instead of having a "version" enum in your soc_info, why not
>>> \x7fjust have a "reg_conf" field that gives you directly the right
>>> register?
>>>
>>
>> We still need a version to distinguish the SFC before X1600 SoC and
>> the SFC after X1600 SoC, because in addition to the difference in the
>> cfg register, another difference is that the SFC before X1600 SoC does
>> not support address unaligned RX DMA transfer, while SFC in X1600 and
>> later SoCs can support this feature.
>
> Then add a .dma_supports_unaligned_address field.
Sure, I will try.
>
>>
>>
>>>
>>>> + val &= ~sfc->soc_info->tran_mode_mask;
>>>> + if (op->cmd.buswidth == 8)
>>>> + val |= (TRAN_MODE_OCTAL_FULL <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>
>>> Looks like you're really trying to reinvent the wheel.
>>>
>>> val |= FIELD_PREP(sfc->soc_info->tran_mode_mask, TRAN_MODE_OCTAL_FULL);
>>>
>>> using <linux/bitfield.h>.
>>>
>>> Also, just define a 'mode' variable and set it in your if/else
>>> blocks, \x7fthat would look much better. Then you can set val |=
>>> FIELD_PREP(..., \x7fmode) at the end.
>>
>>
>> Sure, will change this in the next version.
>>
>>
>>>
>>>> + else if (op->cmd.buswidth == 4)
>>>> + val |= (TRAN_MODE_QUAD_FULL <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>> + else if (op->cmd.buswidth == 2)
>>>> + val |= (TRAN_MODE_DUAL_FULL <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>> + else if (op->addr.buswidth == 8)
>>>> + val |= (TRAN_MODE_OCTAL_IO <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>> + else if (op->addr.buswidth == 4)
>>>> + val |= (TRAN_MODE_QUAD_IO <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>> + else if (op->addr.buswidth == 2)
>>>> + val |= (TRAN_MODE_DUAL_IO <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>> + else if (op->data.buswidth == 8)
>>>> + val |= (TRAN_MODE_OCTAL_DATA <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>> + else if (op->data.buswidth == 4)
>>>> + val |= (TRAN_MODE_QUAD_DATA <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>> + else if (op->data.buswidth == 2)
>>>> + val |= (TRAN_MODE_DUAL_DATA <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>> + else
>>>> + val |= (TRAN_MODE_STANDARD <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>> + writel(val, sfc->base + (sfc->soc_info->version >= ID_X1600 ?
>>>> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
>>>> +}
>>>> +
>>>> +/*
>>>> + * We only need PIO mode to handle the SPI_MEM_NO_DATA transfers,
>>>> + * and the unaligned accesses in SPI_MEM_DATA_IN transfers.
>>>> + */
>>>> +static void ingenic_sfc_read_rxfifo(struct ingenic_sfc *sfc, u8
>>>> *to, \x7f\x7funsigned int len)
>>>> +{
>>>> + void __iomem *from;
>>>> +
>>>> + from = sfc->base + SFC_REG_DR;
>>>> +
>>>> + for (; len >= 4; len -= 4, to += 4) {
>>>> + u32 val = __raw_readl(from);
>>>> + memcpy(to, &val, 4);
>>>
>>> No need to use memcpy for 4 bytes. You can do: put_unaligned(val,
>>> (u32 \x7f*)to);
>>>
>>
>> Sure.
>>
>>
>>>> + }
>>>> +
>>>> + if (len) {
>>>> + u32 val = __raw_readl(from);
>>>> + memcpy(to, &val, len);
>>>
>>> Hmm, I'm not sure that is endian-safe. I would prefer if you copied
>>> \x7fbyte by byte.
>>>
>>
>> Sure.
>>
>>
>>>> + }
>>>> +}
>>>> +
>>>> +static int ingenic_sfc_exec_op_pio(struct ingenic_sfc *sfc, const
>>>> \x7f\x7fstruct spi_mem_op *op)
>>>> +{
>>>> + int ret, val;
>>>> +
>>>> + val = readl(sfc->base + SFC_REG_GLB);
>>>> + u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
>>>> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
>>>> + writel(val, sfc->base + SFC_REG_GLB);
>>>
>>> By the way, have you considered using regmap?
>>>
>>> It would give you things like regmap_update_bits() for this kind of
>>> \x7fthings, and regmap_field() to handle your conf register being at a
>>> \x7fdifferent address across SoCs.
>>>
>>
>> It seems that the overhead of using regmap will be greater than using
>> readl and writel,
>> the purpose of using readl and writel is to minimize overhead and
>> maximize performance. :)
>
> If you really think that this is a valid reason not to use regmap,
> then prove it with a benchmark, because I'm *very* doubtful about your
> claim.
>
I have tested your writel/readl version of the spi driver and the
current regmap
version of the spi driver, and indeed the measured performance
differences are
perceivable. I will do another test for the sfc driver to determine if
it's the
same case. By the way, I'm trying to use interrupts in the spi driver
instead of
using regmap_read_poll_timeout(), which can indeed improve some performance.
Thanks and best regards!
> Cheers,
> -Paul
>
>>>> +
>>>> + val = TRAN_CONF_CMD_EN | op->cmd.opcode;
>>>> +
>>>> + if (op->addr.nbytes > 0) {
>>>> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK,
>>>> op->addr.nbytes);
>>>> +
>>>> + writel(op->addr.val & 0xffffffff, sfc->base +
>>>> \x7f\x7fSFC_REG_DEV_ADDR(0));
>>>> + writel(op->addr.val >> 32, sfc->base +
>>>> \x7f\x7fSFC_REG_DEV_ADDR_PLUS(0));
>>>> + }
>>>> +
>>>> + if (op->dummy.nbytes > 0)
>>>> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
>>>> + op->dummy.nbytes * 8 / op->dummy.buswidth);
>>>> +
>>>> + if (op->data.nbytes > 0)
>>>> + val |= TRAN_CONF_DATA_EN;
>>>> +
>>>> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
>>>> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
>>>> +
>>>> + ingenic_sfc_set_transfer_mode(sfc, op);
>>>> +
>>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>>
>>> Random 0x1f value here, maybe use a macro?
>>
>>
>> Sure.
>>
>>
>>>
>>>> + writel(~(INTC_MASK_END | INTC_MASK_RREQ), sfc->base +
>>>> \x7f\x7fSFC_REG_INTC);
>>>> +
>>>> + writel(0, sfc->base + SFC_REG_MEM_ADDR);
>>>> +
>>>> + writel(TRIG_FLUSH, sfc->base + SFC_REG_TRIG);
>>>> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
>>>> +
>>>> + ret = wait_for_completion_timeout(&sfc->completion,
>>>> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
>>>> + if (!ret) {
>>>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>>> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC
>>>> \x7f\x7fdevice\n", __LINE__);
>>>> + return -ETIMEDOUT;
>>>> + }
>>>> +
>>>> + ingenic_sfc_read_rxfifo(sfc, op->data.buf.in, op->data.nbytes);
>>>> + readl_poll_timeout(sfc->base + SFC_REG_SR, val, val & SR_END,
>>>> \x7f\x7f10, 0);
>>>
>>> Infinite timeout? Is that very wise?
>>
>>
>> Will fix it in the next version.
>>
>>
>>>
>>>> +
>>>> + writel(INTC_MASK_END | INTC_MASK_RREQ, sfc->base + SFC_REG_SCR);
>>>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int ingenic_sfc_exec_op_dma(struct ingenic_sfc *sfc, const
>>>> \x7f\x7fstruct spi_mem_op *op)
>>>> +{
>>>> + dma_addr_t addr;
>>>> + int ret, val;
>>>> +
>>>> + val = readl(sfc->base + SFC_REG_GLB);
>>>> + u32p_replace_bits(&val, op->data.dir == SPI_MEM_DATA_IN ?
>>>> + GLB_TRAN_DIR_READ : GLB_TRAN_DIR_WRITE,
>>>> GLB_TRAN_DIR_MASK);
>>>> + u32p_replace_bits(&val, GLB_OP_MODE_DMA, GLB_OP_MODE_MASK);
>>>> + writel(val, sfc->base + SFC_REG_GLB);
>>>> +
>>>> + val = TRAN_CONF_CMD_EN | op->cmd.opcode;
>>>> +
>>>> + if (op->addr.nbytes > 0) {
>>>> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK,
>>>> op->addr.nbytes);
>>>> + writel(op->addr.val & 0xffffffff, sfc->base +
>>>> \x7f\x7fSFC_REG_DEV_ADDR(0));
>>>> + writel(op->addr.val >> 32, sfc->base +
>>>> \x7f\x7fSFC_REG_DEV_ADDR_PLUS(0));
>>>> + }
>>>> +
>>>> + if (op->dummy.nbytes > 0)
>>>> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
>>>> + op->dummy.nbytes * 8 / op->dummy.buswidth);
>>>> +
>>>> + if (op->data.nbytes > 0)
>>>> + val |= TRAN_CONF_DATA_EN;
>>>
>>> There's a lot of code duplication here with
>>> ingenic_sfc_exec_op_pio(). \x7fA lot can be factorized.
>>
>>
>> Sure, I will try to do it.
>>
>>
>>>
>>>> +
>>>> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
>>>> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
>>>> +
>>>> + ingenic_sfc_set_transfer_mode(sfc, op);
>>>> +
>>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>>> + writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
>>>> +
>>>> + switch (op->data.dir) {
>>>> + case SPI_MEM_DATA_IN:
>>>> + addr = dma_map_single(sfc->dev, op->data.buf.in,
>>>> \x7f\x7fop->data.nbytes, DMA_FROM_DEVICE);
>>>> + if (dma_mapping_error(sfc->dev, addr)) {
>>>> + dev_err(sfc->dev, "RX DMA memory not mapped\n");
>>>> + return -ENOMEM;
>>>> + }
>>>> +
>>>> + writel(addr, sfc->base + SFC_REG_MEM_ADDR);
>>>> + break;
>>>> +
>>>> + case SPI_MEM_DATA_OUT:
>>>> + addr = dma_map_single(sfc->dev, (void *)op->data.buf.out,
>>>> + op->data.nbytes, DMA_TO_DEVICE);
>>>> + if (dma_mapping_error(sfc->dev, addr)) {
>>>> + dev_err(sfc->dev, "TX DMA memory not mapped\n");
>>>> + return -ENOMEM;
>>>> + }
>>>> +
>>>> + writel(addr, sfc->base + SFC_REG_MEM_ADDR);
>>>> + break;
>>>> +
>>>> + default:
>>>> + return -EINVAL;
>>>> + }
>>>> +
>>>> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
>>>> +
>>>> + ret = wait_for_completion_timeout(&sfc->completion,
>>>> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
>>>> + if (!ret) {
>>>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>>> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC
>>>> \x7f\x7fdevice\n", __LINE__);
>>>> + return -ETIMEDOUT;
>>>> + }
>>>> +
>>>> + dma_unmap_single(sfc->dev, addr, op->data.nbytes,
>>>> + op->data.dir == SPI_MEM_DATA_IN ? DMA_FROM_DEVICE :
>>>> \x7f\x7fDMA_TO_DEVICE);
>>>
>>> Use a small inline function for that too. My personal rule is that
>>> ?: \x7fis fine if the line fits in 80 characters, but if you have to
>>> break, \x7fthen you really need to move it somewhere else.
>>>
>>
>> Sure.
>>
>>
>>>> +
>>>> + writel(INTC_MASK_END, sfc->base + SFC_REG_SCR);
>>>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int ingenic_sfc_exec_op(struct spi_mem *mem, const struct
>>>> \x7f\x7fspi_mem_op *op)
>>>> +{
>>>> + struct spi_device *spi = mem->spi;
>>>> + struct ingenic_sfc *sfc =
>>>> spi_controller_get_devdata(spi->master);
>>>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>>>> +
>>>> + init_completion(&sfc->completion);
>>>> +
>>>> + switch (op->data.dir) {
>>>> + case SPI_MEM_DATA_IN:
>>>> + if (sfc->soc_info->version >= ID_X1600 || IS_ALIGNED(addr,
>>>> 4))
>>>> + break;
>>>> +
>>>> + fallthrough;
>>>> +
>>>> + case SPI_MEM_NO_DATA:
>>>> + return ingenic_sfc_exec_op_pio(sfc, op);
>>>> +
>>>> + default:
>>>> + break;
>>>> + }
>>>> +
>>>> + return ingenic_sfc_exec_op_dma(sfc, op);
>>>> +}
>>>> +
>>>> +static int ingenic_sfc_poll_status(struct spi_mem *mem, const
>>>> struct \x7f\x7fspi_mem_op *op,
>>>> + u16 mask, u16 match, unsigned long initial_delay_us,
>>>> + unsigned long polling_delay_us, unsigned long timeout_ms)
>>>> +{
>>>> + struct spi_device *spi = mem->spi;
>>>> + struct ingenic_sfc *sfc =
>>>> spi_controller_get_devdata(spi->master);
>>>> + int ret, val;
>>>> +
>>>> + init_completion(&sfc->completion);
>>>> +
>>>> + val = readl(sfc->base + SFC_REG_GLB);
>>>> + u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
>>>> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
>>>> + writel(val, sfc->base + SFC_REG_GLB);
>>>> +
>>>> + writel(match, sfc->base + SFC_REG_DEV_STA_EXP);
>>>> + writel(mask, sfc->base + SFC_REG_DEV_STA_MSK);
>>>> +
>>>> + val = TRAN_CONF_POLL_EN | TRAN_CONF_CMD_EN | op->cmd.opcode;
>>>> +
>>>> + if (op->addr.nbytes > 0) {
>>>> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK,
>>>> op->addr.nbytes);
>>>> +
>>>> + writel(op->addr.val & 0xffffffff, sfc->base +
>>>> \x7f\x7fSFC_REG_DEV_ADDR(0));
>>>> + writel(op->addr.val >> 32, sfc->base +
>>>> \x7f\x7fSFC_REG_DEV_ADDR_PLUS(0));
>>>> + }
>>>> +
>>>> + if (op->dummy.nbytes > 0)
>>>> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
>>>> + op->dummy.nbytes * 8 / op->dummy.buswidth);
>>>> +
>>>> + if (op->data.nbytes > 0)
>>>> + val |= TRAN_CONF_DATA_EN;
>>>> +
>>>> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
>>>> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
>>>> +
>>>> + ingenic_sfc_set_transfer_mode(sfc, op);
>>>> +
>>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>>> + writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
>>>> +
>>>> + writel(0, sfc->base + SFC_REG_MEM_ADDR);
>>>> +
>>>> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
>>>> +
>>>> + ret = wait_for_completion_timeout(&sfc->completion,
>>>> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
>>>> + if (!ret) {
>>>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>>> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC
>>>> \x7f\x7fdevice\n", __LINE__);
>>>> + return -ETIMEDOUT;
>>>> + }
>>>> +
>>>> + writel(SCR_CLR_END, sfc->base + SFC_REG_SCR);
>>>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static const struct spi_controller_mem_ops ingenic_sfc_mem_ops = {
>>>> + .adjust_op_size = ingenic_sfc_adjust_op_size,
>>>> + .supports_op = ingenic_sfc_supports_op,
>>>> + .exec_op = ingenic_sfc_exec_op,
>>>> + .poll_status = ingenic_sfc_poll_status,
>>>> +};
>>>> +
>>>> +static int ingenic_sfc_setup(struct spi_device *spi)
>>>> +{
>>>> + struct ingenic_sfc *sfc =
>>>> spi_controller_get_devdata(spi->master);
>>>> + unsigned long rate;
>>>> + int ret, val;
>>>> +
>>>> + if (!spi->max_speed_hz)
>>>> + return -EINVAL;
>>>
>>> Maybe set a sane default?
>>
>>
>> Sure.
>>
>>
>>>
>>>> +
>>>> + ret = clk_set_rate(sfc->clk, spi->max_speed_hz * 2);
>>>> + if (ret)
>>>> + return -EINVAL;
>>>> +
>>>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>>>> + writel(0, sfc->base + SFC_REG_DEV_CONF);
>>>> + writel(0, sfc->base + SFC_REG_CGE);
>>>> +
>>>> + val = readl(sfc->base + SFC_REG_GLB);
>>>> + u32p_replace_bits(&val, 64 - 1, GLB_THRESHOLD_MASK);
>>>> + writel(val, sfc->base + SFC_REG_GLB);
>>>> +
>>>> + val = readl(sfc->base + SFC_REG_DEV_CONF);
>>>> +
>>>> + /* cpha bit:0 , cpol bit:0 */
>>>> + val &= ~(DEV_CONF_CPHA | DEV_CONF_CPOL);
>>>> + val |= spi->mode & SPI_CPHA ? DEV_CONF_CPHA : 0;
>>>> + val |= spi->mode & SPI_CPOL ? DEV_CONF_CPOL : 0;
>>>> +
>>>> + /* ce_dl bit:1, hold bit:1, wp bit:1 */
>>>> + val |= (DEV_CONF_CE_DL | DEV_CONF_HOLD_DL | DEV_CONF_WP_DL);
>>>> +
>>>> + writel(val, sfc->base + SFC_REG_DEV_CONF);
>>>> +
>>>> + val = readl(sfc->base + SFC_REG_GLB);
>>>> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
>>>> + writel(val, sfc->base + SFC_REG_GLB);
>>>> +
>>>> + rate = clk_get_rate(sfc->clk);
>>>
>>> I'd suggest using clk_round_rate() before clk_set_rate() because
>>> then \x7fyou know what frequency it's going to be, and you don't have
>>> to call \x7fclk_get_rate() afterwards.
>>>
>>
>> Sure, will try.
>>
>>
>> Thanks and best regards!
>>
>>
>>> Cheers,
>>> -Paul
>>>
>>>> + val = readl(sfc->base + SFC_REG_DEV_CONF);
>>>> + if (sfc->soc_info->version >= ID_X1600 && rate >= 200000000)
>>>> + u32p_replace_bits(&val, DEV_CONF_SMP_DELAY_180DEG,
>>>> \x7f\x7fDEV_CONF_SMP_DELAY_MASK);
>>>> + else if (sfc->soc_info->version == ID_X1000 && rate >= 100000000)
>>>> + u32p_replace_bits(&val, DEV_CONF_SMP_DELAY_HALF_CYCLE,
>>>> \x7f\x7fDEV_CONF_SMP_DELAY_MASK);
>>>> + writel(val, sfc->base + SFC_REG_DEV_CONF);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int ingenic_sfc_probe(struct platform_device *pdev)
>>>> +{
>>>> + struct ingenic_sfc *sfc;
>>>> + struct spi_controller *ctlr;
>>>> + int ret;
>>>> +
>>>> + ctlr = spi_alloc_master(&pdev->dev, sizeof(*sfc));
>>>> + if (!ctlr)
>>>> + return -ENOMEM;
>>>> +
>>>> + sfc = spi_controller_get_devdata(ctlr);
>>>> +
>>>> + sfc->soc_info = of_device_get_match_data(&pdev->dev);
>>>> + if (!sfc->soc_info) {
>>>> + dev_err(&pdev->dev, "No of match data provided\n");
>>>> + ret = -ENODEV;
>>>> + goto err_put_master;
>>>> + }
>>>> +
>>>> + sfc->base = devm_platform_ioremap_resource(pdev, 0);
>>>> + if (IS_ERR(sfc->base)) {
>>>> + ret = PTR_ERR(sfc->base);
>>>> + goto err_put_master;
>>>> + }
>>>> +
>>>> + sfc->clk = devm_clk_get(&pdev->dev, "sfc");
>>>> + if (IS_ERR(sfc->clk)) {
>>>> + ret = IS_ERR(sfc->clk);
>>>> + goto err_put_master;
>>>> + }
>>>> +
>>>> + ret = clk_prepare_enable(sfc->clk);
>>>> + if (ret)
>>>> + goto err_put_master;
>>>> +
>>>> + sfc->irq = platform_get_irq(pdev, 0);
>>>> + if (sfc->irq < 0) {
>>>> + ret = sfc->irq;
>>>> + goto err_put_master;
>>>> + }
>>>> +
>>>> + sfc->dev = &pdev->dev;
>>>> +
>>>> + platform_set_drvdata(pdev, sfc);
>>>> +
>>>> + ret = devm_request_irq(&pdev->dev, sfc->irq,
>>>> \x7f\x7fingenic_sfc_irq_handler, 0,
>>>> + dev_name(&pdev->dev), sfc);
>>>> + if (ret) {
>>>> + dev_err(&pdev->dev, "Failed to request irq%d, ret = %d\n",
>>>> \x7f\x7fsfc->irq, ret);
>>>> + goto err_put_master;
>>>> + }
>>>> +
>>>> + ctlr->bus_num = -1;
>>>> + ctlr->num_chipselect = 1;
>>>> + ctlr->mem_ops = &ingenic_sfc_mem_ops;
>>>> + ctlr->dev.of_node = pdev->dev.of_node;
>>>> + ctlr->setup = ingenic_sfc_setup;
>>>> + ctlr->mode_bits = SPI_CPHA | SPI_CPOL |
>>>> + SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
>>>> + if (sfc->soc_info->version >= ID_X2000)
>>>> + ctlr->mode_bits |= SPI_RX_OCTAL | SPI_TX_OCTAL;
>>>> +
>>>> + ret = devm_spi_register_controller(&pdev->dev, ctlr);
>>>> + if (ret)
>>>> + goto err_put_master;
>>>> +
>>>> + return 0;
>>>> +
>>>> +err_put_master:
>>>> + spi_master_put(ctlr);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static const struct ingenic_soc_info x1000_soc_info = {
>>>> + .version = ID_X1000,
>>>> +
>>>> + .max_bus_width = 4,
>>>> +
>>>> + .tran_mode_mask = TRAN_CONF_TRAN_MODE_MASK,
>>>> +};
>>>> +
>>>> +static const struct ingenic_soc_info x1600_soc_info = {
>>>> + .version = ID_X1600,
>>>> +
>>>> + .max_bus_width = 4,
>>>> +
>>>> + .tran_mode_mask = TRAN_CONF_TRAN_MODE_MASK,
>>>> +};
>>>> +
>>>> +static const struct ingenic_soc_info x2000_soc_info = {
>>>> + .version = ID_X2000,
>>>> +
>>>> + .max_bus_width = 8,
>>>> +
>>>> + .tran_mode_mask = TRAN_CFG1_TRAN_MODE_MASK,
>>>> +};
>>>> +
>>>> +static const struct of_device_id ingenic_sfc_of_matches[] = {
>>>> + { .compatible = "ingenic,x1000-sfc", .data = &x1000_soc_info },
>>>> + { .compatible = "ingenic,x1600-sfc", .data = &x1600_soc_info },
>>>> + { .compatible = "ingenic,x2000-sfc", .data = &x2000_soc_info },
>>>> + { /* sentinel */ }
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, ingenic_sfc_of_matches);
>>>> +
>>>> +static struct platform_driver ingenic_sfc_driver = {
>>>> + .driver = {
>>>> + .name = "ingenic-sfc",
>>>> + .of_match_table = ingenic_sfc_of_matches,
>>>> + },
>>>> + .probe = ingenic_sfc_probe,
>>>> +};
>>>> +module_platform_driver(ingenic_sfc_driver);
>>>> +
>>>> +MODULE_LICENSE("GPL");
>>>> +MODULE_AUTHOR("周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>");
>>>> +MODULE_DESCRIPTION("Ingenic SoCs SPI Flash Controller Driver");
>>>> --
>>>> 2.7.4
>>>>
>>>
>
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
@ 2022-07-24 15:29 ` Zhou Yanjie
0 siblings, 0 replies; 78+ messages in thread
From: Zhou Yanjie @ 2022-07-24 15:29 UTC (permalink / raw)
To: Paul Cercueil
Cc: tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, broonie, robh+dt, krzysztof.kozlowski+dt, linux-mtd,
linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, dongsheng.qiu, aric.pzqi, rick.tyliu,
jinghui.liu, sernia.zhou, reimu
Hi Paul,
On 2022/7/24 上午4:24, Paul Cercueil wrote:
> Hi Zhou,
>
> Le dim., juil. 24 2022 at 01:26:56 +0800, Zhou Yanjie
> <zhouyanjie@wanyeetech.com> a écrit :
>> Hi Paul,
>>
>> On 2022/7/23 上午4:03, Paul Cercueil wrote:
>>> Hi Zhou,
>>>
>>>
>>> Le sam., juil. 23 2022 at 00:48:30 +0800, 周琰杰 (Zhou Yanjie)
>>> \x7f<zhouyanjie@wanyeetech.com> a écrit :
>>>> Add SFC support for the X1000 SoC, the X1600 SoC, and the X2000 SoC
>>>> from Ingenic.
>>>>
>>>> Signed-off-by: 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
>>>> ---
>>>> drivers/spi/Kconfig | 9 +
>>>> drivers/spi/Makefile | 1 +
>>>> drivers/spi/spi-ingenic-sfc.c | 662
>>>> \x7f\x7f++++++++++++++++++++++++++++++++++++++++++
>>>> 3 files changed, 672 insertions(+)
>>>> create mode 100644 drivers/spi/spi-ingenic-sfc.c
>>>>
>>>> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
>>>> index 3b1044e..1077bd3 100644
>>>> --- a/drivers/spi/Kconfig
>>>> +++ b/drivers/spi/Kconfig
>>>> @@ -437,6 +437,15 @@ config SPI_INGENIC
>>>> To compile this driver as a module, choose M here: the module
>>>> will be called spi-ingenic.
>>>>
>>>> +config SPI_INGENIC_SFC
>>>> + tristate "Ingenic SoCs SPI Flash Controller"
>>>> + depends on MACH_INGENIC || COMPILE_TEST
>>>> + help
>>>> + This enables support for the Ingenic SoCs SPI flash controller.
>>>> +
>>>> + To compile this driver as a module, choose M here: the module
>>>> + will be called ingenic-sfc.
>>>> +
>>>> config SPI_INTEL
>>>> tristate
>>>>
>>>> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
>>>> index 0f44eb6..f3e42c0 100644
>>>> --- a/drivers/spi/Makefile
>>>> +++ b/drivers/spi/Makefile
>>>> @@ -62,6 +62,7 @@ obj-$(CONFIG_SPI_HISI_SFC_V3XX) +=
>>>> \x7f\x7fspi-hisi-sfc-v3xx.o
>>>> obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o
>>>> obj-$(CONFIG_SPI_IMX) += spi-imx.o
>>>> obj-$(CONFIG_SPI_INGENIC) += spi-ingenic.o
>>>> +obj-$(CONFIG_SPI_INGENIC_SFC) += spi-ingenic-sfc.o
>>>> obj-$(CONFIG_SPI_INTEL) += spi-intel.o
>>>> obj-$(CONFIG_SPI_INTEL_PCI) += spi-intel-pci.o
>>>> obj-$(CONFIG_SPI_INTEL_PLATFORM) += spi-intel-platform.o
>>>> diff --git a/drivers/spi/spi-ingenic-sfc.c
>>>> \x7f\x7fb/drivers/spi/spi-ingenic-sfc.c
>>>> new file mode 100644
>>>> index 00000000..a565546
>>>> --- /dev/null
>>>> +++ b/drivers/spi/spi-ingenic-sfc.c
>>>> @@ -0,0 +1,662 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>>
>>> Dual-license driver? That's not what MODULE_LICENSE() says.
>>>
>>
>> My fault, forgot to modify MODULE_LICENSE(), will fix it in the next
>> version.
>>
>>
>>>> +/*
>>>> + * Ingenic SoCs SPI Flash Controller Driver
>>>> + * Copyright (c) 2022 周琰杰 (Zhou Yanjie)
>>>> <zhouyanjie@wanyeetech.com>
>>>> + */
>>>> +
>>>> +#include <linux/bitfield.h>
>>>> +#include <linux/bitops.h>
>>>> +#include <linux/clk.h>
>>>> +#include <linux/completion.h>
>>>> +#include <linux/dma-mapping.h>
>>>> +#include <linux/interrupt.h>
>>>> +#include <linux/iopoll.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/mtd/mtd.h>
>>>> +#include <linux/of_device.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/slab.h>
>>>> +#include <linux/spi/spi.h>
>>>> +#include <linux/spi/spi-mem.h>
>>>> +
>>>> +/* SFC register offsets */
>>>> +#define SFC_REG_GLB 0x0000
>>>> +#define SFC_REG_DEV_CONF 0x0004
>>>> +#define SFC_REG_DEV_STA_EXP 0x0008
>>>> +#define SFC_REG_DEV_STA_RT 0x000c
>>>> +#define SFC_REG_DEV_STA_MSK 0x0010
>>>> +#define SFC_REG_TRAN_CONF(n) (0x0014 + n * 4)
>>>> +#define SFC_REG_TRAN_CFG0(n) (0x0014 + n * 4)
>>>
>>> You should protect the macro parameter. If you do for instance
>>> \x7fSFC_REG_TRAN_CONF(x + 1) it would resolve to (0x0014 + x + 1 * 4)
>>> \x7fwhich is not what you'd want.
>>>
>>
>> Sure, will fix it.
>>
>>
>>> Also - looks like SFC_REG_TRAN_CONF() and SFC_REG_TRAN_CFG0() are
>>> the \x7fsame thing, that's on purpose?
>>>
>>>> +#define SFC_REG_TRAN_LEN 0x002c
>>>> +#define SFC_REG_DEV_ADDR(n) (0x0030 + n * 4)
>>>> +#define SFC_REG_DEV_ADDR_PLUS(n) (0x0048 + n * 4)
>>>> +#define SFC_REG_MEM_ADDR 0x0060
>>>> +#define SFC_REG_TRIG 0x0064
>>>> +#define SFC_REG_SR 0x0068
>>>> +#define SFC_REG_SCR 0x006c
>>>> +#define SFC_REG_INTC 0x0070
>>>> +#define SFC_REG_FSM 0x0074
>>>> +#define SFC_REG_CGE 0x0078
>>>> +#define SFC_REG_TRAN_CFG1(n) (0x009c + n * 4)
>>>> +#define SFC_REG_DR 0x1000
>>>> +
>>>> +/* bits within the GLB register */
>>>> +#define GLB_TRAN_DIR_MASK GENMASK(13, 13)
>>>> +#define GLB_TRAN_DIR_WRITE 0x1
>>>> +#define GLB_TRAN_DIR_READ 0x0
>>>
>>> When it's a single bit - just use BIT().
>>
>>
>> Sure.
>>
>>
>>>
>>>> +#define GLB_THRESHOLD_MASK GENMASK(12, 7)
>>>> +#define GLB_OP_MODE_MASK GENMASK(6, 6)
>>>
>>> Same here, and I see it a few times below as well.
>>>
>>
>> Sure.
>>
>>
>>>> +#define GLB_OP_MODE_DMA 0x1
>>>> +#define GLB_OP_MODE_SLAVE 0x0
>>>> +#define GLB_PHASE_NUM_MASK GENMASK(5, 3)
>>>> +#define GLB_WP_EN BIT(2)
>>>> +#define GLB_BURST_MD_MASK GENMASK(1, 0)
>>>> +#define GLB_BURST_MD_INCR32 0x3
>>>> +#define GLB_BURST_MD_INCR16 0x2
>>>> +#define GLB_BURST_MD_INCR8 0x1
>>>> +#define GLB_BURST_MD_INCR4 0x0
>>>> +
>>>> +/* bits within the DEV_CONF register */
>>>> +#define DEV_CONF_SMP_DELAY_MASK GENMASK(20, 16)
>>>> +#define DEV_CONF_SMP_DELAY_180DEG 0x4
>>>> +#define DEV_CONF_SMP_DELAY_HALF_CYCLE 0x1
>>>> +#define DEV_CONF_CMD_TYPE_MASK GENMASK(15, 15)
>>>> +#define DEV_CONF_CMD_TYPE_16BIT 0x1
>>>> +#define DEV_CONF_CMD_TYPE_8BIT 0x0
>>>> +#define DEV_CONF_STA_TYPE_MASK GENMASK(14, 13)
>>>> +#define DEV_CONF_THOLD_MASK GENMASK(12, 11)
>>>> +#define DEV_CONF_TSETUP_MASK GENMASK(10, 9)
>>>> +#define DEV_CONF_TSH_MASK GENMASK(8, 5)
>>>> +#define DEV_CONF_CPHA BIT(4)
>>>> +#define DEV_CONF_CPOL BIT(3)
>>>> +#define DEV_CONF_CE_DL BIT(2)
>>>> +#define DEV_CONF_HOLD_DL BIT(1)
>>>> +#define DEV_CONF_WP_DL BIT(0)
>>>> +
>>>> +/* bits within the TRAN_CONF(n) register */
>>>> +#define TRAN_CONF_TRAN_MODE_MASK GENMASK(31, 29)
>>>> +#define TRAN_CONF_ADDR_WIDTH_MASK GENMASK(28, 26)
>>>> +#define TRAN_CONF_POLL_EN BIT(25)
>>>> +#define TRAN_CONF_CMD_EN BIT(24)
>>>> +#define TRAN_CONF_PHASE_FORMAT_MASK GENMASK(23, 23)
>>>> +#define TRAN_CONF_DMY_BITS_MASK GENMASK(22, 17)
>>>> +#define TRAN_CONF_DATA_EN BIT(16)
>>>> +#define TRAN_CONF_CMD_MASK GENMASK(15, 0)
>>>> +
>>>> +/* bits within the TRIG register */
>>>> +#define TRIG_FLUSH BIT(2)
>>>> +#define TRIG_STOP BIT(1)
>>>> +#define TRIG_START BIT(0)
>>>> +
>>>> +/* bits within the SR register */
>>>> +#define SR_FIFO_NUM_MASK GENMASK(22, 16)
>>>> +#define SR_END BIT(4)
>>>> +#define SR_TRAN_REQ BIT(3)
>>>> +#define SR_RECE_REQ BIT(2)
>>>> +#define SR_OVER BIT(1)
>>>> +#define SR_UNDER BIT(0)
>>>> +
>>>> +/* bits within the SCR register */
>>>> +#define SCR_CLR_END BIT(4)
>>>> +#define SCR_CLR_TREQ BIT(3)
>>>> +#define SCR_CLR_RREQ BIT(2)
>>>> +#define SCR_CLR_OVER BIT(1)
>>>> +#define SCR_CLR_UNDER BIT(0)
>>>> +
>>>> +/* bits within the INTC register */
>>>> +#define INTC_MASK_END BIT(4)
>>>> +#define INTC_MASK_TREQ BIT(3)
>>>> +#define INTC_MASK_RREQ BIT(2)
>>>> +#define INTC_MASK_OVER BIT(1)
>>>> +#define INTC_MASK_UNDER BIT(0)
>>>> +
>>>> +/* bits within the TRAN_CFG1(n) register */
>>>> +#define TRAN_CFG1_TRAN_MODE_MASK GENMASK(7, 4)
>>>> +
>>>> +#define TRAN_MODE_STANDARD 0
>>>> +#define TRAN_MODE_DUAL_DATA 1
>>>> +#define TRAN_MODE_DUAL_IO 2
>>>> +#define TRAN_MODE_DUAL_FULL 3
>>>> +#define TRAN_MODE_QUAD_DATA 5
>>>> +#define TRAN_MODE_QUAD_IO 6
>>>> +#define TRAN_MODE_QUAD_FULL 7
>>>> +#define TRAN_MODE_OCTAL_DATA 9
>>>> +#define TRAN_MODE_OCTAL_IO 10
>>>> +#define TRAN_MODE_OCTAL_FULL 11
>>>> +
>>>> +#define INGENIC_SFC_FIFO_SIZE (64 * 4)
>>>> +
>>>> +#define INGENIC_SFC_TRANSFER_TIMEOUT 1000
>>>
>>> Maybe add the unit name in the macro as well -
>>> \x7fINGENIC_SFC_TRANSFER_TIMEOUT_MS.
>>
>>
>> Sure.
>>
>>
>>>
>>>> +
>>>> +enum ingenic_sfc_version {
>>>> + ID_X1000,
>>>> + ID_X1600,
>>>> + ID_X2000,
>>>> +};
>>>> +
>>>> +struct ingenic_soc_info {
>>>> + enum ingenic_sfc_version version;
>>>> +
>>>> + unsigned int max_bus_width;
>>>> +
>>>> + const u32 tran_mode_mask;
>>>> +};
>>>> +
>>>> +struct ingenic_sfc {
>>>> + const struct ingenic_soc_info *soc_info;
>>>> +
>>>> + void __iomem *base;
>>>> + struct device *dev;
>>>> + struct clk *clk;
>>>> + int irq;
>>>> +
>>>> + struct completion completion;
>>>> +};
>>>> +
>>>> +static irqreturn_t ingenic_sfc_irq_handler(int irq, void *data)
>>>> +{
>>>> + struct ingenic_sfc *sfc = data;
>>>> +
>>>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>>>> +
>>>> + complete(&sfc->completion);
>>>> +
>>>> + return IRQ_HANDLED;
>>>> +}
>>>> +
>>>> +static int ingenic_sfc_adjust_op_size(struct spi_mem *mem, struct
>>>> \x7f\x7fspi_mem_op *op)
>>>> +{
>>>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>>>> +
>>>> + if (op->data.nbytes > INGENIC_SFC_FIFO_SIZE &&
>>>> !IS_ALIGNED(addr, \x7f\x7f4))
>>>> + op->data.nbytes = INGENIC_SFC_FIFO_SIZE;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static bool ingenic_sfc_supports_op(struct spi_mem *mem, const
>>>> \x7f\x7fstruct spi_mem_op *op)
>>>> +{
>>>> + struct spi_device *spi = mem->spi;
>>>> + struct ingenic_sfc *sfc =
>>>> spi_controller_get_devdata(spi->master);
>>>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>>>> +
>>>> + /* The controller only supports Standard SPI mode, Duall mode,
>>>> \x7f\x7fQuad mode and Octal mode. */
>>>
>>> Dual*
>>
>>
>> Oops, will fix it.
>>
>>
>>>
>>>> + if (op->cmd.buswidth > sfc->soc_info->max_bus_width ||
>>>> + op->addr.buswidth > sfc->soc_info->max_bus_width ||
>>>> + op->dummy.buswidth > sfc->soc_info->max_bus_width ||
>>>> + op->data.buswidth > sfc->soc_info->max_bus_width)
>>>> + return false;
>>>> +
>>>> + /* Max 32 dummy clock cycles supported */
>>>> + if (op->dummy.nbytes && op->dummy.nbytes * 8 /
>>>> \x7f\x7fop->dummy.buswidth > 32)
>>>> + return false;
>>>> +
>>>> + /* Max rx data length, check controller limits and alignment */
>>>> + if (op->data.dir == SPI_MEM_DATA_IN &&
>>>> + op->data.nbytes > INGENIC_SFC_FIFO_SIZE &&
>>>> !IS_ALIGNED(addr, \x7f\x7f4))
>>>
>>> This does the same check than in ingenic_sfc_adjust_op_size(), maybe
>>> \x7fmove it to a new inline function?
>>>
>>
>> Sure.
>>
>>
>>>> + return false;
>>>> +
>>>> + /* Max 6 bytes address width supported */
>>>> + if (op->addr.nbytes > 6)
>>>> + return false;
>>>> +
>>>> + return spi_mem_default_supports_op(mem, op);
>>>> +}
>>>> +
>>>> +static void ingenic_sfc_set_transfer_mode(struct ingenic_sfc *sfc,
>>>> \x7f\x7fconst struct spi_mem_op *op)
>>>> +{
>>>> + int val;
>>>> +
>>>> + val = readl(sfc->base + (sfc->soc_info->version >= ID_X1600 ?
>>>> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
>>>
>>> As Krzysztof said - ugh.
>>>
>>
>> Will fix it.
>>
>>
>>> Also, instead of having a "version" enum in your soc_info, why not
>>> \x7fjust have a "reg_conf" field that gives you directly the right
>>> register?
>>>
>>
>> We still need a version to distinguish the SFC before X1600 SoC and
>> the SFC after X1600 SoC, because in addition to the difference in the
>> cfg register, another difference is that the SFC before X1600 SoC does
>> not support address unaligned RX DMA transfer, while SFC in X1600 and
>> later SoCs can support this feature.
>
> Then add a .dma_supports_unaligned_address field.
Sure, I will try.
>
>>
>>
>>>
>>>> + val &= ~sfc->soc_info->tran_mode_mask;
>>>> + if (op->cmd.buswidth == 8)
>>>> + val |= (TRAN_MODE_OCTAL_FULL <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>
>>> Looks like you're really trying to reinvent the wheel.
>>>
>>> val |= FIELD_PREP(sfc->soc_info->tran_mode_mask, TRAN_MODE_OCTAL_FULL);
>>>
>>> using <linux/bitfield.h>.
>>>
>>> Also, just define a 'mode' variable and set it in your if/else
>>> blocks, \x7fthat would look much better. Then you can set val |=
>>> FIELD_PREP(..., \x7fmode) at the end.
>>
>>
>> Sure, will change this in the next version.
>>
>>
>>>
>>>> + else if (op->cmd.buswidth == 4)
>>>> + val |= (TRAN_MODE_QUAD_FULL <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>> + else if (op->cmd.buswidth == 2)
>>>> + val |= (TRAN_MODE_DUAL_FULL <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>> + else if (op->addr.buswidth == 8)
>>>> + val |= (TRAN_MODE_OCTAL_IO <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>> + else if (op->addr.buswidth == 4)
>>>> + val |= (TRAN_MODE_QUAD_IO <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>> + else if (op->addr.buswidth == 2)
>>>> + val |= (TRAN_MODE_DUAL_IO <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>> + else if (op->data.buswidth == 8)
>>>> + val |= (TRAN_MODE_OCTAL_DATA <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>> + else if (op->data.buswidth == 4)
>>>> + val |= (TRAN_MODE_QUAD_DATA <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>> + else if (op->data.buswidth == 2)
>>>> + val |= (TRAN_MODE_DUAL_DATA <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>> + else
>>>> + val |= (TRAN_MODE_STANDARD <<
>>>> \x7f\x7f(ffs(sfc->soc_info->tran_mode_mask) - 1)) &
>>>> + sfc->soc_info->tran_mode_mask;
>>>> + writel(val, sfc->base + (sfc->soc_info->version >= ID_X1600 ?
>>>> + SFC_REG_TRAN_CFG1(0) : SFC_REG_TRAN_CONF(0)));
>>>> +}
>>>> +
>>>> +/*
>>>> + * We only need PIO mode to handle the SPI_MEM_NO_DATA transfers,
>>>> + * and the unaligned accesses in SPI_MEM_DATA_IN transfers.
>>>> + */
>>>> +static void ingenic_sfc_read_rxfifo(struct ingenic_sfc *sfc, u8
>>>> *to, \x7f\x7funsigned int len)
>>>> +{
>>>> + void __iomem *from;
>>>> +
>>>> + from = sfc->base + SFC_REG_DR;
>>>> +
>>>> + for (; len >= 4; len -= 4, to += 4) {
>>>> + u32 val = __raw_readl(from);
>>>> + memcpy(to, &val, 4);
>>>
>>> No need to use memcpy for 4 bytes. You can do: put_unaligned(val,
>>> (u32 \x7f*)to);
>>>
>>
>> Sure.
>>
>>
>>>> + }
>>>> +
>>>> + if (len) {
>>>> + u32 val = __raw_readl(from);
>>>> + memcpy(to, &val, len);
>>>
>>> Hmm, I'm not sure that is endian-safe. I would prefer if you copied
>>> \x7fbyte by byte.
>>>
>>
>> Sure.
>>
>>
>>>> + }
>>>> +}
>>>> +
>>>> +static int ingenic_sfc_exec_op_pio(struct ingenic_sfc *sfc, const
>>>> \x7f\x7fstruct spi_mem_op *op)
>>>> +{
>>>> + int ret, val;
>>>> +
>>>> + val = readl(sfc->base + SFC_REG_GLB);
>>>> + u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
>>>> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
>>>> + writel(val, sfc->base + SFC_REG_GLB);
>>>
>>> By the way, have you considered using regmap?
>>>
>>> It would give you things like regmap_update_bits() for this kind of
>>> \x7fthings, and regmap_field() to handle your conf register being at a
>>> \x7fdifferent address across SoCs.
>>>
>>
>> It seems that the overhead of using regmap will be greater than using
>> readl and writel,
>> the purpose of using readl and writel is to minimize overhead and
>> maximize performance. :)
>
> If you really think that this is a valid reason not to use regmap,
> then prove it with a benchmark, because I'm *very* doubtful about your
> claim.
>
I have tested your writel/readl version of the spi driver and the
current regmap
version of the spi driver, and indeed the measured performance
differences are
perceivable. I will do another test for the sfc driver to determine if
it's the
same case. By the way, I'm trying to use interrupts in the spi driver
instead of
using regmap_read_poll_timeout(), which can indeed improve some performance.
Thanks and best regards!
> Cheers,
> -Paul
>
>>>> +
>>>> + val = TRAN_CONF_CMD_EN | op->cmd.opcode;
>>>> +
>>>> + if (op->addr.nbytes > 0) {
>>>> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK,
>>>> op->addr.nbytes);
>>>> +
>>>> + writel(op->addr.val & 0xffffffff, sfc->base +
>>>> \x7f\x7fSFC_REG_DEV_ADDR(0));
>>>> + writel(op->addr.val >> 32, sfc->base +
>>>> \x7f\x7fSFC_REG_DEV_ADDR_PLUS(0));
>>>> + }
>>>> +
>>>> + if (op->dummy.nbytes > 0)
>>>> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
>>>> + op->dummy.nbytes * 8 / op->dummy.buswidth);
>>>> +
>>>> + if (op->data.nbytes > 0)
>>>> + val |= TRAN_CONF_DATA_EN;
>>>> +
>>>> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
>>>> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
>>>> +
>>>> + ingenic_sfc_set_transfer_mode(sfc, op);
>>>> +
>>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>>
>>> Random 0x1f value here, maybe use a macro?
>>
>>
>> Sure.
>>
>>
>>>
>>>> + writel(~(INTC_MASK_END | INTC_MASK_RREQ), sfc->base +
>>>> \x7f\x7fSFC_REG_INTC);
>>>> +
>>>> + writel(0, sfc->base + SFC_REG_MEM_ADDR);
>>>> +
>>>> + writel(TRIG_FLUSH, sfc->base + SFC_REG_TRIG);
>>>> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
>>>> +
>>>> + ret = wait_for_completion_timeout(&sfc->completion,
>>>> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
>>>> + if (!ret) {
>>>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>>> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC
>>>> \x7f\x7fdevice\n", __LINE__);
>>>> + return -ETIMEDOUT;
>>>> + }
>>>> +
>>>> + ingenic_sfc_read_rxfifo(sfc, op->data.buf.in, op->data.nbytes);
>>>> + readl_poll_timeout(sfc->base + SFC_REG_SR, val, val & SR_END,
>>>> \x7f\x7f10, 0);
>>>
>>> Infinite timeout? Is that very wise?
>>
>>
>> Will fix it in the next version.
>>
>>
>>>
>>>> +
>>>> + writel(INTC_MASK_END | INTC_MASK_RREQ, sfc->base + SFC_REG_SCR);
>>>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int ingenic_sfc_exec_op_dma(struct ingenic_sfc *sfc, const
>>>> \x7f\x7fstruct spi_mem_op *op)
>>>> +{
>>>> + dma_addr_t addr;
>>>> + int ret, val;
>>>> +
>>>> + val = readl(sfc->base + SFC_REG_GLB);
>>>> + u32p_replace_bits(&val, op->data.dir == SPI_MEM_DATA_IN ?
>>>> + GLB_TRAN_DIR_READ : GLB_TRAN_DIR_WRITE,
>>>> GLB_TRAN_DIR_MASK);
>>>> + u32p_replace_bits(&val, GLB_OP_MODE_DMA, GLB_OP_MODE_MASK);
>>>> + writel(val, sfc->base + SFC_REG_GLB);
>>>> +
>>>> + val = TRAN_CONF_CMD_EN | op->cmd.opcode;
>>>> +
>>>> + if (op->addr.nbytes > 0) {
>>>> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK,
>>>> op->addr.nbytes);
>>>> + writel(op->addr.val & 0xffffffff, sfc->base +
>>>> \x7f\x7fSFC_REG_DEV_ADDR(0));
>>>> + writel(op->addr.val >> 32, sfc->base +
>>>> \x7f\x7fSFC_REG_DEV_ADDR_PLUS(0));
>>>> + }
>>>> +
>>>> + if (op->dummy.nbytes > 0)
>>>> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
>>>> + op->dummy.nbytes * 8 / op->dummy.buswidth);
>>>> +
>>>> + if (op->data.nbytes > 0)
>>>> + val |= TRAN_CONF_DATA_EN;
>>>
>>> There's a lot of code duplication here with
>>> ingenic_sfc_exec_op_pio(). \x7fA lot can be factorized.
>>
>>
>> Sure, I will try to do it.
>>
>>
>>>
>>>> +
>>>> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
>>>> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
>>>> +
>>>> + ingenic_sfc_set_transfer_mode(sfc, op);
>>>> +
>>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>>> + writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
>>>> +
>>>> + switch (op->data.dir) {
>>>> + case SPI_MEM_DATA_IN:
>>>> + addr = dma_map_single(sfc->dev, op->data.buf.in,
>>>> \x7f\x7fop->data.nbytes, DMA_FROM_DEVICE);
>>>> + if (dma_mapping_error(sfc->dev, addr)) {
>>>> + dev_err(sfc->dev, "RX DMA memory not mapped\n");
>>>> + return -ENOMEM;
>>>> + }
>>>> +
>>>> + writel(addr, sfc->base + SFC_REG_MEM_ADDR);
>>>> + break;
>>>> +
>>>> + case SPI_MEM_DATA_OUT:
>>>> + addr = dma_map_single(sfc->dev, (void *)op->data.buf.out,
>>>> + op->data.nbytes, DMA_TO_DEVICE);
>>>> + if (dma_mapping_error(sfc->dev, addr)) {
>>>> + dev_err(sfc->dev, "TX DMA memory not mapped\n");
>>>> + return -ENOMEM;
>>>> + }
>>>> +
>>>> + writel(addr, sfc->base + SFC_REG_MEM_ADDR);
>>>> + break;
>>>> +
>>>> + default:
>>>> + return -EINVAL;
>>>> + }
>>>> +
>>>> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
>>>> +
>>>> + ret = wait_for_completion_timeout(&sfc->completion,
>>>> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
>>>> + if (!ret) {
>>>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>>> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC
>>>> \x7f\x7fdevice\n", __LINE__);
>>>> + return -ETIMEDOUT;
>>>> + }
>>>> +
>>>> + dma_unmap_single(sfc->dev, addr, op->data.nbytes,
>>>> + op->data.dir == SPI_MEM_DATA_IN ? DMA_FROM_DEVICE :
>>>> \x7f\x7fDMA_TO_DEVICE);
>>>
>>> Use a small inline function for that too. My personal rule is that
>>> ?: \x7fis fine if the line fits in 80 characters, but if you have to
>>> break, \x7fthen you really need to move it somewhere else.
>>>
>>
>> Sure.
>>
>>
>>>> +
>>>> + writel(INTC_MASK_END, sfc->base + SFC_REG_SCR);
>>>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int ingenic_sfc_exec_op(struct spi_mem *mem, const struct
>>>> \x7f\x7fspi_mem_op *op)
>>>> +{
>>>> + struct spi_device *spi = mem->spi;
>>>> + struct ingenic_sfc *sfc =
>>>> spi_controller_get_devdata(spi->master);
>>>> + uintptr_t addr = (uintptr_t)op->data.buf.in;
>>>> +
>>>> + init_completion(&sfc->completion);
>>>> +
>>>> + switch (op->data.dir) {
>>>> + case SPI_MEM_DATA_IN:
>>>> + if (sfc->soc_info->version >= ID_X1600 || IS_ALIGNED(addr,
>>>> 4))
>>>> + break;
>>>> +
>>>> + fallthrough;
>>>> +
>>>> + case SPI_MEM_NO_DATA:
>>>> + return ingenic_sfc_exec_op_pio(sfc, op);
>>>> +
>>>> + default:
>>>> + break;
>>>> + }
>>>> +
>>>> + return ingenic_sfc_exec_op_dma(sfc, op);
>>>> +}
>>>> +
>>>> +static int ingenic_sfc_poll_status(struct spi_mem *mem, const
>>>> struct \x7f\x7fspi_mem_op *op,
>>>> + u16 mask, u16 match, unsigned long initial_delay_us,
>>>> + unsigned long polling_delay_us, unsigned long timeout_ms)
>>>> +{
>>>> + struct spi_device *spi = mem->spi;
>>>> + struct ingenic_sfc *sfc =
>>>> spi_controller_get_devdata(spi->master);
>>>> + int ret, val;
>>>> +
>>>> + init_completion(&sfc->completion);
>>>> +
>>>> + val = readl(sfc->base + SFC_REG_GLB);
>>>> + u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
>>>> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
>>>> + writel(val, sfc->base + SFC_REG_GLB);
>>>> +
>>>> + writel(match, sfc->base + SFC_REG_DEV_STA_EXP);
>>>> + writel(mask, sfc->base + SFC_REG_DEV_STA_MSK);
>>>> +
>>>> + val = TRAN_CONF_POLL_EN | TRAN_CONF_CMD_EN | op->cmd.opcode;
>>>> +
>>>> + if (op->addr.nbytes > 0) {
>>>> + val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK,
>>>> op->addr.nbytes);
>>>> +
>>>> + writel(op->addr.val & 0xffffffff, sfc->base +
>>>> \x7f\x7fSFC_REG_DEV_ADDR(0));
>>>> + writel(op->addr.val >> 32, sfc->base +
>>>> \x7f\x7fSFC_REG_DEV_ADDR_PLUS(0));
>>>> + }
>>>> +
>>>> + if (op->dummy.nbytes > 0)
>>>> + val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
>>>> + op->dummy.nbytes * 8 / op->dummy.buswidth);
>>>> +
>>>> + if (op->data.nbytes > 0)
>>>> + val |= TRAN_CONF_DATA_EN;
>>>> +
>>>> + writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
>>>> + writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
>>>> +
>>>> + ingenic_sfc_set_transfer_mode(sfc, op);
>>>> +
>>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>>> + writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
>>>> +
>>>> + writel(0, sfc->base + SFC_REG_MEM_ADDR);
>>>> +
>>>> + writel(TRIG_START, sfc->base + SFC_REG_TRIG);
>>>> +
>>>> + ret = wait_for_completion_timeout(&sfc->completion,
>>>> + msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
>>>> + if (!ret) {
>>>> + writel(0x1f, sfc->base + SFC_REG_INTC);
>>>> + writel(0x1f, sfc->base + SFC_REG_SCR);
>>>> + dev_err(sfc->dev, "line:%d Timeout for ACK from SFC
>>>> \x7f\x7fdevice\n", __LINE__);
>>>> + return -ETIMEDOUT;
>>>> + }
>>>> +
>>>> + writel(SCR_CLR_END, sfc->base + SFC_REG_SCR);
>>>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static const struct spi_controller_mem_ops ingenic_sfc_mem_ops = {
>>>> + .adjust_op_size = ingenic_sfc_adjust_op_size,
>>>> + .supports_op = ingenic_sfc_supports_op,
>>>> + .exec_op = ingenic_sfc_exec_op,
>>>> + .poll_status = ingenic_sfc_poll_status,
>>>> +};
>>>> +
>>>> +static int ingenic_sfc_setup(struct spi_device *spi)
>>>> +{
>>>> + struct ingenic_sfc *sfc =
>>>> spi_controller_get_devdata(spi->master);
>>>> + unsigned long rate;
>>>> + int ret, val;
>>>> +
>>>> + if (!spi->max_speed_hz)
>>>> + return -EINVAL;
>>>
>>> Maybe set a sane default?
>>
>>
>> Sure.
>>
>>
>>>
>>>> +
>>>> + ret = clk_set_rate(sfc->clk, spi->max_speed_hz * 2);
>>>> + if (ret)
>>>> + return -EINVAL;
>>>> +
>>>> + writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
>>>> + writel(0, sfc->base + SFC_REG_DEV_CONF);
>>>> + writel(0, sfc->base + SFC_REG_CGE);
>>>> +
>>>> + val = readl(sfc->base + SFC_REG_GLB);
>>>> + u32p_replace_bits(&val, 64 - 1, GLB_THRESHOLD_MASK);
>>>> + writel(val, sfc->base + SFC_REG_GLB);
>>>> +
>>>> + val = readl(sfc->base + SFC_REG_DEV_CONF);
>>>> +
>>>> + /* cpha bit:0 , cpol bit:0 */
>>>> + val &= ~(DEV_CONF_CPHA | DEV_CONF_CPOL);
>>>> + val |= spi->mode & SPI_CPHA ? DEV_CONF_CPHA : 0;
>>>> + val |= spi->mode & SPI_CPOL ? DEV_CONF_CPOL : 0;
>>>> +
>>>> + /* ce_dl bit:1, hold bit:1, wp bit:1 */
>>>> + val |= (DEV_CONF_CE_DL | DEV_CONF_HOLD_DL | DEV_CONF_WP_DL);
>>>> +
>>>> + writel(val, sfc->base + SFC_REG_DEV_CONF);
>>>> +
>>>> + val = readl(sfc->base + SFC_REG_GLB);
>>>> + u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
>>>> + writel(val, sfc->base + SFC_REG_GLB);
>>>> +
>>>> + rate = clk_get_rate(sfc->clk);
>>>
>>> I'd suggest using clk_round_rate() before clk_set_rate() because
>>> then \x7fyou know what frequency it's going to be, and you don't have
>>> to call \x7fclk_get_rate() afterwards.
>>>
>>
>> Sure, will try.
>>
>>
>> Thanks and best regards!
>>
>>
>>> Cheers,
>>> -Paul
>>>
>>>> + val = readl(sfc->base + SFC_REG_DEV_CONF);
>>>> + if (sfc->soc_info->version >= ID_X1600 && rate >= 200000000)
>>>> + u32p_replace_bits(&val, DEV_CONF_SMP_DELAY_180DEG,
>>>> \x7f\x7fDEV_CONF_SMP_DELAY_MASK);
>>>> + else if (sfc->soc_info->version == ID_X1000 && rate >= 100000000)
>>>> + u32p_replace_bits(&val, DEV_CONF_SMP_DELAY_HALF_CYCLE,
>>>> \x7f\x7fDEV_CONF_SMP_DELAY_MASK);
>>>> + writel(val, sfc->base + SFC_REG_DEV_CONF);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int ingenic_sfc_probe(struct platform_device *pdev)
>>>> +{
>>>> + struct ingenic_sfc *sfc;
>>>> + struct spi_controller *ctlr;
>>>> + int ret;
>>>> +
>>>> + ctlr = spi_alloc_master(&pdev->dev, sizeof(*sfc));
>>>> + if (!ctlr)
>>>> + return -ENOMEM;
>>>> +
>>>> + sfc = spi_controller_get_devdata(ctlr);
>>>> +
>>>> + sfc->soc_info = of_device_get_match_data(&pdev->dev);
>>>> + if (!sfc->soc_info) {
>>>> + dev_err(&pdev->dev, "No of match data provided\n");
>>>> + ret = -ENODEV;
>>>> + goto err_put_master;
>>>> + }
>>>> +
>>>> + sfc->base = devm_platform_ioremap_resource(pdev, 0);
>>>> + if (IS_ERR(sfc->base)) {
>>>> + ret = PTR_ERR(sfc->base);
>>>> + goto err_put_master;
>>>> + }
>>>> +
>>>> + sfc->clk = devm_clk_get(&pdev->dev, "sfc");
>>>> + if (IS_ERR(sfc->clk)) {
>>>> + ret = IS_ERR(sfc->clk);
>>>> + goto err_put_master;
>>>> + }
>>>> +
>>>> + ret = clk_prepare_enable(sfc->clk);
>>>> + if (ret)
>>>> + goto err_put_master;
>>>> +
>>>> + sfc->irq = platform_get_irq(pdev, 0);
>>>> + if (sfc->irq < 0) {
>>>> + ret = sfc->irq;
>>>> + goto err_put_master;
>>>> + }
>>>> +
>>>> + sfc->dev = &pdev->dev;
>>>> +
>>>> + platform_set_drvdata(pdev, sfc);
>>>> +
>>>> + ret = devm_request_irq(&pdev->dev, sfc->irq,
>>>> \x7f\x7fingenic_sfc_irq_handler, 0,
>>>> + dev_name(&pdev->dev), sfc);
>>>> + if (ret) {
>>>> + dev_err(&pdev->dev, "Failed to request irq%d, ret = %d\n",
>>>> \x7f\x7fsfc->irq, ret);
>>>> + goto err_put_master;
>>>> + }
>>>> +
>>>> + ctlr->bus_num = -1;
>>>> + ctlr->num_chipselect = 1;
>>>> + ctlr->mem_ops = &ingenic_sfc_mem_ops;
>>>> + ctlr->dev.of_node = pdev->dev.of_node;
>>>> + ctlr->setup = ingenic_sfc_setup;
>>>> + ctlr->mode_bits = SPI_CPHA | SPI_CPOL |
>>>> + SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
>>>> + if (sfc->soc_info->version >= ID_X2000)
>>>> + ctlr->mode_bits |= SPI_RX_OCTAL | SPI_TX_OCTAL;
>>>> +
>>>> + ret = devm_spi_register_controller(&pdev->dev, ctlr);
>>>> + if (ret)
>>>> + goto err_put_master;
>>>> +
>>>> + return 0;
>>>> +
>>>> +err_put_master:
>>>> + spi_master_put(ctlr);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static const struct ingenic_soc_info x1000_soc_info = {
>>>> + .version = ID_X1000,
>>>> +
>>>> + .max_bus_width = 4,
>>>> +
>>>> + .tran_mode_mask = TRAN_CONF_TRAN_MODE_MASK,
>>>> +};
>>>> +
>>>> +static const struct ingenic_soc_info x1600_soc_info = {
>>>> + .version = ID_X1600,
>>>> +
>>>> + .max_bus_width = 4,
>>>> +
>>>> + .tran_mode_mask = TRAN_CONF_TRAN_MODE_MASK,
>>>> +};
>>>> +
>>>> +static const struct ingenic_soc_info x2000_soc_info = {
>>>> + .version = ID_X2000,
>>>> +
>>>> + .max_bus_width = 8,
>>>> +
>>>> + .tran_mode_mask = TRAN_CFG1_TRAN_MODE_MASK,
>>>> +};
>>>> +
>>>> +static const struct of_device_id ingenic_sfc_of_matches[] = {
>>>> + { .compatible = "ingenic,x1000-sfc", .data = &x1000_soc_info },
>>>> + { .compatible = "ingenic,x1600-sfc", .data = &x1600_soc_info },
>>>> + { .compatible = "ingenic,x2000-sfc", .data = &x2000_soc_info },
>>>> + { /* sentinel */ }
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, ingenic_sfc_of_matches);
>>>> +
>>>> +static struct platform_driver ingenic_sfc_driver = {
>>>> + .driver = {
>>>> + .name = "ingenic-sfc",
>>>> + .of_match_table = ingenic_sfc_of_matches,
>>>> + },
>>>> + .probe = ingenic_sfc_probe,
>>>> +};
>>>> +module_platform_driver(ingenic_sfc_driver);
>>>> +
>>>> +MODULE_LICENSE("GPL");
>>>> +MODULE_AUTHOR("周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>");
>>>> +MODULE_DESCRIPTION("Ingenic SoCs SPI Flash Controller Driver");
>>>> --
>>>> 2.7.4
>>>>
>>>
>
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
2022-07-22 16:48 ` 周琰杰 (Zhou Yanjie)
(?)
@ 2022-07-23 15:15 ` Christophe JAILLET
-1 siblings, 0 replies; 78+ messages in thread
From: Christophe JAILLET @ 2022-07-23 15:15 UTC (permalink / raw)
To: 周琰杰 (Zhou Yanjie),
tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, broonie, robh+dt, krzysztof.kozlowski+dt
Cc: linux-mtd, linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, paul, dongsheng.qiu, aric.pzqi,
rick.tyliu, jinghui.liu, sernia.zhou, reimu
Le 22/07/2022 à 18:48, 周琰杰 (Zhou Yanjie) a écrit :
> Add SFC support for the X1000 SoC, the X1600 SoC, and the X2000 SoC
> from Ingenic.
>
> Signed-off-by: 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
> ---
> drivers/spi/Kconfig | 9 +
> drivers/spi/Makefile | 1 +
> drivers/spi/spi-ingenic-sfc.c | 662 ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 672 insertions(+)
> create mode 100644 drivers/spi/spi-ingenic-sfc.c
>
[...]
> +static int ingenic_sfc_probe(struct platform_device *pdev)
> +{
> + struct ingenic_sfc *sfc;
> + struct spi_controller *ctlr;
> + int ret;
> +
> + ctlr = spi_alloc_master(&pdev->dev, sizeof(*sfc));
> + if (!ctlr)
> + return -ENOMEM;
devm_spi_alloc_master()? (+ error handling simplification)
Or there should be a .remove() function.
CJ
> +
> + sfc = spi_controller_get_devdata(ctlr);
> +
> + sfc->soc_info = of_device_get_match_data(&pdev->dev);
> + if (!sfc->soc_info) {
> + dev_err(&pdev->dev, "No of match data provided\n");
> + ret = -ENODEV;
> + goto err_put_master;
> + }
> +
[...]
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
@ 2022-07-23 15:15 ` Christophe JAILLET
0 siblings, 0 replies; 78+ messages in thread
From: Christophe JAILLET @ 2022-07-23 15:15 UTC (permalink / raw)
To: 周琰杰 (Zhou Yanjie),
tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, broonie, robh+dt, krzysztof.kozlowski+dt
Cc: linux-mtd, linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, paul, dongsheng.qiu, aric.pzqi,
rick.tyliu, jinghui.liu, sernia.zhou, reimu
Le 22/07/2022 à 18:48, 周琰杰 (Zhou Yanjie) a écrit :
> Add SFC support for the X1000 SoC, the X1600 SoC, and the X2000 SoC
> from Ingenic.
>
> Signed-off-by: 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
> ---
> drivers/spi/Kconfig | 9 +
> drivers/spi/Makefile | 1 +
> drivers/spi/spi-ingenic-sfc.c | 662 ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 672 insertions(+)
> create mode 100644 drivers/spi/spi-ingenic-sfc.c
>
[...]
> +static int ingenic_sfc_probe(struct platform_device *pdev)
> +{
> + struct ingenic_sfc *sfc;
> + struct spi_controller *ctlr;
> + int ret;
> +
> + ctlr = spi_alloc_master(&pdev->dev, sizeof(*sfc));
> + if (!ctlr)
> + return -ENOMEM;
devm_spi_alloc_master()? (+ error handling simplification)
Or there should be a .remove() function.
CJ
> +
> + sfc = spi_controller_get_devdata(ctlr);
> +
> + sfc->soc_info = of_device_get_match_data(&pdev->dev);
> + if (!sfc->soc_info) {
> + dev_err(&pdev->dev, "No of match data provided\n");
> + ret = -ENODEV;
> + goto err_put_master;
> + }
> +
[...]
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
@ 2022-07-23 15:15 ` Christophe JAILLET
0 siblings, 0 replies; 78+ messages in thread
From: Christophe JAILLET @ 2022-07-23 15:15 UTC (permalink / raw)
To: linux-mtd
Cc: linux-mips, linux-spi, linux-kernel, devicetree, linux-mtd,
linux-spi, linux-kernel, devicetree
Le 22/07/2022 à 18:48, 周琰杰 (Zhou Yanjie) a écrit :
> Add SFC support for the X1000 SoC, the X1600 SoC, and the X2000 SoC
> from Ingenic.
>
> Signed-off-by: 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
> ---
> drivers/spi/Kconfig | 9 +
> drivers/spi/Makefile | 1 +
> drivers/spi/spi-ingenic-sfc.c | 662 ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 672 insertions(+)
> create mode 100644 drivers/spi/spi-ingenic-sfc.c
>
[...]
> +static int ingenic_sfc_probe(struct platform_device *pdev)
> +{
> + struct ingenic_sfc *sfc;
> + struct spi_controller *ctlr;
> + int ret;
> +
> + ctlr = spi_alloc_master(&pdev->dev, sizeof(*sfc));
> + if (!ctlr)
> + return -ENOMEM;
devm_spi_alloc_master()? (+ error handling simplification)
Or there should be a .remove() function.
CJ
> +
> + sfc = spi_controller_get_devdata(ctlr);
> +
> + sfc->soc_info = of_device_get_match_data(&pdev->dev);
> + if (!sfc->soc_info) {
> + dev_err(&pdev->dev, "No of match data provided\n");
> + ret = -ENODEV;
> + goto err_put_master;
> + }
> +
[...]
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
2022-07-23 15:15 ` Christophe JAILLET
@ 2022-07-24 1:22 ` Zhou Yanjie
-1 siblings, 0 replies; 78+ messages in thread
From: Zhou Yanjie @ 2022-07-24 1:22 UTC (permalink / raw)
To: Christophe JAILLET, tudor.ambarus, p.yadav, michael,
miquel.raynal, richard, vigneshr, broonie, robh+dt,
krzysztof.kozlowski+dt
Cc: linux-mtd, linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, paul, dongsheng.qiu, aric.pzqi,
rick.tyliu, jinghui.liu, sernia.zhou, reimu
Hi Christophe,
On 2022/7/23 下午11:15, Christophe JAILLET wrote:
> Le 22/07/2022 à 18:48, 周琰杰 (Zhou Yanjie) a écrit :
>> Add SFC support for the X1000 SoC, the X1600 SoC, and the X2000 SoC
>> from Ingenic.
>>
>> Signed-off-by: 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
>> ---
>> drivers/spi/Kconfig | 9 +
>> drivers/spi/Makefile | 1 +
>> drivers/spi/spi-ingenic-sfc.c | 662
>> ++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 672 insertions(+)
>> create mode 100644 drivers/spi/spi-ingenic-sfc.c
>>
>
> [...]
>
>> +static int ingenic_sfc_probe(struct platform_device *pdev)
>> +{
>> + struct ingenic_sfc *sfc;
>> + struct spi_controller *ctlr;
>> + int ret;
>> +
>> + ctlr = spi_alloc_master(&pdev->dev, sizeof(*sfc));
>> + if (!ctlr)
>> + return -ENOMEM;
>
> devm_spi_alloc_master()? (+ error handling simplification)
> Or there should be a .remove() function.
>
Sure, will do in the next version.
Thanks and best regards!
> CJ
>
>> +
>> + sfc = spi_controller_get_devdata(ctlr);
>> +
>> + sfc->soc_info = of_device_get_match_data(&pdev->dev);
>> + if (!sfc->soc_info) {
>> + dev_err(&pdev->dev, "No of match data provided\n");
>> + ret = -ENODEV;
>> + goto err_put_master;
>> + }
>> +
>
> [...]
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
@ 2022-07-24 1:22 ` Zhou Yanjie
0 siblings, 0 replies; 78+ messages in thread
From: Zhou Yanjie @ 2022-07-24 1:22 UTC (permalink / raw)
To: Christophe JAILLET, tudor.ambarus, p.yadav, michael,
miquel.raynal, richard, vigneshr, broonie, robh+dt,
krzysztof.kozlowski+dt
Cc: linux-mtd, linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, paul, dongsheng.qiu, aric.pzqi,
rick.tyliu, jinghui.liu, sernia.zhou, reimu
Hi Christophe,
On 2022/7/23 下午11:15, Christophe JAILLET wrote:
> Le 22/07/2022 à 18:48, 周琰杰 (Zhou Yanjie) a écrit :
>> Add SFC support for the X1000 SoC, the X1600 SoC, and the X2000 SoC
>> from Ingenic.
>>
>> Signed-off-by: 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
>> ---
>> drivers/spi/Kconfig | 9 +
>> drivers/spi/Makefile | 1 +
>> drivers/spi/spi-ingenic-sfc.c | 662
>> ++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 672 insertions(+)
>> create mode 100644 drivers/spi/spi-ingenic-sfc.c
>>
>
> [...]
>
>> +static int ingenic_sfc_probe(struct platform_device *pdev)
>> +{
>> + struct ingenic_sfc *sfc;
>> + struct spi_controller *ctlr;
>> + int ret;
>> +
>> + ctlr = spi_alloc_master(&pdev->dev, sizeof(*sfc));
>> + if (!ctlr)
>> + return -ENOMEM;
>
> devm_spi_alloc_master()? (+ error handling simplification)
> Or there should be a .remove() function.
>
Sure, will do in the next version.
Thanks and best regards!
> CJ
>
>> +
>> + sfc = spi_controller_get_devdata(ctlr);
>> +
>> + sfc->soc_info = of_device_get_match_data(&pdev->dev);
>> + if (!sfc->soc_info) {
>> + dev_err(&pdev->dev, "No of match data provided\n");
>> + ret = -ENODEV;
>> + goto err_put_master;
>> + }
>> +
>
> [...]
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
2022-07-22 16:48 ` 周琰杰 (Zhou Yanjie)
@ 2022-07-24 0:43 ` kernel test robot
-1 siblings, 0 replies; 78+ messages in thread
From: kernel test robot @ 2022-07-24 0:43 UTC (permalink / raw)
To: 周琰杰 (Zhou Yanjie),
tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, broonie, robh+dt, krzysztof.kozlowski+dt
Cc: kbuild-all, linux-mtd, linux-spi, linux-mips, linux-kernel,
devicetree, aidanmacdonald.0x0, tmn505, paul, dongsheng.qiu,
aric.pzqi, rick.tyliu, jinghui.liu, sernia.zhou, reimu
Hi "周琰杰,
Thank you for the patch! Perhaps something to improve:
[auto build test WARNING on broonie-spi/for-next]
[also build test WARNING on mtd/spi-nor/next linus/master v5.19-rc7 next-20220722]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Zhou-Yanjie/Add-SFC-support-for-Ingenic-SoCs/20220723-005011
base: https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git for-next
config: alpha-allyesconfig (https://download.01.org/0day-ci/archive/20220724/202207240839.YeZ8uQ0T-lkp@intel.com/config)
compiler: alpha-linux-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# https://github.com/intel-lab-lkp/linux/commit/0d9d0e60238025a04d428e64e18211c037229284
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review Zhou-Yanjie/Add-SFC-support-for-Ingenic-SoCs/20220723-005011
git checkout 0d9d0e60238025a04d428e64e18211c037229284
# save the config file
mkdir build_dir && cp config build_dir/.config
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=alpha SHELL=/bin/bash drivers/spi/
If you fix the issue, kindly add following tag where applicable
Reported-by: kernel test robot <lkp@intel.com>
All warnings (new ones prefixed by >>):
drivers/spi/spi-ingenic-sfc.c: In function 'ingenic_sfc_exec_op_pio':
>> drivers/spi/spi-ingenic-sfc.c:299:16: warning: conversion from 'long unsigned int' to 'u32' {aka 'unsigned int'} changes value from '18446744073709551595' to '4294967275' [-Woverflow]
299 | writel(~(INTC_MASK_END | INTC_MASK_RREQ), sfc->base + SFC_REG_INTC);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/spi/spi-ingenic-sfc.c: In function 'ingenic_sfc_exec_op_dma':
drivers/spi/spi-ingenic-sfc.c:356:16: warning: conversion from 'long unsigned int' to 'u32' {aka 'unsigned int'} changes value from '18446744073709551599' to '4294967279' [-Woverflow]
356 | writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
drivers/spi/spi-ingenic-sfc.c: In function 'ingenic_sfc_poll_status':
drivers/spi/spi-ingenic-sfc.c:469:16: warning: conversion from 'long unsigned int' to 'u32' {aka 'unsigned int'} changes value from '18446744073709551599' to '4294967279' [-Woverflow]
469 | writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
vim +299 drivers/spi/spi-ingenic-sfc.c
267
268 static int ingenic_sfc_exec_op_pio(struct ingenic_sfc *sfc, const struct spi_mem_op *op)
269 {
270 int ret, val;
271
272 val = readl(sfc->base + SFC_REG_GLB);
273 u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
274 u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
275 writel(val, sfc->base + SFC_REG_GLB);
276
277 val = TRAN_CONF_CMD_EN | op->cmd.opcode;
278
279 if (op->addr.nbytes > 0) {
280 val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK, op->addr.nbytes);
281
282 writel(op->addr.val & 0xffffffff, sfc->base + SFC_REG_DEV_ADDR(0));
283 writel(op->addr.val >> 32, sfc->base + SFC_REG_DEV_ADDR_PLUS(0));
284 }
285
286 if (op->dummy.nbytes > 0)
287 val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
288 op->dummy.nbytes * 8 / op->dummy.buswidth);
289
290 if (op->data.nbytes > 0)
291 val |= TRAN_CONF_DATA_EN;
292
293 writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
294 writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
295
296 ingenic_sfc_set_transfer_mode(sfc, op);
297
298 writel(0x1f, sfc->base + SFC_REG_SCR);
> 299 writel(~(INTC_MASK_END | INTC_MASK_RREQ), sfc->base + SFC_REG_INTC);
300
301 writel(0, sfc->base + SFC_REG_MEM_ADDR);
302
303 writel(TRIG_FLUSH, sfc->base + SFC_REG_TRIG);
304 writel(TRIG_START, sfc->base + SFC_REG_TRIG);
305
306 ret = wait_for_completion_timeout(&sfc->completion,
307 msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
308 if (!ret) {
309 writel(0x1f, sfc->base + SFC_REG_INTC);
310 writel(0x1f, sfc->base + SFC_REG_SCR);
311 dev_err(sfc->dev, "line:%d Timeout for ACK from SFC device\n", __LINE__);
312 return -ETIMEDOUT;
313 }
314
315 ingenic_sfc_read_rxfifo(sfc, op->data.buf.in, op->data.nbytes);
316 readl_poll_timeout(sfc->base + SFC_REG_SR, val, val & SR_END, 10, 0);
317
318 writel(INTC_MASK_END | INTC_MASK_RREQ, sfc->base + SFC_REG_SCR);
319 writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
320
321 return 0;
322 }
323
--
0-DAY CI Kernel Test Service
https://01.org/lkp
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
@ 2022-07-24 0:43 ` kernel test robot
0 siblings, 0 replies; 78+ messages in thread
From: kernel test robot @ 2022-07-24 0:43 UTC (permalink / raw)
To: 周琰杰 (Zhou Yanjie),
tudor.ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, broonie, robh+dt, krzysztof.kozlowski+dt
Cc: kbuild-all, linux-mtd, linux-spi, linux-mips, linux-kernel,
devicetree, aidanmacdonald.0x0, tmn505, paul, dongsheng.qiu,
aric.pzqi, rick.tyliu, jinghui.liu, sernia.zhou, reimu
Hi "周琰杰,
Thank you for the patch! Perhaps something to improve:
[auto build test WARNING on broonie-spi/for-next]
[also build test WARNING on mtd/spi-nor/next linus/master v5.19-rc7 next-20220722]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Zhou-Yanjie/Add-SFC-support-for-Ingenic-SoCs/20220723-005011
base: https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git for-next
config: alpha-allyesconfig (https://download.01.org/0day-ci/archive/20220724/202207240839.YeZ8uQ0T-lkp@intel.com/config)
compiler: alpha-linux-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# https://github.com/intel-lab-lkp/linux/commit/0d9d0e60238025a04d428e64e18211c037229284
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review Zhou-Yanjie/Add-SFC-support-for-Ingenic-SoCs/20220723-005011
git checkout 0d9d0e60238025a04d428e64e18211c037229284
# save the config file
mkdir build_dir && cp config build_dir/.config
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=alpha SHELL=/bin/bash drivers/spi/
If you fix the issue, kindly add following tag where applicable
Reported-by: kernel test robot <lkp@intel.com>
All warnings (new ones prefixed by >>):
drivers/spi/spi-ingenic-sfc.c: In function 'ingenic_sfc_exec_op_pio':
>> drivers/spi/spi-ingenic-sfc.c:299:16: warning: conversion from 'long unsigned int' to 'u32' {aka 'unsigned int'} changes value from '18446744073709551595' to '4294967275' [-Woverflow]
299 | writel(~(INTC_MASK_END | INTC_MASK_RREQ), sfc->base + SFC_REG_INTC);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/spi/spi-ingenic-sfc.c: In function 'ingenic_sfc_exec_op_dma':
drivers/spi/spi-ingenic-sfc.c:356:16: warning: conversion from 'long unsigned int' to 'u32' {aka 'unsigned int'} changes value from '18446744073709551599' to '4294967279' [-Woverflow]
356 | writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
drivers/spi/spi-ingenic-sfc.c: In function 'ingenic_sfc_poll_status':
drivers/spi/spi-ingenic-sfc.c:469:16: warning: conversion from 'long unsigned int' to 'u32' {aka 'unsigned int'} changes value from '18446744073709551599' to '4294967279' [-Woverflow]
469 | writel(~INTC_MASK_END, sfc->base + SFC_REG_INTC);
vim +299 drivers/spi/spi-ingenic-sfc.c
267
268 static int ingenic_sfc_exec_op_pio(struct ingenic_sfc *sfc, const struct spi_mem_op *op)
269 {
270 int ret, val;
271
272 val = readl(sfc->base + SFC_REG_GLB);
273 u32p_replace_bits(&val, GLB_TRAN_DIR_READ, GLB_TRAN_DIR_MASK);
274 u32p_replace_bits(&val, GLB_OP_MODE_SLAVE, GLB_OP_MODE_MASK);
275 writel(val, sfc->base + SFC_REG_GLB);
276
277 val = TRAN_CONF_CMD_EN | op->cmd.opcode;
278
279 if (op->addr.nbytes > 0) {
280 val |= FIELD_PREP(TRAN_CONF_ADDR_WIDTH_MASK, op->addr.nbytes);
281
282 writel(op->addr.val & 0xffffffff, sfc->base + SFC_REG_DEV_ADDR(0));
283 writel(op->addr.val >> 32, sfc->base + SFC_REG_DEV_ADDR_PLUS(0));
284 }
285
286 if (op->dummy.nbytes > 0)
287 val |= FIELD_PREP(TRAN_CONF_DMY_BITS_MASK,
288 op->dummy.nbytes * 8 / op->dummy.buswidth);
289
290 if (op->data.nbytes > 0)
291 val |= TRAN_CONF_DATA_EN;
292
293 writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
294 writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
295
296 ingenic_sfc_set_transfer_mode(sfc, op);
297
298 writel(0x1f, sfc->base + SFC_REG_SCR);
> 299 writel(~(INTC_MASK_END | INTC_MASK_RREQ), sfc->base + SFC_REG_INTC);
300
301 writel(0, sfc->base + SFC_REG_MEM_ADDR);
302
303 writel(TRIG_FLUSH, sfc->base + SFC_REG_TRIG);
304 writel(TRIG_START, sfc->base + SFC_REG_TRIG);
305
306 ret = wait_for_completion_timeout(&sfc->completion,
307 msecs_to_jiffies(INGENIC_SFC_TRANSFER_TIMEOUT));
308 if (!ret) {
309 writel(0x1f, sfc->base + SFC_REG_INTC);
310 writel(0x1f, sfc->base + SFC_REG_SCR);
311 dev_err(sfc->dev, "line:%d Timeout for ACK from SFC device\n", __LINE__);
312 return -ETIMEDOUT;
313 }
314
315 ingenic_sfc_read_rxfifo(sfc, op->data.buf.in, op->data.nbytes);
316 readl_poll_timeout(sfc->base + SFC_REG_SR, val, val & SR_END, 10, 0);
317
318 writel(INTC_MASK_END | INTC_MASK_RREQ, sfc->base + SFC_REG_SCR);
319 writel(TRIG_STOP, sfc->base + SFC_REG_TRIG);
320
321 return 0;
322 }
323
--
0-DAY CI Kernel Test Service
https://01.org/lkp
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
2022-07-24 0:43 ` kernel test robot
@ 2022-07-24 1:28 ` Vanessa Page
-1 siblings, 0 replies; 78+ messages in thread
From: Vanessa Page @ 2022-07-24 1:28 UTC (permalink / raw)
To: kernel test robot
Cc: 周琰杰 (Zhou Yanjie) ,
Tudor.Ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, broonie, robh+dt, krzysztof.kozlowski+dt, kbuild-all,
linux-mtd, linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, paul, dongsheng.qiu, aric.pzqi,
rick.tyliu, jinghui.liu, sernia.zhou, reimu
288 op->dummy.nbytes * 8 / op->dummy.buswidth);
289
290 if (op->data.nbytes > 0)
291 val |= TRAN_CONF_DATA_EN;
292
293 writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
294 writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
295
296
Sent from my iPhone
> On Jul 23, 2022, at 8:47 PM, kernel test robot <lkp@intel.com> wrote:
>
> 288 op->dummy.nbytes * 8 / op->dummy.buswidth);
> 289
> 290 if (op->data.nbytes > 0)
> 291 val |= TRAN_CONF_DATA_EN;
> 292
> 293 writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
> 294 writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
> 295
> 296
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
@ 2022-07-24 1:28 ` Vanessa Page
0 siblings, 0 replies; 78+ messages in thread
From: Vanessa Page @ 2022-07-24 1:28 UTC (permalink / raw)
To: kernel test robot
Cc: 周琰杰 (Zhou Yanjie) ,
Tudor.Ambarus, p.yadav, michael, miquel.raynal, richard,
vigneshr, broonie, robh+dt, krzysztof.kozlowski+dt, kbuild-all,
linux-mtd, linux-spi, linux-mips, linux-kernel, devicetree,
aidanmacdonald.0x0, tmn505, paul, dongsheng.qiu, aric.pzqi,
rick.tyliu, jinghui.liu, sernia.zhou, reimu
288 op->dummy.nbytes * 8 / op->dummy.buswidth);
289
290 if (op->data.nbytes > 0)
291 val |= TRAN_CONF_DATA_EN;
292
293 writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
294 writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
295
296
Sent from my iPhone
> On Jul 23, 2022, at 8:47 PM, kernel test robot <lkp@intel.com> wrote:
>
> 288 op->dummy.nbytes * 8 / op->dummy.buswidth);
> 289
> 290 if (op->data.nbytes > 0)
> 291 val |= TRAN_CONF_DATA_EN;
> 292
> 293 writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
> 294 writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
> 295
> 296
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 78+ messages in thread
* Re: [PATCH 3/3] SPI: Ingenic: Add SFC support for Ingenic SoCs.
2022-07-24 0:43 ` kernel test robot
(?)
(?)
@ 2022-07-25 16:58 ` Vanessa Page
-1 siblings, 0 replies; 78+ messages in thread
From: Vanessa Page @ 2022-07-25 16:58 UTC (permalink / raw)
To: kbuild-all
[-- Attachment #1: Type: text/plain, Size: 775 bytes --]
288 op->dummy.nbytes * 8 / op->dummy.buswidth);
289
290 if (op->data.nbytes > 0)
291 val |= TRAN_CONF_DATA_EN;
292
293 writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
294 writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
295
296
Sent from my iPhone
> On Jul 23, 2022, at 8:47 PM, kernel test robot <lkp@intel.com> wrote:
>
> 288 op->dummy.nbytes * 8 / op->dummy.buswidth);
> 289
> 290 if (op->data.nbytes > 0)
> 291 val |= TRAN_CONF_DATA_EN;
> 292
> 293 writel(val, sfc->base + SFC_REG_TRAN_CONF(0));
> 294 writel(op->data.nbytes, sfc->base + SFC_REG_TRAN_LEN);
> 295
> 296
^ permalink raw reply [flat|nested] 78+ messages in thread