diff options
Diffstat (limited to 'drivers/spi/spi-hisi-sfc-v3xx.c')
-rw-r--r-- | drivers/spi/spi-hisi-sfc-v3xx.c | 261 |
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; |