1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (c) 2017 exceet electronics GmbH 4 * 5 * Authors: 6 * Frieder Schrempf <frieder.schrempf@exceet.de> 7 * Boris Brezillon <boris.brezillon@bootlin.com> 8 */ 9 10 #ifndef __UBOOT__ 11 #include <linux/device.h> 12 #include <linux/kernel.h> 13 #endif 14 #include <linux/mtd/spinand.h> 15 16 #define SPINAND_MFR_WINBOND 0xEF 17 18 #define WINBOND_CFG_BUF_READ BIT(3) 19 #define WINBOND_STATUS_ECC_HAS_BITFLIPS_T (3 << 4) 20 21 static SPINAND_OP_VARIANTS(read_cache_variants, 22 SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0), 23 SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), 24 SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), 25 SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), 26 SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), 27 SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); 28 29 static SPINAND_OP_VARIANTS(write_cache_variants, 30 SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), 31 SPINAND_PROG_LOAD(true, 0, NULL, 0)); 32 33 static SPINAND_OP_VARIANTS(update_cache_variants, 34 SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), 35 SPINAND_PROG_LOAD(false, 0, NULL, 0)); 36 37 static int w25m02gv_ooblayout_ecc(struct mtd_info *mtd, int section, 38 struct mtd_oob_region *region) 39 { 40 if (section > 3) 41 return -ERANGE; 42 43 region->offset = (16 * section) + 8; 44 region->length = 8; 45 46 return 0; 47 } 48 49 static int w25m02gv_ooblayout_free(struct mtd_info *mtd, int section, 50 struct mtd_oob_region *region) 51 { 52 if (section > 3) 53 return -ERANGE; 54 55 region->offset = (16 * section) + 2; 56 region->length = 6; 57 58 return 0; 59 } 60 61 static const struct mtd_ooblayout_ops w25m02gv_ooblayout = { 62 .ecc = w25m02gv_ooblayout_ecc, 63 .rfree = w25m02gv_ooblayout_free, 64 }; 65 66 static int w25m02gv_select_target(struct spinand_device *spinand, 67 unsigned int target) 68 { 69 struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0xc2, 1), 70 SPI_MEM_OP_NO_ADDR, 71 SPI_MEM_OP_NO_DUMMY, 72 SPI_MEM_OP_DATA_OUT(1, 73 spinand->scratchbuf, 74 1)); 75 76 *spinand->scratchbuf = target; 77 return spi_mem_exec_op(spinand->slave, &op); 78 } 79 80 static int w25n02kv_ooblayout_ecc(struct mtd_info *mtd, int section, 81 struct mtd_oob_region *region) 82 { 83 if (section) 84 return -ERANGE; 85 86 region->offset = 64; 87 region->length = 64; 88 89 return 0; 90 } 91 92 static int w25n02kv_ooblayout_free(struct mtd_info *mtd, int section, 93 struct mtd_oob_region *region) 94 { 95 if (section) 96 return -ERANGE; 97 98 /* Reserve 2 bytes for the BBM. */ 99 region->offset = 2; 100 region->length = 62; 101 102 return 0; 103 } 104 105 static const struct mtd_ooblayout_ops w25n02kv_ooblayout = { 106 .ecc = w25n02kv_ooblayout_ecc, 107 .rfree = w25n02kv_ooblayout_free, 108 }; 109 110 static int w25n02kv_ecc_get_status(struct spinand_device *spinand, 111 u8 status) 112 { 113 struct nand_device *nand = spinand_to_nand(spinand); 114 u8 mbf = 0; 115 struct spi_mem_op op = SPINAND_GET_FEATURE_OP(0x30, &mbf); 116 117 switch (status & STATUS_ECC_MASK) { 118 case STATUS_ECC_NO_BITFLIPS: 119 return 0; 120 121 case STATUS_ECC_UNCOR_ERROR: 122 return -EBADMSG; 123 124 case STATUS_ECC_HAS_BITFLIPS: 125 case WINBOND_STATUS_ECC_HAS_BITFLIPS_T: 126 /* 127 * Let's try to retrieve the real maximum number of bitflips 128 * in order to avoid forcing the wear-leveling layer to move 129 * data around if it's not necessary. 130 */ 131 if (spi_mem_exec_op(spinand->slave, &op)) 132 return nand->eccreq.strength; 133 134 mbf >>= 4; 135 136 if (WARN_ON(mbf > nand->eccreq.strength || !mbf)) 137 return nand->eccreq.strength; 138 139 return mbf; 140 141 default: 142 break; 143 } 144 145 return -EINVAL; 146 } 147 148 static const struct spinand_info winbond_spinand_table[] = { 149 SPINAND_INFO("W25M02GV", 0xAB, 150 NAND_MEMORG(1, 2048, 64, 64, 1024, 1, 1, 2), 151 NAND_ECCREQ(1, 512), 152 SPINAND_INFO_OP_VARIANTS(&read_cache_variants, 153 &write_cache_variants, 154 &update_cache_variants), 155 0, 156 SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL), 157 SPINAND_SELECT_TARGET(w25m02gv_select_target)), 158 SPINAND_INFO("W25N01GV", 0xAA, 159 NAND_MEMORG(1, 2048, 64, 64, 1024, 1, 1, 1), 160 NAND_ECCREQ(1, 512), 161 SPINAND_INFO_OP_VARIANTS(&read_cache_variants, 162 &write_cache_variants, 163 &update_cache_variants), 164 0, 165 SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL), 166 SPINAND_SELECT_TARGET(w25m02gv_select_target)), 167 }; 168 169 /* Another set for the same id[2] devices in one series */ 170 static const struct spinand_info winbond_spinand_table2[] = { 171 SPINAND_INFO("W25N02KV", 0xAA, 172 NAND_MEMORG(1, 2048, 64, 64, 2048, 1, 1, 1), 173 NAND_ECCREQ(8, 512), 174 SPINAND_INFO_OP_VARIANTS(&read_cache_variants, 175 &write_cache_variants, 176 &update_cache_variants), 177 0, 178 SPINAND_ECCINFO(&w25n02kv_ooblayout, 179 w25n02kv_ecc_get_status)), 180 }; 181 182 /** 183 * winbond_spinand_detect - initialize device related part in spinand_device 184 * struct if it is a Winbond device. 185 * @spinand: SPI NAND device structure 186 */ 187 static int winbond_spinand_detect(struct spinand_device *spinand) 188 { 189 u8 *id = spinand->id.data; 190 int ret; 191 192 /* 193 * Winbond SPI NAND read ID need a dummy byte, 194 * so the first byte in raw_id is dummy. 195 */ 196 if (id[1] != SPINAND_MFR_WINBOND) 197 return 0; 198 199 if (id[2] == 0xAA && id[3] == 0x22) 200 ret = spinand_match_and_init(spinand, winbond_spinand_table2, 201 ARRAY_SIZE(winbond_spinand_table), 202 id[2]); 203 else 204 ret = spinand_match_and_init(spinand, winbond_spinand_table, 205 ARRAY_SIZE(winbond_spinand_table), 206 id[2]); 207 208 if (ret) 209 return ret; 210 211 return 1; 212 } 213 214 static int winbond_spinand_init(struct spinand_device *spinand) 215 { 216 struct nand_device *nand = spinand_to_nand(spinand); 217 unsigned int i; 218 219 /* 220 * Make sure all dies are in buffer read mode and not continuous read 221 * mode. 222 */ 223 for (i = 0; i < nand->memorg.ntargets; i++) { 224 spinand_select_target(spinand, i); 225 spinand_upd_cfg(spinand, WINBOND_CFG_BUF_READ, 226 WINBOND_CFG_BUF_READ); 227 } 228 229 return 0; 230 } 231 232 static const struct spinand_manufacturer_ops winbond_spinand_manuf_ops = { 233 .detect = winbond_spinand_detect, 234 .init = winbond_spinand_init, 235 }; 236 237 const struct spinand_manufacturer winbond_spinand_manufacturer = { 238 .id = SPINAND_MFR_WINBOND, 239 .name = "Winbond", 240 .ops = &winbond_spinand_manuf_ops, 241 }; 242