aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/spi/spi-hisi-sfc-v3xx.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/spi/spi-hisi-sfc-v3xx.c')
-rw-r--r--drivers/spi/spi-hisi-sfc-v3xx.c261
1 files changed, 190 insertions, 71 deletions
diff --git a/drivers/spi/spi-hisi-sfc-v3xx.c b/drivers/spi/spi-hisi-sfc-v3xx.c
index 64a18d08a4d9..4650b483a33d 100644
--- a/drivers/spi/spi-hisi-sfc-v3xx.c
+++ b/drivers/spi/spi-hisi-sfc-v3xx.c
@@ -7,7 +7,9 @@
#include <linux/acpi.h>
#include <linux/bitops.h>
+#include <linux/completion.h>
#include <linux/dmi.h>
+#include <linux/interrupt.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/platform_device.h>
@@ -17,18 +19,11 @@
#define HISI_SFC_V3XX_VERSION (0x1f8)
-#define HISI_SFC_V3XX_INT_STAT (0x120)
-#define HISI_SFC_V3XX_INT_STAT_PP_ERR BIT(2)
-#define HISI_SFC_V3XX_INT_STAT_ADDR_IACCES BIT(5)
+#define HISI_SFC_V3XX_RAW_INT_STAT (0x120)
+#define HISI_SFC_V3XX_INT_STAT (0x124)
+#define HISI_SFC_V3XX_INT_MASK (0x128)
#define HISI_SFC_V3XX_INT_CLR (0x12c)
-#define HISI_SFC_V3XX_INT_CLR_CLEAR (0xff)
#define HISI_SFC_V3XX_CMD_CFG (0x300)
-#define HISI_SFC_V3XX_CMD_CFG_DUAL_IN_DUAL_OUT (1 << 17)
-#define HISI_SFC_V3XX_CMD_CFG_DUAL_IO (2 << 17)
-#define HISI_SFC_V3XX_CMD_CFG_FULL_DIO (3 << 17)
-#define HISI_SFC_V3XX_CMD_CFG_QUAD_IN_QUAD_OUT (5 << 17)
-#define HISI_SFC_V3XX_CMD_CFG_QUAD_IO (6 << 17)
-#define HISI_SFC_V3XX_CMD_CFG_FULL_QIO (7 << 17)
#define HISI_SFC_V3XX_CMD_CFG_DATA_CNT_OFF 9
#define HISI_SFC_V3XX_CMD_CFG_RW_MSK BIT(8)
#define HISI_SFC_V3XX_CMD_CFG_DATA_EN_MSK BIT(7)
@@ -40,12 +35,99 @@
#define HISI_SFC_V3XX_CMD_ADDR (0x30c)
#define HISI_SFC_V3XX_CMD_DATABUF0 (0x400)
+/* Common definition of interrupt bit masks */
+#define HISI_SFC_V3XX_INT_MASK_ALL (0x1ff) /* all the masks */
+#define HISI_SFC_V3XX_INT_MASK_CPLT BIT(0) /* command execution complete */
+#define HISI_SFC_V3XX_INT_MASK_PP_ERR BIT(2) /* page progrom error */
+#define HISI_SFC_V3XX_INT_MASK_IACCES BIT(5) /* error visiting inaccessible/
+ * protected address
+ */
+
+/* IO Mode definition in HISI_SFC_V3XX_CMD_CFG */
+#define HISI_SFC_V3XX_STD (0 << 17)
+#define HISI_SFC_V3XX_DIDO (1 << 17)
+#define HISI_SFC_V3XX_DIO (2 << 17)
+#define HISI_SFC_V3XX_FULL_DIO (3 << 17)
+#define HISI_SFC_V3XX_QIQO (5 << 17)
+#define HISI_SFC_V3XX_QIO (6 << 17)
+#define HISI_SFC_V3XX_FULL_QIO (7 << 17)
+
+/*
+ * The IO modes lookup table. hisi_sfc_v3xx_io_modes[(z - 1) / 2][y / 2][x / 2]
+ * stands for x-y-z mode, as described in SFDP terminology. -EIO indicates
+ * an invalid mode.
+ */
+static const int hisi_sfc_v3xx_io_modes[2][3][3] = {
+ {
+ { HISI_SFC_V3XX_DIDO, HISI_SFC_V3XX_DIDO, HISI_SFC_V3XX_DIDO },
+ { HISI_SFC_V3XX_DIO, HISI_SFC_V3XX_FULL_DIO, -EIO },
+ { -EIO, -EIO, -EIO },
+ },
+ {
+ { HISI_SFC_V3XX_QIQO, HISI_SFC_V3XX_QIQO, HISI_SFC_V3XX_QIQO },
+ { -EIO, -EIO, -EIO },
+ { HISI_SFC_V3XX_QIO, -EIO, HISI_SFC_V3XX_FULL_QIO },
+ },
+};
+
struct hisi_sfc_v3xx_host {
struct device *dev;
void __iomem *regbase;
int max_cmd_dword;
+ struct completion *completion;
+ int irq;
};
+static void hisi_sfc_v3xx_disable_int(struct hisi_sfc_v3xx_host *host)
+{
+ writel(0, host->regbase + HISI_SFC_V3XX_INT_MASK);
+}
+
+static void hisi_sfc_v3xx_enable_int(struct hisi_sfc_v3xx_host *host)
+{
+ writel(HISI_SFC_V3XX_INT_MASK_ALL, host->regbase + HISI_SFC_V3XX_INT_MASK);
+}
+
+static void hisi_sfc_v3xx_clear_int(struct hisi_sfc_v3xx_host *host)
+{
+ writel(HISI_SFC_V3XX_INT_MASK_ALL, host->regbase + HISI_SFC_V3XX_INT_CLR);
+}
+
+/*
+ * The interrupt status register indicates whether an error occurs
+ * after per operation. Check it, and clear the interrupts for
+ * next time judgement.
+ */
+static int hisi_sfc_v3xx_handle_completion(struct hisi_sfc_v3xx_host *host)
+{
+ u32 reg;
+
+ reg = readl(host->regbase + HISI_SFC_V3XX_RAW_INT_STAT);
+ hisi_sfc_v3xx_clear_int(host);
+
+ if (reg & HISI_SFC_V3XX_INT_MASK_IACCES) {
+ dev_err(host->dev, "fail to access protected address\n");
+ return -EIO;
+ }
+
+ if (reg & HISI_SFC_V3XX_INT_MASK_PP_ERR) {
+ dev_err(host->dev, "page program operation failed\n");
+ return -EIO;
+ }
+
+ /*
+ * The other bits of the interrupt registers is not currently
+ * used and probably not be triggered in this driver. When it
+ * happens, we regard it as an unsupported error here.
+ */
+ if (!(reg & HISI_SFC_V3XX_INT_MASK_CPLT)) {
+ dev_err(host->dev, "unsupported error occurred, status=0x%x\n", reg);
+ return -EIO;
+ }
+
+ return 0;
+}
+
#define HISI_SFC_V3XX_WAIT_TIMEOUT_US 1000000
#define HISI_SFC_V3XX_WAIT_POLL_INTERVAL_US 10
@@ -80,6 +162,20 @@ static int hisi_sfc_v3xx_adjust_op_size(struct spi_mem *mem,
}
/*
+ * The controller only supports Standard SPI mode, Duall mode and
+ * Quad mode. Double sanitize the ops here to avoid OOB access.
+ */
+static bool hisi_sfc_v3xx_supports_op(struct spi_mem *mem,
+ const struct spi_mem_op *op)
+{
+ if (op->data.buswidth > 4 || op->dummy.buswidth > 4 ||
+ op->addr.buswidth > 4 || op->cmd.buswidth > 4)
+ return false;
+
+ return spi_mem_default_supports_op(mem, op);
+}
+
+/*
* memcpy_{to,from}io doesn't gurantee 32b accesses - which we require for the
* DATABUF registers -so use __io{read,write}32_copy when possible. For
* trailing bytes, copy them byte-by-byte from the DATABUF register, as we
@@ -163,61 +259,36 @@ static void hisi_sfc_v3xx_write_databuf(struct hisi_sfc_v3xx_host *host,
}
}
-static int hisi_sfc_v3xx_generic_exec_op(struct hisi_sfc_v3xx_host *host,
- const struct spi_mem_op *op,
- u8 chip_select)
+static int hisi_sfc_v3xx_start_bus(struct hisi_sfc_v3xx_host *host,
+ const struct spi_mem_op *op,
+ u8 chip_select)
{
- int ret, len = op->data.nbytes;
- u32 int_stat, config = 0;
+ int len = op->data.nbytes, buswidth_mode;
+ u32 config = 0;
if (op->addr.nbytes)
config |= HISI_SFC_V3XX_CMD_CFG_ADDR_EN_MSK;
- switch (op->data.buswidth) {
- case 0 ... 1:
- break;
- case 2:
- if (op->addr.buswidth <= 1) {
- config |= HISI_SFC_V3XX_CMD_CFG_DUAL_IN_DUAL_OUT;
- } else if (op->addr.buswidth == 2) {
- if (op->cmd.buswidth <= 1) {
- config |= HISI_SFC_V3XX_CMD_CFG_DUAL_IO;
- } else if (op->cmd.buswidth == 2) {
- config |= HISI_SFC_V3XX_CMD_CFG_FULL_DIO;
- } else {
- return -EIO;
- }
- } else {
- return -EIO;
- }
- break;
- case 4:
- if (op->addr.buswidth <= 1) {
- config |= HISI_SFC_V3XX_CMD_CFG_QUAD_IN_QUAD_OUT;
- } else if (op->addr.buswidth == 4) {
- if (op->cmd.buswidth <= 1) {
- config |= HISI_SFC_V3XX_CMD_CFG_QUAD_IO;
- } else if (op->cmd.buswidth == 4) {
- config |= HISI_SFC_V3XX_CMD_CFG_FULL_QIO;
- } else {
- return -EIO;
- }
- } else {
- return -EIO;
- }
- break;
- default:
- return -EOPNOTSUPP;
+ if (op->data.buswidth == 0 || op->data.buswidth == 1) {
+ buswidth_mode = HISI_SFC_V3XX_STD;
+ } else {
+ int data_idx, addr_idx, cmd_idx;
+
+ data_idx = (op->data.buswidth - 1) / 2;
+ addr_idx = op->addr.buswidth / 2;
+ cmd_idx = op->cmd.buswidth / 2;
+ buswidth_mode = hisi_sfc_v3xx_io_modes[data_idx][addr_idx][cmd_idx];
}
+ if (buswidth_mode < 0)
+ return buswidth_mode;
+ config |= buswidth_mode;
if (op->data.dir != SPI_MEM_NO_DATA) {
config |= (len - 1) << HISI_SFC_V3XX_CMD_CFG_DATA_CNT_OFF;
config |= HISI_SFC_V3XX_CMD_CFG_DATA_EN_MSK;
}
- if (op->data.dir == SPI_MEM_DATA_OUT)
- hisi_sfc_v3xx_write_databuf(host, op->data.buf.out, len);
- else if (op->data.dir == SPI_MEM_DATA_IN)
+ if (op->data.dir == SPI_MEM_DATA_IN)
config |= HISI_SFC_V3XX_CMD_CFG_RW_MSK;
config |= op->dummy.nbytes << HISI_SFC_V3XX_CMD_CFG_DUMMY_CNT_OFF |
@@ -229,31 +300,46 @@ static int hisi_sfc_v3xx_generic_exec_op(struct hisi_sfc_v3xx_host *host,
writel(config, host->regbase + HISI_SFC_V3XX_CMD_CFG);
- ret = hisi_sfc_v3xx_wait_cmd_idle(host);
+ return 0;
+}
+
+static int hisi_sfc_v3xx_generic_exec_op(struct hisi_sfc_v3xx_host *host,
+ const struct spi_mem_op *op,
+ u8 chip_select)
+{
+ DECLARE_COMPLETION_ONSTACK(done);
+ int ret;
+
+ if (host->irq) {
+ host->completion = &done;
+ hisi_sfc_v3xx_enable_int(host);
+ }
+
+ if (op->data.dir == SPI_MEM_DATA_OUT)
+ hisi_sfc_v3xx_write_databuf(host, op->data.buf.out, op->data.nbytes);
+
+ ret = hisi_sfc_v3xx_start_bus(host, op, chip_select);
if (ret)
return ret;
- /*
- * The interrupt status register indicates whether an error occurs
- * after per operation. Check it, and clear the interrupts for
- * next time judgement.
- */
- int_stat = readl(host->regbase + HISI_SFC_V3XX_INT_STAT);
- writel(HISI_SFC_V3XX_INT_CLR_CLEAR,
- host->regbase + HISI_SFC_V3XX_INT_CLR);
+ if (host->irq) {
+ ret = wait_for_completion_timeout(host->completion,
+ usecs_to_jiffies(HISI_SFC_V3XX_WAIT_TIMEOUT_US));
+ if (!ret)
+ ret = -ETIMEDOUT;
+ else
+ ret = 0;
- if (int_stat & HISI_SFC_V3XX_INT_STAT_ADDR_IACCES) {
- dev_err(host->dev, "fail to access protected address\n");
- return -EIO;
+ hisi_sfc_v3xx_disable_int(host);
+ host->completion = NULL;
+ } else {
+ ret = hisi_sfc_v3xx_wait_cmd_idle(host);
}
-
- if (int_stat & HISI_SFC_V3XX_INT_STAT_PP_ERR) {
- dev_err(host->dev, "page program operation failed\n");
+ if (hisi_sfc_v3xx_handle_completion(host) || ret)
return -EIO;
- }
if (op->data.dir == SPI_MEM_DATA_IN)
- hisi_sfc_v3xx_read_databuf(host, op->data.buf.in, len);
+ hisi_sfc_v3xx_read_databuf(host, op->data.buf.in, op->data.nbytes);
return 0;
}
@@ -272,9 +358,21 @@ static int hisi_sfc_v3xx_exec_op(struct spi_mem *mem,
static const struct spi_controller_mem_ops hisi_sfc_v3xx_mem_ops = {
.adjust_op_size = hisi_sfc_v3xx_adjust_op_size,
+ .supports_op = hisi_sfc_v3xx_supports_op,
.exec_op = hisi_sfc_v3xx_exec_op,
};
+static irqreturn_t hisi_sfc_v3xx_isr(int irq, void *data)
+{
+ struct hisi_sfc_v3xx_host *host = data;
+
+ hisi_sfc_v3xx_disable_int(host);
+
+ complete(host->completion);
+
+ return IRQ_HANDLED;
+}
+
static int hisi_sfc_v3xx_buswidth_override_bits;
/*
@@ -341,6 +439,26 @@ static int hisi_sfc_v3xx_probe(struct platform_device *pdev)
goto err_put_master;
}
+ host->irq = platform_get_irq_optional(pdev, 0);
+ if (host->irq == -EPROBE_DEFER) {
+ ret = -EPROBE_DEFER;
+ goto err_put_master;
+ }
+
+ hisi_sfc_v3xx_disable_int(host);
+
+ if (host->irq > 0) {
+ ret = devm_request_irq(dev, host->irq, hisi_sfc_v3xx_isr, 0,
+ "hisi-sfc-v3xx", host);
+
+ if (ret) {
+ dev_err(dev, "failed to request irq%d, ret = %d\n", host->irq, ret);
+ host->irq = 0;
+ }
+ } else {
+ host->irq = 0;
+ }
+
ctlr->bus_num = -1;
ctlr->num_chipselect = 1;
ctlr->mem_ops = &hisi_sfc_v3xx_mem_ops;
@@ -360,7 +478,8 @@ static int hisi_sfc_v3xx_probe(struct platform_device *pdev)
if (ret)
goto err_put_master;
- dev_info(&pdev->dev, "hw version 0x%x\n", version);
+ dev_info(&pdev->dev, "hw version 0x%x, %s mode.\n",
+ version, host->irq ? "irq" : "polling");
return 0;