245 lines
6.9 KiB
Diff
245 lines
6.9 KiB
Diff
From 34f366184657baff168c7b6e8f219585a7cd5e14 Mon Sep 17 00:00:00 2001
|
|
From: Yicong Yang <yangyicong@hisilicon.com>
|
|
Date: Thu, 24 Sep 2020 20:24:30 +0800
|
|
Subject: [PATCH 34/39] spi: hisi-sfc-v3xx: add support for IRQ mode
|
|
|
|
mainline inclusion
|
|
from mainline-v5.10-rc1
|
|
commit b1dd565124bea0f3ecde87336b48c5d0e98cd5bc
|
|
category: feature
|
|
bugzilla: https://gitee.com/openeuler/kernel/issues/I8CSBP
|
|
CVE: NA
|
|
|
|
Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b1dd565124bea0f3ecde87336b48c5d0e98cd5bc
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
The controller can work with interrupts, so add support for it.
|
|
Then we can work under IRQ mode or Poll mode now, if firmware
|
|
doesn't declare the IRQ support, it will fall back to Poll mode.
|
|
|
|
Acked-by: John Garry <john.garry@huawei.com>
|
|
Signed-off-by: Yicong Yang <yangyicong@hisilicon.com>
|
|
Link: https://lore.kernel.org/r/1600950270-52536-5-git-send-email-yangyicong@hisilicon.com
|
|
Signed-off-by: Mark Brown <broonie@kernel.org>
|
|
Signed-off-by: YunYi Yang <yangyunyi2@huawei.com>
|
|
---
|
|
drivers/spi/spi-hisi-sfc-v3xx.c | 137 +++++++++++++++++++++++++++-----
|
|
1 file changed, 115 insertions(+), 22 deletions(-)
|
|
|
|
diff --git a/drivers/spi/spi-hisi-sfc-v3xx.c b/drivers/spi/spi-hisi-sfc-v3xx.c
|
|
index fbb9b837655c..3e695784abf4 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,7 +19,9 @@
|
|
|
|
#define HISI_SFC_V3XX_VERSION (0x1f8)
|
|
|
|
-#define HISI_SFC_V3XX_INT_STAT (0x120)
|
|
+#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_CMD_CFG (0x300)
|
|
#define HISI_SFC_V3XX_CMD_CFG_DATA_CNT_OFF 9
|
|
@@ -33,6 +37,7 @@
|
|
|
|
/* 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
|
|
@@ -69,8 +74,64 @@ 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
|
|
|
|
@@ -251,9 +312,14 @@ static int hisi_sfc_v3xx_generic_exec_op(struct hisi_sfc_v3xx_host *host,
|
|
const struct spi_mem_op *op,
|
|
u8 chip_select)
|
|
{
|
|
- u32 int_stat;
|
|
+ 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,
|
|
@@ -263,28 +329,21 @@ static int hisi_sfc_v3xx_generic_exec_op(struct hisi_sfc_v3xx_host *host,
|
|
if (ret)
|
|
return ret;
|
|
|
|
- ret = hisi_sfc_v3xx_wait_cmd_idle(host);
|
|
- 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_MASK_ALL,
|
|
- 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_MASK_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_MASK_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,
|
|
@@ -312,6 +371,17 @@ static const struct spi_controller_mem_ops hisi_sfc_v3xx_mem_ops = {
|
|
.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;
|
|
|
|
/*
|
|
@@ -378,6 +448,28 @@ static int hisi_sfc_v3xx_probe(struct platform_device *pdev)
|
|
goto err_put_master;
|
|
}
|
|
|
|
+ host->irq = platform_get_irq(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;
|
|
@@ -397,7 +489,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;
|
|
|
|
--
|
|
2.27.0
|
|
|