AnsweredAssumed Answered

iMX SPI driver improvements

Question asked by Daniel Sobe Employee on Mar 14, 2014
Latest reply on Mar 14, 2014 by Daniel Sobe

Hi,

 

in the course of a project I have taken a close look at the iMX SPI kernel driver. The patch below is against the 3.0.35 kernel as it is used in the jb4.3_1.1.0-ga Android release for iMX6.

 

The patch addresses 2 improvements:

 

1) Burstsize supported up to the maximum burstsize of the hardware.

2) Variable trigger level allows to increase priority for data transfer, enables continuous transfers up to the burstsize.

 

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.

 

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

 

--- orig_kernel_imx/drivers/spi/spi_imx.c    2014-03-14 14:16:07.000000000 +0100

+++ kernel_imx/drivers/spi/spi_imx.c    2014-02-10 16:33:13.773621961 +0100

@@ -49,14 +49,19 @@

#define MX3_CSPISTAT_RR        (1 << 3)

 

/* 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_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 */

+

+#define SPIDRV_USESYSFILE

+

 

struct spi_imx_config {

     unsigned int speed_hz;

     unsigned int bpw;

     unsigned int mode;

     u8 cs;

+    unsigned long burstlen;

};

 

enum spi_imx_devtype {

@@ -138,6 +143,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)

@@ -188,8 +197,14 @@

 

#define SPI_IMX2_3_INT        0x10

#define SPI_IMX2_3_INT_TEEN        (1 <<  0)

+#define SPI_IMX2_3_INT_TDREN  (1 <<  1)

#define SPI_IMX2_3_INT_RREN        (1 <<  3)

 

+#define SPI_IMX2_3_DMA    0x14

+#define SPI_IMX2_3_DMA_TX_THRES_OFFSET   0

+#define SPI_IMX2_3_DMA_TX_THRES_MASK     (0x3F)

+

+

#define SPI_IMX2_3_STAT        0x18

#define SPI_IMX2_3_STAT_RR        (1 <<  3)

 

@@ -231,7 +246,16 @@

     unsigned val = 0;

 

     if (enable & MXC_INT_TE)

-        val |= SPI_IMX2_3_INT_TEEN;

+    {

+    val |= SPI_IMX2_3_INT_TEEN;

+    }

+

+    // enable Tx threshold interrupt for faster reaction when transmitting long messages

+    if (enable & MXC_INT_THE)

+    {

+    val |= SPI_IMX2_3_INT_TDREN;

+    writel((unsigned int) tx_fifo_thres, spi_imx->base + SPI_IMX2_3_DMA);

+    }

 

     if (enable & MXC_INT_RR)

         val |= SPI_IMX2_3_INT_RREN;

@@ -252,6 +276,7 @@

         struct spi_imx_config *config)

{

     u32 ctrl = SPI_IMX2_3_CTRL_ENABLE, cfg = 0;

+    unsigned long burstlen;

 

     /*

      * The hardware seems to have a race condition when changing modes. The

@@ -268,7 +293,34 @@

     /* set chip select to use */

     ctrl |= SPI_IMX2_3_CTRL_CS(config->cs);

 

-    ctrl |= (config->bpw - 1) << SPI_IMX2_3_CTRL_BL_OFFSET;

+    // 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;

+    }

+

+    // printk(KERN_ALERT "config->burstlen=%lu resulting burstlen=%lu", config->burstlen, burstlen);

+

+    ctrl |= (burstlen - 1) << SPI_IMX2_3_CTRL_BL_OFFSET;

 

     cfg |= SPI_IMX2_3_CONFIG_SBBCTRL(config->cs);

 

@@ -614,6 +666,10 @@

         spi_imx->txfifo++;

     }

 

+    // always reconfigure IRQ source (useful for the TX data request IRQ)

+    spi_imx->devtype_data.intctrl(spi_imx,

+        ((spi_imx->txfifo == spi_imx->devtype_data.fifosize) && (tx_fifo_thres != -1)) ? MXC_INT_THE : MXC_INT_TE);

+

     spi_imx->devtype_data.trigger(spi_imx);

}

 

@@ -657,6 +713,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;

@@ -698,7 +755,7 @@

 

     spi_imx_push(spi_imx);

 

-    spi_imx->devtype_data.intctrl(spi_imx, MXC_INT_TE);

+    // spi_imx->devtype_data.intctrl(spi_imx, MXC_INT_TE);

 

     wait_for_completion(&spi_imx->xfer_done);

     clk_disable(spi_imx->clk);

@@ -768,6 +825,79 @@

     }

};

 

+#if defined (SPIDRV_USESYSFILE)

+

+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)

+    {

+      printk(KERN_INFO "SPI master big bursts on\n");

+      allow_big_bursts = 1;

+    }

+    else

+    {

+      printk(KERN_INFO "SPI master big bursts off\n");

+      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))

+    {

+      printk(KERN_INFO "SPI master TX FIFO data request IRQ on with threshold=%lu\n", tmp);

+      tx_fifo_thres = tmp;

+    }

+    else

+    {

+      printk(KERN_INFO "SPI master TX FIFO data request IRQ off\n");

+      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,

+};

+

+#endif /* SPIDRV_USESYSFILE */

+

+

+

static int __devinit spi_imx_probe(struct platform_device *pdev)

{

     struct spi_imx_master *mxc_platform_info;

@@ -795,6 +925,12 @@

     spi_imx->bitbang.master = spi_master_get(master);

     spi_imx->chipselect = mxc_platform_info->chipselect;

 

+#if defined (SPIDRV_USESYSFILE)

+    if (sysfs_create_group(&pdev->dev.kobj, &spidrv_group)) {

+        return -ENOMEM;

+    }

+#endif /* SPIDRV_USESYSFILE */

+

     for (i = 0; i < master->num_chipselect; i++) {

         if (spi_imx->chipselect[i] < 0)

             continue;

Outcomes