1b98ac5e2SFrieder Schrempf // SPDX-License-Identifier: GPL-2.0 2b98ac5e2SFrieder Schrempf /* 3b98ac5e2SFrieder Schrempf * Copyright (c) 2017 exceet electronics GmbH 4b98ac5e2SFrieder Schrempf * 5b98ac5e2SFrieder Schrempf * Authors: 6b98ac5e2SFrieder Schrempf * Frieder Schrempf <frieder.schrempf@exceet.de> 7b98ac5e2SFrieder Schrempf * Boris Brezillon <boris.brezillon@bootlin.com> 8b98ac5e2SFrieder Schrempf */ 9b98ac5e2SFrieder Schrempf 10b98ac5e2SFrieder Schrempf #ifndef __UBOOT__ 11b98ac5e2SFrieder Schrempf #include <linux/device.h> 12b98ac5e2SFrieder Schrempf #include <linux/kernel.h> 13b98ac5e2SFrieder Schrempf #endif 14b98ac5e2SFrieder Schrempf #include <linux/mtd/spinand.h> 15b98ac5e2SFrieder Schrempf 16b98ac5e2SFrieder Schrempf #define SPINAND_MFR_WINBOND 0xEF 17b98ac5e2SFrieder Schrempf 18b98ac5e2SFrieder Schrempf #define WINBOND_CFG_BUF_READ BIT(3) 19b98ac5e2SFrieder Schrempf 20b98ac5e2SFrieder Schrempf static SPINAND_OP_VARIANTS(read_cache_variants, 21b98ac5e2SFrieder Schrempf SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0), 22b98ac5e2SFrieder Schrempf SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), 23b98ac5e2SFrieder Schrempf SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), 24b98ac5e2SFrieder Schrempf SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), 25b98ac5e2SFrieder Schrempf SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), 26b98ac5e2SFrieder Schrempf SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); 27b98ac5e2SFrieder Schrempf 28b98ac5e2SFrieder Schrempf static SPINAND_OP_VARIANTS(write_cache_variants, 29b98ac5e2SFrieder Schrempf SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), 30b98ac5e2SFrieder Schrempf SPINAND_PROG_LOAD(true, 0, NULL, 0)); 31b98ac5e2SFrieder Schrempf 32b98ac5e2SFrieder Schrempf static SPINAND_OP_VARIANTS(update_cache_variants, 33b98ac5e2SFrieder Schrempf SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), 34b98ac5e2SFrieder Schrempf SPINAND_PROG_LOAD(false, 0, NULL, 0)); 35b98ac5e2SFrieder Schrempf 36b98ac5e2SFrieder Schrempf static int w25m02gv_ooblayout_ecc(struct mtd_info *mtd, int section, 37b98ac5e2SFrieder Schrempf struct mtd_oob_region *region) 38b98ac5e2SFrieder Schrempf { 39b98ac5e2SFrieder Schrempf if (section > 3) 40b98ac5e2SFrieder Schrempf return -ERANGE; 41b98ac5e2SFrieder Schrempf 42b98ac5e2SFrieder Schrempf region->offset = (16 * section) + 8; 43b98ac5e2SFrieder Schrempf region->length = 8; 44b98ac5e2SFrieder Schrempf 45b98ac5e2SFrieder Schrempf return 0; 46b98ac5e2SFrieder Schrempf } 47b98ac5e2SFrieder Schrempf 48b98ac5e2SFrieder Schrempf static int w25m02gv_ooblayout_free(struct mtd_info *mtd, int section, 49b98ac5e2SFrieder Schrempf struct mtd_oob_region *region) 50b98ac5e2SFrieder Schrempf { 51b98ac5e2SFrieder Schrempf if (section > 3) 52b98ac5e2SFrieder Schrempf return -ERANGE; 53b98ac5e2SFrieder Schrempf 54b98ac5e2SFrieder Schrempf region->offset = (16 * section) + 2; 55b98ac5e2SFrieder Schrempf region->length = 6; 56b98ac5e2SFrieder Schrempf 57b98ac5e2SFrieder Schrempf return 0; 58b98ac5e2SFrieder Schrempf } 59b98ac5e2SFrieder Schrempf 60b98ac5e2SFrieder Schrempf static const struct mtd_ooblayout_ops w25m02gv_ooblayout = { 61b98ac5e2SFrieder Schrempf .ecc = w25m02gv_ooblayout_ecc, 62301f8dd1SSimon Glass .rfree = w25m02gv_ooblayout_free, 63b98ac5e2SFrieder Schrempf }; 64b98ac5e2SFrieder Schrempf 65b98ac5e2SFrieder Schrempf static int w25m02gv_select_target(struct spinand_device *spinand, 66b98ac5e2SFrieder Schrempf unsigned int target) 67b98ac5e2SFrieder Schrempf { 68b98ac5e2SFrieder Schrempf struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0xc2, 1), 69b98ac5e2SFrieder Schrempf SPI_MEM_OP_NO_ADDR, 70b98ac5e2SFrieder Schrempf SPI_MEM_OP_NO_DUMMY, 71b98ac5e2SFrieder Schrempf SPI_MEM_OP_DATA_OUT(1, 72b98ac5e2SFrieder Schrempf spinand->scratchbuf, 73b98ac5e2SFrieder Schrempf 1)); 74b98ac5e2SFrieder Schrempf 75b98ac5e2SFrieder Schrempf *spinand->scratchbuf = target; 76b98ac5e2SFrieder Schrempf return spi_mem_exec_op(spinand->slave, &op); 77b98ac5e2SFrieder Schrempf } 78b98ac5e2SFrieder Schrempf 7922edf958SJon Lin static int w25n02kv_ooblayout_ecc(struct mtd_info *mtd, int section, 8022edf958SJon Lin struct mtd_oob_region *region) 8122edf958SJon Lin { 8222edf958SJon Lin if (section) 8322edf958SJon Lin return -ERANGE; 8422edf958SJon Lin 8522edf958SJon Lin region->offset = 64; 8622edf958SJon Lin region->length = 64; 8722edf958SJon Lin 8822edf958SJon Lin return 0; 8922edf958SJon Lin } 9022edf958SJon Lin 9122edf958SJon Lin static int w25n02kv_ooblayout_free(struct mtd_info *mtd, int section, 9222edf958SJon Lin struct mtd_oob_region *region) 9322edf958SJon Lin { 9422edf958SJon Lin if (section) 9522edf958SJon Lin return -ERANGE; 9622edf958SJon Lin 9722edf958SJon Lin /* Reserve 2 bytes for the BBM. */ 9822edf958SJon Lin region->offset = 2; 9922edf958SJon Lin region->length = 62; 10022edf958SJon Lin 10122edf958SJon Lin return 0; 10222edf958SJon Lin } 10322edf958SJon Lin 10422edf958SJon Lin static const struct mtd_ooblayout_ops w25n02kv_ooblayout = { 10522edf958SJon Lin .ecc = w25n02kv_ooblayout_ecc, 10622edf958SJon Lin .rfree = w25n02kv_ooblayout_free, 10722edf958SJon Lin }; 10822edf958SJon Lin 10922edf958SJon Lin static int w25n02kv_ecc_get_status(struct spinand_device *spinand, 11022edf958SJon Lin u8 status) 11122edf958SJon Lin { 11222edf958SJon Lin struct nand_device *nand = spinand_to_nand(spinand); 11322edf958SJon Lin 11422edf958SJon Lin switch (status & STATUS_ECC_MASK) { 11522edf958SJon Lin case STATUS_ECC_NO_BITFLIPS: 11622edf958SJon Lin return 0; 11722edf958SJon Lin 11822edf958SJon Lin case STATUS_ECC_UNCOR_ERROR: 11922edf958SJon Lin return -EBADMSG; 12022edf958SJon Lin 12122edf958SJon Lin case STATUS_ECC_HAS_BITFLIPS: 1229dd597e7SJon Lin return 1; 12322edf958SJon Lin 12422edf958SJon Lin default: 1259dd597e7SJon Lin return nand->eccreq.strength; 12622edf958SJon Lin } 12722edf958SJon Lin 12822edf958SJon Lin return -EINVAL; 12922edf958SJon Lin } 13022edf958SJon Lin 13181afcfe1SJon Lin /* Another set for the same id[2] devices in one series */ 132b98ac5e2SFrieder Schrempf static const struct spinand_info winbond_spinand_table[] = { 13381afcfe1SJon Lin SPINAND_INFO("W25M02GV", 13481afcfe1SJon Lin SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xAB), 135b98ac5e2SFrieder Schrempf NAND_MEMORG(1, 2048, 64, 64, 1024, 1, 1, 2), 136b98ac5e2SFrieder Schrempf NAND_ECCREQ(1, 512), 137b98ac5e2SFrieder Schrempf SPINAND_INFO_OP_VARIANTS(&read_cache_variants, 138b98ac5e2SFrieder Schrempf &write_cache_variants, 139b98ac5e2SFrieder Schrempf &update_cache_variants), 140b98ac5e2SFrieder Schrempf 0, 141b98ac5e2SFrieder Schrempf SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL), 142b98ac5e2SFrieder Schrempf SPINAND_SELECT_TARGET(w25m02gv_select_target)), 14381afcfe1SJon Lin SPINAND_INFO("W25N512GV", 14481afcfe1SJon Lin SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xAA, 0x20), 145527cb271SJon Lin NAND_MEMORG(1, 2048, 64, 64, 512, 1, 1, 1), 146527cb271SJon Lin NAND_ECCREQ(1, 512), 147527cb271SJon Lin SPINAND_INFO_OP_VARIANTS(&read_cache_variants, 148527cb271SJon Lin &write_cache_variants, 149527cb271SJon Lin &update_cache_variants), 150527cb271SJon Lin 0, 151527cb271SJon Lin SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL), 152527cb271SJon Lin SPINAND_SELECT_TARGET(w25m02gv_select_target)), 15381afcfe1SJon Lin SPINAND_INFO("W25N01GV", 15481afcfe1SJon Lin SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xAA, 0x21), 1552ec05489SJon Lin NAND_MEMORG(1, 2048, 64, 64, 1024, 1, 1, 1), 1562ec05489SJon Lin NAND_ECCREQ(1, 512), 1572ec05489SJon Lin SPINAND_INFO_OP_VARIANTS(&read_cache_variants, 1582ec05489SJon Lin &write_cache_variants, 1592ec05489SJon Lin &update_cache_variants), 1602ec05489SJon Lin 0, 161b2ee20d3SJason Zhu SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL), 162b2ee20d3SJason Zhu SPINAND_SELECT_TARGET(w25m02gv_select_target)), 16381afcfe1SJon Lin SPINAND_INFO("W25N02KV", 16481afcfe1SJon Lin SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xAA, 0x22), 1659dd597e7SJon Lin NAND_MEMORG(1, 2048, 128, 64, 2048, 1, 1, 1), 16622edf958SJon Lin NAND_ECCREQ(8, 512), 16722edf958SJon Lin SPINAND_INFO_OP_VARIANTS(&read_cache_variants, 16822edf958SJon Lin &write_cache_variants, 16922edf958SJon Lin &update_cache_variants), 17022edf958SJon Lin 0, 17122edf958SJon Lin SPINAND_ECCINFO(&w25n02kv_ooblayout, 17222edf958SJon Lin w25n02kv_ecc_get_status)), 17381afcfe1SJon Lin SPINAND_INFO("W25N04KV", 17481afcfe1SJon Lin SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xAA, 0x23), 1759dd597e7SJon Lin NAND_MEMORG(1, 2048, 128, 64, 4096, 1, 1, 1), 176027a4716SJon Lin NAND_ECCREQ(8, 512), 177027a4716SJon Lin SPINAND_INFO_OP_VARIANTS(&read_cache_variants, 178027a4716SJon Lin &write_cache_variants, 179027a4716SJon Lin &update_cache_variants), 180027a4716SJon Lin 0, 181027a4716SJon Lin SPINAND_ECCINFO(&w25n02kv_ooblayout, 182027a4716SJon Lin w25n02kv_ecc_get_status)), 18381afcfe1SJon Lin SPINAND_INFO("W25N01GW", 18481afcfe1SJon Lin SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xBA, 0x21), 18581afcfe1SJon Lin NAND_MEMORG(1, 2048, 64, 64, 1024, 1, 1, 1), 18681afcfe1SJon Lin NAND_ECCREQ(1, 512), 18781afcfe1SJon Lin SPINAND_INFO_OP_VARIANTS(&read_cache_variants, 18881afcfe1SJon Lin &write_cache_variants, 18981afcfe1SJon Lin &update_cache_variants), 19081afcfe1SJon Lin 0, 19181afcfe1SJon Lin SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL), 19281afcfe1SJon Lin SPINAND_SELECT_TARGET(w25m02gv_select_target)), 193f9e45ef5SJon Lin SPINAND_INFO("W25N02KW", 194f9e45ef5SJon Lin SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xBA, 0x22), 1959dd597e7SJon Lin NAND_MEMORG(1, 2048, 128, 64, 2048, 1, 1, 1), 196f9e45ef5SJon Lin NAND_ECCREQ(8, 512), 197f9e45ef5SJon Lin SPINAND_INFO_OP_VARIANTS(&read_cache_variants, 198f9e45ef5SJon Lin &write_cache_variants, 199f9e45ef5SJon Lin &update_cache_variants), 200f9e45ef5SJon Lin 0, 201f9e45ef5SJon Lin SPINAND_ECCINFO(&w25n02kv_ooblayout, 202f9e45ef5SJon Lin w25n02kv_ecc_get_status)), 2039dd597e7SJon Lin SPINAND_INFO("W25N01KV", 2049dd597e7SJon Lin SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xAE, 0x21), 2059dd597e7SJon Lin NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1), 2069dd597e7SJon Lin NAND_ECCREQ(4, 512), 2079dd597e7SJon Lin SPINAND_INFO_OP_VARIANTS(&read_cache_variants, 2089dd597e7SJon Lin &write_cache_variants, 2099dd597e7SJon Lin &update_cache_variants), 2109dd597e7SJon Lin 0, 2119dd597e7SJon Lin SPINAND_ECCINFO(&w25n02kv_ooblayout, 2129dd597e7SJon Lin w25n02kv_ecc_get_status)), 213473de4c4SJon Lin SPINAND_INFO("W25N01JWZEIG", 214473de4c4SJon Lin SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xBC), 215473de4c4SJon Lin NAND_MEMORG(1, 2048, 64, 64, 1024, 1, 1, 1), 216473de4c4SJon Lin NAND_ECCREQ(1, 512), 217473de4c4SJon Lin SPINAND_INFO_OP_VARIANTS(&read_cache_variants, 218473de4c4SJon Lin &write_cache_variants, 219473de4c4SJon Lin &update_cache_variants), 220473de4c4SJon Lin SPINAND_HAS_QE_BIT, 221473de4c4SJon Lin SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL)), 2227512998aSJon Lin SPINAND_INFO("W25N01KWZPIG", 2237512998aSJon Lin SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xBE), 2247512998aSJon Lin NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1), 2257512998aSJon Lin NAND_ECCREQ(4, 512), 2267512998aSJon Lin SPINAND_INFO_OP_VARIANTS(&read_cache_variants, 2277512998aSJon Lin &write_cache_variants, 2287512998aSJon Lin &update_cache_variants), 2297512998aSJon Lin 0, 2307512998aSJon Lin SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)), 23122edf958SJon Lin }; 23222edf958SJon Lin 233b98ac5e2SFrieder Schrempf static int winbond_spinand_init(struct spinand_device *spinand) 234b98ac5e2SFrieder Schrempf { 235b98ac5e2SFrieder Schrempf struct nand_device *nand = spinand_to_nand(spinand); 236b98ac5e2SFrieder Schrempf unsigned int i; 237b98ac5e2SFrieder Schrempf 238b98ac5e2SFrieder Schrempf /* 239b98ac5e2SFrieder Schrempf * Make sure all dies are in buffer read mode and not continuous read 240b98ac5e2SFrieder Schrempf * mode. 241b98ac5e2SFrieder Schrempf */ 242b98ac5e2SFrieder Schrempf for (i = 0; i < nand->memorg.ntargets; i++) { 243b98ac5e2SFrieder Schrempf spinand_select_target(spinand, i); 244b98ac5e2SFrieder Schrempf spinand_upd_cfg(spinand, WINBOND_CFG_BUF_READ, 245b98ac5e2SFrieder Schrempf WINBOND_CFG_BUF_READ); 246b98ac5e2SFrieder Schrempf } 247b98ac5e2SFrieder Schrempf 2484857d1caSJon Lin /* W25N01JWZEIG enable continuous read */ 249*fc61dde5SJon Lin #ifdef CONFIG_SPI_NAND_WINBOND_CONT_READ 2504857d1caSJon Lin if (spinand->id.data[1] == 0xaa && spinand->id.data[2] == 0x21) { 2514857d1caSJon Lin spinand->support_cont_read = true; 2524857d1caSJon Lin spinand_upd_cfg(spinand, CFG_BUF_ENABLE, 0); 2534857d1caSJon Lin printf("Support cont_read\n"); 2544857d1caSJon Lin } 2554857d1caSJon Lin #endif 2564857d1caSJon Lin 257b98ac5e2SFrieder Schrempf return 0; 258b98ac5e2SFrieder Schrempf } 259b98ac5e2SFrieder Schrempf 260b98ac5e2SFrieder Schrempf static const struct spinand_manufacturer_ops winbond_spinand_manuf_ops = { 261b98ac5e2SFrieder Schrempf .init = winbond_spinand_init, 262b98ac5e2SFrieder Schrempf }; 263b98ac5e2SFrieder Schrempf 264b98ac5e2SFrieder Schrempf const struct spinand_manufacturer winbond_spinand_manufacturer = { 265b98ac5e2SFrieder Schrempf .id = SPINAND_MFR_WINBOND, 266b98ac5e2SFrieder Schrempf .name = "Winbond", 26781afcfe1SJon Lin .chips = winbond_spinand_table, 26881afcfe1SJon Lin .nchips = ARRAY_SIZE(winbond_spinand_table), 269b98ac5e2SFrieder Schrempf .ops = &winbond_spinand_manuf_ops, 270b98ac5e2SFrieder Schrempf }; 271