xref: /linux/drivers/mtd/nand/spi/heyangtek.c (revision df51bdc5e80dae43cab81f6cc641e98638303c88)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Authors:
4  *	Andrey Zolotarev <andrey.zolotarev@keenetic.com> - the main driver logic
5  *	Aleksei Sviridkin <f@lex.la> - adaptation to the mainline Linux kernel
6  *
7  * Based on:
8  *	https://github.com/keenetic/kernel-49/commit/bacade569fb12bc0ad31ba09bca9b890118fbca7
9  */
10 
11 #include <linux/device.h>
12 #include <linux/kernel.h>
13 #include <linux/mtd/spinand.h>
14 
15 #define SPINAND_MFR_HEYANGTEK			0xc9
16 
17 #define HYF1GQ4_STATUS_ECC_LIMIT_BITFLIPS	(3 << 4)
18 
19 static SPINAND_OP_VARIANTS(read_cache_variants,
20 		SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 1, NULL, 0, 0),
21 		SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
22 		SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
23 		SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
24 		SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
25 		SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
26 
27 static SPINAND_OP_VARIANTS(write_cache_variants,
28 		SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
29 		SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
30 
31 static SPINAND_OP_VARIANTS(update_cache_variants,
32 		SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
33 		SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
34 
35 /*
36  * HYF1GQ4UDACAE is a GD5F1GQ4-compatible die, so the OOB layout is taken
37  * from gd5fxgq4xa: the on-die ECC parity occupies bytes 8..15 of each
38  * 16-byte section, the bad block marker sits in byte 0 and the remaining
39  * bytes are exposed as free.
40  */
41 static int hyf1gq4_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 = (16 * section) + 8;
48 	region->length = 8;
49 
50 	return 0;
51 }
52 
53 static int hyf1gq4_ooblayout_free(struct mtd_info *mtd, int section,
54 				  struct mtd_oob_region *region)
55 {
56 	if (section > 3)
57 		return -ERANGE;
58 
59 	if (section) {
60 		region->offset = 16 * section;
61 		region->length = 8;
62 	} else {
63 		/* section 0 has one byte reserved for the bad block marker */
64 		region->offset = 1;
65 		region->length = 7;
66 	}
67 
68 	return 0;
69 }
70 
71 static const struct mtd_ooblayout_ops hyf1gq4_ooblayout = {
72 	.ecc = hyf1gq4_ooblayout_ecc,
73 	.free = hyf1gq4_ooblayout_free,
74 };
75 
76 static int hyf1gq4_ecc_get_status(struct spinand_device *spinand, u8 status)
77 {
78 	struct nand_device *nand = spinand_to_nand(spinand);
79 
80 	switch (status & STATUS_ECC_MASK) {
81 	case STATUS_ECC_NO_BITFLIPS:
82 		return 0;
83 
84 	case STATUS_ECC_UNCOR_ERROR:
85 		return -EBADMSG;
86 
87 	case STATUS_ECC_HAS_BITFLIPS:
88 		/*
89 		 * The die exposes only a coarse 2-bit ECC status and has no
90 		 * register for the exact bitflip count. This code means
91 		 * "corrected, below the refresh threshold", so report half of
92 		 * the ECC strength as a representative value.
93 		 */
94 		return nanddev_get_ecc_conf(nand)->strength / 2;
95 
96 	case HYF1GQ4_STATUS_ECC_LIMIT_BITFLIPS:
97 		/*
98 		 * "Corrected, refresh recommended": report the full ECC
99 		 * strength so the upper layers relocate the data.
100 		 */
101 		return nanddev_get_ecc_conf(nand)->strength;
102 
103 	default:
104 		break;
105 	}
106 
107 	return -EINVAL;
108 }
109 
110 static const struct spinand_info heyangtek_spinand_table[] = {
111 	SPINAND_INFO("HYF1GQ4UDACAE",
112 		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x21),
113 		     NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
114 		     NAND_ECCREQ(4, 512),
115 		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
116 					      &write_cache_variants,
117 					      &update_cache_variants),
118 		     SPINAND_HAS_QE_BIT,
119 		     SPINAND_ECCINFO(&hyf1gq4_ooblayout,
120 				     hyf1gq4_ecc_get_status)),
121 };
122 
123 static const struct spinand_manufacturer_ops heyangtek_spinand_manuf_ops = {
124 };
125 
126 const struct spinand_manufacturer heyangtek_spinand_manufacturer = {
127 	.id = SPINAND_MFR_HEYANGTEK,
128 	.name = "HeYangTek",
129 	.chips = heyangtek_spinand_table,
130 	.nchips = ARRAY_SIZE(heyangtek_spinand_table),
131 	.ops = &heyangtek_spinand_manuf_ops,
132 };
133