altos: Add LPC spi driver
[fw/altos] / src / lpc / ao_spi_lpc.c
1 /*
2  * Copyright © 2013 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 #include <ao.h>
19
20 static uint8_t          ao_spi_mutex[LPC_NUM_SPI];
21
22 static struct lpc_ssp * const ao_lpc_ssp[LPC_NUM_SPI] = { &lpc_ssp0, &lpc_ssp1 };
23
24 static uint8_t  spi_dev_null;
25
26 #define spi_loop(len, put, get) do {                                    \
27                 while (len--) {                                         \
28                         /* Wait for space in the fifo */                \
29                         while ((lpc_ssp->sr & (1 << LPC_SSP_SR_TNF)) == 0) \
30                                 ;                                       \
31                         /* send a byte */                               \
32                         lpc_ssp->dr = put;                              \
33                                                                         \
34                         /* recv a byte */                               \
35                         get lpc_ssp->dr;                                \
36                 }                                                       \
37                                                                         \
38                 /* Wait for the fifo to drain */                        \
39                 while ((lpc_ssp->sr & (1 << LPC_SSP_SR_BSY)))           \
40                         ;                                               \
41         } while (0);
42
43 void
44 ao_spi_send(void *block, uint16_t len, uint8_t id)
45 {
46         uint8_t *b = block;
47         struct lpc_ssp *lpc_ssp = ao_lpc_ssp[id];
48
49         spi_loop(len, *b++, (void));
50 }
51
52 void
53 ao_spi_send_fixed(uint8_t value, uint16_t len, uint8_t id)
54 {
55         struct lpc_ssp *lpc_ssp = ao_lpc_ssp[id];
56
57         spi_loop(len, value, (void));
58 }
59
60 void
61 ao_spi_recv(void *block, uint16_t len, uint8_t id)
62 {
63         uint8_t *b = block;
64         struct lpc_ssp *lpc_ssp = ao_lpc_ssp[id];
65
66         spi_loop(len, 0xff, *b++ =);
67 }
68
69 void
70 ao_spi_duplex(void *out, void *in, uint16_t len, uint8_t id)
71 {
72         uint8_t *o = out;
73         uint8_t *i = in;
74         struct lpc_ssp *lpc_ssp = ao_lpc_ssp[id];
75
76         spi_loop(len, *o++, *i++ =);
77 }
78
79 void
80 ao_spi_get(uint8_t id, uint32_t speed)
81 {
82         struct lpc_ssp  *lpc_ssp = ao_lpc_ssp[id];
83
84         ao_mutex_get(&ao_spi_mutex[id]);
85         
86         /* Set the clock prescale */
87         lpc_ssp->cpsr = speed;
88
89         /* Enable the device */
90         lpc_ssp->cr1 = ((0 << LPC_SSP_CR1_LBM) |
91                         (1 << LPC_SSP_CR1_SSE) |
92                         (LPC_SSP_CR1_MS_MASTER << LPC_SSP_CR1_MS) |
93                         (0 << LPC_SSP_CR1_SOD));
94 }
95
96 void
97 ao_spi_put(uint8_t id)
98 {
99         struct lpc_ssp  *lpc_ssp = ao_lpc_ssp[id];
100
101         /* Disable the device */
102         lpc_ssp->cr1 = ((0 << LPC_SSP_CR1_LBM) |
103                         (0 << LPC_SSP_CR1_SSE) |
104                         (LPC_SSP_CR1_MS_MASTER << LPC_SSP_CR1_MS) |
105                         (0 << LPC_SSP_CR1_SOD));
106         ao_mutex_put(&ao_spi_mutex[id]);
107 }
108
109 static void
110 ao_spi_channel_init(uint8_t id)
111 {
112         struct lpc_ssp  *lpc_ssp = ao_lpc_ssp[id];
113         uint8_t d;
114
115         lpc_ssp->cr0 = ((LPC_SSP_CR0_DSS_8 << LPC_SSP_CR0_DSS) |
116                         (LPC_SSP_CR0_FRF_SPI << LPC_SSP_CR0_FRF) |
117                         (0 << LPC_SSP_CR0_CPOL) |
118                         (0 << LPC_SSP_CR0_CPHA) |
119                         (0 << LPC_SSP_CR0_SCR));
120         /* Drain the receive fifo */
121         for (d = 0; d < LPC_SSP_FIFOSIZE; d++)
122                 (void) lpc_ssp->dr;
123 }
124
125 void
126 ao_spi_init(void)
127 {
128 #if HAS_SPI_0
129         /* Configure pins */
130         lpc_ioconf.pio0_6 = ao_lpc_alternate(LPC_IOCONF_FUNC_SCK0);
131         lpc_ioconf.pio0_8 = ao_lpc_alternate(LPC_IOCONF_FUNC_MISO0);
132         lpc_ioconf.pio0_9 = ao_lpc_alternate(LPC_IOCONF_FUNC_MOSI0);
133
134         /* Enable the device */
135         lpc_scb.sysahbclkctrl |= (1 << LPC_SCB_SYSAHBCLKCTRL_SSP0);
136
137         /* Turn on the clock */
138         lpc_scb.ssp0clkdiv = 1;
139
140         /* Reset the device */
141         lpc_scb.presetctrl &= ~(1 << LPC_SCB_PRESETCTRL_SSP0_RST_N);
142         lpc_scb.presetctrl |= (1 << LPC_SCB_PRESETCTRL_SSP0_RST_N);
143         ao_spi_channel_init(0);
144 #endif                     
145
146 #if HAS_SPI_1
147
148 #if SPI_SCK1_P1_15
149         lpc_ioconf.pio1_15 = ao_lpc_alternate(LPC_IOCONF_FUNC_PIO1_15_SCK1);
150 #define HAS_SCK1
151 #endif
152 #if SPI_SCK1_P1_20
153         lpc_ioconf.pio1_20 = ao_lpc_alternate(LPC_IOCONF_FUNC_PIO1_20_SCK1);
154 #define HAS_SCK1
155 #endif
156 #ifndef HAS_SCK1
157 #error "No pin specified for SCK1"
158 #endif
159
160 #if SPI_MISO1_P0_22
161         lpc_ioconf.pio0_22 = ao_lpc_alternate(LPC_IOCONF_FUNC_PIO0_22_MISO1);
162 #define HAS_MISO1
163 #endif
164 #if SPI_MISO1_P1_21
165         lpc_ioconf.pio1_21 = ao_lpc_alternate(LPC_IOCONF_FUNC_PIO1_21_MISO1);
166 #define HAS_MISO1
167 #endif
168 #ifndef HAS_MISO1
169 #error "No pin specified for MISO1"
170 #endif
171
172 #if SPI_MOSI1_P0_21
173         lpc_ioconf.pio1_21 = ao_lpc_alternate(LPC_IOCONF_FUNC_PIO0_21_MOSI1);
174 #define HAS_MOSI1
175 #endif
176 #if SPI_MOSI1_P1_22
177         lpc_ioconf.pio1_22 = ao_lpc_alternate(LPC_IOCONF_FUNC_PIO1_22_MOSI1);
178 #define HAS_MOSI1
179 #endif
180 #ifndef HAS_MOSI1
181 #error "No pin specified for MOSI1"
182 #endif
183                 
184         /* Enable the device */
185         lpc_scb.sysahbclkctrl |= (1 << LPC_SCB_SYSAHBCLKCTRL_SSP1);
186
187         /* Turn on the clock */
188         lpc_scb.ssp1clkdiv = 1;
189
190         /* De-assert reset */
191         lpc_scb.presetctrl |= (1 << LPC_SCB_PRESETCTRL_SSP1_RST_N);
192         ao_spi_channel_init(1);
193 #endif /* HAS_SPI_1 */
194 }