blob: dd4ed257a836d88196d1829b616dc2f1f7c894ba [file] [log] [blame]
Frieder Schrempfea4d7c82018-08-16 17:30:14 +02001// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (c) 2017 exceet electronics GmbH
4 *
5 * Authors:
6 * Frieder Schrempf <frieder.schrempf@exceet.de>
7 * Boris Brezillon <boris.brezillon@bootlin.com>
8 */
9
10#ifndef __UBOOT__
11#include <linux/device.h>
12#include <linux/kernel.h>
13#endif
Mikhail Kshevetskiy165b02dc2023-01-10 12:58:42 +010014#include <linux/bug.h>
Frieder Schrempfea4d7c82018-08-16 17:30:14 +020015#include <linux/mtd/spinand.h>
16
17#define SPINAND_MFR_WINBOND 0xEF
18
19#define WINBOND_CFG_BUF_READ BIT(3)
20
21static SPINAND_OP_VARIANTS(read_cache_variants,
22 SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0),
23 SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
24 SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
25 SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
26 SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
27 SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
28
29static SPINAND_OP_VARIANTS(write_cache_variants,
30 SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
31 SPINAND_PROG_LOAD(true, 0, NULL, 0));
32
33static SPINAND_OP_VARIANTS(update_cache_variants,
34 SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
35 SPINAND_PROG_LOAD(false, 0, NULL, 0));
36
37static int w25m02gv_ooblayout_ecc(struct mtd_info *mtd, int section,
38 struct mtd_oob_region *region)
39{
40 if (section > 3)
41 return -ERANGE;
42
43 region->offset = (16 * section) + 8;
44 region->length = 8;
45
46 return 0;
47}
48
49static int w25m02gv_ooblayout_free(struct mtd_info *mtd, int section,
50 struct mtd_oob_region *region)
51{
52 if (section > 3)
53 return -ERANGE;
54
55 region->offset = (16 * section) + 2;
56 region->length = 6;
57
58 return 0;
59}
60
61static const struct mtd_ooblayout_ops w25m02gv_ooblayout = {
62 .ecc = w25m02gv_ooblayout_ecc,
Simon Glass62fd1a42020-02-03 07:35:56 -070063 .rfree = w25m02gv_ooblayout_free,
Frieder Schrempfea4d7c82018-08-16 17:30:14 +020064};
65
66static int w25m02gv_select_target(struct spinand_device *spinand,
67 unsigned int target)
68{
69 struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0xc2, 1),
70 SPI_MEM_OP_NO_ADDR,
71 SPI_MEM_OP_NO_DUMMY,
72 SPI_MEM_OP_DATA_OUT(1,
73 spinand->scratchbuf,
74 1));
75
76 *spinand->scratchbuf = target;
77 return spi_mem_exec_op(spinand->slave, &op);
78}
79
Mikhail Kshevetskiy165b02dc2023-01-10 12:58:42 +010080static int w25n02kv_ooblayout_ecc(struct mtd_info *mtd, int section,
81 struct mtd_oob_region *region)
82{
83 if (section > 3)
84 return -ERANGE;
85
86 region->offset = 64 + (16 * section);
87 region->length = 13;
88
89 return 0;
90}
91
92static int w25n02kv_ooblayout_free(struct mtd_info *mtd, int section,
93 struct mtd_oob_region *region)
94{
95 if (section > 3)
96 return -ERANGE;
97
98 region->offset = (16 * section) + 2;
99 region->length = 14;
100
101 return 0;
102}
103
104static const struct mtd_ooblayout_ops w25n02kv_ooblayout = {
105 .ecc = w25n02kv_ooblayout_ecc,
106 .rfree = w25n02kv_ooblayout_free,
107};
108
109static int w25n02kv_ecc_get_status(struct spinand_device *spinand,
110 u8 status)
111{
112 struct nand_device *nand = spinand_to_nand(spinand);
113 u8 mbf = 0;
114 struct spi_mem_op op = SPINAND_GET_FEATURE_OP(0x30, &mbf);
115
116 switch (status & STATUS_ECC_MASK) {
117 case STATUS_ECC_NO_BITFLIPS:
118 return 0;
119
120 case STATUS_ECC_UNCOR_ERROR:
121 return -EBADMSG;
122
123 case STATUS_ECC_HAS_BITFLIPS:
124 /*
125 * Let's try to retrieve the real maximum number of bitflips
126 * in order to avoid forcing the wear-leveling layer to move
127 * data around if it's not necessary.
128 */
129 if (spi_mem_exec_op(spinand->slave, &op))
130 return nand->eccreq.strength;
131
132 mbf >>= 4;
133
134 if (WARN_ON(mbf > nand->eccreq.strength || !mbf))
135 return nand->eccreq.strength;
136
137 return mbf;
138
139 default:
140 break;
141 }
142
143 return -EINVAL;
144}
145
Frieder Schrempfea4d7c82018-08-16 17:30:14 +0200146static const struct spinand_info winbond_spinand_table[] = {
Mikhail Kshevetskiy72010312023-01-10 12:58:38 +0100147 SPINAND_INFO("W25M02GV",
Mikhail Kshevetskiy8f17a9a2023-01-10 12:58:41 +0100148 SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xab, 0x21),
Mikhail Kshevetskiy2a1e78b2023-01-10 12:58:40 +0100149 NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 2),
Frieder Schrempfea4d7c82018-08-16 17:30:14 +0200150 NAND_ECCREQ(1, 512),
151 SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
152 &write_cache_variants,
153 &update_cache_variants),
154 0,
155 SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL),
156 SPINAND_SELECT_TARGET(w25m02gv_select_target)),
Mikhail Kshevetskiy72010312023-01-10 12:58:38 +0100157 SPINAND_INFO("W25N01GV",
Mikhail Kshevetskiy8f17a9a2023-01-10 12:58:41 +0100158 SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x21),
Mikhail Kshevetskiy2a1e78b2023-01-10 12:58:40 +0100159 NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
Robert Markoa2ee9162020-01-16 14:03:35 +0100160 NAND_ECCREQ(1, 512),
161 SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
162 &write_cache_variants,
163 &update_cache_variants),
164 0,
165 SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL)),
Mikhail Kshevetskiy165b02dc2023-01-10 12:58:42 +0100166 SPINAND_INFO("W25N02KV",
167 SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x22),
168 NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
169 NAND_ECCREQ(8, 512),
170 SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
171 &write_cache_variants,
172 &update_cache_variants),
173 0,
174 SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
Frieder Schrempfea4d7c82018-08-16 17:30:14 +0200175};
176
Frieder Schrempfea4d7c82018-08-16 17:30:14 +0200177static int winbond_spinand_init(struct spinand_device *spinand)
178{
179 struct nand_device *nand = spinand_to_nand(spinand);
180 unsigned int i;
181
182 /*
183 * Make sure all dies are in buffer read mode and not continuous read
184 * mode.
185 */
186 for (i = 0; i < nand->memorg.ntargets; i++) {
187 spinand_select_target(spinand, i);
188 spinand_upd_cfg(spinand, WINBOND_CFG_BUF_READ,
189 WINBOND_CFG_BUF_READ);
190 }
191
192 return 0;
193}
194
195static const struct spinand_manufacturer_ops winbond_spinand_manuf_ops = {
Frieder Schrempfea4d7c82018-08-16 17:30:14 +0200196 .init = winbond_spinand_init,
197};
198
199const struct spinand_manufacturer winbond_spinand_manufacturer = {
200 .id = SPINAND_MFR_WINBOND,
201 .name = "Winbond",
Mikhail Kshevetskiy72010312023-01-10 12:58:38 +0100202 .chips = winbond_spinand_table,
203 .nchips = ARRAY_SIZE(winbond_spinand_table),
Frieder Schrempfea4d7c82018-08-16 17:30:14 +0200204 .ops = &winbond_spinand_manuf_ops,
205};