xref: /linux/drivers/mtd/nand/spi/gigadevice.c (revision ebf68996de0ab250c5d520eb2291ab65643e9a1e)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Author:
4  *	Chuanhong Guo <gch981213@gmail.com>
5  */
6 
7 #include <linux/device.h>
8 #include <linux/kernel.h>
9 #include <linux/mtd/spinand.h>
10 
11 #define SPINAND_MFR_GIGADEVICE			0xC8
12 #define GD5FXGQ4XA_STATUS_ECC_1_7_BITFLIPS	(1 << 4)
13 #define GD5FXGQ4XA_STATUS_ECC_8_BITFLIPS	(3 << 4)
14 
15 #define GD5FXGQ4UEXXG_REG_STATUS2		0xf0
16 
17 static SPINAND_OP_VARIANTS(read_cache_variants,
18 		SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0),
19 		SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
20 		SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
21 		SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
22 		SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
23 		SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
24 
25 static SPINAND_OP_VARIANTS(write_cache_variants,
26 		SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
27 		SPINAND_PROG_LOAD(true, 0, NULL, 0));
28 
29 static SPINAND_OP_VARIANTS(update_cache_variants,
30 		SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
31 		SPINAND_PROG_LOAD(false, 0, NULL, 0));
32 
33 static int gd5fxgq4xa_ooblayout_ecc(struct mtd_info *mtd, int section,
34 				  struct mtd_oob_region *region)
35 {
36 	if (section > 3)
37 		return -ERANGE;
38 
39 	region->offset = (16 * section) + 8;
40 	region->length = 8;
41 
42 	return 0;
43 }
44 
45 static int gd5fxgq4xa_ooblayout_free(struct mtd_info *mtd, int section,
46 				   struct mtd_oob_region *region)
47 {
48 	if (section > 3)
49 		return -ERANGE;
50 
51 	if (section) {
52 		region->offset = 16 * section;
53 		region->length = 8;
54 	} else {
55 		/* section 0 has one byte reserved for bad block mark */
56 		region->offset = 1;
57 		region->length = 7;
58 	}
59 	return 0;
60 }
61 
62 static int gd5fxgq4xa_ecc_get_status(struct spinand_device *spinand,
63 					 u8 status)
64 {
65 	switch (status & STATUS_ECC_MASK) {
66 	case STATUS_ECC_NO_BITFLIPS:
67 		return 0;
68 
69 	case GD5FXGQ4XA_STATUS_ECC_1_7_BITFLIPS:
70 		/* 1-7 bits are flipped. return the maximum. */
71 		return 7;
72 
73 	case GD5FXGQ4XA_STATUS_ECC_8_BITFLIPS:
74 		return 8;
75 
76 	case STATUS_ECC_UNCOR_ERROR:
77 		return -EBADMSG;
78 
79 	default:
80 		break;
81 	}
82 
83 	return -EINVAL;
84 }
85 
86 static int gd5fxgq4uexxg_ooblayout_ecc(struct mtd_info *mtd, int section,
87 				       struct mtd_oob_region *region)
88 {
89 	if (section)
90 		return -ERANGE;
91 
92 	region->offset = 64;
93 	region->length = 64;
94 
95 	return 0;
96 }
97 
98 static int gd5fxgq4uexxg_ooblayout_free(struct mtd_info *mtd, int section,
99 					struct mtd_oob_region *region)
100 {
101 	if (section)
102 		return -ERANGE;
103 
104 	/* Reserve 1 bytes for the BBM. */
105 	region->offset = 1;
106 	region->length = 63;
107 
108 	return 0;
109 }
110 
111 static int gd5fxgq4uexxg_ecc_get_status(struct spinand_device *spinand,
112 					u8 status)
113 {
114 	u8 status2;
115 	struct spi_mem_op op = SPINAND_GET_FEATURE_OP(GD5FXGQ4UEXXG_REG_STATUS2,
116 						      &status2);
117 	int ret;
118 
119 	switch (status & STATUS_ECC_MASK) {
120 	case STATUS_ECC_NO_BITFLIPS:
121 		return 0;
122 
123 	case GD5FXGQ4XA_STATUS_ECC_1_7_BITFLIPS:
124 		/*
125 		 * Read status2 register to determine a more fine grained
126 		 * bit error status
127 		 */
128 		ret = spi_mem_exec_op(spinand->spimem, &op);
129 		if (ret)
130 			return ret;
131 
132 		/*
133 		 * 4 ... 7 bits are flipped (1..4 can't be detected, so
134 		 * report the maximum of 4 in this case
135 		 */
136 		/* bits sorted this way (3...0): ECCS1,ECCS0,ECCSE1,ECCSE0 */
137 		return ((status & STATUS_ECC_MASK) >> 2) |
138 			((status2 & STATUS_ECC_MASK) >> 4);
139 
140 	case GD5FXGQ4XA_STATUS_ECC_8_BITFLIPS:
141 		return 8;
142 
143 	case STATUS_ECC_UNCOR_ERROR:
144 		return -EBADMSG;
145 
146 	default:
147 		break;
148 	}
149 
150 	return -EINVAL;
151 }
152 
153 static const struct mtd_ooblayout_ops gd5fxgq4xa_ooblayout = {
154 	.ecc = gd5fxgq4xa_ooblayout_ecc,
155 	.free = gd5fxgq4xa_ooblayout_free,
156 };
157 
158 static const struct mtd_ooblayout_ops gd5fxgq4uexxg_ooblayout = {
159 	.ecc = gd5fxgq4uexxg_ooblayout_ecc,
160 	.free = gd5fxgq4uexxg_ooblayout_free,
161 };
162 
163 static const struct spinand_info gigadevice_spinand_table[] = {
164 	SPINAND_INFO("GD5F1GQ4xA", 0xF1,
165 		     NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
166 		     NAND_ECCREQ(8, 512),
167 		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
168 					      &write_cache_variants,
169 					      &update_cache_variants),
170 		     0,
171 		     SPINAND_ECCINFO(&gd5fxgq4xa_ooblayout,
172 				     gd5fxgq4xa_ecc_get_status)),
173 	SPINAND_INFO("GD5F2GQ4xA", 0xF2,
174 		     NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1),
175 		     NAND_ECCREQ(8, 512),
176 		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
177 					      &write_cache_variants,
178 					      &update_cache_variants),
179 		     0,
180 		     SPINAND_ECCINFO(&gd5fxgq4xa_ooblayout,
181 				     gd5fxgq4xa_ecc_get_status)),
182 	SPINAND_INFO("GD5F4GQ4xA", 0xF4,
183 		     NAND_MEMORG(1, 2048, 64, 64, 4096, 40, 1, 1, 1),
184 		     NAND_ECCREQ(8, 512),
185 		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
186 					      &write_cache_variants,
187 					      &update_cache_variants),
188 		     0,
189 		     SPINAND_ECCINFO(&gd5fxgq4xa_ooblayout,
190 				     gd5fxgq4xa_ecc_get_status)),
191 	SPINAND_INFO("GD5F1GQ4UExxG", 0xd1,
192 		     NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
193 		     NAND_ECCREQ(8, 512),
194 		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
195 					      &write_cache_variants,
196 					      &update_cache_variants),
197 		     0,
198 		     SPINAND_ECCINFO(&gd5fxgq4uexxg_ooblayout,
199 				     gd5fxgq4uexxg_ecc_get_status)),
200 };
201 
202 static int gigadevice_spinand_detect(struct spinand_device *spinand)
203 {
204 	u8 *id = spinand->id.data;
205 	int ret;
206 
207 	/*
208 	 * For GD NANDs, There is an address byte needed to shift in before IDs
209 	 * are read out, so the first byte in raw_id is dummy.
210 	 */
211 	if (id[1] != SPINAND_MFR_GIGADEVICE)
212 		return 0;
213 
214 	ret = spinand_match_and_init(spinand, gigadevice_spinand_table,
215 				     ARRAY_SIZE(gigadevice_spinand_table),
216 				     id[2]);
217 	if (ret)
218 		return ret;
219 
220 	return 1;
221 }
222 
223 static const struct spinand_manufacturer_ops gigadevice_spinand_manuf_ops = {
224 	.detect = gigadevice_spinand_detect,
225 };
226 
227 const struct spinand_manufacturer gigadevice_spinand_manufacturer = {
228 	.id = SPINAND_MFR_GIGADEVICE,
229 	.name = "GigaDevice",
230 	.ops = &gigadevice_spinand_manuf_ops,
231 };
232