2019-01-07 07:55:05

by Clark Wang

[permalink] [raw]
Subject: [PATCH 0/8] spi: lpspi: Fix bugs and Add some functions support

Hi Mark,

As subject, these fucntions support, including:
- Support i.MX8 series boards;
- Support cs-gpio fucntion;
- Support DMA mode for both master and salve mode.

From patch 3 to 6 are some bug-fix for PIO mode. In order to avoid data loss
and improve data transmission stability.

These are some notes about cs-gpio and DMA:
- cs-gpio:
Because LPSPI driver don't use default implementation of
transfer_one_message(), I do the cs-gpio control way as same as the way
used in spi core;

- DMA:
Any frame length longer than half txfifosize will be sent by DMA mode.
For now, there are some limits:
1. The maximum transfer speed in master mode depends on the slave device,
at least 40MHz on i.MX8 series (tested by spi-nor on 8qm-lpddr4-arm2
base board);
2. The maximum transfer speed in slave mode is 15MHz(i.MX7ULP),
22MHz(i.MX8 series).

Clark Wang (8):
spi: lpspi: Add i.MX8 boards support for lpspi
spi: lpspi: enable runtime pm for lpspi
spi: lpspi: Improve the stability of lpspi data transmission
spi: lpspi: Fix wrong transmission when don't use CONT
spi: lpspi: Fix CLK pin becomes low before one transfer
spi: lpspi: add the error info of transfer speed setting
spi: lpspi: Add cs-gpio support
spi: lpspi: add dma mode support

drivers/spi/spi-fsl-lpspi.c | 612 ++++++++++++++++++++++++++++++++----
1 file changed, 552 insertions(+), 60 deletions(-)

--
2.17.1



2019-01-07 07:49:12

by Clark Wang

[permalink] [raw]
Subject: [PATCH 2/8] spi: lpspi: enable runtime pm for lpspi

From: Han Xu <[email protected]>

Enable the runtime power management for lpspi module.

Do some adaptation work from kernel 4.9 to 4.14.

Signed-off-by: Clark Wang <[email protected]>
Signed-off-by: Han Xu <[email protected]>
Reviewed-by: Frank Li <[email protected]>
---
drivers/spi/spi-fsl-lpspi.c | 117 ++++++++++++++++++++++++++++--------
1 file changed, 92 insertions(+), 25 deletions(-)

diff --git a/drivers/spi/spi-fsl-lpspi.c b/drivers/spi/spi-fsl-lpspi.c
index 5802f188051b..3e935db5ff02 100644
--- a/drivers/spi/spi-fsl-lpspi.c
+++ b/drivers/spi/spi-fsl-lpspi.c
@@ -16,7 +16,9 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
+#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi_bitbang.h>
@@ -24,6 +26,8 @@

#define DRIVER_NAME "fsl_lpspi"

+#define FSL_LPSPI_RPM_TIMEOUT 50 /* 50ms */
+
/* i.MX7ULP LPSPI registers */
#define IMX7ULP_VERID 0x0
#define IMX7ULP_PARAM 0x4
@@ -150,13 +154,9 @@ static int lpspi_prepare_xfer_hardware(struct spi_controller *controller)
spi_controller_get_devdata(controller);
int ret;

- ret = clk_prepare_enable(fsl_lpspi->clk_ipg);
- if (ret)
- return ret;
-
- ret = clk_prepare_enable(fsl_lpspi->clk_per);
- if (ret) {
- clk_disable_unprepare(fsl_lpspi->clk_ipg);
+ ret = pm_runtime_get_sync(fsl_lpspi->dev);
+ if (ret < 0) {
+ dev_err(fsl_lpspi->dev, "failed to enable clock\n");
return ret;
}

@@ -168,8 +168,8 @@ static int lpspi_unprepare_xfer_hardware(struct spi_controller *controller)
struct fsl_lpspi_data *fsl_lpspi =
spi_controller_get_devdata(controller);

- clk_disable_unprepare(fsl_lpspi->clk_ipg);
- clk_disable_unprepare(fsl_lpspi->clk_per);
+ pm_runtime_mark_last_busy(fsl_lpspi->dev);
+ pm_runtime_put_autosuspend(fsl_lpspi->dev);

return 0;
}
@@ -476,6 +476,45 @@ static irqreturn_t fsl_lpspi_isr(int irq, void *dev_id)
return IRQ_NONE;
}

+int fsl_lpspi_runtime_resume(struct device *dev)
+{
+ struct fsl_lpspi_data *fsl_lpspi = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(fsl_lpspi->clk_per);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(fsl_lpspi->clk_ipg);
+ if (ret) {
+ clk_disable_unprepare(fsl_lpspi->clk_per);
+ return ret;
+ }
+
+ return 0;
+}
+
+int fsl_lpspi_runtime_suspend(struct device *dev)
+{
+ struct fsl_lpspi_data *fsl_lpspi = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(fsl_lpspi->clk_per);
+ clk_disable_unprepare(fsl_lpspi->clk_ipg);
+
+ return 0;
+}
+
+static int fsl_lpspi_init_rpm(struct fsl_lpspi_data *fsl_lpspi)
+{
+ struct device *dev = fsl_lpspi->dev;
+
+ pm_runtime_enable(dev);
+ pm_runtime_set_autosuspend_delay(dev, FSL_LPSPI_RPM_TIMEOUT);
+ pm_runtime_use_autosuspend(dev);
+
+ return 0;
+}
+
static int fsl_lpspi_probe(struct platform_device *pdev)
{
struct fsl_lpspi_data *fsl_lpspi;
@@ -501,6 +540,7 @@ static int fsl_lpspi_probe(struct platform_device *pdev)

fsl_lpspi = spi_controller_get_devdata(controller);
fsl_lpspi->dev = &pdev->dev;
+ dev_set_drvdata(&pdev->dev, fsl_lpspi);
fsl_lpspi->is_slave = of_property_read_bool((&pdev->dev)->of_node,
"spi-slave");

@@ -547,28 +587,21 @@ static int fsl_lpspi_probe(struct platform_device *pdev)
goto out_controller_put;
}

- ret = clk_prepare_enable(fsl_lpspi->clk_ipg);
- if (ret) {
- dev_err(&pdev->dev,
- "can't enable lpspi ipg clock, ret=%d\n", ret);
+ /* enable the clock */
+ ret = fsl_lpspi_init_rpm(fsl_lpspi);
+ if (ret)
goto out_controller_put;
- }

- ret = clk_prepare_enable(fsl_lpspi->clk_per);
- if (ret) {
- dev_err(&pdev->dev,
- "can't enable lpspi per clock, ret=%d\n", ret);
- clk_disable_unprepare(fsl_lpspi->clk_ipg);
- goto out_controller_put;
+ ret = pm_runtime_get_sync(fsl_lpspi->dev);
+ if (ret < 0) {
+ dev_err(fsl_lpspi->dev, "failed to enable clock\n");
+ return ret;
}

temp = readl(fsl_lpspi->base + IMX7ULP_PARAM);
fsl_lpspi->txfifosize = 1 << (temp & 0x0f);
fsl_lpspi->rxfifosize = 1 << ((temp >> 8) & 0x0f);

- clk_disable_unprepare(fsl_lpspi->clk_per);
- clk_disable_unprepare(fsl_lpspi->clk_ipg);
-
ret = devm_spi_register_controller(&pdev->dev, controller);
if (ret < 0) {
dev_err(&pdev->dev, "spi_register_controller error.\n");
@@ -589,16 +622,50 @@ static int fsl_lpspi_remove(struct platform_device *pdev)
struct fsl_lpspi_data *fsl_lpspi =
spi_controller_get_devdata(controller);

- clk_disable_unprepare(fsl_lpspi->clk_per);
- clk_disable_unprepare(fsl_lpspi->clk_ipg);
+ pm_runtime_disable(fsl_lpspi->dev);
+
+ spi_master_put(controller);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int fsl_lpspi_suspend(struct device *dev)
+{
+ int ret;
+
+ pinctrl_pm_select_sleep_state(dev);
+ ret = pm_runtime_force_suspend(dev);
+ return ret;
+}
+
+static int fsl_lpspi_resume(struct device *dev)
+{
+ int ret;
+
+ ret = pm_runtime_force_resume(dev);
+ if (ret) {
+ dev_err(dev, "Error in resume: %d\n", ret);
+ return ret;
+ }
+
+ pinctrl_pm_select_default_state(dev);

return 0;
}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops fsl_lpspi_pm_ops = {
+ SET_RUNTIME_PM_OPS(fsl_lpspi_runtime_suspend,
+ fsl_lpspi_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(fsl_lpspi_suspend, fsl_lpspi_resume)
+};

static struct platform_driver fsl_lpspi_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = fsl_lpspi_dt_ids,
+ .pm = &fsl_lpspi_pm_ops,
},
.probe = fsl_lpspi_probe,
.remove = fsl_lpspi_remove,
--
2.17.1


2019-01-07 07:49:25

by Clark Wang

[permalink] [raw]
Subject: [PATCH 5/8] spi: lpspi: Fix CLK pin becomes low before one transfer

Remove Reset operation in fsl_lpspi_config(). This RST may cause both CLK
and CS pins go from high to low level under cs-gpio mode.
Add fsl_lpspi_reset() function after one message transfer to clear all
flags in use.

Signed-off-by: Clark Wang <[email protected]>
Reviewed-by: Fugang Duan <[email protected]>
---
drivers/spi/spi-fsl-lpspi.c | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)

diff --git a/drivers/spi/spi-fsl-lpspi.c b/drivers/spi/spi-fsl-lpspi.c
index 51c85cf0bd9f..84dcb9e176b8 100644
--- a/drivers/spi/spi-fsl-lpspi.c
+++ b/drivers/spi/spi-fsl-lpspi.c
@@ -281,10 +281,6 @@ static int fsl_lpspi_config(struct fsl_lpspi_data *fsl_lpspi)
u32 temp;
int ret;

- temp = CR_RST;
- writel(temp, fsl_lpspi->base + IMX7ULP_CR);
- writel(0, fsl_lpspi->base + IMX7ULP_CR);
-
if (!fsl_lpspi->is_slave) {
ret = fsl_lpspi_set_bitrate(fsl_lpspi);
if (ret)
@@ -375,6 +371,24 @@ static int fsl_lpspi_wait_for_completion(struct spi_controller *controller)
return 0;
}

+static int fsl_lpspi_reset(struct fsl_lpspi_data *fsl_lpspi)
+{
+ u32 temp;
+
+ /* Disable all interrupt */
+ fsl_lpspi_intctrl(fsl_lpspi, 0);
+
+ /* W1C for all flags in SR */
+ temp = 0x3F << 8;
+ writel(temp, fsl_lpspi->base + IMX7ULP_SR);
+
+ /* Clear FIFO and disable module */
+ temp = CR_RRF | CR_RTF;
+ writel(temp, fsl_lpspi->base + IMX7ULP_CR);
+
+ return 0;
+}
+
static int fsl_lpspi_transfer_one(struct spi_controller *controller,
struct spi_device *spi,
struct spi_transfer *t)
@@ -396,6 +410,8 @@ static int fsl_lpspi_transfer_one(struct spi_controller *controller,
if (ret)
return ret;

+ fsl_lpspi_reset(fsl_lpspi);
+
return 0;
}

--
2.17.1


2019-01-07 07:49:36

by Clark Wang

[permalink] [raw]
Subject: [PATCH 7/8] spi: lpspi: Add cs-gpio support

Add cs-gpio feature for LPSPI.
The cs line will be controlled in fsl_lpspi_transfe_one_msg() function.

Still support using the mode without cs-gpio. It depends on if attribute
cs-gpio has been configured in dts file.

Signed-off-by: Clark Wang <[email protected]>
---
drivers/spi/spi-fsl-lpspi.c | 89 ++++++++++++++++++++++++++++++++++++-
1 file changed, 88 insertions(+), 1 deletion(-)

diff --git a/drivers/spi/spi-fsl-lpspi.c b/drivers/spi/spi-fsl-lpspi.c
index 69635cde0e22..83e15366b739 100644
--- a/drivers/spi/spi-fsl-lpspi.c
+++ b/drivers/spi/spi-fsl-lpspi.c
@@ -9,6 +9,7 @@
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/err.h>
+#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
@@ -16,7 +17,9 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
+#include <linux/of_gpio.h>
#include <linux/pinctrl/consumer.h>
+#include <linux/platform_data/spi-imx.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
@@ -28,6 +31,10 @@

#define FSL_LPSPI_RPM_TIMEOUT 50 /* 50ms */

+#define LPSPI_CS_ACTIVE 1
+#define LPSPI_CS_INACTIVE 0
+#define LPSPI_CS_DELAY 100
+
/* i.MX7ULP LPSPI registers */
#define IMX7ULP_VERID 0x0
#define IMX7ULP_PARAM 0x4
@@ -91,6 +98,7 @@ struct fsl_lpspi_data {
struct clk *clk_ipg;
struct clk *clk_per;
bool is_slave;
+ bool hascsgpio;

void *rx_buf;
const void *tx_buf;
@@ -106,6 +114,8 @@ struct fsl_lpspi_data {
struct completion xfer_done;

bool slave_aborted;
+
+ int chipselect[4];
};

static const struct of_device_id fsl_lpspi_dt_ids[] = {
@@ -178,6 +188,20 @@ static int lpspi_unprepare_xfer_hardware(struct spi_controller *controller)
return 0;
}

+static void fsl_lpspi_chipselect(struct spi_device *spi, bool enable)
+{
+ struct fsl_lpspi_data *fsl_lpspi =
+ spi_controller_get_devdata(spi->controller);
+ int gpio = fsl_lpspi->chipselect[spi->chip_select];
+
+ enable = (!!(spi->mode & SPI_CS_HIGH) == enable);
+
+ if (!gpio_is_valid(gpio))
+ return;
+
+ gpio_set_value_cansleep(gpio, enable);
+}
+
static void fsl_lpspi_write_tx_fifo(struct fsl_lpspi_data *fsl_lpspi)
{
u8 txfifo_cnt;
@@ -422,6 +446,25 @@ static int fsl_lpspi_transfer_one(struct spi_controller *controller,
return 0;
}

+static int fsl_lpspi_setup(struct spi_device *spi)
+{
+ struct fsl_lpspi_data *fsl_lpspi =
+ spi_controller_get_devdata(spi->controller);
+ int gpio = fsl_lpspi->chipselect[spi->chip_select];
+
+ dev_dbg(&spi->dev, "%s: mode %d, %u bpw, %d hz\n", __func__,
+ spi->mode, spi->bits_per_word, spi->max_speed_hz);
+
+ if (gpio_is_valid(gpio)) {
+ gpio_direction_output(gpio,
+ fsl_lpspi->config.mode & SPI_CS_HIGH ? 0 : 1);
+ }
+
+ fsl_lpspi_chipselect(spi, LPSPI_CS_INACTIVE);
+
+ return 0;
+}
+
static int fsl_lpspi_transfer_one_msg(struct spi_controller *controller,
struct spi_message *msg)
{
@@ -430,8 +473,12 @@ static int fsl_lpspi_transfer_one_msg(struct spi_controller *controller,
struct spi_device *spi = msg->spi;
struct spi_transfer *xfer;
bool is_first_xfer = true;
+ bool keep_cs = false;
int ret = 0;

+ if (fsl_lpspi->hascsgpio)
+ fsl_lpspi_chipselect(spi, LPSPI_CS_ACTIVE);
+
msg->status = 0;
msg->actual_length = 0;

@@ -448,10 +495,24 @@ static int fsl_lpspi_transfer_one_msg(struct spi_controller *controller,
if (ret < 0)
goto complete;

+ if (fsl_lpspi->hascsgpio && xfer->cs_change) {
+ if (list_is_last(&xfer->transfer_list,
+ &msg->transfers)) {
+ keep_cs = true;
+ } else {
+ fsl_lpspi_chipselect(spi, LPSPI_CS_INACTIVE);
+ udelay(10);
+ fsl_lpspi_chipselect(spi, LPSPI_CS_ACTIVE);
+ }
+ }
+
msg->actual_length += xfer->len;
}

complete:
+ if (fsl_lpspi->hascsgpio && !keep_cs)
+ fsl_lpspi_chipselect(spi, LPSPI_CS_INACTIVE);
+
msg->status = ret;
spi_finalize_current_message(controller);

@@ -531,10 +592,13 @@ static int fsl_lpspi_init_rpm(struct fsl_lpspi_data *fsl_lpspi)

static int fsl_lpspi_probe(struct platform_device *pdev)
{
+ struct device_node *np = pdev->dev.of_node;
struct fsl_lpspi_data *fsl_lpspi;
struct spi_controller *controller;
+ struct spi_imx_master *lpspi_platform_info =
+ dev_get_platdata(&pdev->dev);
struct resource *res;
- int ret, irq;
+ int i, ret, irq;
u32 temp;

if (of_property_read_bool((&pdev->dev)->of_node, "spi-slave"))
@@ -558,6 +622,29 @@ static int fsl_lpspi_probe(struct platform_device *pdev)
fsl_lpspi->is_slave = of_property_read_bool((&pdev->dev)->of_node,
"spi-slave");

+ fsl_lpspi->hascsgpio = false;
+ if (!fsl_lpspi->is_slave) {
+ for (i = 0; i < controller->num_chipselect; i++) {
+ int cs_gpio = of_get_named_gpio(np, "cs-gpios", i);
+
+ if (!gpio_is_valid(cs_gpio) && lpspi_platform_info)
+ cs_gpio = lpspi_platform_info->chipselect[i];
+
+ fsl_lpspi->chipselect[i] = cs_gpio;
+ if (!gpio_is_valid(cs_gpio))
+ continue;
+
+ ret = devm_gpio_request(&pdev->dev,
+ fsl_lpspi->chipselect[i], DRIVER_NAME);
+ if (ret) {
+ dev_err(&pdev->dev, "can't get cs gpios\n");
+ goto out_controller_put;
+ }
+ controller->setup = fsl_lpspi_setup;
+ fsl_lpspi->hascsgpio = true;
+ }
+ }
+
controller->transfer_one_message = fsl_lpspi_transfer_one_msg;
controller->prepare_transfer_hardware = lpspi_prepare_xfer_hardware;
controller->unprepare_transfer_hardware = lpspi_unprepare_xfer_hardware;
--
2.17.1


2019-01-07 07:50:19

by Clark Wang

[permalink] [raw]
Subject: [PATCH 6/8] spi: lpspi: add the error info of transfer speed setting

Add a error info when set a speed which greater than half of per-clk of
spi module.

The minimum SCK period is 2 cycles(CCR[SCKDIV]). So the maximum transfer
speed is half of spi per-clk.

Signed-off-by: Clark Wang <[email protected]>
---
drivers/spi/spi-fsl-lpspi.c | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/drivers/spi/spi-fsl-lpspi.c b/drivers/spi/spi-fsl-lpspi.c
index 84dcb9e176b8..69635cde0e22 100644
--- a/drivers/spi/spi-fsl-lpspi.c
+++ b/drivers/spi/spi-fsl-lpspi.c
@@ -255,6 +255,13 @@ static int fsl_lpspi_set_bitrate(struct fsl_lpspi_data *fsl_lpspi)
u8 prescale;

perclk_rate = clk_get_rate(fsl_lpspi->clk_per);
+
+ if (config.speed_hz > perclk_rate / 2) {
+ dev_err(fsl_lpspi->dev,
+ "per-clk should be at least two times of transfer speed");
+ return -EINVAL;
+ }
+
for (prescale = 0; prescale < 8; prescale++) {
scldiv = perclk_rate /
(clkdivs[prescale] * config.speed_hz) - 2;
@@ -304,7 +311,7 @@ static int fsl_lpspi_config(struct fsl_lpspi_data *fsl_lpspi)
return 0;
}

-static void fsl_lpspi_setup_transfer(struct spi_device *spi,
+static int fsl_lpspi_setup_transfer(struct spi_device *spi,
struct spi_transfer *t)
{
struct fsl_lpspi_data *fsl_lpspi =
@@ -337,7 +344,7 @@ static void fsl_lpspi_setup_transfer(struct spi_device *spi,
else
fsl_lpspi->watermark = fsl_lpspi->txfifosize;

- fsl_lpspi_config(fsl_lpspi);
+ return fsl_lpspi_config(fsl_lpspi);
}

static int fsl_lpspi_slave_abort(struct spi_controller *controller)
@@ -429,7 +436,10 @@ static int fsl_lpspi_transfer_one_msg(struct spi_controller *controller,
msg->actual_length = 0;

list_for_each_entry(xfer, &msg->transfers, transfer_list) {
- fsl_lpspi_setup_transfer(spi, xfer);
+ ret = fsl_lpspi_setup_transfer(spi, xfer);
+ if (ret < 0)
+ goto complete;
+
fsl_lpspi_set_cmd(fsl_lpspi, is_first_xfer);

is_first_xfer = false;
--
2.17.1


2019-01-07 07:50:20

by Clark Wang

[permalink] [raw]
Subject: [PATCH 4/8] spi: lpspi: Fix wrong transmission when don't use CONT

Add judgment on SR_MBF and FSR_RXCOUNT.

In PIO mode, if don't use CONT to keep cs selected in one transfer, the
transfer will go wrong. FCIE will be set after one frame transfer
finish. If use CONT, the frame refer to the whole data in one transfer.
If don't use CONT, the frame refer to one byte of whole data. This will
cause the transfer ending early.

This patch add a register reading in isr function, it might lead to a
slight decrease in the max transmission speed in PIO mode.

Signed-off-by: Clark Wang <[email protected]>
---
drivers/spi/spi-fsl-lpspi.c | 9 +++++++++
1 file changed, 9 insertions(+)

diff --git a/drivers/spi/spi-fsl-lpspi.c b/drivers/spi/spi-fsl-lpspi.c
index f32a2e0d7ae1..51c85cf0bd9f 100644
--- a/drivers/spi/spi-fsl-lpspi.c
+++ b/drivers/spi/spi-fsl-lpspi.c
@@ -52,6 +52,7 @@
#define CR_RTF BIT(8)
#define CR_RST BIT(1)
#define CR_MEN BIT(0)
+#define SR_MBF BIT(24)
#define SR_TCF BIT(10)
#define SR_FCF BIT(9)
#define SR_RDF BIT(1)
@@ -65,6 +66,7 @@
#define CFGR1_PCSPOL BIT(8)
#define CFGR1_NOSTALL BIT(3)
#define CFGR1_MASTER BIT(0)
+#define FSR_RXCOUNT (BIT(16)|BIT(17)|BIT(18))
#define RSR_RXEMPTY BIT(1)
#define TCR_CPOL BIT(31)
#define TCR_CPHA BIT(30)
@@ -446,6 +448,13 @@ static irqreturn_t fsl_lpspi_isr(int irq, void *dev_id)
return IRQ_HANDLED;
}

+ if (temp_SR & SR_MBF ||
+ readl(fsl_lpspi->base + IMX7ULP_FSR) & FSR_RXCOUNT) {
+ writel(SR_FCF, fsl_lpspi->base + IMX7ULP_SR);
+ fsl_lpspi_intctrl(fsl_lpspi, IER_FCIE);
+ return IRQ_HANDLED;
+ }
+
if (temp_SR & SR_FCF && (temp_IER & IER_FCIE)) {
writel(SR_FCF, fsl_lpspi->base + IMX7ULP_SR);
complete(&fsl_lpspi->xfer_done);
--
2.17.1


2019-01-07 07:51:06

by Clark Wang

[permalink] [raw]
Subject: [PATCH 8/8] spi: lpspi: add dma mode support

Add dma mode support for LPSPI. Any frame longer than half txfifosize will
be sent by dma mode.

For now, there are some notes:
1. The maximum transfer speed in master mode depends on the slave device,
at least 40MHz on i.MX8 series (tested by spi-nor on 8qm-lpddr4-arm2
base board);
2. The maximum transfer speed in slave mode is 15MHz(i.MX7ULP),
22MHz(i.MX8 series).

Signed-off-by: Clark Wang <[email protected]>
---
drivers/spi/spi-fsl-lpspi.c | 318 ++++++++++++++++++++++++++++++++++--
1 file changed, 306 insertions(+), 12 deletions(-)

diff --git a/drivers/spi/spi-fsl-lpspi.c b/drivers/spi/spi-fsl-lpspi.c
index 83e15366b739..85e4e36b71a3 100644
--- a/drivers/spi/spi-fsl-lpspi.c
+++ b/drivers/spi/spi-fsl-lpspi.c
@@ -8,6 +8,8 @@
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/delay.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
@@ -19,6 +21,7 @@
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/pinctrl/consumer.h>
+#include <linux/platform_data/dma-imx.h>
#include <linux/platform_data/spi-imx.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
@@ -31,6 +34,9 @@

#define FSL_LPSPI_RPM_TIMEOUT 50 /* 50ms */

+/* The maximum bytes that edma can transfer once.*/
+#define FSL_LPSPI_MAX_EDMA_BYTES ((1 << 15) - 1)
+
#define LPSPI_CS_ACTIVE 1
#define LPSPI_CS_INACTIVE 0
#define LPSPI_CS_DELAY 100
@@ -68,6 +74,8 @@
#define IER_FCIE BIT(9)
#define IER_RDIE BIT(1)
#define IER_TDIE BIT(0)
+#define DER_RDDE BIT(1)
+#define DER_TDDE BIT(0)
#define CFGR1_PCSCFG BIT(27)
#define CFGR1_PINCFG (BIT(24)|BIT(25))
#define CFGR1_PCSPOL BIT(8)
@@ -95,6 +103,7 @@ struct lpspi_config {
struct fsl_lpspi_data {
struct device *dev;
void __iomem *base;
+ unsigned long base_phys;
struct clk *clk_ipg;
struct clk *clk_per;
bool is_slave;
@@ -105,6 +114,8 @@ struct fsl_lpspi_data {
void (*tx)(struct fsl_lpspi_data *);
void (*rx)(struct fsl_lpspi_data *);

+ u32 bytes_per_word;
+ u32 bits_per_word;
u32 remain;
u8 watermark;
u8 txfifosize;
@@ -115,6 +126,11 @@ struct fsl_lpspi_data {

bool slave_aborted;

+ /* DMA */
+ bool usedma;
+ struct completion dma_rx_completion;
+ struct completion dma_tx_completion;
+
int chipselect[4];
};

@@ -162,6 +178,35 @@ static void fsl_lpspi_intctrl(struct fsl_lpspi_data *fsl_lpspi,
writel(enable, fsl_lpspi->base + IMX7ULP_IER);
}

+static int fsl_lpspi_bytes_per_word(const int bpw)
+{
+ return DIV_ROUND_UP(bpw, BITS_PER_BYTE);
+}
+
+static bool fsl_lpspi_can_dma(struct spi_controller *controller,
+ struct spi_device *spi,
+ struct spi_transfer *transfer)
+{
+ struct fsl_lpspi_data *fsl_lpspi =
+ spi_controller_get_devdata(controller);
+ unsigned int bytes_per_word;
+
+ if (!controller->dma_rx)
+ return false;
+
+ if (fsl_lpspi->is_slave)
+ return false;
+
+ bytes_per_word = fsl_lpspi_bytes_per_word(transfer->bits_per_word);
+ if (bytes_per_word != 1 && bytes_per_word != 2 && bytes_per_word != 4)
+ return false;
+
+ if (transfer->len < fsl_lpspi->txfifosize / 2)
+ return false;
+
+ return true;
+}
+
static int lpspi_prepare_xfer_hardware(struct spi_controller *controller)
{
struct fsl_lpspi_data *fsl_lpspi =
@@ -250,11 +295,13 @@ static void fsl_lpspi_set_cmd(struct fsl_lpspi_data *fsl_lpspi,
* For the first transfer, clear TCR_CONTC to assert SS.
* For subsequent transfer, set TCR_CONTC to keep SS asserted.
*/
- temp |= TCR_CONT;
- if (is_first_xfer)
- temp &= ~TCR_CONTC;
- else
- temp |= TCR_CONTC;
+ if (!fsl_lpspi->usedma) {
+ temp |= TCR_CONT;
+ if (is_first_xfer)
+ temp &= ~TCR_CONTC;
+ else
+ temp |= TCR_CONTC;
+ }
}
writel(temp, fsl_lpspi->base + IMX7ULP_TCR);

@@ -265,7 +312,11 @@ static void fsl_lpspi_set_watermark(struct fsl_lpspi_data *fsl_lpspi)
{
u32 temp;

- temp = fsl_lpspi->watermark >> 1 | (fsl_lpspi->watermark >> 1) << 16;
+ if (!fsl_lpspi->usedma)
+ temp = fsl_lpspi->watermark >> 1 |
+ (fsl_lpspi->watermark >> 1) << 16;
+ else
+ temp = fsl_lpspi->txfifosize >> 1;

writel(temp, fsl_lpspi->base + IMX7ULP_FCR);

@@ -301,12 +352,59 @@ static int fsl_lpspi_set_bitrate(struct fsl_lpspi_data *fsl_lpspi)
writel(scldiv | (scldiv << 8) | ((scldiv >> 1) << 16),
fsl_lpspi->base + IMX7ULP_CCR);

- dev_dbg(fsl_lpspi->dev, "perclk=%d, speed=%d, prescale =%d, scldiv=%d\n",
+ dev_dbg(fsl_lpspi->dev, "perclk=%d, speed=%d, prescale=%d, scldiv=%d\n",
perclk_rate, config.speed_hz, prescale, scldiv);

return 0;
}

+static int fsl_lpspi_dma_configure(struct spi_controller *controller)
+{
+ int ret;
+ enum dma_slave_buswidth buswidth;
+ struct dma_slave_config rx = {}, tx = {};
+ struct fsl_lpspi_data *fsl_lpspi =
+ spi_controller_get_devdata(controller);
+
+ switch (fsl_lpspi_bytes_per_word(fsl_lpspi->bits_per_word)) {
+ case 4:
+ buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ break;
+ case 2:
+ buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ break;
+ case 1:
+ buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ tx.direction = DMA_MEM_TO_DEV;
+ tx.dst_addr = fsl_lpspi->base_phys + IMX7ULP_TDR;
+ tx.dst_addr_width = buswidth;
+ tx.dst_maxburst = 1;
+ ret = dmaengine_slave_config(controller->dma_tx, &tx);
+ if (ret) {
+ dev_err(fsl_lpspi->dev, "TX dma configuration failed with %d\n",
+ ret);
+ return ret;
+ }
+
+ rx.direction = DMA_DEV_TO_MEM;
+ rx.src_addr = fsl_lpspi->base_phys + IMX7ULP_RDR;
+ rx.src_addr_width = buswidth;
+ rx.src_maxburst = 1;
+ ret = dmaengine_slave_config(controller->dma_rx, &rx);
+ if (ret) {
+ dev_err(fsl_lpspi->dev, "RX dma configuration failed with %d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
static int fsl_lpspi_config(struct fsl_lpspi_data *fsl_lpspi)
{
u32 temp;
@@ -332,15 +430,22 @@ static int fsl_lpspi_config(struct fsl_lpspi_data *fsl_lpspi)
temp |= CR_RRF | CR_RTF | CR_MEN;
writel(temp, fsl_lpspi->base + IMX7ULP_CR);

+ temp = 0;
+ if (fsl_lpspi->usedma)
+ temp = DER_TDDE | DER_RDDE;
+ writel(temp, fsl_lpspi->base + IMX7ULP_DER);
+
return 0;
}

-static int fsl_lpspi_setup_transfer(struct spi_device *spi,
+static int fsl_lpspi_setup_transfer(struct spi_controller *controller,
+ struct spi_device *spi,
struct spi_transfer *t)
{
struct fsl_lpspi_data *fsl_lpspi =
spi_controller_get_devdata(spi->controller);

+ fsl_lpspi->bits_per_word = t->bits_per_word;
fsl_lpspi->config.mode = spi->mode;
fsl_lpspi->config.bpw = t ? t->bits_per_word : spi->bits_per_word;
fsl_lpspi->config.speed_hz = t ? t->speed_hz : spi->max_speed_hz;
@@ -368,6 +473,11 @@ static int fsl_lpspi_setup_transfer(struct spi_device *spi,
else
fsl_lpspi->watermark = fsl_lpspi->txfifosize;

+ if (fsl_lpspi_can_dma(controller, spi, t))
+ fsl_lpspi->usedma = 1;
+ else
+ fsl_lpspi->usedma = 0;
+
return fsl_lpspi_config(fsl_lpspi);
}

@@ -406,8 +516,10 @@ static int fsl_lpspi_reset(struct fsl_lpspi_data *fsl_lpspi)
{
u32 temp;

- /* Disable all interrupt */
- fsl_lpspi_intctrl(fsl_lpspi, 0);
+ if (!fsl_lpspi->usedma) {
+ /* Disable all interrupt */
+ fsl_lpspi_intctrl(fsl_lpspi, 0);
+ }

/* W1C for all flags in SR */
temp = 0x3F << 8;
@@ -420,6 +532,128 @@ static int fsl_lpspi_reset(struct fsl_lpspi_data *fsl_lpspi)
return 0;
}

+static void fsl_lpspi_dma_rx_callback(void *cookie)
+{
+ struct fsl_lpspi_data *fsl_lpspi = (struct fsl_lpspi_data *)cookie;
+
+ complete(&fsl_lpspi->dma_rx_completion);
+}
+
+static void fsl_lpspi_dma_tx_callback(void *cookie)
+{
+ struct fsl_lpspi_data *fsl_lpspi = (struct fsl_lpspi_data *)cookie;
+
+ complete(&fsl_lpspi->dma_tx_completion);
+}
+
+static int fsl_lpspi_calculate_timeout(struct fsl_lpspi_data *fsl_lpspi,
+ int size)
+{
+ unsigned long timeout = 0;
+
+ /* Time with actual data transfer and CS change delay related to HW */
+ timeout = (8 + 4) * size / fsl_lpspi->config.speed_hz;
+
+ /* Add extra second for scheduler related activities */
+ timeout += 1;
+
+ /* Double calculated timeout */
+ return msecs_to_jiffies(2 * timeout * MSEC_PER_SEC);
+}
+
+static int fsl_lpspi_dma_transfer(struct spi_controller *controller,
+ struct fsl_lpspi_data *fsl_lpspi,
+ struct spi_transfer *transfer)
+{
+ struct dma_async_tx_descriptor *desc_tx, *desc_rx;
+ unsigned long transfer_timeout;
+ unsigned long timeout;
+ struct sg_table *tx = &transfer->tx_sg, *rx = &transfer->rx_sg;
+ int ret;
+
+ ret = fsl_lpspi_dma_configure(controller);
+ if (ret)
+ return ret;
+
+ desc_rx = dmaengine_prep_slave_sg(controller->dma_rx,
+ rx->sgl, rx->nents, DMA_DEV_TO_MEM,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!desc_rx)
+ return -EINVAL;
+
+ desc_rx->callback = fsl_lpspi_dma_rx_callback;
+ desc_rx->callback_param = (void *)fsl_lpspi;
+ dmaengine_submit(desc_rx);
+ reinit_completion(&fsl_lpspi->dma_rx_completion);
+ dma_async_issue_pending(controller->dma_rx);
+
+ desc_tx = dmaengine_prep_slave_sg(controller->dma_tx,
+ tx->sgl, tx->nents, DMA_MEM_TO_DEV,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!desc_tx) {
+ dmaengine_terminate_all(controller->dma_tx);
+ return -EINVAL;
+ }
+
+ desc_tx->callback = fsl_lpspi_dma_tx_callback;
+ desc_tx->callback_param = (void *)fsl_lpspi;
+ dmaengine_submit(desc_tx);
+ reinit_completion(&fsl_lpspi->dma_tx_completion);
+ dma_async_issue_pending(controller->dma_tx);
+
+ fsl_lpspi->slave_aborted = false;
+
+ if (!fsl_lpspi->is_slave) {
+ transfer_timeout = fsl_lpspi_calculate_timeout(fsl_lpspi,
+ transfer->len);
+
+ /* Wait eDMA to finish the data transfer.*/
+ timeout = wait_for_completion_timeout(&fsl_lpspi->dma_tx_completion,
+ transfer_timeout);
+ if (!timeout) {
+ dev_err(fsl_lpspi->dev, "I/O Error in DMA TX\n");
+ dmaengine_terminate_all(controller->dma_tx);
+ dmaengine_terminate_all(controller->dma_rx);
+ fsl_lpspi_reset(fsl_lpspi);
+ return -ETIMEDOUT;
+ }
+
+ timeout = wait_for_completion_timeout(&fsl_lpspi->dma_rx_completion,
+ transfer_timeout);
+ if (!timeout) {
+ dev_err(fsl_lpspi->dev, "I/O Error in DMA RX\n");
+ dmaengine_terminate_all(controller->dma_tx);
+ dmaengine_terminate_all(controller->dma_rx);
+ fsl_lpspi_reset(fsl_lpspi);
+ return -ETIMEDOUT;
+ }
+ } else {
+ if (wait_for_completion_interruptible(&fsl_lpspi->dma_tx_completion) ||
+ fsl_lpspi->slave_aborted) {
+ dev_dbg(fsl_lpspi->dev,
+ "I/O Error in DMA TX interrupted\n");
+ dmaengine_terminate_all(controller->dma_tx);
+ dmaengine_terminate_all(controller->dma_rx);
+ fsl_lpspi_reset(fsl_lpspi);
+ return -EINTR;
+ }
+
+ if (wait_for_completion_interruptible(&fsl_lpspi->dma_rx_completion) ||
+ fsl_lpspi->slave_aborted) {
+ dev_dbg(fsl_lpspi->dev,
+ "I/O Error in DMA RX interrupted\n");
+ dmaengine_terminate_all(controller->dma_tx);
+ dmaengine_terminate_all(controller->dma_rx);
+ fsl_lpspi_reset(fsl_lpspi);
+ return -EINTR;
+ }
+ }
+
+ fsl_lpspi_reset(fsl_lpspi);
+
+ return 0;
+}
+
static int fsl_lpspi_transfer_one(struct spi_controller *controller,
struct spi_device *spi,
struct spi_transfer *t)
@@ -446,6 +680,54 @@ static int fsl_lpspi_transfer_one(struct spi_controller *controller,
return 0;
}

+static void fsl_lpspi_dma_exit(struct spi_controller *controller)
+{
+ if (controller->dma_rx) {
+ dma_release_channel(controller->dma_rx);
+ controller->dma_rx = NULL;
+ }
+
+ if (controller->dma_tx) {
+ dma_release_channel(controller->dma_tx);
+ controller->dma_tx = NULL;
+ }
+}
+
+static int fsl_lpspi_dma_init(struct device *dev,
+ struct fsl_lpspi_data *fsl_lpspi,
+ struct spi_controller *controller)
+{
+ int ret;
+
+ /* Prepare for TX DMA: */
+ controller->dma_tx = dma_request_slave_channel_reason(dev, "tx");
+ if (IS_ERR(controller->dma_tx)) {
+ ret = PTR_ERR(controller->dma_tx);
+ dev_dbg(dev, "can't get the TX DMA channel, error %d!\n", ret);
+ controller->dma_tx = NULL;
+ goto err;
+ }
+
+ /* Prepare for RX DMA: */
+ controller->dma_rx = dma_request_slave_channel(dev, "rx");
+ if (IS_ERR(controller->dma_rx)) {
+ ret = PTR_ERR(controller->dma_rx);
+ dev_dbg(dev, "can't get the RX DMA channel, error %d\n", ret);
+ controller->dma_rx = NULL;
+ goto err;
+ }
+
+ init_completion(&fsl_lpspi->dma_rx_completion);
+ init_completion(&fsl_lpspi->dma_tx_completion);
+ controller->can_dma = fsl_lpspi_can_dma;
+ controller->max_dma_len = FSL_LPSPI_MAX_EDMA_BYTES;
+
+ return 0;
+err:
+ fsl_lpspi_dma_exit(controller);
+ return ret;
+}
+
static int fsl_lpspi_setup(struct spi_device *spi)
{
struct fsl_lpspi_data *fsl_lpspi =
@@ -483,7 +765,7 @@ static int fsl_lpspi_transfer_one_msg(struct spi_controller *controller,
msg->actual_length = 0;

list_for_each_entry(xfer, &msg->transfers, transfer_list) {
- ret = fsl_lpspi_setup_transfer(spi, xfer);
+ ret = fsl_lpspi_setup_transfer(controller, spi, xfer);
if (ret < 0)
goto complete;

@@ -491,7 +773,11 @@ static int fsl_lpspi_transfer_one_msg(struct spi_controller *controller,

is_first_xfer = false;

- ret = fsl_lpspi_transfer_one(controller, spi, xfer);
+ if (fsl_lpspi->usedma)
+ ret = fsl_lpspi_dma_transfer(controller, fsl_lpspi,
+ xfer);
+ else
+ ret = fsl_lpspi_transfer_one(controller, spi, xfer);
if (ret < 0)
goto complete;

@@ -662,6 +948,7 @@ static int fsl_lpspi_probe(struct platform_device *pdev)
ret = PTR_ERR(fsl_lpspi->base);
goto out_controller_put;
}
+ fsl_lpspi->base_phys = res->start;

irq = platform_get_irq(pdev, 0);
if (irq < 0) {
@@ -703,6 +990,13 @@ static int fsl_lpspi_probe(struct platform_device *pdev)
fsl_lpspi->txfifosize = 1 << (temp & 0x0f);
fsl_lpspi->rxfifosize = 1 << ((temp >> 8) & 0x0f);

+ ret = fsl_lpspi_dma_init(&pdev->dev, fsl_lpspi, controller);
+ if (ret == -EPROBE_DEFER)
+ goto out_controller_put;
+
+ if (ret < 0)
+ dev_err(&pdev->dev, "dma setup error %d, use pio\n", ret);
+
ret = devm_spi_register_controller(&pdev->dev, controller);
if (ret < 0) {
dev_err(&pdev->dev, "spi_register_controller error.\n");
--
2.17.1


2019-01-07 07:55:06

by Clark Wang

[permalink] [raw]
Subject: [PATCH 1/8] spi: lpspi: Add i.MX8 boards support for lpspi

Add both ipg and per clock for lpspi to support i.MX8QM/QXP boards.

Signed-off-by: Clark Wang <[email protected]>
---
drivers/spi/spi-fsl-lpspi.c | 52 +++++++++++++++++++++++++++++--------
1 file changed, 41 insertions(+), 11 deletions(-)

diff --git a/drivers/spi/spi-fsl-lpspi.c b/drivers/spi/spi-fsl-lpspi.c
index 08dcc3c22e88..5802f188051b 100644
--- a/drivers/spi/spi-fsl-lpspi.c
+++ b/drivers/spi/spi-fsl-lpspi.c
@@ -80,7 +80,8 @@ struct lpspi_config {
struct fsl_lpspi_data {
struct device *dev;
void __iomem *base;
- struct clk *clk;
+ struct clk *clk_ipg;
+ struct clk *clk_per;
bool is_slave;

void *rx_buf;
@@ -147,8 +148,19 @@ static int lpspi_prepare_xfer_hardware(struct spi_controller *controller)
{
struct fsl_lpspi_data *fsl_lpspi =
spi_controller_get_devdata(controller);
+ int ret;
+
+ ret = clk_prepare_enable(fsl_lpspi->clk_ipg);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(fsl_lpspi->clk_per);
+ if (ret) {
+ clk_disable_unprepare(fsl_lpspi->clk_ipg);
+ return ret;
+ }

- return clk_prepare_enable(fsl_lpspi->clk);
+ return 0;
}

static int lpspi_unprepare_xfer_hardware(struct spi_controller *controller)
@@ -156,7 +168,8 @@ static int lpspi_unprepare_xfer_hardware(struct spi_controller *controller)
struct fsl_lpspi_data *fsl_lpspi =
spi_controller_get_devdata(controller);

- clk_disable_unprepare(fsl_lpspi->clk);
+ clk_disable_unprepare(fsl_lpspi->clk_ipg);
+ clk_disable_unprepare(fsl_lpspi->clk_per);

return 0;
}
@@ -249,7 +262,7 @@ static int fsl_lpspi_set_bitrate(struct fsl_lpspi_data *fsl_lpspi)
unsigned int perclk_rate, scldiv;
u8 prescale;

- perclk_rate = clk_get_rate(fsl_lpspi->clk);
+ perclk_rate = clk_get_rate(fsl_lpspi->clk_per);
for (prescale = 0; prescale < 8; prescale++) {
scldiv = perclk_rate /
(clkdivs[prescale] * config.speed_hz) - 2;
@@ -522,15 +535,30 @@ static int fsl_lpspi_probe(struct platform_device *pdev)
goto out_controller_put;
}

- fsl_lpspi->clk = devm_clk_get(&pdev->dev, "ipg");
- if (IS_ERR(fsl_lpspi->clk)) {
- ret = PTR_ERR(fsl_lpspi->clk);
+ fsl_lpspi->clk_per = devm_clk_get(&pdev->dev, "per");
+ if (IS_ERR(fsl_lpspi->clk_per)) {
+ ret = PTR_ERR(fsl_lpspi->clk_per);
+ goto out_controller_put;
+ }
+
+ fsl_lpspi->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
+ if (IS_ERR(fsl_lpspi->clk_ipg)) {
+ ret = PTR_ERR(fsl_lpspi->clk_ipg);
+ goto out_controller_put;
+ }
+
+ ret = clk_prepare_enable(fsl_lpspi->clk_ipg);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "can't enable lpspi ipg clock, ret=%d\n", ret);
goto out_controller_put;
}

- ret = clk_prepare_enable(fsl_lpspi->clk);
+ ret = clk_prepare_enable(fsl_lpspi->clk_per);
if (ret) {
- dev_err(&pdev->dev, "can't enable lpspi clock, ret=%d\n", ret);
+ dev_err(&pdev->dev,
+ "can't enable lpspi per clock, ret=%d\n", ret);
+ clk_disable_unprepare(fsl_lpspi->clk_ipg);
goto out_controller_put;
}

@@ -538,7 +566,8 @@ static int fsl_lpspi_probe(struct platform_device *pdev)
fsl_lpspi->txfifosize = 1 << (temp & 0x0f);
fsl_lpspi->rxfifosize = 1 << ((temp >> 8) & 0x0f);

- clk_disable_unprepare(fsl_lpspi->clk);
+ clk_disable_unprepare(fsl_lpspi->clk_per);
+ clk_disable_unprepare(fsl_lpspi->clk_ipg);

ret = devm_spi_register_controller(&pdev->dev, controller);
if (ret < 0) {
@@ -560,7 +589,8 @@ static int fsl_lpspi_remove(struct platform_device *pdev)
struct fsl_lpspi_data *fsl_lpspi =
spi_controller_get_devdata(controller);

- clk_disable_unprepare(fsl_lpspi->clk);
+ clk_disable_unprepare(fsl_lpspi->clk_per);
+ clk_disable_unprepare(fsl_lpspi->clk_ipg);

return 0;
}
--
2.17.1


2019-01-07 07:55:07

by Clark Wang

[permalink] [raw]
Subject: [PATCH 3/8] spi: lpspi: Improve the stability of lpspi data transmission

Use SR_TDF to judge if need to send data, and SR_FCF is to judge if
transmission end and to replace the waiting after transmission end.
This waiting has no actual meaning, for module will set the FCF
flag at the real end.

The changes of interrupt flag and ISR function reduce the times of
calling ISR. The use of the FCF flag improves the stability of the
data transmission. These two points generally improve the data
transfer speed of lpspi, especially when it is set to slave mode
it can support higher transfer speed of the host.

After making these changes, there is no need to use
fsl_lpspi_txfifo_empty(), so remove it.

Signed-off-by: Clark Wang <[email protected]>
---
drivers/spi/spi-fsl-lpspi.c | 61 ++++++++++++-------------------------
1 file changed, 20 insertions(+), 41 deletions(-)

diff --git a/drivers/spi/spi-fsl-lpspi.c b/drivers/spi/spi-fsl-lpspi.c
index 3e935db5ff02..f32a2e0d7ae1 100644
--- a/drivers/spi/spi-fsl-lpspi.c
+++ b/drivers/spi/spi-fsl-lpspi.c
@@ -53,9 +53,11 @@
#define CR_RST BIT(1)
#define CR_MEN BIT(0)
#define SR_TCF BIT(10)
+#define SR_FCF BIT(9)
#define SR_RDF BIT(1)
#define SR_TDF BIT(0)
#define IER_TCIE BIT(10)
+#define IER_FCIE BIT(9)
#define IER_RDIE BIT(1)
#define IER_TDIE BIT(0)
#define CFGR1_PCSCFG BIT(27)
@@ -174,28 +176,10 @@ static int lpspi_unprepare_xfer_hardware(struct spi_controller *controller)
return 0;
}

-static int fsl_lpspi_txfifo_empty(struct fsl_lpspi_data *fsl_lpspi)
-{
- u32 txcnt;
- unsigned long orig_jiffies = jiffies;
-
- do {
- txcnt = readl(fsl_lpspi->base + IMX7ULP_FSR) & 0xff;
-
- if (time_after(jiffies, orig_jiffies + msecs_to_jiffies(500))) {
- dev_dbg(fsl_lpspi->dev, "txfifo empty timeout\n");
- return -ETIMEDOUT;
- }
- cond_resched();
-
- } while (txcnt);
-
- return 0;
-}
-
static void fsl_lpspi_write_tx_fifo(struct fsl_lpspi_data *fsl_lpspi)
{
u8 txfifo_cnt;
+ u32 temp;

txfifo_cnt = readl(fsl_lpspi->base + IMX7ULP_FSR) & 0xff;

@@ -206,9 +190,15 @@ static void fsl_lpspi_write_tx_fifo(struct fsl_lpspi_data *fsl_lpspi)
txfifo_cnt++;
}

- if (!fsl_lpspi->remain && (txfifo_cnt < fsl_lpspi->txfifosize))
- writel(0, fsl_lpspi->base + IMX7ULP_TDR);
- else
+ if (txfifo_cnt < fsl_lpspi->txfifosize) {
+ if (!fsl_lpspi->is_slave) {
+ temp = readl(fsl_lpspi->base + IMX7ULP_TCR);
+ temp &= ~TCR_CONTC;
+ writel(temp, fsl_lpspi->base + IMX7ULP_TCR);
+ }
+
+ fsl_lpspi_intctrl(fsl_lpspi, IER_FCIE);
+ } else
fsl_lpspi_intctrl(fsl_lpspi, IER_TDIE);
}

@@ -404,12 +394,6 @@ static int fsl_lpspi_transfer_one(struct spi_controller *controller,
if (ret)
return ret;

- ret = fsl_lpspi_txfifo_empty(fsl_lpspi);
- if (ret)
- return ret;
-
- fsl_lpspi_read_rx_fifo(fsl_lpspi);
-
return 0;
}

@@ -421,7 +405,6 @@ static int fsl_lpspi_transfer_one_msg(struct spi_controller *controller,
struct spi_device *spi = msg->spi;
struct spi_transfer *xfer;
bool is_first_xfer = true;
- u32 temp;
int ret = 0;

msg->status = 0;
@@ -441,13 +424,6 @@ static int fsl_lpspi_transfer_one_msg(struct spi_controller *controller,
}

complete:
- if (!fsl_lpspi->is_slave) {
- /* de-assert SS, then finalize current message */
- temp = readl(fsl_lpspi->base + IMX7ULP_TCR);
- temp &= ~TCR_CONTC;
- writel(temp, fsl_lpspi->base + IMX7ULP_TCR);
- }
-
msg->status = ret;
spi_finalize_current_message(controller);

@@ -456,20 +432,23 @@ static int fsl_lpspi_transfer_one_msg(struct spi_controller *controller,

static irqreturn_t fsl_lpspi_isr(int irq, void *dev_id)
{
+ u32 temp_SR, temp_IER;
struct fsl_lpspi_data *fsl_lpspi = dev_id;
- u32 temp;

+ temp_IER = readl(fsl_lpspi->base + IMX7ULP_IER);
fsl_lpspi_intctrl(fsl_lpspi, 0);
- temp = readl(fsl_lpspi->base + IMX7ULP_SR);
+ temp_SR = readl(fsl_lpspi->base + IMX7ULP_SR);

fsl_lpspi_read_rx_fifo(fsl_lpspi);

- if (temp & SR_TDF) {
+ if ((temp_SR & SR_TDF) && (temp_IER & IER_TDIE)) {
fsl_lpspi_write_tx_fifo(fsl_lpspi);
+ return IRQ_HANDLED;
+ }

- if (!fsl_lpspi->remain)
+ if (temp_SR & SR_FCF && (temp_IER & IER_FCIE)) {
+ writel(SR_FCF, fsl_lpspi->base + IMX7ULP_SR);
complete(&fsl_lpspi->xfer_done);
-
return IRQ_HANDLED;
}

--
2.17.1


2019-01-07 16:36:50

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 8/8] spi: lpspi: add dma mode support

On Mon, Jan 07, 2019 at 07:47:49AM +0000, Clark Wang wrote:

A couple of small nits but otherwise this looks good:

> + bytes_per_word = fsl_lpspi_bytes_per_word(transfer->bits_per_word);
> + if (bytes_per_word != 1 && bytes_per_word != 2 && bytes_per_word != 4)
> + return false;

This would be clearer written as a switch statement.

> @@ -301,12 +352,59 @@ static int fsl_lpspi_set_bitrate(struct fsl_lpspi_data *fsl_lpspi)
> writel(scldiv | (scldiv << 8) | ((scldiv >> 1) << 16),
> fsl_lpspi->base + IMX7ULP_CCR);
>
> - dev_dbg(fsl_lpspi->dev, "perclk=%d, speed=%d, prescale =%d, scldiv=%d\n",
> + dev_dbg(fsl_lpspi->dev, "perclk=%d, speed=%d, prescale=%d, scldiv=%d\n",
> perclk_rate, config.speed_hz, prescale, scldiv);
>
> return 0;
> }
>

This looks like an unrelated typo fix, should be a separate patch.


Attachments:
(No filename) (858.00 B)
signature.asc (499.00 B)
Download all attachments

2019-01-07 16:36:55

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 2/8] spi: lpspi: enable runtime pm for lpspi

On Mon, Jan 07, 2019 at 07:47:36AM +0000, Clark Wang wrote:
> From: Han Xu <[email protected]>
>
> Enable the runtime power management for lpspi module.
>
> Do some adaptation work from kernel 4.9 to 4.14.
>

Looks like the changelog here is a bit corrupted (extra indents on the
first two lines). Otherwise this looks fine.


Attachments:
(No filename) (338.00 B)
signature.asc (499.00 B)
Download all attachments

2019-01-07 17:32:49

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 1/8] spi: lpspi: Add i.MX8 boards support for lpspi

On Mon, Jan 07, 2019 at 07:47:34AM +0000, Clark Wang wrote:
> Add both ipg and per clock for lpspi to support i.MX8QM/QXP boards.

This needs a binding doc update to reflect the new clock that is being
added (it's not clear from the changelog that ipg was already being
handled).


Attachments:
(No filename) (286.00 B)
signature.asc (499.00 B)
Download all attachments

2019-01-07 19:02:57

by Mark Brown

[permalink] [raw]
Subject: Applied "spi: lpspi: Fix wrong transmission when don't use CONT" to the spi tree

The patch

spi: lpspi: Fix wrong transmission when don't use CONT

has been applied to the spi tree at

https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

From 6a130448498c151d31f1fb6f3003f5cf37b829e0 Mon Sep 17 00:00:00 2001
From: Clark Wang <[email protected]>
Date: Mon, 7 Jan 2019 07:47:41 +0000
Subject: [PATCH] spi: lpspi: Fix wrong transmission when don't use CONT

Add judgment on SR_MBF and FSR_RXCOUNT.

In PIO mode, if don't use CONT to keep cs selected in one transfer, the
transfer will go wrong. FCIE will be set after one frame transfer
finish. If use CONT, the frame refer to the whole data in one transfer.
If don't use CONT, the frame refer to one byte of whole data. This will
cause the transfer ending early.

This patch add a register reading in isr function, it might lead to a
slight decrease in the max transmission speed in PIO mode.

Signed-off-by: Clark Wang <[email protected]>
Signed-off-by: Mark Brown <[email protected]>
---
drivers/spi/spi-fsl-lpspi.c | 9 +++++++++
1 file changed, 9 insertions(+)

diff --git a/drivers/spi/spi-fsl-lpspi.c b/drivers/spi/spi-fsl-lpspi.c
index ba207931e209..d0b2d551cc43 100644
--- a/drivers/spi/spi-fsl-lpspi.c
+++ b/drivers/spi/spi-fsl-lpspi.c
@@ -48,6 +48,7 @@
#define CR_RTF BIT(8)
#define CR_RST BIT(1)
#define CR_MEN BIT(0)
+#define SR_MBF BIT(24)
#define SR_TCF BIT(10)
#define SR_FCF BIT(9)
#define SR_RDF BIT(1)
@@ -61,6 +62,7 @@
#define CFGR1_PCSPOL BIT(8)
#define CFGR1_NOSTALL BIT(3)
#define CFGR1_MASTER BIT(0)
+#define FSR_RXCOUNT (BIT(16)|BIT(17)|BIT(18))
#define RSR_RXEMPTY BIT(1)
#define TCR_CPOL BIT(31)
#define TCR_CPHA BIT(30)
@@ -433,6 +435,13 @@ static irqreturn_t fsl_lpspi_isr(int irq, void *dev_id)
return IRQ_HANDLED;
}

+ if (temp_SR & SR_MBF ||
+ readl(fsl_lpspi->base + IMX7ULP_FSR) & FSR_RXCOUNT) {
+ writel(SR_FCF, fsl_lpspi->base + IMX7ULP_SR);
+ fsl_lpspi_intctrl(fsl_lpspi, IER_FCIE);
+ return IRQ_HANDLED;
+ }
+
if (temp_SR & SR_FCF && (temp_IER & IER_FCIE)) {
writel(SR_FCF, fsl_lpspi->base + IMX7ULP_SR);
complete(&fsl_lpspi->xfer_done);
--
2.20.1


2019-01-09 13:08:02

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 7/8] spi: lpspi: Add cs-gpio support

On Mon, Jan 07, 2019 at 07:47:47AM +0000, Clark Wang wrote:
> Add cs-gpio feature for LPSPI.
> The cs line will be controlled in fsl_lpspi_transfe_one_msg() function.
>
> Still support using the mode without cs-gpio. It depends on if attribute
> cs-gpio has been configured in dts file.

This is open coding the GPIO - it'd be better to convert to use
transfer_one() so you can use the core GPIO handling but that's not
really directly relevant to this patch so I'm going to apply, just
something it'd be good to work on.


Attachments:
(No filename) (534.00 B)
signature.asc (499.00 B)
Download all attachments

2019-01-09 13:12:08

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 6/8] spi: lpspi: add the error info of transfer speed setting

On Mon, Jan 07, 2019 at 07:47:45AM +0000, Clark Wang wrote:
> Add a error info when set a speed which greater than half of per-clk of
> spi module.

This (and it turns out the subsequent patch which I said I'd apply)
doesn't apply against current code, please check and resend.


Attachments:
(No filename) (284.00 B)
signature.asc (499.00 B)
Download all attachments