spi_dma_at91.c

Go to the documentation of this file.
00001 
00040 #include "cfg/cfg_spi_dma.h"
00041 
00042 #include "spi_dma_at91.h"
00043 #include "hw/hw_spi_dma.h"
00044 
00045 #include <kern/kfile.h>
00046 #include <struct/fifobuf.h>
00047 #include <struct/kfile_fifo.h>
00048 #include <drv/timer.h>
00049 
00050 #include <cpu/attr.h>
00051 #include <cpu/power.h>
00052 
00053 #include <string.h> /* memset */
00054 
00055 static uint8_t tx_fifo_buffer[CONFIG_SPI_DMA_TXBUFSIZE];
00056 static FIFOBuffer tx_fifo;
00057 static KFileFifo kfifo;
00058 
00059 
00060 INLINE void spi_dma_startTx(void)
00061 {
00062     if (fifo_isempty(&tx_fifo))
00063         return;
00064 
00065     if (SPI0_SR & BV(SPI_TXBUFE))
00066     {
00067         SPI0_PTCR = BV(PDC_TXTDIS);
00068         SPI0_TPR = (reg32_t)tx_fifo.head;
00069         if (tx_fifo.head < tx_fifo.tail)
00070             SPI0_TCR = tx_fifo.tail - tx_fifo.head;
00071         else
00072             SPI0_TCR = tx_fifo.end - tx_fifo.head + 1;
00073 
00074         SPI0_PTCR = BV(PDC_TXTEN);
00075     }
00076 }
00077 
00078 static DECLARE_ISR(spi0_dma_write_irq_handler)
00079 {
00080     SPI_DMA_STROBE_ON();
00081     /* Pop sent chars from FIFO */
00082     tx_fifo.head = (uint8_t *)SPI0_TPR;
00083     if (tx_fifo.head > tx_fifo.end)
00084         tx_fifo.head = tx_fifo.begin;
00085 
00086     spi_dma_startTx();
00087 
00088     AIC_EOICR = 0;
00089     SPI_DMA_STROBE_OFF();
00090 }
00091 
00092 
00093 void spi_dma_setclock(uint32_t rate)
00094 {
00095     SPI0_CSR0 &= ~SPI_SCBR;
00096 
00097     ASSERT((uint8_t)DIV_ROUND(CPU_FREQ, rate));
00098     SPI0_CSR0 |= DIV_ROUND(CPU_FREQ, rate) << SPI_SCBR_SHIFT;
00099 }
00100 
00101 static size_t spi_dma_write(UNUSED_ARG(struct KFile *, fd), const void *_buf, size_t size)
00102 {
00103     size_t count, total_wr = 0;
00104     const uint8_t *buf = (const uint8_t *) _buf;
00105 
00106     // copy buffer to internal fifo
00107     while (size)
00108     {
00109         #if CONFIG_SPI_DMA_TX_TIMEOUT != -1
00110             ticks_t start = timer_clock();
00111             while (fifo_isfull(&tx_fifo) && (timer_clock() - start < ms_to_ticks(CONFIG_SPI_DMA_TX_TIMEOUT)))
00112                 cpu_relax();
00113 
00114             if (fifo_isfull(&tx_fifo))
00115                 break;
00116         #else
00117             while (fifo_isfull(&tx_fifo))
00118                 cpu_relax();
00119         #endif /* CONFIG_SPI_DMA_TX_TIMEOUT */
00120 
00121         // FIXME: improve copy performance
00122         count = kfile_write(&kfifo.fd, buf, size);
00123         size -= count;
00124         buf += count;
00125         total_wr += count;
00126         spi_dma_startTx();
00127     }
00128 
00129     return total_wr;
00130 }
00131 
00132 static int spi_dma_flush(UNUSED_ARG(struct KFile *, fd))
00133 {
00134     /* Wait FIFO flush */
00135     while (!fifo_isempty(&tx_fifo))
00136         cpu_relax();
00137 
00138     /* Wait until last bit has been shifted out */
00139     while (!(SPI0_SR & BV(SPI_TXEMPTY)))
00140         cpu_relax();
00141 
00142     return 0;
00143 }
00144 
00145 static DECLARE_ISR(spi0_dma_read_irq_handler)
00146 {
00147     /* do nothing */
00148     AIC_EOICR = 0;
00149 }
00150 
00151 /*
00152  * Dummy buffer used to transmit 0xff chars while receiving data.
00153  * This buffer is completetly constant and the compiler should allocate it
00154  * in flash memory.
00155  */
00156 static const uint8_t tx_dummy_buf[CONFIG_SPI_DMA_MAX_RX] = { [0 ... (CONFIG_SPI_DMA_MAX_RX - 1)] = 0xFF };
00157 
00158 static size_t spi_dma_read(struct KFile *fd, void *_buf, size_t size)
00159 {
00160     size_t count, total_rx = 0;
00161     uint8_t *buf = (uint8_t *)_buf;
00162 
00163     spi_dma_flush(fd);
00164 
00165     /* Dummy irq handler that do nothing */
00166     AIC_SVR(SPI0_ID) = spi0_dma_read_irq_handler;
00167 
00168     while (size)
00169     {
00170         count = MIN(size, (size_t)CONFIG_SPI_DMA_MAX_RX);
00171 
00172         SPI0_PTCR = BV(PDC_TXTDIS) | BV(PDC_RXTDIS);
00173 
00174         SPI0_RPR = (reg32_t)buf;
00175         SPI0_RCR = count;
00176         SPI0_TPR = (reg32_t)tx_dummy_buf;
00177         SPI0_TCR = count;
00178 
00179         /* Avoid reading the previous sent char */
00180         *buf = SPI0_RDR;
00181 
00182         /* Start transfer */
00183         SPI0_PTCR = BV(PDC_RXTEN) | BV(PDC_TXTEN);
00184 
00185         /* wait for transfer to finish */
00186         while (!(SPI0_SR & BV(SPI_ENDRX)))
00187             cpu_relax();
00188 
00189         size -= count;
00190         total_rx += count;
00191         buf += count;
00192     }
00193     SPI0_PTCR = BV(PDC_RXTDIS) | BV(PDC_TXTDIS);
00194 
00195     /* set write irq handler back in place */
00196     AIC_SVR(SPI0_ID) = spi0_dma_write_irq_handler;
00197 
00198     return total_rx;
00199 }
00200 
00201 #define SPI_DMA_IRQ_PRIORITY 4
00202 
00203 void spi_dma_init(SpiDmaAt91 *spi)
00204 {
00205     /* Disable PIO on SPI pins */
00206     PIOA_PDR = BV(SPI0_SPCK) | BV(SPI0_MOSI) | BV(SPI0_MISO);
00207 
00208     /* Reset device */
00209     SPI0_CR = BV(SPI_SWRST);
00210 
00211     /*
00212      * Set SPI to master mode, fixed peripheral select, chip select directly connected to a peripheral device,
00213      * SPI clock set to MCK, mode fault detection disabled, loopback disable, NPCS0 active, Delay between CS = 0
00214      */
00215     SPI0_MR = BV(SPI_MSTR) | BV(SPI_MODFDIS);
00216 
00217     /*
00218      * Set SPI mode.
00219      * At reset clock division factor is set to 0, that is
00220      * *forbidden*. Set SPI clock to minimum to keep it valid.
00221      */
00222     SPI0_CSR0 = BV(SPI_NCPHA) | (255 << SPI_SCBR_SHIFT);
00223 
00224     /* Disable all irqs */
00225     SPI0_IDR = 0xFFFFFFFF;
00226     /* Set the vector. */
00227     AIC_SVR(SPI0_ID) = spi0_dma_write_irq_handler;
00228     /* Initialize to edge triggered with defined priority. */
00229     AIC_SMR(SPI0_ID) = AIC_SRCTYPE_INT_EDGE_TRIGGERED | SPI_DMA_IRQ_PRIORITY;
00230     /* Enable the USART IRQ */
00231     AIC_IECR = BV(SPI0_ID);
00232     PMC_PCER = BV(SPI0_ID);
00233 
00234     /* Enable interrupt on tx buffer empty */
00235     SPI0_IER = BV(SPI_ENDTX);
00236 
00237     /* Enable SPI */
00238     SPI0_CR = BV(SPI_SPIEN);
00239 
00240     DB(spi->fd._type = KFT_SPIDMAAT91);
00241     spi->fd.write = spi_dma_write;
00242     spi->fd.read = spi_dma_read;
00243     spi->fd.flush = spi_dma_flush;
00244 
00245     fifo_init(&tx_fifo, tx_fifo_buffer, sizeof(tx_fifo_buffer));
00246     kfilefifo_init(&kfifo, &tx_fifo);
00247 
00248     SPI_DMA_STROBE_INIT();
00249 }