406e9d8742a6f299db0d7fec95a6f87f4107244f
[bertos.git] / bertos / cpu / arm / drv / spi_dma_at91.c
1 /**
2  * \file
3  * <!--
4  * This file is part of BeRTOS.
5  *
6  * Bertos is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  * As a special exception, you may use this file as part of a free software
21  * library without restriction.  Specifically, if other files instantiate
22  * templates or use macros or inline functions from this file, or you compile
23  * this file and link it with other files to produce an executable, this
24  * file does not by itself cause the resulting executable to be covered by
25  * the GNU General Public License.  This exception does not however
26  * invalidate any other reasons why the executable file might be covered by
27  * the GNU General Public License.
28  *
29  * Copyright 2008 Develer S.r.l. (http://www.develer.com/)
30  *
31  * -->
32  *
33  * \brief SPI driver with DMA.
34  *
35  * \version $Id$
36  * \author Francesco Sacchi <batt@develer.com>
37  * \author Luca Ottaviano <lottaviano@develer.com>
38  */
39
40 #include "cfg/cfg_spi_dma.h"
41
42 #include "spi_dma_at91.h"
43 #include "hw/hw_spi_dma.h"
44
45 #include <kern/kfile.h>
46 #include <struct/fifobuf.h>
47 #include <struct/kfile_fifo.h>
48 #include <drv/timer.h>
49
50 #include <cpu/attr.h>
51 #include <cpu/power.h>
52
53 #include <string.h> /* memset */
54
55 static uint8_t tx_fifo_buffer[CONFIG_SPI_DMA_TXBUFSIZE];
56 static FIFOBuffer tx_fifo;
57 static KFileFifo kfifo;
58
59
60 INLINE void spi_dma_startTx(void)
61 {
62         if (fifo_isempty(&tx_fifo))
63                 return;
64
65         if (SPI0_SR & BV(SPI_TXBUFE))
66         {
67                 SPI0_PTCR = BV(PDC_TXTDIS);
68                 SPI0_TPR = (reg32_t)tx_fifo.head;
69                 if (tx_fifo.head < tx_fifo.tail)
70                         SPI0_TCR = tx_fifo.tail - tx_fifo.head;
71                 else
72                         SPI0_TCR = tx_fifo.end - tx_fifo.head + 1;
73
74                 SPI0_PTCR = BV(PDC_TXTEN);
75         }
76 }
77
78 static void spi0_dma_write_irq_handler(void) __attribute__ ((interrupt));
79 static void spi0_dma_write_irq_handler(void)
80 {
81         SPI_DMA_STROBE_ON();
82         /* Pop sent chars from FIFO */
83         tx_fifo.head = (uint8_t *)SPI0_TPR;
84         if (tx_fifo.head > tx_fifo.end)
85                 tx_fifo.head = tx_fifo.begin;
86
87         spi_dma_startTx();
88
89         AIC_EOICR = 0;
90         SPI_DMA_STROBE_OFF();
91 }
92
93
94 void spi_dma_setclock(uint32_t rate)
95 {
96         SPI0_CSR0 &= ~SPI_SCBR;
97
98         ASSERT((uint8_t)DIV_ROUND(CPU_FREQ, rate));
99         SPI0_CSR0 |= DIV_ROUND(CPU_FREQ, rate) << SPI_SCBR_SHIFT;
100 }
101
102 static size_t spi_dma_write(UNUSED_ARG(struct KFile *, fd), const void *_buf, size_t size)
103 {
104         size_t count, total_wr = 0;
105         const uint8_t *buf = (const uint8_t *) _buf;
106
107         // copy buffer to internal fifo
108         while (size)
109         {
110                 #if CONFIG_SPI_DMA_TX_TIMEOUT != -1
111                         ticks_t start = timer_clock();
112                         while (fifo_isfull(&tx_fifo) && (timer_clock() - start < ms_to_ticks(CONFIG_SPI_DMA_TX_TIMEOUT)))
113                                 cpu_relax();
114
115                         if (fifo_isfull(&tx_fifo))
116                                 break;
117                 #else
118                         while (fifo_isfull(&tx_fifo))
119                                 cpu_relax();
120                 #endif /* CONFIG_SPI_DMA_TX_TIMEOUT */
121
122                 // FIXME: improve copy performance
123                 count = kfile_write(&kfifo.fd, buf, size);
124                 size -= count;
125                 buf += count;
126                 total_wr += count;
127                 spi_dma_startTx();
128         }
129
130         return total_wr;
131 }
132
133 static int spi_dma_flush(UNUSED_ARG(struct KFile *, fd))
134 {
135         /* Wait FIFO flush */
136         while (!fifo_isempty(&tx_fifo))
137                 cpu_relax();
138
139         /* Wait until last bit has been shifted out */
140         while (!(SPI0_SR & BV(SPI_TXEMPTY)))
141                 cpu_relax();
142
143         return 0;
144 }
145
146 static void spi0_dma_read_irq_handler(void) __attribute__ ((interrupt));
147 static void spi0_dma_read_irq_handler(void)
148 {
149         /* do nothing */
150         AIC_EOICR = 0;
151 }
152
153 /*
154  * Dummy buffer used to transmit 0xff chars while receiving data.
155  * This buffer is completetly constant and the compiler should allocate it
156  * in flash memory.
157  */
158 static const uint8_t tx_dummy_buf[CONFIG_SPI_DMA_MAX_RX] = { [0 ... (CONFIG_SPI_DMA_MAX_RX - 1)] = 0xFF };
159
160 static size_t spi_dma_read(struct KFile *fd, void *_buf, size_t size)
161 {
162         size_t count, total_rx = 0;
163         uint8_t *buf = (uint8_t *)_buf;
164
165         spi_dma_flush(fd);
166
167         /* Dummy irq handler that do nothing */
168         AIC_SVR(SPI0_ID) = spi0_dma_read_irq_handler;
169
170         while (size)
171         {
172                 count = MIN(size, (size_t)CONFIG_SPI_DMA_MAX_RX);
173
174                 SPI0_PTCR = BV(PDC_TXTDIS) | BV(PDC_RXTDIS);
175
176                 SPI0_RPR = (reg32_t)buf;
177                 SPI0_RCR = count;
178                 SPI0_TPR = (reg32_t)tx_dummy_buf;
179                 SPI0_TCR = count;
180
181                 /* Avoid reading the previous sent char */
182                 *buf = SPI0_RDR;
183
184                 /* Start transfer */
185                 SPI0_PTCR = BV(PDC_RXTEN) | BV(PDC_TXTEN);
186
187                 /* wait for transfer to finish */
188                 while (!(SPI0_SR & BV(SPI_ENDRX)))
189                         cpu_relax();
190
191                 size -= count;
192                 total_rx += count;
193                 buf += count;
194         }
195         SPI0_PTCR = BV(PDC_RXTDIS) | BV(PDC_TXTDIS);
196
197         /* set write irq handler back in place */
198         AIC_SVR(SPI0_ID) = spi0_dma_write_irq_handler;
199
200         return total_rx;
201 }
202
203 #define SPI_DMA_IRQ_PRIORITY 4
204
205 void spi_dma_init(SpiDmaAt91 *spi)
206 {
207         /* Disable PIO on SPI pins */
208         PIOA_PDR = BV(SPI0_SPCK) | BV(SPI0_MOSI) | BV(SPI0_MISO);
209
210         /* Reset device */
211         SPI0_CR = BV(SPI_SWRST);
212
213         /*
214          * Set SPI to master mode, fixed peripheral select, chip select directly connected to a peripheral device,
215          * SPI clock set to MCK, mode fault detection disabled, loopback disable, NPCS0 active, Delay between CS = 0
216          */
217         SPI0_MR = BV(SPI_MSTR) | BV(SPI_MODFDIS);
218
219         /*
220          * Set SPI mode.
221          * At reset clock division factor is set to 0, that is
222          * *forbidden*. Set SPI clock to minimum to keep it valid.
223          */
224         SPI0_CSR0 = BV(SPI_NCPHA) | (255 << SPI_SCBR_SHIFT);
225
226         /* Disable all irqs */
227         SPI0_IDR = 0xFFFFFFFF;
228         /* Set the vector. */
229         AIC_SVR(SPI0_ID) = spi0_dma_write_irq_handler;
230         /* Initialize to edge triggered with defined priority. */
231         AIC_SMR(SPI0_ID) = AIC_SRCTYPE_INT_EDGE_TRIGGERED | SPI_DMA_IRQ_PRIORITY;
232         /* Enable the USART IRQ */
233         AIC_IECR = BV(SPI0_ID);
234         PMC_PCER = BV(SPI0_ID);
235
236         /* Enable interrupt on tx buffer empty */
237         SPI0_IER = BV(SPI_ENDTX);
238
239         /* Enable SPI */
240         SPI0_CR = BV(SPI_SPIEN);
241
242         DB(spi->fd._type = KFT_SPIDMAAT91);
243         spi->fd.write = spi_dma_write;
244         spi->fd.read = spi_dma_read;
245         spi->fd.flush = spi_dma_flush;
246
247         fifo_init(&tx_fifo, tx_fifo_buffer, sizeof(tx_fifo_buffer));
248         kfilefifo_init(&kfifo, &tx_fifo);
249
250         SPI_DMA_STROBE_INIT();
251 }