Commit | Line | Data |
---|---|---|
aaf7ea20 MR |
1 | /* |
2 | * drivers/mtd/nand/gpio.c | |
3 | * | |
4 | * Updated, and converted to generic GPIO based driver by Russell King. | |
5 | * | |
6 | * Written by Ben Dooks <ben@simtec.co.uk> | |
7 | * Based on 2.4 version by Mark Whittaker | |
8 | * | |
9 | * © 2004 Simtec Electronics | |
10 | * | |
11 | * Device driver for NAND connected via GPIO | |
12 | * | |
13 | * This program is free software; you can redistribute it and/or modify | |
14 | * it under the terms of the GNU General Public License version 2 as | |
15 | * published by the Free Software Foundation. | |
16 | * | |
17 | */ | |
18 | ||
19 | #include <linux/kernel.h> | |
20 | #include <linux/init.h> | |
21 | #include <linux/slab.h> | |
22 | #include <linux/module.h> | |
23 | #include <linux/platform_device.h> | |
24 | #include <linux/gpio.h> | |
25 | #include <linux/io.h> | |
26 | #include <linux/mtd/mtd.h> | |
27 | #include <linux/mtd/nand.h> | |
28 | #include <linux/mtd/partitions.h> | |
29 | #include <linux/mtd/nand-gpio.h> | |
775c3220 JI |
30 | #include <linux/of.h> |
31 | #include <linux/of_address.h> | |
32 | #include <linux/of_gpio.h> | |
aaf7ea20 MR |
33 | |
34 | struct gpiomtd { | |
35 | void __iomem *io_sync; | |
36 | struct mtd_info mtd_info; | |
37 | struct nand_chip nand_chip; | |
38 | struct gpio_nand_platdata plat; | |
39 | }; | |
40 | ||
41 | #define gpio_nand_getpriv(x) container_of(x, struct gpiomtd, mtd_info) | |
42 | ||
43 | ||
44 | #ifdef CONFIG_ARM | |
45 | /* gpio_nand_dosync() | |
46 | * | |
47 | * Make sure the GPIO state changes occur in-order with writes to NAND | |
48 | * memory region. | |
49 | * Needed on PXA due to bus-reordering within the SoC itself (see section on | |
50 | * I/O ordering in PXA manual (section 2.3, p35) | |
51 | */ | |
52 | static void gpio_nand_dosync(struct gpiomtd *gpiomtd) | |
53 | { | |
54 | unsigned long tmp; | |
55 | ||
56 | if (gpiomtd->io_sync) { | |
57 | /* | |
58 | * Linux memory barriers don't cater for what's required here. | |
59 | * What's required is what's here - a read from a separate | |
60 | * region with a dependency on that read. | |
61 | */ | |
62 | tmp = readl(gpiomtd->io_sync); | |
63 | asm volatile("mov %1, %0\n" : "=r" (tmp) : "r" (tmp)); | |
64 | } | |
65 | } | |
66 | #else | |
67 | static inline void gpio_nand_dosync(struct gpiomtd *gpiomtd) {} | |
68 | #endif | |
69 | ||
70 | static void gpio_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl) | |
71 | { | |
72 | struct gpiomtd *gpiomtd = gpio_nand_getpriv(mtd); | |
73 | ||
74 | gpio_nand_dosync(gpiomtd); | |
75 | ||
76 | if (ctrl & NAND_CTRL_CHANGE) { | |
77 | gpio_set_value(gpiomtd->plat.gpio_nce, !(ctrl & NAND_NCE)); | |
78 | gpio_set_value(gpiomtd->plat.gpio_cle, !!(ctrl & NAND_CLE)); | |
79 | gpio_set_value(gpiomtd->plat.gpio_ale, !!(ctrl & NAND_ALE)); | |
80 | gpio_nand_dosync(gpiomtd); | |
81 | } | |
82 | if (cmd == NAND_CMD_NONE) | |
83 | return; | |
84 | ||
85 | writeb(cmd, gpiomtd->nand_chip.IO_ADDR_W); | |
86 | gpio_nand_dosync(gpiomtd); | |
87 | } | |
88 | ||
89 | static void gpio_nand_writebuf(struct mtd_info *mtd, const u_char *buf, int len) | |
90 | { | |
91 | struct nand_chip *this = mtd->priv; | |
92 | ||
93 | writesb(this->IO_ADDR_W, buf, len); | |
94 | } | |
95 | ||
96 | static void gpio_nand_readbuf(struct mtd_info *mtd, u_char *buf, int len) | |
97 | { | |
98 | struct nand_chip *this = mtd->priv; | |
99 | ||
100 | readsb(this->IO_ADDR_R, buf, len); | |
101 | } | |
102 | ||
aaf7ea20 MR |
103 | static void gpio_nand_writebuf16(struct mtd_info *mtd, const u_char *buf, |
104 | int len) | |
105 | { | |
106 | struct nand_chip *this = mtd->priv; | |
107 | ||
108 | if (IS_ALIGNED((unsigned long)buf, 2)) { | |
109 | writesw(this->IO_ADDR_W, buf, len>>1); | |
110 | } else { | |
111 | int i; | |
112 | unsigned short *ptr = (unsigned short *)buf; | |
113 | ||
114 | for (i = 0; i < len; i += 2, ptr++) | |
115 | writew(*ptr, this->IO_ADDR_W); | |
116 | } | |
117 | } | |
118 | ||
119 | static void gpio_nand_readbuf16(struct mtd_info *mtd, u_char *buf, int len) | |
120 | { | |
121 | struct nand_chip *this = mtd->priv; | |
122 | ||
123 | if (IS_ALIGNED((unsigned long)buf, 2)) { | |
124 | readsw(this->IO_ADDR_R, buf, len>>1); | |
125 | } else { | |
126 | int i; | |
127 | unsigned short *ptr = (unsigned short *)buf; | |
128 | ||
129 | for (i = 0; i < len; i += 2, ptr++) | |
130 | *ptr = readw(this->IO_ADDR_R); | |
131 | } | |
132 | } | |
133 | ||
aaf7ea20 MR |
134 | static int gpio_nand_devready(struct mtd_info *mtd) |
135 | { | |
136 | struct gpiomtd *gpiomtd = gpio_nand_getpriv(mtd); | |
137 | return gpio_get_value(gpiomtd->plat.gpio_rdy); | |
138 | } | |
139 | ||
775c3220 JI |
140 | #ifdef CONFIG_OF |
141 | static const struct of_device_id gpio_nand_id_table[] = { | |
142 | { .compatible = "gpio-control-nand" }, | |
143 | {} | |
144 | }; | |
145 | MODULE_DEVICE_TABLE(of, gpio_nand_id_table); | |
146 | ||
147 | static int gpio_nand_get_config_of(const struct device *dev, | |
148 | struct gpio_nand_platdata *plat) | |
149 | { | |
150 | u32 val; | |
151 | ||
152 | if (!of_property_read_u32(dev->of_node, "bank-width", &val)) { | |
153 | if (val == 2) { | |
154 | plat->options |= NAND_BUSWIDTH_16; | |
155 | } else if (val != 1) { | |
156 | dev_err(dev, "invalid bank-width %u\n", val); | |
157 | return -EINVAL; | |
158 | } | |
159 | } | |
160 | ||
161 | plat->gpio_rdy = of_get_gpio(dev->of_node, 0); | |
162 | plat->gpio_nce = of_get_gpio(dev->of_node, 1); | |
163 | plat->gpio_ale = of_get_gpio(dev->of_node, 2); | |
164 | plat->gpio_cle = of_get_gpio(dev->of_node, 3); | |
165 | plat->gpio_nwp = of_get_gpio(dev->of_node, 4); | |
166 | ||
167 | if (!of_property_read_u32(dev->of_node, "chip-delay", &val)) | |
168 | plat->chip_delay = val; | |
169 | ||
170 | return 0; | |
171 | } | |
172 | ||
173 | static struct resource *gpio_nand_get_io_sync_of(struct platform_device *pdev) | |
174 | { | |
175 | struct resource *r = devm_kzalloc(&pdev->dev, sizeof(*r), GFP_KERNEL); | |
176 | u64 addr; | |
177 | ||
178 | if (!r || of_property_read_u64(pdev->dev.of_node, | |
179 | "gpio-control-nand,io-sync-reg", &addr)) | |
180 | return NULL; | |
181 | ||
182 | r->start = addr; | |
183 | r->end = r->start + 0x3; | |
184 | r->flags = IORESOURCE_MEM; | |
185 | ||
186 | return r; | |
187 | } | |
188 | #else /* CONFIG_OF */ | |
189 | #define gpio_nand_id_table NULL | |
190 | static inline int gpio_nand_get_config_of(const struct device *dev, | |
191 | struct gpio_nand_platdata *plat) | |
192 | { | |
193 | return -ENOSYS; | |
194 | } | |
195 | ||
196 | static inline struct resource * | |
197 | gpio_nand_get_io_sync_of(struct platform_device *pdev) | |
198 | { | |
199 | return NULL; | |
200 | } | |
201 | #endif /* CONFIG_OF */ | |
202 | ||
203 | static inline int gpio_nand_get_config(const struct device *dev, | |
204 | struct gpio_nand_platdata *plat) | |
205 | { | |
206 | int ret = gpio_nand_get_config_of(dev, plat); | |
207 | ||
208 | if (!ret) | |
209 | return ret; | |
210 | ||
211 | if (dev->platform_data) { | |
212 | memcpy(plat, dev->platform_data, sizeof(*plat)); | |
213 | return 0; | |
214 | } | |
215 | ||
216 | return -EINVAL; | |
217 | } | |
218 | ||
219 | static inline struct resource * | |
220 | gpio_nand_get_io_sync(struct platform_device *pdev) | |
221 | { | |
222 | struct resource *r = gpio_nand_get_io_sync_of(pdev); | |
223 | ||
224 | if (r) | |
225 | return r; | |
226 | ||
227 | return platform_get_resource(pdev, IORESOURCE_MEM, 1); | |
228 | } | |
229 | ||
aaf7ea20 MR |
230 | static int __devexit gpio_nand_remove(struct platform_device *dev) |
231 | { | |
232 | struct gpiomtd *gpiomtd = platform_get_drvdata(dev); | |
233 | struct resource *res; | |
234 | ||
235 | nand_release(&gpiomtd->mtd_info); | |
236 | ||
775c3220 | 237 | res = gpio_nand_get_io_sync(dev); |
aaf7ea20 MR |
238 | iounmap(gpiomtd->io_sync); |
239 | if (res) | |
db5a5ae2 | 240 | release_mem_region(res->start, resource_size(res)); |
aaf7ea20 MR |
241 | |
242 | res = platform_get_resource(dev, IORESOURCE_MEM, 0); | |
243 | iounmap(gpiomtd->nand_chip.IO_ADDR_R); | |
db5a5ae2 | 244 | release_mem_region(res->start, resource_size(res)); |
aaf7ea20 MR |
245 | |
246 | if (gpio_is_valid(gpiomtd->plat.gpio_nwp)) | |
247 | gpio_set_value(gpiomtd->plat.gpio_nwp, 0); | |
248 | gpio_set_value(gpiomtd->plat.gpio_nce, 1); | |
249 | ||
250 | gpio_free(gpiomtd->plat.gpio_cle); | |
251 | gpio_free(gpiomtd->plat.gpio_ale); | |
252 | gpio_free(gpiomtd->plat.gpio_nce); | |
253 | if (gpio_is_valid(gpiomtd->plat.gpio_nwp)) | |
254 | gpio_free(gpiomtd->plat.gpio_nwp); | |
255 | gpio_free(gpiomtd->plat.gpio_rdy); | |
256 | ||
257 | kfree(gpiomtd); | |
258 | ||
259 | return 0; | |
260 | } | |
261 | ||
262 | static void __iomem *request_and_remap(struct resource *res, size_t size, | |
263 | const char *name, int *err) | |
264 | { | |
265 | void __iomem *ptr; | |
266 | ||
db5a5ae2 | 267 | if (!request_mem_region(res->start, resource_size(res), name)) { |
aaf7ea20 MR |
268 | *err = -EBUSY; |
269 | return NULL; | |
270 | } | |
271 | ||
272 | ptr = ioremap(res->start, size); | |
273 | if (!ptr) { | |
db5a5ae2 | 274 | release_mem_region(res->start, resource_size(res)); |
aaf7ea20 MR |
275 | *err = -ENOMEM; |
276 | } | |
277 | return ptr; | |
278 | } | |
279 | ||
280 | static int __devinit gpio_nand_probe(struct platform_device *dev) | |
281 | { | |
282 | struct gpiomtd *gpiomtd; | |
283 | struct nand_chip *this; | |
284 | struct resource *res0, *res1; | |
775c3220 JI |
285 | struct mtd_part_parser_data ppdata = {}; |
286 | int ret = 0; | |
aaf7ea20 | 287 | |
775c3220 | 288 | if (!dev->dev.of_node && !dev->dev.platform_data) |
aaf7ea20 MR |
289 | return -EINVAL; |
290 | ||
291 | res0 = platform_get_resource(dev, IORESOURCE_MEM, 0); | |
292 | if (!res0) | |
293 | return -EINVAL; | |
294 | ||
295 | gpiomtd = kzalloc(sizeof(*gpiomtd), GFP_KERNEL); | |
296 | if (gpiomtd == NULL) { | |
297 | dev_err(&dev->dev, "failed to create NAND MTD\n"); | |
298 | return -ENOMEM; | |
299 | } | |
300 | ||
301 | this = &gpiomtd->nand_chip; | |
302 | this->IO_ADDR_R = request_and_remap(res0, 2, "NAND", &ret); | |
303 | if (!this->IO_ADDR_R) { | |
304 | dev_err(&dev->dev, "unable to map NAND\n"); | |
305 | goto err_map; | |
306 | } | |
307 | ||
775c3220 | 308 | res1 = gpio_nand_get_io_sync(dev); |
aaf7ea20 MR |
309 | if (res1) { |
310 | gpiomtd->io_sync = request_and_remap(res1, 4, "NAND sync", &ret); | |
311 | if (!gpiomtd->io_sync) { | |
312 | dev_err(&dev->dev, "unable to map sync NAND\n"); | |
313 | goto err_sync; | |
314 | } | |
315 | } | |
316 | ||
775c3220 JI |
317 | ret = gpio_nand_get_config(&dev->dev, &gpiomtd->plat); |
318 | if (ret) | |
319 | goto err_nce; | |
aaf7ea20 MR |
320 | |
321 | ret = gpio_request(gpiomtd->plat.gpio_nce, "NAND NCE"); | |
322 | if (ret) | |
323 | goto err_nce; | |
324 | gpio_direction_output(gpiomtd->plat.gpio_nce, 1); | |
325 | if (gpio_is_valid(gpiomtd->plat.gpio_nwp)) { | |
326 | ret = gpio_request(gpiomtd->plat.gpio_nwp, "NAND NWP"); | |
327 | if (ret) | |
328 | goto err_nwp; | |
329 | gpio_direction_output(gpiomtd->plat.gpio_nwp, 1); | |
330 | } | |
331 | ret = gpio_request(gpiomtd->plat.gpio_ale, "NAND ALE"); | |
332 | if (ret) | |
333 | goto err_ale; | |
334 | gpio_direction_output(gpiomtd->plat.gpio_ale, 0); | |
335 | ret = gpio_request(gpiomtd->plat.gpio_cle, "NAND CLE"); | |
336 | if (ret) | |
337 | goto err_cle; | |
338 | gpio_direction_output(gpiomtd->plat.gpio_cle, 0); | |
339 | ret = gpio_request(gpiomtd->plat.gpio_rdy, "NAND RDY"); | |
340 | if (ret) | |
341 | goto err_rdy; | |
342 | gpio_direction_input(gpiomtd->plat.gpio_rdy); | |
343 | ||
344 | ||
345 | this->IO_ADDR_W = this->IO_ADDR_R; | |
346 | this->ecc.mode = NAND_ECC_SOFT; | |
347 | this->options = gpiomtd->plat.options; | |
348 | this->chip_delay = gpiomtd->plat.chip_delay; | |
349 | ||
350 | /* install our routines */ | |
351 | this->cmd_ctrl = gpio_nand_cmd_ctrl; | |
352 | this->dev_ready = gpio_nand_devready; | |
353 | ||
354 | if (this->options & NAND_BUSWIDTH_16) { | |
355 | this->read_buf = gpio_nand_readbuf16; | |
356 | this->write_buf = gpio_nand_writebuf16; | |
aaf7ea20 MR |
357 | } else { |
358 | this->read_buf = gpio_nand_readbuf; | |
359 | this->write_buf = gpio_nand_writebuf; | |
aaf7ea20 MR |
360 | } |
361 | ||
362 | /* set the mtd private data for the nand driver */ | |
363 | gpiomtd->mtd_info.priv = this; | |
364 | gpiomtd->mtd_info.owner = THIS_MODULE; | |
365 | ||
366 | if (nand_scan(&gpiomtd->mtd_info, 1)) { | |
367 | dev_err(&dev->dev, "no nand chips found?\n"); | |
368 | ret = -ENXIO; | |
369 | goto err_wp; | |
370 | } | |
371 | ||
372 | if (gpiomtd->plat.adjust_parts) | |
373 | gpiomtd->plat.adjust_parts(&gpiomtd->plat, | |
374 | gpiomtd->mtd_info.size); | |
375 | ||
775c3220 JI |
376 | ppdata.of_node = dev->dev.of_node; |
377 | ret = mtd_device_parse_register(&gpiomtd->mtd_info, NULL, &ppdata, | |
378 | gpiomtd->plat.parts, | |
379 | gpiomtd->plat.num_parts); | |
380 | if (ret) | |
381 | goto err_wp; | |
aaf7ea20 MR |
382 | platform_set_drvdata(dev, gpiomtd); |
383 | ||
384 | return 0; | |
385 | ||
386 | err_wp: | |
387 | if (gpio_is_valid(gpiomtd->plat.gpio_nwp)) | |
388 | gpio_set_value(gpiomtd->plat.gpio_nwp, 0); | |
389 | gpio_free(gpiomtd->plat.gpio_rdy); | |
390 | err_rdy: | |
391 | gpio_free(gpiomtd->plat.gpio_cle); | |
392 | err_cle: | |
393 | gpio_free(gpiomtd->plat.gpio_ale); | |
394 | err_ale: | |
395 | if (gpio_is_valid(gpiomtd->plat.gpio_nwp)) | |
396 | gpio_free(gpiomtd->plat.gpio_nwp); | |
397 | err_nwp: | |
398 | gpio_free(gpiomtd->plat.gpio_nce); | |
399 | err_nce: | |
400 | iounmap(gpiomtd->io_sync); | |
401 | if (res1) | |
db5a5ae2 | 402 | release_mem_region(res1->start, resource_size(res1)); |
aaf7ea20 MR |
403 | err_sync: |
404 | iounmap(gpiomtd->nand_chip.IO_ADDR_R); | |
db5a5ae2 | 405 | release_mem_region(res0->start, resource_size(res0)); |
aaf7ea20 MR |
406 | err_map: |
407 | kfree(gpiomtd); | |
408 | return ret; | |
409 | } | |
410 | ||
411 | static struct platform_driver gpio_nand_driver = { | |
412 | .probe = gpio_nand_probe, | |
413 | .remove = gpio_nand_remove, | |
414 | .driver = { | |
415 | .name = "gpio-nand", | |
775c3220 | 416 | .of_match_table = gpio_nand_id_table, |
aaf7ea20 MR |
417 | }, |
418 | }; | |
419 | ||
2fe87aef | 420 | module_platform_driver(gpio_nand_driver); |
aaf7ea20 MR |
421 | |
422 | MODULE_LICENSE("GPL"); | |
423 | MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); | |
424 | MODULE_DESCRIPTION("GPIO NAND Driver"); |