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 | #include <linux/device.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/mtd/spinand.h> |
13 | |
14 | #define SPINAND_MFR_WINBOND 0xEF |
15 | |
16 | #define WINBOND_CFG_BUF_READ BIT(3) |
17 | |
18 | #define W25N04KV_STATUS_ECC_5_8_BITFLIPS (3 << 4) |
19 | |
20 | static SPINAND_OP_VARIANTS(read_cache_variants, |
21 | SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0), |
22 | SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), |
23 | SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), |
24 | SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), |
25 | SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), |
26 | SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); |
27 | |
28 | static SPINAND_OP_VARIANTS(write_cache_variants, |
29 | SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), |
30 | SPINAND_PROG_LOAD(true, 0, NULL, 0)); |
31 | |
32 | static SPINAND_OP_VARIANTS(update_cache_variants, |
33 | SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), |
34 | SPINAND_PROG_LOAD(false, 0, NULL, 0)); |
35 | |
36 | static int w25m02gv_ooblayout_ecc(struct mtd_info *mtd, int section, |
37 | struct mtd_oob_region *region) |
38 | { |
39 | if (section > 3) |
40 | return -ERANGE; |
41 | |
42 | region->offset = (16 * section) + 8; |
43 | region->length = 8; |
44 | |
45 | return 0; |
46 | } |
47 | |
48 | static int w25m02gv_ooblayout_free(struct mtd_info *mtd, int section, |
49 | struct mtd_oob_region *region) |
50 | { |
51 | if (section > 3) |
52 | return -ERANGE; |
53 | |
54 | region->offset = (16 * section) + 2; |
55 | region->length = 6; |
56 | |
57 | return 0; |
58 | } |
59 | |
60 | static const struct mtd_ooblayout_ops w25m02gv_ooblayout = { |
61 | .ecc = w25m02gv_ooblayout_ecc, |
62 | .free = w25m02gv_ooblayout_free, |
63 | }; |
64 | |
65 | static int w25m02gv_select_target(struct spinand_device *spinand, |
66 | unsigned int target) |
67 | { |
68 | struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0xc2, 1), |
69 | SPI_MEM_OP_NO_ADDR, |
70 | SPI_MEM_OP_NO_DUMMY, |
71 | SPI_MEM_OP_DATA_OUT(1, |
72 | spinand->scratchbuf, |
73 | 1)); |
74 | |
75 | *spinand->scratchbuf = target; |
76 | return spi_mem_exec_op(mem: spinand->spimem, op: &op); |
77 | } |
78 | |
79 | static int w25n02kv_ooblayout_ecc(struct mtd_info *mtd, int section, |
80 | struct mtd_oob_region *region) |
81 | { |
82 | if (section > 3) |
83 | return -ERANGE; |
84 | |
85 | region->offset = 64 + (16 * section); |
86 | region->length = 13; |
87 | |
88 | return 0; |
89 | } |
90 | |
91 | static int w25n02kv_ooblayout_free(struct mtd_info *mtd, int section, |
92 | struct mtd_oob_region *region) |
93 | { |
94 | if (section > 3) |
95 | return -ERANGE; |
96 | |
97 | region->offset = (16 * section) + 2; |
98 | region->length = 14; |
99 | |
100 | return 0; |
101 | } |
102 | |
103 | static const struct mtd_ooblayout_ops w25n02kv_ooblayout = { |
104 | .ecc = w25n02kv_ooblayout_ecc, |
105 | .free = w25n02kv_ooblayout_free, |
106 | }; |
107 | |
108 | static int w25n02kv_ecc_get_status(struct spinand_device *spinand, |
109 | u8 status) |
110 | { |
111 | struct nand_device *nand = spinand_to_nand(spinand); |
112 | u8 mbf = 0; |
113 | struct spi_mem_op op = SPINAND_GET_FEATURE_OP(0x30, spinand->scratchbuf); |
114 | |
115 | switch (status & STATUS_ECC_MASK) { |
116 | case STATUS_ECC_NO_BITFLIPS: |
117 | return 0; |
118 | |
119 | case STATUS_ECC_UNCOR_ERROR: |
120 | return -EBADMSG; |
121 | |
122 | case STATUS_ECC_HAS_BITFLIPS: |
123 | case W25N04KV_STATUS_ECC_5_8_BITFLIPS: |
124 | /* |
125 | * Let's try to retrieve the real maximum number of bitflips |
126 | * in order to avoid forcing the wear-leveling layer to move |
127 | * data around if it's not necessary. |
128 | */ |
129 | if (spi_mem_exec_op(mem: spinand->spimem, op: &op)) |
130 | return nanddev_get_ecc_conf(nand)->strength; |
131 | |
132 | mbf = *(spinand->scratchbuf) >> 4; |
133 | |
134 | if (WARN_ON(mbf > nanddev_get_ecc_conf(nand)->strength || !mbf)) |
135 | return nanddev_get_ecc_conf(nand)->strength; |
136 | |
137 | return mbf; |
138 | |
139 | default: |
140 | break; |
141 | } |
142 | |
143 | return -EINVAL; |
144 | } |
145 | |
146 | static const struct spinand_info winbond_spinand_table[] = { |
147 | SPINAND_INFO("W25M02GV" , |
148 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xab, 0x21), |
149 | NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 2), |
150 | NAND_ECCREQ(1, 512), |
151 | SPINAND_INFO_OP_VARIANTS(&read_cache_variants, |
152 | &write_cache_variants, |
153 | &update_cache_variants), |
154 | 0, |
155 | SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL), |
156 | SPINAND_SELECT_TARGET(w25m02gv_select_target)), |
157 | SPINAND_INFO("W25N01GV" , |
158 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x21), |
159 | NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 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_INFO("W25N02KV" , |
167 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x22), |
168 | NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), |
169 | NAND_ECCREQ(8, 512), |
170 | SPINAND_INFO_OP_VARIANTS(&read_cache_variants, |
171 | &write_cache_variants, |
172 | &update_cache_variants), |
173 | 0, |
174 | SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)), |
175 | SPINAND_INFO("W25N01JW" , |
176 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xbc, 0x21), |
177 | NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), |
178 | NAND_ECCREQ(4, 512), |
179 | SPINAND_INFO_OP_VARIANTS(&read_cache_variants, |
180 | &write_cache_variants, |
181 | &update_cache_variants), |
182 | 0, |
183 | SPINAND_ECCINFO(&w25m02gv_ooblayout, w25n02kv_ecc_get_status)), |
184 | SPINAND_INFO("W25N02JWZEIF" , |
185 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xbf, 0x22), |
186 | NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 2, 1), |
187 | NAND_ECCREQ(4, 512), |
188 | SPINAND_INFO_OP_VARIANTS(&read_cache_variants, |
189 | &write_cache_variants, |
190 | &update_cache_variants), |
191 | 0, |
192 | SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)), |
193 | SPINAND_INFO("W25N512GW" , |
194 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xba, 0x20), |
195 | NAND_MEMORG(1, 2048, 64, 64, 512, 10, 1, 1, 1), |
196 | NAND_ECCREQ(4, 512), |
197 | SPINAND_INFO_OP_VARIANTS(&read_cache_variants, |
198 | &write_cache_variants, |
199 | &update_cache_variants), |
200 | 0, |
201 | SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)), |
202 | SPINAND_INFO("W25N02KWZEIR" , |
203 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xba, 0x22), |
204 | NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), |
205 | NAND_ECCREQ(8, 512), |
206 | SPINAND_INFO_OP_VARIANTS(&read_cache_variants, |
207 | &write_cache_variants, |
208 | &update_cache_variants), |
209 | 0, |
210 | SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)), |
211 | SPINAND_INFO("W25N01GWZEIG" , |
212 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xba, 0x21), |
213 | NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), |
214 | NAND_ECCREQ(4, 512), |
215 | SPINAND_INFO_OP_VARIANTS(&read_cache_variants, |
216 | &write_cache_variants, |
217 | &update_cache_variants), |
218 | 0, |
219 | SPINAND_ECCINFO(&w25m02gv_ooblayout, w25n02kv_ecc_get_status)), |
220 | SPINAND_INFO("W25N04KV" , |
221 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x23), |
222 | NAND_MEMORG(1, 2048, 128, 64, 4096, 40, 2, 1, 1), |
223 | NAND_ECCREQ(8, 512), |
224 | SPINAND_INFO_OP_VARIANTS(&read_cache_variants, |
225 | &write_cache_variants, |
226 | &update_cache_variants), |
227 | 0, |
228 | SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)), |
229 | }; |
230 | |
231 | static int winbond_spinand_init(struct spinand_device *spinand) |
232 | { |
233 | struct nand_device *nand = spinand_to_nand(spinand); |
234 | unsigned int i; |
235 | |
236 | /* |
237 | * Make sure all dies are in buffer read mode and not continuous read |
238 | * mode. |
239 | */ |
240 | for (i = 0; i < nand->memorg.ntargets; i++) { |
241 | spinand_select_target(spinand, target: i); |
242 | spinand_upd_cfg(spinand, WINBOND_CFG_BUF_READ, |
243 | WINBOND_CFG_BUF_READ); |
244 | } |
245 | |
246 | return 0; |
247 | } |
248 | |
249 | static const struct spinand_manufacturer_ops winbond_spinand_manuf_ops = { |
250 | .init = winbond_spinand_init, |
251 | }; |
252 | |
253 | const struct spinand_manufacturer winbond_spinand_manufacturer = { |
254 | .id = SPINAND_MFR_WINBOND, |
255 | .name = "Winbond" , |
256 | .chips = winbond_spinand_table, |
257 | .nchips = ARRAY_SIZE(winbond_spinand_table), |
258 | .ops = &winbond_spinand_manuf_ops, |
259 | }; |
260 | |