altos: Cannot scan for flash chips until the OS is running.
[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  * Scan the possible chip select lines
178  * to see which flash chips are connected
179  */
180 static void
181 ao_m25_scan(void)
182 {
183         uint8_t pin, size;
184
185         if (ao_m25_total)
186                 return;
187
188         ao_m25_numchips = 0;
189         for (pin = 1; pin != 0; pin <<= 1) {
190                 if (M25_CS_MASK & pin) {
191                         size = ao_m25_read_capacity(pin);
192                         if (size != 0) {
193                                 ao_m25_size[ao_m25_numchips] = size;
194                                 ao_m25_pin[ao_m25_numchips] = pin;
195                                 ao_m25_total += size;
196                                 ao_m25_numchips++;
197                         }
198                 }
199         }
200 }
201
202 /*
203  * Erase the specified sector
204  */
205 void
206 ao_flash_erase_sector(uint8_t sector) __reentrant
207 {
208         uint8_t cs;
209         uint16_t page = AO_M25_SECTOR_TO_PAGE(sector);
210
211         ao_mutex_get(&ao_m25_mutex);
212         ao_m25_scan();
213
214         cs = ao_m25_set_page_address(page);
215         ao_m25_wait_wip(cs);
216         ao_m25_write_enable(cs);
217
218         ao_m25_instruction[0] = M25_SE;
219         AO_M25_SELECT(cs);
220         ao_spi_send(ao_m25_instruction, 4);
221         AO_M25_DESELECT(cs);
222         ao_m25_wip |= cs;
223
224         ao_mutex_put(&ao_m25_mutex);
225 }
226
227 /*
228  * Write one page
229  */
230 void
231 ao_flash_write_page(uint16_t page, uint8_t __xdata *d) __reentrant
232 {
233         uint8_t cs;
234
235         ao_mutex_get(&ao_m25_mutex);
236         ao_m25_scan();
237
238         cs = ao_m25_set_page_address(page);
239         ao_m25_write_enable(cs);
240
241         ao_m25_instruction[0] = M25_PP;
242         AO_M25_SELECT(cs);
243         ao_spi_send(ao_m25_instruction, 4);
244         ao_spi_send(d, 256);
245         AO_M25_DESELECT(cs);
246
247         ao_mutex_put(&ao_m25_mutex);
248 }
249
250 /*
251  * Read one page
252  */
253 void
254 ao_flash_read_page(uint16_t page, __xdata uint8_t *d) __reentrant
255 {
256         uint8_t cs;
257
258         ao_mutex_get(&ao_m25_mutex);
259         ao_m25_scan();
260
261         cs = ao_m25_set_page_address(page);
262
263         /* No need to use the FAST_READ as we're running at only 8MHz */
264         ao_m25_instruction[0] = M25_READ;
265         AO_M25_SELECT(cs);
266         ao_spi_send(ao_m25_instruction, 4);
267         ao_spi_recv(d, 256);
268         AO_M25_DESELECT(cs);
269
270         ao_mutex_put(&ao_m25_mutex);
271 }
272
273 static __xdata uint8_t ao_flash_block[256];
274
275 static void
276 ao_flash_dump(void) __reentrant
277 {
278         uint8_t i;
279
280         ao_cmd_hex();
281         if (ao_cmd_status != ao_cmd_success)
282                 return;
283         ao_flash_read_page(ao_cmd_lex_i, ao_flash_block);
284         i = 0;
285         do {
286                 if ((i & 7) == 0) {
287                         if (i)
288                                 putchar('\n');
289                         ao_cmd_put16((uint16_t) i);
290                 }
291                 putchar(' ');
292                 ao_cmd_put8(ao_flash_block[i]);
293                 ++i;
294         } while (i != 0);
295         putchar('\n');
296 }
297
298 static void
299 ao_flash_store(void) __reentrant
300 {
301         uint16_t block;
302         uint8_t i;
303         uint16_t len;
304         uint8_t b;
305
306         ao_cmd_hex();
307         block = ao_cmd_lex_i;
308         ao_cmd_hex();
309         i = ao_cmd_lex_i;
310         ao_cmd_hex();
311         len = ao_cmd_lex_i;
312         if (ao_cmd_status != ao_cmd_success)
313                 return;
314         ao_flash_read_page(block, ao_flash_block);
315         while (len--) {
316                 ao_cmd_hex();
317                 if (ao_cmd_status != ao_cmd_success)
318                         return;
319                 b = ao_cmd_lex_i;
320                 ao_flash_block[i] = ao_cmd_lex_i;
321                 i++;
322         }
323         ao_flash_write_page(block, ao_flash_block);
324 }
325
326 static void
327 ao_flash_info(void) __reentrant
328 {
329         uint8_t chip, cs;
330
331         ao_mutex_get(&ao_m25_mutex);
332         ao_m25_scan();
333         ao_mutex_put(&ao_m25_mutex);
334
335         printf ("Detected chips %d size %d\n", ao_m25_numchips, ao_m25_total);
336         for (chip = 0; chip < ao_m25_numchips; chip++)
337                 printf ("Flash chip %d select %02x size %d manf %02x type %02x cap %02x uid %02x\n",
338                         chip, ao_m25_pin[chip], ao_m25_size[chip]);
339
340         printf ("Available chips:\n");
341         for (cs = 1; cs != 0; cs <<= 1) {
342                 if ((M25_CS_MASK & cs) == 0)
343                         continue;
344
345                 ao_mutex_get(&ao_m25_mutex);
346                 AO_M25_SELECT(cs);
347                 ao_m25_instruction[0] = M25_RDID;
348                 ao_spi_send(ao_m25_instruction, 1);
349                 ao_spi_recv(ao_m25_instruction, M25_RDID_LEN);
350                 AO_M25_DESELECT(cs);
351
352                 printf ("Select %02x manf %02x type %02x cap %02x uid %02x\n",
353                         cs,
354                         ao_m25_instruction[M25_MANUF_OFFSET],
355                         ao_m25_instruction[M25_MEMORY_TYPE_OFFSET],
356                         ao_m25_instruction[M25_CAPACITY_OFFSET],
357                         ao_m25_instruction[M25_UID_OFFSET]);
358                 ao_mutex_put(&ao_m25_mutex);
359         }
360 }
361
362 __code struct ao_cmds ao_flash_cmds[] = {
363         { 'e', ao_flash_dump,   "e <block>                          Dump a block of EEPROM data" },
364         { 'w', ao_flash_store,  "w <block> <start> <len> <data> ... Write data to EEPROM" },
365         { 'F', ao_flash_info,   "F                                  Display flash info" },
366         { 0,   ao_flash_store, NULL },
367 };
368
369 void
370 ao_flash_init(void)
371 {
372         /* Set up chip select wires */
373         SPI_CS_PORT |= M25_CS_MASK;     /* raise all CS pins */
374         SPI_CS_DIR |= M25_CS_MASK;      /* set CS pins as outputs */
375         SPI_CS_SEL &= ~M25_CS_MASK;     /* set CS pins as GPIO */
376         ao_spi_init();
377
378         ao_cmd_register(&ao_flash_cmds[0]);
379 }