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