summaryrefslogtreecommitdiffstats
path: root/c/src/lib/libbsp/arm/atsam/spi/atsam_spi_bus.c
diff options
context:
space:
mode:
Diffstat (limited to 'c/src/lib/libbsp/arm/atsam/spi/atsam_spi_bus.c')
-rw-r--r--c/src/lib/libbsp/arm/atsam/spi/atsam_spi_bus.c576
1 files changed, 576 insertions, 0 deletions
diff --git a/c/src/lib/libbsp/arm/atsam/spi/atsam_spi_bus.c b/c/src/lib/libbsp/arm/atsam/spi/atsam_spi_bus.c
new file mode 100644
index 0000000000..3f44a4e1f1
--- /dev/null
+++ b/c/src/lib/libbsp/arm/atsam/spi/atsam_spi_bus.c
@@ -0,0 +1,576 @@
+/* ---------------------------------------------------------------------------- */
+/* Atmel Microcontroller Software Support */
+/* SAM Software Package License */
+/* ---------------------------------------------------------------------------- */
+/* Copyright (c) 2015, Atmel Corporation */
+/* Copyright (c) 2016, embedded brains GmbH */
+/* */
+/* All rights reserved. */
+/* */
+/* Redistribution and use in source and binary forms, with or without */
+/* modification, are permitted provided that the following condition is met: */
+/* */
+/* - Redistributions of source code must retain the above copyright notice, */
+/* this list of conditions and the disclaimer below. */
+/* */
+/* Atmel's name may not be used to endorse or promote products derived from */
+/* this software without specific prior written permission. */
+/* */
+/* DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR */
+/* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE */
+/* DISCLAIMED. IN NO EVENT SHALL ATMEL 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 <bsp/atsam-spi.h>
+
+#include <rtems/irq-extension.h>
+
+#define MAX_SPI_FREQUENCY 50000000
+
+static void atsam_finish_command(Spid *SpiDma)
+{
+ Spi *pSpiHw = SpiDma->pSpiHw;
+
+ SPI_Disable(pSpiHw);
+
+ PMC_DisablePeripheral(SpiDma->spiId);
+}
+
+static void atsam_interrupt_handler(void *arg)
+{
+ atsam_spi_bus *bus = (atsam_spi_bus *)arg;
+ sXdmad *Dma = &bus->Dma;
+ Spid *spid = &bus->SpiDma;
+ Xdmac *xdmac;
+ sXdmadChannel *ch;
+ uint32_t xdmaChannelIntStatus, xdmaGlobaIntStatus, xdmaGlobalChStatus;
+ uint8_t channel;
+ uint8_t bExec = 0;
+ rtems_status_code sc;
+ assert(Dma != NULL);
+
+ xdmac = Dma->pXdmacs;
+ xdmaGlobaIntStatus = XDMAC_GetGIsr(xdmac);
+
+ if ((xdmaGlobaIntStatus & 0xFFFFFF) != 0) {
+ xdmaGlobalChStatus = XDMAC_GetGlobalChStatus(xdmac);
+
+ for (channel = 0; channel < Dma->numChannels; channel ++) {
+ if (!(xdmaGlobaIntStatus & (1 << channel))) {
+ continue;
+ }
+
+ ch = &Dma->XdmaChannels[channel];
+
+ if (ch->state == XDMAD_STATE_FREE) {
+ return;
+ }
+
+ if ((xdmaGlobalChStatus & (XDMAC_GS_ST0 << channel)) == 0) {
+ bExec = 0;
+ xdmaChannelIntStatus = XDMAC_GetMaskChannelIsr(xdmac, channel);
+
+ if (xdmaChannelIntStatus & XDMAC_CIS_BIS) {
+ if ((XDMAC_GetChannelItMask(xdmac, channel) & XDMAC_CIM_LIM) == 0) {
+ ch->state = XDMAD_STATE_DONE;
+ bExec = 1;
+ }
+ }
+
+ if (xdmaChannelIntStatus & XDMAC_CIS_LIS) {
+ ch->state = XDMAD_STATE_DONE;
+ bExec = 1;
+ }
+
+ if (xdmaChannelIntStatus & XDMAC_CIS_DIS) {
+ ch->state = XDMAD_STATE_DONE;
+ bExec = 1;
+ }
+
+ } else {
+ /* Block end interrupt for LLI dma mode */
+ if (XDMAC_GetChannelIsr(xdmac, channel) & XDMAC_CIS_BIS) {
+ atsam_finish_command(spid);
+ }
+ }
+
+ if (bExec == 1 && (channel == bus->dma_rx_channel)) {
+ bus->rx_transfer_done = true;
+ XDMAC_DisableGIt(spid->pXdmad->pXdmacs, bus->dma_rx_channel);
+ } else if (bExec == 1 && (channel == bus->dma_tx_channel)) {
+ bus->tx_transfer_done = true;
+ XDMAC_DisableGIt(spid->pXdmad->pXdmacs, bus->dma_tx_channel);
+ }
+
+ if (bus->rx_transfer_done && bus->tx_transfer_done) {
+ atsam_finish_command(spid);
+ sc = rtems_event_transient_send(bus->task_id);
+ assert(sc == RTEMS_SUCCESSFUL);
+ }
+ }
+ }
+}
+
+static void atsam_spi_interrupt(void *arg)
+{
+ sXdmad *Dma = (sXdmad *)arg;
+ atsam_interrupt_handler(Dma);
+}
+
+static uint8_t atsam_calculate_dlybcs(uint16_t delay_in_us)
+{
+ return (
+ (BOARD_MCK / delay_in_us) < 0xFF) ?
+ (BOARD_MCK / delay_in_us) : 0xFF;
+}
+
+static void atsam_set_phase_and_polarity(uint32_t mode, uint32_t *csr)
+{
+ uint32_t mode_mask = mode & SPI_MODE_3;
+
+ switch(mode_mask) {
+ case SPI_MODE_0:
+ *csr |= SPI_CSR_NCPHA;
+ break;
+ case SPI_MODE_1:
+ break;
+ case SPI_MODE_2:
+ *csr |= SPI_CSR_NCPHA;
+ *csr |= SPI_CSR_CPOL;
+ break;
+ case SPI_MODE_3:
+ *csr |= SPI_CSR_CPOL;
+ break;
+ }
+}
+
+static void atsam_configure_spi(atsam_spi_bus *bus)
+{
+ uint8_t delay_cs;
+ uint32_t csr = 0;
+
+ delay_cs = atsam_calculate_dlybcs(bus->base.delay_usecs);
+
+ SPID_Configure(
+ &bus->SpiDma,
+ bus->regs,
+ bus->board_id,
+ (SPI_MR_DLYBCS(delay_cs) |
+ SPI_MR_MSTR |
+ SPI_MR_MODFDIS |
+ SPI_PCS(bus->base.cs)),
+ &bus->Dma
+ );
+
+ csr =
+ SPI_DLYBCT(1000, BOARD_MCK) |
+ SPI_DLYBS(1000, BOARD_MCK) |
+ SPI_SCBR(bus->base.speed_hz, BOARD_MCK) |
+ SPI_CSR_BITS(bus->base.bits_per_word - 8);
+
+ atsam_set_phase_and_polarity(bus->base.mode, &csr);
+
+ SPI_ConfigureNPCS(bus->regs, bus->base.cs, csr);
+}
+
+static uint8_t atsam_configure_dma_channels(
+ Spid *pSpid,
+ uint32_t *dma_tx_channel,
+ uint32_t *dma_rx_channel
+)
+{
+ /* Driver initialize */
+ XDMAD_Initialize(pSpid->pXdmad, 0);
+
+ /* Allocate a DMA channel for SPI0/1 TX. */
+ *dma_tx_channel = XDMAD_AllocateChannel(
+ pSpid->pXdmad,
+ XDMAD_TRANSFER_MEMORY,
+ pSpid->spiId
+ );
+
+ if (*dma_tx_channel == XDMAD_ALLOC_FAILED) {
+ return SPID_ERROR;
+ }
+
+ /* Allocate a DMA channel for SPI0/1 RX. */
+ *dma_rx_channel = XDMAD_AllocateChannel(
+ pSpid->pXdmad,
+ pSpid->spiId,
+ XDMAD_TRANSFER_MEMORY
+ );
+
+ if (*dma_rx_channel == XDMAD_ALLOC_FAILED) {
+ return SPID_ERROR;
+ }
+
+ if (XDMAD_PrepareChannel(pSpid->pXdmad, *dma_rx_channel))
+ return SPID_ERROR;
+
+ if (XDMAD_PrepareChannel(pSpid->pXdmad, *dma_tx_channel))
+ return SPID_ERROR;
+
+ return 0;
+}
+
+static uint32_t atsam_set_dmac(atsam_spi_bus *bus)
+{
+ Spid *spid = &bus->SpiDma;
+
+ /* Initialize DMA controller using channel 0 for RX, 1 for TX. */
+ if (
+ atsam_configure_dma_channels(
+ spid,
+ &bus->dma_tx_channel,
+ &bus->dma_rx_channel
+ )
+ ) {
+ return SPID_ERROR_LOCK;
+ }
+
+ return 0;
+}
+
+static uint8_t atsam_configure_link_list(
+ Spi *pSpiHw,
+ void *pXdmad,
+ uint32_t dma_tx_channel,
+ uint32_t dma_rx_channel,
+ const spi_ioc_transfer *msg
+)
+{
+ sXdmadCfg xdmadRxCfg, xdmadTxCfg;
+ uint32_t xdmaCndc, xdmaInt;
+ uint32_t spiId;
+
+ if ((unsigned int)pSpiHw == (unsigned int)SPI0) spiId = ID_SPI0;
+
+ if ((unsigned int)pSpiHw == (unsigned int)SPI1) spiId = ID_SPI1;
+
+ /* Setup TX */
+
+ xdmadTxCfg.mbr_sa = (uint32_t)msg->tx_buf;
+
+ xdmadTxCfg.mbr_da = (uint32_t)&pSpiHw->SPI_TDR;
+
+ xdmadTxCfg.mbr_ubc =
+ XDMA_UBC_NVIEW_NDV0 |
+ XDMA_UBC_NDE_FETCH_DIS |
+ XDMA_UBC_NSEN_UPDATED |
+ msg->len;
+
+ xdmadTxCfg.mbr_cfg =
+ XDMAC_CC_TYPE_PER_TRAN |
+ XDMAC_CC_MBSIZE_SINGLE |
+ XDMAC_CC_DSYNC_MEM2PER |
+ XDMAC_CC_CSIZE_CHK_1 |
+ XDMAC_CC_DWIDTH_BYTE |
+ XDMAC_CC_SIF_AHB_IF1 |
+ XDMAC_CC_DIF_AHB_IF1 |
+ XDMAC_CC_SAM_INCREMENTED_AM |
+ XDMAC_CC_DAM_FIXED_AM |
+ XDMAC_CC_PERID(XDMAIF_Get_ChannelNumber(spiId, XDMAD_TRANSFER_TX));
+
+ xdmadTxCfg.mbr_bc = 0;
+ xdmadTxCfg.mbr_sus = 0;
+ xdmadTxCfg.mbr_dus = 0;
+
+ /* Setup RX Link List */
+
+ xdmadRxCfg.mbr_ubc =
+ XDMA_UBC_NVIEW_NDV0 |
+ XDMA_UBC_NDE_FETCH_DIS |
+ XDMA_UBC_NDEN_UPDATED |
+ msg->len;
+
+ xdmadRxCfg.mbr_da = (uint32_t)msg->rx_buf;
+
+ xdmadRxCfg.mbr_sa = (uint32_t)&pSpiHw->SPI_RDR;
+ xdmadRxCfg.mbr_cfg =
+ XDMAC_CC_TYPE_PER_TRAN |
+ XDMAC_CC_MBSIZE_SINGLE |
+ XDMAC_CC_DSYNC_PER2MEM |
+ XDMAC_CC_CSIZE_CHK_1 |
+ XDMAC_CC_DWIDTH_BYTE |
+ XDMAC_CC_SIF_AHB_IF1 |
+ XDMAC_CC_DIF_AHB_IF1 |
+ XDMAC_CC_SAM_FIXED_AM |
+ XDMAC_CC_DAM_INCREMENTED_AM |
+ XDMAC_CC_PERID(XDMAIF_Get_ChannelNumber(spiId, XDMAD_TRANSFER_RX));
+
+ xdmadRxCfg.mbr_bc = 0;
+ xdmadRxCfg.mbr_sus = 0;
+ xdmadRxCfg.mbr_dus = 0;
+
+ xdmaCndc = 0;
+
+ /* Put all interrupts on for non LLI list setup of DMA */
+ xdmaInt = (
+ XDMAC_CIE_BIE |
+ XDMAC_CIE_DIE |
+ XDMAC_CIE_FIE |
+ XDMAC_CIE_RBIE |
+ XDMAC_CIE_WBIE |
+ XDMAC_CIE_ROIE);
+
+ if (
+ XDMAD_ConfigureTransfer(
+ pXdmad,
+ dma_rx_channel,
+ &xdmadRxCfg,
+ xdmaCndc,
+ 0,
+ xdmaInt
+ )
+ ) {
+ return SPID_ERROR;
+ }
+
+ if (
+ XDMAD_ConfigureTransfer(
+ pXdmad,
+ dma_tx_channel,
+ &xdmadTxCfg,
+ xdmaCndc,
+ 0,
+ xdmaInt
+ )
+ ) {
+ return SPID_ERROR;
+ }
+
+ return 0;
+}
+
+static uint32_t atsam_send_command(
+ atsam_spi_bus *bus,
+ const spi_ioc_transfer *msg
+)
+{
+ Spid *spid = &bus->SpiDma;
+
+ Spi *pSpiHw = spid->pSpiHw;
+
+ if (
+ atsam_configure_link_list(
+ pSpiHw,
+ spid->pXdmad,
+ bus->dma_tx_channel,
+ bus->dma_rx_channel,
+ msg
+ )
+ ) {
+ return SPID_ERROR_LOCK;
+ }
+
+ /* Enable the SPI Peripheral */
+ PMC_EnablePeripheral(spid->spiId);
+
+ /* SPI chip select */
+ SPI_ChipSelect(pSpiHw, 1 << msg->cs);
+
+ /* Enables the SPI to transfer and receive data. */
+ SPI_Enable (pSpiHw);
+
+ /* Start DMA 0(RX) && 1(TX) */
+ if (XDMAD_StartTransfer(spid->pXdmad, bus->dma_rx_channel)) {
+ return SPID_ERROR_LOCK;
+ }
+
+ if (XDMAD_StartTransfer(spid->pXdmad, bus->dma_tx_channel)) {
+ return SPID_ERROR_LOCK;
+ }
+
+ return 0;
+}
+
+static int atsam_message_checks(atsam_spi_bus *bus, const spi_ioc_transfer *msg)
+{
+ int status = 0;
+
+ if (msg->bits_per_word < 8 || msg->bits_per_word > 16) {
+ status = -EINVAL;
+ } else if (msg->mode > 3) {
+ status = -EINVAL;
+ } else if (msg->speed_hz > bus->base.max_speed_hz) {
+ status = -EINVAL;
+ } else {
+ if (msg->mode != bus->base.mode ||
+ msg->speed_hz != bus->base.speed_hz ||
+ msg->bits_per_word != bus->base.bits_per_word ||
+ msg->cs != bus->base.cs ||
+ msg->delay_usecs != bus->base.delay_usecs
+ ) {
+ bus->base.mode = msg->mode;
+ bus->base.speed_hz = msg->speed_hz;
+ bus->base.bits_per_word = msg->bits_per_word;
+ bus->base.cs = msg->cs;
+ bus->base.delay_usecs = msg->delay_usecs;
+ atsam_configure_spi(bus);
+ status = 1;
+ }
+ }
+
+ return status;
+}
+
+static int atsam_spi_setup_transfer(atsam_spi_bus *bus)
+{
+ const spi_ioc_transfer *msgs = bus->msgs;
+ uint32_t msg_todo = bus->msg_todo;
+ uint32_t i;
+ uint32_t rv_command;
+ int rv = 0;
+ rtems_status_code sc;
+
+ for (i=0; i<msg_todo; i++) {
+ rv = atsam_message_checks(bus, &msgs[i]);
+ if (rv < 0) {
+ break;
+ } else if (rv == 1) {
+ atsam_configure_spi(bus);
+ rv = 0;
+ }
+ rv_command = atsam_send_command(bus, &msgs[i]);
+ if (rv_command != 0) {
+ rv = -1;
+ break;
+ }
+ sc = rtems_event_transient_receive(RTEMS_WAIT, 10000);
+ if (sc != RTEMS_SUCCESSFUL) {
+ rtems_event_transient_clear();
+ rv = -ETIMEDOUT;
+ break;
+ }
+ bus->rx_transfer_done = false;
+ bus->tx_transfer_done = false;
+ if (msgs[i].cs_change > 0) {
+ SPI_ReleaseCS(bus->SpiDma.pSpiHw);
+ }
+ }
+ return rv;
+}
+
+static int atsam_spi_transfer(
+ spi_bus *base,
+ const spi_ioc_transfer *msgs,
+ uint32_t msg_count
+)
+{
+ int rv;
+ atsam_spi_bus *bus = (atsam_spi_bus *)base;
+
+ if (msg_count == 0) {
+ return 0;
+ }
+
+ bus->msgs = &msgs[0];
+ bus->msg_todo = msg_count;
+ bus->task_id = rtems_task_self();
+
+ rv = atsam_spi_setup_transfer(bus);
+
+ return rv;
+}
+
+
+static void atsam_spi_destroy(spi_bus *base)
+{
+ atsam_spi_bus *bus = (atsam_spi_bus *)base;
+ rtems_status_code sc;
+
+ /* Free XDMAD Channels */
+ XDMAD_FreeChannel(bus->SpiDma.pXdmad, 0);
+ XDMAD_FreeChannel(bus->SpiDma.pXdmad, 1);
+
+ sc = rtems_interrupt_handler_remove(bus->irq, atsam_spi_interrupt, bus);
+ assert(sc == RTEMS_SUCCESSFUL);
+
+ spi_bus_destroy_and_free(&bus->base);
+}
+
+static int atsam_spi_setup(spi_bus *base)
+{
+ atsam_spi_bus *bus = (atsam_spi_bus *)base;
+
+ if (
+ bus->base.speed_hz > MAX_SPI_FREQUENCY ||
+ bus->base.bits_per_word < 8 ||
+ bus->base.bits_per_word > 16
+ ) {
+ return -EINVAL;
+ }
+ atsam_configure_spi(bus);
+ return 0;
+}
+
+static void atsam_spi_init(
+ atsam_spi_bus *bus,
+ size_t pin_amount,
+ const Pin *pins
+)
+{
+ PIO_Configure(pins, pin_amount);
+ ENABLE_PERIPHERAL(bus->board_id);
+ XDMAD_Initialize(&bus->Dma, 0);
+ bus->base.mode = 0;
+ bus->base.bits_per_word = 8;
+ bus->base.speed_hz = bus->base.max_speed_hz;
+ bus->base.delay_usecs = 1;
+ bus->base.cs = 1;
+
+ atsam_configure_spi(bus);
+
+ atsam_set_dmac(bus);
+}
+
+int spi_bus_register_atsam(
+ const char *bus_path,
+ Spi *register_base,
+ rtems_vector_number irq,
+ size_t pin_amount,
+ const Pin *pins
+)
+{
+ atsam_spi_bus *bus;
+ rtems_status_code sc;
+ uint32_t board_id = (uint32_t) irq;
+
+ bus = (atsam_spi_bus *) spi_bus_alloc_and_init(sizeof(*bus));
+ if (bus == NULL) {
+ return -1;
+ }
+
+ bus->regs = register_base;
+ bus->board_id = board_id;
+ bus->irq = ID_XDMAC;
+
+ atsam_spi_init(bus, pin_amount, pins);
+
+ sc = rtems_interrupt_handler_install(
+ bus->irq,
+ "SPI",
+ RTEMS_INTERRUPT_UNIQUE,
+ atsam_spi_interrupt,
+ bus
+ );
+ if (sc != RTEMS_SUCCESSFUL) {
+ (*bus->base.destroy)(&bus->base);
+
+ rtems_set_errno_and_return_minus_one(EIO);
+ }
+
+ bus->base.transfer = atsam_spi_transfer;
+ bus->base.destroy = atsam_spi_destroy;
+ bus->base.setup = atsam_spi_setup;
+ bus->base.max_speed_hz = MAX_SPI_FREQUENCY;
+
+ return spi_bus_register(&bus->base, bus_path);
+}