/* * Copyright (c) 2015-2018, Texas Instruments Incorporated * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Texas Instruments Incorporated nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include DeviceFamily_constructPath(inc/hw_memmap.h) #include DeviceFamily_constructPath(inc/hw_ints.h) #include DeviceFamily_constructPath(inc/hw_types.h) #include DeviceFamily_constructPath(driverlib/ssi.h) #include DeviceFamily_constructPath(driverlib/sys_ctrl.h) #include DeviceFamily_constructPath(driverlib/udma.h) #include DeviceFamily_constructPath(driverlib/ioc.h) #include DeviceFamily_constructPath(driverlib/prcm.h) #include DeviceFamily_constructPath(driverlib/rom.h) #include #include #include #include #include #include #include #include #define MAX_DMA_TRANSFER_AMOUNT (1024) /* SPI test control register */ #define SSI_O_TCR 0x00000080 #define SSI_TCR_TESTFIFO_ENABLE 0x2 #define SSI_TCR_TESTFIFO_DISABLE 0x0 /* SPI test data register */ #define SSI_O_TDR 0x0000008C /* Local typedef only. Used to easily migrate hwattrs versions */ typedef SPICC26XXDMA_HWAttrsV1 SPICC26XXDMA_HWAttrs; /* Allocate space for DMA control table entries */ ALLOCATE_CONTROL_TABLE_ENTRY(dmaSpi0TxControlTableEntry, UDMA_CHAN_SSI0_TX); ALLOCATE_CONTROL_TABLE_ENTRY(dmaSpi0RxControlTableEntry, UDMA_CHAN_SSI0_RX); ALLOCATE_CONTROL_TABLE_ENTRY(dmaSpi1TxControlTableEntry, UDMA_CHAN_SSI1_TX); ALLOCATE_CONTROL_TABLE_ENTRY(dmaSpi1RxControlTableEntry, UDMA_CHAN_SSI1_RX); /* SPICC26XX functions */ void SPICC26XXDMA_close(SPI_Handle handle); int_fast16_t SPICC26XXDMA_control(SPI_Handle handle, uint_fast16_t cmd, void *arg); void SPICC26XXDMA_init(SPI_Handle handle); SPI_Handle SPICC26XXDMA_open(SPI_Handle handle, SPI_Params *params); bool SPICC26XXDMA_transfer(SPI_Handle handle, SPI_Transaction *transaction); void SPICC26XXDMA_transferCancel(SPI_Handle handle); /* SPICC26XX internal functions */ static void SPICC26XXDMA_transferCallback(SPI_Handle handle, SPI_Transaction *msg); static void SPICC26XXDMA_csnCallback(PIN_Handle handle, PIN_Id pinId); static void SPICC26XXDMA_initHw(SPI_Handle handle); static bool SPICC26XXDMA_initIO(SPI_Handle handle); static void SPICC26XXDMA_flushFifos(SPI_Handle handle); /* Internal status macro */ static inline bool txFifoEmpty(SPICC26XXDMA_HWAttrs const *hwAttrs); /* Internal power functions */ static int spiPostNotify(unsigned int eventType, uintptr_t eventArg, uintptr_t clientArg); /* SPI function table for SPICC26XXDMA implementation */ const SPI_FxnTable SPICC26XXDMA_fxnTable = { SPICC26XXDMA_close, SPICC26XXDMA_control, SPICC26XXDMA_init, SPICC26XXDMA_open, SPICC26XXDMA_transfer, SPICC26XXDMA_transferCancel }; /* Mapping SPI mode from generic driver to CC26XX driverlib */ static const uint32_t mode[] = { SSI_MODE_MASTER, /* SPI_MASTER */ SSI_MODE_SLAVE /* SPI_SLAVE */ }; /* Mapping SPI frame format from generic driver to CC26XX driverlib */ static const uint32_t frameFormat[] = { SSI_FRF_MOTO_MODE_0, /* SPI_POLO_PHA0 */ SSI_FRF_MOTO_MODE_1, /* SPI_POLO_PHA1 */ SSI_FRF_MOTO_MODE_2, /* SPI_POL1_PHA0 */ SSI_FRF_MOTO_MODE_3, /* SPI_POL1_PHA1 */ SSI_FRF_TI, /* SPI_TI */ SSI_FRF_NMW /* SPI_MW */ }; /* * This lookup table is used to configure the DMA channels for the appropriate * (8bit or 16bit) transfer sizes. * Table for an SPI DMA TX channel */ static const unsigned long dmaTxConfig[] = { UDMA_MODE_BASIC | UDMA_SIZE_8 | UDMA_SRC_INC_8 | UDMA_DST_INC_NONE | UDMA_ARB_4, /* 8bit */ UDMA_MODE_BASIC | UDMA_SIZE_16 | UDMA_SRC_INC_16 | UDMA_DST_INC_NONE | UDMA_ARB_4 /* 16bit */ }; /* * This lookup table is used to configure the DMA channels for the appropriate * (8bit or 16bit) transfer sizes. * Table for an SPI DMA RX channel */ static const unsigned long dmaRxConfig[] = { UDMA_MODE_BASIC | UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_8 | UDMA_ARB_4, /* 8bit */ UDMA_MODE_BASIC | UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_16 | UDMA_ARB_4 /* 16bit */ }; /* * This lookup table is used to configure the DMA channels for the appropriate * (8bit or 16bit) transfer sizes when either txBuf or rxBuf are NULL */ static const uint32_t dmaNullConfig[] = { UDMA_MODE_BASIC | UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_NONE | UDMA_ARB_4, /* 8bit */ UDMA_MODE_BASIC | UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_NONE | UDMA_ARB_4 /* 16bit */ }; /* * Ensure safe setting of the standby disallow constraint. */ static inline void threadSafeConstraintSet(uint32_t txBufAddr, SPICC26XXDMA_Object *object) { unsigned int key; /* Disable interrupts */ key = HwiP_disable(); if (!object->spiPowerConstraint) { /* Ensure flash is available if TX buffer is in flash. Flash starts with 0x0..*/ if (((txBufAddr & 0xF0000000) == 0x0) && (txBufAddr)) { Power_setConstraint(PowerCC26XX_NEED_FLASH_IN_IDLE); Power_setConstraint(PowerCC26XX_DISALLOW_XOSC_HF_SWITCHING); } /* Set constraints to guarantee operation */ Power_setConstraint(PowerCC26XX_DISALLOW_STANDBY); object->spiPowerConstraint = true; } /* Re-enable interrupts */ HwiP_restore(key); } /* * Ensure safe releasing of the standby disallow constraint. */ static inline void threadSafeConstraintRelease(uint32_t txBufAddr, SPICC26XXDMA_Object *object) { unsigned int key; /* Disable interrupts */ key = HwiP_disable(); if (object->spiPowerConstraint) { /* Release need flash if buffer was in flash. */ if (((txBufAddr & 0xF0000000) == 0x0) && (txBufAddr)) { Power_releaseConstraint(PowerCC26XX_DISALLOW_XOSC_HF_SWITCHING); Power_releaseConstraint(PowerCC26XX_NEED_FLASH_IN_IDLE); } /* Release standby constraint since operation is done. */ Power_releaseConstraint(PowerCC26XX_DISALLOW_STANDBY); object->spiPowerConstraint = false; } /* Re-enable interrupts */ HwiP_restore(key); } /* * ======== spiPollingTransfer ======== */ static inline void spiPollingTransfer(SPICC26XXDMA_Object *object, SPICC26XXDMA_HWAttrsV1 const *hwAttrs, SPI_Transaction *transaction) { uint8_t increment; uint32_t dummyBuffer; size_t transferCount; void *rxBuf; void *txBuf; if (transaction->rxBuf) { rxBuf = transaction->rxBuf; } else { rxBuf = &(object->scratchBuf); } if (transaction->txBuf) { txBuf = transaction->txBuf; } else { object->scratchBuf = hwAttrs->defaultTxBufValue; txBuf = &(object->scratchBuf); } increment = (object->dataSize < 9) ? sizeof(uint8_t) : sizeof(uint16_t); transferCount = transaction->count; while (transferCount--) { if (object->dataSize < 9) { SSIDataPut(hwAttrs->baseAddr, *((uint8_t *) txBuf)); SSIDataGet(hwAttrs->baseAddr, &dummyBuffer); *((uint8_t *) rxBuf) = (uint8_t) dummyBuffer; } else { SSIDataPut(hwAttrs->baseAddr, *((uint16_t *) txBuf)); SSIDataGet(hwAttrs->baseAddr, &dummyBuffer); *((uint16_t *) rxBuf) = (uint16_t) dummyBuffer; } /* Only increment source & destination if buffers were provided */ if (transaction->rxBuf) { rxBuf = (void *) (((uint32_t) rxBuf) + increment); } if (transaction->txBuf) { txBuf = (void *) (((uint32_t) txBuf) + increment); } } while (!txFifoEmpty(hwAttrs)) {} } /*! * @brief Function to close a given CC26XX SPI peripheral specified by the * SPI handle. * * Will disable the SPI, disable all SPI interrupts and release the * dependency on the corresponding power domain. * * @pre SPICC26XXDMA_open() has to be called first. * Calling context: Task * * @param handle A SPI_Handle returned from SPI_open() * * @sa SPICC26XXDMA_open */ void SPICC26XXDMA_close(SPI_Handle handle) { SPICC26XXDMA_Object *object; SPICC26XXDMA_HWAttrs const *hwAttrs; /* Get the pointer to the object and hwAttrs */ hwAttrs = handle->hwAttrs; object = handle->object; /* Release the uDMA dependency and potentially power down uDMA. */ UDMACC26XX_close(object->udmaHandle); /* Deallocate pins */ PIN_close(object->pinHandle); /* Disable the SPI */ SSIDisable(hwAttrs->baseAddr); /* Destroy the Hwi */ HwiP_destruct(&(object->hwi)); /* Destroy the Swi */ SwiP_destruct(&(object->swi)); /* Release power dependency on SPI. */ Power_releaseDependency(hwAttrs->powerMngrId); if (object->transferMode == SPI_MODE_BLOCKING) { SemaphoreP_destruct(&(object->transferComplete)); } /* Unregister power notification objects */ Power_unregisterNotify(&object->spiPostObj); /* Mark the module as available */ object->isOpen = false; } /*! * @brief Function for setting control parameters of the SPI driver * after it has been opened. * * @pre SPICC26XXDMA_open() has to be called first. * Calling context: Hwi, Swi, Task * * @param handle A SPI handle returned from SPICC26XXDMA_open() * * @param cmd The command to execute, supported commands are: * | Command | Description | * |-------------------------------------- |-------------------------| * | ::SPICC26XXDMA_CMD_RETURN_PARTIAL_ENABLE | Enable RETURN_PARTIAL | * | ::SPICC26XXDMA_CMD_RETURN_PARTIAL_DISABLE | Disable RETURN_PARTIAL | * | ::SPICC26XXDMA_CMD_SET_CSN_PIN | Re-configure chip select pin | * * @param *arg Pointer to command arguments. * * @return ::SPI_STATUS_SUCCESS if success, or error code if error. */ int_fast16_t SPICC26XXDMA_control(SPI_Handle handle, uint_fast16_t cmd, void *arg) { SPICC26XXDMA_Object *object; SPICC26XXDMA_HWAttrs const *hwAttrs; PIN_Config pinConfig; PIN_Id pinId; /* Get the pointer to the object and hwAttr */ hwAttrs = handle->hwAttrs; object = handle->object; /* Initialize return value*/ int ret = SPI_STATUS_ERROR; /* Perform command */ switch(cmd) { case SPICC26XXDMA_CMD_RETURN_PARTIAL_ENABLE: /* Enable RETURN_PARTIAL if slave mode is enabled */ if(object->mode == SPI_SLAVE){ object->returnPartial = true; ret = SPI_STATUS_SUCCESS; } else{ /* Partial return not available in master mode. */ ret = SPI_STATUS_ERROR; } break; case SPICC26XXDMA_CMD_RETURN_PARTIAL_DISABLE: /* Disable RETURN_PARTIAL */ object->returnPartial = false; ret = SPI_STATUS_SUCCESS; break; case SPICC26XXDMA_CMD_SET_CSN_PIN: pinId = ((*(PIN_Id *) arg)); /* 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 (pinId != PIN_UNASSIGNED) { /* 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 { /* We want to use software ctrl CSN. Hence, undo any prior hardware CSN pin muxing */ /* 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; default: /* This command is not defined */ ret = SPI_STATUS_UNDEFINEDCMD; break; } return (ret); } /* * ======== SPICC26XXDMA_configDMA ======== * This functions configures the transmit and receive DMA channels for a given * SPI_Handle and SPI_Transaction * * @pre Function assumes that the handle and transaction is not NULL */ static void SPICC26XXDMA_configDMA(SPICC26XXDMA_Object *object, SPICC26XXDMA_HWAttrs const *hwAttrs) { volatile tDMAControlTable *dmaControlTableEntry; uint16_t numberOfBytes; /* * The DMA has a max transfer amount of 1024. If the transaction is * greater; we must transfer it in chunks. object->amtDataXferred has * how much data has already been sent. */ if ((object->currentTransaction->count - object->amtDataXferred) > MAX_DMA_TRANSFER_AMOUNT) { object->currentXferAmt = MAX_DMA_TRANSFER_AMOUNT; } else { object->currentXferAmt = (object->currentTransaction->count - object->amtDataXferred); } /* Calculate the number of bytes for the transfer */ numberOfBytes = ((uint16_t) object->currentXferAmt) << (object->frameSize); /* Setup RX side */ /* Set pointer to Rx control table entry */ dmaControlTableEntry = (hwAttrs->baseAddr == SSI0_BASE ? &dmaSpi0RxControlTableEntry : &dmaSpi1RxControlTableEntry); if (object->currentTransaction->rxBuf) { dmaControlTableEntry->ui32Control = dmaRxConfig[object->frameSize]; /* * Add an offset for the amount of data transfered. The offset is * calculated by: object->amtDataXferred * (object->frameSize + 1). * This accounts for 8 or 16-bit sized transfers. */ dmaControlTableEntry->pvDstEndAddr = (void *)((uint32_t) object->currentTransaction->rxBuf + ((uint32_t) object->amtDataXferred * (object->frameSize + 1)) + numberOfBytes - 1); } else { dmaControlTableEntry->ui32Control = dmaNullConfig[object->frameSize]; dmaControlTableEntry->pvDstEndAddr = (void *) &(object->scratchBuf); } dmaControlTableEntry->pvSrcEndAddr = (void *)(hwAttrs->baseAddr + SSI_O_DR); dmaControlTableEntry->ui32Control |= UDMACC26XX_SET_TRANSFER_SIZE((uint16_t) object->currentXferAmt); /* Setup TX side */ /* Set pointer to Tx control table entry */ dmaControlTableEntry = (hwAttrs->baseAddr == SSI0_BASE ? &dmaSpi0TxControlTableEntry : &dmaSpi1TxControlTableEntry); if (object->currentTransaction->txBuf) { dmaControlTableEntry->ui32Control = dmaTxConfig[object->frameSize]; /* * Add an offset for the amount of data transfered. The offset is * calculated by: object->amtDataXferred * (object->frameSize + 1). * This accounts for 8 or 16-bit sized transfers. */ dmaControlTableEntry->pvSrcEndAddr = (void *)((uint32_t) object->currentTransaction->txBuf + ((uint32_t) object->amtDataXferred * (object->frameSize + 1)) + numberOfBytes - 1); } else { object->scratchBuf = hwAttrs->defaultTxBufValue; dmaControlTableEntry->ui32Control = dmaNullConfig[object->frameSize]; dmaControlTableEntry->pvSrcEndAddr = (void *) &(object->scratchBuf); } dmaControlTableEntry->pvDstEndAddr = (void *)(hwAttrs->baseAddr + SSI_O_DR); dmaControlTableEntry->ui32Control |= UDMACC26XX_SET_TRANSFER_SIZE((uint16_t) object->currentXferAmt); /* Enable the channels */ UDMACC26XX_channelEnable(object->udmaHandle, (hwAttrs->rxChannelBitMask) | (hwAttrs->txChannelBitMask)); /* Enable the required DMA channels in the SPI module to start the transaction */ SSIDMAEnable(hwAttrs->baseAddr, SSI_DMA_TX | SSI_DMA_RX); } /* * ======== SPICC26XXDMA_hwiFxn ======== * HWI ISR for the SPI when we use the UDMA */ static void SPICC26XXDMA_hwiFxn (uintptr_t arg) { SPICC26XXDMA_Object *object; SPICC26XXDMA_HWAttrs const *hwAttrs; uint32_t intStatus; /* Get the pointer to the object and hwAttrs */ object = ((SPI_Handle)arg)->object; hwAttrs = ((SPI_Handle)arg)->hwAttrs; /* Get the interrupt status of the SPI controller */ intStatus = SSIIntStatus(hwAttrs->baseAddr, true); SSIIntClear(hwAttrs->baseAddr, intStatus); /* Error handling: * Overrun in the RX Fifo -> at least one sample in the shift * register has been discarded */ if (intStatus & SSI_RXOR) { /* disable the interrupt */ SSIIntDisable(hwAttrs->baseAddr, SSI_RXOR); /* If the RX overrun occurred during a transfer */ if (object->currentTransaction) { /* Then cancel the ongoing transfer */ SPICC26XXDMA_transferCancel((SPI_Handle)arg); } else { /* Otherwise disable the SPI and DMA modules and flush FIFOs */ SSIDisable(hwAttrs->baseAddr); /* Disable SPI TX/RX DMA and clear DMA done interrupt just in case it finished */ SSIDMADisable(hwAttrs->baseAddr, SSI_DMA_TX | SSI_DMA_RX); UDMACC26XX_clearInterrupt(object->udmaHandle, (hwAttrs->rxChannelBitMask) | (hwAttrs->txChannelBitMask)); /* Clear out the FIFO by resetting SPI module and re-initting */ SPICC26XXDMA_flushFifos((SPI_Handle)arg); } } else { /* * Determine if the TX DMA channel has completed... In SPI slave mode * this interrupt may occur immediately (without the RX DMA channel). * * All transfers will set up both TX and RX DMA channels and both will finish. * Even if the transaction->rxBuf == NULL, it will setup a dummy RX transfer to * a scratch memory location which is then discarded. */ if (UDMACC26XX_channelDone(object->udmaHandle, hwAttrs->txChannelBitMask)) { /* Disable SPI TX DMA and clear DMA done interrupt. */ SSIDMADisable(hwAttrs->baseAddr, SSI_DMA_TX); UDMACC26XX_clearInterrupt(object->udmaHandle, hwAttrs->txChannelBitMask); } /* * Determine if the RX DMA channel has completed... In slave mode this interrupt * occurrence depends on when the SPI master starts sending data. */ if (UDMACC26XX_channelDone(object->udmaHandle, hwAttrs->rxChannelBitMask)) { /* Disable SPI RX DMA and clear DMA done interrupt. */ SSIDMADisable(hwAttrs->baseAddr, SSI_DMA_RX); UDMACC26XX_clearInterrupt(object->udmaHandle, hwAttrs->rxChannelBitMask); /* Post SWI to handle remaining clean up and invocation of callback */ SwiP_post(&(object->swi)); } } } /* * ======== SPICC26XXDMA_swiFxn ======== * SWI ISR for the SPI when we use the UDMA * Determine if the RX DMA channel has completed... In slave mode this interrupt * occurrence depends on when the SPI master starts sending data. */ static void SPICC26XXDMA_swiFxn (uintptr_t arg0, uintptr_t arg1) { SPI_Transaction *transaction; SPICC26XXDMA_Object *object; SPICC26XXDMA_HWAttrs const *hwAttrs; /* Get the pointer to the object and hwAttrs */ object = ((SPI_Handle) arg0)->object; hwAttrs = ((SPI_Handle) arg0)->hwAttrs; /* Check if there is an active transaction */ if (object->currentTransaction == NULL) { /* If the currentTransaction is NULL, this SWI was posted already by either the ISR or transferCancel. * Do nothing and return. * There is no need to disable interrupts and set up a temporary pointer for this fxn. The only place * currentTransaction can be set after initialization is in this function and this swi cannot preempt * itself to cause a race condition in the middle of it. */ return; } if (object->currentTransaction->count - object->amtDataXferred > MAX_DMA_TRANSFER_AMOUNT) { /* Data still remaining, configure another DMA transfer */ object->amtDataXferred += object->currentXferAmt; SPICC26XXDMA_configDMA(object, hwAttrs); } else { /* Transaction is complete */ if (object->currentTransaction->status == SPI_TRANSFER_STARTED) { object->currentTransaction->status = SPI_TRANSFER_COMPLETED; } /* Use a temporary transaction pointer in case the callback function * attempts to perform another SPI_transfer call */ transaction = object->currentTransaction; /* Indicate we are done with this transfer */ object->currentTransaction = NULL; /* Release constraint since transaction is done */ threadSafeConstraintRelease((uint32_t)(transaction->txBuf), object); /* Perform callback */ object->transferCallbackFxn((SPI_Handle)arg0, transaction); } } /* * ======== SPICC26XXDMA_flushTxFifo ======== */ void SPICC26XXDMA_flushFifos(SPI_Handle handle) { /* Locals */ SPICC26XXDMA_HWAttrs const *hwAttrs; /* Get the pointer to the hwAttrs */ hwAttrs = handle->hwAttrs; /* Flush RX FIFO */ while(HWREG(hwAttrs->baseAddr + SSI_O_SR) & SSI_RX_NOT_EMPTY) { /* Read element from RX FIFO and discard */ HWREG(hwAttrs->baseAddr + SSI_O_DR); } /* Enable TESTFIFO mode */ HWREG(hwAttrs->baseAddr + SSI_O_TCR) = SSI_TCR_TESTFIFO_ENABLE; /* Flush TX FIFO */ while(!(HWREG(hwAttrs->baseAddr + SSI_O_SR) & SSI_TX_EMPTY)) { /* Read element from TX FIFO and discard */ HWREG(hwAttrs->baseAddr + SSI_O_TDR); } /* Disable TESTFIFO mode */ HWREG(hwAttrs->baseAddr + SSI_O_TCR) = SSI_TCR_TESTFIFO_DISABLE; } /*! * @brief SPI CC26XX initialization * * The function will set the isOpen flag to false. * * @pre Calling context: Hwi, Swi, Task, Main * * @param handle A SPI_Handle * */ void SPICC26XXDMA_init(SPI_Handle handle) { SPICC26XXDMA_Object *object; /* Get the pointer to the object */ object = handle->object; /* Mark the object as available */ object->isOpen = false; /* Init power constraint flag. */ object->spiPowerConstraint = false; } /*! * @brief Function to initialize the CC26XX SPI peripheral specified by the * particular handle. The parameter specifies which mode the SPI * will operate. * * The function will set a dependency on it power domain, i.e. power up the * module and enable the clock. The IOs are allocated. Neither the SPI nor UDMA module * will be enabled. * * @pre SPI controller has been initialized. * Calling context: Task * * @param handle A SPI_Handle * * @param params Pointer to a parameter block, if NULL it will use * default values * * @return A SPI_Handle on success or a NULL on an error or if it has been * already opened * * @sa SPICC26XXDMA_close() */ SPI_Handle SPICC26XXDMA_open(SPI_Handle handle, SPI_Params *params) { /* Use union to save on stack allocation */ union { HwiP_Params hwiParams; SwiP_Params swiParams; } paramsUnion; SPICC26XXDMA_Object *object; SPICC26XXDMA_HWAttrs const *hwAttrs; unsigned int key; /* Get the pointer to the object and hwAttrs */ object = handle->object; hwAttrs = handle->hwAttrs; /* Disable preemption while checking if the SPI is open. */ key = HwiP_disable(); /* Check if the SPI is open already with the base addr. */ if (object->isOpen) { HwiP_restore(key); return (NULL); } /* Mark the handle as being used */ object->isOpen = true; HwiP_restore(key); DebugP_assert((params->dataSize >= 4) && (params->dataSize <= 16)); /* Initialize the SPI object */ object->currentTransaction = NULL; object->bitRate = params->bitRate; object->dataSize = params->dataSize; object->frameFormat = params->frameFormat; object->mode = params->mode; object->transferMode = params->transferMode; object->transferTimeout = params->transferTimeout; object->returnPartial = false; /* Determine if we need to use an 8-bit or 16-bit framesize for the DMA */ object->frameSize = (params->dataSize < 9) ? SPICC26XXDMA_8bit : SPICC26XXDMA_16bit; /* Register power dependency - i.e. power up and enable clock for SPI. */ Power_setDependency(hwAttrs->powerMngrId); /* Configure the hardware module */ SPICC26XXDMA_initHw(handle); /* CSN is initialized using hwAttrs initially, but can be re-configured later */ object->csnPin = hwAttrs->csnPin; /* Configure IOs after hardware has been initialized so that IOs aren't */ /* toggled unnecessary and make sure it was successful */ if (!SPICC26XXDMA_initIO(handle)) { /* Trying to use SPI driver when some other driver or application * has already allocated these pins, error! */ /* Release power dependency - i.e. potentially power down serial domain. */ Power_releaseDependency(hwAttrs->powerMngrId); object->isOpen = false; /* Signal back to application that SPI driver was not successfully opened */ return (NULL); } /* Create the Hwi for this SPI peripheral. */ HwiP_Params_init(¶msUnion.hwiParams); paramsUnion.hwiParams.arg = (uintptr_t) handle; paramsUnion.hwiParams.priority = hwAttrs->intPriority; HwiP_construct(&(object->hwi), (int) hwAttrs->intNum, SPICC26XXDMA_hwiFxn, ¶msUnion.hwiParams); /* Create Swi object for this SPI peripheral */ SwiP_Params_init(¶msUnion.swiParams); paramsUnion.swiParams.arg0 = (uintptr_t)handle; paramsUnion.swiParams.priority = hwAttrs->swiPriority; SwiP_construct(&(object->swi), SPICC26XXDMA_swiFxn, &(paramsUnion.swiParams)); /* Declare the dependency on the UDMA driver */ object->udmaHandle = UDMACC26XX_open(); /* Configure PIN driver for CSN callback in optional RETURN_PARTIAL slave mode */ if (object->mode == SPI_SLAVE) { PIN_registerIntCb(object->pinHandle, SPICC26XXDMA_csnCallback); PIN_setUserArg(object->pinHandle, (uintptr_t) handle); } /* Register notification functions */ Power_registerNotify(&object->spiPostObj, PowerCC26XX_AWAKE_STANDBY, (Power_NotifyFxn)spiPostNotify, (uint32_t)handle); /* Check the transfer mode */ if (object->transferMode == SPI_MODE_BLOCKING) { /* Create a semaphore to block task execution for the duration of the * SPI transfer */ SemaphoreP_constructBinary(&(object->transferComplete), 0); /* Store internal callback function */ object->transferCallbackFxn = SPICC26XXDMA_transferCallback; } else { /* Check to see if a callback function was defined for async mode */ DebugP_assert(params->transferCallbackFxn != NULL); /* Save the callback function pointer */ object->transferCallbackFxn = params->transferCallbackFxn; } return (handle); } /*! * @brief Function for transferring using the SPI interface. * * The function will enable the SPI and UDMA modules and disallow * the device from going into standby. * * In ::SPI_MODE_BLOCKING, SPI_transfer will block task execution until the transfer * has ended. * * In ::SPI_MODE_CALLBACK, SPI_transfer does not block task execution, but calls a * callback function specified by transferCallback when the transfer has ended. * * @pre SPICC26XXDMA_open() has to be called first. * Calling context: Hwi and Swi (only if using ::SPI_MODE_CALLBACK), Task * * @param handle A SPI handle returned from SPICC26XXDMA_open() * * @param *transaction Pointer to transaction struct * * @return True if transfer is successful and false if not * * @sa SPICC26XXDMA_open(), SPICC26XXDMA_transferCancel() */ bool SPICC26XXDMA_transfer(SPI_Handle handle, SPI_Transaction *transaction) { unsigned int key; uint8_t alignMask; bool buffersAligned; SPICC26XXDMA_Object *object; SPICC26XXDMA_HWAttrs const *hwAttrs; /* Get the pointer to the object and hwAttr*/ object = handle->object; hwAttrs = handle->hwAttrs; if (transaction->count == 0 || (transaction->rxBuf == NULL && transaction->txBuf == NULL)) { return (false); } /* * Make sure that the buffers are aligned properly. * alignMask is used to check if the RX/TX buffers addresses * are aligned to the frameSize. */ alignMask = (object->frameSize == SPICC26XXDMA_16bit) ? 0x1 : 0x0; buffersAligned = ((((uint32_t) transaction->rxBuf & alignMask) == 0) && (((uint32_t) transaction->txBuf & alignMask) == 0)); if (!buffersAligned) { return (false); } /* Disable preemption while checking if a transfer is in progress */ key = HwiP_disable(); if (object->currentTransaction) { HwiP_restore(key); /* Flag that the transfer failed to start */ transaction->status = SPI_TRANSFER_FAILED; /* Transfer is in progress */ return (false); } /* Make sure to flag that a transaction is now active */ transaction->status = SPI_TRANSFER_STARTED; object->currentTransaction = transaction; object->amtDataXferred = 0; object->currentXferAmt = 0; HwiP_restore(key); /* In slave mode, optionally enable callback on CSN de-assert */ if (object->returnPartial) { PIN_setInterrupt(object->pinHandle, object->csnPin | PIN_IRQ_POSEDGE); } /* Set constraints to guarantee transaction */ threadSafeConstraintSet((uint32_t)(transaction->txBuf), object); /* Enable the SPI module */ SSIEnable(hwAttrs->baseAddr); /* * Polling transfer if BLOCKING mode & transaction->count < threshold * Slaves not allowed to use polling unless timeout is disabled */ if (object->transferMode == SPI_MODE_BLOCKING && transaction->count < hwAttrs->minDmaTransferSize && (object->mode == SPI_MASTER || object->transferTimeout == SPI_WAIT_FOREVER)) { HwiP_restore(key); spiPollingTransfer(object, hwAttrs, transaction); /* Disable the SPI */ SSIDisable(hwAttrs->baseAddr); /* Release constraint since transaction is done */ threadSafeConstraintRelease((uint32_t)(transaction->txBuf), object); /* Transaction completed; set status & mark SPI ready */ object->currentTransaction->status = SPI_TRANSFER_COMPLETED; object->currentTransaction = NULL; return (true); } /* Setup DMA transfer. */ SPICC26XXDMA_configDMA(object, hwAttrs); /* Enable the RX overrun interrupt in the SSI module */ SSIIntEnable(hwAttrs->baseAddr, SSI_RXOR); if (object->transferMode == SPI_MODE_BLOCKING) { if (SemaphoreP_OK != SemaphoreP_pend(&(object->transferComplete), object->transferTimeout)) { /* Mark the transfer as failed. Otherwise SPICC26XXDMA_transferCancel will set it to canceled. */ if (object->currentTransaction->status == SPI_TRANSFER_STARTED) { object->currentTransaction->status = SPI_TRANSFER_FAILED; } /* Cancel the transfer, if we experience a timeout */ SPICC26XXDMA_transferCancel(handle); /* * SPICC26XXDMA_transferCancel performs a callback which posts a * transferComplete semaphore. This call consumes this extra post. */ SemaphoreP_pend(&(object->transferComplete), SemaphoreP_NO_WAIT); return (false); } } return (true); } /*! * @brief Function that cancels a SPI transfer. Will disable SPI and UDMA modules * and allow standby. * * @pre SPICC26XXDMA_open() has to be called first. * Calling context: Task * * @param handle The SPI_Handle for ongoing transaction. */ void SPICC26XXDMA_transferCancel(SPI_Handle handle) { SPICC26XXDMA_Object *object; SPICC26XXDMA_HWAttrs const *hwAttrs; SPI_Transaction *transaction; volatile tDMAControlTable *dmaControlTableEntry; /* Get the pointer to the object and hwAttrs */ object = handle->object; hwAttrs = handle->hwAttrs; /* Disable interrupts to ensure that this function is not interrupted. The calls to SPICC26XXDMA_transferCancel * within the driver are safe. However, this function may be called by the application in callback mode. * Hence, it might be preempted by the SPI hwi and swi or the PIN hwi and swi. */ uint32_t key = HwiP_disable(); /* Check if there is an active transaction */ if(object->currentTransaction == NULL) { HwiP_restore(key); return; } transaction = object->currentTransaction; HwiP_restore(key); /* Disable the SPI module */ SSIDisable(hwAttrs->baseAddr); /* Disable SPI TX/RX DMA and clear DMA done interrupt just in case it finished */ SSIDMADisable(hwAttrs->baseAddr, SSI_DMA_TX | SSI_DMA_RX); UDMACC26XX_clearInterrupt(object->udmaHandle, (hwAttrs->rxChannelBitMask) | (hwAttrs->txChannelBitMask)); /* Disable and clear any pending interrupts */ SSIIntDisable(hwAttrs->baseAddr, SSI_RXOR); SSIIntClear(hwAttrs->baseAddr, SSI_RXOR); /* Flush the FIFOs and re-initialize module */ SPICC26XXDMA_flushFifos(handle); /* Release constraint since transaction is done */ threadSafeConstraintRelease((uint32_t)(transaction->txBuf), object); /* Mark the transaction as failed if we didn't end up here due to a CSN deassertion */ if (transaction->status == SPI_TRANSFER_STARTED) { transaction->status = SPI_TRANSFER_CANCELED; } /* Disable the UDMA channels */ UDMACC26XX_channelDisable(object->udmaHandle, (hwAttrs->rxChannelBitMask) | (hwAttrs->txChannelBitMask)); /* Update the SPI_Transaction.count parameter */ /* rxChannel always finishes after txChannel so remaining bytes of the rxChannel is used to update count */ dmaControlTableEntry = (hwAttrs->baseAddr == SSI0_BASE ? &dmaSpi0RxControlTableEntry : &dmaSpi1RxControlTableEntry); transaction->count = object->amtDataXferred + (object->currentXferAmt - UDMACC26XX_GET_TRANSFER_SIZE(dmaControlTableEntry->ui32Control)); /* Post SWI to handle remaining clean up and invocation of callback */ SwiP_post(&(object->swi)); } /* * ======== SPICC26XXDMA_transferCallback ======== * Callback function for when the SPI is in SPI_MODE_BLOCKING * * @pre Function assumes that the handle is not NULL */ static void SPICC26XXDMA_transferCallback(SPI_Handle handle, SPI_Transaction *msg) { SPICC26XXDMA_Object *object; /* Get the pointer to the object */ object = handle->object; /* Post the semaphore */ SemaphoreP_post(&(object->transferComplete)); } /* * ======== SPICC26XXDMA_csnDeassertCallback ======== * Slave mode optional callback function for when the CSN is deasserted * * @pre Function assumes that the handle is not NULL */ static void SPICC26XXDMA_csnCallback(PIN_Handle handle, PIN_Id pinId) { SPICC26XXDMA_Object *object; SPI_Handle spiHandle; PIN_Config csnConfig; /* Get the pointer to the SPI object */ spiHandle = (SPI_Handle) PIN_getUserArg(handle); object = spiHandle->object; /* Get current CSN config */ csnConfig = PIN_getConfig(object->csnPin); /* Disable all interrupts */ PIN_setInterrupt(handle, object->csnPin); /* Cancel transfer if POSEDGE interrupt */ /* TODO: Consider doing this in a SWI */ if ((csnConfig & PIN_IRQ_POSEDGE) == PIN_IRQ_POSEDGE) { /* Indicate why the transaction completed */ if ((object->currentTransaction != NULL) && (object->currentTransaction->status == SPI_TRANSFER_STARTED)) { object->currentTransaction->status = SPI_TRANSFER_CSN_DEASSERT; } /* Cancel the current transaction */ SPICC26XXDMA_transferCancel(spiHandle); } } /* * ======== SPICC26XXDMA_hwInit ======== * This functions initializes the SPI hardware module. * * @pre Function assumes that the SPI handle is pointing to a hardware * module which has already been opened. */ static void SPICC26XXDMA_initHw(SPI_Handle handle) { SPICC26XXDMA_Object *object; SPICC26XXDMA_HWAttrs const *hwAttrs; ClockP_FreqHz freq; /* Get the pointer to the object and hwAttrs */ object = handle->object; hwAttrs = handle->hwAttrs; /* Disable SSI operation */ SSIDisable(hwAttrs->baseAddr); /* Disable SPI module interrupts */ SSIIntDisable(hwAttrs->baseAddr, SSI_RXOR | SSI_RXFF | SSI_RXTO | SSI_TXFF); SSIIntClear(hwAttrs->baseAddr, SSI_RXOR | SSI_RXTO); /* Set the SPI configuration */ ClockP_getCpuFreq(&freq); SSIConfigSetExpClk(hwAttrs->baseAddr, freq.lo, frameFormat[object->frameFormat], mode[object->mode], object->bitRate, object->dataSize); } /* * ======== SPICC26XXDMA_hwInit ======== * This functions initializes the SPI IOs. * * @pre Function assumes that the SPI handle is pointing to a hardware * module which has already been opened. */ static bool SPICC26XXDMA_initIO(SPI_Handle handle) { SPICC26XXDMA_Object *object; SPICC26XXDMA_HWAttrs const *hwAttrs; PIN_Config spiPinTable[5]; uint32_t i = 0; /* Get the pointer to the object and hwAttrs */ object = handle->object; hwAttrs = handle->hwAttrs; /* 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++] = hwAttrs->mosiPin | PIN_INPUT_EN; spiPinTable[i++] = hwAttrs->misoPin | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_INPUT_DIS | PIN_DRVSTR_MED; spiPinTable[i++] = hwAttrs->clkPin | PIN_INPUT_EN; spiPinTable[i++] = object->csnPin | PIN_INPUT_EN | PIN_PULLUP; } else { /* Configure IOs for master mode */ spiPinTable[i++] = hwAttrs->mosiPin | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_INPUT_DIS | PIN_DRVSTR_MED; spiPinTable[i++] = hwAttrs->misoPin | PIN_INPUT_EN | PIN_PULLDOWN; /* Output low signal on SCLK until SPI module drives signal if clock polarity is configured to '0' */ /* Output high signal on SCLK until SPI module drives signal if clock polarity is configured to '1' */ if (object->frameFormat == SPI_POL0_PHA0 || object->frameFormat == SPI_POL0_PHA1) { spiPinTable[i++] = hwAttrs->clkPin | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_INPUT_DIS | PIN_DRVSTR_MED; } else { spiPinTable[i++] = hwAttrs->clkPin | PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH | PIN_PUSHPULL | PIN_INPUT_DIS | PIN_DRVSTR_MED; } /* 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; } } spiPinTable[i++] = PIN_TERMINATE; /* Open and assign pins through pin driver */ if (!(object->pinHandle = PIN_open(&(object->pinState), spiPinTable))) { return false; } /* Set IO muxing for the SPI pins */ if (mode[object->mode] == SSI_MODE_SLAVE) { /* Configure IOs for slave mode */ PINCC26XX_setMux(object->pinHandle, hwAttrs->mosiPin, (hwAttrs->baseAddr == SSI0_BASE ? IOC_PORT_MCU_SSI0_RX : IOC_PORT_MCU_SSI1_RX)); PINCC26XX_setMux(object->pinHandle, hwAttrs->misoPin, (hwAttrs->baseAddr == SSI0_BASE ? IOC_PORT_MCU_SSI0_TX : IOC_PORT_MCU_SSI1_TX)); PINCC26XX_setMux(object->pinHandle, hwAttrs->clkPin, (hwAttrs->baseAddr == SSI0_BASE ? IOC_PORT_MCU_SSI0_CLK : IOC_PORT_MCU_SSI1_CLK)); PINCC26XX_setMux(object->pinHandle, object->csnPin, (hwAttrs->baseAddr == SSI0_BASE ? IOC_PORT_MCU_SSI0_FSS : IOC_PORT_MCU_SSI1_FSS)); } else { /* Configure IOs for master mode */ PINCC26XX_setMux(object->pinHandle, hwAttrs->mosiPin, (hwAttrs->baseAddr == SSI0_BASE ? IOC_PORT_MCU_SSI0_TX : IOC_PORT_MCU_SSI1_TX)); PINCC26XX_setMux(object->pinHandle, hwAttrs->misoPin, (hwAttrs->baseAddr == SSI0_BASE ? IOC_PORT_MCU_SSI0_RX : IOC_PORT_MCU_SSI1_RX)); PINCC26XX_setMux(object->pinHandle, hwAttrs->clkPin, (hwAttrs->baseAddr == SSI0_BASE ? IOC_PORT_MCU_SSI0_CLK : IOC_PORT_MCU_SSI1_CLK)); if(object->csnPin != PIN_UNASSIGNED) { PINCC26XX_setMux(object->pinHandle, object->csnPin, (hwAttrs->baseAddr == SSI0_BASE ? IOC_PORT_MCU_SSI0_FSS : IOC_PORT_MCU_SSI1_FSS)); } } return true; } /* * ======== spiPostNotify ======== * This functions is called to notify the SPI driver of an ongoing transition * out of sleep mode. * * @pre Function assumes that the SPI handle (clientArg) is pointing to a * hardware module which has already been opened. */ static int spiPostNotify(unsigned int eventType, uintptr_t eventArg, uintptr_t clientArg) { SPI_Handle spiHandle; /* Get the pointers to SPI objects */ spiHandle = (SPI_Handle) clientArg; /* Reconfigure the hardware when returning from standby */ SPICC26XXDMA_initHw(spiHandle); return Power_NOTIFYDONE; } /* * ======== txFifoEmpty ======== */ static inline bool txFifoEmpty(SPICC26XXDMA_HWAttrs const *hwAttrs) { return(HWREG(hwAttrs->baseAddr + SSI_O_SR) & SSI_SR_TFE); }