diff options
Diffstat (limited to 'drivers/mtd/devices/m25p80.c')
-rw-r--r-- | drivers/mtd/devices/m25p80.c | 126 |
1 files changed, 125 insertions, 1 deletions
diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c index 6d8d265ae91a..53de9f05ad50 100644 --- a/drivers/mtd/devices/m25p80.c +++ b/drivers/mtd/devices/m25p80.c | |||
@@ -44,6 +44,11 @@ | |||
44 | #define OPCODE_SE 0xd8 /* Sector erase (usually 64KiB) */ | 44 | #define OPCODE_SE 0xd8 /* Sector erase (usually 64KiB) */ |
45 | #define OPCODE_RDID 0x9f /* Read JEDEC ID */ | 45 | #define OPCODE_RDID 0x9f /* Read JEDEC ID */ |
46 | 46 | ||
47 | /* Used for SST flashes only. */ | ||
48 | #define OPCODE_BP 0x02 /* Byte program */ | ||
49 | #define OPCODE_WRDI 0x04 /* Write disable */ | ||
50 | #define OPCODE_AAI_WP 0xad /* Auto address increment word program */ | ||
51 | |||
47 | /* Status Register bits. */ | 52 | /* Status Register bits. */ |
48 | #define SR_WIP 1 /* Write in progress */ | 53 | #define SR_WIP 1 /* Write in progress */ |
49 | #define SR_WEL 2 /* Write enable latch */ | 54 | #define SR_WEL 2 /* Write enable latch */ |
@@ -132,6 +137,15 @@ static inline int write_enable(struct m25p *flash) | |||
132 | return spi_write_then_read(flash->spi, &code, 1, NULL, 0); | 137 | return spi_write_then_read(flash->spi, &code, 1, NULL, 0); |
133 | } | 138 | } |
134 | 139 | ||
140 | /* | ||
141 | * Send write disble instruction to the chip. | ||
142 | */ | ||
143 | static inline int write_disable(struct m25p *flash) | ||
144 | { | ||
145 | u8 code = OPCODE_WRDI; | ||
146 | |||
147 | return spi_write_then_read(flash->spi, &code, 1, NULL, 0); | ||
148 | } | ||
135 | 149 | ||
136 | /* | 150 | /* |
137 | * Service routine to read status register until ready, or timeout occurs. | 151 | * Service routine to read status register until ready, or timeout occurs. |
@@ -454,6 +468,111 @@ static int m25p80_write(struct mtd_info *mtd, loff_t to, size_t len, | |||
454 | return 0; | 468 | return 0; |
455 | } | 469 | } |
456 | 470 | ||
471 | static int sst_write(struct mtd_info *mtd, loff_t to, size_t len, | ||
472 | size_t *retlen, const u_char *buf) | ||
473 | { | ||
474 | struct m25p *flash = mtd_to_m25p(mtd); | ||
475 | struct spi_transfer t[2]; | ||
476 | struct spi_message m; | ||
477 | size_t actual; | ||
478 | int cmd_sz, ret; | ||
479 | |||
480 | if (retlen) | ||
481 | *retlen = 0; | ||
482 | |||
483 | /* sanity checks */ | ||
484 | if (!len) | ||
485 | return 0; | ||
486 | |||
487 | if (to + len > flash->mtd.size) | ||
488 | return -EINVAL; | ||
489 | |||
490 | spi_message_init(&m); | ||
491 | memset(t, 0, (sizeof t)); | ||
492 | |||
493 | t[0].tx_buf = flash->command; | ||
494 | t[0].len = CMD_SIZE; | ||
495 | spi_message_add_tail(&t[0], &m); | ||
496 | |||
497 | t[1].tx_buf = buf; | ||
498 | spi_message_add_tail(&t[1], &m); | ||
499 | |||
500 | mutex_lock(&flash->lock); | ||
501 | |||
502 | /* Wait until finished previous write command. */ | ||
503 | ret = wait_till_ready(flash); | ||
504 | if (ret) | ||
505 | goto time_out; | ||
506 | |||
507 | write_enable(flash); | ||
508 | |||
509 | actual = to % 2; | ||
510 | /* Start write from odd address. */ | ||
511 | if (actual) { | ||
512 | flash->command[0] = OPCODE_BP; | ||
513 | flash->command[1] = to >> 16; | ||
514 | flash->command[2] = to >> 8; | ||
515 | flash->command[3] = to; | ||
516 | |||
517 | /* write one byte. */ | ||
518 | t[1].len = 1; | ||
519 | spi_sync(flash->spi, &m); | ||
520 | ret = wait_till_ready(flash); | ||
521 | if (ret) | ||
522 | goto time_out; | ||
523 | *retlen += m.actual_length - CMD_SIZE; | ||
524 | } | ||
525 | to += actual; | ||
526 | |||
527 | flash->command[0] = OPCODE_AAI_WP; | ||
528 | flash->command[1] = to >> 16; | ||
529 | flash->command[2] = to >> 8; | ||
530 | flash->command[3] = to; | ||
531 | |||
532 | /* Write out most of the data here. */ | ||
533 | cmd_sz = CMD_SIZE; | ||
534 | for (; actual < len - 1; actual += 2) { | ||
535 | t[0].len = cmd_sz; | ||
536 | /* write two bytes. */ | ||
537 | t[1].len = 2; | ||
538 | t[1].tx_buf = buf + actual; | ||
539 | |||
540 | spi_sync(flash->spi, &m); | ||
541 | ret = wait_till_ready(flash); | ||
542 | if (ret) | ||
543 | goto time_out; | ||
544 | *retlen += m.actual_length - cmd_sz; | ||
545 | cmd_sz = 1; | ||
546 | to += 2; | ||
547 | } | ||
548 | write_disable(flash); | ||
549 | ret = wait_till_ready(flash); | ||
550 | if (ret) | ||
551 | goto time_out; | ||
552 | |||
553 | /* Write out trailing byte if it exists. */ | ||
554 | if (actual != len) { | ||
555 | write_enable(flash); | ||
556 | flash->command[0] = OPCODE_BP; | ||
557 | flash->command[1] = to >> 16; | ||
558 | flash->command[2] = to >> 8; | ||
559 | flash->command[3] = to; | ||
560 | t[0].len = CMD_SIZE; | ||
561 | t[1].len = 1; | ||
562 | t[1].tx_buf = buf + actual; | ||
563 | |||
564 | spi_sync(flash->spi, &m); | ||
565 | ret = wait_till_ready(flash); | ||
566 | if (ret) | ||
567 | goto time_out; | ||
568 | *retlen += m.actual_length - CMD_SIZE; | ||
569 | write_disable(flash); | ||
570 | } | ||
571 | |||
572 | time_out: | ||
573 | mutex_unlock(&flash->lock); | ||
574 | return ret; | ||
575 | } | ||
457 | 576 | ||
458 | /****************************************************************************/ | 577 | /****************************************************************************/ |
459 | 578 | ||
@@ -670,7 +789,12 @@ static int __devinit m25p_probe(struct spi_device *spi) | |||
670 | flash->mtd.size = info->sector_size * info->n_sectors; | 789 | flash->mtd.size = info->sector_size * info->n_sectors; |
671 | flash->mtd.erase = m25p80_erase; | 790 | flash->mtd.erase = m25p80_erase; |
672 | flash->mtd.read = m25p80_read; | 791 | flash->mtd.read = m25p80_read; |
673 | flash->mtd.write = m25p80_write; | 792 | |
793 | /* sst flash chips use AAI word program */ | ||
794 | if (info->jedec_id >> 16 == 0xbf) | ||
795 | flash->mtd.write = sst_write; | ||
796 | else | ||
797 | flash->mtd.write = m25p80_write; | ||
674 | 798 | ||
675 | /* prefer "small sector" erase if possible */ | 799 | /* prefer "small sector" erase if possible */ |
676 | if (info->flags & SECT_4K) { | 800 | if (info->flags & SECT_4K) { |