mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-10 07:04:28 +00:00
Merge branch 'master' of git://git.denx.de/u-boot-spi
This is the PR for SPI-NAND changes along with few spi changes. [trini: Re-sync changes for ls1012afrwy_qspi*_defconfig] Signed-off-by: Tom Rini <trini@konsulko.com>
This commit is contained in:
commit
592cd5defd
104 changed files with 6002 additions and 1044 deletions
|
@ -299,7 +299,7 @@ F: drivers/i2c/i2c-cdns.c
|
|||
F: drivers/i2c/muxes/pca954x.c
|
||||
F: drivers/i2c/zynq_i2c.c
|
||||
F: drivers/mmc/zynq_sdhci.c
|
||||
F: drivers/mtd/nand/zynq_nand.c
|
||||
F: drivers/mtd/nand/raw/zynq_nand.c
|
||||
F: drivers/net/phy/xilinx_phy.c
|
||||
F: drivers/net/zynq_gem.c
|
||||
F: drivers/serial/serial_zynq.c
|
||||
|
@ -323,7 +323,7 @@ F: drivers/i2c/i2c-cdns.c
|
|||
F: drivers/i2c/muxes/pca954x.c
|
||||
F: drivers/i2c/zynq_i2c.c
|
||||
F: drivers/mmc/zynq_sdhci.c
|
||||
F: drivers/mtd/nand/zynq_nand.c
|
||||
F: drivers/mtd/nand/raw/zynq_nand.c
|
||||
F: drivers/net/phy/xilinx_phy.c
|
||||
F: drivers/net/zynq_gem.c
|
||||
F: drivers/serial/serial_zynq.c
|
||||
|
@ -478,7 +478,7 @@ NAND FLASH
|
|||
#M: Scott Wood <oss@buserror.net>
|
||||
S: Orphaned (Since 2018-07)
|
||||
T: git git://git.denx.de/u-boot-nand-flash.git
|
||||
F: drivers/mtd/nand/
|
||||
F: drivers/mtd/nand/raw/
|
||||
|
||||
NDS32
|
||||
M: Macpaul Lin <macpaul@andestech.com>
|
||||
|
|
2
Makefile
2
Makefile
|
@ -689,7 +689,7 @@ libs-y += drivers/dma/
|
|||
libs-y += drivers/gpio/
|
||||
libs-y += drivers/i2c/
|
||||
libs-y += drivers/mtd/
|
||||
libs-$(CONFIG_CMD_NAND) += drivers/mtd/nand/
|
||||
libs-$(CONFIG_CMD_NAND) += drivers/mtd/nand/raw/
|
||||
libs-y += drivers/mtd/onenand/
|
||||
libs-$(CONFIG_CMD_UBI) += drivers/mtd/ubi/
|
||||
libs-y += drivers/mtd/spi/
|
||||
|
|
6
README
6
README
|
@ -3237,8 +3237,8 @@ Low Level (hardware related) configuration options:
|
|||
a 16 bit bus.
|
||||
Not all NAND drivers use this symbol.
|
||||
Example of drivers that use it:
|
||||
- drivers/mtd/nand/ndfc.c
|
||||
- drivers/mtd/nand/mxc_nand.c
|
||||
- drivers/mtd/nand/raw/ndfc.c
|
||||
- drivers/mtd/nand/raw/mxc_nand.c
|
||||
|
||||
- CONFIG_SYS_NDFC_EBC0_CFG
|
||||
Sets the EBC0_CFG register for the NDFC. If not defined
|
||||
|
@ -3355,7 +3355,7 @@ Low Level (hardware related) configuration options:
|
|||
- CONFIG_SYS_NAND_NO_SUBPAGE_WRITE
|
||||
Option to disable subpage write in NAND driver
|
||||
driver that uses this:
|
||||
drivers/mtd/nand/davinci_nand.c
|
||||
drivers/mtd/nand/raw/davinci_nand.c
|
||||
|
||||
Freescale QE/FMAN Firmware Support:
|
||||
-----------------------------------
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#include <stdio.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/printk.h>
|
||||
#include <../drivers/mtd/nand/denali.h>
|
||||
#include <../drivers/mtd/nand/raw/denali.h>
|
||||
|
||||
#include "init.h"
|
||||
|
||||
|
|
18
cmd/Kconfig
18
cmd/Kconfig
|
@ -866,6 +866,12 @@ config CMD_MMC_SWRITE
|
|||
Enable support for the "mmc swrite" command to write Android sparse
|
||||
images to eMMC.
|
||||
|
||||
config CMD_MTD
|
||||
bool "mtd"
|
||||
select MTD_PARTITIONS
|
||||
help
|
||||
MTD commands support.
|
||||
|
||||
config CMD_NAND
|
||||
bool "nand"
|
||||
default y if NAND_SUNXI
|
||||
|
@ -1714,18 +1720,22 @@ config CMD_MTDPARTS
|
|||
bool "MTD partition support"
|
||||
select MTD_DEVICE if (CMD_NAND || NAND)
|
||||
help
|
||||
MTD partition support
|
||||
MTD partitioning tool support.
|
||||
It is strongly encouraged to avoid using this command
|
||||
anymore along with 'sf', 'nand', 'onenand'. One can still
|
||||
declare the partitions in the mtdparts environment variable
|
||||
but better use the MTD stack and the 'mtd' command instead.
|
||||
|
||||
config MTDIDS_DEFAULT
|
||||
string "Default MTD IDs"
|
||||
depends on CMD_MTDPARTS || CMD_NAND || CMD_FLASH
|
||||
depends on CMD_MTD || CMD_MTDPARTS || CMD_NAND || CMD_FLASH
|
||||
help
|
||||
Defines a default MTD IDs list for use with MTD partitions in the
|
||||
Linux MTD command line partitions format.
|
||||
|
||||
config MTDPARTS_DEFAULT
|
||||
string "Default MTD partition scheme"
|
||||
depends on CMD_MTDPARTS || CMD_NAND || CMD_FLASH
|
||||
depends on CMD_MTD || CMD_MTDPARTS || CMD_NAND || CMD_FLASH
|
||||
help
|
||||
Defines a default MTD partitioning scheme in the Linux MTD command
|
||||
line partitions format
|
||||
|
@ -1855,6 +1865,8 @@ config CMD_UBI
|
|||
capabilities. Please, consult the MTD web site for more details
|
||||
(www.linux-mtd.infradead.org). Activate this option if you want
|
||||
to use U-Boot UBI commands.
|
||||
It is also strongly encouraged to also enable CONFIG_MTD to get full
|
||||
partition support.
|
||||
|
||||
config CMD_UBIFS
|
||||
tristate "Enable UBIFS - Unsorted block images filesystem commands"
|
||||
|
|
|
@ -93,6 +93,7 @@ obj-$(CONFIG_CMD_MISC) += misc.o
|
|||
obj-$(CONFIG_CMD_MMC) += mmc.o
|
||||
obj-$(CONFIG_CMD_MMC_SPI) += mmc_spi.o
|
||||
obj-$(CONFIG_MP) += mp.o
|
||||
obj-$(CONFIG_CMD_MTD) += mtd.o
|
||||
obj-$(CONFIG_CMD_MTDPARTS) += mtdparts.o
|
||||
obj-$(CONFIG_CMD_NAND) += nand.o
|
||||
obj-$(CONFIG_CMD_NET) += net.o
|
||||
|
|
473
cmd/mtd.c
Normal file
473
cmd/mtd.c
Normal file
|
@ -0,0 +1,473 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* mtd.c
|
||||
*
|
||||
* Generic command to handle basic operations on any memory device.
|
||||
*
|
||||
* Copyright: Bootlin, 2018
|
||||
* Author: Miquèl Raynal <miquel.raynal@bootlin.com>
|
||||
*/
|
||||
|
||||
#include <command.h>
|
||||
#include <common.h>
|
||||
#include <console.h>
|
||||
#include <malloc.h>
|
||||
#include <mapmem.h>
|
||||
#include <mtd.h>
|
||||
|
||||
static uint mtd_len_to_pages(struct mtd_info *mtd, u64 len)
|
||||
{
|
||||
do_div(len, mtd->writesize);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static bool mtd_is_aligned_with_min_io_size(struct mtd_info *mtd, u64 size)
|
||||
{
|
||||
return !do_div(size, mtd->writesize);
|
||||
}
|
||||
|
||||
static bool mtd_is_aligned_with_block_size(struct mtd_info *mtd, u64 size)
|
||||
{
|
||||
return !do_div(size, mtd->erasesize);
|
||||
}
|
||||
|
||||
static void mtd_dump_buf(const u8 *buf, uint len, uint offset)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = 0; i < len; ) {
|
||||
printf("0x%08x:\t", offset + i);
|
||||
for (j = 0; j < 8; j++)
|
||||
printf("%02x ", buf[i + j]);
|
||||
printf(" ");
|
||||
i += 8;
|
||||
for (j = 0; j < 8; j++)
|
||||
printf("%02x ", buf[i + j]);
|
||||
printf("\n");
|
||||
i += 8;
|
||||
}
|
||||
}
|
||||
|
||||
static void mtd_dump_device_buf(struct mtd_info *mtd, u64 start_off,
|
||||
const u8 *buf, u64 len, bool woob)
|
||||
{
|
||||
bool has_pages = mtd->type == MTD_NANDFLASH ||
|
||||
mtd->type == MTD_MLCNANDFLASH;
|
||||
int npages = mtd_len_to_pages(mtd, len);
|
||||
uint page;
|
||||
|
||||
if (has_pages) {
|
||||
for (page = 0; page < npages; page++) {
|
||||
u64 data_off = page * mtd->writesize;
|
||||
|
||||
printf("\nDump %d data bytes from 0x%08llx:\n",
|
||||
mtd->writesize, start_off + data_off);
|
||||
mtd_dump_buf(&buf[data_off],
|
||||
mtd->writesize, start_off + data_off);
|
||||
|
||||
if (woob) {
|
||||
u64 oob_off = page * mtd->oobsize;
|
||||
|
||||
printf("Dump %d OOB bytes from page at 0x%08llx:\n",
|
||||
mtd->oobsize, start_off + data_off);
|
||||
mtd_dump_buf(&buf[len + oob_off],
|
||||
mtd->oobsize, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printf("\nDump %lld data bytes from 0x%llx:\n",
|
||||
len, start_off);
|
||||
mtd_dump_buf(buf, len, start_off);
|
||||
}
|
||||
}
|
||||
|
||||
static void mtd_show_parts(struct mtd_info *mtd, int level)
|
||||
{
|
||||
struct mtd_info *part;
|
||||
int i;
|
||||
|
||||
list_for_each_entry(part, &mtd->partitions, node) {
|
||||
for (i = 0; i < level; i++)
|
||||
printf("\t");
|
||||
printf(" - 0x%012llx-0x%012llx : \"%s\"\n",
|
||||
part->offset, part->offset + part->size, part->name);
|
||||
|
||||
mtd_show_parts(part, level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void mtd_show_device(struct mtd_info *mtd)
|
||||
{
|
||||
/* Device */
|
||||
printf("* %s\n", mtd->name);
|
||||
#if defined(CONFIG_DM)
|
||||
if (mtd->dev) {
|
||||
printf(" - device: %s\n", mtd->dev->name);
|
||||
printf(" - parent: %s\n", mtd->dev->parent->name);
|
||||
printf(" - driver: %s\n", mtd->dev->driver->name);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* MTD device information */
|
||||
printf(" - type: ");
|
||||
switch (mtd->type) {
|
||||
case MTD_RAM:
|
||||
printf("RAM\n");
|
||||
break;
|
||||
case MTD_ROM:
|
||||
printf("ROM\n");
|
||||
break;
|
||||
case MTD_NORFLASH:
|
||||
printf("NOR flash\n");
|
||||
break;
|
||||
case MTD_NANDFLASH:
|
||||
printf("NAND flash\n");
|
||||
break;
|
||||
case MTD_DATAFLASH:
|
||||
printf("Data flash\n");
|
||||
break;
|
||||
case MTD_UBIVOLUME:
|
||||
printf("UBI volume\n");
|
||||
break;
|
||||
case MTD_MLCNANDFLASH:
|
||||
printf("MLC NAND flash\n");
|
||||
break;
|
||||
case MTD_ABSENT:
|
||||
default:
|
||||
printf("Unknown\n");
|
||||
break;
|
||||
}
|
||||
|
||||
printf(" - block size: 0x%x bytes\n", mtd->erasesize);
|
||||
printf(" - min I/O: 0x%x bytes\n", mtd->writesize);
|
||||
|
||||
if (mtd->oobsize) {
|
||||
printf(" - OOB size: %u bytes\n", mtd->oobsize);
|
||||
printf(" - OOB available: %u bytes\n", mtd->oobavail);
|
||||
}
|
||||
|
||||
if (mtd->ecc_strength) {
|
||||
printf(" - ECC strength: %u bits\n", mtd->ecc_strength);
|
||||
printf(" - ECC step size: %u bytes\n", mtd->ecc_step_size);
|
||||
printf(" - bitflip threshold: %u bits\n",
|
||||
mtd->bitflip_threshold);
|
||||
}
|
||||
|
||||
printf(" - 0x%012llx-0x%012llx : \"%s\"\n",
|
||||
mtd->offset, mtd->offset + mtd->size, mtd->name);
|
||||
|
||||
/* MTD partitions, if any */
|
||||
mtd_show_parts(mtd, 1);
|
||||
}
|
||||
|
||||
/* Logic taken from fs/ubifs/recovery.c:is_empty() */
|
||||
static bool mtd_oob_write_is_empty(struct mtd_oob_ops *op)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < op->len; i++)
|
||||
if (op->datbuf[i] != 0xff)
|
||||
return false;
|
||||
|
||||
for (i = 0; i < op->ooblen; i++)
|
||||
if (op->oobbuf[i] != 0xff)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int do_mtd_list(void)
|
||||
{
|
||||
struct mtd_info *mtd;
|
||||
int dev_nb = 0;
|
||||
|
||||
/* Ensure all devices (and their partitions) are probed */
|
||||
mtd_probe_devices();
|
||||
|
||||
printf("List of MTD devices:\n");
|
||||
mtd_for_each_device(mtd) {
|
||||
if (!mtd_is_partition(mtd))
|
||||
mtd_show_device(mtd);
|
||||
|
||||
dev_nb++;
|
||||
}
|
||||
|
||||
if (!dev_nb) {
|
||||
printf("No MTD device found\n");
|
||||
return CMD_RET_FAILURE;
|
||||
}
|
||||
|
||||
return CMD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
static int mtd_special_write_oob(struct mtd_info *mtd, u64 off,
|
||||
struct mtd_oob_ops *io_op,
|
||||
bool write_empty_pages, bool woob)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* By default, do not write an empty page.
|
||||
* Skip it by simulating a successful write.
|
||||
*/
|
||||
if (!write_empty_pages && mtd_oob_write_is_empty(io_op)) {
|
||||
io_op->retlen = mtd->writesize;
|
||||
io_op->oobretlen = woob ? mtd->oobsize : 0;
|
||||
} else {
|
||||
ret = mtd_write_oob(mtd, off, io_op);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int do_mtd(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
|
||||
{
|
||||
struct mtd_info *mtd;
|
||||
const char *cmd;
|
||||
char *mtd_name;
|
||||
|
||||
/* All MTD commands need at least two arguments */
|
||||
if (argc < 2)
|
||||
return CMD_RET_USAGE;
|
||||
|
||||
/* Parse the command name and its optional suffixes */
|
||||
cmd = argv[1];
|
||||
|
||||
/* List the MTD devices if that is what the user wants */
|
||||
if (strcmp(cmd, "list") == 0)
|
||||
return do_mtd_list();
|
||||
|
||||
/*
|
||||
* The remaining commands require also at least a device ID.
|
||||
* Check the selected device is valid. Ensure it is probed.
|
||||
*/
|
||||
if (argc < 3)
|
||||
return CMD_RET_USAGE;
|
||||
|
||||
mtd_name = argv[2];
|
||||
mtd_probe_devices();
|
||||
mtd = get_mtd_device_nm(mtd_name);
|
||||
if (IS_ERR_OR_NULL(mtd)) {
|
||||
printf("MTD device %s not found, ret %ld\n",
|
||||
mtd_name, PTR_ERR(mtd));
|
||||
return CMD_RET_FAILURE;
|
||||
}
|
||||
put_mtd_device(mtd);
|
||||
|
||||
argc -= 3;
|
||||
argv += 3;
|
||||
|
||||
/* Do the parsing */
|
||||
if (!strncmp(cmd, "read", 4) || !strncmp(cmd, "dump", 4) ||
|
||||
!strncmp(cmd, "write", 5)) {
|
||||
bool has_pages = mtd->type == MTD_NANDFLASH ||
|
||||
mtd->type == MTD_MLCNANDFLASH;
|
||||
bool dump, read, raw, woob, write_empty_pages;
|
||||
struct mtd_oob_ops io_op = {};
|
||||
uint user_addr = 0, npages;
|
||||
u64 start_off, off, len, remaining, default_len;
|
||||
u32 oob_len;
|
||||
u8 *buf;
|
||||
int ret;
|
||||
|
||||
dump = !strncmp(cmd, "dump", 4);
|
||||
read = dump || !strncmp(cmd, "read", 4);
|
||||
raw = strstr(cmd, ".raw");
|
||||
woob = strstr(cmd, ".oob");
|
||||
write_empty_pages = !has_pages || strstr(cmd, ".dontskipff");
|
||||
|
||||
if (!dump) {
|
||||
if (!argc)
|
||||
return CMD_RET_USAGE;
|
||||
|
||||
user_addr = simple_strtoul(argv[0], NULL, 16);
|
||||
argc--;
|
||||
argv++;
|
||||
}
|
||||
|
||||
start_off = argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0;
|
||||
if (!mtd_is_aligned_with_min_io_size(mtd, start_off)) {
|
||||
printf("Offset not aligned with a page (0x%x)\n",
|
||||
mtd->writesize);
|
||||
return CMD_RET_FAILURE;
|
||||
}
|
||||
|
||||
default_len = dump ? mtd->writesize : mtd->size;
|
||||
len = argc > 1 ? simple_strtoul(argv[1], NULL, 16) :
|
||||
default_len;
|
||||
if (!mtd_is_aligned_with_min_io_size(mtd, len)) {
|
||||
len = round_up(len, mtd->writesize);
|
||||
printf("Size not on a page boundary (0x%x), rounding to 0x%llx\n",
|
||||
mtd->writesize, len);
|
||||
}
|
||||
|
||||
remaining = len;
|
||||
npages = mtd_len_to_pages(mtd, len);
|
||||
oob_len = woob ? npages * mtd->oobsize : 0;
|
||||
|
||||
if (dump)
|
||||
buf = kmalloc(len + oob_len, GFP_KERNEL);
|
||||
else
|
||||
buf = map_sysmem(user_addr, 0);
|
||||
|
||||
if (!buf) {
|
||||
printf("Could not map/allocate the user buffer\n");
|
||||
return CMD_RET_FAILURE;
|
||||
}
|
||||
|
||||
if (has_pages)
|
||||
printf("%s %lld byte(s) (%d page(s)) at offset 0x%08llx%s%s%s\n",
|
||||
read ? "Reading" : "Writing", len, npages, start_off,
|
||||
raw ? " [raw]" : "", woob ? " [oob]" : "",
|
||||
!read && write_empty_pages ? " [dontskipff]" : "");
|
||||
else
|
||||
printf("%s %lld byte(s) at offset 0x%08llx\n",
|
||||
read ? "Reading" : "Writing", len, start_off);
|
||||
|
||||
io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_AUTO_OOB;
|
||||
io_op.len = has_pages ? mtd->writesize : len;
|
||||
io_op.ooblen = woob ? mtd->oobsize : 0;
|
||||
io_op.datbuf = buf;
|
||||
io_op.oobbuf = woob ? &buf[len] : NULL;
|
||||
|
||||
/* Search for the first good block after the given offset */
|
||||
off = start_off;
|
||||
while (mtd_block_isbad(mtd, off))
|
||||
off += mtd->erasesize;
|
||||
|
||||
/* Loop over the pages to do the actual read/write */
|
||||
while (remaining) {
|
||||
/* Skip the block if it is bad */
|
||||
if (mtd_is_aligned_with_block_size(mtd, off) &&
|
||||
mtd_block_isbad(mtd, off)) {
|
||||
off += mtd->erasesize;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read)
|
||||
ret = mtd_read_oob(mtd, off, &io_op);
|
||||
else
|
||||
ret = mtd_special_write_oob(mtd, off, &io_op,
|
||||
write_empty_pages,
|
||||
woob);
|
||||
|
||||
if (ret) {
|
||||
printf("Failure while %s at offset 0x%llx\n",
|
||||
read ? "reading" : "writing", off);
|
||||
return CMD_RET_FAILURE;
|
||||
}
|
||||
|
||||
off += io_op.retlen;
|
||||
remaining -= io_op.retlen;
|
||||
io_op.datbuf += io_op.retlen;
|
||||
io_op.oobbuf += io_op.oobretlen;
|
||||
}
|
||||
|
||||
if (!ret && dump)
|
||||
mtd_dump_device_buf(mtd, start_off, buf, len, woob);
|
||||
|
||||
if (dump)
|
||||
kfree(buf);
|
||||
else
|
||||
unmap_sysmem(buf);
|
||||
|
||||
if (ret) {
|
||||
printf("%s on %s failed with error %d\n",
|
||||
read ? "Read" : "Write", mtd->name, ret);
|
||||
return CMD_RET_FAILURE;
|
||||
}
|
||||
|
||||
} else if (!strcmp(cmd, "erase")) {
|
||||
bool scrub = strstr(cmd, ".dontskipbad");
|
||||
struct erase_info erase_op = {};
|
||||
u64 off, len;
|
||||
int ret;
|
||||
|
||||
off = argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0;
|
||||
len = argc > 1 ? simple_strtoul(argv[1], NULL, 16) : mtd->size;
|
||||
|
||||
if (!mtd_is_aligned_with_block_size(mtd, off)) {
|
||||
printf("Offset not aligned with a block (0x%x)\n",
|
||||
mtd->erasesize);
|
||||
return CMD_RET_FAILURE;
|
||||
}
|
||||
|
||||
if (!mtd_is_aligned_with_block_size(mtd, len)) {
|
||||
printf("Size not a multiple of a block (0x%x)\n",
|
||||
mtd->erasesize);
|
||||
return CMD_RET_FAILURE;
|
||||
}
|
||||
|
||||
printf("Erasing 0x%08llx ... 0x%08llx (%d eraseblock(s))\n",
|
||||
off, off + len - 1, mtd_div_by_eb(len, mtd));
|
||||
|
||||
erase_op.mtd = mtd;
|
||||
erase_op.addr = off;
|
||||
erase_op.len = len;
|
||||
erase_op.scrub = scrub;
|
||||
|
||||
while (erase_op.len) {
|
||||
ret = mtd_erase(mtd, &erase_op);
|
||||
|
||||
/* Abort if its not a bad block error */
|
||||
if (ret != -EIO)
|
||||
break;
|
||||
|
||||
printf("Skipping bad block at 0x%08llx\n",
|
||||
erase_op.fail_addr);
|
||||
|
||||
/* Skip bad block and continue behind it */
|
||||
erase_op.len -= erase_op.fail_addr - erase_op.addr;
|
||||
erase_op.len -= mtd->erasesize;
|
||||
erase_op.addr = erase_op.fail_addr + mtd->erasesize;
|
||||
}
|
||||
|
||||
if (ret && ret != -EIO)
|
||||
return CMD_RET_FAILURE;
|
||||
} else if (!strcmp(cmd, "bad")) {
|
||||
loff_t off;
|
||||
|
||||
if (!mtd_can_have_bb(mtd)) {
|
||||
printf("Only NAND-based devices can have bad blocks\n");
|
||||
return CMD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
printf("MTD device %s bad blocks list:\n", mtd->name);
|
||||
for (off = 0; off < mtd->size; off += mtd->erasesize)
|
||||
if (mtd_block_isbad(mtd, off))
|
||||
printf("\t0x%08llx\n", off);
|
||||
} else {
|
||||
return CMD_RET_USAGE;
|
||||
}
|
||||
|
||||
return CMD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
static char mtd_help_text[] =
|
||||
#ifdef CONFIG_SYS_LONGHELP
|
||||
"- generic operations on memory technology devices\n\n"
|
||||
"mtd list\n"
|
||||
"mtd read[.raw][.oob] <name> <addr> [<off> [<size>]]\n"
|
||||
"mtd dump[.raw][.oob] <name> [<off> [<size>]]\n"
|
||||
"mtd write[.raw][.oob][.dontskipff] <name> <addr> [<off> [<size>]]\n"
|
||||
"mtd erase[.dontskipbad] <name> [<off> [<size>]]\n"
|
||||
"\n"
|
||||
"Specific functions:\n"
|
||||
"mtd bad <name>\n"
|
||||
"\n"
|
||||
"With:\n"
|
||||
"\t<name>: NAND partition/chip name\n"
|
||||
"\t<addr>: user address from/to which data will be retrieved/stored\n"
|
||||
"\t<off>: offset in <name> in bytes (default: start of the part)\n"
|
||||
"\t\t* must be block-aligned for erase\n"
|
||||
"\t\t* must be page-aligned otherwise\n"
|
||||
"\t<size>: length of the operation in bytes (default: the entire device)\n"
|
||||
"\t\t* must be a multiple of a block for erase\n"
|
||||
"\t\t* must be a multiple of a page otherwise (special case: default is a page with dump)\n"
|
||||
"\n"
|
||||
"The .dontskipff option forces writing empty pages, don't use it if unsure.\n"
|
||||
#endif
|
||||
"";
|
||||
|
||||
U_BOOT_CMD(mtd, 10, 1, do_mtd, "MTD utils", mtd_help_text);
|
|
@ -37,14 +37,14 @@
|
|||
* mtdids=<idmap>[,<idmap>,...]
|
||||
*
|
||||
* <idmap> := <dev-id>=<mtd-id>
|
||||
* <dev-id> := 'nand'|'nor'|'onenand'<dev-num>
|
||||
* <dev-id> := 'nand'|'nor'|'onenand'|'spi-nand'<dev-num>
|
||||
* <dev-num> := mtd device number, 0...
|
||||
* <mtd-id> := unique device tag used by linux kernel to find mtd device (mtd->name)
|
||||
*
|
||||
*
|
||||
* 'mtdparts' - partition list
|
||||
*
|
||||
* mtdparts=mtdparts=<mtd-def>[;<mtd-def>...]
|
||||
* mtdparts=[mtdparts=]<mtd-def>[;<mtd-def>...]
|
||||
*
|
||||
* <mtd-def> := <mtd-id>:<part-def>[,<part-def>...]
|
||||
* <mtd-id> := unique device tag used by linux kernel to find mtd device (mtd->name)
|
||||
|
@ -62,11 +62,11 @@
|
|||
*
|
||||
* 1 NOR Flash, with 1 single writable partition:
|
||||
* mtdids=nor0=edb7312-nor
|
||||
* mtdparts=mtdparts=edb7312-nor:-
|
||||
* mtdparts=[mtdparts=]edb7312-nor:-
|
||||
*
|
||||
* 1 NOR Flash with 2 partitions, 1 NAND with one
|
||||
* mtdids=nor0=edb7312-nor,nand0=edb7312-nand
|
||||
* mtdparts=mtdparts=edb7312-nor:256k(ARMboot)ro,-(root);edb7312-nand:-(home)
|
||||
* mtdparts=[mtdparts=]edb7312-nor:256k(ARMboot)ro,-(root);edb7312-nand:-(home)
|
||||
*
|
||||
*/
|
||||
|
||||
|
@ -177,13 +177,16 @@ static u64 memsize_parse (const char *const ptr, const char **retptr)
|
|||
case 'G':
|
||||
case 'g':
|
||||
ret <<= 10;
|
||||
/* Fallthrough */
|
||||
case 'M':
|
||||
case 'm':
|
||||
ret <<= 10;
|
||||
/* Fallthrough */
|
||||
case 'K':
|
||||
case 'k':
|
||||
ret <<= 10;
|
||||
(*retptr)++;
|
||||
/* Fallthrough */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -336,7 +339,7 @@ static int part_validate_eraseblock(struct mtdids *id, struct part_info *part)
|
|||
|
||||
if (!mtd->numeraseregions) {
|
||||
/*
|
||||
* Only one eraseregion (NAND, OneNAND or uniform NOR),
|
||||
* Only one eraseregion (NAND, SPI-NAND, OneNAND or uniform NOR),
|
||||
* checking for alignment is easy here
|
||||
*/
|
||||
offset = part->offset;
|
||||
|
@ -1027,7 +1030,7 @@ static struct mtdids* id_find_by_mtd_id(const char *mtd_id, unsigned int mtd_id_
|
|||
}
|
||||
|
||||
/**
|
||||
* Parse device id string <dev-id> := 'nand'|'nor'|'onenand'<dev-num>,
|
||||
* Parse device id string <dev-id> := 'nand'|'nor'|'onenand'|'spi-nand'<dev-num>,
|
||||
* return device type and number.
|
||||
*
|
||||
* @param id string describing device id
|
||||
|
@ -1051,6 +1054,9 @@ int mtd_id_parse(const char *id, const char **ret_id, u8 *dev_type,
|
|||
} else if (strncmp(p, "onenand", 7) == 0) {
|
||||
*dev_type = MTD_DEV_TYPE_ONENAND;
|
||||
p += 7;
|
||||
} else if (strncmp(p, "spi-nand", 8) == 0) {
|
||||
*dev_type = MTD_DEV_TYPE_SPINAND;
|
||||
p += 8;
|
||||
} else {
|
||||
printf("incorrect device type in %s\n", id);
|
||||
return 1;
|
||||
|
@ -1093,9 +1099,6 @@ static int generate_mtdparts(char *buf, u32 buflen)
|
|||
return 0;
|
||||
}
|
||||
|
||||
strcpy(p, "mtdparts=");
|
||||
p += 9;
|
||||
|
||||
list_for_each(dentry, &devices) {
|
||||
dev = list_entry(dentry, struct mtd_device, link);
|
||||
|
||||
|
@ -1566,11 +1569,9 @@ static int parse_mtdparts(const char *const mtdparts)
|
|||
if (!p)
|
||||
p = mtdparts;
|
||||
|
||||
if (strncmp(p, "mtdparts=", 9) != 0) {
|
||||
printf("mtdparts variable doesn't start with 'mtdparts='\n");
|
||||
return err;
|
||||
}
|
||||
p += 9;
|
||||
/* Skip the useless prefix, if any */
|
||||
if (strncmp(p, "mtdparts=", 9) == 0)
|
||||
p += 9;
|
||||
|
||||
while (*p != '\0') {
|
||||
err = 1;
|
||||
|
@ -1633,7 +1634,7 @@ static int parse_mtdids(const char *const ids)
|
|||
while(p && (*p != '\0')) {
|
||||
|
||||
ret = 1;
|
||||
/* parse 'nor'|'nand'|'onenand'<dev-num> */
|
||||
/* parse 'nor'|'nand'|'onenand'|'spi-nand'<dev-num> */
|
||||
if (mtd_id_parse(p, &p, &type, &num) != 0)
|
||||
break;
|
||||
|
||||
|
@ -2109,7 +2110,7 @@ static char mtdparts_help_text[] =
|
|||
"'mtdids' - linux kernel mtd device id <-> u-boot device id mapping\n\n"
|
||||
"mtdids=<idmap>[,<idmap>,...]\n\n"
|
||||
"<idmap> := <dev-id>=<mtd-id>\n"
|
||||
"<dev-id> := 'nand'|'nor'|'onenand'<dev-num>\n"
|
||||
"<dev-id> := 'nand'|'nor'|'onenand'|'spi-nand'<dev-num>\n"
|
||||
"<dev-num> := mtd device number, 0...\n"
|
||||
"<mtd-id> := unique device tag used by linux kernel to find mtd device (mtd->name)\n\n"
|
||||
"'mtdparts' - partition list\n\n"
|
||||
|
|
101
cmd/ubi.c
101
cmd/ubi.c
|
@ -15,6 +15,7 @@
|
|||
#include <command.h>
|
||||
#include <exports.h>
|
||||
#include <memalign.h>
|
||||
#include <mtd.h>
|
||||
#include <nand.h>
|
||||
#include <onenand_uboot.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
|
@ -27,24 +28,8 @@
|
|||
#undef ubi_msg
|
||||
#define ubi_msg(fmt, ...) printf("UBI: " fmt "\n", ##__VA_ARGS__)
|
||||
|
||||
#define DEV_TYPE_NONE 0
|
||||
#define DEV_TYPE_NAND 1
|
||||
#define DEV_TYPE_ONENAND 2
|
||||
#define DEV_TYPE_NOR 3
|
||||
|
||||
/* Private own data */
|
||||
static struct ubi_device *ubi;
|
||||
static char buffer[80];
|
||||
static int ubi_initialized;
|
||||
|
||||
struct selected_dev {
|
||||
char part_name[80];
|
||||
int selected;
|
||||
int nr;
|
||||
struct mtd_info *mtd_info;
|
||||
};
|
||||
|
||||
static struct selected_dev ubi_dev;
|
||||
|
||||
#ifdef CONFIG_CMD_UBIFS
|
||||
#include <ubifs_uboot.h>
|
||||
|
@ -408,43 +393,24 @@ int ubi_volume_read(char *volume, char *buf, size_t size)
|
|||
return err;
|
||||
}
|
||||
|
||||
static int ubi_dev_scan(struct mtd_info *info, char *ubidev,
|
||||
const char *vid_header_offset)
|
||||
static int ubi_dev_scan(struct mtd_info *info, const char *vid_header_offset)
|
||||
{
|
||||
struct mtd_device *dev;
|
||||
struct part_info *part;
|
||||
struct mtd_partition mtd_part;
|
||||
char ubi_mtd_param_buffer[80];
|
||||
u8 pnum;
|
||||
int err;
|
||||
|
||||
if (find_dev_and_part(ubidev, &dev, &pnum, &part) != 0)
|
||||
return 1;
|
||||
if (!vid_header_offset)
|
||||
sprintf(ubi_mtd_param_buffer, "%s", info->name);
|
||||
else
|
||||
sprintf(ubi_mtd_param_buffer, "%s,%s", info->name,
|
||||
vid_header_offset);
|
||||
|
||||
sprintf(buffer, "mtd=%d", pnum);
|
||||
memset(&mtd_part, 0, sizeof(mtd_part));
|
||||
mtd_part.name = buffer;
|
||||
mtd_part.size = part->size;
|
||||
mtd_part.offset = part->offset;
|
||||
add_mtd_partitions(info, &mtd_part, 1);
|
||||
|
||||
strcpy(ubi_mtd_param_buffer, buffer);
|
||||
if (vid_header_offset)
|
||||
sprintf(ubi_mtd_param_buffer, "mtd=%d,%s", pnum,
|
||||
vid_header_offset);
|
||||
err = ubi_mtd_param_parse(ubi_mtd_param_buffer, NULL);
|
||||
if (err) {
|
||||
del_mtd_partitions(info);
|
||||
if (err)
|
||||
return -err;
|
||||
}
|
||||
|
||||
err = ubi_init();
|
||||
if (err) {
|
||||
del_mtd_partitions(info);
|
||||
if (err)
|
||||
return -err;
|
||||
}
|
||||
|
||||
ubi_initialized = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -469,50 +435,33 @@ int ubi_detach(void)
|
|||
/*
|
||||
* Call ubi_exit() before re-initializing the UBI subsystem
|
||||
*/
|
||||
if (ubi_initialized) {
|
||||
if (ubi)
|
||||
ubi_exit();
|
||||
del_mtd_partitions(ubi_dev.mtd_info);
|
||||
ubi_initialized = 0;
|
||||
}
|
||||
|
||||
ubi_dev.selected = 0;
|
||||
ubi = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ubi_part(char *part_name, const char *vid_header_offset)
|
||||
{
|
||||
struct mtd_info *mtd;
|
||||
int err = 0;
|
||||
char mtd_dev[16];
|
||||
struct mtd_device *dev;
|
||||
struct part_info *part;
|
||||
u8 pnum;
|
||||
|
||||
ubi_detach();
|
||||
/*
|
||||
* Search the mtd device number where this partition
|
||||
* is located
|
||||
*/
|
||||
if (find_dev_and_part(part_name, &dev, &pnum, &part)) {
|
||||
|
||||
mtd_probe_devices();
|
||||
mtd = get_mtd_device_nm(part_name);
|
||||
if (IS_ERR(mtd)) {
|
||||
printf("Partition %s not found!\n", part_name);
|
||||
return 1;
|
||||
}
|
||||
sprintf(mtd_dev, "%s%d", MTD_DEV_TYPE(dev->id->type), dev->id->num);
|
||||
ubi_dev.mtd_info = get_mtd_device_nm(mtd_dev);
|
||||
if (IS_ERR(ubi_dev.mtd_info)) {
|
||||
printf("Partition %s not found on device %s!\n", part_name,
|
||||
mtd_dev);
|
||||
return 1;
|
||||
}
|
||||
put_mtd_device(mtd);
|
||||
|
||||
ubi_dev.selected = 1;
|
||||
|
||||
strcpy(ubi_dev.part_name, part_name);
|
||||
err = ubi_dev_scan(ubi_dev.mtd_info, ubi_dev.part_name,
|
||||
vid_header_offset);
|
||||
err = ubi_dev_scan(mtd, vid_header_offset);
|
||||
if (err) {
|
||||
printf("UBI init error %d\n", err);
|
||||
printf("Please check, if the correct MTD partition is used (size big enough?)\n");
|
||||
ubi_dev.selected = 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -543,13 +492,13 @@ static int do_ubi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
|
|||
|
||||
/* Print current partition */
|
||||
if (argc == 2) {
|
||||
if (!ubi_dev.selected) {
|
||||
printf("Error, no UBI device/partition selected!\n");
|
||||
if (!ubi) {
|
||||
printf("Error, no UBI device selected!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Device %d: %s, partition %s\n",
|
||||
ubi_dev.nr, ubi_dev.mtd_info->name, ubi_dev.part_name);
|
||||
printf("Device %d: %s, MTD partition %s\n",
|
||||
ubi->ubi_num, ubi->ubi_name, ubi->mtd->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -562,8 +511,8 @@ static int do_ubi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
|
|||
return ubi_part(argv[2], vid_header_offset);
|
||||
}
|
||||
|
||||
if ((strcmp(argv[1], "part") != 0) && (!ubi_dev.selected)) {
|
||||
printf("Error, no UBI device/partition selected!\n");
|
||||
if ((strcmp(argv[1], "part") != 0) && !ubi) {
|
||||
printf("Error, no UBI device selected!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -487,7 +487,7 @@ config SPL_NAND_SUPPORT
|
|||
help
|
||||
Enable support for NAND (Negative AND) flash in SPL. NAND flash
|
||||
can be used to allow SPL to load U-Boot from supported devices.
|
||||
This enables the drivers in drivers/mtd/nand as part of an SPL
|
||||
This enables the drivers in drivers/mtd/nand/raw as part of an SPL
|
||||
build.
|
||||
|
||||
config SPL_NET_SUPPORT
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/*
|
||||
* Copyright (C) 2011 OMICRON electronics GmbH
|
||||
*
|
||||
* based on drivers/mtd/nand/nand_spl_load.c
|
||||
* based on drivers/mtd/nand/raw/nand_spl_load.c
|
||||
*
|
||||
* Copyright (C) 2011
|
||||
* Heiko Schocher, DENX Software Engineering, hs@denx.de.
|
||||
|
|
|
@ -30,6 +30,7 @@ CONFIG_NET_RANDOM_ETHADDR=y
|
|||
CONFIG_DM=y
|
||||
# CONFIG_BLK is not set
|
||||
CONFIG_DM_MMC=y
|
||||
CONFIG_DM_SPI_FLASH=y
|
||||
CONFIG_SPI_FLASH=y
|
||||
CONFIG_SPI_FLASH_WINBOND=y
|
||||
CONFIG_FSL_PFE=y
|
||||
|
@ -40,6 +41,8 @@ CONFIG_DM_PCI=y
|
|||
CONFIG_DM_PCI_COMPAT=y
|
||||
CONFIG_PCIE_LAYERSCAPE=y
|
||||
CONFIG_SYS_NS16550=y
|
||||
CONFIG_SPI=y
|
||||
CONFIG_DM_SPI=y
|
||||
CONFIG_USB=y
|
||||
CONFIG_DM_USB=y
|
||||
CONFIG_USB_XHCI_HCD=y
|
||||
|
|
|
@ -31,6 +31,7 @@ CONFIG_NET_RANDOM_ETHADDR=y
|
|||
CONFIG_DM=y
|
||||
# CONFIG_BLK is not set
|
||||
CONFIG_DM_MMC=y
|
||||
CONFIG_DM_SPI_FLASH=y
|
||||
CONFIG_SPI_FLASH=y
|
||||
CONFIG_SPI_FLASH_WINBOND=y
|
||||
CONFIG_FSL_PFE=y
|
||||
|
@ -41,6 +42,8 @@ CONFIG_DM_PCI=y
|
|||
CONFIG_DM_PCI_COMPAT=y
|
||||
CONFIG_PCIE_LAYERSCAPE=y
|
||||
CONFIG_SYS_NS16550=y
|
||||
CONFIG_SPI=y
|
||||
CONFIG_DM_SPI=y
|
||||
CONFIG_USB=y
|
||||
CONFIG_DM_USB=y
|
||||
CONFIG_USB_XHCI_HCD=y
|
||||
|
|
|
@ -57,11 +57,11 @@ CONFIG_SPL_FAT_SUPPORT (fs/fat/libfat.o)
|
|||
CONFIG_SPL_EXT_SUPPORT
|
||||
CONFIG_SPL_LIBGENERIC_SUPPORT (lib/libgeneric.o)
|
||||
CONFIG_SPL_POWER_SUPPORT (drivers/power/libpower.o)
|
||||
CONFIG_SPL_NAND_SUPPORT (drivers/mtd/nand/libnand.o)
|
||||
CONFIG_SPL_NAND_SUPPORT (drivers/mtd/nand/raw/libnand.o)
|
||||
CONFIG_SPL_DRIVERS_MISC_SUPPORT (drivers/misc)
|
||||
CONFIG_SPL_DMA_SUPPORT (drivers/dma/libdma.o)
|
||||
CONFIG_SPL_POST_MEM_SUPPORT (post/drivers/memory.o)
|
||||
CONFIG_SPL_NAND_LOAD (drivers/mtd/nand/nand_spl_load.o)
|
||||
CONFIG_SPL_NAND_LOAD (drivers/mtd/nand/raw/nand_spl_load.o)
|
||||
CONFIG_SPL_SPI_LOAD (drivers/mtd/spi/spi_spl_load.o)
|
||||
CONFIG_SPL_RAM_DEVICE (common/spl/spl.c)
|
||||
CONFIG_SPL_WATCHDOG_SUPPORT (drivers/watchdog/libwatchdog.o)
|
||||
|
|
|
@ -84,7 +84,7 @@ Relocation with SPL (example for the tx25 booting from NAND Flash):
|
|||
- cpu copies the first page from NAND to 0xbb000000 (IMX_NFC_BASE)
|
||||
and start with code execution on this address.
|
||||
|
||||
- The First page contains u-boot code from drivers/mtd/nand/mxc_nand_spl.c
|
||||
- The First page contains u-boot code from drivers/mtd/nand/raw/mxc_nand_spl.c
|
||||
which inits the dram, cpu registers, reloacte itself to CONFIG_SPL_TEXT_BASE and loads
|
||||
the "real" u-boot to CONFIG_SYS_NAND_U_BOOT_DST and starts execution
|
||||
@CONFIG_SYS_NAND_U_BOOT_START
|
||||
|
|
|
@ -116,7 +116,7 @@ Configuration Options:
|
|||
The maximum number of NAND chips per device to be supported.
|
||||
|
||||
CONFIG_SYS_NAND_SELF_INIT
|
||||
Traditionally, glue code in drivers/mtd/nand/nand.c has driven
|
||||
Traditionally, glue code in drivers/mtd/nand/raw/nand.c has driven
|
||||
the initialization process -- it provides the mtd and nand
|
||||
structs, calls a board init function for a specific device,
|
||||
calls nand_scan(), and registers with mtd.
|
||||
|
@ -125,7 +125,7 @@ Configuration Options:
|
|||
run code between nand_scan_ident() and nand_scan_tail(), or other
|
||||
deviations from the "normal" flow.
|
||||
|
||||
If a board defines CONFIG_SYS_NAND_SELF_INIT, drivers/mtd/nand/nand.c
|
||||
If a board defines CONFIG_SYS_NAND_SELF_INIT, drivers/mtd/nand/raw/nand.c
|
||||
will make one call to board_nand_init(), with no arguments. That
|
||||
function is responsible for calling a driver init function for
|
||||
each NAND device on the board, that performs all initialization
|
||||
|
@ -280,7 +280,7 @@ NOTE:
|
|||
=====
|
||||
|
||||
The Disk On Chip driver is currently broken and has been for some time.
|
||||
There is a driver in drivers/mtd/nand, taken from Linux, that works with
|
||||
There is a driver in drivers/mtd/nand/raw, taken from Linux, that works with
|
||||
the current NAND system but has not yet been adapted to the u-boot
|
||||
environment.
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ bootmode strings at runtime.
|
|||
spi - drivers/spi/zynq_spi.c
|
||||
qspi - drivers/spi/zynq_qspi.c
|
||||
i2c - drivers/i2c/zynq_i2c.c
|
||||
nand - drivers/mtd/nand/zynq_nand.c
|
||||
nand - drivers/mtd/nand/raw/zynq_nand.c
|
||||
- Done proper cleanups on board configurations
|
||||
- Added basic FDT support for zynq boards
|
||||
- d-cache support for zynq_gem.c
|
||||
|
|
5
doc/device-tree-bindings/mtd/spi-nand.txt
Normal file
5
doc/device-tree-bindings/mtd/spi-nand.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
SPI NAND flash
|
||||
|
||||
Required properties:
|
||||
- compatible: should be "spi-nand"
|
||||
- reg: should encode the chip-select line used to access the NAND chip
|
|
@ -6,7 +6,7 @@ obj-$(CONFIG_$(SPL_TPL_)DRIVERS_MISC_SUPPORT) += misc/ sysreset/ firmware/
|
|||
obj-$(CONFIG_$(SPL_TPL_)I2C_SUPPORT) += i2c/
|
||||
obj-$(CONFIG_$(SPL_TPL_)LED) += led/
|
||||
obj-$(CONFIG_$(SPL_TPL_)MMC_SUPPORT) += mmc/
|
||||
obj-$(CONFIG_$(SPL_TPL_)NAND_SUPPORT) += mtd/nand/
|
||||
obj-$(CONFIG_$(SPL_TPL_)NAND_SUPPORT) += mtd/nand/raw/
|
||||
obj-$(CONFIG_$(SPL_TPL_)PHY) += phy/
|
||||
obj-$(CONFIG_$(SPL_TPL_)PINCTRL) += pinctrl/
|
||||
obj-$(CONFIG_$(SPL_TPL_)RAM) += ram/
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
menu "MTD Support"
|
||||
|
||||
config MTD_PARTITIONS
|
||||
bool
|
||||
|
||||
config MTD
|
||||
bool "Enable Driver Model for MTD drivers"
|
||||
depends on DM
|
||||
|
@ -59,10 +62,10 @@ config RENESAS_RPC_HF
|
|||
This enables access to Hyperflash memory through the Renesas
|
||||
RCar Gen3 RPC controller.
|
||||
|
||||
endmenu
|
||||
|
||||
source "drivers/mtd/nand/Kconfig"
|
||||
|
||||
source "drivers/mtd/spi/Kconfig"
|
||||
|
||||
source "drivers/mtd/ubi/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# (C) Copyright 2000-2007
|
||||
# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
|
||||
|
||||
ifneq (,$(findstring y,$(CONFIG_MTD_DEVICE)$(CONFIG_CMD_NAND)$(CONFIG_CMD_ONENAND)$(CONFIG_CMD_SF)))
|
||||
ifneq (,$(findstring y,$(CONFIG_MTD_DEVICE)$(CONFIG_CMD_NAND)$(CONFIG_CMD_ONENAND)$(CONFIG_CMD_SF)$(CONFIG_CMD_MTD)))
|
||||
obj-y += mtdcore.o mtd_uboot.o
|
||||
endif
|
||||
obj-$(CONFIG_MTD) += mtd-uclass.o
|
||||
|
@ -18,3 +18,5 @@ obj-$(CONFIG_FLASH_PIC32) += pic32_flash.o
|
|||
obj-$(CONFIG_ST_SMI) += st_smi.o
|
||||
obj-$(CONFIG_STM32_FLASH) += stm32_flash.o
|
||||
obj-$(CONFIG_RENESAS_RPC_HF) += renesas_rpc_hf.o
|
||||
|
||||
obj-y += nand/
|
||||
|
|
|
@ -5,9 +5,25 @@
|
|||
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <dm/device-internal.h>
|
||||
#include <errno.h>
|
||||
#include <mtd.h>
|
||||
|
||||
/**
|
||||
* mtd_probe - Probe the device @dev if not already done
|
||||
*
|
||||
* @dev: U-Boot device to probe
|
||||
*
|
||||
* @return 0 on success, an error otherwise.
|
||||
*/
|
||||
int mtd_probe(struct udevice *dev)
|
||||
{
|
||||
if (device_active(dev))
|
||||
return 0;
|
||||
|
||||
return device_probe(dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* Implement a MTD uclass which should include most flash drivers.
|
||||
* The uclass private is pointed to mtd_info.
|
||||
|
|
|
@ -4,8 +4,230 @@
|
|||
* Heiko Schocher, DENX Software Engineering, hs@denx.de.
|
||||
*/
|
||||
#include <common.h>
|
||||
#include <dm/device.h>
|
||||
#include <dm/uclass-internal.h>
|
||||
#include <jffs2/jffs2.h> /* LEGACY */
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <jffs2/jffs2.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <mtd.h>
|
||||
|
||||
#define MTD_NAME_MAX_LEN 20
|
||||
|
||||
|
||||
/**
|
||||
* mtd_search_alternate_name - Search an alternate name for @mtdname thanks to
|
||||
* the mtdids legacy environment variable.
|
||||
*
|
||||
* The mtdids string is a list of comma-separated 'dev_id=mtd_id' tupples.
|
||||
* Check if one of the mtd_id matches mtdname, in this case save dev_id in
|
||||
* altname.
|
||||
*
|
||||
* @mtdname: Current MTD device name
|
||||
* @altname: Alternate name to return
|
||||
* @max_len: Length of the alternate name buffer
|
||||
*
|
||||
* @return 0 on success, an error otherwise.
|
||||
*/
|
||||
int mtd_search_alternate_name(const char *mtdname, char *altname,
|
||||
unsigned int max_len)
|
||||
{
|
||||
const char *mtdids, *equal, *comma, *dev_id, *mtd_id;
|
||||
int dev_id_len, mtd_id_len;
|
||||
|
||||
mtdids = env_get("mtdids");
|
||||
if (!mtdids)
|
||||
return -EINVAL;
|
||||
|
||||
do {
|
||||
/* Find the '=' sign */
|
||||
dev_id = mtdids;
|
||||
equal = strchr(dev_id, '=');
|
||||
if (!equal)
|
||||
break;
|
||||
dev_id_len = equal - mtdids;
|
||||
mtd_id = equal + 1;
|
||||
|
||||
/* Find the end of the tupple */
|
||||
comma = strchr(mtdids, ',');
|
||||
if (comma)
|
||||
mtd_id_len = comma - mtd_id;
|
||||
else
|
||||
mtd_id_len = &mtdids[strlen(mtdids)] - mtd_id + 1;
|
||||
|
||||
if (!dev_id_len || !mtd_id_len)
|
||||
return -EINVAL;
|
||||
|
||||
if (dev_id_len + 1 > max_len)
|
||||
continue;
|
||||
|
||||
/* Compare the name we search with the current mtd_id */
|
||||
if (!strncmp(mtdname, mtd_id, mtd_id_len)) {
|
||||
strncpy(altname, dev_id, dev_id_len);
|
||||
altname[dev_id_len] = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Go to the next tupple */
|
||||
mtdids = comma + 1;
|
||||
} while (comma);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_MTD)
|
||||
static void mtd_probe_uclass_mtd_devs(void)
|
||||
{
|
||||
struct udevice *dev;
|
||||
int idx = 0;
|
||||
|
||||
/* Probe devices with DM compliant drivers */
|
||||
while (!uclass_find_device(UCLASS_MTD, idx, &dev) && dev) {
|
||||
mtd_probe(dev);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void mtd_probe_uclass_mtd_devs(void) { }
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_MTD_PARTITIONS)
|
||||
int mtd_probe_devices(void)
|
||||
{
|
||||
static char *old_mtdparts;
|
||||
static char *old_mtdids;
|
||||
const char *mtdparts = env_get("mtdparts");
|
||||
const char *mtdids = env_get("mtdids");
|
||||
bool remaining_partitions = true;
|
||||
struct mtd_info *mtd;
|
||||
|
||||
mtd_probe_uclass_mtd_devs();
|
||||
|
||||
/* Check if mtdparts/mtdids changed since last call, otherwise: exit */
|
||||
if (!strcmp(mtdparts, old_mtdparts) && !strcmp(mtdids, old_mtdids))
|
||||
return 0;
|
||||
|
||||
/* Update the local copy of mtdparts */
|
||||
free(old_mtdparts);
|
||||
free(old_mtdids);
|
||||
old_mtdparts = strdup(mtdparts);
|
||||
old_mtdids = strdup(mtdids);
|
||||
|
||||
/* If at least one partition is still in use, do not delete anything */
|
||||
mtd_for_each_device(mtd) {
|
||||
if (mtd->usecount) {
|
||||
printf("Partition \"%s\" already in use, aborting\n",
|
||||
mtd->name);
|
||||
return -EACCES;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Everything looks clear, remove all partitions. It is not safe to
|
||||
* remove entries from the mtd_for_each_device loop as it uses idr
|
||||
* indexes and the partitions removal is done in bulk (all partitions of
|
||||
* one device at the same time), so break and iterate from start each
|
||||
* time a new partition is found and deleted.
|
||||
*/
|
||||
while (remaining_partitions) {
|
||||
remaining_partitions = false;
|
||||
mtd_for_each_device(mtd) {
|
||||
if (!mtd_is_partition(mtd) && mtd_has_partitions(mtd)) {
|
||||
del_mtd_partitions(mtd);
|
||||
remaining_partitions = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Start the parsing by ignoring the extra 'mtdparts=' prefix, if any */
|
||||
if (strstr(mtdparts, "mtdparts="))
|
||||
mtdparts += 9;
|
||||
|
||||
/* For each MTD device in mtdparts */
|
||||
while (mtdparts[0] != '\0') {
|
||||
char mtd_name[MTD_NAME_MAX_LEN], *colon;
|
||||
struct mtd_partition *parts;
|
||||
int mtd_name_len, nparts;
|
||||
int ret;
|
||||
|
||||
colon = strchr(mtdparts, ':');
|
||||
if (!colon) {
|
||||
printf("Wrong mtdparts: %s\n", mtdparts);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mtd_name_len = colon - mtdparts;
|
||||
strncpy(mtd_name, mtdparts, mtd_name_len);
|
||||
mtd_name[mtd_name_len] = '\0';
|
||||
/* Move the pointer forward (including the ':') */
|
||||
mtdparts += mtd_name_len + 1;
|
||||
mtd = get_mtd_device_nm(mtd_name);
|
||||
if (IS_ERR_OR_NULL(mtd)) {
|
||||
char linux_name[MTD_NAME_MAX_LEN];
|
||||
|
||||
/*
|
||||
* The MTD device named "mtd_name" does not exist. Try
|
||||
* to find a correspondance with an MTD device having
|
||||
* the same type and number as defined in the mtdids.
|
||||
*/
|
||||
debug("No device named %s\n", mtd_name);
|
||||
ret = mtd_search_alternate_name(mtd_name, linux_name,
|
||||
MTD_NAME_MAX_LEN);
|
||||
if (!ret)
|
||||
mtd = get_mtd_device_nm(linux_name);
|
||||
|
||||
/*
|
||||
* If no device could be found, move the mtdparts
|
||||
* pointer forward until the next set of partitions.
|
||||
*/
|
||||
if (ret || IS_ERR_OR_NULL(mtd)) {
|
||||
printf("Could not find a valid device for %s\n",
|
||||
mtd_name);
|
||||
mtdparts = strchr(mtdparts, ';');
|
||||
if (mtdparts)
|
||||
mtdparts++;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the MTD device partitions. It will update the mtdparts
|
||||
* pointer, create an array of parts (that must be freed), and
|
||||
* return the number of partition structures in the array.
|
||||
*/
|
||||
ret = mtd_parse_partitions(mtd, &mtdparts, &parts, &nparts);
|
||||
if (ret) {
|
||||
printf("Could not parse device %s\n", mtd->name);
|
||||
put_mtd_device(mtd);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!nparts)
|
||||
continue;
|
||||
|
||||
/* Create the new MTD partitions */
|
||||
add_mtd_partitions(mtd, parts, nparts);
|
||||
|
||||
/* Free the structures allocated during the parsing */
|
||||
mtd_free_parsed_partitions(parts, nparts);
|
||||
|
||||
put_mtd_device(mtd);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
int mtd_probe_devices(void)
|
||||
{
|
||||
mtd_probe_uclass_mtd_devs();
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* defined(CONFIG_MTD_PARTITIONS) */
|
||||
|
||||
/* Legacy */
|
||||
|
||||
static int get_part(const char *partname, int *idx, loff_t *off, loff_t *size,
|
||||
loff_t *maxsize, int devtype)
|
||||
|
|
|
@ -426,6 +426,8 @@ int add_mtd_device(struct mtd_info *mtd)
|
|||
mtd->index = i;
|
||||
mtd->usecount = 0;
|
||||
|
||||
INIT_LIST_HEAD(&mtd->partitions);
|
||||
|
||||
/* default value if not set by driver */
|
||||
if (mtd->bitflip_threshold == 0)
|
||||
mtd->bitflip_threshold = mtd->ecc_strength;
|
||||
|
@ -937,7 +939,20 @@ int mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen,
|
|||
* representing the maximum number of bitflips that were corrected on
|
||||
* any one ecc region (if applicable; zero otherwise).
|
||||
*/
|
||||
ret_code = mtd->_read(mtd, from, len, retlen, buf);
|
||||
if (mtd->_read) {
|
||||
ret_code = mtd->_read(mtd, from, len, retlen, buf);
|
||||
} else if (mtd->_read_oob) {
|
||||
struct mtd_oob_ops ops = {
|
||||
.len = len,
|
||||
.datbuf = buf,
|
||||
};
|
||||
|
||||
ret_code = mtd->_read_oob(mtd, from, &ops);
|
||||
*retlen = ops.retlen;
|
||||
} else {
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
if (unlikely(ret_code < 0))
|
||||
return ret_code;
|
||||
if (mtd->ecc_strength == 0)
|
||||
|
@ -952,10 +967,24 @@ int mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
|
|||
*retlen = 0;
|
||||
if (to < 0 || to > mtd->size || len > mtd->size - to)
|
||||
return -EINVAL;
|
||||
if (!mtd->_write || !(mtd->flags & MTD_WRITEABLE))
|
||||
if ((!mtd->_write && !mtd->_write_oob) ||
|
||||
!(mtd->flags & MTD_WRITEABLE))
|
||||
return -EROFS;
|
||||
if (!len)
|
||||
return 0;
|
||||
|
||||
if (!mtd->_write) {
|
||||
struct mtd_oob_ops ops = {
|
||||
.len = len,
|
||||
.datbuf = (u8 *)buf,
|
||||
};
|
||||
int ret;
|
||||
|
||||
ret = mtd->_write_oob(mtd, to, &ops);
|
||||
*retlen = ops.retlen;
|
||||
return ret;
|
||||
}
|
||||
|
||||
return mtd->_write(mtd, to, len, retlen, buf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_write);
|
||||
|
@ -983,19 +1012,64 @@ int mtd_panic_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_panic_write);
|
||||
|
||||
static int mtd_check_oob_ops(struct mtd_info *mtd, loff_t offs,
|
||||
struct mtd_oob_ops *ops)
|
||||
{
|
||||
/*
|
||||
* Some users are setting ->datbuf or ->oobbuf to NULL, but are leaving
|
||||
* ->len or ->ooblen uninitialized. Force ->len and ->ooblen to 0 in
|
||||
* this case.
|
||||
*/
|
||||
if (!ops->datbuf)
|
||||
ops->len = 0;
|
||||
|
||||
if (!ops->oobbuf)
|
||||
ops->ooblen = 0;
|
||||
|
||||
if (offs < 0 || offs + ops->len > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
if (ops->ooblen) {
|
||||
u64 maxooblen;
|
||||
|
||||
if (ops->ooboffs >= mtd_oobavail(mtd, ops))
|
||||
return -EINVAL;
|
||||
|
||||
maxooblen = ((mtd_div_by_ws(mtd->size, mtd) -
|
||||
mtd_div_by_ws(offs, mtd)) *
|
||||
mtd_oobavail(mtd, ops)) - ops->ooboffs;
|
||||
if (ops->ooblen > maxooblen)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mtd_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
|
||||
{
|
||||
int ret_code;
|
||||
ops->retlen = ops->oobretlen = 0;
|
||||
if (!mtd->_read_oob)
|
||||
|
||||
ret_code = mtd_check_oob_ops(mtd, from, ops);
|
||||
if (ret_code)
|
||||
return ret_code;
|
||||
|
||||
/* Check the validity of a potential fallback on mtd->_read */
|
||||
if (!mtd->_read_oob && (!mtd->_read || ops->oobbuf))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (mtd->_read_oob)
|
||||
ret_code = mtd->_read_oob(mtd, from, ops);
|
||||
else
|
||||
ret_code = mtd->_read(mtd, from, ops->len, &ops->retlen,
|
||||
ops->datbuf);
|
||||
|
||||
/*
|
||||
* In cases where ops->datbuf != NULL, mtd->_read_oob() has semantics
|
||||
* similar to mtd->_read(), returning a non-negative integer
|
||||
* representing max bitflips. In other cases, mtd->_read_oob() may
|
||||
* return -EUCLEAN. In all cases, perform similar logic to mtd_read().
|
||||
*/
|
||||
ret_code = mtd->_read_oob(mtd, from, ops);
|
||||
if (unlikely(ret_code < 0))
|
||||
return ret_code;
|
||||
if (mtd->ecc_strength == 0)
|
||||
|
@ -1004,6 +1078,32 @@ int mtd_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_read_oob);
|
||||
|
||||
int mtd_write_oob(struct mtd_info *mtd, loff_t to,
|
||||
struct mtd_oob_ops *ops)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ops->retlen = ops->oobretlen = 0;
|
||||
|
||||
if (!(mtd->flags & MTD_WRITEABLE))
|
||||
return -EROFS;
|
||||
|
||||
ret = mtd_check_oob_ops(mtd, to, ops);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Check the validity of a potential fallback on mtd->_write */
|
||||
if (!mtd->_write_oob && (!mtd->_write || ops->oobbuf))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (mtd->_write_oob)
|
||||
return mtd->_write_oob(mtd, to, ops);
|
||||
else
|
||||
return mtd->_write(mtd, to, ops->len, &ops->retlen,
|
||||
ops->datbuf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_write_oob);
|
||||
|
||||
/**
|
||||
* mtd_ooblayout_ecc - Get the OOB region definition of a specific ECC section
|
||||
* @mtd: MTD device structure
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
extern struct mutex mtd_table_mutex;
|
||||
|
||||
struct mtd_info *__mtd_next_device(int i);
|
||||
int add_mtd_device(struct mtd_info *mtd);
|
||||
int del_mtd_device(struct mtd_info *mtd);
|
||||
int add_mtd_partitions(struct mtd_info *, const struct mtd_partition *, int);
|
||||
|
@ -16,8 +15,3 @@ int parse_mtd_partitions(struct mtd_info *master, const char * const *types,
|
|||
|
||||
int __init init_mtdchar(void);
|
||||
void __exit cleanup_mtdchar(void);
|
||||
|
||||
#define mtd_for_each_device(mtd) \
|
||||
for ((mtd) = __mtd_next_device(0); \
|
||||
(mtd) != NULL; \
|
||||
(mtd) = __mtd_next_device(mtd->index + 1))
|
||||
|
|
|
@ -26,32 +26,16 @@
|
|||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
#include "mtdcore.h"
|
||||
|
||||
/* Our partition linked list */
|
||||
static LIST_HEAD(mtd_partitions);
|
||||
#ifndef __UBOOT__
|
||||
static DEFINE_MUTEX(mtd_partitions_mutex);
|
||||
#else
|
||||
DEFINE_MUTEX(mtd_partitions_mutex);
|
||||
#endif
|
||||
|
||||
/* Our partition node structure */
|
||||
struct mtd_part {
|
||||
struct mtd_info mtd;
|
||||
struct mtd_info *master;
|
||||
uint64_t offset;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
/*
|
||||
* Given a pointer to the MTD object in the mtd_part structure, we can retrieve
|
||||
* the pointer to that structure with this macro.
|
||||
*/
|
||||
#define PART(x) ((struct mtd_part *)(x))
|
||||
|
||||
|
||||
#ifdef __UBOOT__
|
||||
/* from mm/util.c */
|
||||
|
||||
|
@ -76,6 +60,215 @@ char *kstrdup(const char *s, gfp_t gfp)
|
|||
}
|
||||
#endif
|
||||
|
||||
#define MTD_SIZE_REMAINING (~0LLU)
|
||||
#define MTD_OFFSET_NOT_SPECIFIED (~0LLU)
|
||||
|
||||
/**
|
||||
* mtd_parse_partition - Parse @mtdparts partition definition, fill @partition
|
||||
* with it and update the @mtdparts string pointer.
|
||||
*
|
||||
* The partition name is allocated and must be freed by the caller.
|
||||
*
|
||||
* This function is widely inspired from part_parse (mtdparts.c).
|
||||
*
|
||||
* @mtdparts: String describing the partition with mtdparts command syntax
|
||||
* @partition: MTD partition structure to fill
|
||||
*
|
||||
* @return 0 on success, an error otherwise.
|
||||
*/
|
||||
static int mtd_parse_partition(const char **_mtdparts,
|
||||
struct mtd_partition *partition)
|
||||
{
|
||||
const char *mtdparts = *_mtdparts;
|
||||
const char *name = NULL;
|
||||
int name_len;
|
||||
char *buf;
|
||||
|
||||
/* Ensure the partition structure is empty */
|
||||
memset(partition, 0, sizeof(struct mtd_partition));
|
||||
|
||||
/* Fetch the partition size */
|
||||
if (*mtdparts == '-') {
|
||||
/* Assign all remaining space to this partition */
|
||||
partition->size = MTD_SIZE_REMAINING;
|
||||
mtdparts++;
|
||||
} else {
|
||||
partition->size = ustrtoull(mtdparts, (char **)&mtdparts, 0);
|
||||
if (partition->size < SZ_4K) {
|
||||
printf("Minimum partition size 4kiB, %lldB requested\n",
|
||||
partition->size);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for the offset */
|
||||
partition->offset = MTD_OFFSET_NOT_SPECIFIED;
|
||||
if (*mtdparts == '@') {
|
||||
mtdparts++;
|
||||
partition->offset = ustrtoull(mtdparts, (char **)&mtdparts, 0);
|
||||
}
|
||||
|
||||
/* Now look for the name */
|
||||
if (*mtdparts == '(') {
|
||||
name = ++mtdparts;
|
||||
mtdparts = strchr(name, ')');
|
||||
if (!mtdparts) {
|
||||
printf("No closing ')' found in partition name\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
name_len = mtdparts - name + 1;
|
||||
if ((name_len - 1) == 0) {
|
||||
printf("Empty partition name\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
mtdparts++;
|
||||
} else {
|
||||
/* Name will be of the form size@offset */
|
||||
name_len = 22;
|
||||
}
|
||||
|
||||
/* Check if the partition is read-only */
|
||||
if (strncmp(mtdparts, "ro", 2) == 0) {
|
||||
partition->mask_flags |= MTD_WRITEABLE;
|
||||
mtdparts += 2;
|
||||
}
|
||||
|
||||
/* Check for a potential next partition definition */
|
||||
if (*mtdparts == ',') {
|
||||
if (partition->size == MTD_SIZE_REMAINING) {
|
||||
printf("No partitions allowed after a fill-up\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
++mtdparts;
|
||||
} else if ((*mtdparts == ';') || (*mtdparts == '\0')) {
|
||||
/* NOP */
|
||||
} else {
|
||||
printf("Unexpected character '%c' in mtdparts\n", *mtdparts);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a buffer for the name and either copy the provided name or
|
||||
* auto-generate it with the form 'size@offset'.
|
||||
*/
|
||||
buf = malloc(name_len);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
if (name)
|
||||
strncpy(buf, name, name_len - 1);
|
||||
else
|
||||
snprintf(buf, name_len, "0x%08llx@0x%08llx",
|
||||
partition->size, partition->offset);
|
||||
|
||||
buf[name_len - 1] = '\0';
|
||||
partition->name = buf;
|
||||
|
||||
*_mtdparts = mtdparts;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mtd_parse_partitions - Create a partition array from an mtdparts definition
|
||||
*
|
||||
* Stateless function that takes a @parent MTD device, a string @_mtdparts
|
||||
* describing the partitions (with the "mtdparts" command syntax) and creates
|
||||
* the corresponding MTD partition structure array @_parts. Both the name and
|
||||
* the structure partition itself must be freed freed, the caller may use
|
||||
* @mtd_free_parsed_partitions() for this purpose.
|
||||
*
|
||||
* @parent: MTD device which contains the partitions
|
||||
* @_mtdparts: Pointer to a string describing the partitions with "mtdparts"
|
||||
* command syntax.
|
||||
* @_parts: Allocated array containing the partitions, must be freed by the
|
||||
* caller.
|
||||
* @_nparts: Size of @_parts array.
|
||||
*
|
||||
* @return 0 on success, an error otherwise.
|
||||
*/
|
||||
int mtd_parse_partitions(struct mtd_info *parent, const char **_mtdparts,
|
||||
struct mtd_partition **_parts, int *_nparts)
|
||||
{
|
||||
struct mtd_partition partition = {}, *parts;
|
||||
const char *mtdparts = *_mtdparts;
|
||||
int cur_off = 0, cur_sz = 0;
|
||||
int nparts = 0;
|
||||
int ret, idx;
|
||||
u64 sz;
|
||||
|
||||
/* First, iterate over the partitions until we know their number */
|
||||
while (mtdparts[0] != '\0' && mtdparts[0] != ';') {
|
||||
ret = mtd_parse_partition(&mtdparts, &partition);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
free((char *)partition.name);
|
||||
nparts++;
|
||||
}
|
||||
|
||||
/* Allocate an array of partitions to give back to the caller */
|
||||
parts = malloc(sizeof(*parts) * nparts);
|
||||
if (!parts) {
|
||||
printf("Not enough space to save partitions meta-data\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Iterate again over each partition to save the data in our array */
|
||||
for (idx = 0; idx < nparts; idx++) {
|
||||
ret = mtd_parse_partition(_mtdparts, &parts[idx]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (parts[idx].size == MTD_SIZE_REMAINING)
|
||||
parts[idx].size = parent->size - cur_sz;
|
||||
cur_sz += parts[idx].size;
|
||||
|
||||
sz = parts[idx].size;
|
||||
if (sz < parent->writesize || do_div(sz, parent->writesize)) {
|
||||
printf("Partition size must be a multiple of %d\n",
|
||||
parent->writesize);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (parts[idx].offset == MTD_OFFSET_NOT_SPECIFIED)
|
||||
parts[idx].offset = cur_off;
|
||||
cur_off += parts[idx].size;
|
||||
|
||||
parts[idx].ecclayout = parent->ecclayout;
|
||||
}
|
||||
|
||||
/* Offset by one mtdparts to point to the next device if any */
|
||||
if (*_mtdparts[0] == ';')
|
||||
(*_mtdparts)++;
|
||||
|
||||
*_parts = parts;
|
||||
*_nparts = nparts;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mtd_free_parsed_partitions - Free dynamically allocated partitions
|
||||
*
|
||||
* Each successful call to @mtd_parse_partitions must be followed by a call to
|
||||
* @mtd_free_parsed_partitions to free any allocated array during the parsing
|
||||
* process.
|
||||
*
|
||||
* @parts: Array containing the partitions that will be freed.
|
||||
* @nparts: Size of @parts array.
|
||||
*/
|
||||
void mtd_free_parsed_partitions(struct mtd_partition *parts,
|
||||
unsigned int nparts)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nparts; i++)
|
||||
free((char *)parts[i].name);
|
||||
|
||||
free(parts);
|
||||
}
|
||||
|
||||
/*
|
||||
* MTD methods which simply translate the effective address and pass through
|
||||
* to the _real_ device.
|
||||
|
@ -84,19 +277,18 @@ char *kstrdup(const char *s, gfp_t gfp)
|
|||
static int part_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
struct mtd_ecc_stats stats;
|
||||
int res;
|
||||
|
||||
stats = part->master->ecc_stats;
|
||||
res = part->master->_read(part->master, from + part->offset, len,
|
||||
retlen, buf);
|
||||
stats = mtd->parent->ecc_stats;
|
||||
res = mtd->parent->_read(mtd->parent, from + mtd->offset, len,
|
||||
retlen, buf);
|
||||
if (unlikely(mtd_is_eccerr(res)))
|
||||
mtd->ecc_stats.failed +=
|
||||
part->master->ecc_stats.failed - stats.failed;
|
||||
mtd->parent->ecc_stats.failed - stats.failed;
|
||||
else
|
||||
mtd->ecc_stats.corrected +=
|
||||
part->master->ecc_stats.corrected - stats.corrected;
|
||||
mtd->parent->ecc_stats.corrected - stats.corrected;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -104,17 +296,13 @@ static int part_read(struct mtd_info *mtd, loff_t from, size_t len,
|
|||
static int part_point(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, void **virt, resource_size_t *phys)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
|
||||
return part->master->_point(part->master, from + part->offset, len,
|
||||
retlen, virt, phys);
|
||||
return mtd->parent->_point(mtd->parent, from + mtd->offset, len,
|
||||
retlen, virt, phys);
|
||||
}
|
||||
|
||||
static int part_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
|
||||
return part->master->_unpoint(part->master, from + part->offset, len);
|
||||
return mtd->parent->_unpoint(mtd->parent, from + mtd->offset, len);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -123,17 +311,13 @@ static unsigned long part_get_unmapped_area(struct mtd_info *mtd,
|
|||
unsigned long offset,
|
||||
unsigned long flags)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
|
||||
offset += part->offset;
|
||||
return part->master->_get_unmapped_area(part->master, len, offset,
|
||||
flags);
|
||||
offset += mtd->offset;
|
||||
return mtd->parent->_get_unmapped_area(mtd->parent, len, offset, flags);
|
||||
}
|
||||
|
||||
static int part_read_oob(struct mtd_info *mtd, loff_t from,
|
||||
struct mtd_oob_ops *ops)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
int res;
|
||||
|
||||
if (from >= mtd->size)
|
||||
|
@ -158,7 +342,7 @@ static int part_read_oob(struct mtd_info *mtd, loff_t from,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
res = part->master->_read_oob(part->master, from + part->offset, ops);
|
||||
res = mtd->parent->_read_oob(mtd->parent, from + mtd->offset, ops);
|
||||
if (unlikely(res)) {
|
||||
if (mtd_is_bitflip(res))
|
||||
mtd->ecc_stats.corrected++;
|
||||
|
@ -171,99 +355,87 @@ static int part_read_oob(struct mtd_info *mtd, loff_t from,
|
|||
static int part_read_user_prot_reg(struct mtd_info *mtd, loff_t from,
|
||||
size_t len, size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
return part->master->_read_user_prot_reg(part->master, from, len,
|
||||
retlen, buf);
|
||||
return mtd->parent->_read_user_prot_reg(mtd->parent, from, len,
|
||||
retlen, buf);
|
||||
}
|
||||
|
||||
static int part_get_user_prot_info(struct mtd_info *mtd, size_t len,
|
||||
size_t *retlen, struct otp_info *buf)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
return part->master->_get_user_prot_info(part->master, len, retlen,
|
||||
buf);
|
||||
return mtd->parent->_get_user_prot_info(mtd->parent, len, retlen,
|
||||
buf);
|
||||
}
|
||||
|
||||
static int part_read_fact_prot_reg(struct mtd_info *mtd, loff_t from,
|
||||
size_t len, size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
return part->master->_read_fact_prot_reg(part->master, from, len,
|
||||
retlen, buf);
|
||||
return mtd->parent->_read_fact_prot_reg(mtd->parent, from, len,
|
||||
retlen, buf);
|
||||
}
|
||||
|
||||
static int part_get_fact_prot_info(struct mtd_info *mtd, size_t len,
|
||||
size_t *retlen, struct otp_info *buf)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
return part->master->_get_fact_prot_info(part->master, len, retlen,
|
||||
buf);
|
||||
return mtd->parent->_get_fact_prot_info(mtd->parent, len, retlen,
|
||||
buf);
|
||||
}
|
||||
|
||||
static int part_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
return part->master->_write(part->master, to + part->offset, len,
|
||||
retlen, buf);
|
||||
return mtd->parent->_write(mtd->parent, to + mtd->offset, len,
|
||||
retlen, buf);
|
||||
}
|
||||
|
||||
static int part_panic_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
return part->master->_panic_write(part->master, to + part->offset, len,
|
||||
retlen, buf);
|
||||
return mtd->parent->_panic_write(mtd->parent, to + mtd->offset, len,
|
||||
retlen, buf);
|
||||
}
|
||||
|
||||
static int part_write_oob(struct mtd_info *mtd, loff_t to,
|
||||
struct mtd_oob_ops *ops)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
|
||||
if (to >= mtd->size)
|
||||
return -EINVAL;
|
||||
if (ops->datbuf && to + ops->len > mtd->size)
|
||||
return -EINVAL;
|
||||
return part->master->_write_oob(part->master, to + part->offset, ops);
|
||||
return mtd->parent->_write_oob(mtd->parent, to + mtd->offset, ops);
|
||||
}
|
||||
|
||||
static int part_write_user_prot_reg(struct mtd_info *mtd, loff_t from,
|
||||
size_t len, size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
return part->master->_write_user_prot_reg(part->master, from, len,
|
||||
retlen, buf);
|
||||
return mtd->parent->_write_user_prot_reg(mtd->parent, from, len,
|
||||
retlen, buf);
|
||||
}
|
||||
|
||||
static int part_lock_user_prot_reg(struct mtd_info *mtd, loff_t from,
|
||||
size_t len)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
return part->master->_lock_user_prot_reg(part->master, from, len);
|
||||
return mtd->parent->_lock_user_prot_reg(mtd->parent, from, len);
|
||||
}
|
||||
|
||||
#ifndef __UBOOT__
|
||||
static int part_writev(struct mtd_info *mtd, const struct kvec *vecs,
|
||||
unsigned long count, loff_t to, size_t *retlen)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
return part->master->_writev(part->master, vecs, count,
|
||||
to + part->offset, retlen);
|
||||
return mtd->parent->_writev(mtd->parent, vecs, count,
|
||||
to + mtd->offset, retlen);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int part_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
int ret;
|
||||
|
||||
instr->addr += part->offset;
|
||||
ret = part->master->_erase(part->master, instr);
|
||||
instr->addr += mtd->offset;
|
||||
ret = mtd->parent->_erase(mtd->parent, instr);
|
||||
if (ret) {
|
||||
if (instr->fail_addr != MTD_FAIL_ADDR_UNKNOWN)
|
||||
instr->fail_addr -= part->offset;
|
||||
instr->addr -= part->offset;
|
||||
instr->fail_addr -= mtd->offset;
|
||||
instr->addr -= mtd->offset;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
@ -271,11 +443,9 @@ static int part_erase(struct mtd_info *mtd, struct erase_info *instr)
|
|||
void mtd_erase_callback(struct erase_info *instr)
|
||||
{
|
||||
if (instr->mtd->_erase == part_erase) {
|
||||
struct mtd_part *part = PART(instr->mtd);
|
||||
|
||||
if (instr->fail_addr != MTD_FAIL_ADDR_UNKNOWN)
|
||||
instr->fail_addr -= part->offset;
|
||||
instr->addr -= part->offset;
|
||||
instr->fail_addr -= instr->mtd->offset;
|
||||
instr->addr -= instr->mtd->offset;
|
||||
}
|
||||
if (instr->callback)
|
||||
instr->callback(instr);
|
||||
|
@ -284,105 +454,112 @@ EXPORT_SYMBOL_GPL(mtd_erase_callback);
|
|||
|
||||
static int part_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
return part->master->_lock(part->master, ofs + part->offset, len);
|
||||
return mtd->parent->_lock(mtd->parent, ofs + mtd->offset, len);
|
||||
}
|
||||
|
||||
static int part_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
return part->master->_unlock(part->master, ofs + part->offset, len);
|
||||
return mtd->parent->_unlock(mtd->parent, ofs + mtd->offset, len);
|
||||
}
|
||||
|
||||
static int part_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
return part->master->_is_locked(part->master, ofs + part->offset, len);
|
||||
return mtd->parent->_is_locked(mtd->parent, ofs + mtd->offset, len);
|
||||
}
|
||||
|
||||
static void part_sync(struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
part->master->_sync(part->master);
|
||||
mtd->parent->_sync(mtd->parent);
|
||||
}
|
||||
|
||||
#ifndef __UBOOT__
|
||||
static int part_suspend(struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
return part->master->_suspend(part->master);
|
||||
return mtd->parent->_suspend(mtd->parent);
|
||||
}
|
||||
|
||||
static void part_resume(struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
part->master->_resume(part->master);
|
||||
mtd->parent->_resume(mtd->parent);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int part_block_isreserved(struct mtd_info *mtd, loff_t ofs)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
ofs += part->offset;
|
||||
return part->master->_block_isreserved(part->master, ofs);
|
||||
ofs += mtd->offset;
|
||||
return mtd->parent->_block_isreserved(mtd->parent, ofs);
|
||||
}
|
||||
|
||||
static int part_block_isbad(struct mtd_info *mtd, loff_t ofs)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
ofs += part->offset;
|
||||
return part->master->_block_isbad(part->master, ofs);
|
||||
ofs += mtd->offset;
|
||||
return mtd->parent->_block_isbad(mtd->parent, ofs);
|
||||
}
|
||||
|
||||
static int part_block_markbad(struct mtd_info *mtd, loff_t ofs)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
int res;
|
||||
|
||||
ofs += part->offset;
|
||||
res = part->master->_block_markbad(part->master, ofs);
|
||||
ofs += mtd->offset;
|
||||
res = mtd->parent->_block_markbad(mtd->parent, ofs);
|
||||
if (!res)
|
||||
mtd->ecc_stats.badblocks++;
|
||||
return res;
|
||||
}
|
||||
|
||||
static inline void free_partition(struct mtd_part *p)
|
||||
static inline void free_partition(struct mtd_info *p)
|
||||
{
|
||||
kfree(p->mtd.name);
|
||||
kfree(p->name);
|
||||
kfree(p);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function unregisters and destroy all slave MTD objects which are
|
||||
* attached to the given master MTD object.
|
||||
* attached to the given master MTD object, recursively.
|
||||
*/
|
||||
|
||||
int del_mtd_partitions(struct mtd_info *master)
|
||||
static int do_del_mtd_partitions(struct mtd_info *master)
|
||||
{
|
||||
struct mtd_part *slave, *next;
|
||||
struct mtd_info *slave, *next;
|
||||
int ret, err = 0;
|
||||
|
||||
mutex_lock(&mtd_partitions_mutex);
|
||||
list_for_each_entry_safe(slave, next, &mtd_partitions, list)
|
||||
if (slave->master == master) {
|
||||
ret = del_mtd_device(&slave->mtd);
|
||||
if (ret < 0) {
|
||||
err = ret;
|
||||
continue;
|
||||
}
|
||||
list_del(&slave->list);
|
||||
free_partition(slave);
|
||||
list_for_each_entry_safe(slave, next, &master->partitions, node) {
|
||||
if (mtd_has_partitions(slave))
|
||||
del_mtd_partitions(slave);
|
||||
|
||||
debug("Deleting %s MTD partition\n", slave->name);
|
||||
ret = del_mtd_device(slave);
|
||||
if (ret < 0) {
|
||||
printf("Error when deleting partition \"%s\" (%d)\n",
|
||||
slave->name, ret);
|
||||
err = ret;
|
||||
continue;
|
||||
}
|
||||
mutex_unlock(&mtd_partitions_mutex);
|
||||
|
||||
list_del(&slave->node);
|
||||
free_partition(slave);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct mtd_part *allocate_partition(struct mtd_info *master,
|
||||
const struct mtd_partition *part, int partno,
|
||||
uint64_t cur_offset)
|
||||
int del_mtd_partitions(struct mtd_info *master)
|
||||
{
|
||||
struct mtd_part *slave;
|
||||
int ret;
|
||||
|
||||
debug("Deleting MTD partitions on \"%s\":\n", master->name);
|
||||
|
||||
mutex_lock(&mtd_partitions_mutex);
|
||||
ret = do_del_mtd_partitions(master);
|
||||
mutex_unlock(&mtd_partitions_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct mtd_info *allocate_partition(struct mtd_info *master,
|
||||
const struct mtd_partition *part,
|
||||
int partno, uint64_t cur_offset)
|
||||
{
|
||||
struct mtd_info *slave;
|
||||
char *name;
|
||||
|
||||
/* allocate the partition structure */
|
||||
|
@ -397,83 +574,87 @@ static struct mtd_part *allocate_partition(struct mtd_info *master,
|
|||
}
|
||||
|
||||
/* set up the MTD object for this partition */
|
||||
slave->mtd.type = master->type;
|
||||
slave->mtd.flags = master->flags & ~part->mask_flags;
|
||||
slave->mtd.size = part->size;
|
||||
slave->mtd.writesize = master->writesize;
|
||||
slave->mtd.writebufsize = master->writebufsize;
|
||||
slave->mtd.oobsize = master->oobsize;
|
||||
slave->mtd.oobavail = master->oobavail;
|
||||
slave->mtd.subpage_sft = master->subpage_sft;
|
||||
slave->type = master->type;
|
||||
slave->flags = master->flags & ~part->mask_flags;
|
||||
slave->size = part->size;
|
||||
slave->writesize = master->writesize;
|
||||
slave->writebufsize = master->writebufsize;
|
||||
slave->oobsize = master->oobsize;
|
||||
slave->oobavail = master->oobavail;
|
||||
slave->subpage_sft = master->subpage_sft;
|
||||
|
||||
slave->mtd.name = name;
|
||||
slave->mtd.owner = master->owner;
|
||||
slave->name = name;
|
||||
slave->owner = master->owner;
|
||||
#ifndef __UBOOT__
|
||||
slave->mtd.backing_dev_info = master->backing_dev_info;
|
||||
slave->backing_dev_info = master->backing_dev_info;
|
||||
|
||||
/* NOTE: we don't arrange MTDs as a tree; it'd be error-prone
|
||||
* to have the same data be in two different partitions.
|
||||
*/
|
||||
slave->mtd.dev.parent = master->dev.parent;
|
||||
slave->dev.parent = master->dev.parent;
|
||||
#endif
|
||||
|
||||
slave->mtd._read = part_read;
|
||||
slave->mtd._write = part_write;
|
||||
if (master->_read)
|
||||
slave->_read = part_read;
|
||||
if (master->_write)
|
||||
slave->_write = part_write;
|
||||
|
||||
if (master->_panic_write)
|
||||
slave->mtd._panic_write = part_panic_write;
|
||||
slave->_panic_write = part_panic_write;
|
||||
|
||||
#ifndef __UBOOT__
|
||||
if (master->_point && master->_unpoint) {
|
||||
slave->mtd._point = part_point;
|
||||
slave->mtd._unpoint = part_unpoint;
|
||||
slave->_point = part_point;
|
||||
slave->_unpoint = part_unpoint;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (master->_get_unmapped_area)
|
||||
slave->mtd._get_unmapped_area = part_get_unmapped_area;
|
||||
slave->_get_unmapped_area = part_get_unmapped_area;
|
||||
if (master->_read_oob)
|
||||
slave->mtd._read_oob = part_read_oob;
|
||||
slave->_read_oob = part_read_oob;
|
||||
if (master->_write_oob)
|
||||
slave->mtd._write_oob = part_write_oob;
|
||||
slave->_write_oob = part_write_oob;
|
||||
if (master->_read_user_prot_reg)
|
||||
slave->mtd._read_user_prot_reg = part_read_user_prot_reg;
|
||||
slave->_read_user_prot_reg = part_read_user_prot_reg;
|
||||
if (master->_read_fact_prot_reg)
|
||||
slave->mtd._read_fact_prot_reg = part_read_fact_prot_reg;
|
||||
slave->_read_fact_prot_reg = part_read_fact_prot_reg;
|
||||
if (master->_write_user_prot_reg)
|
||||
slave->mtd._write_user_prot_reg = part_write_user_prot_reg;
|
||||
slave->_write_user_prot_reg = part_write_user_prot_reg;
|
||||
if (master->_lock_user_prot_reg)
|
||||
slave->mtd._lock_user_prot_reg = part_lock_user_prot_reg;
|
||||
slave->_lock_user_prot_reg = part_lock_user_prot_reg;
|
||||
if (master->_get_user_prot_info)
|
||||
slave->mtd._get_user_prot_info = part_get_user_prot_info;
|
||||
slave->_get_user_prot_info = part_get_user_prot_info;
|
||||
if (master->_get_fact_prot_info)
|
||||
slave->mtd._get_fact_prot_info = part_get_fact_prot_info;
|
||||
slave->_get_fact_prot_info = part_get_fact_prot_info;
|
||||
if (master->_sync)
|
||||
slave->mtd._sync = part_sync;
|
||||
slave->_sync = part_sync;
|
||||
#ifndef __UBOOT__
|
||||
if (!partno && !master->dev.class && master->_suspend &&
|
||||
master->_resume) {
|
||||
slave->mtd._suspend = part_suspend;
|
||||
slave->mtd._resume = part_resume;
|
||||
slave->_suspend = part_suspend;
|
||||
slave->_resume = part_resume;
|
||||
}
|
||||
if (master->_writev)
|
||||
slave->mtd._writev = part_writev;
|
||||
slave->_writev = part_writev;
|
||||
#endif
|
||||
if (master->_lock)
|
||||
slave->mtd._lock = part_lock;
|
||||
slave->_lock = part_lock;
|
||||
if (master->_unlock)
|
||||
slave->mtd._unlock = part_unlock;
|
||||
slave->_unlock = part_unlock;
|
||||
if (master->_is_locked)
|
||||
slave->mtd._is_locked = part_is_locked;
|
||||
slave->_is_locked = part_is_locked;
|
||||
if (master->_block_isreserved)
|
||||
slave->mtd._block_isreserved = part_block_isreserved;
|
||||
slave->_block_isreserved = part_block_isreserved;
|
||||
if (master->_block_isbad)
|
||||
slave->mtd._block_isbad = part_block_isbad;
|
||||
slave->_block_isbad = part_block_isbad;
|
||||
if (master->_block_markbad)
|
||||
slave->mtd._block_markbad = part_block_markbad;
|
||||
slave->mtd._erase = part_erase;
|
||||
slave->master = master;
|
||||
slave->_block_markbad = part_block_markbad;
|
||||
slave->_erase = part_erase;
|
||||
slave->parent = master;
|
||||
slave->offset = part->offset;
|
||||
INIT_LIST_HEAD(&slave->partitions);
|
||||
INIT_LIST_HEAD(&slave->node);
|
||||
|
||||
if (slave->offset == MTDPART_OFS_APPEND)
|
||||
slave->offset = cur_offset;
|
||||
|
@ -489,41 +670,41 @@ static struct mtd_part *allocate_partition(struct mtd_info *master,
|
|||
}
|
||||
if (slave->offset == MTDPART_OFS_RETAIN) {
|
||||
slave->offset = cur_offset;
|
||||
if (master->size - slave->offset >= slave->mtd.size) {
|
||||
slave->mtd.size = master->size - slave->offset
|
||||
- slave->mtd.size;
|
||||
if (master->size - slave->offset >= slave->size) {
|
||||
slave->size = master->size - slave->offset
|
||||
- slave->size;
|
||||
} else {
|
||||
debug("mtd partition \"%s\" doesn't have enough space: %#llx < %#llx, disabled\n",
|
||||
part->name, master->size - slave->offset,
|
||||
slave->mtd.size);
|
||||
slave->size);
|
||||
/* register to preserve ordering */
|
||||
goto out_register;
|
||||
}
|
||||
}
|
||||
if (slave->mtd.size == MTDPART_SIZ_FULL)
|
||||
slave->mtd.size = master->size - slave->offset;
|
||||
if (slave->size == MTDPART_SIZ_FULL)
|
||||
slave->size = master->size - slave->offset;
|
||||
|
||||
debug("0x%012llx-0x%012llx : \"%s\"\n", (unsigned long long)slave->offset,
|
||||
(unsigned long long)(slave->offset + slave->mtd.size), slave->mtd.name);
|
||||
(unsigned long long)(slave->offset + slave->size), slave->name);
|
||||
|
||||
/* let's do some sanity checks */
|
||||
if (slave->offset >= master->size) {
|
||||
/* let's register it anyway to preserve ordering */
|
||||
slave->offset = 0;
|
||||
slave->mtd.size = 0;
|
||||
slave->size = 0;
|
||||
printk(KERN_ERR"mtd: partition \"%s\" is out of reach -- disabled\n",
|
||||
part->name);
|
||||
goto out_register;
|
||||
}
|
||||
if (slave->offset + slave->mtd.size > master->size) {
|
||||
slave->mtd.size = master->size - slave->offset;
|
||||
if (slave->offset + slave->size > master->size) {
|
||||
slave->size = master->size - slave->offset;
|
||||
printk(KERN_WARNING"mtd: partition \"%s\" extends beyond the end of device \"%s\" -- size truncated to %#llx\n",
|
||||
part->name, master->name, (unsigned long long)slave->mtd.size);
|
||||
part->name, master->name, slave->size);
|
||||
}
|
||||
if (master->numeraseregions > 1) {
|
||||
/* Deal with variable erase size stuff */
|
||||
int i, max = master->numeraseregions;
|
||||
u64 end = slave->offset + slave->mtd.size;
|
||||
u64 end = slave->offset + slave->size;
|
||||
struct mtd_erase_region_info *regions = master->eraseregions;
|
||||
|
||||
/* Find the first erase regions which is part of this
|
||||
|
@ -536,44 +717,43 @@ static struct mtd_part *allocate_partition(struct mtd_info *master,
|
|||
|
||||
/* Pick biggest erasesize */
|
||||
for (; i < max && regions[i].offset < end; i++) {
|
||||
if (slave->mtd.erasesize < regions[i].erasesize) {
|
||||
slave->mtd.erasesize = regions[i].erasesize;
|
||||
}
|
||||
if (slave->erasesize < regions[i].erasesize)
|
||||
slave->erasesize = regions[i].erasesize;
|
||||
}
|
||||
BUG_ON(slave->mtd.erasesize == 0);
|
||||
WARN_ON(slave->erasesize == 0);
|
||||
} else {
|
||||
/* Single erase size */
|
||||
slave->mtd.erasesize = master->erasesize;
|
||||
slave->erasesize = master->erasesize;
|
||||
}
|
||||
|
||||
if ((slave->mtd.flags & MTD_WRITEABLE) &&
|
||||
mtd_mod_by_eb(slave->offset, &slave->mtd)) {
|
||||
if ((slave->flags & MTD_WRITEABLE) &&
|
||||
mtd_mod_by_eb(slave->offset, slave)) {
|
||||
/* Doesn't start on a boundary of major erase size */
|
||||
/* FIXME: Let it be writable if it is on a boundary of
|
||||
* _minor_ erase size though */
|
||||
slave->mtd.flags &= ~MTD_WRITEABLE;
|
||||
slave->flags &= ~MTD_WRITEABLE;
|
||||
printk(KERN_WARNING"mtd: partition \"%s\" doesn't start on an erase block boundary -- force read-only\n",
|
||||
part->name);
|
||||
}
|
||||
if ((slave->mtd.flags & MTD_WRITEABLE) &&
|
||||
mtd_mod_by_eb(slave->mtd.size, &slave->mtd)) {
|
||||
slave->mtd.flags &= ~MTD_WRITEABLE;
|
||||
if ((slave->flags & MTD_WRITEABLE) &&
|
||||
mtd_mod_by_eb(slave->size, slave)) {
|
||||
slave->flags &= ~MTD_WRITEABLE;
|
||||
printk(KERN_WARNING"mtd: partition \"%s\" doesn't end on an erase block -- force read-only\n",
|
||||
part->name);
|
||||
}
|
||||
|
||||
slave->mtd.ecclayout = master->ecclayout;
|
||||
slave->mtd.ecc_step_size = master->ecc_step_size;
|
||||
slave->mtd.ecc_strength = master->ecc_strength;
|
||||
slave->mtd.bitflip_threshold = master->bitflip_threshold;
|
||||
slave->ecclayout = master->ecclayout;
|
||||
slave->ecc_step_size = master->ecc_step_size;
|
||||
slave->ecc_strength = master->ecc_strength;
|
||||
slave->bitflip_threshold = master->bitflip_threshold;
|
||||
|
||||
if (master->_block_isbad) {
|
||||
uint64_t offs = 0;
|
||||
|
||||
while (offs < slave->mtd.size) {
|
||||
while (offs < slave->size) {
|
||||
if (mtd_block_isbad(master, offs + slave->offset))
|
||||
slave->mtd.ecc_stats.badblocks++;
|
||||
offs += slave->mtd.erasesize;
|
||||
slave->ecc_stats.badblocks++;
|
||||
offs += slave->erasesize;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -586,7 +766,7 @@ int mtd_add_partition(struct mtd_info *master, const char *name,
|
|||
long long offset, long long length)
|
||||
{
|
||||
struct mtd_partition part;
|
||||
struct mtd_part *p, *new;
|
||||
struct mtd_info *p, *new;
|
||||
uint64_t start, end;
|
||||
int ret = 0;
|
||||
|
||||
|
@ -615,21 +795,20 @@ int mtd_add_partition(struct mtd_info *master, const char *name,
|
|||
end = offset + length;
|
||||
|
||||
mutex_lock(&mtd_partitions_mutex);
|
||||
list_for_each_entry(p, &mtd_partitions, list)
|
||||
if (p->master == master) {
|
||||
if ((start >= p->offset) &&
|
||||
(start < (p->offset + p->mtd.size)))
|
||||
goto err_inv;
|
||||
list_for_each_entry(p, &master->partitions, node) {
|
||||
if (start >= p->offset &&
|
||||
(start < (p->offset + p->size)))
|
||||
goto err_inv;
|
||||
|
||||
if ((end >= p->offset) &&
|
||||
(end < (p->offset + p->mtd.size)))
|
||||
goto err_inv;
|
||||
}
|
||||
if (end >= p->offset &&
|
||||
(end < (p->offset + p->size)))
|
||||
goto err_inv;
|
||||
}
|
||||
|
||||
list_add(&new->list, &mtd_partitions);
|
||||
list_add_tail(&new->node, &master->partitions);
|
||||
mutex_unlock(&mtd_partitions_mutex);
|
||||
|
||||
add_mtd_device(&new->mtd);
|
||||
add_mtd_device(new);
|
||||
|
||||
return ret;
|
||||
err_inv:
|
||||
|
@ -641,18 +820,17 @@ EXPORT_SYMBOL_GPL(mtd_add_partition);
|
|||
|
||||
int mtd_del_partition(struct mtd_info *master, int partno)
|
||||
{
|
||||
struct mtd_part *slave, *next;
|
||||
struct mtd_info *slave, *next;
|
||||
int ret = -EINVAL;
|
||||
|
||||
mutex_lock(&mtd_partitions_mutex);
|
||||
list_for_each_entry_safe(slave, next, &mtd_partitions, list)
|
||||
if ((slave->master == master) &&
|
||||
(slave->mtd.index == partno)) {
|
||||
ret = del_mtd_device(&slave->mtd);
|
||||
list_for_each_entry_safe(slave, next, &master->partitions, node)
|
||||
if (slave->index == partno) {
|
||||
ret = del_mtd_device(slave);
|
||||
if (ret < 0)
|
||||
break;
|
||||
|
||||
list_del(&slave->list);
|
||||
list_del(&slave->node);
|
||||
free_partition(slave);
|
||||
break;
|
||||
}
|
||||
|
@ -676,20 +854,10 @@ int add_mtd_partitions(struct mtd_info *master,
|
|||
const struct mtd_partition *parts,
|
||||
int nbparts)
|
||||
{
|
||||
struct mtd_part *slave;
|
||||
struct mtd_info *slave;
|
||||
uint64_t cur_offset = 0;
|
||||
int i;
|
||||
|
||||
#ifdef __UBOOT__
|
||||
/*
|
||||
* Need to init the list here, since LIST_INIT() does not
|
||||
* work on platforms where relocation has problems (like MIPS
|
||||
* & PPC).
|
||||
*/
|
||||
if (mtd_partitions.next == NULL)
|
||||
INIT_LIST_HEAD(&mtd_partitions);
|
||||
#endif
|
||||
|
||||
debug("Creating %d MTD partitions on \"%s\":\n", nbparts, master->name);
|
||||
|
||||
for (i = 0; i < nbparts; i++) {
|
||||
|
@ -698,12 +866,12 @@ int add_mtd_partitions(struct mtd_info *master,
|
|||
return PTR_ERR(slave);
|
||||
|
||||
mutex_lock(&mtd_partitions_mutex);
|
||||
list_add(&slave->list, &mtd_partitions);
|
||||
list_add_tail(&slave->node, &master->partitions);
|
||||
mutex_unlock(&mtd_partitions_mutex);
|
||||
|
||||
add_mtd_device(&slave->mtd);
|
||||
add_mtd_device(slave);
|
||||
|
||||
cur_offset = slave->offset + slave->mtd.size;
|
||||
cur_offset = slave->offset + slave->size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -806,29 +974,12 @@ int parse_mtd_partitions(struct mtd_info *master, const char *const *types,
|
|||
}
|
||||
#endif
|
||||
|
||||
int mtd_is_partition(const struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_part *part;
|
||||
int ispart = 0;
|
||||
|
||||
mutex_lock(&mtd_partitions_mutex);
|
||||
list_for_each_entry(part, &mtd_partitions, list)
|
||||
if (&part->mtd == mtd) {
|
||||
ispart = 1;
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&mtd_partitions_mutex);
|
||||
|
||||
return ispart;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_is_partition);
|
||||
|
||||
/* Returns the size of the entire flash chip */
|
||||
uint64_t mtd_get_device_size(const struct mtd_info *mtd)
|
||||
{
|
||||
if (!mtd_is_partition(mtd))
|
||||
return mtd->size;
|
||||
if (mtd_is_partition(mtd))
|
||||
return mtd->parent->size;
|
||||
|
||||
return PART(mtd)->master->size;
|
||||
return mtd->size;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_get_device_size);
|
||||
|
|
|
@ -1,297 +1,6 @@
|
|||
config MTD_NAND_CORE
|
||||
tristate
|
||||
|
||||
menuconfig NAND
|
||||
bool "NAND Device Support"
|
||||
if NAND
|
||||
source "drivers/mtd/nand/raw/Kconfig"
|
||||
|
||||
config SYS_NAND_SELF_INIT
|
||||
bool
|
||||
help
|
||||
This option, if enabled, provides more flexible and linux-like
|
||||
NAND initialization process.
|
||||
|
||||
config NAND_ATMEL
|
||||
bool "Support Atmel NAND controller"
|
||||
imply SYS_NAND_USE_FLASH_BBT
|
||||
help
|
||||
Enable this driver for NAND flash platforms using an Atmel NAND
|
||||
controller.
|
||||
|
||||
config NAND_DAVINCI
|
||||
bool "Support TI Davinci NAND controller"
|
||||
help
|
||||
Enable this driver for NAND flash controllers available in TI Davinci
|
||||
and Keystone2 platforms
|
||||
|
||||
config NAND_DENALI
|
||||
bool
|
||||
select SYS_NAND_SELF_INIT
|
||||
imply CMD_NAND
|
||||
|
||||
config NAND_DENALI_DT
|
||||
bool "Support Denali NAND controller as a DT device"
|
||||
select NAND_DENALI
|
||||
depends on OF_CONTROL && DM
|
||||
help
|
||||
Enable the driver for NAND flash on platforms using a Denali NAND
|
||||
controller as a DT device.
|
||||
|
||||
config NAND_DENALI_SPARE_AREA_SKIP_BYTES
|
||||
int "Number of bytes skipped in OOB area"
|
||||
depends on NAND_DENALI
|
||||
range 0 63
|
||||
help
|
||||
This option specifies the number of bytes to skip from the beginning
|
||||
of OOB area before last ECC sector data starts. This is potentially
|
||||
used to preserve the bad block marker in the OOB area.
|
||||
|
||||
config NAND_LPC32XX_SLC
|
||||
bool "Support LPC32XX_SLC controller"
|
||||
help
|
||||
Enable the LPC32XX SLC NAND controller.
|
||||
|
||||
config NAND_OMAP_GPMC
|
||||
bool "Support OMAP GPMC NAND controller"
|
||||
depends on ARCH_OMAP2PLUS
|
||||
help
|
||||
Enables omap_gpmc.c driver for OMAPx and AMxxxx platforms.
|
||||
GPMC controller is used for parallel NAND flash devices, and can
|
||||
do ECC calculation (not ECC error detection) for HAM1, BCH4, BCH8
|
||||
and BCH16 ECC algorithms.
|
||||
|
||||
config NAND_OMAP_GPMC_PREFETCH
|
||||
bool "Enable GPMC Prefetch"
|
||||
depends on NAND_OMAP_GPMC
|
||||
default y
|
||||
help
|
||||
On OMAP platforms that use the GPMC controller
|
||||
(CONFIG_NAND_OMAP_GPMC_PREFETCH), this options enables the code that
|
||||
uses the prefetch mode to speed up read operations.
|
||||
|
||||
config NAND_OMAP_ELM
|
||||
bool "Enable ELM driver for OMAPxx and AMxx platforms."
|
||||
depends on NAND_OMAP_GPMC && !OMAP34XX
|
||||
help
|
||||
ELM controller is used for ECC error detection (not ECC calculation)
|
||||
of BCH4, BCH8 and BCH16 ECC algorithms.
|
||||
Some legacy platforms like OMAP3xx do not have in-built ELM h/w engine,
|
||||
thus such SoC platforms need to depend on software library for ECC error
|
||||
detection. However ECC calculation on such plaforms would still be
|
||||
done by GPMC controller.
|
||||
|
||||
config NAND_VF610_NFC
|
||||
bool "Support for Freescale NFC for VF610"
|
||||
select SYS_NAND_SELF_INIT
|
||||
imply CMD_NAND
|
||||
help
|
||||
Enables support for NAND Flash Controller on some Freescale
|
||||
processors like the VF610, MCF54418 or Kinetis K70.
|
||||
The driver supports a maximum 2k page size. The driver
|
||||
currently does not support hardware ECC.
|
||||
|
||||
choice
|
||||
prompt "Hardware ECC strength"
|
||||
depends on NAND_VF610_NFC
|
||||
default SYS_NAND_VF610_NFC_45_ECC_BYTES
|
||||
help
|
||||
Select the ECC strength used in the hardware BCH ECC block.
|
||||
|
||||
config SYS_NAND_VF610_NFC_45_ECC_BYTES
|
||||
bool "24-error correction (45 ECC bytes)"
|
||||
|
||||
config SYS_NAND_VF610_NFC_60_ECC_BYTES
|
||||
bool "32-error correction (60 ECC bytes)"
|
||||
|
||||
endchoice
|
||||
|
||||
config NAND_PXA3XX
|
||||
bool "Support for NAND on PXA3xx and Armada 370/XP/38x"
|
||||
select SYS_NAND_SELF_INIT
|
||||
imply CMD_NAND
|
||||
help
|
||||
This enables the driver for the NAND flash device found on
|
||||
PXA3xx processors (NFCv1) and also on Armada 370/XP (NFCv2).
|
||||
|
||||
config NAND_SUNXI
|
||||
bool "Support for NAND on Allwinner SoCs"
|
||||
default ARCH_SUNXI
|
||||
depends on MACH_SUN4I || MACH_SUN5I || MACH_SUN7I || MACH_SUN8I
|
||||
select SYS_NAND_SELF_INIT
|
||||
select SYS_NAND_U_BOOT_LOCATIONS
|
||||
select SPL_NAND_SUPPORT
|
||||
imply CMD_NAND
|
||||
---help---
|
||||
Enable support for NAND. This option enables the standard and
|
||||
SPL drivers.
|
||||
The SPL driver only supports reading from the NAND using DMA
|
||||
transfers.
|
||||
|
||||
if NAND_SUNXI
|
||||
|
||||
config NAND_SUNXI_SPL_ECC_STRENGTH
|
||||
int "Allwinner NAND SPL ECC Strength"
|
||||
default 64
|
||||
|
||||
config NAND_SUNXI_SPL_ECC_SIZE
|
||||
int "Allwinner NAND SPL ECC Step Size"
|
||||
default 1024
|
||||
|
||||
config NAND_SUNXI_SPL_USABLE_PAGE_SIZE
|
||||
int "Allwinner NAND SPL Usable Page Size"
|
||||
default 1024
|
||||
|
||||
endif
|
||||
|
||||
config NAND_ARASAN
|
||||
bool "Configure Arasan Nand"
|
||||
select SYS_NAND_SELF_INIT
|
||||
imply CMD_NAND
|
||||
help
|
||||
This enables Nand driver support for Arasan nand flash
|
||||
controller. This uses the hardware ECC for read and
|
||||
write operations.
|
||||
|
||||
config NAND_MXC
|
||||
bool "MXC NAND support"
|
||||
depends on CPU_ARM926EJS || CPU_ARM1136 || MX5
|
||||
imply CMD_NAND
|
||||
help
|
||||
This enables the NAND driver for the NAND flash controller on the
|
||||
i.MX27 / i.MX31 / i.MX5 rocessors.
|
||||
|
||||
config NAND_MXS
|
||||
bool "MXS NAND support"
|
||||
depends on MX23 || MX28 || MX6 || MX7
|
||||
select SYS_NAND_SELF_INIT
|
||||
imply CMD_NAND
|
||||
select APBH_DMA
|
||||
select APBH_DMA_BURST if ARCH_MX6 || ARCH_MX7
|
||||
select APBH_DMA_BURST8 if ARCH_MX6 || ARCH_MX7
|
||||
help
|
||||
This enables NAND driver for the NAND flash controller on the
|
||||
MXS processors.
|
||||
|
||||
if NAND_MXS
|
||||
|
||||
config NAND_MXS_DT
|
||||
bool "Support MXS NAND controller as a DT device"
|
||||
depends on OF_CONTROL && MTD
|
||||
help
|
||||
Enable the driver for MXS NAND flash on platforms using
|
||||
device tree.
|
||||
|
||||
config NAND_MXS_USE_MINIMUM_ECC
|
||||
bool "Use minimum ECC strength supported by the controller"
|
||||
default false
|
||||
|
||||
endif
|
||||
|
||||
config NAND_ZYNQ
|
||||
bool "Support for Zynq Nand controller"
|
||||
select SYS_NAND_SELF_INIT
|
||||
imply CMD_NAND
|
||||
help
|
||||
This enables Nand driver support for Nand flash controller
|
||||
found on Zynq SoC.
|
||||
|
||||
config NAND_ZYNQ_USE_BOOTLOADER1_TIMINGS
|
||||
bool "Enable use of 1st stage bootloader timing for NAND"
|
||||
depends on NAND_ZYNQ
|
||||
help
|
||||
This flag prevent U-boot reconfigure NAND flash controller and reuse
|
||||
the NAND timing from 1st stage bootloader.
|
||||
|
||||
comment "Generic NAND options"
|
||||
|
||||
config SYS_NAND_BLOCK_SIZE
|
||||
hex "NAND chip eraseblock size"
|
||||
depends on ARCH_SUNXI
|
||||
help
|
||||
Number of data bytes in one eraseblock for the NAND chip on the
|
||||
board. This is the multiple of NAND_PAGE_SIZE and the number of
|
||||
pages.
|
||||
|
||||
config SYS_NAND_PAGE_SIZE
|
||||
hex "NAND chip page size"
|
||||
depends on ARCH_SUNXI
|
||||
help
|
||||
Number of data bytes in one page for the NAND chip on the
|
||||
board, not including the OOB area.
|
||||
|
||||
config SYS_NAND_OOBSIZE
|
||||
hex "NAND chip OOB size"
|
||||
depends on ARCH_SUNXI
|
||||
help
|
||||
Number of bytes in the Out-Of-Band area for the NAND chip on
|
||||
the board.
|
||||
|
||||
# Enhance depends when converting drivers to Kconfig which use this config
|
||||
# option (mxc_nand, ndfc, omap_gpmc).
|
||||
config SYS_NAND_BUSWIDTH_16BIT
|
||||
bool "Use 16-bit NAND interface"
|
||||
depends on NAND_VF610_NFC || NAND_OMAP_GPMC || NAND_MXC || ARCH_DAVINCI
|
||||
help
|
||||
Indicates that NAND device has 16-bit wide data-bus. In absence of this
|
||||
config, bus-width of NAND device is assumed to be either 8-bit and later
|
||||
determined by reading ONFI params.
|
||||
Above config is useful when NAND device's bus-width information cannot
|
||||
be determined from on-chip ONFI params, like in following scenarios:
|
||||
- SPL boot does not support reading of ONFI parameters. This is done to
|
||||
keep SPL code foot-print small.
|
||||
- In current U-Boot flow using nand_init(), driver initialization
|
||||
happens in board_nand_init() which is called before any device probe
|
||||
(nand_scan_ident + nand_scan_tail), thus device's ONFI parameters are
|
||||
not available while configuring controller. So a static CONFIG_NAND_xx
|
||||
is needed to know the device's bus-width in advance.
|
||||
|
||||
if SPL
|
||||
|
||||
config SYS_NAND_U_BOOT_LOCATIONS
|
||||
bool "Define U-boot binaries locations in NAND"
|
||||
help
|
||||
Enable CONFIG_SYS_NAND_U_BOOT_OFFS though Kconfig.
|
||||
This option should not be enabled when compiling U-boot for boards
|
||||
defining CONFIG_SYS_NAND_U_BOOT_OFFS in their include/configs/<board>.h
|
||||
file.
|
||||
|
||||
config SYS_NAND_U_BOOT_OFFS
|
||||
hex "Location in NAND to read U-Boot from"
|
||||
default 0x800000 if NAND_SUNXI
|
||||
depends on SYS_NAND_U_BOOT_LOCATIONS
|
||||
help
|
||||
Set the offset from the start of the nand where u-boot should be
|
||||
loaded from.
|
||||
|
||||
config SYS_NAND_U_BOOT_OFFS_REDUND
|
||||
hex "Location in NAND to read U-Boot from"
|
||||
default SYS_NAND_U_BOOT_OFFS
|
||||
depends on SYS_NAND_U_BOOT_LOCATIONS
|
||||
help
|
||||
Set the offset from the start of the nand where the redundant u-boot
|
||||
should be loaded from.
|
||||
|
||||
config SPL_NAND_AM33XX_BCH
|
||||
bool "Enables SPL-NAND driver which supports ELM based"
|
||||
depends on NAND_OMAP_GPMC && !OMAP34XX
|
||||
default y
|
||||
help
|
||||
Hardware ECC correction. This is useful for platforms which have ELM
|
||||
hardware engine and use NAND boot mode.
|
||||
Some legacy platforms like OMAP3xx do not have in-built ELM h/w engine,
|
||||
so those platforms should use CONFIG_SPL_NAND_SIMPLE for enabling
|
||||
SPL-NAND driver with software ECC correction support.
|
||||
|
||||
config SPL_NAND_DENALI
|
||||
bool "Support Denali NAND controller for SPL"
|
||||
help
|
||||
This is a small implementation of the Denali NAND controller
|
||||
for use on SPL.
|
||||
|
||||
config SPL_NAND_SIMPLE
|
||||
bool "Use simple SPL NAND driver"
|
||||
depends on !SPL_NAND_AM33XX_BCH
|
||||
help
|
||||
Support for NAND boot using simple NAND drivers that
|
||||
expose the cmd_ctrl() interface.
|
||||
endif
|
||||
|
||||
endif # if NAND
|
||||
source "drivers/mtd/nand/spi/Kconfig"
|
||||
|
|
|
@ -1,77 +1,5 @@
|
|||
# SPDX-License-Identifier: GPL-2.0+
|
||||
#
|
||||
# (C) Copyright 2006
|
||||
# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
|
||||
|
||||
ifdef CONFIG_SPL_BUILD
|
||||
|
||||
ifdef CONFIG_SPL_NAND_DRIVERS
|
||||
NORMAL_DRIVERS=y
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_SPL_NAND_AM33XX_BCH) += am335x_spl_bch.o
|
||||
obj-$(CONFIG_SPL_NAND_DENALI) += denali_spl.o
|
||||
obj-$(CONFIG_SPL_NAND_SIMPLE) += nand_spl_simple.o
|
||||
obj-$(CONFIG_SPL_NAND_LOAD) += nand_spl_load.o
|
||||
obj-$(CONFIG_SPL_NAND_ECC) += nand_ecc.o
|
||||
obj-$(CONFIG_SPL_NAND_BASE) += nand_base.o
|
||||
obj-$(CONFIG_SPL_NAND_IDENT) += nand_ids.o nand_timings.o
|
||||
obj-$(CONFIG_SPL_NAND_INIT) += nand.o
|
||||
ifeq ($(CONFIG_SPL_ENV_SUPPORT),y)
|
||||
obj-$(CONFIG_ENV_IS_IN_NAND) += nand_util.o
|
||||
endif
|
||||
|
||||
else # not spl
|
||||
|
||||
NORMAL_DRIVERS=y
|
||||
|
||||
obj-y += nand.o
|
||||
obj-y += nand_bbt.o
|
||||
obj-y += nand_ids.o
|
||||
obj-y += nand_util.o
|
||||
obj-y += nand_ecc.o
|
||||
obj-y += nand_base.o
|
||||
obj-y += nand_timings.o
|
||||
|
||||
endif # not spl
|
||||
|
||||
ifdef NORMAL_DRIVERS
|
||||
|
||||
obj-$(CONFIG_NAND_ECC_BCH) += nand_bch.o
|
||||
|
||||
obj-$(CONFIG_NAND_ATMEL) += atmel_nand.o
|
||||
obj-$(CONFIG_NAND_ARASAN) += arasan_nfc.o
|
||||
obj-$(CONFIG_NAND_DAVINCI) += davinci_nand.o
|
||||
obj-$(CONFIG_NAND_DENALI) += denali.o
|
||||
obj-$(CONFIG_NAND_DENALI_DT) += denali_dt.o
|
||||
obj-$(CONFIG_NAND_FSL_ELBC) += fsl_elbc_nand.o
|
||||
obj-$(CONFIG_NAND_FSL_IFC) += fsl_ifc_nand.o
|
||||
obj-$(CONFIG_NAND_FSL_UPM) += fsl_upm.o
|
||||
obj-$(CONFIG_NAND_FSMC) += fsmc_nand.o
|
||||
obj-$(CONFIG_NAND_KB9202) += kb9202_nand.o
|
||||
obj-$(CONFIG_NAND_KIRKWOOD) += kirkwood_nand.o
|
||||
obj-$(CONFIG_NAND_KMETER1) += kmeter1_nand.o
|
||||
obj-$(CONFIG_NAND_LPC32XX_MLC) += lpc32xx_nand_mlc.o
|
||||
obj-$(CONFIG_NAND_LPC32XX_SLC) += lpc32xx_nand_slc.o
|
||||
obj-$(CONFIG_NAND_VF610_NFC) += vf610_nfc.o
|
||||
obj-$(CONFIG_NAND_MXC) += mxc_nand.o
|
||||
obj-$(CONFIG_NAND_MXS) += mxs_nand.o
|
||||
obj-$(CONFIG_NAND_MXS_DT) += mxs_nand_dt.o
|
||||
obj-$(CONFIG_NAND_PXA3XX) += pxa3xx_nand.o
|
||||
obj-$(CONFIG_NAND_SPEAR) += spr_nand.o
|
||||
obj-$(CONFIG_TEGRA_NAND) += tegra_nand.o
|
||||
obj-$(CONFIG_NAND_OMAP_GPMC) += omap_gpmc.o
|
||||
obj-$(CONFIG_NAND_OMAP_ELM) += omap_elm.o
|
||||
obj-$(CONFIG_NAND_PLAT) += nand_plat.o
|
||||
obj-$(CONFIG_NAND_SUNXI) += sunxi_nand.o
|
||||
obj-$(CONFIG_NAND_ZYNQ) += zynq_nand.o
|
||||
|
||||
else # minimal SPL drivers
|
||||
|
||||
obj-$(CONFIG_NAND_FSL_ELBC) += fsl_elbc_spl.o
|
||||
obj-$(CONFIG_NAND_FSL_IFC) += fsl_ifc_spl.o
|
||||
obj-$(CONFIG_NAND_MXC) += mxc_nand_spl.o
|
||||
obj-$(CONFIG_NAND_MXS) += mxs_nand_spl.o mxs_nand.o
|
||||
obj-$(CONFIG_NAND_SUNXI) += sunxi_nand_spl.o
|
||||
|
||||
endif # drivers
|
||||
nandcore-objs := core.o bbt.o
|
||||
obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o
|
||||
obj-$(CONFIG_MTD_SPI_NAND) += spi/
|
||||
|
|
132
drivers/mtd/nand/bbt.c
Normal file
132
drivers/mtd/nand/bbt.c
Normal file
|
@ -0,0 +1,132 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2017 Free Electrons
|
||||
*
|
||||
* Authors:
|
||||
* Boris Brezillon <boris.brezillon@free-electrons.com>
|
||||
* Peter Pan <peterpandong@micron.com>
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "nand-bbt: " fmt
|
||||
|
||||
#include <linux/mtd/nand.h>
|
||||
#ifndef __UBOOT__
|
||||
#include <linux/slab.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* nanddev_bbt_init() - Initialize the BBT (Bad Block Table)
|
||||
* @nand: NAND device
|
||||
*
|
||||
* Initialize the in-memory BBT.
|
||||
*
|
||||
* Return: 0 in case of success, a negative error code otherwise.
|
||||
*/
|
||||
int nanddev_bbt_init(struct nand_device *nand)
|
||||
{
|
||||
unsigned int bits_per_block = fls(NAND_BBT_BLOCK_NUM_STATUS);
|
||||
unsigned int nblocks = nanddev_neraseblocks(nand);
|
||||
unsigned int nwords = DIV_ROUND_UP(nblocks * bits_per_block,
|
||||
BITS_PER_LONG);
|
||||
|
||||
nand->bbt.cache = kzalloc(nwords, GFP_KERNEL);
|
||||
if (!nand->bbt.cache)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nanddev_bbt_init);
|
||||
|
||||
/**
|
||||
* nanddev_bbt_cleanup() - Cleanup the BBT (Bad Block Table)
|
||||
* @nand: NAND device
|
||||
*
|
||||
* Undoes what has been done in nanddev_bbt_init()
|
||||
*/
|
||||
void nanddev_bbt_cleanup(struct nand_device *nand)
|
||||
{
|
||||
kfree(nand->bbt.cache);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nanddev_bbt_cleanup);
|
||||
|
||||
/**
|
||||
* nanddev_bbt_update() - Update a BBT
|
||||
* @nand: nand device
|
||||
*
|
||||
* Update the BBT. Currently a NOP function since on-flash bbt is not yet
|
||||
* supported.
|
||||
*
|
||||
* Return: 0 in case of success, a negative error code otherwise.
|
||||
*/
|
||||
int nanddev_bbt_update(struct nand_device *nand)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nanddev_bbt_update);
|
||||
|
||||
/**
|
||||
* nanddev_bbt_get_block_status() - Return the status of an eraseblock
|
||||
* @nand: nand device
|
||||
* @entry: the BBT entry
|
||||
*
|
||||
* Return: a positive number nand_bbt_block_status status or -%ERANGE if @entry
|
||||
* is bigger than the BBT size.
|
||||
*/
|
||||
int nanddev_bbt_get_block_status(const struct nand_device *nand,
|
||||
unsigned int entry)
|
||||
{
|
||||
unsigned int bits_per_block = fls(NAND_BBT_BLOCK_NUM_STATUS);
|
||||
unsigned long *pos = nand->bbt.cache +
|
||||
((entry * bits_per_block) / BITS_PER_LONG);
|
||||
unsigned int offs = (entry * bits_per_block) % BITS_PER_LONG;
|
||||
unsigned long status;
|
||||
|
||||
if (entry >= nanddev_neraseblocks(nand))
|
||||
return -ERANGE;
|
||||
|
||||
status = pos[0] >> offs;
|
||||
if (bits_per_block + offs > BITS_PER_LONG)
|
||||
status |= pos[1] << (BITS_PER_LONG - offs);
|
||||
|
||||
return status & GENMASK(bits_per_block - 1, 0);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nanddev_bbt_get_block_status);
|
||||
|
||||
/**
|
||||
* nanddev_bbt_set_block_status() - Update the status of an eraseblock in the
|
||||
* in-memory BBT
|
||||
* @nand: nand device
|
||||
* @entry: the BBT entry to update
|
||||
* @status: the new status
|
||||
*
|
||||
* Update an entry of the in-memory BBT. If you want to push the updated BBT
|
||||
* the NAND you should call nanddev_bbt_update().
|
||||
*
|
||||
* Return: 0 in case of success or -%ERANGE if @entry is bigger than the BBT
|
||||
* size.
|
||||
*/
|
||||
int nanddev_bbt_set_block_status(struct nand_device *nand, unsigned int entry,
|
||||
enum nand_bbt_block_status status)
|
||||
{
|
||||
unsigned int bits_per_block = fls(NAND_BBT_BLOCK_NUM_STATUS);
|
||||
unsigned long *pos = nand->bbt.cache +
|
||||
((entry * bits_per_block) / BITS_PER_LONG);
|
||||
unsigned int offs = (entry * bits_per_block) % BITS_PER_LONG;
|
||||
unsigned long val = status & GENMASK(bits_per_block - 1, 0);
|
||||
|
||||
if (entry >= nanddev_neraseblocks(nand))
|
||||
return -ERANGE;
|
||||
|
||||
pos[0] &= ~GENMASK(offs + bits_per_block - 1, offs);
|
||||
pos[0] |= val << offs;
|
||||
|
||||
if (bits_per_block + offs > BITS_PER_LONG) {
|
||||
unsigned int rbits = bits_per_block + offs - BITS_PER_LONG;
|
||||
|
||||
pos[1] &= ~GENMASK(rbits - 1, 0);
|
||||
pos[1] |= val >> rbits;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nanddev_bbt_set_block_status);
|
243
drivers/mtd/nand/core.c
Normal file
243
drivers/mtd/nand/core.c
Normal file
|
@ -0,0 +1,243 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2017 Free Electrons
|
||||
*
|
||||
* Authors:
|
||||
* Boris Brezillon <boris.brezillon@free-electrons.com>
|
||||
* Peter Pan <peterpandong@micron.com>
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "nand: " fmt
|
||||
|
||||
#ifndef __UBOOT__
|
||||
#include <linux/module.h>
|
||||
#endif
|
||||
#include <linux/mtd/nand.h>
|
||||
|
||||
/**
|
||||
* nanddev_isbad() - Check if a block is bad
|
||||
* @nand: NAND device
|
||||
* @pos: position pointing to the block we want to check
|
||||
*
|
||||
* Return: true if the block is bad, false otherwise.
|
||||
*/
|
||||
bool nanddev_isbad(struct nand_device *nand, const struct nand_pos *pos)
|
||||
{
|
||||
if (nanddev_bbt_is_initialized(nand)) {
|
||||
unsigned int entry;
|
||||
int status;
|
||||
|
||||
entry = nanddev_bbt_pos_to_entry(nand, pos);
|
||||
status = nanddev_bbt_get_block_status(nand, entry);
|
||||
/* Lazy block status retrieval */
|
||||
if (status == NAND_BBT_BLOCK_STATUS_UNKNOWN) {
|
||||
if (nand->ops->isbad(nand, pos))
|
||||
status = NAND_BBT_BLOCK_FACTORY_BAD;
|
||||
else
|
||||
status = NAND_BBT_BLOCK_GOOD;
|
||||
|
||||
nanddev_bbt_set_block_status(nand, entry, status);
|
||||
}
|
||||
|
||||
if (status == NAND_BBT_BLOCK_WORN ||
|
||||
status == NAND_BBT_BLOCK_FACTORY_BAD)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return nand->ops->isbad(nand, pos);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nanddev_isbad);
|
||||
|
||||
/**
|
||||
* nanddev_markbad() - Mark a block as bad
|
||||
* @nand: NAND device
|
||||
* @pos: position of the block to mark bad
|
||||
*
|
||||
* Mark a block bad. This function is updating the BBT if available and
|
||||
* calls the low-level markbad hook (nand->ops->markbad()).
|
||||
*
|
||||
* Return: 0 in case of success, a negative error code otherwise.
|
||||
*/
|
||||
int nanddev_markbad(struct nand_device *nand, const struct nand_pos *pos)
|
||||
{
|
||||
struct mtd_info *mtd = nanddev_to_mtd(nand);
|
||||
unsigned int entry;
|
||||
int ret = 0;
|
||||
|
||||
if (nanddev_isbad(nand, pos))
|
||||
return 0;
|
||||
|
||||
ret = nand->ops->markbad(nand, pos);
|
||||
if (ret)
|
||||
pr_warn("failed to write BBM to block @%llx (err = %d)\n",
|
||||
nanddev_pos_to_offs(nand, pos), ret);
|
||||
|
||||
if (!nanddev_bbt_is_initialized(nand))
|
||||
goto out;
|
||||
|
||||
entry = nanddev_bbt_pos_to_entry(nand, pos);
|
||||
ret = nanddev_bbt_set_block_status(nand, entry, NAND_BBT_BLOCK_WORN);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = nanddev_bbt_update(nand);
|
||||
|
||||
out:
|
||||
if (!ret)
|
||||
mtd->ecc_stats.badblocks++;
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nanddev_markbad);
|
||||
|
||||
/**
|
||||
* nanddev_isreserved() - Check whether an eraseblock is reserved or not
|
||||
* @nand: NAND device
|
||||
* @pos: NAND position to test
|
||||
*
|
||||
* Checks whether the eraseblock pointed by @pos is reserved or not.
|
||||
*
|
||||
* Return: true if the eraseblock is reserved, false otherwise.
|
||||
*/
|
||||
bool nanddev_isreserved(struct nand_device *nand, const struct nand_pos *pos)
|
||||
{
|
||||
unsigned int entry;
|
||||
int status;
|
||||
|
||||
if (!nanddev_bbt_is_initialized(nand))
|
||||
return false;
|
||||
|
||||
/* Return info from the table */
|
||||
entry = nanddev_bbt_pos_to_entry(nand, pos);
|
||||
status = nanddev_bbt_get_block_status(nand, entry);
|
||||
return status == NAND_BBT_BLOCK_RESERVED;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nanddev_isreserved);
|
||||
|
||||
/**
|
||||
* nanddev_erase() - Erase a NAND portion
|
||||
* @nand: NAND device
|
||||
* @pos: position of the block to erase
|
||||
*
|
||||
* Erases the block if it's not bad.
|
||||
*
|
||||
* Return: 0 in case of success, a negative error code otherwise.
|
||||
*/
|
||||
int nanddev_erase(struct nand_device *nand, const struct nand_pos *pos)
|
||||
{
|
||||
if (nanddev_isbad(nand, pos) || nanddev_isreserved(nand, pos)) {
|
||||
pr_warn("attempt to erase a bad/reserved block @%llx\n",
|
||||
nanddev_pos_to_offs(nand, pos));
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return nand->ops->erase(nand, pos);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nanddev_erase);
|
||||
|
||||
/**
|
||||
* nanddev_mtd_erase() - Generic mtd->_erase() implementation for NAND devices
|
||||
* @mtd: MTD device
|
||||
* @einfo: erase request
|
||||
*
|
||||
* This is a simple mtd->_erase() implementation iterating over all blocks
|
||||
* concerned by @einfo and calling nand->ops->erase() on each of them.
|
||||
*
|
||||
* Note that mtd->_erase should not be directly assigned to this helper,
|
||||
* because there's no locking here. NAND specialized layers should instead
|
||||
* implement there own wrapper around nanddev_mtd_erase() taking the
|
||||
* appropriate lock before calling nanddev_mtd_erase().
|
||||
*
|
||||
* Return: 0 in case of success, a negative error code otherwise.
|
||||
*/
|
||||
int nanddev_mtd_erase(struct mtd_info *mtd, struct erase_info *einfo)
|
||||
{
|
||||
struct nand_device *nand = mtd_to_nanddev(mtd);
|
||||
struct nand_pos pos, last;
|
||||
int ret;
|
||||
|
||||
nanddev_offs_to_pos(nand, einfo->addr, &pos);
|
||||
nanddev_offs_to_pos(nand, einfo->addr + einfo->len - 1, &last);
|
||||
while (nanddev_pos_cmp(&pos, &last) <= 0) {
|
||||
ret = nanddev_erase(nand, &pos);
|
||||
if (ret) {
|
||||
einfo->fail_addr = nanddev_pos_to_offs(nand, &pos);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
nanddev_pos_next_eraseblock(nand, &pos);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nanddev_mtd_erase);
|
||||
|
||||
/**
|
||||
* nanddev_init() - Initialize a NAND device
|
||||
* @nand: NAND device
|
||||
* @ops: NAND device operations
|
||||
* @owner: NAND device owner
|
||||
*
|
||||
* Initializes a NAND device object. Consistency checks are done on @ops and
|
||||
* @nand->memorg. Also takes care of initializing the BBT.
|
||||
*
|
||||
* Return: 0 in case of success, a negative error code otherwise.
|
||||
*/
|
||||
int nanddev_init(struct nand_device *nand, const struct nand_ops *ops,
|
||||
struct module *owner)
|
||||
{
|
||||
struct mtd_info *mtd = nanddev_to_mtd(nand);
|
||||
struct nand_memory_organization *memorg = nanddev_get_memorg(nand);
|
||||
|
||||
if (!nand || !ops)
|
||||
return -EINVAL;
|
||||
|
||||
if (!ops->erase || !ops->markbad || !ops->isbad)
|
||||
return -EINVAL;
|
||||
|
||||
if (!memorg->bits_per_cell || !memorg->pagesize ||
|
||||
!memorg->pages_per_eraseblock || !memorg->eraseblocks_per_lun ||
|
||||
!memorg->planes_per_lun || !memorg->luns_per_target ||
|
||||
!memorg->ntargets)
|
||||
return -EINVAL;
|
||||
|
||||
nand->rowconv.eraseblock_addr_shift =
|
||||
fls(memorg->pages_per_eraseblock - 1);
|
||||
nand->rowconv.lun_addr_shift = fls(memorg->eraseblocks_per_lun - 1) +
|
||||
nand->rowconv.eraseblock_addr_shift;
|
||||
|
||||
nand->ops = ops;
|
||||
|
||||
mtd->type = memorg->bits_per_cell == 1 ?
|
||||
MTD_NANDFLASH : MTD_MLCNANDFLASH;
|
||||
mtd->flags = MTD_CAP_NANDFLASH;
|
||||
mtd->erasesize = memorg->pagesize * memorg->pages_per_eraseblock;
|
||||
mtd->writesize = memorg->pagesize;
|
||||
mtd->writebufsize = memorg->pagesize;
|
||||
mtd->oobsize = memorg->oobsize;
|
||||
mtd->size = nanddev_size(nand);
|
||||
mtd->owner = owner;
|
||||
|
||||
return nanddev_bbt_init(nand);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nanddev_init);
|
||||
|
||||
/**
|
||||
* nanddev_cleanup() - Release resources allocated in nanddev_init()
|
||||
* @nand: NAND device
|
||||
*
|
||||
* Basically undoes what has been done in nanddev_init().
|
||||
*/
|
||||
void nanddev_cleanup(struct nand_device *nand)
|
||||
{
|
||||
if (nanddev_bbt_is_initialized(nand))
|
||||
nanddev_bbt_cleanup(nand);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nanddev_cleanup);
|
||||
|
||||
MODULE_DESCRIPTION("Generic NAND framework");
|
||||
MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
297
drivers/mtd/nand/raw/Kconfig
Normal file
297
drivers/mtd/nand/raw/Kconfig
Normal file
|
@ -0,0 +1,297 @@
|
|||
|
||||
menuconfig NAND
|
||||
bool "Raw NAND Device Support"
|
||||
if NAND
|
||||
|
||||
config SYS_NAND_SELF_INIT
|
||||
bool
|
||||
help
|
||||
This option, if enabled, provides more flexible and linux-like
|
||||
NAND initialization process.
|
||||
|
||||
config NAND_ATMEL
|
||||
bool "Support Atmel NAND controller"
|
||||
imply SYS_NAND_USE_FLASH_BBT
|
||||
help
|
||||
Enable this driver for NAND flash platforms using an Atmel NAND
|
||||
controller.
|
||||
|
||||
config NAND_DAVINCI
|
||||
bool "Support TI Davinci NAND controller"
|
||||
help
|
||||
Enable this driver for NAND flash controllers available in TI Davinci
|
||||
and Keystone2 platforms
|
||||
|
||||
config NAND_DENALI
|
||||
bool
|
||||
select SYS_NAND_SELF_INIT
|
||||
imply CMD_NAND
|
||||
|
||||
config NAND_DENALI_DT
|
||||
bool "Support Denali NAND controller as a DT device"
|
||||
select NAND_DENALI
|
||||
depends on OF_CONTROL && DM
|
||||
help
|
||||
Enable the driver for NAND flash on platforms using a Denali NAND
|
||||
controller as a DT device.
|
||||
|
||||
config NAND_DENALI_SPARE_AREA_SKIP_BYTES
|
||||
int "Number of bytes skipped in OOB area"
|
||||
depends on NAND_DENALI
|
||||
range 0 63
|
||||
help
|
||||
This option specifies the number of bytes to skip from the beginning
|
||||
of OOB area before last ECC sector data starts. This is potentially
|
||||
used to preserve the bad block marker in the OOB area.
|
||||
|
||||
config NAND_LPC32XX_SLC
|
||||
bool "Support LPC32XX_SLC controller"
|
||||
help
|
||||
Enable the LPC32XX SLC NAND controller.
|
||||
|
||||
config NAND_OMAP_GPMC
|
||||
bool "Support OMAP GPMC NAND controller"
|
||||
depends on ARCH_OMAP2PLUS
|
||||
help
|
||||
Enables omap_gpmc.c driver for OMAPx and AMxxxx platforms.
|
||||
GPMC controller is used for parallel NAND flash devices, and can
|
||||
do ECC calculation (not ECC error detection) for HAM1, BCH4, BCH8
|
||||
and BCH16 ECC algorithms.
|
||||
|
||||
config NAND_OMAP_GPMC_PREFETCH
|
||||
bool "Enable GPMC Prefetch"
|
||||
depends on NAND_OMAP_GPMC
|
||||
default y
|
||||
help
|
||||
On OMAP platforms that use the GPMC controller
|
||||
(CONFIG_NAND_OMAP_GPMC_PREFETCH), this options enables the code that
|
||||
uses the prefetch mode to speed up read operations.
|
||||
|
||||
config NAND_OMAP_ELM
|
||||
bool "Enable ELM driver for OMAPxx and AMxx platforms."
|
||||
depends on NAND_OMAP_GPMC && !OMAP34XX
|
||||
help
|
||||
ELM controller is used for ECC error detection (not ECC calculation)
|
||||
of BCH4, BCH8 and BCH16 ECC algorithms.
|
||||
Some legacy platforms like OMAP3xx do not have in-built ELM h/w engine,
|
||||
thus such SoC platforms need to depend on software library for ECC error
|
||||
detection. However ECC calculation on such plaforms would still be
|
||||
done by GPMC controller.
|
||||
|
||||
config NAND_VF610_NFC
|
||||
bool "Support for Freescale NFC for VF610"
|
||||
select SYS_NAND_SELF_INIT
|
||||
imply CMD_NAND
|
||||
help
|
||||
Enables support for NAND Flash Controller on some Freescale
|
||||
processors like the VF610, MCF54418 or Kinetis K70.
|
||||
The driver supports a maximum 2k page size. The driver
|
||||
currently does not support hardware ECC.
|
||||
|
||||
choice
|
||||
prompt "Hardware ECC strength"
|
||||
depends on NAND_VF610_NFC
|
||||
default SYS_NAND_VF610_NFC_45_ECC_BYTES
|
||||
help
|
||||
Select the ECC strength used in the hardware BCH ECC block.
|
||||
|
||||
config SYS_NAND_VF610_NFC_45_ECC_BYTES
|
||||
bool "24-error correction (45 ECC bytes)"
|
||||
|
||||
config SYS_NAND_VF610_NFC_60_ECC_BYTES
|
||||
bool "32-error correction (60 ECC bytes)"
|
||||
|
||||
endchoice
|
||||
|
||||
config NAND_PXA3XX
|
||||
bool "Support for NAND on PXA3xx and Armada 370/XP/38x"
|
||||
select SYS_NAND_SELF_INIT
|
||||
imply CMD_NAND
|
||||
help
|
||||
This enables the driver for the NAND flash device found on
|
||||
PXA3xx processors (NFCv1) and also on Armada 370/XP (NFCv2).
|
||||
|
||||
config NAND_SUNXI
|
||||
bool "Support for NAND on Allwinner SoCs"
|
||||
default ARCH_SUNXI
|
||||
depends on MACH_SUN4I || MACH_SUN5I || MACH_SUN7I || MACH_SUN8I
|
||||
select SYS_NAND_SELF_INIT
|
||||
select SYS_NAND_U_BOOT_LOCATIONS
|
||||
select SPL_NAND_SUPPORT
|
||||
imply CMD_NAND
|
||||
---help---
|
||||
Enable support for NAND. This option enables the standard and
|
||||
SPL drivers.
|
||||
The SPL driver only supports reading from the NAND using DMA
|
||||
transfers.
|
||||
|
||||
if NAND_SUNXI
|
||||
|
||||
config NAND_SUNXI_SPL_ECC_STRENGTH
|
||||
int "Allwinner NAND SPL ECC Strength"
|
||||
default 64
|
||||
|
||||
config NAND_SUNXI_SPL_ECC_SIZE
|
||||
int "Allwinner NAND SPL ECC Step Size"
|
||||
default 1024
|
||||
|
||||
config NAND_SUNXI_SPL_USABLE_PAGE_SIZE
|
||||
int "Allwinner NAND SPL Usable Page Size"
|
||||
default 1024
|
||||
|
||||
endif
|
||||
|
||||
config NAND_ARASAN
|
||||
bool "Configure Arasan Nand"
|
||||
select SYS_NAND_SELF_INIT
|
||||
imply CMD_NAND
|
||||
help
|
||||
This enables Nand driver support for Arasan nand flash
|
||||
controller. This uses the hardware ECC for read and
|
||||
write operations.
|
||||
|
||||
config NAND_MXC
|
||||
bool "MXC NAND support"
|
||||
depends on CPU_ARM926EJS || CPU_ARM1136 || MX5
|
||||
imply CMD_NAND
|
||||
help
|
||||
This enables the NAND driver for the NAND flash controller on the
|
||||
i.MX27 / i.MX31 / i.MX5 rocessors.
|
||||
|
||||
config NAND_MXS
|
||||
bool "MXS NAND support"
|
||||
depends on MX23 || MX28 || MX6 || MX7
|
||||
select SYS_NAND_SELF_INIT
|
||||
imply CMD_NAND
|
||||
select APBH_DMA
|
||||
select APBH_DMA_BURST if ARCH_MX6 || ARCH_MX7
|
||||
select APBH_DMA_BURST8 if ARCH_MX6 || ARCH_MX7
|
||||
help
|
||||
This enables NAND driver for the NAND flash controller on the
|
||||
MXS processors.
|
||||
|
||||
if NAND_MXS
|
||||
|
||||
config NAND_MXS_DT
|
||||
bool "Support MXS NAND controller as a DT device"
|
||||
depends on OF_CONTROL && MTD
|
||||
help
|
||||
Enable the driver for MXS NAND flash on platforms using
|
||||
device tree.
|
||||
|
||||
config NAND_MXS_USE_MINIMUM_ECC
|
||||
bool "Use minimum ECC strength supported by the controller"
|
||||
default false
|
||||
|
||||
endif
|
||||
|
||||
config NAND_ZYNQ
|
||||
bool "Support for Zynq Nand controller"
|
||||
select SYS_NAND_SELF_INIT
|
||||
imply CMD_NAND
|
||||
help
|
||||
This enables Nand driver support for Nand flash controller
|
||||
found on Zynq SoC.
|
||||
|
||||
config NAND_ZYNQ_USE_BOOTLOADER1_TIMINGS
|
||||
bool "Enable use of 1st stage bootloader timing for NAND"
|
||||
depends on NAND_ZYNQ
|
||||
help
|
||||
This flag prevent U-boot reconfigure NAND flash controller and reuse
|
||||
the NAND timing from 1st stage bootloader.
|
||||
|
||||
comment "Generic NAND options"
|
||||
|
||||
config SYS_NAND_BLOCK_SIZE
|
||||
hex "NAND chip eraseblock size"
|
||||
depends on ARCH_SUNXI
|
||||
help
|
||||
Number of data bytes in one eraseblock for the NAND chip on the
|
||||
board. This is the multiple of NAND_PAGE_SIZE and the number of
|
||||
pages.
|
||||
|
||||
config SYS_NAND_PAGE_SIZE
|
||||
hex "NAND chip page size"
|
||||
depends on ARCH_SUNXI
|
||||
help
|
||||
Number of data bytes in one page for the NAND chip on the
|
||||
board, not including the OOB area.
|
||||
|
||||
config SYS_NAND_OOBSIZE
|
||||
hex "NAND chip OOB size"
|
||||
depends on ARCH_SUNXI
|
||||
help
|
||||
Number of bytes in the Out-Of-Band area for the NAND chip on
|
||||
the board.
|
||||
|
||||
# Enhance depends when converting drivers to Kconfig which use this config
|
||||
# option (mxc_nand, ndfc, omap_gpmc).
|
||||
config SYS_NAND_BUSWIDTH_16BIT
|
||||
bool "Use 16-bit NAND interface"
|
||||
depends on NAND_VF610_NFC || NAND_OMAP_GPMC || NAND_MXC || ARCH_DAVINCI
|
||||
help
|
||||
Indicates that NAND device has 16-bit wide data-bus. In absence of this
|
||||
config, bus-width of NAND device is assumed to be either 8-bit and later
|
||||
determined by reading ONFI params.
|
||||
Above config is useful when NAND device's bus-width information cannot
|
||||
be determined from on-chip ONFI params, like in following scenarios:
|
||||
- SPL boot does not support reading of ONFI parameters. This is done to
|
||||
keep SPL code foot-print small.
|
||||
- In current U-Boot flow using nand_init(), driver initialization
|
||||
happens in board_nand_init() which is called before any device probe
|
||||
(nand_scan_ident + nand_scan_tail), thus device's ONFI parameters are
|
||||
not available while configuring controller. So a static CONFIG_NAND_xx
|
||||
is needed to know the device's bus-width in advance.
|
||||
|
||||
if SPL
|
||||
|
||||
config SYS_NAND_U_BOOT_LOCATIONS
|
||||
bool "Define U-boot binaries locations in NAND"
|
||||
help
|
||||
Enable CONFIG_SYS_NAND_U_BOOT_OFFS though Kconfig.
|
||||
This option should not be enabled when compiling U-boot for boards
|
||||
defining CONFIG_SYS_NAND_U_BOOT_OFFS in their include/configs/<board>.h
|
||||
file.
|
||||
|
||||
config SYS_NAND_U_BOOT_OFFS
|
||||
hex "Location in NAND to read U-Boot from"
|
||||
default 0x800000 if NAND_SUNXI
|
||||
depends on SYS_NAND_U_BOOT_LOCATIONS
|
||||
help
|
||||
Set the offset from the start of the nand where u-boot should be
|
||||
loaded from.
|
||||
|
||||
config SYS_NAND_U_BOOT_OFFS_REDUND
|
||||
hex "Location in NAND to read U-Boot from"
|
||||
default SYS_NAND_U_BOOT_OFFS
|
||||
depends on SYS_NAND_U_BOOT_LOCATIONS
|
||||
help
|
||||
Set the offset from the start of the nand where the redundant u-boot
|
||||
should be loaded from.
|
||||
|
||||
config SPL_NAND_AM33XX_BCH
|
||||
bool "Enables SPL-NAND driver which supports ELM based"
|
||||
depends on NAND_OMAP_GPMC && !OMAP34XX
|
||||
default y
|
||||
help
|
||||
Hardware ECC correction. This is useful for platforms which have ELM
|
||||
hardware engine and use NAND boot mode.
|
||||
Some legacy platforms like OMAP3xx do not have in-built ELM h/w engine,
|
||||
so those platforms should use CONFIG_SPL_NAND_SIMPLE for enabling
|
||||
SPL-NAND driver with software ECC correction support.
|
||||
|
||||
config SPL_NAND_DENALI
|
||||
bool "Support Denali NAND controller for SPL"
|
||||
help
|
||||
This is a small implementation of the Denali NAND controller
|
||||
for use on SPL.
|
||||
|
||||
config SPL_NAND_SIMPLE
|
||||
bool "Use simple SPL NAND driver"
|
||||
depends on !SPL_NAND_AM33XX_BCH
|
||||
help
|
||||
Support for NAND boot using simple NAND drivers that
|
||||
expose the cmd_ctrl() interface.
|
||||
endif
|
||||
|
||||
endif # if NAND
|
77
drivers/mtd/nand/raw/Makefile
Normal file
77
drivers/mtd/nand/raw/Makefile
Normal file
|
@ -0,0 +1,77 @@
|
|||
# SPDX-License-Identifier: GPL-2.0+
|
||||
#
|
||||
# (C) Copyright 2006
|
||||
# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
|
||||
|
||||
ifdef CONFIG_SPL_BUILD
|
||||
|
||||
ifdef CONFIG_SPL_NAND_DRIVERS
|
||||
NORMAL_DRIVERS=y
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_SPL_NAND_AM33XX_BCH) += am335x_spl_bch.o
|
||||
obj-$(CONFIG_SPL_NAND_DENALI) += denali_spl.o
|
||||
obj-$(CONFIG_SPL_NAND_SIMPLE) += nand_spl_simple.o
|
||||
obj-$(CONFIG_SPL_NAND_LOAD) += nand_spl_load.o
|
||||
obj-$(CONFIG_SPL_NAND_ECC) += nand_ecc.o
|
||||
obj-$(CONFIG_SPL_NAND_BASE) += nand_base.o
|
||||
obj-$(CONFIG_SPL_NAND_IDENT) += nand_ids.o nand_timings.o
|
||||
obj-$(CONFIG_SPL_NAND_INIT) += nand.o
|
||||
ifeq ($(CONFIG_SPL_ENV_SUPPORT),y)
|
||||
obj-$(CONFIG_ENV_IS_IN_NAND) += nand_util.o
|
||||
endif
|
||||
|
||||
else # not spl
|
||||
|
||||
NORMAL_DRIVERS=y
|
||||
|
||||
obj-y += nand.o
|
||||
obj-y += nand_bbt.o
|
||||
obj-y += nand_ids.o
|
||||
obj-y += nand_util.o
|
||||
obj-y += nand_ecc.o
|
||||
obj-y += nand_base.o
|
||||
obj-y += nand_timings.o
|
||||
|
||||
endif # not spl
|
||||
|
||||
ifdef NORMAL_DRIVERS
|
||||
|
||||
obj-$(CONFIG_NAND_ECC_BCH) += nand_bch.o
|
||||
|
||||
obj-$(CONFIG_NAND_ATMEL) += atmel_nand.o
|
||||
obj-$(CONFIG_NAND_ARASAN) += arasan_nfc.o
|
||||
obj-$(CONFIG_NAND_DAVINCI) += davinci_nand.o
|
||||
obj-$(CONFIG_NAND_DENALI) += denali.o
|
||||
obj-$(CONFIG_NAND_DENALI_DT) += denali_dt.o
|
||||
obj-$(CONFIG_NAND_FSL_ELBC) += fsl_elbc_nand.o
|
||||
obj-$(CONFIG_NAND_FSL_IFC) += fsl_ifc_nand.o
|
||||
obj-$(CONFIG_NAND_FSL_UPM) += fsl_upm.o
|
||||
obj-$(CONFIG_NAND_FSMC) += fsmc_nand.o
|
||||
obj-$(CONFIG_NAND_KB9202) += kb9202_nand.o
|
||||
obj-$(CONFIG_NAND_KIRKWOOD) += kirkwood_nand.o
|
||||
obj-$(CONFIG_NAND_KMETER1) += kmeter1_nand.o
|
||||
obj-$(CONFIG_NAND_LPC32XX_MLC) += lpc32xx_nand_mlc.o
|
||||
obj-$(CONFIG_NAND_LPC32XX_SLC) += lpc32xx_nand_slc.o
|
||||
obj-$(CONFIG_NAND_VF610_NFC) += vf610_nfc.o
|
||||
obj-$(CONFIG_NAND_MXC) += mxc_nand.o
|
||||
obj-$(CONFIG_NAND_MXS) += mxs_nand.o
|
||||
obj-$(CONFIG_NAND_MXS_DT) += mxs_nand_dt.o
|
||||
obj-$(CONFIG_NAND_PXA3XX) += pxa3xx_nand.o
|
||||
obj-$(CONFIG_NAND_SPEAR) += spr_nand.o
|
||||
obj-$(CONFIG_TEGRA_NAND) += tegra_nand.o
|
||||
obj-$(CONFIG_NAND_OMAP_GPMC) += omap_gpmc.o
|
||||
obj-$(CONFIG_NAND_OMAP_ELM) += omap_elm.o
|
||||
obj-$(CONFIG_NAND_PLAT) += nand_plat.o
|
||||
obj-$(CONFIG_NAND_SUNXI) += sunxi_nand.o
|
||||
obj-$(CONFIG_NAND_ZYNQ) += zynq_nand.o
|
||||
|
||||
else # minimal SPL drivers
|
||||
|
||||
obj-$(CONFIG_NAND_FSL_ELBC) += fsl_elbc_spl.o
|
||||
obj-$(CONFIG_NAND_FSL_IFC) += fsl_ifc_spl.o
|
||||
obj-$(CONFIG_NAND_MXC) += mxc_nand_spl.o
|
||||
obj-$(CONFIG_NAND_MXS) += mxs_nand_spl.o mxs_nand.o
|
||||
obj-$(CONFIG_NAND_SUNXI) += sunxi_nand_spl.o
|
||||
|
||||
endif # drivers
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
/*
|
||||
*
|
||||
* linux/drivers/mtd/nand/nand_davinci.c
|
||||
* linux/drivers/mtd/nand/raw/nand_davinci.c
|
||||
*
|
||||
* NAND Flash Driver
|
||||
*
|
|
@ -1863,33 +1863,6 @@ read_retry:
|
|||
return max_bitflips;
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_read - [MTD Interface] MTD compatibility function for nand_do_read_ecc
|
||||
* @mtd: MTD device structure
|
||||
* @from: offset to read from
|
||||
* @len: number of bytes to read
|
||||
* @retlen: pointer to variable to store the number of read bytes
|
||||
* @buf: the databuffer to put data
|
||||
*
|
||||
* Get hold of the chip and call nand_do_read.
|
||||
*/
|
||||
static int nand_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, uint8_t *buf)
|
||||
{
|
||||
struct mtd_oob_ops ops;
|
||||
int ret;
|
||||
|
||||
nand_get_device(mtd, FL_READING);
|
||||
memset(&ops, 0, sizeof(ops));
|
||||
ops.len = len;
|
||||
ops.datbuf = buf;
|
||||
ops.mode = MTD_OPS_PLACE_OOB;
|
||||
ret = nand_do_read_ops(mtd, from, &ops);
|
||||
*retlen = ops.retlen;
|
||||
nand_release_device(mtd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_read_oob_std - [REPLACEABLE] the most common OOB data read function
|
||||
* @mtd: mtd info structure
|
||||
|
@ -2674,33 +2647,6 @@ static int panic_nand_write(struct mtd_info *mtd, loff_t to, size_t len,
|
|||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_write - [MTD Interface] NAND write with ECC
|
||||
* @mtd: MTD device structure
|
||||
* @to: offset to write to
|
||||
* @len: number of bytes to write
|
||||
* @retlen: pointer to variable to store the number of written bytes
|
||||
* @buf: the data to write
|
||||
*
|
||||
* NAND write with ECC.
|
||||
*/
|
||||
static int nand_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const uint8_t *buf)
|
||||
{
|
||||
struct mtd_oob_ops ops;
|
||||
int ret;
|
||||
|
||||
nand_get_device(mtd, FL_WRITING);
|
||||
memset(&ops, 0, sizeof(ops));
|
||||
ops.len = len;
|
||||
ops.datbuf = (uint8_t *)buf;
|
||||
ops.mode = MTD_OPS_PLACE_OOB;
|
||||
ret = nand_do_write_ops(mtd, to, &ops);
|
||||
*retlen = ops.retlen;
|
||||
nand_release_device(mtd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_do_write_oob - [MTD Interface] NAND write out-of-band
|
||||
* @mtd: MTD device structure
|
||||
|
@ -4620,8 +4566,6 @@ int nand_scan_tail(struct mtd_info *mtd)
|
|||
mtd->flags = (chip->options & NAND_ROM) ? MTD_CAP_ROM :
|
||||
MTD_CAP_NANDFLASH;
|
||||
mtd->_erase = nand_erase;
|
||||
mtd->_read = nand_read;
|
||||
mtd->_write = nand_write;
|
||||
mtd->_panic_write = panic_nand_write;
|
||||
mtd->_read_oob = nand_read_oob;
|
||||
mtd->_write_oob = nand_write_oob;
|
|
@ -3,7 +3,7 @@
|
|||
* This file contains an ECC algorithm from Toshiba that detects and
|
||||
* corrects 1 bit errors in a 256 byte block of data.
|
||||
*
|
||||
* drivers/mtd/nand/nand_ecc.c
|
||||
* drivers/mtd/nand/raw/nand_ecc.c
|
||||
*
|
||||
* Copyright (C) 2000-2004 Steven J. Hill (sjhill@realitydiluted.com)
|
||||
* Toshiba America Electronics Components, Inc.
|
|
@ -1,6 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* drivers/mtd/nand/nand_util.c
|
||||
* drivers/mtd/nand/raw/nand_util.c
|
||||
*
|
||||
* Copyright (C) 2006 by Weiss-Electronic GmbH.
|
||||
* All rights reserved.
|
|
@ -1,6 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* drivers/mtd/nand/pxa3xx_nand.c
|
||||
* drivers/mtd/nand/raw/pxa3xx_nand.c
|
||||
*
|
||||
* Copyright © 2005 Intel Corporation
|
||||
* Copyright © 2006 Marvell International Ltd.
|
7
drivers/mtd/nand/spi/Kconfig
Normal file
7
drivers/mtd/nand/spi/Kconfig
Normal file
|
@ -0,0 +1,7 @@
|
|||
menuconfig MTD_SPI_NAND
|
||||
bool "SPI NAND device Support"
|
||||
depends on MTD && DM_SPI
|
||||
select MTD_NAND_CORE
|
||||
select SPI_MEM
|
||||
help
|
||||
This is the framework for the SPI NAND device drivers.
|
4
drivers/mtd/nand/spi/Makefile
Normal file
4
drivers/mtd/nand/spi/Makefile
Normal file
|
@ -0,0 +1,4 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
spinand-objs := core.o macronix.o micron.o winbond.o
|
||||
obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
|
1254
drivers/mtd/nand/spi/core.c
Normal file
1254
drivers/mtd/nand/spi/core.c
Normal file
File diff suppressed because it is too large
Load diff
146
drivers/mtd/nand/spi/macronix.c
Normal file
146
drivers/mtd/nand/spi/macronix.c
Normal file
|
@ -0,0 +1,146 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2018 Macronix
|
||||
*
|
||||
* Author: Boris Brezillon <boris.brezillon@bootlin.com>
|
||||
*/
|
||||
|
||||
#ifndef __UBOOT__
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#endif
|
||||
#include <linux/mtd/spinand.h>
|
||||
|
||||
#define SPINAND_MFR_MACRONIX 0xC2
|
||||
|
||||
static SPINAND_OP_VARIANTS(read_cache_variants,
|
||||
SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(write_cache_variants,
|
||||
SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
|
||||
SPINAND_PROG_LOAD(true, 0, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(update_cache_variants,
|
||||
SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
|
||||
SPINAND_PROG_LOAD(false, 0, NULL, 0));
|
||||
|
||||
static int mx35lfxge4ab_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
{
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
static int mx35lfxge4ab_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
{
|
||||
if (section)
|
||||
return -ERANGE;
|
||||
|
||||
region->offset = 2;
|
||||
region->length = mtd->oobsize - 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mtd_ooblayout_ops mx35lfxge4ab_ooblayout = {
|
||||
.ecc = mx35lfxge4ab_ooblayout_ecc,
|
||||
.free = mx35lfxge4ab_ooblayout_free,
|
||||
};
|
||||
|
||||
static int mx35lf1ge4ab_get_eccsr(struct spinand_device *spinand, u8 *eccsr)
|
||||
{
|
||||
struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0x7c, 1),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_DUMMY(1, 1),
|
||||
SPI_MEM_OP_DATA_IN(1, eccsr, 1));
|
||||
|
||||
return spi_mem_exec_op(spinand->slave, &op);
|
||||
}
|
||||
|
||||
static int mx35lf1ge4ab_ecc_get_status(struct spinand_device *spinand,
|
||||
u8 status)
|
||||
{
|
||||
struct nand_device *nand = spinand_to_nand(spinand);
|
||||
u8 eccsr;
|
||||
|
||||
switch (status & STATUS_ECC_MASK) {
|
||||
case STATUS_ECC_NO_BITFLIPS:
|
||||
return 0;
|
||||
|
||||
case STATUS_ECC_UNCOR_ERROR:
|
||||
return -EBADMSG;
|
||||
|
||||
case STATUS_ECC_HAS_BITFLIPS:
|
||||
/*
|
||||
* Let's try to retrieve the real maximum number of bitflips
|
||||
* in order to avoid forcing the wear-leveling layer to move
|
||||
* data around if it's not necessary.
|
||||
*/
|
||||
if (mx35lf1ge4ab_get_eccsr(spinand, &eccsr))
|
||||
return nand->eccreq.strength;
|
||||
|
||||
if (WARN_ON(eccsr > nand->eccreq.strength || !eccsr))
|
||||
return nand->eccreq.strength;
|
||||
|
||||
return eccsr;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct spinand_info macronix_spinand_table[] = {
|
||||
SPINAND_INFO("MX35LF1GE4AB", 0x12,
|
||||
NAND_MEMORG(1, 2048, 64, 64, 1024, 1, 1, 1),
|
||||
NAND_ECCREQ(4, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
|
||||
mx35lf1ge4ab_ecc_get_status)),
|
||||
SPINAND_INFO("MX35LF2GE4AB", 0x22,
|
||||
NAND_MEMORG(1, 2048, 64, 64, 2048, 2, 1, 1),
|
||||
NAND_ECCREQ(4, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)),
|
||||
};
|
||||
|
||||
static int macronix_spinand_detect(struct spinand_device *spinand)
|
||||
{
|
||||
u8 *id = spinand->id.data;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Macronix SPI NAND read ID needs a dummy byte, so the first byte in
|
||||
* raw_id is garbage.
|
||||
*/
|
||||
if (id[1] != SPINAND_MFR_MACRONIX)
|
||||
return 0;
|
||||
|
||||
ret = spinand_match_and_init(spinand, macronix_spinand_table,
|
||||
ARRAY_SIZE(macronix_spinand_table),
|
||||
id[2]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const struct spinand_manufacturer_ops macronix_spinand_manuf_ops = {
|
||||
.detect = macronix_spinand_detect,
|
||||
};
|
||||
|
||||
const struct spinand_manufacturer macronix_spinand_manufacturer = {
|
||||
.id = SPINAND_MFR_MACRONIX,
|
||||
.name = "Macronix",
|
||||
.ops = ¯onix_spinand_manuf_ops,
|
||||
};
|
135
drivers/mtd/nand/spi/micron.c
Normal file
135
drivers/mtd/nand/spi/micron.c
Normal file
|
@ -0,0 +1,135 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2016-2017 Micron Technology, Inc.
|
||||
*
|
||||
* Authors:
|
||||
* Peter Pan <peterpandong@micron.com>
|
||||
*/
|
||||
|
||||
#ifndef __UBOOT__
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#endif
|
||||
#include <linux/mtd/spinand.h>
|
||||
|
||||
#define SPINAND_MFR_MICRON 0x2c
|
||||
|
||||
#define MICRON_STATUS_ECC_MASK GENMASK(7, 4)
|
||||
#define MICRON_STATUS_ECC_NO_BITFLIPS (0 << 4)
|
||||
#define MICRON_STATUS_ECC_1TO3_BITFLIPS (1 << 4)
|
||||
#define MICRON_STATUS_ECC_4TO6_BITFLIPS (3 << 4)
|
||||
#define MICRON_STATUS_ECC_7TO8_BITFLIPS (5 << 4)
|
||||
|
||||
static SPINAND_OP_VARIANTS(read_cache_variants,
|
||||
SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(write_cache_variants,
|
||||
SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
|
||||
SPINAND_PROG_LOAD(true, 0, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(update_cache_variants,
|
||||
SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
|
||||
SPINAND_PROG_LOAD(false, 0, NULL, 0));
|
||||
|
||||
static int mt29f2g01abagd_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
{
|
||||
if (section)
|
||||
return -ERANGE;
|
||||
|
||||
region->offset = 64;
|
||||
region->length = 64;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt29f2g01abagd_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
{
|
||||
if (section)
|
||||
return -ERANGE;
|
||||
|
||||
/* Reserve 2 bytes for the BBM. */
|
||||
region->offset = 2;
|
||||
region->length = 62;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mtd_ooblayout_ops mt29f2g01abagd_ooblayout = {
|
||||
.ecc = mt29f2g01abagd_ooblayout_ecc,
|
||||
.free = mt29f2g01abagd_ooblayout_free,
|
||||
};
|
||||
|
||||
static int mt29f2g01abagd_ecc_get_status(struct spinand_device *spinand,
|
||||
u8 status)
|
||||
{
|
||||
switch (status & MICRON_STATUS_ECC_MASK) {
|
||||
case STATUS_ECC_NO_BITFLIPS:
|
||||
return 0;
|
||||
|
||||
case STATUS_ECC_UNCOR_ERROR:
|
||||
return -EBADMSG;
|
||||
|
||||
case MICRON_STATUS_ECC_1TO3_BITFLIPS:
|
||||
return 3;
|
||||
|
||||
case MICRON_STATUS_ECC_4TO6_BITFLIPS:
|
||||
return 6;
|
||||
|
||||
case MICRON_STATUS_ECC_7TO8_BITFLIPS:
|
||||
return 8;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct spinand_info micron_spinand_table[] = {
|
||||
SPINAND_INFO("MT29F2G01ABAGD", 0x24,
|
||||
NAND_MEMORG(1, 2048, 128, 64, 2048, 2, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
0,
|
||||
SPINAND_ECCINFO(&mt29f2g01abagd_ooblayout,
|
||||
mt29f2g01abagd_ecc_get_status)),
|
||||
};
|
||||
|
||||
static int micron_spinand_detect(struct spinand_device *spinand)
|
||||
{
|
||||
u8 *id = spinand->id.data;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Micron SPI NAND read ID need a dummy byte,
|
||||
* so the first byte in raw_id is dummy.
|
||||
*/
|
||||
if (id[1] != SPINAND_MFR_MICRON)
|
||||
return 0;
|
||||
|
||||
ret = spinand_match_and_init(spinand, micron_spinand_table,
|
||||
ARRAY_SIZE(micron_spinand_table), id[2]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const struct spinand_manufacturer_ops micron_spinand_manuf_ops = {
|
||||
.detect = micron_spinand_detect,
|
||||
};
|
||||
|
||||
const struct spinand_manufacturer micron_spinand_manufacturer = {
|
||||
.id = SPINAND_MFR_MICRON,
|
||||
.name = "Micron",
|
||||
.ops = µn_spinand_manuf_ops,
|
||||
};
|
143
drivers/mtd/nand/spi/winbond.c
Normal file
143
drivers/mtd/nand/spi/winbond.c
Normal file
|
@ -0,0 +1,143 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2017 exceet electronics GmbH
|
||||
*
|
||||
* Authors:
|
||||
* Frieder Schrempf <frieder.schrempf@exceet.de>
|
||||
* Boris Brezillon <boris.brezillon@bootlin.com>
|
||||
*/
|
||||
|
||||
#ifndef __UBOOT__
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#endif
|
||||
#include <linux/mtd/spinand.h>
|
||||
|
||||
#define SPINAND_MFR_WINBOND 0xEF
|
||||
|
||||
#define WINBOND_CFG_BUF_READ BIT(3)
|
||||
|
||||
static SPINAND_OP_VARIANTS(read_cache_variants,
|
||||
SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(write_cache_variants,
|
||||
SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
|
||||
SPINAND_PROG_LOAD(true, 0, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(update_cache_variants,
|
||||
SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
|
||||
SPINAND_PROG_LOAD(false, 0, NULL, 0));
|
||||
|
||||
static int w25m02gv_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
{
|
||||
if (section > 3)
|
||||
return -ERANGE;
|
||||
|
||||
region->offset = (16 * section) + 8;
|
||||
region->length = 8;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int w25m02gv_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
{
|
||||
if (section > 3)
|
||||
return -ERANGE;
|
||||
|
||||
region->offset = (16 * section) + 2;
|
||||
region->length = 6;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mtd_ooblayout_ops w25m02gv_ooblayout = {
|
||||
.ecc = w25m02gv_ooblayout_ecc,
|
||||
.free = w25m02gv_ooblayout_free,
|
||||
};
|
||||
|
||||
static int w25m02gv_select_target(struct spinand_device *spinand,
|
||||
unsigned int target)
|
||||
{
|
||||
struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0xc2, 1),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_OUT(1,
|
||||
spinand->scratchbuf,
|
||||
1));
|
||||
|
||||
*spinand->scratchbuf = target;
|
||||
return spi_mem_exec_op(spinand->slave, &op);
|
||||
}
|
||||
|
||||
static const struct spinand_info winbond_spinand_table[] = {
|
||||
SPINAND_INFO("W25M02GV", 0xAB,
|
||||
NAND_MEMORG(1, 2048, 64, 64, 1024, 1, 1, 2),
|
||||
NAND_ECCREQ(1, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
0,
|
||||
SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL),
|
||||
SPINAND_SELECT_TARGET(w25m02gv_select_target)),
|
||||
};
|
||||
|
||||
/**
|
||||
* winbond_spinand_detect - initialize device related part in spinand_device
|
||||
* struct if it is a Winbond device.
|
||||
* @spinand: SPI NAND device structure
|
||||
*/
|
||||
static int winbond_spinand_detect(struct spinand_device *spinand)
|
||||
{
|
||||
u8 *id = spinand->id.data;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Winbond SPI NAND read ID need a dummy byte,
|
||||
* so the first byte in raw_id is dummy.
|
||||
*/
|
||||
if (id[1] != SPINAND_MFR_WINBOND)
|
||||
return 0;
|
||||
|
||||
ret = spinand_match_and_init(spinand, winbond_spinand_table,
|
||||
ARRAY_SIZE(winbond_spinand_table), id[2]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int winbond_spinand_init(struct spinand_device *spinand)
|
||||
{
|
||||
struct nand_device *nand = spinand_to_nand(spinand);
|
||||
unsigned int i;
|
||||
|
||||
/*
|
||||
* Make sure all dies are in buffer read mode and not continuous read
|
||||
* mode.
|
||||
*/
|
||||
for (i = 0; i < nand->memorg.ntargets; i++) {
|
||||
spinand_select_target(spinand, i);
|
||||
spinand_upd_cfg(spinand, WINBOND_CFG_BUF_READ,
|
||||
WINBOND_CFG_BUF_READ);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spinand_manufacturer_ops winbond_spinand_manuf_ops = {
|
||||
.detect = winbond_spinand_detect,
|
||||
.init = winbond_spinand_init,
|
||||
};
|
||||
|
||||
const struct spinand_manufacturer winbond_spinand_manufacturer = {
|
||||
.id = SPINAND_MFR_WINBOND,
|
||||
.name = "Winbond",
|
||||
.ops = &winbond_spinand_manuf_ops,
|
||||
};
|
|
@ -2656,8 +2656,6 @@ int onenand_probe(struct mtd_info *mtd)
|
|||
|
||||
mtd->flags = MTD_CAP_NANDFLASH;
|
||||
mtd->_erase = onenand_erase;
|
||||
mtd->_read = onenand_read;
|
||||
mtd->_write = onenand_write;
|
||||
mtd->_read_oob = onenand_read_oob;
|
||||
mtd->_write_oob = onenand_write_oob;
|
||||
mtd->_sync = onenand_sync;
|
||||
|
|
|
@ -18,6 +18,13 @@ config DM_SPI
|
|||
|
||||
if DM_SPI
|
||||
|
||||
config SPI_MEM
|
||||
bool "SPI memory extension"
|
||||
help
|
||||
Enable this option if you want to enable the SPI memory extension.
|
||||
This extension is meant to simplify interaction with SPI memories
|
||||
by providing an high-level interface to send memory-like commands.
|
||||
|
||||
config ALTERA_SPI
|
||||
bool "Altera SPI driver"
|
||||
help
|
||||
|
|
|
@ -8,6 +8,7 @@ ifdef CONFIG_DM_SPI
|
|||
obj-y += spi-uclass.o
|
||||
obj-$(CONFIG_SANDBOX) += spi-emul-uclass.o
|
||||
obj-$(CONFIG_SOFT_SPI) += soft_spi.o
|
||||
obj-$(CONFIG_SPI_MEM) += spi-mem.o
|
||||
else
|
||||
obj-y += spi.o
|
||||
obj-$(CONFIG_SOFT_SPI) += soft_spi_legacy.o
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <malloc.h>
|
||||
#include <spi.h>
|
||||
#include <fdtdec.h>
|
||||
#include <reset.h>
|
||||
#include <linux/compat.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <asm/io.h>
|
||||
|
@ -111,6 +112,8 @@ struct dw_spi_priv {
|
|||
void *tx_end;
|
||||
void *rx;
|
||||
void *rx_end;
|
||||
|
||||
struct reset_ctl_bulk resets;
|
||||
};
|
||||
|
||||
static inline u32 dw_read(struct dw_spi_priv *priv, u32 offset)
|
||||
|
@ -231,6 +234,34 @@ err_rate:
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int dw_spi_reset(struct udevice *bus)
|
||||
{
|
||||
int ret;
|
||||
struct dw_spi_priv *priv = dev_get_priv(bus);
|
||||
|
||||
ret = reset_get_bulk(bus, &priv->resets);
|
||||
if (ret) {
|
||||
/*
|
||||
* Return 0 if error due to !CONFIG_DM_RESET and reset
|
||||
* DT property is not present.
|
||||
*/
|
||||
if (ret == -ENOENT || ret == -ENOTSUPP)
|
||||
return 0;
|
||||
|
||||
dev_warn(bus, "Can't get reset: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = reset_deassert_bulk(&priv->resets);
|
||||
if (ret) {
|
||||
reset_release_bulk(&priv->resets);
|
||||
dev_err(bus, "Failed to reset: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_spi_probe(struct udevice *bus)
|
||||
{
|
||||
struct dw_spi_platdata *plat = dev_get_platdata(bus);
|
||||
|
@ -244,6 +275,10 @@ static int dw_spi_probe(struct udevice *bus)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = dw_spi_reset(bus);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Currently only bits_per_word == 8 supported */
|
||||
priv->bits_per_word = 8;
|
||||
|
||||
|
@ -478,6 +513,13 @@ static int dw_spi_set_mode(struct udevice *bus, uint mode)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int dw_spi_remove(struct udevice *bus)
|
||||
{
|
||||
struct dw_spi_priv *priv = dev_get_priv(bus);
|
||||
|
||||
return reset_release_bulk(&priv->resets);
|
||||
}
|
||||
|
||||
static const struct dm_spi_ops dw_spi_ops = {
|
||||
.xfer = dw_spi_xfer,
|
||||
.set_speed = dw_spi_set_speed,
|
||||
|
@ -502,4 +544,5 @@ U_BOOT_DRIVER(dw_spi) = {
|
|||
.platdata_auto_alloc_size = sizeof(struct dw_spi_platdata),
|
||||
.priv_auto_alloc_size = sizeof(struct dw_spi_priv),
|
||||
.probe = dw_spi_probe,
|
||||
.remove = dw_spi_remove,
|
||||
};
|
||||
|
|
|
@ -84,7 +84,6 @@ DECLARE_GLOBAL_DATA_PTR;
|
|||
/* QSPI max chipselect signals number */
|
||||
#define FSL_QSPI_MAX_CHIPSELECT_NUM 4
|
||||
|
||||
#ifdef CONFIG_DM_SPI
|
||||
/**
|
||||
* struct fsl_qspi_platdata - platform data for Freescale QSPI
|
||||
*
|
||||
|
@ -105,7 +104,6 @@ struct fsl_qspi_platdata {
|
|||
u32 flash_num;
|
||||
u32 num_chipselect;
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* struct fsl_qspi_priv - private data for Freescale QSPI
|
||||
|
@ -136,12 +134,6 @@ struct fsl_qspi_priv {
|
|||
struct fsl_qspi_regs *regs;
|
||||
};
|
||||
|
||||
#ifndef CONFIG_DM_SPI
|
||||
struct fsl_qspi {
|
||||
struct spi_slave slave;
|
||||
struct fsl_qspi_priv priv;
|
||||
};
|
||||
#endif
|
||||
|
||||
static u32 qspi_read32(u32 flags, u32 *addr)
|
||||
{
|
||||
|
@ -869,136 +861,7 @@ void qspi_cfg_smpr(struct fsl_qspi_priv *priv, u32 clear_bits, u32 set_bits)
|
|||
smpr_val |= set_bits;
|
||||
qspi_write32(priv->flags, &priv->regs->smpr, smpr_val);
|
||||
}
|
||||
#ifndef CONFIG_DM_SPI
|
||||
static unsigned long spi_bases[] = {
|
||||
QSPI0_BASE_ADDR,
|
||||
#ifdef CONFIG_MX6SX
|
||||
QSPI1_BASE_ADDR,
|
||||
#endif
|
||||
};
|
||||
|
||||
static unsigned long amba_bases[] = {
|
||||
QSPI0_AMBA_BASE,
|
||||
#ifdef CONFIG_MX6SX
|
||||
QSPI1_AMBA_BASE,
|
||||
#endif
|
||||
};
|
||||
|
||||
static inline struct fsl_qspi *to_qspi_spi(struct spi_slave *slave)
|
||||
{
|
||||
return container_of(slave, struct fsl_qspi, slave);
|
||||
}
|
||||
|
||||
struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs,
|
||||
unsigned int max_hz, unsigned int mode)
|
||||
{
|
||||
u32 mcr_val;
|
||||
struct fsl_qspi *qspi;
|
||||
struct fsl_qspi_regs *regs;
|
||||
u32 total_size;
|
||||
|
||||
if (bus >= ARRAY_SIZE(spi_bases))
|
||||
return NULL;
|
||||
|
||||
if (cs >= FSL_QSPI_FLASH_NUM)
|
||||
return NULL;
|
||||
|
||||
qspi = spi_alloc_slave(struct fsl_qspi, bus, cs);
|
||||
if (!qspi)
|
||||
return NULL;
|
||||
|
||||
#ifdef CONFIG_SYS_FSL_QSPI_BE
|
||||
qspi->priv.flags |= QSPI_FLAG_REGMAP_ENDIAN_BIG;
|
||||
#endif
|
||||
|
||||
regs = (struct fsl_qspi_regs *)spi_bases[bus];
|
||||
qspi->priv.regs = regs;
|
||||
/*
|
||||
* According cs, use different amba_base to choose the
|
||||
* corresponding flash devices.
|
||||
*
|
||||
* If not, only one flash device is used even if passing
|
||||
* different cs using `sf probe`
|
||||
*/
|
||||
qspi->priv.cur_amba_base = amba_bases[bus] + cs * FSL_QSPI_FLASH_SIZE;
|
||||
|
||||
qspi->slave.max_write_size = TX_BUFFER_SIZE;
|
||||
|
||||
mcr_val = qspi_read32(qspi->priv.flags, ®s->mcr);
|
||||
|
||||
/* Set endianness to LE for i.mx */
|
||||
if (IS_ENABLED(CONFIG_MX6) || IS_ENABLED(CONFIG_MX7))
|
||||
mcr_val = QSPI_MCR_END_CFD_LE;
|
||||
|
||||
qspi_write32(qspi->priv.flags, ®s->mcr,
|
||||
QSPI_MCR_RESERVED_MASK | QSPI_MCR_MDIS_MASK |
|
||||
(mcr_val & QSPI_MCR_END_CFD_MASK));
|
||||
|
||||
qspi_cfg_smpr(&qspi->priv,
|
||||
~(QSPI_SMPR_FSDLY_MASK | QSPI_SMPR_DDRSMP_MASK |
|
||||
QSPI_SMPR_FSPHS_MASK | QSPI_SMPR_HSENA_MASK), 0);
|
||||
|
||||
total_size = FSL_QSPI_FLASH_SIZE * FSL_QSPI_FLASH_NUM;
|
||||
/*
|
||||
* Any read access to non-implemented addresses will provide
|
||||
* undefined results.
|
||||
*
|
||||
* In case single die flash devices, TOP_ADDR_MEMA2 and
|
||||
* TOP_ADDR_MEMB2 should be initialized/programmed to
|
||||
* TOP_ADDR_MEMA1 and TOP_ADDR_MEMB1 respectively - in effect,
|
||||
* setting the size of these devices to 0. This would ensure
|
||||
* that the complete memory map is assigned to only one flash device.
|
||||
*/
|
||||
qspi_write32(qspi->priv.flags, ®s->sfa1ad,
|
||||
FSL_QSPI_FLASH_SIZE | amba_bases[bus]);
|
||||
qspi_write32(qspi->priv.flags, ®s->sfa2ad,
|
||||
FSL_QSPI_FLASH_SIZE | amba_bases[bus]);
|
||||
qspi_write32(qspi->priv.flags, ®s->sfb1ad,
|
||||
total_size | amba_bases[bus]);
|
||||
qspi_write32(qspi->priv.flags, ®s->sfb2ad,
|
||||
total_size | amba_bases[bus]);
|
||||
|
||||
qspi_set_lut(&qspi->priv);
|
||||
|
||||
#ifdef CONFIG_SYS_FSL_QSPI_AHB
|
||||
qspi_init_ahb_read(&qspi->priv);
|
||||
#endif
|
||||
|
||||
qspi_module_disable(&qspi->priv, 0);
|
||||
|
||||
return &qspi->slave;
|
||||
}
|
||||
|
||||
void spi_free_slave(struct spi_slave *slave)
|
||||
{
|
||||
struct fsl_qspi *qspi = to_qspi_spi(slave);
|
||||
|
||||
free(qspi);
|
||||
}
|
||||
|
||||
int spi_claim_bus(struct spi_slave *slave)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void spi_release_bus(struct spi_slave *slave)
|
||||
{
|
||||
/* Nothing to do */
|
||||
}
|
||||
|
||||
int spi_xfer(struct spi_slave *slave, unsigned int bitlen,
|
||||
const void *dout, void *din, unsigned long flags)
|
||||
{
|
||||
struct fsl_qspi *qspi = to_qspi_spi(slave);
|
||||
|
||||
return qspi_xfer(&qspi->priv, bitlen, dout, din, flags);
|
||||
}
|
||||
|
||||
void spi_init(void)
|
||||
{
|
||||
/* Nothing to do */
|
||||
}
|
||||
#else
|
||||
static int fsl_qspi_child_pre_probe(struct udevice *dev)
|
||||
{
|
||||
struct spi_slave *slave = dev_get_parent_priv(dev);
|
||||
|
@ -1265,4 +1128,3 @@ U_BOOT_DRIVER(fsl_qspi) = {
|
|||
.probe = fsl_qspi_probe,
|
||||
.child_pre_probe = fsl_qspi_child_pre_probe,
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -67,15 +67,12 @@ struct sh_qspi_regs {
|
|||
};
|
||||
|
||||
struct sh_qspi_slave {
|
||||
#ifndef CONFIG_DM_SPI
|
||||
struct spi_slave slave;
|
||||
#endif
|
||||
struct sh_qspi_regs *regs;
|
||||
};
|
||||
|
||||
static inline struct sh_qspi_slave *to_sh_qspi(struct spi_slave *slave)
|
||||
{
|
||||
return container_of(slave, struct sh_qspi_slave, slave);
|
||||
}
|
||||
|
||||
static void sh_qspi_init(struct sh_qspi_slave *ss)
|
||||
{
|
||||
/* QSPI initialize */
|
||||
|
@ -119,15 +116,8 @@ static void sh_qspi_init(struct sh_qspi_slave *ss)
|
|||
setbits_8(&ss->regs->spcr, SPCR_SPE);
|
||||
}
|
||||
|
||||
int spi_cs_is_valid(unsigned int bus, unsigned int cs)
|
||||
static void sh_qspi_cs_activate(struct sh_qspi_slave *ss)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
void spi_cs_activate(struct spi_slave *slave)
|
||||
{
|
||||
struct sh_qspi_slave *ss = to_sh_qspi(slave);
|
||||
|
||||
/* Set master mode only */
|
||||
writeb(SPCR_MSTR, &ss->regs->spcr);
|
||||
|
||||
|
@ -147,61 +137,15 @@ void spi_cs_activate(struct spi_slave *slave)
|
|||
setbits_8(&ss->regs->spcr, SPCR_SPE);
|
||||
}
|
||||
|
||||
void spi_cs_deactivate(struct spi_slave *slave)
|
||||
static void sh_qspi_cs_deactivate(struct sh_qspi_slave *ss)
|
||||
{
|
||||
struct sh_qspi_slave *ss = to_sh_qspi(slave);
|
||||
|
||||
/* Disable SPI Function */
|
||||
clrbits_8(&ss->regs->spcr, SPCR_SPE);
|
||||
}
|
||||
|
||||
void spi_init(void)
|
||||
static int sh_qspi_xfer_common(struct sh_qspi_slave *ss, unsigned int bitlen,
|
||||
const void *dout, void *din, unsigned long flags)
|
||||
{
|
||||
/* nothing to do */
|
||||
}
|
||||
|
||||
struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs,
|
||||
unsigned int max_hz, unsigned int mode)
|
||||
{
|
||||
struct sh_qspi_slave *ss;
|
||||
|
||||
if (!spi_cs_is_valid(bus, cs))
|
||||
return NULL;
|
||||
|
||||
ss = spi_alloc_slave(struct sh_qspi_slave, bus, cs);
|
||||
if (!ss) {
|
||||
printf("SPI_error: Fail to allocate sh_qspi_slave\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ss->regs = (struct sh_qspi_regs *)SH_QSPI_BASE;
|
||||
|
||||
/* Init SH QSPI */
|
||||
sh_qspi_init(ss);
|
||||
|
||||
return &ss->slave;
|
||||
}
|
||||
|
||||
void spi_free_slave(struct spi_slave *slave)
|
||||
{
|
||||
struct sh_qspi_slave *spi = to_sh_qspi(slave);
|
||||
|
||||
free(spi);
|
||||
}
|
||||
|
||||
int spi_claim_bus(struct spi_slave *slave)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void spi_release_bus(struct spi_slave *slave)
|
||||
{
|
||||
}
|
||||
|
||||
int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout,
|
||||
void *din, unsigned long flags)
|
||||
{
|
||||
struct sh_qspi_slave *ss = to_sh_qspi(slave);
|
||||
u32 nbyte, chunk;
|
||||
int i, ret = 0;
|
||||
u8 dtdata = 0, drdata;
|
||||
|
@ -210,7 +154,7 @@ int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout,
|
|||
|
||||
if (dout == NULL && din == NULL) {
|
||||
if (flags & SPI_XFER_END)
|
||||
spi_cs_deactivate(slave);
|
||||
sh_qspi_cs_deactivate(ss);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -222,7 +166,7 @@ int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout,
|
|||
nbyte = bitlen / 8;
|
||||
|
||||
if (flags & SPI_XFER_BEGIN) {
|
||||
spi_cs_activate(slave);
|
||||
sh_qspi_cs_activate(ss);
|
||||
|
||||
/* Set 1048576 byte */
|
||||
writel(0x100000, spbmul0);
|
||||
|
@ -273,7 +217,148 @@ int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout,
|
|||
}
|
||||
|
||||
if (flags & SPI_XFER_END)
|
||||
spi_cs_deactivate(slave);
|
||||
sh_qspi_cs_deactivate(ss);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_DM_SPI
|
||||
static inline struct sh_qspi_slave *to_sh_qspi(struct spi_slave *slave)
|
||||
{
|
||||
return container_of(slave, struct sh_qspi_slave, slave);
|
||||
}
|
||||
|
||||
int spi_cs_is_valid(unsigned int bus, unsigned int cs)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
void spi_cs_activate(struct spi_slave *slave)
|
||||
{
|
||||
struct sh_qspi_slave *ss = to_sh_qspi(slave);
|
||||
|
||||
sh_qspi_cs_activate(ss);
|
||||
}
|
||||
|
||||
void spi_cs_deactivate(struct spi_slave *slave)
|
||||
{
|
||||
struct sh_qspi_slave *ss = to_sh_qspi(slave);
|
||||
|
||||
sh_qspi_cs_deactivate(ss);
|
||||
}
|
||||
|
||||
void spi_init(void)
|
||||
{
|
||||
/* nothing to do */
|
||||
}
|
||||
|
||||
struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs,
|
||||
unsigned int max_hz, unsigned int mode)
|
||||
{
|
||||
struct sh_qspi_slave *ss;
|
||||
|
||||
if (!spi_cs_is_valid(bus, cs))
|
||||
return NULL;
|
||||
|
||||
ss = spi_alloc_slave(struct sh_qspi_slave, bus, cs);
|
||||
if (!ss) {
|
||||
printf("SPI_error: Fail to allocate sh_qspi_slave\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ss->regs = (struct sh_qspi_regs *)SH_QSPI_BASE;
|
||||
|
||||
/* Init SH QSPI */
|
||||
sh_qspi_init(ss);
|
||||
|
||||
return &ss->slave;
|
||||
}
|
||||
|
||||
void spi_free_slave(struct spi_slave *slave)
|
||||
{
|
||||
struct sh_qspi_slave *spi = to_sh_qspi(slave);
|
||||
|
||||
free(spi);
|
||||
}
|
||||
|
||||
int spi_claim_bus(struct spi_slave *slave)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void spi_release_bus(struct spi_slave *slave)
|
||||
{
|
||||
}
|
||||
|
||||
int spi_xfer(struct spi_slave *slave, unsigned int bitlen,
|
||||
const void *dout, void *din, unsigned long flags)
|
||||
{
|
||||
struct sh_qspi_slave *ss = to_sh_qspi(slave);
|
||||
|
||||
return sh_qspi_xfer_common(ss, bitlen, dout, din, flags);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <dm.h>
|
||||
|
||||
static int sh_qspi_xfer(struct udevice *dev, unsigned int bitlen,
|
||||
const void *dout, void *din, unsigned long flags)
|
||||
{
|
||||
struct udevice *bus = dev->parent;
|
||||
struct sh_qspi_slave *ss = dev_get_platdata(bus);
|
||||
|
||||
return sh_qspi_xfer_common(ss, bitlen, dout, din, flags);
|
||||
}
|
||||
|
||||
static int sh_qspi_set_speed(struct udevice *dev, uint speed)
|
||||
{
|
||||
/* This is a SPI NOR controller, do nothing. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sh_qspi_set_mode(struct udevice *dev, uint mode)
|
||||
{
|
||||
/* This is a SPI NOR controller, do nothing. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sh_qspi_probe(struct udevice *dev)
|
||||
{
|
||||
struct sh_qspi_slave *ss = dev_get_platdata(dev);
|
||||
|
||||
sh_qspi_init(ss);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sh_qspi_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct sh_qspi_slave *plat = dev_get_platdata(dev);
|
||||
|
||||
plat->regs = (struct sh_qspi_regs *)dev_read_addr(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dm_spi_ops sh_qspi_ops = {
|
||||
.xfer = sh_qspi_xfer,
|
||||
.set_speed = sh_qspi_set_speed,
|
||||
.set_mode = sh_qspi_set_mode,
|
||||
};
|
||||
|
||||
static const struct udevice_id sh_qspi_ids[] = {
|
||||
{ .compatible = "renesas,qspi" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(sh_qspi) = {
|
||||
.name = "sh_qspi",
|
||||
.id = UCLASS_SPI,
|
||||
.of_match = sh_qspi_ids,
|
||||
.ops = &sh_qspi_ops,
|
||||
.ofdata_to_platdata = sh_qspi_ofdata_to_platdata,
|
||||
.platdata_auto_alloc_size = sizeof(struct sh_qspi_slave),
|
||||
.probe = sh_qspi_probe,
|
||||
};
|
||||
#endif
|
||||
|
|
501
drivers/spi/spi-mem.c
Normal file
501
drivers/spi/spi-mem.c
Normal file
|
@ -0,0 +1,501 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2018 Exceet Electronics GmbH
|
||||
* Copyright (C) 2018 Bootlin
|
||||
*
|
||||
* Author: Boris Brezillon <boris.brezillon@bootlin.com>
|
||||
*/
|
||||
|
||||
#ifndef __UBOOT__
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include "internals.h"
|
||||
#else
|
||||
#include <spi.h>
|
||||
#include <spi-mem.h>
|
||||
#endif
|
||||
|
||||
#ifndef __UBOOT__
|
||||
/**
|
||||
* spi_controller_dma_map_mem_op_data() - DMA-map the buffer attached to a
|
||||
* memory operation
|
||||
* @ctlr: the SPI controller requesting this dma_map()
|
||||
* @op: the memory operation containing the buffer to map
|
||||
* @sgt: a pointer to a non-initialized sg_table that will be filled by this
|
||||
* function
|
||||
*
|
||||
* Some controllers might want to do DMA on the data buffer embedded in @op.
|
||||
* This helper prepares everything for you and provides a ready-to-use
|
||||
* sg_table. This function is not intended to be called from spi drivers.
|
||||
* Only SPI controller drivers should use it.
|
||||
* Note that the caller must ensure the memory region pointed by
|
||||
* op->data.buf.{in,out} is DMA-able before calling this function.
|
||||
*
|
||||
* Return: 0 in case of success, a negative error code otherwise.
|
||||
*/
|
||||
int spi_controller_dma_map_mem_op_data(struct spi_controller *ctlr,
|
||||
const struct spi_mem_op *op,
|
||||
struct sg_table *sgt)
|
||||
{
|
||||
struct device *dmadev;
|
||||
|
||||
if (!op->data.nbytes)
|
||||
return -EINVAL;
|
||||
|
||||
if (op->data.dir == SPI_MEM_DATA_OUT && ctlr->dma_tx)
|
||||
dmadev = ctlr->dma_tx->device->dev;
|
||||
else if (op->data.dir == SPI_MEM_DATA_IN && ctlr->dma_rx)
|
||||
dmadev = ctlr->dma_rx->device->dev;
|
||||
else
|
||||
dmadev = ctlr->dev.parent;
|
||||
|
||||
if (!dmadev)
|
||||
return -EINVAL;
|
||||
|
||||
return spi_map_buf(ctlr, dmadev, sgt, op->data.buf.in, op->data.nbytes,
|
||||
op->data.dir == SPI_MEM_DATA_IN ?
|
||||
DMA_FROM_DEVICE : DMA_TO_DEVICE);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_controller_dma_map_mem_op_data);
|
||||
|
||||
/**
|
||||
* spi_controller_dma_unmap_mem_op_data() - DMA-unmap the buffer attached to a
|
||||
* memory operation
|
||||
* @ctlr: the SPI controller requesting this dma_unmap()
|
||||
* @op: the memory operation containing the buffer to unmap
|
||||
* @sgt: a pointer to an sg_table previously initialized by
|
||||
* spi_controller_dma_map_mem_op_data()
|
||||
*
|
||||
* Some controllers might want to do DMA on the data buffer embedded in @op.
|
||||
* This helper prepares things so that the CPU can access the
|
||||
* op->data.buf.{in,out} buffer again.
|
||||
*
|
||||
* This function is not intended to be called from SPI drivers. Only SPI
|
||||
* controller drivers should use it.
|
||||
*
|
||||
* This function should be called after the DMA operation has finished and is
|
||||
* only valid if the previous spi_controller_dma_map_mem_op_data() call
|
||||
* returned 0.
|
||||
*
|
||||
* Return: 0 in case of success, a negative error code otherwise.
|
||||
*/
|
||||
void spi_controller_dma_unmap_mem_op_data(struct spi_controller *ctlr,
|
||||
const struct spi_mem_op *op,
|
||||
struct sg_table *sgt)
|
||||
{
|
||||
struct device *dmadev;
|
||||
|
||||
if (!op->data.nbytes)
|
||||
return;
|
||||
|
||||
if (op->data.dir == SPI_MEM_DATA_OUT && ctlr->dma_tx)
|
||||
dmadev = ctlr->dma_tx->device->dev;
|
||||
else if (op->data.dir == SPI_MEM_DATA_IN && ctlr->dma_rx)
|
||||
dmadev = ctlr->dma_rx->device->dev;
|
||||
else
|
||||
dmadev = ctlr->dev.parent;
|
||||
|
||||
spi_unmap_buf(ctlr, dmadev, sgt,
|
||||
op->data.dir == SPI_MEM_DATA_IN ?
|
||||
DMA_FROM_DEVICE : DMA_TO_DEVICE);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_controller_dma_unmap_mem_op_data);
|
||||
#endif /* __UBOOT__ */
|
||||
|
||||
static int spi_check_buswidth_req(struct spi_slave *slave, u8 buswidth, bool tx)
|
||||
{
|
||||
u32 mode = slave->mode;
|
||||
|
||||
switch (buswidth) {
|
||||
case 1:
|
||||
return 0;
|
||||
|
||||
case 2:
|
||||
if ((tx && (mode & (SPI_TX_DUAL | SPI_TX_QUAD))) ||
|
||||
(!tx && (mode & (SPI_RX_DUAL | SPI_RX_QUAD))))
|
||||
return 0;
|
||||
|
||||
break;
|
||||
|
||||
case 4:
|
||||
if ((tx && (mode & SPI_TX_QUAD)) ||
|
||||
(!tx && (mode & SPI_RX_QUAD)))
|
||||
return 0;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
bool spi_mem_default_supports_op(struct spi_slave *slave,
|
||||
const struct spi_mem_op *op)
|
||||
{
|
||||
if (spi_check_buswidth_req(slave, op->cmd.buswidth, true))
|
||||
return false;
|
||||
|
||||
if (op->addr.nbytes &&
|
||||
spi_check_buswidth_req(slave, op->addr.buswidth, true))
|
||||
return false;
|
||||
|
||||
if (op->dummy.nbytes &&
|
||||
spi_check_buswidth_req(slave, op->dummy.buswidth, true))
|
||||
return false;
|
||||
|
||||
if (op->data.nbytes &&
|
||||
spi_check_buswidth_req(slave, op->data.buswidth,
|
||||
op->data.dir == SPI_MEM_DATA_OUT))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_mem_default_supports_op);
|
||||
|
||||
/**
|
||||
* spi_mem_supports_op() - Check if a memory device and the controller it is
|
||||
* connected to support a specific memory operation
|
||||
* @slave: the SPI device
|
||||
* @op: the memory operation to check
|
||||
*
|
||||
* Some controllers are only supporting Single or Dual IOs, others might only
|
||||
* support specific opcodes, or it can even be that the controller and device
|
||||
* both support Quad IOs but the hardware prevents you from using it because
|
||||
* only 2 IO lines are connected.
|
||||
*
|
||||
* This function checks whether a specific operation is supported.
|
||||
*
|
||||
* Return: true if @op is supported, false otherwise.
|
||||
*/
|
||||
bool spi_mem_supports_op(struct spi_slave *slave,
|
||||
const struct spi_mem_op *op)
|
||||
{
|
||||
struct udevice *bus = slave->dev->parent;
|
||||
struct dm_spi_ops *ops = spi_get_ops(bus);
|
||||
|
||||
if (ops->mem_ops && ops->mem_ops->supports_op)
|
||||
return ops->mem_ops->supports_op(slave, op);
|
||||
|
||||
return spi_mem_default_supports_op(slave, op);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_mem_supports_op);
|
||||
|
||||
/**
|
||||
* spi_mem_exec_op() - Execute a memory operation
|
||||
* @slave: the SPI device
|
||||
* @op: the memory operation to execute
|
||||
*
|
||||
* Executes a memory operation.
|
||||
*
|
||||
* This function first checks that @op is supported and then tries to execute
|
||||
* it.
|
||||
*
|
||||
* Return: 0 in case of success, a negative error code otherwise.
|
||||
*/
|
||||
int spi_mem_exec_op(struct spi_slave *slave, const struct spi_mem_op *op)
|
||||
{
|
||||
struct udevice *bus = slave->dev->parent;
|
||||
struct dm_spi_ops *ops = spi_get_ops(bus);
|
||||
unsigned int pos = 0;
|
||||
const u8 *tx_buf = NULL;
|
||||
u8 *rx_buf = NULL;
|
||||
u8 *op_buf;
|
||||
int op_len;
|
||||
u32 flag;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
if (!spi_mem_supports_op(slave, op))
|
||||
return -ENOTSUPP;
|
||||
|
||||
if (ops->mem_ops) {
|
||||
#ifndef __UBOOT__
|
||||
/*
|
||||
* Flush the message queue before executing our SPI memory
|
||||
* operation to prevent preemption of regular SPI transfers.
|
||||
*/
|
||||
spi_flush_queue(ctlr);
|
||||
|
||||
if (ctlr->auto_runtime_pm) {
|
||||
ret = pm_runtime_get_sync(ctlr->dev.parent);
|
||||
if (ret < 0) {
|
||||
dev_err(&ctlr->dev,
|
||||
"Failed to power device: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_lock(&ctlr->bus_lock_mutex);
|
||||
mutex_lock(&ctlr->io_mutex);
|
||||
#endif
|
||||
ret = ops->mem_ops->exec_op(slave, op);
|
||||
#ifndef __UBOOT__
|
||||
mutex_unlock(&ctlr->io_mutex);
|
||||
mutex_unlock(&ctlr->bus_lock_mutex);
|
||||
|
||||
if (ctlr->auto_runtime_pm)
|
||||
pm_runtime_put(ctlr->dev.parent);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Some controllers only optimize specific paths (typically the
|
||||
* read path) and expect the core to use the regular SPI
|
||||
* interface in other cases.
|
||||
*/
|
||||
if (!ret || ret != -ENOTSUPP)
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifndef __UBOOT__
|
||||
tmpbufsize = sizeof(op->cmd.opcode) + op->addr.nbytes +
|
||||
op->dummy.nbytes;
|
||||
|
||||
/*
|
||||
* Allocate a buffer to transmit the CMD, ADDR cycles with kmalloc() so
|
||||
* we're guaranteed that this buffer is DMA-able, as required by the
|
||||
* SPI layer.
|
||||
*/
|
||||
tmpbuf = kzalloc(tmpbufsize, GFP_KERNEL | GFP_DMA);
|
||||
if (!tmpbuf)
|
||||
return -ENOMEM;
|
||||
|
||||
spi_message_init(&msg);
|
||||
|
||||
tmpbuf[0] = op->cmd.opcode;
|
||||
xfers[xferpos].tx_buf = tmpbuf;
|
||||
xfers[xferpos].len = sizeof(op->cmd.opcode);
|
||||
xfers[xferpos].tx_nbits = op->cmd.buswidth;
|
||||
spi_message_add_tail(&xfers[xferpos], &msg);
|
||||
xferpos++;
|
||||
totalxferlen++;
|
||||
|
||||
if (op->addr.nbytes) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < op->addr.nbytes; i++)
|
||||
tmpbuf[i + 1] = op->addr.val >>
|
||||
(8 * (op->addr.nbytes - i - 1));
|
||||
|
||||
xfers[xferpos].tx_buf = tmpbuf + 1;
|
||||
xfers[xferpos].len = op->addr.nbytes;
|
||||
xfers[xferpos].tx_nbits = op->addr.buswidth;
|
||||
spi_message_add_tail(&xfers[xferpos], &msg);
|
||||
xferpos++;
|
||||
totalxferlen += op->addr.nbytes;
|
||||
}
|
||||
|
||||
if (op->dummy.nbytes) {
|
||||
memset(tmpbuf + op->addr.nbytes + 1, 0xff, op->dummy.nbytes);
|
||||
xfers[xferpos].tx_buf = tmpbuf + op->addr.nbytes + 1;
|
||||
xfers[xferpos].len = op->dummy.nbytes;
|
||||
xfers[xferpos].tx_nbits = op->dummy.buswidth;
|
||||
spi_message_add_tail(&xfers[xferpos], &msg);
|
||||
xferpos++;
|
||||
totalxferlen += op->dummy.nbytes;
|
||||
}
|
||||
|
||||
if (op->data.nbytes) {
|
||||
if (op->data.dir == SPI_MEM_DATA_IN) {
|
||||
xfers[xferpos].rx_buf = op->data.buf.in;
|
||||
xfers[xferpos].rx_nbits = op->data.buswidth;
|
||||
} else {
|
||||
xfers[xferpos].tx_buf = op->data.buf.out;
|
||||
xfers[xferpos].tx_nbits = op->data.buswidth;
|
||||
}
|
||||
|
||||
xfers[xferpos].len = op->data.nbytes;
|
||||
spi_message_add_tail(&xfers[xferpos], &msg);
|
||||
xferpos++;
|
||||
totalxferlen += op->data.nbytes;
|
||||
}
|
||||
|
||||
ret = spi_sync(slave, &msg);
|
||||
|
||||
kfree(tmpbuf);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (msg.actual_length != totalxferlen)
|
||||
return -EIO;
|
||||
#else
|
||||
|
||||
/* U-Boot does not support parallel SPI data lanes */
|
||||
if ((op->cmd.buswidth != 1) ||
|
||||
(op->addr.nbytes && op->addr.buswidth != 1) ||
|
||||
(op->dummy.nbytes && op->dummy.buswidth != 1) ||
|
||||
(op->data.nbytes && op->data.buswidth != 1)) {
|
||||
printf("Dual/Quad raw SPI transfers not supported\n");
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
if (op->data.nbytes) {
|
||||
if (op->data.dir == SPI_MEM_DATA_IN)
|
||||
rx_buf = op->data.buf.in;
|
||||
else
|
||||
tx_buf = op->data.buf.out;
|
||||
}
|
||||
|
||||
op_len = sizeof(op->cmd.opcode) + op->addr.nbytes + op->dummy.nbytes;
|
||||
op_buf = calloc(1, op_len);
|
||||
|
||||
ret = spi_claim_bus(slave);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
op_buf[pos++] = op->cmd.opcode;
|
||||
|
||||
if (op->addr.nbytes) {
|
||||
for (i = 0; i < op->addr.nbytes; i++)
|
||||
op_buf[pos + i] = op->addr.val >>
|
||||
(8 * (op->addr.nbytes - i - 1));
|
||||
|
||||
pos += op->addr.nbytes;
|
||||
}
|
||||
|
||||
if (op->dummy.nbytes)
|
||||
memset(op_buf + pos, 0xff, op->dummy.nbytes);
|
||||
|
||||
/* 1st transfer: opcode + address + dummy cycles */
|
||||
flag = SPI_XFER_BEGIN;
|
||||
/* Make sure to set END bit if no tx or rx data messages follow */
|
||||
if (!tx_buf && !rx_buf)
|
||||
flag |= SPI_XFER_END;
|
||||
|
||||
ret = spi_xfer(slave, op_len * 8, op_buf, NULL, flag);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* 2nd transfer: rx or tx data path */
|
||||
if (tx_buf || rx_buf) {
|
||||
ret = spi_xfer(slave, op->data.nbytes * 8, tx_buf,
|
||||
rx_buf, SPI_XFER_END);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
spi_release_bus(slave);
|
||||
|
||||
for (i = 0; i < pos; i++)
|
||||
debug("%02x ", op_buf[i]);
|
||||
debug("| [%dB %s] ",
|
||||
tx_buf || rx_buf ? op->data.nbytes : 0,
|
||||
tx_buf || rx_buf ? (tx_buf ? "out" : "in") : "-");
|
||||
for (i = 0; i < op->data.nbytes; i++)
|
||||
debug("%02x ", tx_buf ? tx_buf[i] : rx_buf[i]);
|
||||
debug("[ret %d]\n", ret);
|
||||
|
||||
free(op_buf);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
#endif /* __UBOOT__ */
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_mem_exec_op);
|
||||
|
||||
/**
|
||||
* spi_mem_adjust_op_size() - Adjust the data size of a SPI mem operation to
|
||||
* match controller limitations
|
||||
* @slave: the SPI device
|
||||
* @op: the operation to adjust
|
||||
*
|
||||
* Some controllers have FIFO limitations and must split a data transfer
|
||||
* operation into multiple ones, others require a specific alignment for
|
||||
* optimized accesses. This function allows SPI mem drivers to split a single
|
||||
* operation into multiple sub-operations when required.
|
||||
*
|
||||
* Return: a negative error code if the controller can't properly adjust @op,
|
||||
* 0 otherwise. Note that @op->data.nbytes will be updated if @op
|
||||
* can't be handled in a single step.
|
||||
*/
|
||||
int spi_mem_adjust_op_size(struct spi_slave *slave, struct spi_mem_op *op)
|
||||
{
|
||||
struct udevice *bus = slave->dev->parent;
|
||||
struct dm_spi_ops *ops = spi_get_ops(bus);
|
||||
|
||||
if (ops->mem_ops && ops->mem_ops->adjust_op_size)
|
||||
return ops->mem_ops->adjust_op_size(slave, op);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_mem_adjust_op_size);
|
||||
|
||||
#ifndef __UBOOT__
|
||||
static inline struct spi_mem_driver *to_spi_mem_drv(struct device_driver *drv)
|
||||
{
|
||||
return container_of(drv, struct spi_mem_driver, spidrv.driver);
|
||||
}
|
||||
|
||||
static int spi_mem_probe(struct spi_device *spi)
|
||||
{
|
||||
struct spi_mem_driver *memdrv = to_spi_mem_drv(spi->dev.driver);
|
||||
struct spi_mem *mem;
|
||||
|
||||
mem = devm_kzalloc(&spi->dev, sizeof(*mem), GFP_KERNEL);
|
||||
if (!mem)
|
||||
return -ENOMEM;
|
||||
|
||||
mem->spi = spi;
|
||||
spi_set_drvdata(spi, mem);
|
||||
|
||||
return memdrv->probe(mem);
|
||||
}
|
||||
|
||||
static int spi_mem_remove(struct spi_device *spi)
|
||||
{
|
||||
struct spi_mem_driver *memdrv = to_spi_mem_drv(spi->dev.driver);
|
||||
struct spi_mem *mem = spi_get_drvdata(spi);
|
||||
|
||||
if (memdrv->remove)
|
||||
return memdrv->remove(mem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void spi_mem_shutdown(struct spi_device *spi)
|
||||
{
|
||||
struct spi_mem_driver *memdrv = to_spi_mem_drv(spi->dev.driver);
|
||||
struct spi_mem *mem = spi_get_drvdata(spi);
|
||||
|
||||
if (memdrv->shutdown)
|
||||
memdrv->shutdown(mem);
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_mem_driver_register_with_owner() - Register a SPI memory driver
|
||||
* @memdrv: the SPI memory driver to register
|
||||
* @owner: the owner of this driver
|
||||
*
|
||||
* Registers a SPI memory driver.
|
||||
*
|
||||
* Return: 0 in case of success, a negative error core otherwise.
|
||||
*/
|
||||
|
||||
int spi_mem_driver_register_with_owner(struct spi_mem_driver *memdrv,
|
||||
struct module *owner)
|
||||
{
|
||||
memdrv->spidrv.probe = spi_mem_probe;
|
||||
memdrv->spidrv.remove = spi_mem_remove;
|
||||
memdrv->spidrv.shutdown = spi_mem_shutdown;
|
||||
|
||||
return __spi_register_driver(owner, &memdrv->spidrv);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_mem_driver_register_with_owner);
|
||||
|
||||
/**
|
||||
* spi_mem_driver_unregister_with_owner() - Unregister a SPI memory driver
|
||||
* @memdrv: the SPI memory driver to unregister
|
||||
*
|
||||
* Unregisters a SPI memory driver.
|
||||
*/
|
||||
void spi_mem_driver_unregister(struct spi_mem_driver *memdrv)
|
||||
{
|
||||
spi_unregister_driver(&memdrv->spidrv);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_mem_driver_unregister);
|
||||
#endif /* __UBOOT__ */
|
|
@ -240,7 +240,7 @@
|
|||
/* LB refresh timer prescal, 266MHz/32 */
|
||||
#define CONFIG_SYS_LBC_MRTPR 0x20000000 /*TODO */
|
||||
|
||||
/* drivers/mtd/nand/nand.c */
|
||||
/* drivers/mtd/nand/raw/nand.c */
|
||||
#if defined(CONFIG_NAND) && defined(CONFIG_SPL_BUILD)
|
||||
#define CONFIG_SYS_NAND_BASE 0xFFF00000
|
||||
#else
|
||||
|
|
|
@ -15,9 +15,12 @@
|
|||
#define MTD_DEV_TYPE_NOR 0x0001
|
||||
#define MTD_DEV_TYPE_NAND 0x0002
|
||||
#define MTD_DEV_TYPE_ONENAND 0x0004
|
||||
#define MTD_DEV_TYPE_SPINAND 0x0008
|
||||
|
||||
#define MTD_DEV_TYPE(type) ((type == MTD_DEV_TYPE_NAND) ? "nand" : \
|
||||
(type == MTD_DEV_TYPE_ONENAND) ? "onenand" : "nor")
|
||||
#define MTD_DEV_TYPE(type) (type == MTD_DEV_TYPE_NAND ? "nand" : \
|
||||
(type == MTD_DEV_TYPE_NOR ? "nor" : \
|
||||
(type == MTD_DEV_TYPE_ONENAND ? "onenand" : \
|
||||
"spi-nand"))) \
|
||||
|
||||
struct mtd_device {
|
||||
struct list_head link;
|
||||
|
|
|
@ -20,7 +20,11 @@
|
|||
#include <linux/compat.h>
|
||||
#include <mtd/mtd-abi.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/list.h>
|
||||
#include <div64.h>
|
||||
#if IS_ENABLED(CONFIG_DM)
|
||||
#include <dm/device.h>
|
||||
#endif
|
||||
|
||||
#define MAX_MTD_DEVICES 32
|
||||
#endif
|
||||
|
@ -304,8 +308,64 @@ struct mtd_info {
|
|||
struct udevice *dev;
|
||||
#endif
|
||||
int usecount;
|
||||
|
||||
/* MTD devices do not have any parent. MTD partitions do. */
|
||||
struct mtd_info *parent;
|
||||
|
||||
/*
|
||||
* Offset of the partition relatively to the parent offset.
|
||||
* Is 0 for real MTD devices (ie. not partitions).
|
||||
*/
|
||||
u64 offset;
|
||||
|
||||
/*
|
||||
* List node used to add an MTD partition to the parent
|
||||
* partition list.
|
||||
*/
|
||||
struct list_head node;
|
||||
|
||||
/*
|
||||
* List of partitions attached to this MTD device (the parent
|
||||
* MTD device can itself be a partition).
|
||||
*/
|
||||
struct list_head partitions;
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_DM)
|
||||
static inline void mtd_set_of_node(struct mtd_info *mtd,
|
||||
const struct device_node *np)
|
||||
{
|
||||
mtd->dev->node.np = np;
|
||||
}
|
||||
|
||||
static inline const struct device_node *mtd_get_of_node(struct mtd_info *mtd)
|
||||
{
|
||||
return mtd->dev->node.np;
|
||||
}
|
||||
#else
|
||||
struct device_node;
|
||||
|
||||
static inline void mtd_set_of_node(struct mtd_info *mtd,
|
||||
const struct device_node *np)
|
||||
{
|
||||
}
|
||||
|
||||
static inline const struct device_node *mtd_get_of_node(struct mtd_info *mtd)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline bool mtd_is_partition(const struct mtd_info *mtd)
|
||||
{
|
||||
return mtd->parent;
|
||||
}
|
||||
|
||||
static inline bool mtd_has_partitions(const struct mtd_info *mtd)
|
||||
{
|
||||
return !list_empty(&mtd->partitions);
|
||||
}
|
||||
|
||||
int mtd_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *oobecc);
|
||||
int mtd_ooblayout_find_eccregion(struct mtd_info *mtd, int eccbyte,
|
||||
|
@ -351,17 +411,7 @@ int mtd_panic_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
|
|||
const u_char *buf);
|
||||
|
||||
int mtd_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops);
|
||||
|
||||
static inline int mtd_write_oob(struct mtd_info *mtd, loff_t to,
|
||||
struct mtd_oob_ops *ops)
|
||||
{
|
||||
ops->retlen = ops->oobretlen = 0;
|
||||
if (!mtd->_write_oob)
|
||||
return -EOPNOTSUPP;
|
||||
if (!(mtd->flags & MTD_WRITEABLE))
|
||||
return -EROFS;
|
||||
return mtd->_write_oob(mtd, to, ops);
|
||||
}
|
||||
int mtd_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops);
|
||||
|
||||
int mtd_get_fact_prot_info(struct mtd_info *mtd, size_t len, size_t *retlen,
|
||||
struct otp_info *buf);
|
||||
|
@ -515,6 +565,12 @@ int del_mtd_device(struct mtd_info *mtd);
|
|||
int add_mtd_partitions(struct mtd_info *, const struct mtd_partition *, int);
|
||||
int del_mtd_partitions(struct mtd_info *);
|
||||
|
||||
struct mtd_info *__mtd_next_device(int i);
|
||||
#define mtd_for_each_device(mtd) \
|
||||
for ((mtd) = __mtd_next_device(0); \
|
||||
(mtd) != NULL; \
|
||||
(mtd) = __mtd_next_device(mtd->index + 1))
|
||||
|
||||
int mtd_arg_off(const char *arg, int *idx, loff_t *off, loff_t *size,
|
||||
loff_t *maxsize, int devtype, uint64_t chipsize);
|
||||
int mtd_arg_off_size(int argc, char *const argv[], int *idx, loff_t *off,
|
||||
|
@ -525,5 +581,10 @@ int mtd_arg_off_size(int argc, char *const argv[], int *idx, loff_t *off,
|
|||
void mtd_get_len_incl_bad(struct mtd_info *mtd, uint64_t offset,
|
||||
const uint64_t length, uint64_t *len_incl_bad,
|
||||
int *truncated);
|
||||
|
||||
/* drivers/mtd/mtd_uboot.c */
|
||||
int mtd_search_alternate_name(const char *mtdname, char *altname,
|
||||
unsigned int max_len);
|
||||
|
||||
#endif
|
||||
#endif /* __MTD_MTD_H__ */
|
||||
|
|
734
include/linux/mtd/nand.h
Normal file
734
include/linux/mtd/nand.h
Normal file
|
@ -0,0 +1,734 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright 2017 - Free Electrons
|
||||
*
|
||||
* Authors:
|
||||
* Boris Brezillon <boris.brezillon@free-electrons.com>
|
||||
* Peter Pan <peterpandong@micron.com>
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_MTD_NAND_H
|
||||
#define __LINUX_MTD_NAND_H
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
|
||||
/**
|
||||
* struct nand_memory_organization - Memory organization structure
|
||||
* @bits_per_cell: number of bits per NAND cell
|
||||
* @pagesize: page size
|
||||
* @oobsize: OOB area size
|
||||
* @pages_per_eraseblock: number of pages per eraseblock
|
||||
* @eraseblocks_per_lun: number of eraseblocks per LUN (Logical Unit Number)
|
||||
* @planes_per_lun: number of planes per LUN
|
||||
* @luns_per_target: number of LUN per target (target is a synonym for die)
|
||||
* @ntargets: total number of targets exposed by the NAND device
|
||||
*/
|
||||
struct nand_memory_organization {
|
||||
unsigned int bits_per_cell;
|
||||
unsigned int pagesize;
|
||||
unsigned int oobsize;
|
||||
unsigned int pages_per_eraseblock;
|
||||
unsigned int eraseblocks_per_lun;
|
||||
unsigned int planes_per_lun;
|
||||
unsigned int luns_per_target;
|
||||
unsigned int ntargets;
|
||||
};
|
||||
|
||||
#define NAND_MEMORG(bpc, ps, os, ppe, epl, ppl, lpt, nt) \
|
||||
{ \
|
||||
.bits_per_cell = (bpc), \
|
||||
.pagesize = (ps), \
|
||||
.oobsize = (os), \
|
||||
.pages_per_eraseblock = (ppe), \
|
||||
.eraseblocks_per_lun = (epl), \
|
||||
.planes_per_lun = (ppl), \
|
||||
.luns_per_target = (lpt), \
|
||||
.ntargets = (nt), \
|
||||
}
|
||||
|
||||
/**
|
||||
* struct nand_row_converter - Information needed to convert an absolute offset
|
||||
* into a row address
|
||||
* @lun_addr_shift: position of the LUN identifier in the row address
|
||||
* @eraseblock_addr_shift: position of the eraseblock identifier in the row
|
||||
* address
|
||||
*/
|
||||
struct nand_row_converter {
|
||||
unsigned int lun_addr_shift;
|
||||
unsigned int eraseblock_addr_shift;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct nand_pos - NAND position object
|
||||
* @target: the NAND target/die
|
||||
* @lun: the LUN identifier
|
||||
* @plane: the plane within the LUN
|
||||
* @eraseblock: the eraseblock within the LUN
|
||||
* @page: the page within the LUN
|
||||
*
|
||||
* These information are usually used by specific sub-layers to select the
|
||||
* appropriate target/die and generate a row address to pass to the device.
|
||||
*/
|
||||
struct nand_pos {
|
||||
unsigned int target;
|
||||
unsigned int lun;
|
||||
unsigned int plane;
|
||||
unsigned int eraseblock;
|
||||
unsigned int page;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct nand_page_io_req - NAND I/O request object
|
||||
* @pos: the position this I/O request is targeting
|
||||
* @dataoffs: the offset within the page
|
||||
* @datalen: number of data bytes to read from/write to this page
|
||||
* @databuf: buffer to store data in or get data from
|
||||
* @ooboffs: the OOB offset within the page
|
||||
* @ooblen: the number of OOB bytes to read from/write to this page
|
||||
* @oobbuf: buffer to store OOB data in or get OOB data from
|
||||
* @mode: one of the %MTD_OPS_XXX mode
|
||||
*
|
||||
* This object is used to pass per-page I/O requests to NAND sub-layers. This
|
||||
* way all useful information are already formatted in a useful way and
|
||||
* specific NAND layers can focus on translating these information into
|
||||
* specific commands/operations.
|
||||
*/
|
||||
struct nand_page_io_req {
|
||||
struct nand_pos pos;
|
||||
unsigned int dataoffs;
|
||||
unsigned int datalen;
|
||||
union {
|
||||
const void *out;
|
||||
void *in;
|
||||
} databuf;
|
||||
unsigned int ooboffs;
|
||||
unsigned int ooblen;
|
||||
union {
|
||||
const void *out;
|
||||
void *in;
|
||||
} oobbuf;
|
||||
int mode;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct nand_ecc_req - NAND ECC requirements
|
||||
* @strength: ECC strength
|
||||
* @step_size: ECC step/block size
|
||||
*/
|
||||
struct nand_ecc_req {
|
||||
unsigned int strength;
|
||||
unsigned int step_size;
|
||||
};
|
||||
|
||||
#define NAND_ECCREQ(str, stp) { .strength = (str), .step_size = (stp) }
|
||||
|
||||
/**
|
||||
* struct nand_bbt - bad block table object
|
||||
* @cache: in memory BBT cache
|
||||
*/
|
||||
struct nand_bbt {
|
||||
unsigned long *cache;
|
||||
};
|
||||
|
||||
struct nand_device;
|
||||
|
||||
/**
|
||||
* struct nand_ops - NAND operations
|
||||
* @erase: erase a specific block. No need to check if the block is bad before
|
||||
* erasing, this has been taken care of by the generic NAND layer
|
||||
* @markbad: mark a specific block bad. No need to check if the block is
|
||||
* already marked bad, this has been taken care of by the generic
|
||||
* NAND layer. This method should just write the BBM (Bad Block
|
||||
* Marker) so that future call to struct_nand_ops->isbad() return
|
||||
* true
|
||||
* @isbad: check whether a block is bad or not. This method should just read
|
||||
* the BBM and return whether the block is bad or not based on what it
|
||||
* reads
|
||||
*
|
||||
* These are all low level operations that should be implemented by specialized
|
||||
* NAND layers (SPI NAND, raw NAND, ...).
|
||||
*/
|
||||
struct nand_ops {
|
||||
int (*erase)(struct nand_device *nand, const struct nand_pos *pos);
|
||||
int (*markbad)(struct nand_device *nand, const struct nand_pos *pos);
|
||||
bool (*isbad)(struct nand_device *nand, const struct nand_pos *pos);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct nand_device - NAND device
|
||||
* @mtd: MTD instance attached to the NAND device
|
||||
* @memorg: memory layout
|
||||
* @eccreq: ECC requirements
|
||||
* @rowconv: position to row address converter
|
||||
* @bbt: bad block table info
|
||||
* @ops: NAND operations attached to the NAND device
|
||||
*
|
||||
* Generic NAND object. Specialized NAND layers (raw NAND, SPI NAND, OneNAND)
|
||||
* should declare their own NAND object embedding a nand_device struct (that's
|
||||
* how inheritance is done).
|
||||
* struct_nand_device->memorg and struct_nand_device->eccreq should be filled
|
||||
* at device detection time to reflect the NAND device
|
||||
* capabilities/requirements. Once this is done nanddev_init() can be called.
|
||||
* It will take care of converting NAND information into MTD ones, which means
|
||||
* the specialized NAND layers should never manually tweak
|
||||
* struct_nand_device->mtd except for the ->_read/write() hooks.
|
||||
*/
|
||||
struct nand_device {
|
||||
struct mtd_info *mtd;
|
||||
struct nand_memory_organization memorg;
|
||||
struct nand_ecc_req eccreq;
|
||||
struct nand_row_converter rowconv;
|
||||
struct nand_bbt bbt;
|
||||
const struct nand_ops *ops;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct nand_io_iter - NAND I/O iterator
|
||||
* @req: current I/O request
|
||||
* @oobbytes_per_page: maximum number of OOB bytes per page
|
||||
* @dataleft: remaining number of data bytes to read/write
|
||||
* @oobleft: remaining number of OOB bytes to read/write
|
||||
*
|
||||
* Can be used by specialized NAND layers to iterate over all pages covered
|
||||
* by an MTD I/O request, which should greatly simplifies the boiler-plate
|
||||
* code needed to read/write data from/to a NAND device.
|
||||
*/
|
||||
struct nand_io_iter {
|
||||
struct nand_page_io_req req;
|
||||
unsigned int oobbytes_per_page;
|
||||
unsigned int dataleft;
|
||||
unsigned int oobleft;
|
||||
};
|
||||
|
||||
/**
|
||||
* mtd_to_nanddev() - Get the NAND device attached to the MTD instance
|
||||
* @mtd: MTD instance
|
||||
*
|
||||
* Return: the NAND device embedding @mtd.
|
||||
*/
|
||||
static inline struct nand_device *mtd_to_nanddev(struct mtd_info *mtd)
|
||||
{
|
||||
return mtd->priv;
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_to_mtd() - Get the MTD device attached to a NAND device
|
||||
* @nand: NAND device
|
||||
*
|
||||
* Return: the MTD device embedded in @nand.
|
||||
*/
|
||||
static inline struct mtd_info *nanddev_to_mtd(struct nand_device *nand)
|
||||
{
|
||||
return nand->mtd;
|
||||
}
|
||||
|
||||
/*
|
||||
* nanddev_bits_per_cell() - Get the number of bits per cell
|
||||
* @nand: NAND device
|
||||
*
|
||||
* Return: the number of bits per cell.
|
||||
*/
|
||||
static inline unsigned int nanddev_bits_per_cell(const struct nand_device *nand)
|
||||
{
|
||||
return nand->memorg.bits_per_cell;
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_page_size() - Get NAND page size
|
||||
* @nand: NAND device
|
||||
*
|
||||
* Return: the page size.
|
||||
*/
|
||||
static inline size_t nanddev_page_size(const struct nand_device *nand)
|
||||
{
|
||||
return nand->memorg.pagesize;
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_per_page_oobsize() - Get NAND OOB size
|
||||
* @nand: NAND device
|
||||
*
|
||||
* Return: the OOB size.
|
||||
*/
|
||||
static inline unsigned int
|
||||
nanddev_per_page_oobsize(const struct nand_device *nand)
|
||||
{
|
||||
return nand->memorg.oobsize;
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_pages_per_eraseblock() - Get the number of pages per eraseblock
|
||||
* @nand: NAND device
|
||||
*
|
||||
* Return: the number of pages per eraseblock.
|
||||
*/
|
||||
static inline unsigned int
|
||||
nanddev_pages_per_eraseblock(const struct nand_device *nand)
|
||||
{
|
||||
return nand->memorg.pages_per_eraseblock;
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_per_page_oobsize() - Get NAND erase block size
|
||||
* @nand: NAND device
|
||||
*
|
||||
* Return: the eraseblock size.
|
||||
*/
|
||||
static inline size_t nanddev_eraseblock_size(const struct nand_device *nand)
|
||||
{
|
||||
return nand->memorg.pagesize * nand->memorg.pages_per_eraseblock;
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_eraseblocks_per_lun() - Get the number of eraseblocks per LUN
|
||||
* @nand: NAND device
|
||||
*
|
||||
* Return: the number of eraseblocks per LUN.
|
||||
*/
|
||||
static inline unsigned int
|
||||
nanddev_eraseblocks_per_lun(const struct nand_device *nand)
|
||||
{
|
||||
return nand->memorg.eraseblocks_per_lun;
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_target_size() - Get the total size provided by a single target/die
|
||||
* @nand: NAND device
|
||||
*
|
||||
* Return: the total size exposed by a single target/die in bytes.
|
||||
*/
|
||||
static inline u64 nanddev_target_size(const struct nand_device *nand)
|
||||
{
|
||||
return (u64)nand->memorg.luns_per_target *
|
||||
nand->memorg.eraseblocks_per_lun *
|
||||
nand->memorg.pages_per_eraseblock *
|
||||
nand->memorg.pagesize;
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_ntarget() - Get the total of targets
|
||||
* @nand: NAND device
|
||||
*
|
||||
* Return: the number of targets/dies exposed by @nand.
|
||||
*/
|
||||
static inline unsigned int nanddev_ntargets(const struct nand_device *nand)
|
||||
{
|
||||
return nand->memorg.ntargets;
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_neraseblocks() - Get the total number of erasablocks
|
||||
* @nand: NAND device
|
||||
*
|
||||
* Return: the total number of eraseblocks exposed by @nand.
|
||||
*/
|
||||
static inline unsigned int nanddev_neraseblocks(const struct nand_device *nand)
|
||||
{
|
||||
return (u64)nand->memorg.luns_per_target *
|
||||
nand->memorg.eraseblocks_per_lun *
|
||||
nand->memorg.pages_per_eraseblock;
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_size() - Get NAND size
|
||||
* @nand: NAND device
|
||||
*
|
||||
* Return: the total size (in bytes) exposed by @nand.
|
||||
*/
|
||||
static inline u64 nanddev_size(const struct nand_device *nand)
|
||||
{
|
||||
return nanddev_target_size(nand) * nanddev_ntargets(nand);
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_get_memorg() - Extract memory organization info from a NAND device
|
||||
* @nand: NAND device
|
||||
*
|
||||
* This can be used by the upper layer to fill the memorg info before calling
|
||||
* nanddev_init().
|
||||
*
|
||||
* Return: the memorg object embedded in the NAND device.
|
||||
*/
|
||||
static inline struct nand_memory_organization *
|
||||
nanddev_get_memorg(struct nand_device *nand)
|
||||
{
|
||||
return &nand->memorg;
|
||||
}
|
||||
|
||||
int nanddev_init(struct nand_device *nand, const struct nand_ops *ops,
|
||||
struct module *owner);
|
||||
void nanddev_cleanup(struct nand_device *nand);
|
||||
|
||||
/**
|
||||
* nanddev_register() - Register a NAND device
|
||||
* @nand: NAND device
|
||||
*
|
||||
* Register a NAND device.
|
||||
* This function is just a wrapper around mtd_device_register()
|
||||
* registering the MTD device embedded in @nand.
|
||||
*
|
||||
* Return: 0 in case of success, a negative error code otherwise.
|
||||
*/
|
||||
static inline int nanddev_register(struct nand_device *nand)
|
||||
{
|
||||
return mtd_device_register(nand->mtd, NULL, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_unregister() - Unregister a NAND device
|
||||
* @nand: NAND device
|
||||
*
|
||||
* Unregister a NAND device.
|
||||
* This function is just a wrapper around mtd_device_unregister()
|
||||
* unregistering the MTD device embedded in @nand.
|
||||
*
|
||||
* Return: 0 in case of success, a negative error code otherwise.
|
||||
*/
|
||||
static inline int nanddev_unregister(struct nand_device *nand)
|
||||
{
|
||||
return mtd_device_unregister(nand->mtd);
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_set_of_node() - Attach a DT node to a NAND device
|
||||
* @nand: NAND device
|
||||
* @np: DT node
|
||||
*
|
||||
* Attach a DT node to a NAND device.
|
||||
*/
|
||||
static inline void nanddev_set_of_node(struct nand_device *nand,
|
||||
const struct device_node *np)
|
||||
{
|
||||
mtd_set_of_node(nand->mtd, np);
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_get_of_node() - Retrieve the DT node attached to a NAND device
|
||||
* @nand: NAND device
|
||||
*
|
||||
* Return: the DT node attached to @nand.
|
||||
*/
|
||||
static inline const struct device_node *nanddev_get_of_node(struct nand_device *nand)
|
||||
{
|
||||
return mtd_get_of_node(nand->mtd);
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_offs_to_pos() - Convert an absolute NAND offset into a NAND position
|
||||
* @nand: NAND device
|
||||
* @offs: absolute NAND offset (usually passed by the MTD layer)
|
||||
* @pos: a NAND position object to fill in
|
||||
*
|
||||
* Converts @offs into a nand_pos representation.
|
||||
*
|
||||
* Return: the offset within the NAND page pointed by @pos.
|
||||
*/
|
||||
static inline unsigned int nanddev_offs_to_pos(struct nand_device *nand,
|
||||
loff_t offs,
|
||||
struct nand_pos *pos)
|
||||
{
|
||||
unsigned int pageoffs;
|
||||
u64 tmp = offs;
|
||||
|
||||
pageoffs = do_div(tmp, nand->memorg.pagesize);
|
||||
pos->page = do_div(tmp, nand->memorg.pages_per_eraseblock);
|
||||
pos->eraseblock = do_div(tmp, nand->memorg.eraseblocks_per_lun);
|
||||
pos->plane = pos->eraseblock % nand->memorg.planes_per_lun;
|
||||
pos->lun = do_div(tmp, nand->memorg.luns_per_target);
|
||||
pos->target = tmp;
|
||||
|
||||
return pageoffs;
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_pos_cmp() - Compare two NAND positions
|
||||
* @a: First NAND position
|
||||
* @b: Second NAND position
|
||||
*
|
||||
* Compares two NAND positions.
|
||||
*
|
||||
* Return: -1 if @a < @b, 0 if @a == @b and 1 if @a > @b.
|
||||
*/
|
||||
static inline int nanddev_pos_cmp(const struct nand_pos *a,
|
||||
const struct nand_pos *b)
|
||||
{
|
||||
if (a->target != b->target)
|
||||
return a->target < b->target ? -1 : 1;
|
||||
|
||||
if (a->lun != b->lun)
|
||||
return a->lun < b->lun ? -1 : 1;
|
||||
|
||||
if (a->eraseblock != b->eraseblock)
|
||||
return a->eraseblock < b->eraseblock ? -1 : 1;
|
||||
|
||||
if (a->page != b->page)
|
||||
return a->page < b->page ? -1 : 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_pos_to_offs() - Convert a NAND position into an absolute offset
|
||||
* @nand: NAND device
|
||||
* @pos: the NAND position to convert
|
||||
*
|
||||
* Converts @pos NAND position into an absolute offset.
|
||||
*
|
||||
* Return: the absolute offset. Note that @pos points to the beginning of a
|
||||
* page, if one wants to point to a specific offset within this page
|
||||
* the returned offset has to be adjusted manually.
|
||||
*/
|
||||
static inline loff_t nanddev_pos_to_offs(struct nand_device *nand,
|
||||
const struct nand_pos *pos)
|
||||
{
|
||||
unsigned int npages;
|
||||
|
||||
npages = pos->page +
|
||||
((pos->eraseblock +
|
||||
(pos->lun +
|
||||
(pos->target * nand->memorg.luns_per_target)) *
|
||||
nand->memorg.eraseblocks_per_lun) *
|
||||
nand->memorg.pages_per_eraseblock);
|
||||
|
||||
return (loff_t)npages * nand->memorg.pagesize;
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_pos_to_row() - Extract a row address from a NAND position
|
||||
* @nand: NAND device
|
||||
* @pos: the position to convert
|
||||
*
|
||||
* Converts a NAND position into a row address that can then be passed to the
|
||||
* device.
|
||||
*
|
||||
* Return: the row address extracted from @pos.
|
||||
*/
|
||||
static inline unsigned int nanddev_pos_to_row(struct nand_device *nand,
|
||||
const struct nand_pos *pos)
|
||||
{
|
||||
return (pos->lun << nand->rowconv.lun_addr_shift) |
|
||||
(pos->eraseblock << nand->rowconv.eraseblock_addr_shift) |
|
||||
pos->page;
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_pos_next_target() - Move a position to the next target/die
|
||||
* @nand: NAND device
|
||||
* @pos: the position to update
|
||||
*
|
||||
* Updates @pos to point to the start of the next target/die. Useful when you
|
||||
* want to iterate over all targets/dies of a NAND device.
|
||||
*/
|
||||
static inline void nanddev_pos_next_target(struct nand_device *nand,
|
||||
struct nand_pos *pos)
|
||||
{
|
||||
pos->page = 0;
|
||||
pos->plane = 0;
|
||||
pos->eraseblock = 0;
|
||||
pos->lun = 0;
|
||||
pos->target++;
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_pos_next_lun() - Move a position to the next LUN
|
||||
* @nand: NAND device
|
||||
* @pos: the position to update
|
||||
*
|
||||
* Updates @pos to point to the start of the next LUN. Useful when you want to
|
||||
* iterate over all LUNs of a NAND device.
|
||||
*/
|
||||
static inline void nanddev_pos_next_lun(struct nand_device *nand,
|
||||
struct nand_pos *pos)
|
||||
{
|
||||
if (pos->lun >= nand->memorg.luns_per_target - 1)
|
||||
return nanddev_pos_next_target(nand, pos);
|
||||
|
||||
pos->lun++;
|
||||
pos->page = 0;
|
||||
pos->plane = 0;
|
||||
pos->eraseblock = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_pos_next_eraseblock() - Move a position to the next eraseblock
|
||||
* @nand: NAND device
|
||||
* @pos: the position to update
|
||||
*
|
||||
* Updates @pos to point to the start of the next eraseblock. Useful when you
|
||||
* want to iterate over all eraseblocks of a NAND device.
|
||||
*/
|
||||
static inline void nanddev_pos_next_eraseblock(struct nand_device *nand,
|
||||
struct nand_pos *pos)
|
||||
{
|
||||
if (pos->eraseblock >= nand->memorg.eraseblocks_per_lun - 1)
|
||||
return nanddev_pos_next_lun(nand, pos);
|
||||
|
||||
pos->eraseblock++;
|
||||
pos->page = 0;
|
||||
pos->plane = pos->eraseblock % nand->memorg.planes_per_lun;
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_pos_next_eraseblock() - Move a position to the next page
|
||||
* @nand: NAND device
|
||||
* @pos: the position to update
|
||||
*
|
||||
* Updates @pos to point to the start of the next page. Useful when you want to
|
||||
* iterate over all pages of a NAND device.
|
||||
*/
|
||||
static inline void nanddev_pos_next_page(struct nand_device *nand,
|
||||
struct nand_pos *pos)
|
||||
{
|
||||
if (pos->page >= nand->memorg.pages_per_eraseblock - 1)
|
||||
return nanddev_pos_next_eraseblock(nand, pos);
|
||||
|
||||
pos->page++;
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_io_iter_init - Initialize a NAND I/O iterator
|
||||
* @nand: NAND device
|
||||
* @offs: absolute offset
|
||||
* @req: MTD request
|
||||
* @iter: NAND I/O iterator
|
||||
*
|
||||
* Initializes a NAND iterator based on the information passed by the MTD
|
||||
* layer.
|
||||
*/
|
||||
static inline void nanddev_io_iter_init(struct nand_device *nand,
|
||||
loff_t offs, struct mtd_oob_ops *req,
|
||||
struct nand_io_iter *iter)
|
||||
{
|
||||
struct mtd_info *mtd = nanddev_to_mtd(nand);
|
||||
|
||||
iter->req.mode = req->mode;
|
||||
iter->req.dataoffs = nanddev_offs_to_pos(nand, offs, &iter->req.pos);
|
||||
iter->req.ooboffs = req->ooboffs;
|
||||
iter->oobbytes_per_page = mtd_oobavail(mtd, req);
|
||||
iter->dataleft = req->len;
|
||||
iter->oobleft = req->ooblen;
|
||||
iter->req.databuf.in = req->datbuf;
|
||||
iter->req.datalen = min_t(unsigned int,
|
||||
nand->memorg.pagesize - iter->req.dataoffs,
|
||||
iter->dataleft);
|
||||
iter->req.oobbuf.in = req->oobbuf;
|
||||
iter->req.ooblen = min_t(unsigned int,
|
||||
iter->oobbytes_per_page - iter->req.ooboffs,
|
||||
iter->oobleft);
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_io_iter_next_page - Move to the next page
|
||||
* @nand: NAND device
|
||||
* @iter: NAND I/O iterator
|
||||
*
|
||||
* Updates the @iter to point to the next page.
|
||||
*/
|
||||
static inline void nanddev_io_iter_next_page(struct nand_device *nand,
|
||||
struct nand_io_iter *iter)
|
||||
{
|
||||
nanddev_pos_next_page(nand, &iter->req.pos);
|
||||
iter->dataleft -= iter->req.datalen;
|
||||
iter->req.databuf.in += iter->req.datalen;
|
||||
iter->oobleft -= iter->req.ooblen;
|
||||
iter->req.oobbuf.in += iter->req.ooblen;
|
||||
iter->req.dataoffs = 0;
|
||||
iter->req.ooboffs = 0;
|
||||
iter->req.datalen = min_t(unsigned int, nand->memorg.pagesize,
|
||||
iter->dataleft);
|
||||
iter->req.ooblen = min_t(unsigned int, iter->oobbytes_per_page,
|
||||
iter->oobleft);
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_io_iter_end - Should end iteration or not
|
||||
* @nand: NAND device
|
||||
* @iter: NAND I/O iterator
|
||||
*
|
||||
* Check whether @iter has reached the end of the NAND portion it was asked to
|
||||
* iterate on or not.
|
||||
*
|
||||
* Return: true if @iter has reached the end of the iteration request, false
|
||||
* otherwise.
|
||||
*/
|
||||
static inline bool nanddev_io_iter_end(struct nand_device *nand,
|
||||
const struct nand_io_iter *iter)
|
||||
{
|
||||
if (iter->dataleft || iter->oobleft)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_io_for_each_page - Iterate over all NAND pages contained in an MTD I/O
|
||||
* request
|
||||
* @nand: NAND device
|
||||
* @start: start address to read/write from
|
||||
* @req: MTD I/O request
|
||||
* @iter: NAND I/O iterator
|
||||
*
|
||||
* Should be used for iterate over pages that are contained in an MTD request.
|
||||
*/
|
||||
#define nanddev_io_for_each_page(nand, start, req, iter) \
|
||||
for (nanddev_io_iter_init(nand, start, req, iter); \
|
||||
!nanddev_io_iter_end(nand, iter); \
|
||||
nanddev_io_iter_next_page(nand, iter))
|
||||
|
||||
bool nanddev_isbad(struct nand_device *nand, const struct nand_pos *pos);
|
||||
bool nanddev_isreserved(struct nand_device *nand, const struct nand_pos *pos);
|
||||
int nanddev_erase(struct nand_device *nand, const struct nand_pos *pos);
|
||||
int nanddev_markbad(struct nand_device *nand, const struct nand_pos *pos);
|
||||
|
||||
/* BBT related functions */
|
||||
enum nand_bbt_block_status {
|
||||
NAND_BBT_BLOCK_STATUS_UNKNOWN,
|
||||
NAND_BBT_BLOCK_GOOD,
|
||||
NAND_BBT_BLOCK_WORN,
|
||||
NAND_BBT_BLOCK_RESERVED,
|
||||
NAND_BBT_BLOCK_FACTORY_BAD,
|
||||
NAND_BBT_BLOCK_NUM_STATUS,
|
||||
};
|
||||
|
||||
int nanddev_bbt_init(struct nand_device *nand);
|
||||
void nanddev_bbt_cleanup(struct nand_device *nand);
|
||||
int nanddev_bbt_update(struct nand_device *nand);
|
||||
int nanddev_bbt_get_block_status(const struct nand_device *nand,
|
||||
unsigned int entry);
|
||||
int nanddev_bbt_set_block_status(struct nand_device *nand, unsigned int entry,
|
||||
enum nand_bbt_block_status status);
|
||||
int nanddev_bbt_markbad(struct nand_device *nand, unsigned int block);
|
||||
|
||||
/**
|
||||
* nanddev_bbt_pos_to_entry() - Convert a NAND position into a BBT entry
|
||||
* @nand: NAND device
|
||||
* @pos: the NAND position we want to get BBT entry for
|
||||
*
|
||||
* Return the BBT entry used to store information about the eraseblock pointed
|
||||
* by @pos.
|
||||
*
|
||||
* Return: the BBT entry storing information about eraseblock pointed by @pos.
|
||||
*/
|
||||
static inline unsigned int nanddev_bbt_pos_to_entry(struct nand_device *nand,
|
||||
const struct nand_pos *pos)
|
||||
{
|
||||
return pos->eraseblock +
|
||||
((pos->lun + (pos->target * nand->memorg.luns_per_target)) *
|
||||
nand->memorg.eraseblocks_per_lun);
|
||||
}
|
||||
|
||||
/**
|
||||
* nanddev_bbt_is_initialized() - Check if the BBT has been initialized
|
||||
* @nand: NAND device
|
||||
*
|
||||
* Return: true if the BBT has been initialized, false otherwise.
|
||||
*/
|
||||
static inline bool nanddev_bbt_is_initialized(struct nand_device *nand)
|
||||
{
|
||||
return !!nand->bbt.cache;
|
||||
}
|
||||
|
||||
/* MTD -> NAND helper functions. */
|
||||
int nanddev_mtd_erase(struct mtd_info *mtd, struct erase_info *einfo);
|
||||
|
||||
#endif /* __LINUX_MTD_NAND_H */
|
|
@ -81,10 +81,30 @@ extern void register_mtd_parser(struct mtd_part_parser *parser);
|
|||
extern void deregister_mtd_parser(struct mtd_part_parser *parser);
|
||||
#endif
|
||||
|
||||
int mtd_is_partition(const struct mtd_info *mtd);
|
||||
int mtd_add_partition(struct mtd_info *master, const char *name,
|
||||
long long offset, long long length);
|
||||
int mtd_del_partition(struct mtd_info *master, int partno);
|
||||
uint64_t mtd_get_device_size(const struct mtd_info *mtd);
|
||||
|
||||
#if defined(CONFIG_MTD_PARTITIONS)
|
||||
int mtd_parse_partitions(struct mtd_info *parent, const char **_mtdparts,
|
||||
struct mtd_partition **_parts, int *_nparts);
|
||||
void mtd_free_parsed_partitions(struct mtd_partition *parts,
|
||||
unsigned int nparts);
|
||||
#else
|
||||
static inline int
|
||||
mtd_parse_partitions(struct mtd_info *parent, const char **_mtdparts,
|
||||
struct mtd_partition **_parts, int *_nparts)
|
||||
{
|
||||
*_nparts = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
static inline void
|
||||
mtd_free_parsed_partitions(struct mtd_partition *parts, unsigned int nparts)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif /* defined(MTD_PARTITIONS) */
|
||||
|
||||
#endif
|
||||
|
|
432
include/linux/mtd/spinand.h
Normal file
432
include/linux/mtd/spinand.h
Normal file
|
@ -0,0 +1,432 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (c) 2016-2017 Micron Technology, Inc.
|
||||
*
|
||||
* Authors:
|
||||
* Peter Pan <peterpandong@micron.com>
|
||||
*/
|
||||
#ifndef __LINUX_MTD_SPINAND_H
|
||||
#define __LINUX_MTD_SPINAND_H
|
||||
|
||||
#ifndef __UBOOT__
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/spi-mem.h>
|
||||
#else
|
||||
#include <common.h>
|
||||
#include <spi.h>
|
||||
#include <spi-mem.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Standard SPI NAND flash operations
|
||||
*/
|
||||
|
||||
#define SPINAND_RESET_OP \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0xff, 1), \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_NO_DATA)
|
||||
|
||||
#define SPINAND_WR_EN_DIS_OP(enable) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD((enable) ? 0x06 : 0x04, 1), \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_NO_DATA)
|
||||
|
||||
#define SPINAND_READID_OP(ndummy, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0x9f, 1), \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
SPI_MEM_OP_DUMMY(ndummy, 1), \
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 1))
|
||||
|
||||
#define SPINAND_SET_FEATURE_OP(reg, valptr) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0x1f, 1), \
|
||||
SPI_MEM_OP_ADDR(1, reg, 1), \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_DATA_OUT(1, valptr, 1))
|
||||
|
||||
#define SPINAND_GET_FEATURE_OP(reg, valptr) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0x0f, 1), \
|
||||
SPI_MEM_OP_ADDR(1, reg, 1), \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_DATA_IN(1, valptr, 1))
|
||||
|
||||
#define SPINAND_BLK_ERASE_OP(addr) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0xd8, 1), \
|
||||
SPI_MEM_OP_ADDR(3, addr, 1), \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_NO_DATA)
|
||||
|
||||
#define SPINAND_PAGE_READ_OP(addr) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0x13, 1), \
|
||||
SPI_MEM_OP_ADDR(3, addr, 1), \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_NO_DATA)
|
||||
|
||||
#define SPINAND_PAGE_READ_FROM_CACHE_OP(fast, addr, ndummy, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(fast ? 0x0b : 0x03, 1), \
|
||||
SPI_MEM_OP_ADDR(2, addr, 1), \
|
||||
SPI_MEM_OP_DUMMY(ndummy, 1), \
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 1))
|
||||
|
||||
#define SPINAND_PAGE_READ_FROM_CACHE_X2_OP(addr, ndummy, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0x3b, 1), \
|
||||
SPI_MEM_OP_ADDR(2, addr, 1), \
|
||||
SPI_MEM_OP_DUMMY(ndummy, 1), \
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 2))
|
||||
|
||||
#define SPINAND_PAGE_READ_FROM_CACHE_X4_OP(addr, ndummy, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0x6b, 1), \
|
||||
SPI_MEM_OP_ADDR(2, addr, 1), \
|
||||
SPI_MEM_OP_DUMMY(ndummy, 1), \
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 4))
|
||||
|
||||
#define SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(addr, ndummy, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0xbb, 1), \
|
||||
SPI_MEM_OP_ADDR(2, addr, 2), \
|
||||
SPI_MEM_OP_DUMMY(ndummy, 2), \
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 2))
|
||||
|
||||
#define SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(addr, ndummy, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0xeb, 1), \
|
||||
SPI_MEM_OP_ADDR(2, addr, 4), \
|
||||
SPI_MEM_OP_DUMMY(ndummy, 4), \
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 4))
|
||||
|
||||
#define SPINAND_PROG_EXEC_OP(addr) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0x10, 1), \
|
||||
SPI_MEM_OP_ADDR(3, addr, 1), \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_NO_DATA)
|
||||
|
||||
#define SPINAND_PROG_LOAD(reset, addr, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(reset ? 0x02 : 0x84, 1), \
|
||||
SPI_MEM_OP_ADDR(2, addr, 1), \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_DATA_OUT(len, buf, 1))
|
||||
|
||||
#define SPINAND_PROG_LOAD_X4(reset, addr, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(reset ? 0x32 : 0x34, 1), \
|
||||
SPI_MEM_OP_ADDR(2, addr, 1), \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_DATA_OUT(len, buf, 4))
|
||||
|
||||
/**
|
||||
* Standard SPI NAND flash commands
|
||||
*/
|
||||
#define SPINAND_CMD_PROG_LOAD_X4 0x32
|
||||
#define SPINAND_CMD_PROG_LOAD_RDM_DATA_X4 0x34
|
||||
|
||||
/* feature register */
|
||||
#define REG_BLOCK_LOCK 0xa0
|
||||
#define BL_ALL_UNLOCKED 0x00
|
||||
|
||||
/* configuration register */
|
||||
#define REG_CFG 0xb0
|
||||
#define CFG_OTP_ENABLE BIT(6)
|
||||
#define CFG_ECC_ENABLE BIT(4)
|
||||
#define CFG_QUAD_ENABLE BIT(0)
|
||||
|
||||
/* status register */
|
||||
#define REG_STATUS 0xc0
|
||||
#define STATUS_BUSY BIT(0)
|
||||
#define STATUS_ERASE_FAILED BIT(2)
|
||||
#define STATUS_PROG_FAILED BIT(3)
|
||||
#define STATUS_ECC_MASK GENMASK(5, 4)
|
||||
#define STATUS_ECC_NO_BITFLIPS (0 << 4)
|
||||
#define STATUS_ECC_HAS_BITFLIPS (1 << 4)
|
||||
#define STATUS_ECC_UNCOR_ERROR (2 << 4)
|
||||
|
||||
struct spinand_op;
|
||||
struct spinand_device;
|
||||
|
||||
#define SPINAND_MAX_ID_LEN 4
|
||||
|
||||
/**
|
||||
* struct spinand_id - SPI NAND id structure
|
||||
* @data: buffer containing the id bytes. Currently 4 bytes large, but can
|
||||
* be extended if required
|
||||
* @len: ID length
|
||||
*
|
||||
* struct_spinand_id->data contains all bytes returned after a READ_ID command,
|
||||
* including dummy bytes if the chip does not emit ID bytes right after the
|
||||
* READ_ID command. The responsibility to extract real ID bytes is left to
|
||||
* struct_manufacurer_ops->detect().
|
||||
*/
|
||||
struct spinand_id {
|
||||
u8 data[SPINAND_MAX_ID_LEN];
|
||||
int len;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct manufacurer_ops - SPI NAND manufacturer specific operations
|
||||
* @detect: detect a SPI NAND device. Every time a SPI NAND device is probed
|
||||
* the core calls the struct_manufacurer_ops->detect() hook of each
|
||||
* registered manufacturer until one of them return 1. Note that
|
||||
* the first thing to check in this hook is that the manufacturer ID
|
||||
* in struct_spinand_device->id matches the manufacturer whose
|
||||
* ->detect() hook has been called. Should return 1 if there's a
|
||||
* match, 0 if the manufacturer ID does not match and a negative
|
||||
* error code otherwise. When true is returned, the core assumes
|
||||
* that properties of the NAND chip (spinand->base.memorg and
|
||||
* spinand->base.eccreq) have been filled
|
||||
* @init: initialize a SPI NAND device
|
||||
* @cleanup: cleanup a SPI NAND device
|
||||
*
|
||||
* Each SPI NAND manufacturer driver should implement this interface so that
|
||||
* NAND chips coming from this vendor can be detected and initialized properly.
|
||||
*/
|
||||
struct spinand_manufacturer_ops {
|
||||
int (*detect)(struct spinand_device *spinand);
|
||||
int (*init)(struct spinand_device *spinand);
|
||||
void (*cleanup)(struct spinand_device *spinand);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct spinand_manufacturer - SPI NAND manufacturer instance
|
||||
* @id: manufacturer ID
|
||||
* @name: manufacturer name
|
||||
* @ops: manufacturer operations
|
||||
*/
|
||||
struct spinand_manufacturer {
|
||||
u8 id;
|
||||
char *name;
|
||||
const struct spinand_manufacturer_ops *ops;
|
||||
};
|
||||
|
||||
/* SPI NAND manufacturers */
|
||||
extern const struct spinand_manufacturer macronix_spinand_manufacturer;
|
||||
extern const struct spinand_manufacturer micron_spinand_manufacturer;
|
||||
extern const struct spinand_manufacturer winbond_spinand_manufacturer;
|
||||
|
||||
/**
|
||||
* struct spinand_op_variants - SPI NAND operation variants
|
||||
* @ops: the list of variants for a given operation
|
||||
* @nops: the number of variants
|
||||
*
|
||||
* Some operations like read-from-cache/write-to-cache have several variants
|
||||
* depending on the number of IO lines you use to transfer data or address
|
||||
* cycles. This structure is a way to describe the different variants supported
|
||||
* by a chip and let the core pick the best one based on the SPI mem controller
|
||||
* capabilities.
|
||||
*/
|
||||
struct spinand_op_variants {
|
||||
const struct spi_mem_op *ops;
|
||||
unsigned int nops;
|
||||
};
|
||||
|
||||
#define SPINAND_OP_VARIANTS(name, ...) \
|
||||
const struct spinand_op_variants name = { \
|
||||
.ops = (struct spi_mem_op[]) { __VA_ARGS__ }, \
|
||||
.nops = sizeof((struct spi_mem_op[]){ __VA_ARGS__ }) / \
|
||||
sizeof(struct spi_mem_op), \
|
||||
}
|
||||
|
||||
/**
|
||||
* spinand_ecc_info - description of the on-die ECC implemented by a SPI NAND
|
||||
* chip
|
||||
* @get_status: get the ECC status. Should return a positive number encoding
|
||||
* the number of corrected bitflips if correction was possible or
|
||||
* -EBADMSG if there are uncorrectable errors. I can also return
|
||||
* other negative error codes if the error is not caused by
|
||||
* uncorrectable bitflips
|
||||
* @ooblayout: the OOB layout used by the on-die ECC implementation
|
||||
*/
|
||||
struct spinand_ecc_info {
|
||||
int (*get_status)(struct spinand_device *spinand, u8 status);
|
||||
const struct mtd_ooblayout_ops *ooblayout;
|
||||
};
|
||||
|
||||
#define SPINAND_HAS_QE_BIT BIT(0)
|
||||
|
||||
/**
|
||||
* struct spinand_info - Structure used to describe SPI NAND chips
|
||||
* @model: model name
|
||||
* @devid: device ID
|
||||
* @flags: OR-ing of the SPINAND_XXX flags
|
||||
* @memorg: memory organization
|
||||
* @eccreq: ECC requirements
|
||||
* @eccinfo: on-die ECC info
|
||||
* @op_variants: operations variants
|
||||
* @op_variants.read_cache: variants of the read-cache operation
|
||||
* @op_variants.write_cache: variants of the write-cache operation
|
||||
* @op_variants.update_cache: variants of the update-cache operation
|
||||
* @select_target: function used to select a target/die. Required only for
|
||||
* multi-die chips
|
||||
*
|
||||
* Each SPI NAND manufacturer driver should have a spinand_info table
|
||||
* describing all the chips supported by the driver.
|
||||
*/
|
||||
struct spinand_info {
|
||||
const char *model;
|
||||
u8 devid;
|
||||
u32 flags;
|
||||
struct nand_memory_organization memorg;
|
||||
struct nand_ecc_req eccreq;
|
||||
struct spinand_ecc_info eccinfo;
|
||||
struct {
|
||||
const struct spinand_op_variants *read_cache;
|
||||
const struct spinand_op_variants *write_cache;
|
||||
const struct spinand_op_variants *update_cache;
|
||||
} op_variants;
|
||||
int (*select_target)(struct spinand_device *spinand,
|
||||
unsigned int target);
|
||||
};
|
||||
|
||||
#define SPINAND_INFO_OP_VARIANTS(__read, __write, __update) \
|
||||
{ \
|
||||
.read_cache = __read, \
|
||||
.write_cache = __write, \
|
||||
.update_cache = __update, \
|
||||
}
|
||||
|
||||
#define SPINAND_ECCINFO(__ooblayout, __get_status) \
|
||||
.eccinfo = { \
|
||||
.ooblayout = __ooblayout, \
|
||||
.get_status = __get_status, \
|
||||
}
|
||||
|
||||
#define SPINAND_SELECT_TARGET(__func) \
|
||||
.select_target = __func,
|
||||
|
||||
#define SPINAND_INFO(__model, __id, __memorg, __eccreq, __op_variants, \
|
||||
__flags, ...) \
|
||||
{ \
|
||||
.model = __model, \
|
||||
.devid = __id, \
|
||||
.memorg = __memorg, \
|
||||
.eccreq = __eccreq, \
|
||||
.op_variants = __op_variants, \
|
||||
.flags = __flags, \
|
||||
__VA_ARGS__ \
|
||||
}
|
||||
|
||||
/**
|
||||
* struct spinand_device - SPI NAND device instance
|
||||
* @base: NAND device instance
|
||||
* @slave: pointer to the SPI slave object
|
||||
* @lock: lock used to serialize accesses to the NAND
|
||||
* @id: NAND ID as returned by READ_ID
|
||||
* @flags: NAND flags
|
||||
* @op_templates: various SPI mem op templates
|
||||
* @op_templates.read_cache: read cache op template
|
||||
* @op_templates.write_cache: write cache op template
|
||||
* @op_templates.update_cache: update cache op template
|
||||
* @select_target: select a specific target/die. Usually called before sending
|
||||
* a command addressing a page or an eraseblock embedded in
|
||||
* this die. Only required if your chip exposes several dies
|
||||
* @cur_target: currently selected target/die
|
||||
* @eccinfo: on-die ECC information
|
||||
* @cfg_cache: config register cache. One entry per die
|
||||
* @databuf: bounce buffer for data
|
||||
* @oobbuf: bounce buffer for OOB data
|
||||
* @scratchbuf: buffer used for everything but page accesses. This is needed
|
||||
* because the spi-mem interface explicitly requests that buffers
|
||||
* passed in spi_mem_op be DMA-able, so we can't based the bufs on
|
||||
* the stack
|
||||
* @manufacturer: SPI NAND manufacturer information
|
||||
* @priv: manufacturer private data
|
||||
*/
|
||||
struct spinand_device {
|
||||
struct nand_device base;
|
||||
#ifndef __UBOOT__
|
||||
struct spi_mem *spimem;
|
||||
struct mutex lock;
|
||||
#else
|
||||
struct spi_slave *slave;
|
||||
#endif
|
||||
struct spinand_id id;
|
||||
u32 flags;
|
||||
|
||||
struct {
|
||||
const struct spi_mem_op *read_cache;
|
||||
const struct spi_mem_op *write_cache;
|
||||
const struct spi_mem_op *update_cache;
|
||||
} op_templates;
|
||||
|
||||
int (*select_target)(struct spinand_device *spinand,
|
||||
unsigned int target);
|
||||
unsigned int cur_target;
|
||||
|
||||
struct spinand_ecc_info eccinfo;
|
||||
|
||||
u8 *cfg_cache;
|
||||
u8 *databuf;
|
||||
u8 *oobbuf;
|
||||
u8 *scratchbuf;
|
||||
const struct spinand_manufacturer *manufacturer;
|
||||
void *priv;
|
||||
};
|
||||
|
||||
/**
|
||||
* mtd_to_spinand() - Get the SPI NAND device attached to an MTD instance
|
||||
* @mtd: MTD instance
|
||||
*
|
||||
* Return: the SPI NAND device attached to @mtd.
|
||||
*/
|
||||
static inline struct spinand_device *mtd_to_spinand(struct mtd_info *mtd)
|
||||
{
|
||||
return container_of(mtd_to_nanddev(mtd), struct spinand_device, base);
|
||||
}
|
||||
|
||||
/**
|
||||
* spinand_to_mtd() - Get the MTD device embedded in a SPI NAND device
|
||||
* @spinand: SPI NAND device
|
||||
*
|
||||
* Return: the MTD device embedded in @spinand.
|
||||
*/
|
||||
static inline struct mtd_info *spinand_to_mtd(struct spinand_device *spinand)
|
||||
{
|
||||
return nanddev_to_mtd(&spinand->base);
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_to_spinand() - Get the SPI NAND device embedding an NAND object
|
||||
* @nand: NAND object
|
||||
*
|
||||
* Return: the SPI NAND device embedding @nand.
|
||||
*/
|
||||
static inline struct spinand_device *nand_to_spinand(struct nand_device *nand)
|
||||
{
|
||||
return container_of(nand, struct spinand_device, base);
|
||||
}
|
||||
|
||||
/**
|
||||
* spinand_to_nand() - Get the NAND device embedded in a SPI NAND object
|
||||
* @spinand: SPI NAND device
|
||||
*
|
||||
* Return: the NAND device embedded in @spinand.
|
||||
*/
|
||||
static inline struct nand_device *
|
||||
spinand_to_nand(struct spinand_device *spinand)
|
||||
{
|
||||
return &spinand->base;
|
||||
}
|
||||
|
||||
/**
|
||||
* spinand_set_of_node - Attach a DT node to a SPI NAND device
|
||||
* @spinand: SPI NAND device
|
||||
* @np: DT node
|
||||
*
|
||||
* Attach a DT node to a SPI NAND device.
|
||||
*/
|
||||
static inline void spinand_set_of_node(struct spinand_device *spinand,
|
||||
const struct device_node *np)
|
||||
{
|
||||
nanddev_set_of_node(&spinand->base, np);
|
||||
}
|
||||
|
||||
int spinand_match_and_init(struct spinand_device *dev,
|
||||
const struct spinand_info *table,
|
||||
unsigned int table_size, u8 devid);
|
||||
|
||||
int spinand_upd_cfg(struct spinand_device *spinand, u8 mask, u8 val);
|
||||
int spinand_select_target(struct spinand_device *spinand, unsigned int target);
|
||||
|
||||
#endif /* __LINUX_MTD_SPINAND_H */
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue