diff options
Diffstat (limited to 'firmware/target/mips/ingenic_x1000/nand-x1000.c')
-rw-r--r-- | firmware/target/mips/ingenic_x1000/nand-x1000.c | 500 |
1 files changed, 500 insertions, 0 deletions
diff --git a/firmware/target/mips/ingenic_x1000/nand-x1000.c b/firmware/target/mips/ingenic_x1000/nand-x1000.c new file mode 100644 index 0000000000..54a1d11d95 --- /dev/null +++ b/firmware/target/mips/ingenic_x1000/nand-x1000.c | |||
@@ -0,0 +1,500 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2021 Aidan MacDonald | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | #include "nand-x1000.h" | ||
23 | #include "nand-target.h" | ||
24 | #include "sfc-x1000.h" | ||
25 | #include "system.h" | ||
26 | #include <string.h> | ||
27 | |||
28 | #if !defined(NAND_MAX_PAGE_SIZE) || \ | ||
29 | !defined(NAND_INIT_SFC_DEV_CONF) || \ | ||
30 | !defined(NAND_INIT_CLOCK_SPEED) | ||
31 | # error "Target needs to specify NAND driver parameters" | ||
32 | #endif | ||
33 | |||
34 | /* Must be at least as big as a cacheline */ | ||
35 | #define NAND_AUX_BUFFER_SIZE CACHEALIGN_SIZE | ||
36 | |||
37 | /* Writes have been enabled */ | ||
38 | #define NAND_DRV_FLAG_WRITEABLE 0x01 | ||
39 | |||
40 | /* Defined by target */ | ||
41 | extern const nand_chip_desc target_nand_chip_descs[]; | ||
42 | |||
43 | /* Globals for the driver | ||
44 | * TODO: get rid of pagebuffer in the SPL to save code size | ||
45 | */ | ||
46 | static unsigned char pagebuffer[NAND_MAX_PAGE_SIZE] CACHEALIGN_ATTR; | ||
47 | static unsigned char auxbuffer[NAND_AUX_BUFFER_SIZE] CACHEALIGN_ATTR; | ||
48 | static nand_drv nand_driver; | ||
49 | |||
50 | static void nand_drv_reset(nand_drv* d) | ||
51 | { | ||
52 | d->chip_ops = NULL; | ||
53 | d->chip_data = NULL; | ||
54 | d->pagebuf = &pagebuffer[0]; | ||
55 | d->auxbuf = &auxbuffer[0]; | ||
56 | d->raw_page_size = 0; | ||
57 | d->flags = 0; | ||
58 | } | ||
59 | |||
60 | /* Driver code */ | ||
61 | int nand_open(void) | ||
62 | { | ||
63 | sfc_init(); | ||
64 | sfc_lock(); | ||
65 | |||
66 | /* Reset driver state */ | ||
67 | nand_drv_reset(&nand_driver); | ||
68 | |||
69 | /* Init hardware */ | ||
70 | sfc_open(); | ||
71 | sfc_set_dev_conf(NAND_INIT_SFC_DEV_CONF); | ||
72 | sfc_set_clock(NAND_CLOCK_SOURCE, NAND_INIT_CLOCK_SPEED); | ||
73 | |||
74 | /* Identify NAND chip */ | ||
75 | int status = 0; | ||
76 | int nandid = nandcmd_read_id(&nand_driver); | ||
77 | if(nandid < 0) { | ||
78 | status = -1; | ||
79 | goto _err; | ||
80 | } | ||
81 | |||
82 | unsigned char mf_id = nandid >> 8; | ||
83 | unsigned char dev_id = nandid & 0xff; | ||
84 | const nand_chip_desc* desc = &target_nand_chip_descs[0]; | ||
85 | while(1) { | ||
86 | if(desc->data == NULL || desc->ops == NULL) { | ||
87 | status = -1; | ||
88 | goto _err; | ||
89 | } | ||
90 | |||
91 | if(desc->data->mf_id == mf_id && desc->data->dev_id == dev_id) | ||
92 | break; | ||
93 | } | ||
94 | |||
95 | /* Fill driver parameters */ | ||
96 | nand_driver.chip_ops = desc->ops; | ||
97 | nand_driver.chip_data = desc->data; | ||
98 | nand_driver.raw_page_size = desc->data->page_size + desc->data->spare_size; | ||
99 | |||
100 | /* Configure hardware and run init op */ | ||
101 | sfc_set_dev_conf(desc->data->dev_conf); | ||
102 | sfc_set_clock(NAND_CLOCK_SOURCE, desc->data->clock_freq); | ||
103 | |||
104 | if(desc->ops->open(&nand_driver) < 0) { | ||
105 | status = -1; | ||
106 | goto _err; | ||
107 | } | ||
108 | |||
109 | _exit: | ||
110 | sfc_unlock(); | ||
111 | return status; | ||
112 | _err: | ||
113 | nand_drv_reset(&nand_driver); | ||
114 | sfc_close(); | ||
115 | goto _exit; | ||
116 | } | ||
117 | |||
118 | void nand_close(void) | ||
119 | { | ||
120 | sfc_lock(); | ||
121 | nand_driver.chip_ops->close(&nand_driver); | ||
122 | nand_drv_reset(&nand_driver); | ||
123 | sfc_close(); | ||
124 | sfc_unlock(); | ||
125 | } | ||
126 | |||
127 | int nand_enable_writes(bool en) | ||
128 | { | ||
129 | sfc_lock(); | ||
130 | |||
131 | int st = nand_driver.chip_ops->set_wp_enable(&nand_driver, en); | ||
132 | if(st >= 0) { | ||
133 | if(en) | ||
134 | nand_driver.flags |= NAND_DRV_FLAG_WRITEABLE; | ||
135 | else | ||
136 | nand_driver.flags &= ~NAND_DRV_FLAG_WRITEABLE; | ||
137 | } | ||
138 | |||
139 | sfc_unlock(); | ||
140 | return st; | ||
141 | } | ||
142 | |||
143 | extern int nand_read_bytes(uint32_t byteaddr, int count, void* buf) | ||
144 | { | ||
145 | if(count <= 0) | ||
146 | return 0; | ||
147 | |||
148 | nand_drv* d = &nand_driver; | ||
149 | uint32_t rowaddr = byteaddr / d->chip_data->page_size; | ||
150 | uint32_t coladdr = byteaddr % d->chip_data->page_size; | ||
151 | unsigned char* dstbuf = (unsigned char*)buf; | ||
152 | int status = 0; | ||
153 | |||
154 | sfc_lock(); | ||
155 | do { | ||
156 | if(d->chip_ops->read_page(d, rowaddr, d->pagebuf) < 0) { | ||
157 | status = -1; | ||
158 | goto _end; | ||
159 | } | ||
160 | |||
161 | if(d->chip_ops->ecc_read(d, d->pagebuf) < 0) { | ||
162 | status = -1; | ||
163 | goto _end; | ||
164 | } | ||
165 | |||
166 | int amount = d->chip_data->page_size - coladdr; | ||
167 | if(amount > count) | ||
168 | amount = count; | ||
169 | |||
170 | memcpy(dstbuf, d->pagebuf, amount); | ||
171 | dstbuf += amount; | ||
172 | count -= amount; | ||
173 | rowaddr += 1; | ||
174 | coladdr = 0; | ||
175 | } while(count > 0); | ||
176 | |||
177 | _end: | ||
178 | sfc_unlock(); | ||
179 | return status; | ||
180 | } | ||
181 | |||
182 | int nand_write_bytes(uint32_t byteaddr, int count, const void* buf) | ||
183 | { | ||
184 | nand_drv* d = &nand_driver; | ||
185 | |||
186 | if((d->flags & NAND_DRV_FLAG_WRITEABLE) == 0) | ||
187 | return -1; | ||
188 | |||
189 | if(count <= 0) | ||
190 | return 0; | ||
191 | |||
192 | uint32_t rowaddr = byteaddr / d->chip_data->page_size; | ||
193 | uint32_t coladdr = byteaddr % d->chip_data->page_size; | ||
194 | |||
195 | /* Only support whole page writes right now */ | ||
196 | if(coladdr != 0) | ||
197 | return -1; | ||
198 | if(count % d->chip_data->page_size) | ||
199 | return -1; | ||
200 | |||
201 | const unsigned char* srcbuf = (const unsigned char*)buf; | ||
202 | int status = 0; | ||
203 | |||
204 | sfc_lock(); | ||
205 | do { | ||
206 | memcpy(d->pagebuf, srcbuf, d->chip_data->page_size); | ||
207 | d->chip_ops->ecc_write(d, d->pagebuf); | ||
208 | |||
209 | if(d->chip_ops->write_page(d, rowaddr, d->pagebuf) < 0) { | ||
210 | status = -1; | ||
211 | goto _end; | ||
212 | } | ||
213 | |||
214 | rowaddr += 1; | ||
215 | srcbuf += d->chip_data->page_size; | ||
216 | count -= d->chip_data->page_size; | ||
217 | } while(count > 0); | ||
218 | |||
219 | _end: | ||
220 | sfc_unlock(); | ||
221 | return status; | ||
222 | } | ||
223 | |||
224 | int nand_erase_block(uint32_t byteaddr) | ||
225 | { | ||
226 | nand_drv* d = &nand_driver; | ||
227 | |||
228 | if((d->flags & NAND_DRV_FLAG_WRITEABLE) == 0) | ||
229 | return -1; | ||
230 | |||
231 | /* Ensure address is aligned to a block boundary */ | ||
232 | uint32_t blockaddr = byteaddr / d->chip_data->page_size; | ||
233 | if(blockaddr % d->chip_data->block_size) | ||
234 | return -1; | ||
235 | |||
236 | int status = 0; | ||
237 | sfc_lock(); | ||
238 | |||
239 | if(d->chip_ops->erase_block(d, blockaddr)) { | ||
240 | status = -1; | ||
241 | goto _end; | ||
242 | } | ||
243 | |||
244 | _end: | ||
245 | sfc_unlock(); | ||
246 | return status; | ||
247 | } | ||
248 | |||
249 | int nandcmd_read_id(nand_drv* d) | ||
250 | { | ||
251 | sfc_op op = {0}; | ||
252 | op.command = NAND_CMD_READ_ID; | ||
253 | op.flags = SFC_FLAG_READ; | ||
254 | op.addr_bytes = 1; | ||
255 | op.addr_lo = 0; | ||
256 | op.data_bytes = 2; | ||
257 | op.buffer = d->auxbuf; | ||
258 | if(sfc_exec(&op)) | ||
259 | return -1; | ||
260 | |||
261 | return (d->auxbuf[0] << 8) | d->auxbuf[1]; | ||
262 | } | ||
263 | |||
264 | int nandcmd_write_enable(nand_drv* d) | ||
265 | { | ||
266 | (void)d; | ||
267 | |||
268 | sfc_op op = {0}; | ||
269 | op.command = NAND_CMD_WRITE_ENABLE; | ||
270 | if(sfc_exec(&op)) | ||
271 | return -1; | ||
272 | |||
273 | return 0; | ||
274 | } | ||
275 | |||
276 | int nandcmd_get_feature(nand_drv* d, int reg) | ||
277 | { | ||
278 | sfc_op op = {0}; | ||
279 | op.command = NAND_CMD_GET_FEATURE; | ||
280 | op.flags = SFC_FLAG_READ; | ||
281 | op.addr_bytes = 1; | ||
282 | op.addr_lo = reg & 0xff; | ||
283 | op.data_bytes = 1; | ||
284 | op.buffer = d->auxbuf; | ||
285 | if(sfc_exec(&op)) | ||
286 | return -1; | ||
287 | |||
288 | return d->auxbuf[0]; | ||
289 | } | ||
290 | |||
291 | int nandcmd_set_feature(nand_drv* d, int reg, int val) | ||
292 | { | ||
293 | sfc_op op = {0}; | ||
294 | op.command = NAND_CMD_SET_FEATURE; | ||
295 | op.flags = SFC_FLAG_READ; | ||
296 | op.addr_bytes = 1; | ||
297 | op.addr_lo = reg & 0xff; | ||
298 | op.data_bytes = 1; | ||
299 | op.buffer = d->auxbuf; | ||
300 | d->auxbuf[0] = val & 0xff; | ||
301 | if(sfc_exec(&op)) | ||
302 | return -1; | ||
303 | |||
304 | return 0; | ||
305 | } | ||
306 | |||
307 | int nandcmd_page_read_to_cache(nand_drv* d, uint32_t rowaddr) | ||
308 | { | ||
309 | sfc_op op = {0}; | ||
310 | op.command = NAND_CMD_PAGE_READ_TO_CACHE; | ||
311 | op.addr_bytes = d->chip_data->rowaddr_width; | ||
312 | op.addr_lo = rowaddr; | ||
313 | if(sfc_exec(&op)) | ||
314 | return -1; | ||
315 | |||
316 | return 0; | ||
317 | } | ||
318 | |||
319 | int nandcmd_read_from_cache(nand_drv* d, unsigned char* buf) | ||
320 | { | ||
321 | sfc_op op = {0}; | ||
322 | if(d->chip_data->flags & NANDCHIP_FLAG_QUAD) { | ||
323 | op.command = NAND_CMD_READ_FROM_CACHEx4; | ||
324 | op.mode = SFC_MODE_QUAD_IO; | ||
325 | } else { | ||
326 | op.command = NAND_CMD_READ_FROM_CACHE; | ||
327 | op.mode = SFC_MODE_STANDARD; | ||
328 | } | ||
329 | |||
330 | op.flags = SFC_FLAG_READ; | ||
331 | op.addr_bytes = d->chip_data->coladdr_width; | ||
332 | op.addr_lo = 0; | ||
333 | op.dummy_bits = 8; | ||
334 | op.data_bytes = d->raw_page_size; | ||
335 | op.buffer = buf; | ||
336 | if(sfc_exec(&op)) | ||
337 | return -1; | ||
338 | |||
339 | return 0; | ||
340 | } | ||
341 | |||
342 | int nandcmd_program_load(nand_drv* d, const unsigned char* buf) | ||
343 | { | ||
344 | sfc_op op = {0}; | ||
345 | if(d->chip_data->flags & NANDCHIP_FLAG_QUAD) { | ||
346 | op.command = NAND_CMD_PROGRAM_LOADx4; | ||
347 | op.mode = SFC_MODE_QUAD_IO; | ||
348 | } else { | ||
349 | op.command = NAND_CMD_PROGRAM_LOAD; | ||
350 | op.mode = SFC_MODE_STANDARD; | ||
351 | } | ||
352 | |||
353 | op.flags = SFC_FLAG_WRITE; | ||
354 | op.addr_bytes = d->chip_data->coladdr_width; | ||
355 | op.addr_lo = 0; | ||
356 | op.data_bytes = d->raw_page_size; | ||
357 | op.buffer = (void*)buf; | ||
358 | if(sfc_exec(&op)) | ||
359 | return -1; | ||
360 | |||
361 | return 0; | ||
362 | } | ||
363 | |||
364 | int nandcmd_program_execute(nand_drv* d, uint32_t rowaddr) | ||
365 | { | ||
366 | sfc_op op = {0}; | ||
367 | op.command = NAND_CMD_PROGRAM_EXECUTE; | ||
368 | op.addr_bytes = d->chip_data->rowaddr_width; | ||
369 | op.addr_lo = rowaddr; | ||
370 | if(sfc_exec(&op)) | ||
371 | return -1; | ||
372 | |||
373 | return 0; | ||
374 | } | ||
375 | |||
376 | int nandcmd_block_erase(nand_drv* d, uint32_t blockaddr) | ||
377 | { | ||
378 | sfc_op op = {0}; | ||
379 | op.command = NAND_CMD_BLOCK_ERASE; | ||
380 | op.addr_bytes = d->chip_data->rowaddr_width; | ||
381 | op.addr_lo = blockaddr; | ||
382 | if(sfc_exec(&op)) | ||
383 | return 01; | ||
384 | |||
385 | return 0; | ||
386 | } | ||
387 | |||
388 | const nand_chip_ops nand_chip_ops_std = { | ||
389 | .open = nandop_std_open, | ||
390 | .close = nandop_std_close, | ||
391 | .read_page = nandop_std_read_page, | ||
392 | .write_page = nandop_std_write_page, | ||
393 | .erase_block = nandop_std_erase_block, | ||
394 | .set_wp_enable = nandop_std_set_wp_enable, | ||
395 | .ecc_read = nandop_ecc_none_read, | ||
396 | .ecc_write = nandop_ecc_none_write, | ||
397 | }; | ||
398 | |||
399 | /* Helper needed by other ops */ | ||
400 | static int nandop_std_wait_status(nand_drv* d, int errbit) | ||
401 | { | ||
402 | int reg; | ||
403 | do { | ||
404 | reg = nandcmd_get_feature(d, NAND_FREG_STATUS); | ||
405 | if(reg < 0) | ||
406 | return -1; | ||
407 | } while(reg & NAND_FREG_STATUS_OIP); | ||
408 | |||
409 | if(reg & errbit) | ||
410 | return -1; | ||
411 | |||
412 | return reg; | ||
413 | } | ||
414 | |||
415 | int nandop_std_open(nand_drv* d) | ||
416 | { | ||
417 | (void)d; | ||
418 | return 0; | ||
419 | } | ||
420 | |||
421 | void nandop_std_close(nand_drv* d) | ||
422 | { | ||
423 | (void)d; | ||
424 | } | ||
425 | |||
426 | int nandop_std_read_page(nand_drv* d, uint32_t rowaddr, unsigned char* buf) | ||
427 | { | ||
428 | if(nandcmd_page_read_to_cache(d, rowaddr) < 0) | ||
429 | return -1; | ||
430 | if(nandop_std_wait_status(d, 0) < 0) | ||
431 | return -1; | ||
432 | if(nandcmd_read_from_cache(d, buf) < 0) | ||
433 | return -1; | ||
434 | |||
435 | return 0; | ||
436 | } | ||
437 | |||
438 | int nandop_std_write_page(nand_drv* d, uint32_t rowaddr, const unsigned char* buf) | ||
439 | { | ||
440 | if(nandcmd_write_enable(d) < 0) | ||
441 | return -1; | ||
442 | if(nandcmd_program_load(d, buf) < 0) | ||
443 | return -1; | ||
444 | if(nandcmd_program_execute(d, rowaddr) < 0) | ||
445 | return -1; | ||
446 | if(nandop_std_wait_status(d, NAND_FREG_STATUS_P_FAIL) < 0) | ||
447 | return -1; | ||
448 | |||
449 | return 0; | ||
450 | } | ||
451 | |||
452 | int nandop_std_erase_block(nand_drv* d, uint32_t blockaddr) | ||
453 | { | ||
454 | if(nandcmd_write_enable(d) < 0) | ||
455 | return -1; | ||
456 | if(nandcmd_block_erase(d, blockaddr) < 0) | ||
457 | return -1; | ||
458 | if(nandop_std_wait_status(d, NAND_FREG_STATUS_E_FAIL) < 0) | ||
459 | return -1; | ||
460 | |||
461 | return 0; | ||
462 | } | ||
463 | |||
464 | int nandop_std_set_wp_enable(nand_drv* d, bool en) | ||
465 | { | ||
466 | int val = nandcmd_get_feature(d, NAND_FREG_PROTECTION); | ||
467 | if(val < 0) | ||
468 | return -1; | ||
469 | |||
470 | if(en) { | ||
471 | val &= ~NAND_FREG_PROTECTION_ALLBP; | ||
472 | if(d->chip_data->flags & NANDCHIP_FLAG_USE_BRWD) | ||
473 | val &= ~NAND_FREG_PROTECTION_BRWD; | ||
474 | } else { | ||
475 | val |= NAND_FREG_PROTECTION_ALLBP; | ||
476 | if(d->chip_data->flags & NANDCHIP_FLAG_USE_BRWD) | ||
477 | val |= NAND_FREG_PROTECTION_BRWD; | ||
478 | } | ||
479 | |||
480 | sfc_set_wp_enable(false); | ||
481 | int status = nandcmd_set_feature(d, NAND_FREG_PROTECTION, val); | ||
482 | sfc_set_wp_enable(true); | ||
483 | |||
484 | if(status < 0) | ||
485 | return -1; | ||
486 | |||
487 | return 0; | ||
488 | } | ||
489 | |||
490 | int nandop_ecc_none_read(nand_drv* d, unsigned char* buf) | ||
491 | { | ||
492 | (void)d; | ||
493 | (void)buf; | ||
494 | return 0; | ||
495 | } | ||
496 | |||
497 | void nandop_ecc_none_write(nand_drv* d, unsigned char* buf) | ||
498 | { | ||
499 | memset(&buf[d->chip_data->page_size], 0xff, d->chip_data->spare_size); | ||
500 | } | ||