mirror of
https://github.com/raspberrypi/linux.git
synced 2025-12-09 11:30:02 +00:00
W25N01JW and W25N02JW support many DTR read modes in single, dual and quad configurations. DTR modes however cannot be used at 166MHz, as the bus frequency in this case must be lowered to 80MHz. Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
318 lines
9.8 KiB
C
318 lines
9.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>
|
|
#include <linux/units.h>
|
|
|
|
#define SPINAND_MFR_WINBOND 0xEF
|
|
|
|
#define WINBOND_CFG_BUF_READ BIT(3)
|
|
|
|
#define W25N04KV_STATUS_ECC_5_8_BITFLIPS (3 << 4)
|
|
|
|
/*
|
|
* "X2" in the core is equivalent to "dual output" in the datasheets,
|
|
* "X4" in the core is equivalent to "quad output" in the datasheets.
|
|
*/
|
|
|
|
static SPINAND_OP_VARIANTS(read_cache_dtr_variants,
|
|
SPINAND_PAGE_READ_FROM_CACHE_QUADIO_DTR_OP(0, 8, NULL, 0, 80 * HZ_PER_MHZ),
|
|
SPINAND_PAGE_READ_FROM_CACHE_X4_DTR_OP(0, 2, NULL, 0, 80 * HZ_PER_MHZ),
|
|
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_DTR_OP(0, 4, NULL, 0, 80 * HZ_PER_MHZ),
|
|
SPINAND_PAGE_READ_FROM_CACHE_X2_DTR_OP(0, 2, NULL, 0, 80 * HZ_PER_MHZ),
|
|
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_DTR_OP(0, 2, NULL, 0, 80 * HZ_PER_MHZ),
|
|
SPINAND_PAGE_READ_FROM_CACHE_FAST_OP(0, 1, NULL, 0),
|
|
SPINAND_PAGE_READ_FROM_CACHE_OP(0, 1, NULL, 0, 54 * HZ_PER_MHZ));
|
|
|
|
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_FAST_OP(0, 1, NULL, 0),
|
|
SPINAND_PAGE_READ_FROM_CACHE_OP(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 int w25n01kv_ooblayout_ecc(struct mtd_info *mtd, int section,
|
|
struct mtd_oob_region *region)
|
|
{
|
|
if (section > 3)
|
|
return -ERANGE;
|
|
|
|
region->offset = 64 + (8 * section);
|
|
region->length = 7;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int w25n02kv_ooblayout_ecc(struct mtd_info *mtd, int section,
|
|
struct mtd_oob_region *region)
|
|
{
|
|
if (section > 3)
|
|
return -ERANGE;
|
|
|
|
region->offset = 64 + (16 * section);
|
|
region->length = 13;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int w25n02kv_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 = 14;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct mtd_ooblayout_ops w25n01kv_ooblayout = {
|
|
.ecc = w25n01kv_ooblayout_ecc,
|
|
.free = w25n02kv_ooblayout_free,
|
|
};
|
|
|
|
static const struct mtd_ooblayout_ops w25n02kv_ooblayout = {
|
|
.ecc = w25n02kv_ooblayout_ecc,
|
|
.free = w25n02kv_ooblayout_free,
|
|
};
|
|
|
|
static int w25n02kv_ecc_get_status(struct spinand_device *spinand,
|
|
u8 status)
|
|
{
|
|
struct nand_device *nand = spinand_to_nand(spinand);
|
|
u8 mbf = 0;
|
|
struct spi_mem_op op = SPINAND_GET_FEATURE_OP(0x30, spinand->scratchbuf);
|
|
|
|
switch (status & STATUS_ECC_MASK) {
|
|
case STATUS_ECC_NO_BITFLIPS:
|
|
return 0;
|
|
|
|
case STATUS_ECC_UNCOR_ERROR:
|
|
return -EBADMSG;
|
|
|
|
case STATUS_ECC_HAS_BITFLIPS:
|
|
case W25N04KV_STATUS_ECC_5_8_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 (spi_mem_exec_op(spinand->spimem, &op))
|
|
return nanddev_get_ecc_conf(nand)->strength;
|
|
|
|
mbf = *(spinand->scratchbuf) >> 4;
|
|
|
|
if (WARN_ON(mbf > nanddev_get_ecc_conf(nand)->strength || !mbf))
|
|
return nanddev_get_ecc_conf(nand)->strength;
|
|
|
|
return mbf;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static const struct spinand_info winbond_spinand_table[] = {
|
|
/* 512M-bit densities */
|
|
SPINAND_INFO("W25N512GW", /* 1.8V */
|
|
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xba, 0x20),
|
|
NAND_MEMORG(1, 2048, 64, 64, 512, 10, 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)),
|
|
/* 1G-bit densities */
|
|
SPINAND_INFO("W25N01GV", /* 3.3V */
|
|
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x21),
|
|
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)),
|
|
SPINAND_INFO("W25N01GW", /* 1.8V */
|
|
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xba, 0x21),
|
|
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)),
|
|
SPINAND_INFO("W25N01JW", /* high-speed 1.8V */
|
|
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xbc, 0x21),
|
|
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
|
|
NAND_ECCREQ(1, 512),
|
|
SPINAND_INFO_OP_VARIANTS(&read_cache_dtr_variants,
|
|
&write_cache_variants,
|
|
&update_cache_variants),
|
|
0,
|
|
SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL)),
|
|
SPINAND_INFO("W25N01KV", /* 3.3V */
|
|
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xae, 0x21),
|
|
NAND_MEMORG(1, 2048, 96, 64, 1024, 20, 1, 1, 1),
|
|
NAND_ECCREQ(4, 512),
|
|
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
|
&write_cache_variants,
|
|
&update_cache_variants),
|
|
0,
|
|
SPINAND_ECCINFO(&w25n01kv_ooblayout, w25n02kv_ecc_get_status)),
|
|
/* 2G-bit densities */
|
|
SPINAND_INFO("W25M02GV", /* 2x1G-bit 3.3V */
|
|
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xab, 0x21),
|
|
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("W25N02JW", /* high-speed 1.8V */
|
|
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xbf, 0x22),
|
|
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 2, 1),
|
|
NAND_ECCREQ(1, 512),
|
|
SPINAND_INFO_OP_VARIANTS(&read_cache_dtr_variants,
|
|
&write_cache_variants,
|
|
&update_cache_variants),
|
|
0,
|
|
SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL)),
|
|
SPINAND_INFO("W25N02KV", /* 3.3V */
|
|
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x22),
|
|
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
|
|
NAND_ECCREQ(8, 512),
|
|
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
|
&write_cache_variants,
|
|
&update_cache_variants),
|
|
0,
|
|
SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
|
|
SPINAND_INFO("W25N02KW", /* 1.8V */
|
|
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xba, 0x22),
|
|
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
|
|
NAND_ECCREQ(8, 512),
|
|
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
|
&write_cache_variants,
|
|
&update_cache_variants),
|
|
0,
|
|
SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
|
|
/* 4G-bit densities */
|
|
SPINAND_INFO("W25N04KV", /* 3.3V */
|
|
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x23),
|
|
NAND_MEMORG(1, 2048, 128, 64, 4096, 40, 2, 1, 1),
|
|
NAND_ECCREQ(8, 512),
|
|
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
|
&write_cache_variants,
|
|
&update_cache_variants),
|
|
0,
|
|
SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
|
|
SPINAND_INFO("W25N04KW", /* 1.8V */
|
|
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xba, 0x23),
|
|
NAND_MEMORG(1, 2048, 128, 64, 4096, 40, 1, 1, 1),
|
|
NAND_ECCREQ(8, 512),
|
|
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
|
&write_cache_variants,
|
|
&update_cache_variants),
|
|
0,
|
|
SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
|
|
};
|
|
|
|
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 = {
|
|
.init = winbond_spinand_init,
|
|
};
|
|
|
|
const struct spinand_manufacturer winbond_spinand_manufacturer = {
|
|
.id = SPINAND_MFR_WINBOND,
|
|
.name = "Winbond",
|
|
.chips = winbond_spinand_table,
|
|
.nchips = ARRAY_SIZE(winbond_spinand_table),
|
|
.ops = &winbond_spinand_manuf_ops,
|
|
};
|