| /* |
| * Command for accessing SPI flash. |
| * |
| * Copyright (C) 2008 Atmel Corporation |
| * |
| * SPDX-License-Identifier: GPL-2.0+ |
| */ |
| |
| #include <common.h> |
| #include <malloc.h> |
| #include <spi.h> |
| #include <spi_flash_chip.h> |
| //#include <spi_nand.h> |
| #include <asm/io.h> |
| #include <linux/mtd/mtd.h> |
| |
| #ifndef CONFIG_SF_DEFAULT_SPEED |
| # define CONFIG_SF_DEFAULT_SPEED 1000000 |
| #endif |
| #ifndef CONFIG_SF_DEFAULT_MODE |
| # define CONFIG_SF_DEFAULT_MODE SPI_MODE_3 |
| #endif |
| #ifndef CONFIG_SF_DEFAULT_CS |
| # define CONFIG_SF_DEFAULT_CS 0 |
| #endif |
| #ifndef CONFIG_SF_DEFAULT_BUS |
| # define CONFIG_SF_DEFAULT_BUS 0 |
| #endif |
| |
| static int bit_count(u_char n) |
| { |
| u_char c = 0; |
| |
| for (c = 0; n; ++c) |
| n &= (n - 1); |
| return c ; |
| } |
| |
| static int raw_access(struct spi_flash_chip *chip, const char *addr, |
| loff_t off, ulong count, int read) |
| { |
| struct mtd_info *mtd = chip->mtd; |
| int ret = 0; |
| |
| while (count--) { |
| /* Raw access */ |
| struct mtd_oob_ops ops = { |
| .datbuf = (u8 *)addr, |
| .oobbuf = ((u8 *)addr) + mtd->writesize, |
| .len = mtd->writesize, |
| .ooblen = mtd->oobsize, |
| .mode = MTD_OPS_RAW, |
| }; |
| |
| if (read) |
| ret = mtd_read_oob(mtd, off, &ops); |
| else |
| ret = mtd_write_oob(mtd, off, &ops); |
| |
| if (ret) { |
| printf("%s: error at offset %llx, ret %d\n", |
| __func__, (long long)off, ret); |
| break; |
| } |
| |
| addr += mtd->writesize + mtd->oobsize; |
| off += mtd->writesize; |
| } |
| |
| return ret; |
| } |
| |
| /* |
| * This function computes the length argument for the erase command. |
| * The length on which the command is to operate can be given in two forms: |
| * 1. <cmd> offset len - operate on <'offset', 'len') |
| * 2. <cmd> offset +len - operate on <'offset', 'round_up(len)') |
| * If the second form is used and the length doesn't fall on the |
| * sector boundary, than it will be adjusted to the next sector boundary. |
| * If it isn't in the flash, the function will fail (return -1). |
| * Input: |
| * arg: length specification (i.e. both command arguments) |
| * Output: |
| * len: computed length for operation |
| * Return: |
| * 1: success |
| * -1: failure (bad format, bad address). |
| */ |
| static int sf_parse_len_arg(struct spi_flash_chip *chip, char *arg, ulong *len) |
| { |
| struct mtd_info *mtd = chip->mtd; |
| char *ep; |
| char round_up_len; /* indicates if the "+length" form used */ |
| ulong len_arg; |
| |
| round_up_len = 0; |
| if (*arg == '+') { |
| round_up_len = 1; |
| ++arg; |
| } |
| |
| len_arg = simple_strtoul(arg, &ep, 16); |
| if (ep == arg || *ep != '\0') |
| return -1; |
| |
| if (round_up_len && mtd->erasesize > 0) |
| *len = ROUND(len_arg, mtd->erasesize); |
| else |
| *len = len_arg; |
| |
| return 1; |
| } |
| |
| static int spi_flash_dump(struct spi_flash_chip *chip, ulong off, |
| int only_oob, int repeat) |
| { |
| struct mtd_info *mtd = chip->mtd; |
| struct mtd_oob_ops ops; |
| int i; |
| u_char *datbuf, *oobbuf, *p; |
| static loff_t last; |
| int ret = 0; |
| size_t retlen; |
| |
| if (repeat) |
| off = last + mtd->writesize; |
| |
| last = off; |
| |
| datbuf = memalign(ARCH_DMA_MINALIGN, mtd->writesize); |
| if (!datbuf) { |
| puts("No memory for page buffer\n"); |
| return 1; |
| } |
| |
| if (mtd->oobsize) { |
| oobbuf = memalign(ARCH_DMA_MINALIGN, mtd->oobsize); |
| if (!oobbuf) { |
| puts("No memory for page buffer\n"); |
| ret = 1; |
| goto free_dat; |
| } |
| } |
| |
| off &= ~(mtd->writesize - 1); |
| loff_t addr = (loff_t) off; |
| if (mtd->oobsize) { |
| memset(&ops, 0, sizeof(ops)); |
| ops.datbuf = datbuf; |
| ops.oobbuf = oobbuf; |
| ops.len = mtd->writesize; |
| ops.ooblen = mtd->oobsize; |
| ops.mode = MTD_OPS_RAW; |
| i = mtd_read_oob(mtd, addr, &ops); |
| if (i < 0 && i != -EUCLEAN) { |
| printf("Error (%d) reading page %08lx\n", i, off); |
| ret = 1; |
| goto free_all; |
| } |
| } else { |
| ret = mtd_read(mtd, addr, mtd->writesize, &retlen, datbuf); |
| if ((ret < 0 && ret != -EUCLEAN) || retlen != mtd->writesize) |
| return -1; |
| } |
| printf("Page %08lx dump:\n", off); |
| |
| if (!only_oob) { |
| i = mtd->writesize >> 4; |
| p = datbuf; |
| |
| while (i--) { |
| printf("\t%02x %02x %02x %02x %02x %02x %02x %02x" |
| " %02x %02x %02x %02x %02x %02x %02x %02x\n", |
| p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], |
| p[8], p[9], p[10], p[11], p[12], p[13], p[14], |
| p[15]); |
| p += 16; |
| } |
| } |
| |
| if (mtd->oobsize) { |
| puts("OOB:\n"); |
| i = mtd->oobsize >> 3; |
| p = oobbuf; |
| while (i--) { |
| printf("\t%02x %02x %02x %02x %02x %02x %02x %02x\n", |
| p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); |
| p += 8; |
| } |
| } |
| |
| free_all: |
| if (mtd->oobsize) |
| free(oobbuf); |
| free_dat: |
| free(datbuf); |
| |
| return ret; |
| } |
| |
| static int spi_flash_mark_biterr(struct spi_flash_chip *chip, ulong off, int bad_ct) |
| { |
| struct mtd_info *mtd = chip->mtd; |
| int i, j, k, m, bit_ct; |
| u_char *datbuf, *oobbuf, *p; |
| int ret = 0; |
| |
| datbuf = memalign(ARCH_DMA_MINALIGN, mtd->writesize); |
| if (!datbuf) { |
| puts("No memory for page buffer\n"); |
| return 1; |
| } |
| |
| oobbuf = memalign(ARCH_DMA_MINALIGN, mtd->oobsize); |
| if (!oobbuf) { |
| puts("No memory for page buffer\n"); |
| ret = 1; |
| goto free_dat; |
| } |
| off &= ~(mtd->writesize - 1); |
| loff_t addr = (loff_t) off; |
| struct mtd_oob_ops ops; |
| memset(&ops, 0, sizeof(ops)); |
| ops.datbuf = datbuf; |
| ops.oobbuf = oobbuf; |
| ops.len = mtd->writesize; |
| ops.ooblen = mtd->oobsize; |
| ops.mode = MTD_OPS_RAW; |
| ret = mtd_read_oob(mtd, addr, &ops); |
| if (ret < 0 && ret != -EUCLEAN) { |
| printf("Error (%d) reading page %08lx\n", i, off); |
| ret = 1; |
| goto free_all; |
| } |
| |
| i = 0; |
| bit_ct = 0; |
| while (1) { |
| j = bit_count(datbuf[i]); |
| if (bit_ct + j <= bad_ct) { |
| datbuf[i] = 0; |
| } else { |
| j = bad_ct -bit_ct; |
| m = k = 0; |
| while (1) { |
| if (datbuf[i] & (1 << k)) { |
| datbuf[i] &= ~(1 << k); |
| if (++m >= j) |
| break; |
| } |
| k++; |
| } |
| } |
| |
| bit_ct += j; |
| if (bit_ct >= bad_ct) |
| break; |
| |
| if (++i >= mtd->writesize) { |
| printf("No enough bit set in this page\n"); |
| ret = 1; |
| goto free_all; |
| } |
| } |
| |
| ret = mtd_write_oob(mtd, addr, &ops); |
| if (ret) { |
| printf("%s: write error at offset %llx, ret %d\n", |
| __func__, (long long)addr, ret); |
| ret = 1; |
| } |
| |
| free_all: |
| free(oobbuf); |
| free_dat: |
| free(datbuf); |
| |
| return ret; |
| } |
| |
| struct spi_flash_chip *spi_flash_chip_probe(int dev) |
| { |
| struct spi_flash_chip *chip; |
| int ret; |
| |
| ret = spi_flash_select_dev(dev); |
| if (ret) |
| chip = NULL; |
| else |
| chip = cur_sf_chip; |
| return chip; |
| } |
| |
| void spi_flash_chip_free(struct spi_flash_chip *chip) |
| { |
| } |
| |
| |
| int spi_flash_cmd_write_yaffs(struct spi_flash_chip *chip, u32 offset, |
| size_t len, const void *buf) |
| { |
| struct mtd_info *mtd = chip->mtd; |
| size_t retlen; |
| size_t leftlen = len; |
| size_t writelen; |
| size_t block_len, block_off; |
| u_char *p_buffer = buf; |
| loff_t block_start; |
| u32 writeoffset; |
| int ret = 0; |
| bool end; |
| |
| while (leftlen > 0) { |
| if (offset >= chip->size) |
| return -1; |
| |
| writeoffset = offset; |
| writelen = 0; |
| end = false; |
| if (mtd->type == MTD_NANDFLASH) { |
| while ((writelen < leftlen) && !end) { |
| block_start = offset & ~(loff_t)(mtd->erasesize - 1); |
| block_off = offset & (mtd->erasesize - 1); |
| block_len = mtd->erasesize - block_off; |
| |
| if (!mtd_block_isbad(mtd, block_start)) |
| writelen += block_len; |
| else |
| end = true; |
| offset += block_len; |
| } |
| } else |
| writelen = leftlen; |
| |
| if (writelen) { |
| int page, pages; |
| size_t pagesize = mtd->writesize; |
| size_t pagesize_oob = pagesize + mtd->oobsize; |
| struct mtd_oob_ops ops; |
| |
| writelen = min(writelen, leftlen); |
| |
| ops.len = pagesize; |
| ops.ooblen = mtd->oobsize; |
| ops.mode = MTD_OPS_AUTO_OOB; |
| ops.ooboffs = 0; |
| |
| pages = writelen / pagesize_oob; |
| for (page = 0; page < pages; page++) { |
| ops.datbuf = p_buffer; |
| ops.oobbuf = ops.datbuf + pagesize; |
| |
| ret = mtd_write_oob(mtd, writeoffset, &ops); |
| if (ret != 0) |
| break; |
| |
| writeoffset += pagesize; |
| p_buffer += pagesize_oob; |
| } |
| |
| leftlen -= writelen; |
| } |
| } |
| |
| return ret; |
| } |
| |
| |
| int spi_flash_cmd_write_ops(struct spi_flash_chip *chip, u32 offset, |
| size_t len, const void *buf) |
| { |
| struct mtd_info *mtd = chip->mtd; |
| size_t retlen; |
| size_t leftlen = len; |
| size_t writelen; |
| size_t block_len, block_off; |
| loff_t block_start; |
| u32 writeoffset; |
| int ret = 0; |
| bool end; |
| |
| while (leftlen > 0) { |
| if (offset >= chip->size) |
| return -1; |
| |
| writeoffset = offset; |
| writelen = 0; |
| end = false; |
| if (mtd->type == MTD_NANDFLASH) { |
| while ((writelen < leftlen) && !end) { |
| block_start = offset & ~(loff_t)(mtd->erasesize - 1); |
| block_off = offset & (mtd->erasesize - 1); |
| block_len = mtd->erasesize - block_off; |
| |
| if (!mtd_block_isbad(mtd, block_start)) |
| writelen += block_len; |
| else |
| end = true; |
| offset += block_len; |
| } |
| } else |
| writelen = leftlen; |
| |
| if (writelen) { |
| writelen = min(writelen, leftlen); |
| ret = mtd_write(mtd, writeoffset, writelen, &retlen, buf + (len - leftlen)); |
| if (ret || writelen != retlen) |
| return -1; |
| leftlen -= writelen; |
| } |
| } |
| |
| return ret; |
| } |
| |
| int spi_flash_cmd_erase_ops(struct spi_flash_chip *chip, u32 offset, size_t len, bool spread) |
| { |
| struct mtd_info *mtd = chip->mtd; |
| struct erase_info instr = { |
| .callback = NULL, |
| }; |
| size_t leftlen; |
| size_t eraselen; |
| u32 eraseoffset; |
| u64 endaddr; |
| int ret = 0; |
| bool end; |
| |
| if (offset & (mtd->erasesize - 1)) { |
| printf("%s: Unaligned address\n", __func__); |
| return -EINVAL; |
| } |
| |
| if (len & (mtd->erasesize - 1)) { |
| printf("%s: warn: len=0x%x not block aligned\n", __func__, len); |
| len = roundup(len, mtd->erasesize); |
| } |
| |
| leftlen = len; |
| endaddr = offset + len; |
| if (endaddr > chip->size) { |
| printf("%s: Erase past end of device\n", __func__); |
| return -EINVAL; |
| } |
| |
| while (leftlen > 0) { |
| if (offset >= chip->size) |
| return -1; |
| |
| eraseoffset = offset; |
| eraselen = 0; |
| end = false; |
| if (mtd->type == MTD_NANDFLASH) { |
| while ((eraselen < leftlen) && !end) { |
| if (!mtd_block_isbad(mtd, offset)) |
| eraselen += mtd->erasesize; |
| else |
| end = true; |
| offset += mtd->erasesize; |
| } |
| } else { |
| eraselen = len; |
| offset += len; |
| } |
| if (eraselen) { |
| instr.addr = eraseoffset; |
| instr.len = eraselen; |
| instr.mtd = mtd; |
| ret = mtd_erase(mtd, &instr); |
| if (ret) |
| return -1; |
| } |
| if (spread) |
| leftlen -= eraselen; |
| else |
| leftlen = endaddr - offset; |
| } |
| |
| return ret; |
| } |
| |
| int spi_flash_cmd_read_ops(struct spi_flash_chip *chip, u32 offset, |
| size_t len, void *data) |
| { |
| struct mtd_info *mtd = chip->mtd; |
| size_t retlen; |
| size_t leftlen = len; |
| size_t readlen; |
| size_t block_len, block_off; |
| loff_t block_start; |
| u32 readoffset; |
| int ret = 0; |
| bool end; |
| |
| while (leftlen > 0) { |
| if (offset >= chip->size) |
| return -1; |
| |
| readoffset = offset; |
| readlen = 0; |
| end = false; |
| if (mtd->type == MTD_NANDFLASH) { |
| while ((readlen < leftlen) && !end) { |
| block_start = offset & ~(loff_t)(mtd->erasesize - 1); |
| block_off = offset & (mtd->erasesize - 1); |
| block_len = mtd->erasesize - block_off; |
| |
| if (!mtd_block_isbad(mtd, block_start)) |
| readlen += block_len; |
| else |
| end = true; |
| offset += block_len; |
| } |
| } else |
| readlen = leftlen; |
| if (readlen) { |
| readlen = min(readlen, leftlen); |
| ret = mtd_read(mtd, readoffset, readlen, &retlen, data + (len - leftlen)); |
| if ((ret < 0 && ret != -EUCLEAN) || readlen != retlen) |
| return -1; |
| |
| leftlen -= readlen; |
| } |
| } |
| return ret; |
| } |
| |
| /** |
| * Update an area of SPI flash by erasing and writing any blocks which need |
| * to change. Existing blocks with the correct data are left unchanged. |
| * |
| * @param flash flash context pointer |
| * @param offset flash offset to write |
| * @param len number of bytes to write |
| * @param buf buffer to write from |
| * @return 0 if ok, 1 on error |
| */ |
| static int spi_flash_update(struct spi_flash_chip *chip, u32 offset, |
| size_t len, const char *buf) |
| { |
| int ret = 0; |
| |
| ret = spi_flash_cmd_erase_ops(chip, offset, len, true); |
| if (ret) { |
| printf("SPI-FLASH: %zu bytes @ %#x Erased: ERROR\n", |
| (size_t)len, (u32)offset); |
| return ret; |
| } |
| ret = spi_flash_cmd_write_ops(chip, offset, len, buf); |
| if (ret) { |
| printf("SPI-FLASH: %zu bytes @ %#x Written: ERROR\n", |
| (size_t)len, (u32)offset); |
| } |
| return ret; |
| } |
| |
| static int do_spi_flash_get_info(struct spi_flash_chip *chip, |
| int argc, char * const argv[]) |
| { |
| struct mtd_info *mtd = chip->mtd; |
| unsigned long addr; |
| struct spi_flash_info *info; |
| char *endp; |
| |
| if (argc < 2) |
| return -1; |
| |
| addr = simple_strtoul(argv[1], &endp, 16); |
| if (*argv[1] == 0 || *endp != 0) |
| return -1; |
| |
| info = (struct spi_flash_info *)addr; |
| info->totalsize = chip->size; |
| info->erasesize = mtd->erasesize; |
| info->pagesize = mtd->writesize; |
| |
| #if 0 |
| printf("flash info: totalsize=0x%x erase_size=0x%x page_size=0x%x\n", |
| (unsigned)info->totalsize, (unsigned)info->erasesize, |
| (unsigned)info->pagesize); |
| #endif |
| return 0; |
| } |
| |
| static int do_spi_flash_read_write(struct spi_flash_chip *chip, |
| int argc, char * const argv[]) |
| { |
| struct mtd_info *mtd = chip->mtd; |
| unsigned long addr; |
| unsigned long offset; |
| unsigned long len; |
| int data_len; |
| void *buf; |
| char *endp; |
| char *cmd, *s; |
| int ret = 1; |
| |
| if (argc < 4) |
| return -1; |
| |
| cmd = argv[0]; |
| addr = simple_strtoul(argv[1], &endp, 16); |
| if (*argv[1] == 0 || *endp != 0) |
| return -1; |
| offset = simple_strtoul(argv[2], &endp, 16); |
| if (*argv[2] == 0 || *endp != 0) |
| return -1; |
| len = simple_strtoul(argv[3], &endp, 16); |
| if (*argv[3] == 0 || *endp != 0) |
| return -1; |
| |
| if (mtd->protect_enabled && |
| ((offset <= mtd->protect_start && |
| offset + len > mtd->protect_start) || |
| (offset >= mtd->protect_start && |
| offset < mtd->protect_end))) { |
| printf("error: protected ared start=0x%x end=0x%x\n", |
| mtd->protect_start, mtd->protect_end); |
| return -EACCES; |
| } |
| |
| data_len = len; |
| if (strcmp(argv[0], "update") == 0) { |
| if (offset + len > chip->size) { |
| printf("ERROR: attempting %s past flash size (%#llx)\n", |
| argv[0], chip->size); |
| return 1; |
| } |
| |
| buf = map_physmem(addr, len, MAP_WRBACK); |
| if (!buf) { |
| puts("Failed to map physical memory\n"); |
| return 1; |
| } |
| |
| ret = spi_flash_update(chip, offset, len, buf); |
| printf("SPI-FLASH: %zu bytes @ %#x Updated: %s\n", |
| (size_t)len, (u32)offset, ret ? "ERROR" : "OK"); |
| } else if (strncmp(argv[0], "read", 4) == 0 || |
| strncmp(argv[0], "write", 5) == 0) { |
| int read; |
| int raw = 0; |
| int yaffs = 0; |
| |
| read = strncmp(argv[0], "read", 4) == 0; |
| s = strchr(cmd, '.'); |
| if (s && !strcmp(s, ".raw")) { |
| raw = 1; |
| |
| if (len * mtd->writesize + offset > chip->size) { |
| puts("ERROR: Offset exceeds device limit\n"); |
| return 1; |
| } |
| |
| data_len = len * (mtd->writesize + mtd->oobsize); |
| } else if (s && !strcmp(s, ".yaffs")) { |
| yaffs = 1; |
| } else { |
| if (offset + len > chip->size) { |
| printf("ERROR: attempting %s past flash size (%#llx)\n", |
| argv[0], chip->size); |
| return 1; |
| } |
| } |
| |
| buf = map_physmem(addr, data_len, MAP_WRBACK); |
| if (!buf) { |
| puts("Failed to map physical memory\n"); |
| return 1; |
| } |
| |
| if (!s) { |
| if (read) |
| ret = spi_flash_cmd_read_ops(chip, offset, |
| len, buf); |
| else |
| ret = spi_flash_cmd_write_ops(chip, offset, |
| len, buf); |
| } else if (raw) { |
| ret = raw_access(chip, buf, offset, len, read); |
| } else if (yaffs) { |
| ret = spi_flash_cmd_write_yaffs(chip, offset, len, buf); |
| } else { |
| printf("Unknown spi_flash command suffix '%s'.\n", s); |
| ret = 1; |
| goto exit; |
| } |
| |
| if (ret == -EUCLEAN) |
| ret = 0; |
| |
| if ( len > 0x8000) |
| printf("SPI-FLASH: %zu %s @ %#x %s: %s\n", (size_t)len, |
| raw ? "pages" : "bytes", (u32)offset, |
| read ? "Read" : "Written", |
| (ret < 0 && ret != -EUCLEAN) ? "ERROR" : "OK"); |
| } |
| exit: |
| unmap_physmem(buf, data_len); |
| return ret == 0 ? 0 : 1; |
| } |
| |
| static int do_spi_flash_erase(struct spi_flash_chip *chip, |
| int argc, char * const argv[], bool spread) |
| { |
| struct mtd_info *mtd = chip->mtd; |
| unsigned long offset; |
| unsigned long len; |
| char *endp; |
| int ret; |
| |
| if (argc < 3) |
| return -1; |
| |
| offset = simple_strtoul(argv[1], &endp, 16); |
| if (*argv[1] == 0 || *endp != 0) |
| return -1; |
| |
| ret = sf_parse_len_arg(chip, argv[2], &len); |
| if (ret != 1) |
| return -1; |
| |
| if (mtd->protect_enabled && |
| ((offset <= mtd->protect_start && |
| offset + len > mtd->protect_start) || |
| (offset >= mtd->protect_start && |
| offset < mtd->protect_end))) { |
| printf("error: protected ared start=0x%x end=0x%x\n", |
| mtd->protect_start, mtd->protect_end); |
| return -EACCES; |
| } |
| |
| /* Consistency checking */ |
| if (offset + len > chip->size) { |
| printf("ERROR: attempting %s past flash size (%#llx)\n", |
| argv[0], chip->size); |
| return 1; |
| } |
| |
| ret = spi_flash_cmd_erase_ops(chip, offset, len, spread); |
| printf("SPI-FLASH: %zu bytes @ %#x Erased: %s\n", |
| (size_t)len, (u32)offset, ret ? "ERROR" : "OK"); |
| |
| return ret == 0 ? 0 : 1; |
| } |
| |
| static int do_spi_flash_show_bad(struct spi_flash_chip *chip) |
| { |
| struct mtd_info *mtd = chip->mtd; |
| loff_t offset; |
| |
| for (offset = 0; offset < chip->size; offset += mtd->erasesize) { |
| if(mtd_block_isbad(chip, offset)) |
| printf("Bad block at 0x%#llx\n", offset); |
| } |
| |
| return 0; |
| } |
| |
| static int do_spi_flash_mark_bad(struct spi_flash_chip *chip, |
| int argc, char * const argv[]) |
| { |
| int ret; |
| loff_t offset; |
| char *endp; |
| |
| if (argc < 2) |
| return -1; |
| |
| offset = simple_strtoul(argv[1], &endp, 16); |
| if (*argv[1] == 0 || *endp != 0) |
| return -1; |
| |
| ret = mtd_block_markbad(chip->mtd, offset); |
| if (!ret) { |
| printf("SPI-FLASH: 0x%#llx marked as bad block\n", offset); |
| } |
| |
| return ret == 0 ? 0 : 1; |
| } |
| |
| static int do_spi_flash_dump(struct spi_flash_chip *chip, |
| int argc, char * const argv[]) |
| { |
| int ret; |
| loff_t offset; |
| char *endp; |
| |
| if (argc < 2) |
| return -1; |
| |
| offset = simple_strtoul(argv[1], &endp, 16); |
| if (*argv[1] == 0 || *endp != 0) |
| return -1; |
| |
| ret = spi_flash_dump(chip, offset, 0, 0); |
| return ret == 0 ? 0 : 1; |
| } |
| |
| static int do_spi_flash_probe(int argc, char * const argv[]) |
| { |
| int dev = 0; |
| char *endp; |
| |
| if (argc >= 2) { |
| dev = simple_strtoul(argv[1], &endp, 16); |
| if (*argv[1] == 0 || *endp != 0) |
| return -1; |
| } |
| |
| return spi_flash_select_dev(dev); |
| } |
| |
| static int do_spi_flash(cmd_tbl_t *cmdtp, int flag, int argc, |
| char * const argv[]) |
| { |
| struct spi_flash_chip *chip; |
| const char *cmd; |
| int ret; |
| |
| /* need at least two arguments */ |
| if (argc < 2) |
| goto usage; |
| |
| cmd = argv[1]; |
| --argc; |
| ++argv; |
| |
| if (strcmp(cmd, "probe") == 0) { |
| ret = do_spi_flash_probe(argc, argv); |
| goto done; |
| } |
| |
| /* The remaining commands require a selected device */ |
| chip = cur_sf_chip; |
| if (!chip) { |
| puts("No SPI flash selected. Please run `spi_flash probe'\n"); |
| return 1; |
| } |
| |
| if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0 || |
| strcmp(cmd, "update") == 0) |
| ret = do_spi_flash_read_write(chip, argc, argv); |
| else if (strcmp(cmd, "erase") == 0) |
| ret = do_spi_flash_erase(chip, argc, argv, false); |
| else if (strcmp(cmd, "erase.spread") == 0) |
| ret = do_spi_flash_erase(chip, argc, argv, true); |
| else if (strcmp(cmd, "info") == 0) |
| ret = do_spi_flash_get_info(chip, argc, argv); |
| else if (strcmp(cmd, "bad") == 0) |
| ret = do_spi_flash_show_bad(chip); |
| else if (strcmp(cmd, "markbad") == 0) |
| ret = do_spi_flash_mark_bad(chip, argc, argv); |
| else if (strcmp(cmd, "dump") == 0) |
| ret = do_spi_flash_dump(chip, argc, argv); |
| else if (strcmp(cmd, "biterr") == 0) { |
| int bad_bit, addr; |
| |
| argc -= 1; |
| argv += 1; |
| |
| if (argc <= 0) |
| goto usage; |
| |
| bad_bit = 1; |
| if (argc > 0) |
| addr = (ulong)simple_strtoul(argv[0], NULL, 16); |
| if (argc >= 2) |
| bad_bit = (ulong)simple_strtoul(argv[1], NULL, 16); |
| |
| ret = spi_flash_mark_biterr(chip, addr, bad_bit); |
| printf(" %s\n", ret ? "Failed" : "Passed"); |
| |
| return ret; |
| } else |
| ret = -1; |
| |
| done: |
| if (ret != -1) |
| return ret; |
| |
| usage: |
| return CMD_RET_USAGE; |
| } |
| |
| |
| U_BOOT_CMD( |
| spi_flash, 5, 1, do_spi_flash, |
| "SPI NAND/NOR flash sub-system", |
| "probe [[bus:]cs] [hz] [mode] - init flash device on given SPI bus\n" |
| " and chip select\n" |
| "spi_flash read addr offset len - read `len' bytes starting at\n" |
| " `offset' to memory at `addr', skipping bad blocks.\n" |
| "spi_flash write addr offset len - write `len' bytes from memory\n" |
| " at `addr' to flash at `offset', skipping bad blocks.\n" |
| "spi_flash erase[.spread] offset [+]len - erase `len' bytes from `offset'\n" |
| " `+len' round up `len' to block size\n" |
| " With '.spread', erase enough for given file size, otherwise,\n" |
| " 'size' includes skipped bad blocks.\n" |
| "spi_flash info addr - get flash info, and save to addr\n" |
| "spi_flash update addr offset len - erase and write `len' bytes from memory\n" |
| " at `addr' to flash at `offset'\n" |
| "spi_flash bad - show bad blocks\n" |
| "spi_flash markbad offset - mark block include `offset' as bad block\n" |
| ); |