mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-16 23:45:31 +08:00
377e517b5f
NAND datasheets usually give the maximum number of bad blocks per LUN and this number can be used to help upper layers decide how much blocks they should reserve for bad block handling. Add a max_bad_eraseblocks_per_lun to the nand_memory_organization struct and update the NAND_MEMORG() macro (and its users) accordingly. We also provide a default mtd->_max_bad_blocks() implementation. Signed-off-by: Boris Brezillon <bbrezillon@kernel.org> Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com> Reviewed-by: Frieder Schrempf <frieder.schrempf@kontron.de>
150 lines
3.8 KiB
C
150 lines
3.8 KiB
C
// 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>
|
|
*/
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/kernel.h>
|
|
#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->spimem, &op);
|
|
}
|
|
|
|
static const struct spinand_info winbond_spinand_table[] = {
|
|
SPINAND_INFO("W25M02GV", 0xAB,
|
|
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 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)),
|
|
SPINAND_INFO("W25N01GV", 0xAA,
|
|
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
|
|
NAND_ECCREQ(1, 512),
|
|
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
|
&write_cache_variants,
|
|
&update_cache_variants),
|
|
0,
|
|
SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL)),
|
|
};
|
|
|
|
/**
|
|
* 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,
|
|
};
|