altos: Split out SPI driver.
[fw/altos] / src / ao_ee.c
1 /*
2  * Copyright © 2009 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 #include "25lc1024.h"
20
21 /*
22  * Using SPI on USART 0, with P1_2 as the chip select
23  */
24
25 #define EE_CS           P1_2
26 #define EE_CS_INDEX     2
27
28 __xdata uint8_t ao_ee_mutex;
29
30 #define ao_ee_delay() do { \
31         _asm nop _endasm; \
32         _asm nop _endasm; \
33         _asm nop _endasm; \
34 } while(0)
35
36 void ao_ee_cs_low(void)
37 {
38         ao_ee_delay();
39         EE_CS = 0;
40         ao_ee_delay();
41 }
42
43 void ao_ee_cs_high(void)
44 {
45         ao_ee_delay();
46         EE_CS = 1;
47         ao_ee_delay();
48 }
49
50
51 #define EE_BLOCK        256
52
53 struct ao_ee_instruction {
54         uint8_t instruction;
55         uint8_t address[3];
56 } __xdata ao_ee_instruction;
57
58 static void
59 ao_ee_write_enable(void)
60 {
61         ao_ee_cs_low();
62         ao_ee_instruction.instruction = EE_WREN;
63         ao_spi_send(&ao_ee_instruction, 1);
64         ao_ee_cs_high();
65 }
66
67 static uint8_t
68 ao_ee_rdsr(void)
69 {
70         ao_ee_cs_low();
71         ao_ee_instruction.instruction = EE_RDSR;
72         ao_spi_send(&ao_ee_instruction, 1);
73         ao_spi_recv(&ao_ee_instruction, 1);
74         ao_ee_cs_high();
75         return ao_ee_instruction.instruction;
76 }
77
78 static void
79 ao_ee_wrsr(uint8_t status)
80 {
81         ao_ee_cs_low();
82         ao_ee_instruction.instruction = EE_WRSR;
83         ao_ee_instruction.address[0] = status;
84         ao_spi_send(&ao_ee_instruction, 2);
85         ao_ee_cs_high();
86 }
87
88 #define EE_BLOCK_NONE   0xffff
89
90 static __xdata uint8_t ao_ee_data[EE_BLOCK];
91 static __pdata uint16_t ao_ee_block = EE_BLOCK_NONE;
92 static __pdata uint8_t  ao_ee_block_dirty;
93
94 /* Write the current block to the EEPROM */
95 static void
96 ao_ee_write_block(void)
97 {
98         uint8_t status;
99
100         status = ao_ee_rdsr();
101         if (status & (EE_STATUS_BP0|EE_STATUS_BP1|EE_STATUS_WPEN)) {
102                 status &= ~(EE_STATUS_BP0|EE_STATUS_BP1|EE_STATUS_WPEN);
103                 ao_ee_wrsr(status);
104         }
105         ao_ee_write_enable();
106         ao_ee_cs_low();
107         ao_ee_instruction.instruction = EE_WRITE;
108         ao_ee_instruction.address[0] = ao_ee_block >> 8;
109         ao_ee_instruction.address[1] = ao_ee_block;
110         ao_ee_instruction.address[2] = 0;
111         ao_spi_send(&ao_ee_instruction, 4);
112         ao_spi_send(ao_ee_data, EE_BLOCK);
113         ao_ee_cs_high();
114         for (;;) {
115                 uint8_t status = ao_ee_rdsr();
116                 if ((status & EE_STATUS_WIP) == 0)
117                         break;
118         }
119 }
120
121 /* Read the current block from the EEPROM */
122 static void
123 ao_ee_read_block(void)
124 {
125         ao_ee_cs_low();
126         ao_ee_instruction.instruction = EE_READ;
127         ao_ee_instruction.address[0] = ao_ee_block >> 8;
128         ao_ee_instruction.address[1] = ao_ee_block;
129         ao_ee_instruction.address[2] = 0;
130         ao_spi_send(&ao_ee_instruction, 4);
131         ao_spi_recv(ao_ee_data, EE_BLOCK);
132         ao_ee_cs_high();
133 }
134
135 static void
136 ao_ee_flush_internal(void)
137 {
138         if (ao_ee_block_dirty) {
139                 ao_ee_write_block();
140                 ao_ee_block_dirty = 0;
141         }
142 }
143
144 static void
145 ao_ee_fill(uint16_t block)
146 {
147         if (block != ao_ee_block) {
148                 ao_ee_flush_internal();
149                 ao_ee_block = block;
150                 ao_ee_read_block();
151         }
152 }
153
154 uint8_t
155 ao_ee_write(uint32_t pos, uint8_t *buf, uint16_t len) __reentrant
156 {
157         uint16_t block;
158         uint16_t this_len;
159         uint8_t this_off;
160
161         if (pos >= AO_EE_DATA_SIZE || pos + len > AO_EE_DATA_SIZE)
162                 return 0;
163         while (len) {
164
165                 /* Compute portion of transfer within
166                  * a single block
167                  */
168                 this_off = pos;
169                 this_len = 256 - (uint16_t) this_off;
170                 block = (uint16_t) (pos >> 8);
171                 if (this_len > len)
172                         this_len = len;
173                 if (this_len & 0xff00)
174                         ao_panic(AO_PANIC_EE);
175
176                 /* Transfer the data */
177                 ao_mutex_get(&ao_ee_mutex); {
178                         if (this_len != 256)
179                                 ao_ee_fill(block);
180                         else {
181                                 ao_ee_flush_internal();
182                                 ao_ee_block = block;
183                         }
184                         memcpy(ao_ee_data + this_off, buf, this_len);
185                         ao_ee_block_dirty = 1;
186                 } ao_mutex_put(&ao_ee_mutex);
187
188                 /* See how much is left */
189                 buf += this_len;
190                 len -= this_len;
191                 pos += this_len;
192         }
193         return 1;
194 }
195
196 uint8_t
197 ao_ee_read(uint32_t pos, uint8_t *buf, uint16_t len) __reentrant
198 {
199         uint16_t block;
200         uint16_t this_len;
201         uint8_t this_off;
202
203         if (pos >= AO_EE_DATA_SIZE || pos + len > AO_EE_DATA_SIZE)
204                 return 0;
205         while (len) {
206
207                 /* Compute portion of transfer within
208                  * a single block
209                  */
210                 this_off = pos;
211                 this_len = 256 - (uint16_t) this_off;
212                 block = (uint16_t) (pos >> 8);
213                 if (this_len > len)
214                         this_len = len;
215                 if (this_len & 0xff00)
216                         ao_panic(AO_PANIC_EE);
217
218                 /* Transfer the data */
219                 ao_mutex_get(&ao_ee_mutex); {
220                         ao_ee_fill(block);
221                         memcpy(buf, ao_ee_data + this_off, this_len);
222                 } ao_mutex_put(&ao_ee_mutex);
223
224                 /* See how much is left */
225                 buf += this_len;
226                 len -= this_len;
227                 pos += this_len;
228         }
229         return 1;
230 }
231
232 void
233 ao_ee_flush(void) __reentrant
234 {
235         ao_mutex_get(&ao_ee_mutex); {
236                 ao_ee_flush_internal();
237         } ao_mutex_put(&ao_ee_mutex);
238 }
239
240 /*
241  * Read/write the config block, which is in
242  * the last block of the ao_eeprom
243  */
244 uint8_t
245 ao_ee_write_config(uint8_t *buf, uint16_t len) __reentrant
246 {
247         if (len > AO_EE_BLOCK_SIZE)
248                 return 0;
249         ao_mutex_get(&ao_ee_mutex); {
250                 ao_ee_fill(AO_EE_CONFIG_BLOCK);
251                 memcpy(ao_ee_data, buf, len);
252                 ao_ee_block_dirty = 1;
253                 ao_ee_flush_internal();
254         } ao_mutex_put(&ao_ee_mutex);
255         return 1;
256 }
257
258 uint8_t
259 ao_ee_read_config(uint8_t *buf, uint16_t len) __reentrant
260 {
261         if (len > AO_EE_BLOCK_SIZE)
262                 return 0;
263         ao_mutex_get(&ao_ee_mutex); {
264                 ao_ee_fill(AO_EE_CONFIG_BLOCK);
265                 memcpy(buf, ao_ee_data, len);
266         } ao_mutex_put(&ao_ee_mutex);
267         return 1;
268 }
269
270 static void
271 ee_dump(void) __reentrant
272 {
273         uint8_t b;
274         uint16_t block;
275         uint8_t i;
276
277         ao_cmd_hex();
278         block = ao_cmd_lex_i;
279         if (ao_cmd_status != ao_cmd_success)
280                 return;
281         i = 0;
282         do {
283                 if ((i & 7) == 0) {
284                         if (i)
285                                 putchar('\n');
286                         ao_cmd_put16((uint16_t) i);
287                 }
288                 putchar(' ');
289                 ao_ee_read(((uint32_t) block << 8) | i, &b, 1);
290                 ao_cmd_put8(b);
291                 ++i;
292         } while (i != 0);
293         putchar('\n');
294 }
295
296 static void
297 ee_store(void) __reentrant
298 {
299         uint16_t block;
300         uint8_t i;
301         uint16_t len;
302         uint8_t b;
303         uint32_t addr;
304
305         ao_cmd_hex();
306         block = ao_cmd_lex_i;
307         ao_cmd_hex();
308         i = ao_cmd_lex_i;
309         addr = ((uint32_t) block << 8) | i;
310         ao_cmd_hex();
311         len = ao_cmd_lex_i;
312         if (ao_cmd_status != ao_cmd_success)
313                 return;
314         while (len--) {
315                 ao_cmd_hex();
316                 if (ao_cmd_status != ao_cmd_success)
317                         return;
318                 b = ao_cmd_lex_i;
319                 ao_ee_write(addr, &b, 1);
320                 addr++;
321         }
322         ao_ee_flush();
323 }
324
325 __code struct ao_cmds ao_ee_cmds[] = {
326         { 'e', ee_dump,         "e <block>                          Dump a block of EEPROM data" },
327         { 'w', ee_store,        "w <block> <start> <len> <data> ... Write data to EEPROM" },
328         { 0,   ee_store, NULL },
329 };
330
331 /*
332  * To initialize the chip, set up the CS line and
333  * the SPI interface
334  */
335 void
336 ao_ee_init(void)
337 {
338         /* set up CS */
339         EE_CS = 1;
340         P1DIR |= (1 << EE_CS_INDEX);
341         P1SEL &= ~(1 << EE_CS_INDEX);
342
343         ao_cmd_register(&ao_ee_cmds[0]);
344 }