Remove \version svn tag.
[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  * \author Francesco Sacchi <batt@develer.com>
36  * \author Luca Ottaviano <lottaviano@develer.com>
37  */
38
39 #include "cfg/cfg_spi_dma.h"
40
41 #include "spi_dma_at91.h"
42 #include "hw/hw_spi_dma.h"
43
44 #include <io/kfile.h>
45 #include <struct/fifobuf.h>
46 #include <struct/kfile_fifo.h>
47 #include <drv/timer.h>
48
49 #include <cpu/attr.h>
50 #include <cpu/power.h>
51
52 #include <string.h> /* memset */
53
54 static uint8_t tx_fifo_buffer[CONFIG_SPI_DMA_TXBUFSIZE];
55 static FIFOBuffer tx_fifo;
56 static KFileFifo kfifo;
57
58
59 INLINE void spi_dma_startTx(void)
60 {
61         if (fifo_isempty(&tx_fifo))
62                 return;
63
64         if (SPI0_SR & BV(SPI_TXBUFE))
65         {
66                 SPI0_PTCR = BV(PDC_TXTDIS);
67                 SPI0_TPR = (reg32_t)tx_fifo.head;
68                 if (tx_fifo.head < tx_fifo.tail)
69                         SPI0_TCR = tx_fifo.tail - tx_fifo.head;
70                 else
71                         SPI0_TCR = tx_fifo.end - tx_fifo.head + 1;
72
73                 SPI0_PTCR = BV(PDC_TXTEN);
74         }
75 }
76
77 static DECLARE_ISR(spi0_dma_write_irq_handler)
78 {
79         SPI_DMA_STROBE_ON();
80         /* Pop sent chars from FIFO */
81         tx_fifo.head = (uint8_t *)SPI0_TPR;
82         if (tx_fifo.head > tx_fifo.end)
83                 tx_fifo.head = tx_fifo.begin;
84
85         spi_dma_startTx();
86
87         AIC_EOICR = 0;
88         SPI_DMA_STROBE_OFF();
89 }
90
91
92 void spi_dma_setclock(uint32_t rate)
93 {
94         SPI0_CSR0 &= ~SPI_SCBR;
95
96         ASSERT((uint8_t)DIV_ROUND(CPU_FREQ, rate));
97         SPI0_CSR0 |= DIV_ROUND(CPU_FREQ, rate) << SPI_SCBR_SHIFT;
98 }
99
100 static size_t spi_dma_write(UNUSED_ARG(struct KFile *, fd), const void *_buf, size_t size)
101 {
102         size_t count, total_wr = 0;
103         const uint8_t *buf = (const uint8_t *) _buf;
104
105         // copy buffer to internal fifo
106         while (size)
107         {
108                 #if CONFIG_SPI_DMA_TX_TIMEOUT != -1
109                         ticks_t start = timer_clock();
110                         while (fifo_isfull(&tx_fifo) && (timer_clock() - start < ms_to_ticks(CONFIG_SPI_DMA_TX_TIMEOUT)))
111                                 cpu_relax();
112
113                         if (fifo_isfull(&tx_fifo))
114                                 break;
115                 #else
116                         while (fifo_isfull(&tx_fifo))
117                                 cpu_relax();
118                 #endif /* CONFIG_SPI_DMA_TX_TIMEOUT */
119
120                 // FIXME: improve copy performance
121                 count = kfile_write(&kfifo.fd, buf, size);
122                 size -= count;
123                 buf += count;
124                 total_wr += count;
125                 spi_dma_startTx();
126         }
127
128         return total_wr;
129 }
130
131 static int spi_dma_flush(UNUSED_ARG(struct KFile *, fd))
132 {
133         /* Wait FIFO flush */
134         while (!fifo_isempty(&tx_fifo))
135                 cpu_relax();
136
137         /* Wait until last bit has been shifted out */
138         while (!(SPI0_SR & BV(SPI_TXEMPTY)))
139                 cpu_relax();
140
141         return 0;
142 }
143
144 static DECLARE_ISR(spi0_dma_read_irq_handler)
145 {
146         /* do nothing */
147         AIC_EOICR = 0;
148 }
149
150 /*
151  * Dummy buffer used to transmit 0xff chars while receiving data.
152  * This buffer is completetly constant and the compiler should allocate it
153  * in flash memory.
154  */
155 static const uint8_t tx_dummy_buf[CONFIG_SPI_DMA_MAX_RX] = { [0 ... (CONFIG_SPI_DMA_MAX_RX - 1)] = 0xFF };
156
157 static size_t spi_dma_read(struct KFile *fd, void *_buf, size_t size)
158 {
159         size_t count, total_rx = 0;
160         uint8_t *buf = (uint8_t *)_buf;
161
162         spi_dma_flush(fd);
163
164         /* Dummy irq handler that do nothing */
165         AIC_SVR(SPI0_ID) = spi0_dma_read_irq_handler;
166
167         while (size)
168         {
169                 count = MIN(size, (size_t)CONFIG_SPI_DMA_MAX_RX);
170
171                 SPI0_PTCR = BV(PDC_TXTDIS) | BV(PDC_RXTDIS);
172
173                 SPI0_RPR = (reg32_t)buf;
174                 SPI0_RCR = count;
175                 SPI0_TPR = (reg32_t)tx_dummy_buf;
176                 SPI0_TCR = count;
177
178                 /* Avoid reading the previous sent char */
179                 *buf = SPI0_RDR;
180
181                 /* Start transfer */
182                 SPI0_PTCR = BV(PDC_RXTEN) | BV(PDC_TXTEN);
183
184                 /* wait for transfer to finish */
185                 while (!(SPI0_SR & BV(SPI_ENDRX)))
186                         cpu_relax();
187
188                 size -= count;
189                 total_rx += count;
190                 buf += count;
191         }
192         SPI0_PTCR = BV(PDC_RXTDIS) | BV(PDC_TXTDIS);
193
194         /* set write irq handler back in place */
195         AIC_SVR(SPI0_ID) = spi0_dma_write_irq_handler;
196
197         return total_rx;
198 }
199
200 #define SPI_DMA_IRQ_PRIORITY 4
201
202 void spi_dma_init(SpiDmaAt91 *spi)
203 {
204         /* Disable PIO on SPI pins */
205         PIOA_PDR = BV(SPI0_SPCK) | BV(SPI0_MOSI) | BV(SPI0_MISO);
206
207         /* Reset device */
208         SPI0_CR = BV(SPI_SWRST);
209
210         /*
211          * Set SPI to master mode, fixed peripheral select, chip select directly connected to a peripheral device,
212          * SPI clock set to MCK, mode fault detection disabled, loopback disable, NPCS0 active, Delay between CS = 0
213          */
214         SPI0_MR = BV(SPI_MSTR) | BV(SPI_MODFDIS);
215
216         /*
217          * Set SPI mode.
218          * At reset clock division factor is set to 0, that is
219          * *forbidden*. Set SPI clock to minimum to keep it valid.
220          */
221         SPI0_CSR0 = BV(SPI_NCPHA) | (255 << SPI_SCBR_SHIFT);
222
223         /* Disable all irqs */
224         SPI0_IDR = 0xFFFFFFFF;
225         /* Set the vector. */
226         AIC_SVR(SPI0_ID) = spi0_dma_write_irq_handler;
227         /* Initialize to edge triggered with defined priority. */
228         AIC_SMR(SPI0_ID) = AIC_SRCTYPE_INT_EDGE_TRIGGERED | SPI_DMA_IRQ_PRIORITY;
229         /* Enable the USART IRQ */
230         AIC_IECR = BV(SPI0_ID);
231         PMC_PCER = BV(SPI0_ID);
232
233         /* Enable interrupt on tx buffer empty */
234         SPI0_IER = BV(SPI_ENDTX);
235
236         /* Enable SPI */
237         SPI0_CR = BV(SPI_SPIEN);
238
239         DB(spi->fd._type = KFT_SPIDMAAT91);
240         spi->fd.write = spi_dma_write;
241         spi->fd.read = spi_dma_read;
242         spi->fd.flush = spi_dma_flush;
243
244         fifo_init(&tx_fifo, tx_fifo_buffer, sizeof(tx_fifo_buffer));
245         kfilefifo_init(&kfifo, &tx_fifo);
246
247         SPI_DMA_STROBE_INIT();
248 }