1 | // SPDX-License-Identifier: GPL-2.0 |
---|---|
2 | /* |
3 | * Copyright (c) 2016-2017 Micron Technology, Inc. |
4 | * |
5 | * Authors: |
6 | * Peter Pan <peterpandong@micron.com> |
7 | */ |
8 | |
9 | #include <linux/device.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/mtd/spinand.h> |
12 | #include <linux/spi/spi-mem.h> |
13 | #include <linux/string.h> |
14 | |
15 | #define SPINAND_MFR_MICRON 0x2c |
16 | |
17 | #define MICRON_STATUS_ECC_MASK GENMASK(6, 4) |
18 | #define MICRON_STATUS_ECC_NO_BITFLIPS (0 << 4) |
19 | #define MICRON_STATUS_ECC_1TO3_BITFLIPS (1 << 4) |
20 | #define MICRON_STATUS_ECC_4TO6_BITFLIPS (3 << 4) |
21 | #define MICRON_STATUS_ECC_7TO8_BITFLIPS (5 << 4) |
22 | |
23 | #define MICRON_CFG_CR BIT(0) |
24 | |
25 | /* |
26 | * As per datasheet, die selection is done by the 6th bit of Die |
27 | * Select Register (Address 0xD0). |
28 | */ |
29 | #define MICRON_DIE_SELECT_REG 0xD0 |
30 | |
31 | #define MICRON_SELECT_DIE(x) ((x) << 6) |
32 | |
33 | #define MICRON_MT29F2G01ABAGD_CFG_OTP_STATE BIT(7) |
34 | #define MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK \ |
35 | (CFG_OTP_ENABLE | MICRON_MT29F2G01ABAGD_CFG_OTP_STATE) |
36 | |
37 | static SPINAND_OP_VARIANTS(quadio_read_cache_variants, |
38 | SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 2, NULL, 0), |
39 | SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0), |
40 | SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0), |
41 | SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0), |
42 | SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0), |
43 | SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0)); |
44 | |
45 | static SPINAND_OP_VARIANTS(x4_write_cache_variants, |
46 | SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0), |
47 | SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0)); |
48 | |
49 | static SPINAND_OP_VARIANTS(x4_update_cache_variants, |
50 | SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0), |
51 | SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0)); |
52 | |
53 | /* Micron MT29F2G01AAAED Device */ |
54 | static SPINAND_OP_VARIANTS(x4_read_cache_variants, |
55 | SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0), |
56 | SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0), |
57 | SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0), |
58 | SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0)); |
59 | |
60 | static SPINAND_OP_VARIANTS(x1_write_cache_variants, |
61 | SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0)); |
62 | |
63 | static SPINAND_OP_VARIANTS(x1_update_cache_variants, |
64 | SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0)); |
65 | |
66 | static int micron_8_ooblayout_ecc(struct mtd_info *mtd, int section, |
67 | struct mtd_oob_region *region) |
68 | { |
69 | if (section) |
70 | return -ERANGE; |
71 | |
72 | region->offset = mtd->oobsize / 2; |
73 | region->length = mtd->oobsize / 2; |
74 | |
75 | return 0; |
76 | } |
77 | |
78 | static int micron_8_ooblayout_free(struct mtd_info *mtd, int section, |
79 | struct mtd_oob_region *region) |
80 | { |
81 | if (section) |
82 | return -ERANGE; |
83 | |
84 | /* Reserve 2 bytes for the BBM. */ |
85 | region->offset = 2; |
86 | region->length = (mtd->oobsize / 2) - 2; |
87 | |
88 | return 0; |
89 | } |
90 | |
91 | static const struct mtd_ooblayout_ops micron_8_ooblayout = { |
92 | .ecc = micron_8_ooblayout_ecc, |
93 | .free = micron_8_ooblayout_free, |
94 | }; |
95 | |
96 | static int micron_4_ooblayout_ecc(struct mtd_info *mtd, int section, |
97 | struct mtd_oob_region *region) |
98 | { |
99 | struct spinand_device *spinand = mtd_to_spinand(mtd); |
100 | |
101 | if (section >= spinand->base.memorg.pagesize / |
102 | mtd->ecc_step_size) |
103 | return -ERANGE; |
104 | |
105 | region->offset = (section * 16) + 8; |
106 | region->length = 8; |
107 | |
108 | return 0; |
109 | } |
110 | |
111 | static int micron_4_ooblayout_free(struct mtd_info *mtd, int section, |
112 | struct mtd_oob_region *region) |
113 | { |
114 | struct spinand_device *spinand = mtd_to_spinand(mtd); |
115 | |
116 | if (section >= spinand->base.memorg.pagesize / |
117 | mtd->ecc_step_size) |
118 | return -ERANGE; |
119 | |
120 | if (section) { |
121 | region->offset = 16 * section; |
122 | region->length = 8; |
123 | } else { |
124 | /* section 0 has two bytes reserved for the BBM */ |
125 | region->offset = 2; |
126 | region->length = 6; |
127 | } |
128 | |
129 | return 0; |
130 | } |
131 | |
132 | static const struct mtd_ooblayout_ops micron_4_ooblayout = { |
133 | .ecc = micron_4_ooblayout_ecc, |
134 | .free = micron_4_ooblayout_free, |
135 | }; |
136 | |
137 | static int micron_select_target(struct spinand_device *spinand, |
138 | unsigned int target) |
139 | { |
140 | struct spi_mem_op op = SPINAND_SET_FEATURE_1S_1S_1S_OP(MICRON_DIE_SELECT_REG, |
141 | spinand->scratchbuf); |
142 | |
143 | if (target > 1) |
144 | return -EINVAL; |
145 | |
146 | *spinand->scratchbuf = MICRON_SELECT_DIE(target); |
147 | |
148 | return spi_mem_exec_op(mem: spinand->spimem, op: &op); |
149 | } |
150 | |
151 | static int micron_8_ecc_get_status(struct spinand_device *spinand, |
152 | u8 status) |
153 | { |
154 | switch (status & MICRON_STATUS_ECC_MASK) { |
155 | case STATUS_ECC_NO_BITFLIPS: |
156 | return 0; |
157 | |
158 | case STATUS_ECC_UNCOR_ERROR: |
159 | return -EBADMSG; |
160 | |
161 | case MICRON_STATUS_ECC_1TO3_BITFLIPS: |
162 | return 3; |
163 | |
164 | case MICRON_STATUS_ECC_4TO6_BITFLIPS: |
165 | return 6; |
166 | |
167 | case MICRON_STATUS_ECC_7TO8_BITFLIPS: |
168 | return 8; |
169 | |
170 | default: |
171 | break; |
172 | } |
173 | |
174 | return -EINVAL; |
175 | } |
176 | |
177 | static int mt29f2g01abagd_otp_is_locked(struct spinand_device *spinand) |
178 | { |
179 | size_t bufsize = spinand_otp_page_size(spinand); |
180 | size_t retlen; |
181 | u8 *buf; |
182 | int ret; |
183 | |
184 | buf = kmalloc(bufsize, GFP_KERNEL); |
185 | if (!buf) |
186 | return -ENOMEM; |
187 | |
188 | ret = spinand_upd_cfg(spinand, |
189 | MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK, |
190 | MICRON_MT29F2G01ABAGD_CFG_OTP_STATE); |
191 | if (ret) |
192 | goto free_buf; |
193 | |
194 | ret = spinand_user_otp_read(spinand, ofs: 0, len: bufsize, retlen: &retlen, buf); |
195 | |
196 | if (spinand_upd_cfg(spinand, MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK, |
197 | val: 0)) { |
198 | dev_warn(&spinand_to_mtd(spinand)->dev, |
199 | "Can not disable OTP mode\n"); |
200 | ret = -EIO; |
201 | } |
202 | |
203 | if (ret) |
204 | goto free_buf; |
205 | |
206 | /* If all zeros, then the OTP area is locked. */ |
207 | if (mem_is_zero(s: buf, n: bufsize)) |
208 | ret = 1; |
209 | |
210 | free_buf: |
211 | kfree(objp: buf); |
212 | return ret; |
213 | } |
214 | |
215 | static int mt29f2g01abagd_otp_info(struct spinand_device *spinand, size_t len, |
216 | struct otp_info *buf, size_t *retlen, |
217 | bool user) |
218 | { |
219 | int locked; |
220 | |
221 | if (len < sizeof(*buf)) |
222 | return -EINVAL; |
223 | |
224 | locked = mt29f2g01abagd_otp_is_locked(spinand); |
225 | if (locked < 0) |
226 | return locked; |
227 | |
228 | buf->locked = locked; |
229 | buf->start = 0; |
230 | buf->length = user ? spinand_user_otp_size(spinand) : |
231 | spinand_fact_otp_size(spinand); |
232 | |
233 | *retlen = sizeof(*buf); |
234 | return 0; |
235 | } |
236 | |
237 | static int mt29f2g01abagd_fact_otp_info(struct spinand_device *spinand, |
238 | size_t len, struct otp_info *buf, |
239 | size_t *retlen) |
240 | { |
241 | return mt29f2g01abagd_otp_info(spinand, len, buf, retlen, user: false); |
242 | } |
243 | |
244 | static int mt29f2g01abagd_user_otp_info(struct spinand_device *spinand, |
245 | size_t len, struct otp_info *buf, |
246 | size_t *retlen) |
247 | { |
248 | return mt29f2g01abagd_otp_info(spinand, len, buf, retlen, user: true); |
249 | } |
250 | |
251 | static int mt29f2g01abagd_otp_lock(struct spinand_device *spinand, loff_t from, |
252 | size_t len) |
253 | { |
254 | struct spi_mem_op write_op = SPINAND_WR_EN_DIS_1S_0_0_OP(true); |
255 | struct spi_mem_op exec_op = SPINAND_PROG_EXEC_1S_1S_0_OP(0); |
256 | u8 status; |
257 | int ret; |
258 | |
259 | ret = spinand_upd_cfg(spinand, |
260 | MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK, |
261 | MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK); |
262 | if (!ret) |
263 | return ret; |
264 | |
265 | ret = spi_mem_exec_op(mem: spinand->spimem, op: &write_op); |
266 | if (!ret) |
267 | goto out; |
268 | |
269 | ret = spi_mem_exec_op(mem: spinand->spimem, op: &exec_op); |
270 | if (!ret) |
271 | goto out; |
272 | |
273 | ret = spinand_wait(spinand, |
274 | SPINAND_WRITE_INITIAL_DELAY_US, |
275 | SPINAND_WRITE_POLL_DELAY_US, |
276 | s: &status); |
277 | if (!ret && (status & STATUS_PROG_FAILED)) |
278 | ret = -EIO; |
279 | |
280 | out: |
281 | if (spinand_upd_cfg(spinand, MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK, val: 0)) { |
282 | dev_warn(&spinand_to_mtd(spinand)->dev, |
283 | "Can not disable OTP mode\n"); |
284 | ret = -EIO; |
285 | } |
286 | |
287 | return ret; |
288 | } |
289 | |
290 | static const struct spinand_user_otp_ops mt29f2g01abagd_user_otp_ops = { |
291 | .info = mt29f2g01abagd_user_otp_info, |
292 | .lock = mt29f2g01abagd_otp_lock, |
293 | .read = spinand_user_otp_read, |
294 | .write = spinand_user_otp_write, |
295 | }; |
296 | |
297 | static const struct spinand_fact_otp_ops mt29f2g01abagd_fact_otp_ops = { |
298 | .info = mt29f2g01abagd_fact_otp_info, |
299 | .read = spinand_fact_otp_read, |
300 | }; |
301 | |
302 | static const struct spinand_info micron_spinand_table[] = { |
303 | /* M79A 2Gb 3.3V */ |
304 | SPINAND_INFO("MT29F2G01ABAGD", |
305 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x24), |
306 | NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1), |
307 | NAND_ECCREQ(8, 512), |
308 | SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, |
309 | &x4_write_cache_variants, |
310 | &x4_update_cache_variants), |
311 | 0, |
312 | SPINAND_ECCINFO(µn_8_ooblayout, |
313 | micron_8_ecc_get_status), |
314 | SPINAND_USER_OTP_INFO(12, 2, &mt29f2g01abagd_user_otp_ops), |
315 | SPINAND_FACT_OTP_INFO(2, 0, &mt29f2g01abagd_fact_otp_ops)), |
316 | /* M79A 2Gb 1.8V */ |
317 | SPINAND_INFO("MT29F2G01ABBGD", |
318 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x25), |
319 | NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1), |
320 | NAND_ECCREQ(8, 512), |
321 | SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, |
322 | &x4_write_cache_variants, |
323 | &x4_update_cache_variants), |
324 | 0, |
325 | SPINAND_ECCINFO(µn_8_ooblayout, |
326 | micron_8_ecc_get_status)), |
327 | /* M78A 1Gb 3.3V */ |
328 | SPINAND_INFO("MT29F1G01ABAFD", |
329 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x14), |
330 | NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), |
331 | NAND_ECCREQ(8, 512), |
332 | SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, |
333 | &x4_write_cache_variants, |
334 | &x4_update_cache_variants), |
335 | 0, |
336 | SPINAND_ECCINFO(µn_8_ooblayout, |
337 | micron_8_ecc_get_status)), |
338 | /* M78A 1Gb 1.8V */ |
339 | SPINAND_INFO("MT29F1G01ABAFD", |
340 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x15), |
341 | NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), |
342 | NAND_ECCREQ(8, 512), |
343 | SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, |
344 | &x4_write_cache_variants, |
345 | &x4_update_cache_variants), |
346 | 0, |
347 | SPINAND_ECCINFO(µn_8_ooblayout, |
348 | micron_8_ecc_get_status)), |
349 | /* M79A 4Gb 3.3V */ |
350 | SPINAND_INFO("MT29F4G01ADAGD", |
351 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x36), |
352 | NAND_MEMORG(1, 2048, 128, 64, 2048, 80, 2, 1, 2), |
353 | NAND_ECCREQ(8, 512), |
354 | SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, |
355 | &x4_write_cache_variants, |
356 | &x4_update_cache_variants), |
357 | 0, |
358 | SPINAND_ECCINFO(µn_8_ooblayout, |
359 | micron_8_ecc_get_status), |
360 | SPINAND_SELECT_TARGET(micron_select_target)), |
361 | /* M70A 4Gb 3.3V */ |
362 | SPINAND_INFO("MT29F4G01ABAFD", |
363 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x34), |
364 | NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), |
365 | NAND_ECCREQ(8, 512), |
366 | SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, |
367 | &x4_write_cache_variants, |
368 | &x4_update_cache_variants), |
369 | SPINAND_HAS_CR_FEAT_BIT, |
370 | SPINAND_ECCINFO(µn_8_ooblayout, |
371 | micron_8_ecc_get_status)), |
372 | /* M70A 4Gb 1.8V */ |
373 | SPINAND_INFO("MT29F4G01ABBFD", |
374 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x35), |
375 | NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), |
376 | NAND_ECCREQ(8, 512), |
377 | SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, |
378 | &x4_write_cache_variants, |
379 | &x4_update_cache_variants), |
380 | SPINAND_HAS_CR_FEAT_BIT, |
381 | SPINAND_ECCINFO(µn_8_ooblayout, |
382 | micron_8_ecc_get_status)), |
383 | /* M70A 8Gb 3.3V */ |
384 | SPINAND_INFO("MT29F8G01ADAFD", |
385 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x46), |
386 | NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 2), |
387 | NAND_ECCREQ(8, 512), |
388 | SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, |
389 | &x4_write_cache_variants, |
390 | &x4_update_cache_variants), |
391 | SPINAND_HAS_CR_FEAT_BIT, |
392 | SPINAND_ECCINFO(µn_8_ooblayout, |
393 | micron_8_ecc_get_status), |
394 | SPINAND_SELECT_TARGET(micron_select_target)), |
395 | /* M70A 8Gb 1.8V */ |
396 | SPINAND_INFO("MT29F8G01ADBFD", |
397 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x47), |
398 | NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 2), |
399 | NAND_ECCREQ(8, 512), |
400 | SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, |
401 | &x4_write_cache_variants, |
402 | &x4_update_cache_variants), |
403 | SPINAND_HAS_CR_FEAT_BIT, |
404 | SPINAND_ECCINFO(µn_8_ooblayout, |
405 | micron_8_ecc_get_status), |
406 | SPINAND_SELECT_TARGET(micron_select_target)), |
407 | /* M69A 2Gb 3.3V */ |
408 | SPINAND_INFO("MT29F2G01AAAED", |
409 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x9F), |
410 | NAND_MEMORG(1, 2048, 64, 64, 2048, 80, 2, 1, 1), |
411 | NAND_ECCREQ(4, 512), |
412 | SPINAND_INFO_OP_VARIANTS(&x4_read_cache_variants, |
413 | &x1_write_cache_variants, |
414 | &x1_update_cache_variants), |
415 | 0, |
416 | SPINAND_ECCINFO(µn_4_ooblayout, NULL)), |
417 | }; |
418 | |
419 | static int micron_spinand_init(struct spinand_device *spinand) |
420 | { |
421 | /* |
422 | * M70A device series enable Continuous Read feature at Power-up, |
423 | * which is not supported. Disable this bit to avoid any possible |
424 | * failure. |
425 | */ |
426 | if (spinand->flags & SPINAND_HAS_CR_FEAT_BIT) |
427 | return spinand_upd_cfg(spinand, MICRON_CFG_CR, val: 0); |
428 | |
429 | return 0; |
430 | } |
431 | |
432 | static const struct spinand_manufacturer_ops micron_spinand_manuf_ops = { |
433 | .init = micron_spinand_init, |
434 | }; |
435 | |
436 | const struct spinand_manufacturer micron_spinand_manufacturer = { |
437 | .id = SPINAND_MFR_MICRON, |
438 | .name = "Micron", |
439 | .chips = micron_spinand_table, |
440 | .nchips = ARRAY_SIZE(micron_spinand_table), |
441 | .ops = µn_spinand_manuf_ops, |
442 | }; |
443 |
Definitions
- quadio_read_cache_variants
- x4_write_cache_variants
- x4_update_cache_variants
- x4_read_cache_variants
- x1_write_cache_variants
- x1_update_cache_variants
- micron_8_ooblayout_ecc
- micron_8_ooblayout_free
- micron_8_ooblayout
- micron_4_ooblayout_ecc
- micron_4_ooblayout_free
- micron_4_ooblayout
- micron_select_target
- micron_8_ecc_get_status
- mt29f2g01abagd_otp_is_locked
- mt29f2g01abagd_otp_info
- mt29f2g01abagd_fact_otp_info
- mt29f2g01abagd_user_otp_info
- mt29f2g01abagd_otp_lock
- mt29f2g01abagd_user_otp_ops
- mt29f2g01abagd_fact_otp_ops
- micron_spinand_table
- micron_spinand_init
- micron_spinand_manuf_ops
Improve your Profiling and Debugging skills
Find out more