-
Notifications
You must be signed in to change notification settings - Fork 4
/
sst25.c
674 lines (570 loc) · 15.3 KB
/
sst25.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
/**
* @file sst25.c
* @brief FLASH25 SST25 driver
* @author Vladimir Ermakov Copyright (C) 2014.
*
* Based on 25xx.c from ChibiOS-EEPROM written by Timon Wong
* And sst25.c from NuttX written by Gregory Nutt <gnutt@nuttx.org>
*/
/*
* chibios-flash
* Copyright (c) 2014, Vlidimir Ermakov, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
#include "sst25.h"
/*
* Configuration
*/
#if !defined(SST25_FAST_READ) && !defined(SST25_SLOW_READ)
/* Use slow (< 25 MHz) reads by default */
#define SST25_SLOW_READ
#endif
#if defined(SST25_FAST_READ) && defined(SST25_SLOW_READ)
#error "Please select only one read method: FAST or SLOW"
#endif
#if !defined(SST25_SLOW_WRITE) && !defined(SST25_FAST_WRITE)
/* Use byte program (not AAI) by default */
#define SST25_SLOW_WRITE
#endif
#if defined(SST25_FAST_WRITE) && defined(SST25_SLOW_WRITE)
#error "Please select only one write method: FAST (AAI) or SLOW"
#endif
/* Defines */
#if !SPI_USE_MUTUAL_EXCLUSION
#define spiAcquireBus(spip);
#define spiReleaseBus(spip);
#endif
/* SST25 command set */
#define CMD_READ 0x03
#define CMD_FAST_READ 0x0b
#define CMD_ERASE_4K 0x20
#define CMD_ERASE_32K 0x52
#define CMD_ERASE_64K 0xd8
#define CMD_CHIP_ERASE 0x60 /* or 0xc7 */
#define CMD_BYTE_PROG 0x02
#define CMD_AAI_WORD_PROG 0xad
#define CMD_RDSR 0x05
#define CMD_EWSR 0x50
#define CMD_WRSR 0x01
#define CMD_WREN 0x06
#define CMD_WRDI 0x04
#define CMD_RDID 0x90
#define CMD_JDEC_ID 0x9f
#define CMD_EBSY 0x70
#define CMD_DBSY 0x80
/* SST25 status register bits */
#define STAT_BUSY (1<<0)
#define STAT_WEL (1<<1)
#define STAT_BP0 (1<<2)
#define STAT_BP1 (1<<3)
#define STAT_BP2 (1<<4)
#define STAT_BP3 (1<<5)
#define STAT_AAI (1<<6)
#define STAT_BPL (1<<7)
#define FLASH_TIMEOUT MS2ST(10)
#define ERASE_TIMEOUT MS2ST(100)
#define SST25_PAGESZ 256
/*
* Supported device table
*/
#define INFO(name_, id_, ps_, es_, nr_) { name_, id_, ps_, es_, nr_ }
struct sst25_ll_info {
const char *name;
uint32_t jdec_id;
uint16_t page_size;
uint16_t erase_size;
uint32_t nr_pages;
};
static const struct sst25_ll_info sst25_ll_info_table[] = {
INFO("sst25vf016b", 0xbf2541, SST25_PAGESZ, 4096, 16*1024*1024/8/SST25_PAGESZ),
INFO("sst25vf032b", 0xbf254a, SST25_PAGESZ, 4096, 32*1024*1024/8/SST25_PAGESZ)
};
/*
* Low level flash interface
*/
/**
* @brief SPI-Flash transfer function
* @notapi
*/
static void sst25_ll_transfer(const SST25Config *cfg,
const uint8_t *txbuf, size_t txlen,
uint8_t *rxbuf, size_t rxlen)
{
spiAcquireBus(cfg->spip);
spiStart(cfg->spip, cfg->spicfg);
spiSelect(cfg->spip);
spiSend(cfg->spip, txlen, txbuf);
if (rxlen)
spiReceive(cfg->spip, rxlen, rxbuf);
spiUnselect(cfg->spip);
spiReleaseBus(cfg->spip);
}
/**
* @brief checks busy flag
* @notapi
*/
static bool sst25_ll_is_busy(const SST25Config *cfg)
{
uint8_t cmd = CMD_RDSR;
uint8_t stat;
sst25_ll_transfer(cfg, &cmd, 1, &stat, 1);
return !!(stat & STAT_BUSY);
}
/**
* @brief wait write completion
* @return HAL_FAILED if timeout occurs
* @notapi
*/
static bool sst25_ll_wait_complete(const SST25Config *cfg, systime_t timeout)
{
systime_t start = osalOsGetSystemTimeX();
while (sst25_ll_is_busy(cfg)) {
systime_t now = osalOsGetSystemTimeX();
if (now - start >= timeout)
return HAL_FAILED; /* Timeout */
chThdYield();
}
return HAL_SUCCESS;
}
/**
* @brief write status register (disable block protection)
* @notapi
*/
static void sst25_ll_wrsr(const SST25Config *cfg, uint8_t sr)
{
uint8_t cmd[2];
cmd[0] = CMD_EWSR;
sst25_ll_transfer(cfg, cmd, 1, NULL, 0);
cmd[0] = CMD_WRSR;
cmd[1] = sr;
sst25_ll_transfer(cfg, cmd, 2, NULL, 0);
}
/**
* @brief read JDEC ID from device
* @notapi
*/
static uint32_t sst25_ll_get_jdec_id(const SST25Config *cfg)
{
uint8_t cmd = CMD_JDEC_ID;
uint8_t jdec[3];
/* JDEC: 3 bytes */
sst25_ll_transfer(cfg, &cmd, 1, jdec, sizeof(jdec));
return (jdec[0] << 16) | (jdec[1] << 8) | jdec[2];
}
/**
* @brief prepare command with address
* @notapi
*/
static void sst25_ll_prepare_cmd(uint8_t *buff, uint8_t cmd, uint32_t addr)
{
buff[0] = cmd;
buff[1] = (addr >> 16) & 0xff;
buff[2] = (addr >> 8) & 0xff;
buff[3] = addr & 0xff;
}
#ifdef SST25_SLOW_READ
/**
* @brief Normal read (F_clk < 25 MHz)
* @notapi
*/
static void sst25_ll_read(const SST25Config *cfg, uint32_t addr,
uint8_t *buffer, uint32_t nbytes)
{
uint8_t cmd[4];
sst25_ll_prepare_cmd(cmd, CMD_READ, addr);
sst25_ll_transfer(cfg, cmd, sizeof(cmd), buffer, nbytes);
}
#endif /* SST25_SLOW_READ */
#ifdef SST25_FAST_READ
/**
* @brief Fast read (F_clk < 80 MHz)
* @notapi
*/
static void sst25_ll_fast_read(const SST25Config *cfg, uint32_t addr,
uint8_t *buffer, uint32_t nbytes)
{
uint8_t cmd[5];
sst25_ll_prepare_cmd(cmd, CMD_FAST_READ, addr);
cmd[4] = 0xa5; /* dummy byte */
sst25_ll_transfer(cfg, cmd, sizeof(cmd), buffer, nbytes);
}
#endif /* SST25_FAST_READ */
/**
* @brief Set/Reset write lock
* @notapi
*/
static void sst25_ll_wrlock(const SST25Config *cfg, bool lock)
{
uint8_t cmd = (lock)? CMD_WRDI : CMD_WREN;
sst25_ll_transfer(cfg, &cmd, 1, NULL, 0);
}
/**
* @brief Enables/Disables SO as hw busy pin
* @notapi
*/
static void sst25_ll_hw_busy(const SST25Config *cfg, bool enable)
{
uint8_t cmd = (enable)? CMD_EBSY : CMD_DBSY;
sst25_ll_transfer(cfg, &cmd, 1, NULL, 0);
}
#ifdef SST25_SLOW_WRITE
/**
* @brief Slow write (one byte per cycle)
* @return HAL_FAILED if timeout occurs
* @notapi
*/
static bool sst25_ll_write_byte(const SST25Config *cfg, uint32_t addr,
const uint8_t *buffer, uint32_t nbytes)
{
uint8_t cmd[5];
bool ret;
for (; nbytes > 0; nbytes--, buffer++, addr++) {
/* skip bytes equal to erased state */
if (*buffer == 0xff)
continue;
sst25_ll_prepare_cmd(cmd, CMD_BYTE_PROG, addr);
cmd[4] = *buffer;
sst25_ll_wrlock(cfg, false);
sst25_ll_transfer(cfg, cmd, sizeof(cmd), NULL, 0);
ret = sst25_ll_wait_complete(cfg, FLASH_TIMEOUT);
sst25_ll_wrlock(cfg, true);
if (ret == HAL_FAILED)
break;
}
return ret;
}
#endif /* SST25_SLOW_WRITE */
#ifdef SST25_FAST_WRITE
/**
* @brief Fast write (word per cycle)
* Based on sst25.c mtd driver from NuttX
*
* @return HAL_FAILED if timeout occurs
* @notapi
*/
static bool sst25_ll_write_word(const SST25Config *cfg, uint32_t addr,
const uint8_t *buff, uint32_t nbytes)
{
uint32_t nwords = (nbytes + 1) / 2;
uint8_t cmd[4];
while (nwords > 0) {
/* skip words equal to erased state */
while (nwords > 0 && buff[0] == 0xff && buff[1] == 0xff) {
nwords--;
addr += 2;
buff += 2;
}
if (nwords == 0)
return HAL_SUCCESS; /* all data written */
sst25_ll_prepare_cmd(cmd, CMD_AAI_WORD_PROG, addr);
sst25_ll_wrlock(cfg, false);
spiAcquireBus(cfg->spip);
spiStart(cfg->spip, cfg->spicfg);
spiSelect(cfg->spip);
spiSend(cfg->spip, sizeof(cmd), cmd);
spiSend(cfg->spip, 2, buff);
spiUnselect(cfg->spip);
spiReleaseBus(cfg->spip);
if (sst25_ll_wait_complete(cfg, FLASH_TIMEOUT) == HAL_FAILED) {
sst25_ll_wrlock(cfg, true);
return HAL_FAILED;
}
nwords--;
addr += 2;
buff += 2;
/* write 16-bit cunks */
while (nwords > 0 && (buff[0] != 0xff && buff[1] != 0xff)) {
spiAcquireBus(cfg->spip);
spiStart(cfg->spip, cfg->spicfg);
spiSelect(cfg->spip);
spiSend(cfg->spip, 1, cmd); /* CMD_AAI_WORD_PROG */
spiSend(cfg->spip, 2, buff);
spiUnselect(cfg->spip);
spiReleaseBus(cfg->spip);
if (sst25_ll_wait_complete(cfg, FLASH_TIMEOUT) == HAL_FAILED) {
sst25_ll_wrlock(cfg, true);
return HAL_FAILED;
}
nwords--;
addr += 2;
buff += 2;
}
sst25_ll_wrlock(cfg, true);
}
return HAL_SUCCESS;
}
#endif /* SST25_FAST_WRITE */
static bool sst25_ll_chip_erase(const SST25Config *cfg)
{
uint8_t cmd = CMD_CHIP_ERASE;
bool ret;
sst25_ll_wrlock(cfg, false);
sst25_ll_transfer(cfg, &cmd, 1, NULL, 0);
ret = sst25_ll_wait_complete(cfg, ERASE_TIMEOUT);
sst25_ll_wrlock(cfg, true);
return ret;
}
static bool sst25_ll_erase_block(const SST25Config *cfg, uint32_t addr)
{
uint8_t cmd[4];
bool ret;
sst25_ll_prepare_cmd(cmd, CMD_ERASE_4K, addr);
sst25_ll_wrlock(cfg, false);
sst25_ll_transfer(cfg, cmd, sizeof(cmd), NULL, 0);
ret = sst25_ll_wait_complete(cfg, ERASE_TIMEOUT);
sst25_ll_wrlock(cfg, true);
return ret;
}
/*
* VMT functions
*/
/**
* @brief for unused fields of VMT
* @notapi
*/
static bool sst25_vmt_nop(void *instance __attribute__((unused)))
{
return HAL_SUCCESS;
}
/**
* @brief probe flash chip
* Select page/erase/size of chip
* @api
*/
static bool sst25_connect(SST25Driver *inst)
{
const struct sst25_ll_info *ptbl;
inst->state = BLK_CONNECTING;
inst->jdec_id = sst25_ll_get_jdec_id(inst->config);
for (ptbl = sst25_ll_info_table;
ptbl < (sst25_ll_info_table + ARRAY_SIZE(sst25_ll_info_table));
ptbl++)
if (ptbl->jdec_id == inst->jdec_id) {
inst->state = BLK_ACTIVE;
inst->name = ptbl->name;
inst->page_size = ptbl->page_size;
inst->erase_size = ptbl->erase_size;
inst->nr_pages = ptbl->nr_pages;
/* disable write protection BP[0..3] = 0 */
sst25_ll_hw_busy(inst->config, false);
sst25_ll_wrsr(inst->config, 0);
MTD_INFO("sst25: %s: %" PRIu16 " * %" PRIu32 " erase: %" PRIu16 ", total %lu kB",
mtdGetName(inst),
inst->page_size, inst->nr_pages, inst->erase_size,
mtdGetSize(inst) / 1024);
return HAL_SUCCESS;
}
inst->state = BLK_STOP;
MTD_DEBUG("sst25: connection failed: JDEC ID 0x%06" PRIu32 "x", inst->jdec_id);
return HAL_FAILED;
}
/**
* @brief read blocks from flash
* @api
*/
static bool sst25_read(SST25Driver *inst, uint32_t startblk,
uint8_t *buffer, uint32_t n)
{
startblk += inst->start_page;
uint32_t addr = startblk * inst->page_size;
uint32_t nbytes = n * inst->page_size;
osalDbgCheck(inst->state == BLK_ACTIVE);
if (n > inst->nr_pages) {
MTD_DEBUG("sst25: %s: read oversize (%" PRIu32 ")", mtdGetName(inst), n);
return HAL_FAILED;
}
#ifdef SST25_SLOW_READ
sst25_ll_read(inst->config, addr, buffer, nbytes);
#else /* SST25_FAST_READ */
sst25_ll_fast_read(inst->config, addr, buffer, nbytes);
#endif
return HAL_SUCCESS;
}
/**
* @brief writes blocks to flash
* @api
*/
static bool sst25_write(SST25Driver *inst, uint32_t startblk,
const uint8_t *buffer, uint32_t n)
{
startblk += inst->start_page;
uint32_t addr = startblk * inst->page_size;
uint32_t nbytes = n * inst->page_size;
osalDbgCheck(inst->state == BLK_ACTIVE);
if (n > inst->nr_pages) {
MTD_DEBUG("sst25: %s: write oversize (%" PRIu32 ")", mtdGetName(inst), n);
return HAL_FAILED;
}
#ifdef SST25_SLOW_WRITE
return sst25_ll_write_byte(inst->config, addr, buffer, nbytes);
#else /* SST25_FAST_WRITE */
return sst25_ll_write_word(inst->config, addr, buffer, nbytes);
#endif
}
/**
* @brief erase blocks on flash
* If startblk is 0 and n more than chip capacity then erases whole chip.
*
* @param[in] startblk start block number
* @param[in] n block count (must be equal to erase size, eg. for 4096 es, 256 ps -> n % 4096/256)
* @api
*/
static bool sst25_erase(SST25Driver *inst, uint32_t startblk, uint32_t n)
{
uint32_t addr;
uint32_t nblocks;
bool ret = HAL_FAILED;
osalDbgCheck(inst->state == BLK_ACTIVE);
startblk += inst->start_page;
if (startblk == 0 && n >= inst->nr_pages && inst->parent == NULL) {
MTD_DEBUG("sst25: %s: perform chip erase", mtdGetName(inst));
return sst25_ll_chip_erase(inst->config);
}
/* for partition erase */
if (n > inst->nr_pages)
n = inst->nr_pages;
MTD_DEBUG("sst25: %s: erase [%" PRIu32 "..%" PRIu32 "], %" PRIu32 " pages", mtdGetName(inst),
startblk - inst->start_page,
startblk - inst->start_page + n,
n);
osalDbgAssert((n % (inst->erase_size / inst->page_size)) == 0,
"invalid size");
addr = startblk * inst->page_size;
nblocks = (n + 1) / (inst->erase_size / inst->page_size);
for (; nblocks > 0; nblocks--, addr += inst->erase_size) {
ret = sst25_ll_erase_block(inst->config, addr);
if (ret == HAL_FAILED)
break;
}
return ret;
}
/**
* @brief Get block device info (page size and noumber of pages)
* @api
*/
static bool sst25_get_info(SST25Driver *inst, BlockDeviceInfo *bdip)
{
if (inst->state != BLK_ACTIVE)
return HAL_FAILED;
bdip->blk_size = inst->page_size;
bdip->blk_num = inst->nr_pages;
return HAL_SUCCESS;
}
static const struct BaseMTDDriverVMT sst25_vmt = {
.is_inserted = sst25_vmt_nop,
.is_protected = sst25_vmt_nop,
.connect = (bool (*)(void*)) sst25_connect,
.disconnect = sst25_vmt_nop,
.read = (bool (*)(void*, uint32_t, uint8_t*, uint32_t)) sst25_read,
.write = (bool (*)(void*, uint32_t, const uint8_t*, uint32_t)) sst25_write,
.sync = sst25_vmt_nop,
.get_info = (bool (*)(void*, BlockDeviceInfo*)) sst25_get_info,
.erase = (bool (*)(void*, uint32_t, uint32_t)) sst25_erase
};
/*
* public interface
*/
/**
* @brief SST25 driver initialization.
*
* @init
*/
void sst25Init(void)
{
}
/**
* @brief Initializes an instance.
*
* @init
*/
void sst25ObjectInit(SST25Driver *flp)
{
osalDbgCheck(flp != NULL);
flp->vmt = &sst25_vmt;
flp->config = NULL;
flp->parent = NULL;
flp->state = BLK_STOP;
flp->jdec_id = 0;
flp->page_size = 0;
flp->erase_size = 0;
flp->nr_pages = 0;
flp->start_page = 0;
}
/**
* @brief start flash device
* @api
*/
void sst25Start(SST25Driver *flp, const SST25Config *cfg)
{
osalDbgCheck((flp != NULL) && (cfg != NULL));
osalDbgAssert((flp->state == BLK_STOP) || (flp->state == BLK_ACTIVE),
"invalid state");
flp->config = cfg;
//flp->state = BLK_ACTIVE;
}
/**
* @brief stops device
* @api
*/
void sst25Stop(SST25Driver *flp)
{
osalDbgCheck(flp != NULL);
osalDbgAssert((flp->state == BLK_STOP) || (flp->state == BLK_ACTIVE),
"invalid state");
spiStop(flp->config->spip);
flp->state = BLK_STOP;
}
/**
* @brief init partition
* @api
*/
void sst25InitPartition(SST25Driver *flp, SST25Driver *part_flp, const struct mtd_partition *part_def)
{
osalDbgCheck(flp != NULL);
osalDbgCheck(part_flp != NULL);
osalDbgAssert((flp->state == BLK_ACTIVE),
"invalid state");
#define FLP_COPY(field) (part_flp->field) = (flp->field)
FLP_COPY(vmt);
FLP_COPY(config);
FLP_COPY(state);
FLP_COPY(page_size);
FLP_COPY(erase_size);
FLP_COPY(jdec_id);
part_flp->name = part_def->name;
part_flp->parent = flp;
part_flp->start_page = part_def->start_page;
part_flp->nr_pages = part_def->nr_pages;
if (part_flp->nr_pages > flp->nr_pages)
part_flp->nr_pages = flp->nr_pages - part_def->start_page;
MTD_INFO("sst25: %s/%s: [%" PRIu32 "..%" PRIu32 "] %" PRIu32 " pages, total %lu kB",
mtdGetName(flp), mtdGetName(part_flp),
part_flp->start_page,
part_flp->start_page + part_flp->nr_pages,
part_flp->nr_pages,
mtdGetSize(part_flp) / 1024);
}
/**
* @brief init partitons from table
* @api
*/
void sst25InitPartitionTable(SST25Driver *flp, const struct sst25_partition *part_defs)
{
const struct sst25_partition *ptbl = NULL;
osalDbgCheck(flp != NULL);
osalDbgCheck(part_defs != NULL);
for (ptbl = part_defs; ptbl->partp != NULL; ptbl++)
sst25InitPartition(flp, ptbl->partp, &(ptbl->definition));
}