This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Sitara SDKv7: SPI messages > 191 bytes not transmitted/received

I am developing a Linux kernel driver for a device which communicates via SPI for the AM335x using the Sitara SDK v7 (kernel 3.12.10) and the Beaglebone Black development hardware. I am able to send/receive messages via SPI up to 191 bytes in length. Messages > 191 bytes do not transmitted, however. Looking at the source of the TI OMAP2 McSPI kernel driver, it appears that DMA is used for transfers >= 160 bytes. This works correctly if the transfer is < 192 bytes, however any message longer than 192 bytes is never transmitted and thus never received (i.e. the callback function specified in the spi_message struct is never called after calling spi_async()). Some test code which demonstrates the problem is below.

Any help or suggestions are appreciated.

---

#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/dma-mapping.h>

#define DRV_NAME        "arcspi"
#define DRV_VERSION     "0.001"
#define DRV_DESCRIPTION "SPI test module"
#define DRV_AUTHOR      "xxx"

#define SPI_BUS 2
#define SPI_BUS_CS0 0
#define SPI_BUS_SPEED 6000000

struct arcspi_control {
	struct spi_message SpiMsg;
	struct spi_transfer SpiTransfer;
   dma_addr_t TxBuffDmaHandle, RxBuffDmaHandle;
	u8 *pTxBuff; 
	u8 *pRxBuff;
   u8 bBusy;
};

static struct delayed_work TxWorkStruct;
static struct arcspi_control ArcspiControl;
static struct spi_device *pgSpi_device;

static const u8 TxMessage0[] = { 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
                                 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA };

static void arcspi_tx_done(void *arg)
{
   ArcspiControl.bBusy = 0;
}

static int arcspi_send(struct spi_device *spi_device)
{
   int iRetVal = 0;
   
   spi_message_init(&ArcspiControl.SpiMsg);
   
   ArcspiControl.SpiMsg.complete = arcspi_tx_done;
   ArcspiControl.SpiMsg.context = NULL;
   ArcspiControl.SpiMsg.is_dma_mapped = 1;

   memcpy(ArcspiControl.pTxBuff, TxMessage0, sizeof(TxMessage0));
   
   ArcspiControl.SpiTransfer.tx_buf = ArcspiControl.pTxBuff;
   ArcspiControl.SpiTransfer.rx_buf = ArcspiControl.pRxBuff;
   ArcspiControl.SpiTransfer.len = sizeof(TxMessage0);
   ArcspiControl.SpiTransfer.tx_dma = ArcspiControl.TxBuffDmaHandle;
   ArcspiControl.SpiTransfer.rx_dma = ArcspiControl.RxBuffDmaHandle;
   
   
   spi_message_add_tail(&ArcspiControl.SpiTransfer, &ArcspiControl.SpiMsg);
   
   iRetVal = spi_async(spi_device, &ArcspiControl.SpiMsg);
   
   if (iRetVal)
      printk(KERN_ALERT "spi_async() failed\n");
   else
      ArcspiControl.bBusy = 1;

   return iRetVal;
}

static void arcspi_wq(struct work_struct *pWs)
{
   int iRetVal, iRxCnt;
   
   if (!ArcspiControl.bBusy)
   {
      for (iRxCnt = 0; iRxCnt < 8; iRxCnt++)
         printk("0x%X ", ArcspiControl.pRxBuff[iRxCnt]);
            
      printk("\n");
      
      iRetVal = arcspi_send(pgSpi_device);
   }
   else
      printk("Async send still in progress\n");
   
   schedule_delayed_work(&TxWorkStruct, HZ * 5);
}

static int arcspi_probe(struct spi_device *spi_device)
{
   int iRetVal = 0;
   
   printk("TxMessage is %d bytes\n", sizeof(TxMessage0));
   
   spi_device->dev.coherent_dma_mask = 0xffffffff;
   
   ArcspiControl.pTxBuff = dma_zalloc_coherent(&spi_device->dev, sizeof(TxMessage0), &ArcspiControl.TxBuffDmaHandle, GFP_KERNEL);
   if (!ArcspiControl.pTxBuff)
   {
      iRetVal = -ENOMEM;
      goto err_malloc_tx;
   }

   ArcspiControl.pRxBuff = dma_zalloc_coherent(&spi_device->dev, sizeof(TxMessage0), &ArcspiControl.RxBuffDmaHandle, GFP_KERNEL);
   if (!ArcspiControl.pRxBuff)
   {
      iRetVal = -ENOMEM;
      goto err_malloc_rx;
   }
   
   INIT_DELAYED_WORK(&TxWorkStruct, (void *)arcspi_wq);
   
   schedule_delayed_work(&TxWorkStruct, HZ * 5);
   
   pgSpi_device = spi_device;
   
   ArcspiControl.bBusy = 0;
   
   return 0;

err_malloc_rx:
   dma_free_coherent(&spi_device->dev, sizeof(TxMessage0), ArcspiControl.pTxBuff, ArcspiControl.TxBuffDmaHandle);

err_malloc_tx:
   return iRetVal;
}

static int arcspi_remove(struct spi_device *spi_device)
{
   cancel_delayed_work(&TxWorkStruct);
   
   if (ArcspiControl.pRxBuff)
      dma_free_coherent(&spi_device->dev, sizeof(TxMessage0), ArcspiControl.pRxBuff, ArcspiControl.RxBuffDmaHandle);
      
   if (ArcspiControl.pTxBuff)
      dma_free_coherent(&spi_device->dev, sizeof(TxMessage0), ArcspiControl.pTxBuff, ArcspiControl.TxBuffDmaHandle);
   
   return 0;
}

static int __init add_arcspi_device(void)
{
   struct spi_master *pSpiMaster;
   struct spi_device *pSpiDevice;
   struct device *pDev;
   char cBuff[64];
   int iStatus = 0;

   pSpiMaster = spi_busnum_to_master(SPI_BUS);
   if (!pSpiMaster)
   {
      printk(KERN_ALERT "spi_busnum_to_master(%d) returned NULL\n", SPI_BUS);
      return -1;
   }

   pSpiDevice = spi_alloc_device(pSpiMaster);
   if (!pSpiDevice)
   {
      put_device(&pSpiMaster->dev);
      printk(KERN_ALERT "spi_alloc_device() failed\n");
      return -1;
   }

   pSpiDevice->chip_select = SPI_BUS_CS0;

   snprintf(cBuff, sizeof(cBuff), "%s.%u", dev_name(&pSpiDevice->master->dev), pSpiDevice->chip_select);

   pDev = bus_find_device_by_name(pSpiDevice->dev.bus, NULL, cBuff);
 	if (pDev)
   {
      spi_dev_put(pSpiDevice);
      
      if (pDev->driver && pDev->driver->name && strcmp(DRV_NAME, pDev->driver->name))
      {
         printk(KERN_ALERT "Driver [%s] already registered for %s\n",  pDev->driver->name, cBuff);
         iStatus = -1;
      }
   }
   else
   {
      pSpiDevice->max_speed_hz = SPI_BUS_SPEED;
      pSpiDevice->mode = SPI_MODE_1;
      pSpiDevice->bits_per_word = 8;
      pSpiDevice->irq = -1;
      pSpiDevice->controller_state = NULL;
      pSpiDevice->controller_data = NULL;
      strlcpy(pSpiDevice->modalias, DRV_NAME, SPI_NAME_SIZE);
      
      iStatus = spi_add_device(pSpiDevice);		
      if (iStatus < 0)
      {	
         spi_dev_put(pSpiDevice);
         printk(KERN_ALERT "spi_add_device() failed: %d\n", iStatus);		
      }				
   }

   put_device(&pSpiMaster->dev);
   
   return iStatus;
}

static struct spi_driver arcspi_driver = {
	.driver = {
		.name =	DRV_NAME,
		.owner = THIS_MODULE,
	},
	.probe = arcspi_probe,
	.remove = arcspi_remove,
};


static int arcspi_init(void)
{
   int error;

   error = spi_register_driver(&arcspi_driver);
   if (error < 0)
   {
      printk(KERN_ALERT "spi_register_driver() failed %d\n", error);
      return error;
   }
   
   printk("Initializing " DRV_DESCRIPTION " version " DRV_VERSION "\n");

   error = add_arcspi_device();
   if (error < 0)
   {
      printk(KERN_ALERT "add_arcspi_device() failed\n");
      spi_unregister_driver(&arcspi_driver);
      return error;
   }


   return 0;
}

static void arcspi_exit(void)
{
   spi_unregister_device(pgSpi_device);
	spi_unregister_driver(&arcspi_driver);
   
   printk(DRV_DESCRIPTION " exiting\n");
}


module_init(arcspi_init);
module_exit(arcspi_exit);

MODULE_DESCRIPTION(DRV_DESCRIPTION);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRV_AUTHOR);
MODULE_VERSION(DRV_VERSION);
  • As an additional data point, I changed the #define DMA_MIN_BYTES from 160 to 256 in spi-omap2-mcspi.c and re-compiled the kernel.  Now if I set is_dma_mapped to zero in my driver I am able to transfer > 192 bytes using PIO, suggesting that the problem is DMA-related.  I would like to re-enable DMA, however, since this driver is for a network device.

  • Upon further investigation, it appears to only be messages greater than 160 bytes (thus triggering DMA) whose length is evenly divisible by 64.  I.e. 192, 256, 320, 384 bytes, etc.  Messages of any other length appear to be transmitted correctly.  At this point I believe I can pad messages whose length is divisible by 64 with an extra byte as a workaround, though I would still like to get to the bottom of this problem.

  • Hi Jeffrey,

    I see that you use 8 bit words for your test. Can you clarify if the length of the SPI word is properly set by the spi-omap2_mcspi.c driver? It should be MCSPI_CHxCONF[11:7]WL = 0x7.

    Also can you check if the driver sets AFL & AEL big fields of MCSPI_CHxCONF  according to the description in Section 24.2.3.5 FIFO Buffer Management of AM335x TRM? 

    I suspect that the AEL value is not set correctly. It defines the almost-empty buffer status; When the FIFO pointer has not reached this level, an interrupt or a DMA request is sent to the MPU to enable the system to write AEL + 1 bytes to the transmit register (NOTE that AEL+1 must be multiple of MCSPI_CHxCONF[11:7]WL bit field). When DMA is used, the request is deasserted after the first transmit register write. No new request will be asserted again as long as the system has not performed the right number of write accesses.


    Just for testing, could you also try hardcoding MCSPI_XFERLEVEL[31:16]WCNT to 0xFFFF (maximum possible words transferred on the channel that is using the fifo - 65535), modify the following line in omap2_mcspi_set_fifo() function:
     mcspi_write_reg(master, OMAP2_MCSPI_XFERLEVEL, xferlevel);

     to

     mcspi_write_reg(master, OMAP2_MCSPI_XFERLEVEL, OMAP2_MCSPI_MAX_FIFOWCNT);

    Best Regards,

    Yordan

  • Jeffrey,

    In addition to Yordan post, I would add that AFL+1 (and AEL+1) should correspond to a multiple value of the [11:7] WL value. I would suggest you to review the below resources:

    AM335x TRM, sections 24.2.3.5 FIFO Buffer Management and 24.2.6 DMA Requests

    The below e2e threads:

    http://e2e.ti.com/support/arm/sitara_arm/f/791/t/321533.aspx

    http://e2e.ti.com/support/arm/sitara_arm/f/791/t/334717.aspx

    Regards,
    Pavel

  • Yordan,


    It appears that the word length is being set by the driver correctly.  In all cases (working and not) MCSPI_CHxCONF[11:7] = 0x07, which is correct for 8 bytes.

    As for MCSPI_XFERLEVEL, in the case where I transfer 191 bytes, the check: "if (fifo_depth < 2 || fifo_depth % bytes_per_word != 0)" in omap2_mcspi_set_fifo() does not pass since the GCD of 191,64 = 1 and the fifo is therefore disabled.  I therefore tried transferring 208 bytes (which works), and in that case xferlevel = 0x00D00F0F, so AFL = AEL = 0x0F which corresponds to 16 bytes, which appears to be a valid value.  When I attempt to transfer 192 bytes, xferlevel = 0x00C03F3F, so AFL = AEL = 0x3F which corresponds to 64 bytes, which also appears to be valid, but the transfer fails in this case.  I also tried hard-coding the mcspi_write_reg() call to 0xFFFF as you described above, and all transfers using DMA fail in that case.

  • Pavel,

    As I noted above, in the failing case AFL = AEL = 0x3F which is 64 - 1, and 64 is divisible by the word length (8).  Regardless, those values are set by the TI spi-omap2-mcspi driver and can not (to my knowledge) be affected or changed by my code.

  • Jeffrey,

    In the failing case, do you have any transfer? I mean do you have transferred 191 bytes successful and hang at 192 or the DMA transfer has never begin? Can you provide the status registers values (MCSPI_IRQSTATUS and MCSPI_CH(i)STAT) in the working case (191 bytes) and in the failing case (192 bytes)?

    I see we have some updates in the spi-omap2-mcspi.c driver in the mainline/upstream linux kernel (3.17) comparing with the spi-omap2-mcspi.c driver from the SDK7:

    http://lxr.free-electrons.com/source/drivers/spi/spi-omap2-mcspi.c 3.17

    ti-sdk-am335x-evm-07.00.00.00/board-support/linux-3.12.10-ti2013.12.01/drivers/spi/spi-omap2-mcspi.c

    Can you try applying these changes and see if there will be any difference.

    Regards,
    Pavel

  • Pavel,


    In the failing cases (any transfer whose length is evenly divisible by 64 bytes--i.e. 192 bytes, 256 bytes, etc.), nothing happens.  In other words, the CS is never asserted, the clock does not start, and there is no activity on MOSI.

    In cases where the transmission succeeds (i.e. length is 191 bytes, 255 bytes, etc.), the value of MCSPI_IRQSTATUS immediately following the call to omap2_mcspi_tx_dma in the omap2_mcspi_txrx_dma function is 0x00000005 and the value of MCSPI_CH0STAT at the same point is 0x00000047.  In cases where the transmission fails, the value of MCSPI_IRQSTATUS is 0x00000000 (all zeros) and the value of MCSPI_CH0STAT is 0x0000002A.

    I tried re-compiling the kernel with the source from spi-omap2-mcspi.c supplied with the 3.17 kernel, but that driver version makes calls to devm_kcalloc and devm_spi_register_master which are undefined in the 3.12 kernel sources I am using.

  • I need to amend my previous answer: In the failing cases, the CS does become asserted, but the clock does not start, and there is no activity on MOSI.  The CS then remains asserted indefinitely.  In other words, it never returns to the high state as it normally would after the transmission completes.

  • At the suggestion of TI application support I tried the 3.14 version of spi-omap2-mcspi.c driver.  I did have to change the call to devm_spi_register_master in the probe function to spi_register_master as the former function does not appear to be available in the 3.12 sources.  The problem appears to be corrected in the 3.14 version and I have verified that DMA transfers of 192, 256, 320, etc. bytes complete successfully.