summaryrefslogtreecommitdiff
path: root/firmware/target/mips/ingenic_x1000/nand-x1000.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/target/mips/ingenic_x1000/nand-x1000.c')
-rw-r--r--firmware/target/mips/ingenic_x1000/nand-x1000.c500
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 */
41extern 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 */
46static unsigned char pagebuffer[NAND_MAX_PAGE_SIZE] CACHEALIGN_ATTR;
47static unsigned char auxbuffer[NAND_AUX_BUFFER_SIZE] CACHEALIGN_ATTR;
48static nand_drv nand_driver;
49
50static 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 */
61int 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
118void 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
127int 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
143extern 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
182int 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
224int 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
249int 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
264int 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
276int 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
291int 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
307int 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
319int 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
342int 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
364int 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
376int 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
388const 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 */
400static 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
415int nandop_std_open(nand_drv* d)
416{
417 (void)d;
418 return 0;
419}
420
421void nandop_std_close(nand_drv* d)
422{
423 (void)d;
424}
425
426int 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
438int 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
452int 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
464int 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
490int nandop_ecc_none_read(nand_drv* d, unsigned char* buf)
491{
492 (void)d;
493 (void)buf;
494 return 0;
495}
496
497void 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}