1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Overview: |
4 | * Platform independent driver for NDFC (NanD Flash Controller) |
5 | * integrated into EP440 cores |
6 | * |
7 | * Ported to an OF platform driver by Sean MacLennan |
8 | * |
9 | * The NDFC supports multiple chips, but this driver only supports a |
10 | * single chip since I do not have access to any boards with |
11 | * multiple chips. |
12 | * |
13 | * Author: Thomas Gleixner |
14 | * |
15 | * Copyright 2006 IBM |
16 | * Copyright 2008 PIKA Technologies |
17 | * Sean MacLennan <smaclennan@pikatech.com> |
18 | */ |
19 | #include <linux/module.h> |
20 | #include <linux/mtd/rawnand.h> |
21 | #include <linux/mtd/partitions.h> |
22 | #include <linux/mtd/ndfc.h> |
23 | #include <linux/slab.h> |
24 | #include <linux/mtd/mtd.h> |
25 | #include <linux/of.h> |
26 | #include <linux/of_address.h> |
27 | #include <linux/platform_device.h> |
28 | #include <asm/io.h> |
29 | |
30 | #define NDFC_MAX_CS 4 |
31 | |
32 | struct ndfc_controller { |
33 | struct platform_device *ofdev; |
34 | void __iomem *ndfcbase; |
35 | struct nand_chip chip; |
36 | int chip_select; |
37 | struct nand_controller ndfc_control; |
38 | }; |
39 | |
40 | static struct ndfc_controller ndfc_ctrl[NDFC_MAX_CS]; |
41 | |
42 | static void ndfc_select_chip(struct nand_chip *nchip, int chip) |
43 | { |
44 | uint32_t ccr; |
45 | struct ndfc_controller *ndfc = nand_get_controller_data(chip: nchip); |
46 | |
47 | ccr = in_be32(ndfc->ndfcbase + NDFC_CCR); |
48 | if (chip >= 0) { |
49 | ccr &= ~NDFC_CCR_BS_MASK; |
50 | ccr |= NDFC_CCR_BS(chip + ndfc->chip_select); |
51 | } else |
52 | ccr |= NDFC_CCR_RESET_CE; |
53 | out_be32(ndfc->ndfcbase + NDFC_CCR, ccr); |
54 | } |
55 | |
56 | static void ndfc_hwcontrol(struct nand_chip *chip, int cmd, unsigned int ctrl) |
57 | { |
58 | struct ndfc_controller *ndfc = nand_get_controller_data(chip); |
59 | |
60 | if (cmd == NAND_CMD_NONE) |
61 | return; |
62 | |
63 | if (ctrl & NAND_CLE) |
64 | writel(val: cmd & 0xFF, addr: ndfc->ndfcbase + NDFC_CMD); |
65 | else |
66 | writel(val: cmd & 0xFF, addr: ndfc->ndfcbase + NDFC_ALE); |
67 | } |
68 | |
69 | static int ndfc_ready(struct nand_chip *chip) |
70 | { |
71 | struct ndfc_controller *ndfc = nand_get_controller_data(chip); |
72 | |
73 | return in_be32(ndfc->ndfcbase + NDFC_STAT) & NDFC_STAT_IS_READY; |
74 | } |
75 | |
76 | static void ndfc_enable_hwecc(struct nand_chip *chip, int mode) |
77 | { |
78 | uint32_t ccr; |
79 | struct ndfc_controller *ndfc = nand_get_controller_data(chip); |
80 | |
81 | ccr = in_be32(ndfc->ndfcbase + NDFC_CCR); |
82 | ccr |= NDFC_CCR_RESET_ECC; |
83 | out_be32(ndfc->ndfcbase + NDFC_CCR, ccr); |
84 | wmb(); |
85 | } |
86 | |
87 | static int ndfc_calculate_ecc(struct nand_chip *chip, |
88 | const u_char *dat, u_char *ecc_code) |
89 | { |
90 | struct ndfc_controller *ndfc = nand_get_controller_data(chip); |
91 | uint32_t ecc; |
92 | uint8_t *p = (uint8_t *)&ecc; |
93 | |
94 | wmb(); |
95 | ecc = in_be32(ndfc->ndfcbase + NDFC_ECC); |
96 | /* The NDFC uses Smart Media (SMC) bytes order */ |
97 | ecc_code[0] = p[1]; |
98 | ecc_code[1] = p[2]; |
99 | ecc_code[2] = p[3]; |
100 | |
101 | return 0; |
102 | } |
103 | |
104 | /* |
105 | * Speedups for buffer read/write/verify |
106 | * |
107 | * NDFC allows 32bit read/write of data. So we can speed up the buffer |
108 | * functions. No further checking, as nand_base will always read/write |
109 | * page aligned. |
110 | */ |
111 | static void ndfc_read_buf(struct nand_chip *chip, uint8_t *buf, int len) |
112 | { |
113 | struct ndfc_controller *ndfc = nand_get_controller_data(chip); |
114 | uint32_t *p = (uint32_t *) buf; |
115 | |
116 | for(;len > 0; len -= 4) |
117 | *p++ = in_be32(ndfc->ndfcbase + NDFC_DATA); |
118 | } |
119 | |
120 | static void ndfc_write_buf(struct nand_chip *chip, const uint8_t *buf, int len) |
121 | { |
122 | struct ndfc_controller *ndfc = nand_get_controller_data(chip); |
123 | uint32_t *p = (uint32_t *) buf; |
124 | |
125 | for(;len > 0; len -= 4) |
126 | out_be32(ndfc->ndfcbase + NDFC_DATA, *p++); |
127 | } |
128 | |
129 | /* |
130 | * Initialize chip structure |
131 | */ |
132 | static int ndfc_chip_init(struct ndfc_controller *ndfc, |
133 | struct device_node *node) |
134 | { |
135 | struct device_node *flash_np; |
136 | struct nand_chip *chip = &ndfc->chip; |
137 | struct mtd_info *mtd = nand_to_mtd(chip); |
138 | int ret; |
139 | |
140 | chip->legacy.IO_ADDR_R = ndfc->ndfcbase + NDFC_DATA; |
141 | chip->legacy.IO_ADDR_W = ndfc->ndfcbase + NDFC_DATA; |
142 | chip->legacy.cmd_ctrl = ndfc_hwcontrol; |
143 | chip->legacy.dev_ready = ndfc_ready; |
144 | chip->legacy.select_chip = ndfc_select_chip; |
145 | chip->legacy.chip_delay = 50; |
146 | chip->controller = &ndfc->ndfc_control; |
147 | chip->legacy.read_buf = ndfc_read_buf; |
148 | chip->legacy.write_buf = ndfc_write_buf; |
149 | chip->ecc.correct = rawnand_sw_hamming_correct; |
150 | chip->ecc.hwctl = ndfc_enable_hwecc; |
151 | chip->ecc.calculate = ndfc_calculate_ecc; |
152 | chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; |
153 | chip->ecc.size = 256; |
154 | chip->ecc.bytes = 3; |
155 | chip->ecc.strength = 1; |
156 | nand_set_controller_data(chip, priv: ndfc); |
157 | |
158 | mtd->dev.parent = &ndfc->ofdev->dev; |
159 | |
160 | flash_np = of_get_next_child(node, NULL); |
161 | if (!flash_np) |
162 | return -ENODEV; |
163 | nand_set_flash_node(chip, np: flash_np); |
164 | |
165 | mtd->name = kasprintf(GFP_KERNEL, fmt: "%s.%pOFn" , dev_name(dev: &ndfc->ofdev->dev), |
166 | flash_np); |
167 | if (!mtd->name) { |
168 | ret = -ENOMEM; |
169 | goto err; |
170 | } |
171 | |
172 | ret = nand_scan(chip, max_chips: 1); |
173 | if (ret) |
174 | goto err; |
175 | |
176 | ret = mtd_device_register(mtd, NULL, 0); |
177 | |
178 | err: |
179 | of_node_put(node: flash_np); |
180 | if (ret) |
181 | kfree(objp: mtd->name); |
182 | return ret; |
183 | } |
184 | |
185 | static int ndfc_probe(struct platform_device *ofdev) |
186 | { |
187 | struct ndfc_controller *ndfc; |
188 | const __be32 *reg; |
189 | u32 ccr; |
190 | u32 cs; |
191 | int err, len; |
192 | |
193 | /* Read the reg property to get the chip select */ |
194 | reg = of_get_property(node: ofdev->dev.of_node, name: "reg" , lenp: &len); |
195 | if (reg == NULL || len != 12) { |
196 | dev_err(&ofdev->dev, "unable read reg property (%d)\n" , len); |
197 | return -ENOENT; |
198 | } |
199 | |
200 | cs = be32_to_cpu(reg[0]); |
201 | if (cs >= NDFC_MAX_CS) { |
202 | dev_err(&ofdev->dev, "invalid CS number (%d)\n" , cs); |
203 | return -EINVAL; |
204 | } |
205 | |
206 | ndfc = &ndfc_ctrl[cs]; |
207 | ndfc->chip_select = cs; |
208 | |
209 | nand_controller_init(nfc: &ndfc->ndfc_control); |
210 | ndfc->ofdev = ofdev; |
211 | dev_set_drvdata(dev: &ofdev->dev, data: ndfc); |
212 | |
213 | ndfc->ndfcbase = of_iomap(node: ofdev->dev.of_node, index: 0); |
214 | if (!ndfc->ndfcbase) { |
215 | dev_err(&ofdev->dev, "failed to get memory\n" ); |
216 | return -EIO; |
217 | } |
218 | |
219 | ccr = NDFC_CCR_BS(ndfc->chip_select); |
220 | |
221 | /* It is ok if ccr does not exist - just default to 0 */ |
222 | reg = of_get_property(node: ofdev->dev.of_node, name: "ccr" , NULL); |
223 | if (reg) |
224 | ccr |= be32_to_cpup(p: reg); |
225 | |
226 | out_be32(ndfc->ndfcbase + NDFC_CCR, ccr); |
227 | |
228 | /* Set the bank settings if given */ |
229 | reg = of_get_property(node: ofdev->dev.of_node, name: "bank-settings" , NULL); |
230 | if (reg) { |
231 | int offset = NDFC_BCFG0 + (ndfc->chip_select << 2); |
232 | out_be32(ndfc->ndfcbase + offset, be32_to_cpup(p: reg)); |
233 | } |
234 | |
235 | err = ndfc_chip_init(ndfc, node: ofdev->dev.of_node); |
236 | if (err) { |
237 | iounmap(addr: ndfc->ndfcbase); |
238 | return err; |
239 | } |
240 | |
241 | return 0; |
242 | } |
243 | |
244 | static void ndfc_remove(struct platform_device *ofdev) |
245 | { |
246 | struct ndfc_controller *ndfc = dev_get_drvdata(dev: &ofdev->dev); |
247 | struct nand_chip *chip = &ndfc->chip; |
248 | struct mtd_info *mtd = nand_to_mtd(chip); |
249 | int ret; |
250 | |
251 | ret = mtd_device_unregister(master: mtd); |
252 | WARN_ON(ret); |
253 | nand_cleanup(chip); |
254 | kfree(objp: mtd->name); |
255 | } |
256 | |
257 | static const struct of_device_id ndfc_match[] = { |
258 | { .compatible = "ibm,ndfc" , }, |
259 | {} |
260 | }; |
261 | MODULE_DEVICE_TABLE(of, ndfc_match); |
262 | |
263 | static struct platform_driver ndfc_driver = { |
264 | .driver = { |
265 | .name = "ndfc" , |
266 | .of_match_table = ndfc_match, |
267 | }, |
268 | .probe = ndfc_probe, |
269 | .remove_new = ndfc_remove, |
270 | }; |
271 | |
272 | module_platform_driver(ndfc_driver); |
273 | |
274 | MODULE_LICENSE("GPL" ); |
275 | MODULE_AUTHOR("Thomas Gleixner <tglx@linutronix.de>" ); |
276 | MODULE_DESCRIPTION("OF Platform driver for NDFC" ); |
277 | |