f0208a37a16a21f0ffadd631ec970a15644d94b3
[fw/altos] / src / ao_m25.c
1 /*
2  * Copyright © 2010 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 /*
21  * Each flash chip is arranged in 64kB sectors; the
22  * chip cannot erase in units smaller than that.
23  *
24  * Writing happens in units of 256 byte pages and
25  * can only change bits from 1 to 0. So, you can rewrite
26  * the same contents, or append to an existing page easily enough
27  */
28
29 #define M25_WREN        0x06    /* Write Enable */
30 #define M25_WRDI        0x04    /* Write Disable */
31 #define M25_RDID        0x9f    /* Read Identification */
32 #define M25_RDSR        0x05    /* Read Status Register */
33 #define M25_WRSR        0x01    /* Write Status Register */
34 #define M25_READ        0x03    /* Read Data Bytes */
35 #define M25_FAST_READ   0x0b    /* Read Data Bytes at Higher Speed */
36 #define M25_PP          0x02    /* Page Program */
37 #define M25_SE          0xd8    /* Sector Erase */
38 #define M25_BE          0xc7    /* Bulk Erase */
39 #define M25_DP          0xb9    /* Deep Power-down */
40
41 /* RDID response */
42 #define M25_MANUF_OFFSET        0
43 #define M25_MEMORY_TYPE_OFFSET  1
44 #define M25_CAPACITY_OFFSET     2
45 #define M25_UID_OFFSET          3
46 #define M25_CFI_OFFSET          4
47 #define M25_RDID_LEN            4       /* that's all we need */
48
49 #define M25_CAPACITY_128KB      0x11
50 #define M25_CAPACITY_256KB      0x12
51 #define M25_CAPACITY_512KB      0x13
52 #define M25_CAPACITY_1MB        0x14
53 #define M25_CAPACITY_2MB        0x15
54
55 /*
56  * Status register bits
57  */
58
59 #define M25_STATUS_SRWD         (1 << 7)        /* Status register write disable */
60 #define M25_STATUS_BP_MASK      (7 << 2)        /* Block protect bits */
61 #define M25_STATUS_BP_SHIFT     (2)
62 #define M25_STATUS_WEL          (1 << 1)        /* Write enable latch */
63 #define M25_STATUS_WIP          (1 << 0)        /* Write in progress */
64
65 /*
66  * On teleterra, the m25 chip select pins are
67  * wired on P0_0 through P0_3.
68  */
69
70 #define AO_M25_MAX_CHIPS        4
71
72 static uint8_t ao_m25_size[AO_M25_MAX_CHIPS];   /* number of sectors in each chip */
73 static uint8_t ao_m25_pin[AO_M25_MAX_CHIPS];    /* chip select pin for each chip */
74 static uint8_t ao_m25_numchips;                 /* number of chips detected */
75 static uint8_t ao_m25_total;                    /* total sectors available */
76 static uint8_t ao_m25_wip;                      /* write in progress */
77
78 static __xdata uint8_t ao_m25_mutex;
79
80 /*
81  * This little array is abused to send and receive data. A particular
82  * caution -- the read and write addresses are written into the last
83  * three bytes of the array by ao_m25_set_page_address and then the
84  * first byte is used by ao_m25_wait_wip and ao_m25_write_enable, neither
85  * of which touch those last three bytes.
86  */
87
88 static __xdata uint8_t  ao_m25_instruction[4];
89
90 #define AO_M25_SELECT(cs)               (SPI_CS_PORT &= ~(cs))
91 #define AO_M25_DESELECT(cs)             (SPI_CS_PORT |= (cs))
92 #define AO_M25_PAGE_TO_SECTOR(page)     ((page) >> 8)
93 #define AO_M25_SECTOR_TO_PAGE(sector)   (((uint16_t) (sector)) << 8)
94
95 /*
96  * Block until the specified chip is done writing
97  */
98 static void
99 ao_m25_wait_wip(uint8_t cs)
100 {
101         if (ao_m25_wip & cs) {
102                 AO_M25_SELECT(cs);
103                 ao_m25_instruction[0] = M25_RDSR;
104                 ao_spi_send(ao_m25_instruction, 1);
105                 do {
106                         ao_spi_recv(ao_m25_instruction, 1);
107                 } while (ao_m25_instruction[0] & M25_STATUS_WIP);
108                 AO_M25_DESELECT(cs);
109                 ao_m25_wip &= ~cs;
110         }
111 }
112
113 /*
114  * Set the write enable latch so that page program and sector
115  * erase commands will work. Also mark the chip as busy writing
116  * so that future operations will block until the WIP bit goes off
117  */
118 static void
119 ao_m25_write_enable(uint8_t cs)
120 {
121         AO_M25_SELECT(cs);
122         ao_m25_instruction[0] = M25_WREN;
123         ao_spi_send(&ao_m25_instruction, 1);
124         AO_M25_DESELECT(cs);
125         ao_m25_wip |= cs;
126 }
127
128
129 /*
130  * Returns the number of 64kB sectors
131  */
132 static uint8_t
133 ao_m25_read_capacity(uint8_t cs)
134 {
135         uint8_t capacity;
136         AO_M25_SELECT(cs);
137         ao_m25_instruction[0] = M25_RDID;
138         ao_spi_send(ao_m25_instruction, 1);
139         ao_spi_recv(ao_m25_instruction, M25_RDID_LEN);
140         AO_M25_DESELECT(cs);
141
142         /* Check to see if the chip is present */
143         if (ao_m25_instruction[0] == 0xff)
144                 return 0;
145         capacity = ao_m25_instruction[M25_CAPACITY_OFFSET];
146
147         /* Sanity check capacity number */
148         if (capacity < 0x11 || 0x1f < capacity)
149                 return 0;
150         return 1 << (capacity - 0x10);
151 }
152
153 static uint8_t
154 ao_m25_set_page_address(uint16_t page)
155 {
156         uint8_t chip, size;
157
158         for (chip = 0; chip < ao_m25_numchips; chip++) {
159                 size = ao_m25_size[chip];
160                 if (AO_M25_PAGE_TO_SECTOR(page) < size)
161                         break;
162                 page -= AO_M25_SECTOR_TO_PAGE(size);
163         }
164         if (chip == ao_m25_numchips)
165                 ao_panic(AO_PANIC_EE);
166
167         chip = ao_m25_pin[chip];
168         ao_m25_wait_wip(chip);
169
170         ao_m25_instruction[1] = page >> 8;
171         ao_m25_instruction[2] = page;
172         ao_m25_instruction[3] = 0;
173         return chip;
174 }
175
176 /*
177  * Erase the specified sector
178  */
179 void
180 ao_flash_erase_sector(uint8_t sector) __reentrant
181 {
182         uint8_t cs;
183         uint16_t page = AO_M25_SECTOR_TO_PAGE(sector);
184
185         ao_mutex_get(&ao_m25_mutex);
186
187         cs = ao_m25_set_page_address(page);
188         ao_m25_wait_wip(cs);
189         ao_m25_write_enable(cs);
190
191         ao_m25_instruction[0] = M25_SE;
192         AO_M25_SELECT(cs);
193         ao_spi_send(ao_m25_instruction, 4);
194         AO_M25_DESELECT(cs);
195         ao_m25_wip |= cs;
196
197         ao_mutex_put(&ao_m25_mutex);
198 }
199
200 /*
201  * Write one page
202  */
203 void
204 ao_flash_write_page(uint16_t page, uint8_t __xdata *d) __reentrant
205 {
206         uint8_t cs;
207
208         ao_mutex_get(&ao_m25_mutex);
209
210         cs = ao_m25_set_page_address(page);
211         ao_m25_write_enable(cs);
212
213         ao_m25_instruction[0] = M25_PP;
214         AO_M25_SELECT(cs);
215         ao_spi_send(ao_m25_instruction, 4);
216         ao_spi_send(d, 256);
217         AO_M25_DESELECT(cs);
218
219         ao_mutex_put(&ao_m25_mutex);
220 }
221
222 /*
223  * Read one page
224  */
225 void
226 ao_flash_read_page(uint16_t page, __xdata uint8_t *d) __reentrant
227 {
228         uint8_t cs;
229
230         ao_mutex_get(&ao_m25_mutex);
231
232         cs = ao_m25_set_page_address(page);
233
234         /* No need to use the FAST_READ as we're running at only 8MHz */
235         ao_m25_instruction[0] = M25_READ;
236         AO_M25_SELECT(cs);
237         ao_spi_send(ao_m25_instruction, 4);
238         ao_spi_recv(d, 256);
239         AO_M25_DESELECT(cs);
240
241         ao_mutex_put(&ao_m25_mutex);
242 }
243
244 static __xdata uint8_t ao_flash_block[256];
245
246 static void
247 ao_flash_dump(void) __reentrant
248 {
249         uint8_t i;
250
251         ao_cmd_hex();
252         if (ao_cmd_status != ao_cmd_success)
253                 return;
254         ao_flash_read_page(ao_cmd_lex_i, ao_flash_block);
255         i = 0;
256         do {
257                 if ((i & 7) == 0) {
258                         if (i)
259                                 putchar('\n');
260                         ao_cmd_put16((uint16_t) i);
261                 }
262                 putchar(' ');
263                 ao_cmd_put8(ao_flash_block[i]);
264                 ++i;
265         } while (i != 0);
266         putchar('\n');
267 }
268
269 static void
270 ao_flash_store(void) __reentrant
271 {
272         uint16_t block;
273         uint8_t i;
274         uint16_t len;
275         uint8_t b;
276
277         ao_cmd_hex();
278         block = ao_cmd_lex_i;
279         ao_cmd_hex();
280         i = ao_cmd_lex_i;
281         ao_cmd_hex();
282         len = ao_cmd_lex_i;
283         if (ao_cmd_status != ao_cmd_success)
284                 return;
285         ao_flash_read_page(block, ao_flash_block);
286         while (len--) {
287                 ao_cmd_hex();
288                 if (ao_cmd_status != ao_cmd_success)
289                         return;
290                 b = ao_cmd_lex_i;
291                 ao_flash_block[i] = ao_cmd_lex_i;
292                 i++;
293         }
294         ao_flash_write_page(block, ao_flash_block);
295 }
296
297 static void
298 ao_flash_info(void) __reentrant
299 {
300         uint8_t chip, cs;
301
302         printf ("Detected chips %d size %d\n", ao_m25_numchips, ao_m25_total);
303         for (chip = 0; chip < ao_m25_numchips; chip++)
304                 printf ("Flash chip %d select %02x size %d manf %02x type %02x cap %02x uid %02x\n",
305                         chip, ao_m25_pin[chip], ao_m25_size[chip]);
306
307         printf ("Available chips:\n");
308         for (cs = 1; cs != 0; cs <<= 1) {
309                 if ((M25_CS_MASK & cs) == 0)
310                         continue;
311
312                 ao_mutex_get(&ao_m25_mutex);
313                 AO_M25_SELECT(cs);
314                 ao_m25_instruction[0] = M25_RDID;
315                 ao_spi_send(ao_m25_instruction, 1);
316                 ao_spi_recv(ao_m25_instruction, M25_RDID_LEN);
317                 AO_M25_DESELECT(cs);
318
319                 printf ("Select %02x manf %02x type %02x cap %02x uid %02x\n",
320                         cs,
321                         ao_m25_instruction[M25_MANUF_OFFSET],
322                         ao_m25_instruction[M25_MEMORY_TYPE_OFFSET],
323                         ao_m25_instruction[M25_CAPACITY_OFFSET],
324                         ao_m25_instruction[M25_UID_OFFSET]);
325                 ao_mutex_put(&ao_m25_mutex);
326         }
327 }
328
329 __code struct ao_cmds ao_flash_cmds[] = {
330         { 'e', ao_flash_dump,   "e <block>                          Dump a block of EEPROM data" },
331         { 'w', ao_flash_store,  "w <block> <start> <len> <data> ... Write data to EEPROM" },
332         { 'F', ao_flash_info,   "F                                  Display flash info" },
333         { 0,   ao_flash_store, NULL },
334 };
335
336 void
337 ao_flash_init(void)
338 {
339         uint8_t pin, size;
340
341         /* Set up chip select wires */
342         SPI_CS_PORT |= M25_CS_MASK;     /* raise all CS pins */
343         SPI_CS_DIR |= M25_CS_MASK;      /* set CS pins as outputs */
344         SPI_CS_SEL &= ~M25_CS_MASK;     /* set CS pins as GPIO */
345         ao_spi_init();
346
347         ao_m25_numchips = 0;
348         for (pin = 1; pin != 0; pin <<= 1) {
349                 if (M25_CS_MASK & pin) {
350                         size = ao_m25_read_capacity(pin);
351                         if (size != 0) {
352                                 ao_m25_size[ao_m25_numchips] = size;
353                                 ao_m25_pin[ao_m25_numchips] = pin;
354                                 ao_m25_total += size;
355                                 ao_m25_numchips++;
356                         }
357                 }
358         }
359         ao_cmd_register(&ao_flash_cmds[0]);
360 }