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