blob: d7a57ccb5c5febe400eb6133b477ccab94273e48 [file] [log] [blame]
/*
* 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"
);