spi-imx: improve timing of bursts
diff mbox

Message ID 8c8893eae0014e59ac6942062590d36e@DB4PR04MB393.eurprd04.prod.outlook.com
State New, archived
Headers show

Commit Message

Daniel Sobe March 17, 2014, 4:22 p.m. UTC
Hi,

The patch below addresses 2 improvements:

1) Burstsize of a transfer adjusted up to the maximum burstsize of the hardware --> no unwanted CS toggling between data words
2) Variable trigger level allows to increase priority for data transfer, enables continuous transfers up to the burstsize. --> remove gaps every 64 words

The original behaviour of the driver is untouched. The additional features can be enabled via sysfs.

With file "bigburst" the driver is instructed to use the maximum possible burst length, depending on the length of the transfer it received. Due to the hardware of the iMX6 this only makes sense if a transfer has its bits per word set to 32.

With file "txthres" the transmit FIFO level is set (in the range of 1 to 64), all other values disable the functionality. An interrupt will be triggered if the specified FIFO level (or lower) is reached, re-filling the FIFO up to the max resp. the remaining data of the transfer. If disabled, the original behaviour (refilling when FIFO is empty) is active. The functionality creates a higher interrupt load but allows continuous transfers up to the selected burstsize.

I'd like to receive feedback on the patch, improving it and making it suitable for a possible inclusion into the kernel. Unfortunately, I could only test the patch on a very old kernel. The patch itself applies against a recent kernel (3.14-rc6) and I checked that it compiles without warnings.

Signed-off-by: Daniel Sobe <daniel.sobe@nxp.com>



--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Patch
diff mbox

--- linux-original/drivers/spi/spi-imx.c    2014-03-17 17:12:35.691791218 +0100
+++ linux-stable/drivers/spi/spi-imx.c    2014-03-17 17:09:44.451795597 +0100
@@ -51,12 +51,14 @@ 
 /* generic defines to abstract from the different register layouts */
 #define MXC_INT_RR    (1 << 0) /* Receive data ready interrupt */
 #define MXC_INT_TE    (1 << 1) /* Transmit FIFO empty interrupt */
+#define MXC_INT_THE     (1 << 2) /* Transmit FIFO threshold interrupt */
 
 struct spi_imx_config {
     unsigned int speed_hz;
     unsigned int bpw;
     unsigned int mode;
     u8 cs;
+    unsigned long burstlen;
 };
 
 enum spi_imx_devtype {
@@ -154,6 +156,10 @@ 
 static int mxc_clkdivs[] = {0, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192,
     256, 384, 512, 768, 1024};
 
+// (sysfs-)tunable attributes
+static int allow_big_bursts = 0; // if set to 1 this enables 32-bit big bursts
+static int tx_fifo_thres = -1;   // if positive, use TX data request interrupt when applicable
+
 /* MX21, MX27 */
 static unsigned int spi_imx_clkdiv_1(unsigned int fin,
         unsigned int fspi, unsigned int max)
@@ -200,8 +206,13 @@ 
 
 #define MX51_ECSPI_INT        0x10
 #define MX51_ECSPI_INT_TEEN        (1 <<  0)
+#define MX51_ECSPI_INT_TDREN        (1 <<  1)
 #define MX51_ECSPI_INT_RREN        (1 <<  3)
 
+#define MX51_ECSPI_DMA      0x14
+#define MX51_ECSPI_DMA_TX_THRES_OFFSET  0
+#define MX51_ECSPI_DMA_TX_THRES_MASK    (0x3F)
+
 #define MX51_ECSPI_STAT        0x18
 #define MX51_ECSPI_STAT_RR        (1 <<  3)
 
@@ -250,6 +261,12 @@ 
     if (enable & MXC_INT_TE)
         val |= MX51_ECSPI_INT_TEEN;
 
+    // enable Tx threshold interrupt for faster reaction when transmitting long messages
+    if (enable & MXC_INT_THE) {
+        val |= MX51_ECSPI_INT_TDREN;
+        writel((unsigned int) tx_fifo_thres, spi_imx->base + MX51_ECSPI_DMA);
+    }
+
     if (enable & MXC_INT_RR)
         val |= MX51_ECSPI_INT_RREN;
 
@@ -270,6 +287,7 @@ 
 {
     u32 ctrl = MX51_ECSPI_CTRL_ENABLE, cfg = 0;
     u32 clk = config->speed_hz, delay;
+    unsigned long burstlen;
 
     /*
      * The hardware seems to have a race condition when changing modes. The
@@ -286,6 +304,27 @@ 
     /* set chip select to use */
     ctrl |= MX51_ECSPI_CTRL_CS(config->cs);
 
+    // if possible and requested, increase burstlength
+    // over whole message in 32 bit mode
+    // (which is the only mode where HW supports this)
+    // all other modes still use burstlength == bpw
+    if ((config->bpw == 32) && (allow_big_bursts == 1)) {
+        if (config->burstlen <= 4096) {
+            burstlen = config->burstlen;
+        } else {
+            // burst longer than burst register width -- need to scale appropriately
+            // so that the hardware will always terminate the burst appropriately
+            burstlen = 16;
+            do {
+                burstlen = burstlen << 1;
+            } while ((config->burstlen % (burstlen << 1)) == 0);
+        }
+    } else {
+      burstlen = config->bpw;
+    }
+
+    ctrl |= (burstlen - 1) << MX51_ECSPI_CTRL_BL_OFFSET;
+
     ctrl |= (config->bpw - 1) << MX51_ECSPI_CTRL_BL_OFFSET;
 
     cfg |= MX51_ECSPI_CONFIG_SBBCTRL(config->cs);
@@ -666,6 +705,10 @@ 
         spi_imx->txfifo++;
     }
 
+    // always reconfigure IRQ source (for the TX data request IRQ to work properly)
+    spi_imx->devtype_data->intctrl(spi_imx,
+        ((spi_imx->txfifo == spi_imx_get_fifosize(spi_imx)) && (tx_fifo_thres != -1)) ? MXC_INT_THE : MXC_INT_TE);
+
     spi_imx->devtype_data->trigger(spi_imx);
 }
 
@@ -708,6 +751,7 @@ 
     config.speed_hz  = t ? t->speed_hz : spi->max_speed_hz;
     config.mode = spi->mode;
     config.cs = spi->chip_select;
+    config.burstlen = t ? (t->len * 8) : config.bpw;
 
     if (!config.speed_hz)
         config.speed_hz = spi->max_speed_hz;
@@ -745,8 +789,6 @@ 
 
     spi_imx_push(spi_imx);
 
-    spi_imx->devtype_data->intctrl(spi_imx, MXC_INT_TE);
-
     wait_for_completion(&spi_imx->xfer_done);
 
     return transfer->len;
@@ -801,6 +843,63 @@ 
     return 0;
 }
 
+static ssize_t spidrv_setbigburst(struct device *dev,
+                                  struct device_attribute *attr,
+                                  const char *buf, size_t size)
+{
+    unsigned long tmp;
+    tmp = simple_strtoul(buf, NULL, 10);
+    if (tmp == 1) {
+        allow_big_bursts = 1;
+        } else {
+        allow_big_bursts = 0;
+    }
+    return size;
+}
+
+static ssize_t spidrv_getbigburst(struct device *dev,
+                                  struct device_attribute *attr,
+                                  char *buf)
+{
+    sprintf (buf, "%d\n", allow_big_bursts);
+    return (ssize_t)(strlen (buf) + 1);
+}
+
+static ssize_t spidrv_settxthres(struct device *dev,
+                                 struct device_attribute *attr,
+                                 const char *buf, size_t size)
+{
+    unsigned long tmp;
+    tmp = simple_strtoul(buf, NULL, 10);
+    if ((tmp > 0) && (tmp < 65)) {
+        tx_fifo_thres = tmp;
+    } else {
+        tx_fifo_thres = -1;
+    }
+    return size;
+}
+
+static ssize_t spidrv_gettxthres(struct device *dev,
+                                 struct device_attribute *attr,
+                                 char *buf)
+{
+    sprintf (buf, "%d\n", tx_fifo_thres);
+    return (ssize_t)(strlen (buf) + 1);
+}
+
+static DEVICE_ATTR(bigburst, 0666, spidrv_getbigburst, spidrv_setbigburst);
+static DEVICE_ATTR(txthres,  0666, spidrv_gettxthres , spidrv_settxthres);
+
+static struct attribute *spidrv_attributes[] = {
+    &dev_attr_bigburst.attr,
+    &dev_attr_txthres.attr,
+    NULL
+};
+
+static const struct attribute_group spidrv_group = {
+    .attrs = spidrv_attributes,
+};
+
 static int spi_imx_probe(struct platform_device *pdev)
 {
     struct device_node *np = pdev->dev.of_node;
@@ -840,6 +939,10 @@ 
     spi_imx = spi_master_get_devdata(master);
     spi_imx->bitbang.master = master;
 
+    if (sysfs_create_group(&pdev->dev.kobj, &spidrv_group)) {
+        return -ENOMEM;
+    }
+
     for (i = 0; i < master->num_chipselect; i++) {
         int cs_gpio = of_get_named_gpio(np, "cs-gpios", i);
         if (!gpio_is_valid(cs_gpio) && mxc_platform_info)