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.

[TI Drivers 2.16.00.08] Bug in SPICC26XXDMA SSI Driver breaks hardware chip handling in given scenarios

Dear TI Support Team,

 a software bug in SPICC26XXDMA.c breaks the "Master mode with multiple slaves using hardware chip select" use case scenario documented in the driver's Doxygen documentation due to broken implementation of the SPICC26XXDMA_CMD_SET_CSN_PIN command within the SPICC26XXDMA_control() API.

The issue is present in all TI Drivers component releases up to and including tidrivers_cc13xx_cc26xx_2_16_00_08. Tested distribution packages are tirtos_cc13xx_cc26xx_2_16_00_08 and tirtos_simplelink_2_13_00_06.

According to the driver documentation to change the SSI peripheral's chip select pin definition at run-time, while the SSI peripheral is open in master mode, the following example code could be used as reference:

    // Init SPI and specify non-default parameters
    SPI_Params_init(&params);
    params.bitRate     = 1000000;
    params.frameFormat = SPI_POL1_PHA1;
    params.mode        = SPI_MASTER;
    // Configure the transaction
    transaction.count = sizeof(txBuf);
    transaction.txBuf = txBuf;
    transaction.rxBuf = NULL;
    // Open the SPI and perform transfer to the first slave
    handle = SPI_open(Board_SPI, &params);
    SPI_transfer(handle, &transaction);
    // Then switch chip select pin and perform transfer to the second slave
    SPI_control(handle, SPICC26XXDMA_SET_CSN_PIN, &csnPin1);
    SPI_transfer(handle, &transaction);

In the above code the SPI_contol() API is used to change the active chip select pin definition between transactions.

According to the driver documentation hardware chip select is ought to be supported by both master and slave operation modes. Calling SPI_open() configures the default chip select pin to output (master mode) or input with pull-up (slave mode) within the SPICC26XXDMA_initIO() API:

    /* Configure IOs */
    /* Build local list of pins, allocate through PIN driver and map HW ports */
    if (object->mode == SPI_SLAVE) {
      /* Configure IOs for slave mode */
      ...
      spiPinTable[i++] = object->csnPin   | PIN_INPUT_EN | PIN_PULLUP;
    }
    else {
      /* Configure IOs for master mode */
      ...

      /* If CSN isn't SW controlled, drive it high until SPI module drives signal to avoid glitches */
      if(object->csnPin != PIN_UNASSIGNED) {
          spiPinTable[i++] = object->csnPin | PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH | PIN_PUSHPULL | PIN_INPUT_DIS | PIN_DRVSTR_MED;
      }
    }

As mentioned earlier according to the documentation the driver is supposed to support hardware chip select in master mode with multiple slaves on the same bus line as well. In this use case the driver documentation states that the active chip select pin can be reconfigured by calling SPI_control(handle, SPICC26XXDMA_SET_CSN_PIN, &csPin) before initiating the actual SPI transaction. But the implementation of SPICC26XXDMA_control() only supports slave mode hardware chip select configuration. Within the SPICC26XXDMA_CMD_SET_CSN_PIN command handler code snippet it is assumed that the chip select pin needs to be configured as input with pull-up enabled regardless of the SPI driver's operation mode (master or slave):

        case SPICC26XXDMA_CMD_SET_CSN_PIN:
            /* Configure CSN pin and remap PIN_ID to new CSN pin specified by arg */
            pinConfig = PIN_INPUT_EN | PIN_PULLUP | (*(PIN_Id *) arg);

            /* Attempt to add the new pin */
            if (PIN_add(object->pinHandle, pinConfig) == PIN_SUCCESS) {
                /* Configure pin mux */
                PINCC26XX_setMux(object->pinHandle, *(PIN_Id *)arg,  (hwAttrs->baseAddr == SSI0_BASE ? IOC_PORT_MCU_SSI0_FSS : IOC_PORT_MCU_SSI1_FSS));

                /* Remove old pin and revert to default setting specified in the board file */
                PIN_remove(object->pinHandle, object->csnPin);

                /* Keep track of current CSN pin */
                object->csnPin = *(PIN_Id *)arg;

                /* Set return value to indicate success */
                ret = SPI_STATUS_SUCCESS;
            }
            break;

In the above code pinConfig is set up according to the slave mode SSI operation mode and there is no conditional configuration based on operation mode. There are two issues with the existing implementation. One is that master mode hardware chip select reconfiguration is not implemented and the other is that a valid master mode configuration gets completely broken by calling SPI_control(handle, SPICC26XXDMA_SET_CSN_PIN, &csPin) as it changes the chip select into input so the entire SSI driver gets broken as long as SPI_close() and SPI_open() are not called again.

To eliminate the issue the implementation of SPICC26XXDMA_control() needs to be extended to conditionally set up the pin based on SPI operation mode (master or slave):

        case SPICC26XXDMA_SET_CSN_PIN:
            /* Configure CSN pin and remap PIN_ID to new CSN pin specified by arg */
            if (object->mode == SPI_SLAVE) {
                pinConfig = PIN_INPUT_EN | PIN_PULLUP | (*(PIN_Id *) arg);
            }
            else {
                pinConfig = PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH | PIN_PUSHPULL | PIN_INPUT_DIS | PIN_DRVSTR_MED | (*(PIN_Id *) arg);
            }

Another recommendation would be not to cast and dereference arg , (*(PIN_Id *) arg), each time manually within SPICC26XXDMA_control() instead create a local variable to hold the result of the operation which the compiler can optimize out putting the value into a Cortex-M3 scratch register:

    PIN_Config              pinConfig;
    PIN_Id                  pinId;

    /* Get the pointer to the object and hwAttr */
    hwAttrs = handle->hwAttrs;
    object = handle->object;
    pinId = PIN_ID((*(PIN_Id *) arg));

    /* Initialize return value*/
    int ret = SPI_STATUS_ERROR;

    /* Perform command */
    switch(cmd) {
    ...
        case SPICC26XXDMA_SET_CSN_PIN:
            /* Configure CSN pin and remap PIN_ID to new CSN pin specified by arg */
            if (object->mode == SPI_SLAVE) {
                pinConfig = PIN_INPUT_EN | PIN_PULLUP | pinId;
            }
            else {
                pinConfig = pinId | PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH | PIN_PUSHPULL | PIN_INPUT_DIS | PIN_DRVSTR_MED;
            }

            if (PIN_UNASSIGNED != pinId) {
    ...
                    /* Configure pin mux */
                    PINCC26XX_setMux(object->pinHandle, pinId,  (hwAttrs->baseAddr == SSI0_BASE ? IOC_PORT_MCU_SSI0_FSS : IOC_PORT_MCU_SSI1_FSS));
    ...
                    /* Keep track of current CSN pin */
                    object->csnPin = pinId;

Please fix the above described issue and if possible provide information on whether the bug can be fixed in the next TI-RTOS for CC13xx and CC26xx release and whether the fix would make it into the next CC26xx BLE-SDK release.

Thank you in advance for your kind support.

Best regards,

 Tamas

_

  • I have asked someone in the CC26xx drivers team to look into this.

    Alan
  • Hello Alan,

    thanks for your kind efforts.

    The following implementation resolves all the above issues and adds support to switch between hardware and software chip select mode on the fly by calling the API with PIN_UNASSIGNED stored in a variable passed by address as arg parameter.

    Hope this helps to speed up rolling out an official solution.

    Best regards,

     Tamas

    _

    int SPICC26XXDMA_control(SPI_Handle handle, unsigned int cmd, void *arg)
    {
        SPICC26XX_Object        *object;
        SPICC26XX_HWAttrs const *hwAttrs;
        PIN_Config              pinConfig;
        PIN_Id                  pinId;
    
        /* Get the pointer to the object and hwAttr */
        hwAttrs = handle->hwAttrs;
        object = handle->object;
        pinId = PIN_ID((*(PIN_Id *) arg));
    
        /* Initialize return value*/
        int ret = SPI_STATUS_ERROR;
    
        /* Perform command */
        switch(cmd) {
            case SPICC26XXDMA_RETURN_PARTIAL_ENABLE:
                /* Enable RETURN_PARTIAL if slave mode is enabled */
                object->returnPartial = (object->mode == SPI_SLAVE);
                ret = SPI_STATUS_SUCCESS;
                break;
    
            case SPICC26XXDMA_RETURN_PARTIAL_DISABLE:
                /* Disable RETURN_PARTIAL */
                object->returnPartial = false;
                ret = SPI_STATUS_SUCCESS;
                break;
    
            case SPICC26XXDMA_SET_CSN_PIN:
                /* Configure CSN pin and remap PIN_ID to new CSN pin specified by arg */
                if (object->mode == SPI_SLAVE) {
                    pinConfig = PIN_INPUT_EN | PIN_PULLUP | pinId;
                }
                else {
                    pinConfig = PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH | PIN_PUSHPULL | PIN_INPUT_DIS | PIN_DRVSTR_MED | pinId;
                }
    
                if (PIN_UNASSIGNED != pinId) {
                    /* Attempt to add the new pin */
                    if (PIN_add(object->pinHandle, pinConfig) == PIN_SUCCESS) {
                        /* Configure pin mux */
                        PINCC26XX_setMux(object->pinHandle, pinId,  (hwAttrs->baseAddr == SSI0_BASE ? IOC_PORT_MCU_SSI0_FSS : IOC_PORT_MCU_SSI1_FSS));
    
                        /* Remove old pin and revert to default setting specified in the board file */
                        PIN_remove(object->pinHandle, object->csnPin);
    
                        /* Keep track of current CSN pin */
                        object->csnPin = pinId;
    
                        /* Set return value to indicate success */
                        ret = SPI_STATUS_SUCCESS;
                    }
                }
                else {
                    if(object->csnPin != PIN_UNASSIGNED) {
                        /* Remove old pin and revert to default setting specified in the board file (implicitly sets IO muxing to GPIO mode) */
                        PIN_remove(object->pinHandle, object->csnPin);
                    }
    
                    /* Keep track of current CSN pin */
                    object->csnPin = pinId;
    
                    /* Set return value to indicate success */
                    ret = SPI_STATUS_SUCCESS;
                }
                break;
    
    #ifdef SPICC26XXDMA_WAKEUP_ENABLED
            case SPICC26XXDMA_SET_CSN_WAKEUP:
                /* Set wakeup callback function no matter what, NULL signals that the wakeup feature is disabled */
                object->wakeupCallbackFxn = *(SPICC26XXDMA_CallbackFxn)arg;
                ret = SPI_STATUS_SUCCESS;
                break;
    #endif
            default:
                /* This command is not defined */
                ret = SPI_STATUS_UNDEFINEDCMD;
                break;
        }
    
        return (ret);
    }