1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2019 Jeff Kletsky |
4 | * |
5 | * Author: Jeff Kletsky <git-commits@allycomm.com> |
6 | */ |
7 | |
8 | #include <linux/device.h> |
9 | #include <linux/kernel.h> |
10 | #include <linux/mtd/spinand.h> |
11 | |
12 | |
13 | #define SPINAND_MFR_PARAGON 0xa1 |
14 | |
15 | |
16 | #define PN26G0XA_STATUS_ECC_BITMASK (3 << 4) |
17 | |
18 | #define PN26G0XA_STATUS_ECC_NONE_DETECTED (0 << 4) |
19 | #define PN26G0XA_STATUS_ECC_1_7_CORRECTED (1 << 4) |
20 | #define PN26G0XA_STATUS_ECC_ERRORED (2 << 4) |
21 | #define PN26G0XA_STATUS_ECC_8_CORRECTED (3 << 4) |
22 | |
23 | |
24 | static SPINAND_OP_VARIANTS(read_cache_variants, |
25 | SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0), |
26 | SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), |
27 | SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), |
28 | SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), |
29 | SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), |
30 | SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); |
31 | |
32 | static SPINAND_OP_VARIANTS(write_cache_variants, |
33 | SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), |
34 | SPINAND_PROG_LOAD(true, 0, NULL, 0)); |
35 | |
36 | static SPINAND_OP_VARIANTS(update_cache_variants, |
37 | SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), |
38 | SPINAND_PROG_LOAD(false, 0, NULL, 0)); |
39 | |
40 | |
41 | static int pn26g0xa_ooblayout_ecc(struct mtd_info *mtd, int section, |
42 | struct mtd_oob_region *region) |
43 | { |
44 | if (section > 3) |
45 | return -ERANGE; |
46 | |
47 | region->offset = 6 + (15 * section); /* 4 BBM + 2 user bytes */ |
48 | region->length = 13; |
49 | |
50 | return 0; |
51 | } |
52 | |
53 | static int pn26g0xa_ooblayout_free(struct mtd_info *mtd, int section, |
54 | struct mtd_oob_region *region) |
55 | { |
56 | if (section > 4) |
57 | return -ERANGE; |
58 | |
59 | if (section == 4) { |
60 | region->offset = 64; |
61 | region->length = 64; |
62 | } else { |
63 | region->offset = 4 + (15 * section); |
64 | region->length = 2; |
65 | } |
66 | |
67 | return 0; |
68 | } |
69 | |
70 | static int pn26g0xa_ecc_get_status(struct spinand_device *spinand, |
71 | u8 status) |
72 | { |
73 | switch (status & PN26G0XA_STATUS_ECC_BITMASK) { |
74 | case PN26G0XA_STATUS_ECC_NONE_DETECTED: |
75 | return 0; |
76 | |
77 | case PN26G0XA_STATUS_ECC_1_7_CORRECTED: |
78 | return 7; /* Return upper limit by convention */ |
79 | |
80 | case PN26G0XA_STATUS_ECC_8_CORRECTED: |
81 | return 8; |
82 | |
83 | case PN26G0XA_STATUS_ECC_ERRORED: |
84 | return -EBADMSG; |
85 | |
86 | default: |
87 | break; |
88 | } |
89 | |
90 | return -EINVAL; |
91 | } |
92 | |
93 | static const struct mtd_ooblayout_ops pn26g0xa_ooblayout = { |
94 | .ecc = pn26g0xa_ooblayout_ecc, |
95 | .free = pn26g0xa_ooblayout_free, |
96 | }; |
97 | |
98 | |
99 | static const struct spinand_info paragon_spinand_table[] = { |
100 | SPINAND_INFO("PN26G01A" , |
101 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xe1), |
102 | NAND_MEMORG(1, 2048, 128, 64, 1024, 21, 1, 1, 1), |
103 | NAND_ECCREQ(8, 512), |
104 | SPINAND_INFO_OP_VARIANTS(&read_cache_variants, |
105 | &write_cache_variants, |
106 | &update_cache_variants), |
107 | 0, |
108 | SPINAND_ECCINFO(&pn26g0xa_ooblayout, |
109 | pn26g0xa_ecc_get_status)), |
110 | SPINAND_INFO("PN26G02A" , |
111 | SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xe2), |
112 | NAND_MEMORG(1, 2048, 128, 64, 2048, 41, 1, 1, 1), |
113 | NAND_ECCREQ(8, 512), |
114 | SPINAND_INFO_OP_VARIANTS(&read_cache_variants, |
115 | &write_cache_variants, |
116 | &update_cache_variants), |
117 | 0, |
118 | SPINAND_ECCINFO(&pn26g0xa_ooblayout, |
119 | pn26g0xa_ecc_get_status)), |
120 | }; |
121 | |
122 | static const struct spinand_manufacturer_ops paragon_spinand_manuf_ops = { |
123 | }; |
124 | |
125 | const struct spinand_manufacturer paragon_spinand_manufacturer = { |
126 | .id = SPINAND_MFR_PARAGON, |
127 | .name = "Paragon" , |
128 | .chips = paragon_spinand_table, |
129 | .nchips = ARRAY_SIZE(paragon_spinand_table), |
130 | .ops = ¶gon_spinand_manuf_ops, |
131 | }; |
132 | |