162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2017 exceet electronics GmbH 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Authors: 662306a36Sopenharmony_ci * Frieder Schrempf <frieder.schrempf@exceet.de> 762306a36Sopenharmony_ci * Boris Brezillon <boris.brezillon@bootlin.com> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/device.h> 1162306a36Sopenharmony_ci#include <linux/kernel.h> 1262306a36Sopenharmony_ci#include <linux/mtd/spinand.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#define SPINAND_MFR_WINBOND 0xEF 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#define WINBOND_CFG_BUF_READ BIT(3) 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_cistatic SPINAND_OP_VARIANTS(read_cache_variants, 1962306a36Sopenharmony_ci SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0), 2062306a36Sopenharmony_ci SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), 2162306a36Sopenharmony_ci SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), 2262306a36Sopenharmony_ci SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), 2362306a36Sopenharmony_ci SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), 2462306a36Sopenharmony_ci SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_cistatic SPINAND_OP_VARIANTS(write_cache_variants, 2762306a36Sopenharmony_ci SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), 2862306a36Sopenharmony_ci SPINAND_PROG_LOAD(true, 0, NULL, 0)); 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistatic SPINAND_OP_VARIANTS(update_cache_variants, 3162306a36Sopenharmony_ci SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), 3262306a36Sopenharmony_ci SPINAND_PROG_LOAD(false, 0, NULL, 0)); 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistatic int w25m02gv_ooblayout_ecc(struct mtd_info *mtd, int section, 3562306a36Sopenharmony_ci struct mtd_oob_region *region) 3662306a36Sopenharmony_ci{ 3762306a36Sopenharmony_ci if (section > 3) 3862306a36Sopenharmony_ci return -ERANGE; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci region->offset = (16 * section) + 8; 4162306a36Sopenharmony_ci region->length = 8; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci return 0; 4462306a36Sopenharmony_ci} 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic int w25m02gv_ooblayout_free(struct mtd_info *mtd, int section, 4762306a36Sopenharmony_ci struct mtd_oob_region *region) 4862306a36Sopenharmony_ci{ 4962306a36Sopenharmony_ci if (section > 3) 5062306a36Sopenharmony_ci return -ERANGE; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci region->offset = (16 * section) + 2; 5362306a36Sopenharmony_ci region->length = 6; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci return 0; 5662306a36Sopenharmony_ci} 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_cistatic const struct mtd_ooblayout_ops w25m02gv_ooblayout = { 5962306a36Sopenharmony_ci .ecc = w25m02gv_ooblayout_ecc, 6062306a36Sopenharmony_ci .free = w25m02gv_ooblayout_free, 6162306a36Sopenharmony_ci}; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_cistatic int w25m02gv_select_target(struct spinand_device *spinand, 6462306a36Sopenharmony_ci unsigned int target) 6562306a36Sopenharmony_ci{ 6662306a36Sopenharmony_ci struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0xc2, 1), 6762306a36Sopenharmony_ci SPI_MEM_OP_NO_ADDR, 6862306a36Sopenharmony_ci SPI_MEM_OP_NO_DUMMY, 6962306a36Sopenharmony_ci SPI_MEM_OP_DATA_OUT(1, 7062306a36Sopenharmony_ci spinand->scratchbuf, 7162306a36Sopenharmony_ci 1)); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci *spinand->scratchbuf = target; 7462306a36Sopenharmony_ci return spi_mem_exec_op(spinand->spimem, &op); 7562306a36Sopenharmony_ci} 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cistatic int w25n02kv_ooblayout_ecc(struct mtd_info *mtd, int section, 7862306a36Sopenharmony_ci struct mtd_oob_region *region) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci if (section > 3) 8162306a36Sopenharmony_ci return -ERANGE; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci region->offset = 64 + (16 * section); 8462306a36Sopenharmony_ci region->length = 13; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci return 0; 8762306a36Sopenharmony_ci} 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic int w25n02kv_ooblayout_free(struct mtd_info *mtd, int section, 9062306a36Sopenharmony_ci struct mtd_oob_region *region) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci if (section > 3) 9362306a36Sopenharmony_ci return -ERANGE; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci region->offset = (16 * section) + 2; 9662306a36Sopenharmony_ci region->length = 14; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci return 0; 9962306a36Sopenharmony_ci} 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_cistatic const struct mtd_ooblayout_ops w25n02kv_ooblayout = { 10262306a36Sopenharmony_ci .ecc = w25n02kv_ooblayout_ecc, 10362306a36Sopenharmony_ci .free = w25n02kv_ooblayout_free, 10462306a36Sopenharmony_ci}; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistatic int w25n02kv_ecc_get_status(struct spinand_device *spinand, 10762306a36Sopenharmony_ci u8 status) 10862306a36Sopenharmony_ci{ 10962306a36Sopenharmony_ci struct nand_device *nand = spinand_to_nand(spinand); 11062306a36Sopenharmony_ci u8 mbf = 0; 11162306a36Sopenharmony_ci struct spi_mem_op op = SPINAND_GET_FEATURE_OP(0x30, spinand->scratchbuf); 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci switch (status & STATUS_ECC_MASK) { 11462306a36Sopenharmony_ci case STATUS_ECC_NO_BITFLIPS: 11562306a36Sopenharmony_ci return 0; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci case STATUS_ECC_UNCOR_ERROR: 11862306a36Sopenharmony_ci return -EBADMSG; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci case STATUS_ECC_HAS_BITFLIPS: 12162306a36Sopenharmony_ci /* 12262306a36Sopenharmony_ci * Let's try to retrieve the real maximum number of bitflips 12362306a36Sopenharmony_ci * in order to avoid forcing the wear-leveling layer to move 12462306a36Sopenharmony_ci * data around if it's not necessary. 12562306a36Sopenharmony_ci */ 12662306a36Sopenharmony_ci if (spi_mem_exec_op(spinand->spimem, &op)) 12762306a36Sopenharmony_ci return nanddev_get_ecc_conf(nand)->strength; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci mbf = *(spinand->scratchbuf) >> 4; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci if (WARN_ON(mbf > nanddev_get_ecc_conf(nand)->strength || !mbf)) 13262306a36Sopenharmony_ci return nanddev_get_ecc_conf(nand)->strength; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci return mbf; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci default: 13762306a36Sopenharmony_ci break; 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci return -EINVAL; 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic const struct spinand_info winbond_spinand_table[] = { 14462306a36Sopenharmony_ci SPINAND_INFO("W25M02GV", 14562306a36Sopenharmony_ci SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xab, 0x21), 14662306a36Sopenharmony_ci NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 2), 14762306a36Sopenharmony_ci NAND_ECCREQ(1, 512), 14862306a36Sopenharmony_ci SPINAND_INFO_OP_VARIANTS(&read_cache_variants, 14962306a36Sopenharmony_ci &write_cache_variants, 15062306a36Sopenharmony_ci &update_cache_variants), 15162306a36Sopenharmony_ci 0, 15262306a36Sopenharmony_ci SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL), 15362306a36Sopenharmony_ci SPINAND_SELECT_TARGET(w25m02gv_select_target)), 15462306a36Sopenharmony_ci SPINAND_INFO("W25N01GV", 15562306a36Sopenharmony_ci SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x21), 15662306a36Sopenharmony_ci NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), 15762306a36Sopenharmony_ci NAND_ECCREQ(1, 512), 15862306a36Sopenharmony_ci SPINAND_INFO_OP_VARIANTS(&read_cache_variants, 15962306a36Sopenharmony_ci &write_cache_variants, 16062306a36Sopenharmony_ci &update_cache_variants), 16162306a36Sopenharmony_ci 0, 16262306a36Sopenharmony_ci SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL)), 16362306a36Sopenharmony_ci SPINAND_INFO("W25N02KV", 16462306a36Sopenharmony_ci SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x22), 16562306a36Sopenharmony_ci NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), 16662306a36Sopenharmony_ci NAND_ECCREQ(8, 512), 16762306a36Sopenharmony_ci SPINAND_INFO_OP_VARIANTS(&read_cache_variants, 16862306a36Sopenharmony_ci &write_cache_variants, 16962306a36Sopenharmony_ci &update_cache_variants), 17062306a36Sopenharmony_ci 0, 17162306a36Sopenharmony_ci SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)), 17262306a36Sopenharmony_ci}; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_cistatic int winbond_spinand_init(struct spinand_device *spinand) 17562306a36Sopenharmony_ci{ 17662306a36Sopenharmony_ci struct nand_device *nand = spinand_to_nand(spinand); 17762306a36Sopenharmony_ci unsigned int i; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci /* 18062306a36Sopenharmony_ci * Make sure all dies are in buffer read mode and not continuous read 18162306a36Sopenharmony_ci * mode. 18262306a36Sopenharmony_ci */ 18362306a36Sopenharmony_ci for (i = 0; i < nand->memorg.ntargets; i++) { 18462306a36Sopenharmony_ci spinand_select_target(spinand, i); 18562306a36Sopenharmony_ci spinand_upd_cfg(spinand, WINBOND_CFG_BUF_READ, 18662306a36Sopenharmony_ci WINBOND_CFG_BUF_READ); 18762306a36Sopenharmony_ci } 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci return 0; 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_cistatic const struct spinand_manufacturer_ops winbond_spinand_manuf_ops = { 19362306a36Sopenharmony_ci .init = winbond_spinand_init, 19462306a36Sopenharmony_ci}; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ciconst struct spinand_manufacturer winbond_spinand_manufacturer = { 19762306a36Sopenharmony_ci .id = SPINAND_MFR_WINBOND, 19862306a36Sopenharmony_ci .name = "Winbond", 19962306a36Sopenharmony_ci .chips = winbond_spinand_table, 20062306a36Sopenharmony_ci .nchips = ARRAY_SIZE(winbond_spinand_table), 20162306a36Sopenharmony_ci .ops = &winbond_spinand_manuf_ops, 20262306a36Sopenharmony_ci}; 203