1 From 862bdedd7f4b8aebf00fdb422062e64896e97809 Mon Sep 17 00:00:00 2001
2 From: Christian Marangi <ansuelsmth@gmail.com>
3 Date: Thu, 16 Jun 2022 02:18:34 +0200
4 Subject: [PATCH 2/2] mtd: nand: raw: qcom_nandc: add support for unprotected
7 IPQ8064 nand have special pages where a different layout scheme is used.
8 These special page are used by boot partition and on reading them
9 lots of warning are reported about wrong ECC data and if written to
10 results in broken data and not bootable device.
12 The layout scheme used by these special page consist in using 512 bytes
13 as the codeword size (even for the last codeword) while writing to CFG0
14 register. This forces the NAND controller to unprotect the 4 bytes of
17 Since the kernel is unaware of this different layout for these special
18 page, it does try to protect the spare data too during read/write and
19 warn about CRC errors.
21 Add support for this by permitting the user to declare these special
22 pages in dts by declaring offset and size of the partition. The driver
23 internally will convert these value to nand pages.
25 On user read/write the page is checked and if it's a boot page the
26 correct layout is used.
28 Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
29 Reviewed-by: Manivannan Sadhasivam <mani@kernel.org>
30 Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
31 Link: https://lore.kernel.org/linux-mtd/20220616001835.24393-3-ansuelsmth@gmail.com
33 drivers/mtd/nand/raw/qcom_nandc.c | 199 +++++++++++++++++++++++++++++-
34 1 file changed, 194 insertions(+), 5 deletions(-)
36 --- a/drivers/mtd/nand/raw/qcom_nandc.c
37 +++ b/drivers/mtd/nand/raw/qcom_nandc.c
39 #define DISABLE_STATUS_AFTER_WRITE 4
41 #define UD_SIZE_BYTES 9
42 +#define UD_SIZE_BYTES_MASK GENMASK(18, 9)
43 #define ECC_PARITY_SIZE_BYTES_RS 19
44 #define SPARE_SIZE_BYTES 23
45 +#define SPARE_SIZE_BYTES_MASK GENMASK(26, 23)
46 #define NUM_ADDR_CYCLES 27
47 #define STATUS_BFR_READ 30
48 #define SET_RD_MODE_AFTER_STATUS 31
51 #define ECC_PARITY_SIZE_BYTES_BCH 8
52 #define ECC_NUM_DATA_BYTES 16
53 +#define ECC_NUM_DATA_BYTES_MASK GENMASK(25, 16)
54 #define ECC_FORCE_CLK_OPEN 30
56 /* NAND_DEV_CMD1 bits */
57 @@ -431,12 +434,31 @@ struct qcom_nand_controller {
61 + * NAND special boot partitions
63 + * @page_offset: offset of the partition where spare data is not protected
64 + * by ECC (value in pages)
65 + * @page_offset: size of the partition where spare data is not protected
66 + * by ECC (value in pages)
68 +struct qcom_nand_boot_partition {
76 + * @boot_partitions: array of boot partitions where offset and size of the
77 + * boot partitions are stored
79 * @chip: base NAND chip structure
80 * @node: list node to add itself to host_list in
81 * qcom_nand_controller
83 + * @nr_boot_partitions: count of the boot partitions where spare data is not
86 * @cs: chip select value for this chip
87 * @cw_size: the number of bytes in a single step/codeword
88 * of a page, consisting of all data, ecc, spare
89 @@ -455,14 +477,20 @@ struct qcom_nand_controller {
91 * @status: value to be returned if NAND_CMD_STATUS command
93 + * @codeword_fixup: keep track of the current layout used by
94 + * the driver for read/write operation.
95 * @use_ecc: request the controller to use ECC for the
97 * @bch_enabled: flag to tell whether BCH ECC mode is used
99 struct qcom_nand_host {
100 + struct qcom_nand_boot_partition *boot_partitions;
102 struct nand_chip chip;
103 struct list_head node;
105 + int nr_boot_partitions;
110 @@ -480,6 +508,7 @@ struct qcom_nand_host {
114 + bool codeword_fixup;
118 @@ -492,6 +521,7 @@ struct qcom_nand_host {
119 * @is_bam - whether NAND controller is using BAM
120 * @is_qpic - whether NAND CTRL is part of qpic IP
121 * @qpic_v2 - flag to indicate QPIC IP version 2
122 + * @use_codeword_fixup - whether NAND has different layout for boot partitions
124 struct qcom_nandc_props {
126 @@ -499,6 +529,7 @@ struct qcom_nandc_props {
130 + bool use_codeword_fixup;
133 /* Frees the BAM transaction memory */
134 @@ -1708,7 +1739,7 @@ qcom_nandc_read_cw_raw(struct mtd_info *
135 data_size1 = mtd->writesize - host->cw_size * (ecc->steps - 1);
136 oob_size1 = host->bbm_size;
138 - if (qcom_nandc_is_last_cw(ecc, cw)) {
139 + if (qcom_nandc_is_last_cw(ecc, cw) && !host->codeword_fixup) {
140 data_size2 = ecc->size - data_size1 -
141 ((ecc->steps - 1) * 4);
142 oob_size2 = (ecc->steps * 4) + host->ecc_bytes_hw +
143 @@ -1789,7 +1820,7 @@ check_for_erased_page(struct qcom_nand_h
146 for_each_set_bit(cw, &uncorrectable_cws, ecc->steps) {
147 - if (qcom_nandc_is_last_cw(ecc, cw)) {
148 + if (qcom_nandc_is_last_cw(ecc, cw) && !host->codeword_fixup) {
149 data_size = ecc->size - ((ecc->steps - 1) * 4);
150 oob_size = (ecc->steps * 4) + host->ecc_bytes_hw;
152 @@ -1947,7 +1978,7 @@ static int read_page_ecc(struct qcom_nan
153 for (i = 0; i < ecc->steps; i++) {
154 int data_size, oob_size;
156 - if (qcom_nandc_is_last_cw(ecc, i)) {
157 + if (qcom_nandc_is_last_cw(ecc, i) && !host->codeword_fixup) {
158 data_size = ecc->size - ((ecc->steps - 1) << 2);
159 oob_size = (ecc->steps << 2) + host->ecc_bytes_hw +
161 @@ -2044,6 +2075,69 @@ static int copy_last_cw(struct qcom_nand
165 +static bool qcom_nandc_is_boot_partition(struct qcom_nand_host *host, int page)
167 + struct qcom_nand_boot_partition *boot_partition;
172 + * Since the frequent access will be to the non-boot partitions like rootfs,
173 + * optimize the page check by:
175 + * 1. Checking if the page lies after the last boot partition.
176 + * 2. Checking from the boot partition end.
179 + /* First check the last boot partition */
180 + boot_partition = &host->boot_partitions[host->nr_boot_partitions - 1];
181 + start = boot_partition->page_offset;
182 + end = start + boot_partition->page_size;
184 + /* Page is after the last boot partition end. This is NOT a boot partition */
188 + /* Actually check if it's a boot partition */
189 + if (page < end && page >= start)
192 + /* Check the other boot partitions starting from the second-last partition */
193 + for (i = host->nr_boot_partitions - 2; i >= 0; i--) {
194 + boot_partition = &host->boot_partitions[i];
195 + start = boot_partition->page_offset;
196 + end = start + boot_partition->page_size;
198 + if (page < end && page >= start)
205 +static void qcom_nandc_codeword_fixup(struct qcom_nand_host *host, int page)
207 + bool codeword_fixup = qcom_nandc_is_boot_partition(host, page);
209 + /* Skip conf write if we are already in the correct mode */
210 + if (codeword_fixup == host->codeword_fixup)
213 + host->codeword_fixup = codeword_fixup;
215 + host->cw_data = codeword_fixup ? 512 : 516;
216 + host->spare_bytes = host->cw_size - host->ecc_bytes_hw -
217 + host->bbm_size - host->cw_data;
219 + host->cfg0 &= ~(SPARE_SIZE_BYTES_MASK | UD_SIZE_BYTES_MASK);
220 + host->cfg0 |= host->spare_bytes << SPARE_SIZE_BYTES |
221 + host->cw_data << UD_SIZE_BYTES;
223 + host->ecc_bch_cfg &= ~ECC_NUM_DATA_BYTES_MASK;
224 + host->ecc_bch_cfg |= host->cw_data << ECC_NUM_DATA_BYTES;
225 + host->ecc_buf_cfg = (host->cw_data - 1) << NUM_STEPS;
228 /* implements ecc->read_page() */
229 static int qcom_nandc_read_page(struct nand_chip *chip, uint8_t *buf,
230 int oob_required, int page)
231 @@ -2052,6 +2146,9 @@ static int qcom_nandc_read_page(struct n
232 struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
233 u8 *data_buf, *oob_buf = NULL;
235 + if (host->nr_boot_partitions)
236 + qcom_nandc_codeword_fixup(host, page);
238 nand_read_page_op(chip, page, 0, NULL, 0);
240 oob_buf = oob_required ? chip->oob_poi : NULL;
241 @@ -2071,6 +2168,9 @@ static int qcom_nandc_read_page_raw(stru
243 u8 *data_buf = buf, *oob_buf = chip->oob_poi;
245 + if (host->nr_boot_partitions)
246 + qcom_nandc_codeword_fixup(host, page);
248 for (cw = 0; cw < ecc->steps; cw++) {
249 ret = qcom_nandc_read_cw_raw(mtd, chip, data_buf, oob_buf,
251 @@ -2091,6 +2191,9 @@ static int qcom_nandc_read_oob(struct na
252 struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
253 struct nand_ecc_ctrl *ecc = &chip->ecc;
255 + if (host->nr_boot_partitions)
256 + qcom_nandc_codeword_fixup(host, page);
258 clear_read_regs(nandc);
259 clear_bam_transaction(nandc);
261 @@ -2111,6 +2214,9 @@ static int qcom_nandc_write_page(struct
262 u8 *data_buf, *oob_buf;
265 + if (host->nr_boot_partitions)
266 + qcom_nandc_codeword_fixup(host, page);
268 nand_prog_page_begin_op(chip, page, 0, NULL, 0);
270 clear_read_regs(nandc);
271 @@ -2126,7 +2232,7 @@ static int qcom_nandc_write_page(struct
272 for (i = 0; i < ecc->steps; i++) {
273 int data_size, oob_size;
275 - if (qcom_nandc_is_last_cw(ecc, i)) {
276 + if (qcom_nandc_is_last_cw(ecc, i) && !host->codeword_fixup) {
277 data_size = ecc->size - ((ecc->steps - 1) << 2);
278 oob_size = (ecc->steps << 2) + host->ecc_bytes_hw +
280 @@ -2183,6 +2289,9 @@ static int qcom_nandc_write_page_raw(str
281 u8 *data_buf, *oob_buf;
284 + if (host->nr_boot_partitions)
285 + qcom_nandc_codeword_fixup(host, page);
287 nand_prog_page_begin_op(chip, page, 0, NULL, 0);
288 clear_read_regs(nandc);
289 clear_bam_transaction(nandc);
290 @@ -2201,7 +2310,7 @@ static int qcom_nandc_write_page_raw(str
291 data_size1 = mtd->writesize - host->cw_size * (ecc->steps - 1);
292 oob_size1 = host->bbm_size;
294 - if (qcom_nandc_is_last_cw(ecc, i)) {
295 + if (qcom_nandc_is_last_cw(ecc, i) && !host->codeword_fixup) {
296 data_size2 = ecc->size - data_size1 -
297 ((ecc->steps - 1) << 2);
298 oob_size2 = (ecc->steps << 2) + host->ecc_bytes_hw +
299 @@ -2261,6 +2370,9 @@ static int qcom_nandc_write_oob(struct n
300 int data_size, oob_size;
303 + if (host->nr_boot_partitions)
304 + qcom_nandc_codeword_fixup(host, page);
306 host->use_ecc = true;
307 clear_bam_transaction(nandc);
309 @@ -2922,6 +3034,74 @@ static int qcom_nandc_setup(struct qcom_
311 static const char * const probes[] = { "cmdlinepart", "ofpart", "qcomsmem", NULL };
313 +static int qcom_nand_host_parse_boot_partitions(struct qcom_nand_controller *nandc,
314 + struct qcom_nand_host *host,
315 + struct device_node *dn)
317 + struct nand_chip *chip = &host->chip;
318 + struct mtd_info *mtd = nand_to_mtd(chip);
319 + struct qcom_nand_boot_partition *boot_partition;
320 + struct device *dev = nandc->dev;
321 + int partitions_count, i, j, ret;
323 + if (!of_find_property(dn, "qcom,boot-partitions", NULL))
326 + partitions_count = of_property_count_u32_elems(dn, "qcom,boot-partitions");
327 + if (partitions_count <= 0) {
328 + dev_err(dev, "Error parsing boot partition\n");
329 + return partitions_count ? partitions_count : -EINVAL;
332 + host->nr_boot_partitions = partitions_count / 2;
333 + host->boot_partitions = devm_kcalloc(dev, host->nr_boot_partitions,
334 + sizeof(*host->boot_partitions), GFP_KERNEL);
335 + if (!host->boot_partitions) {
336 + host->nr_boot_partitions = 0;
340 + for (i = 0, j = 0; i < host->nr_boot_partitions; i++, j += 2) {
341 + boot_partition = &host->boot_partitions[i];
343 + ret = of_property_read_u32_index(dn, "qcom,boot-partitions", j,
344 + &boot_partition->page_offset);
346 + dev_err(dev, "Error parsing boot partition offset at index %d\n", i);
347 + host->nr_boot_partitions = 0;
351 + if (boot_partition->page_offset % mtd->writesize) {
352 + dev_err(dev, "Boot partition offset not multiple of writesize at index %i\n",
354 + host->nr_boot_partitions = 0;
357 + /* Convert offset to nand pages */
358 + boot_partition->page_offset /= mtd->writesize;
360 + ret = of_property_read_u32_index(dn, "qcom,boot-partitions", j + 1,
361 + &boot_partition->page_size);
363 + dev_err(dev, "Error parsing boot partition size at index %d\n", i);
364 + host->nr_boot_partitions = 0;
368 + if (boot_partition->page_size % mtd->writesize) {
369 + dev_err(dev, "Boot partition size not multiple of writesize at index %i\n",
371 + host->nr_boot_partitions = 0;
374 + /* Convert size to nand pages */
375 + boot_partition->page_size /= mtd->writesize;
381 static int qcom_nand_host_init_and_register(struct qcom_nand_controller *nandc,
382 struct qcom_nand_host *host,
383 struct device_node *dn)
384 @@ -2979,6 +3159,14 @@ static int qcom_nand_host_init_and_regis
388 + if (nandc->props->use_codeword_fixup) {
389 + ret = qcom_nand_host_parse_boot_partitions(nandc, host, dn);
391 + nand_cleanup(chip);
399 @@ -3144,6 +3332,7 @@ static int qcom_nandc_remove(struct plat
400 static const struct qcom_nandc_props ipq806x_nandc_props = {
401 .ecc_modes = (ECC_RS_4BIT | ECC_BCH_8BIT),
403 + .use_codeword_fixup = true,
404 .dev_cmd_reg_start = 0x0,