]>
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> | |
30 | ||
31 | struct gpiomtd { | |
32 | void __iomem *io_sync; | |
33 | struct mtd_info mtd_info; | |
34 | struct nand_chip nand_chip; | |
35 | struct gpio_nand_platdata plat; | |
36 | }; | |
37 | ||
38 | #define gpio_nand_getpriv(x) container_of(x, struct gpiomtd, mtd_info) | |
39 | ||
40 | ||
41 | #ifdef CONFIG_ARM | |
42 | /* gpio_nand_dosync() | |
43 | * | |
44 | * Make sure the GPIO state changes occur in-order with writes to NAND | |
45 | * memory region. | |
46 | * Needed on PXA due to bus-reordering within the SoC itself (see section on | |
47 | * I/O ordering in PXA manual (section 2.3, p35) | |
48 | */ | |
49 | static void gpio_nand_dosync(struct gpiomtd *gpiomtd) | |
50 | { | |
51 | unsigned long tmp; | |
52 | ||
53 | if (gpiomtd->io_sync) { | |
54 | /* | |
55 | * Linux memory barriers don't cater for what's required here. | |
56 | * What's required is what's here - a read from a separate | |
57 | * region with a dependency on that read. | |
58 | */ | |
59 | tmp = readl(gpiomtd->io_sync); | |
60 | asm volatile("mov %1, %0\n" : "=r" (tmp) : "r" (tmp)); | |
61 | } | |
62 | } | |
63 | #else | |
64 | static inline void gpio_nand_dosync(struct gpiomtd *gpiomtd) {} | |
65 | #endif | |
66 | ||
67 | static void gpio_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl) | |
68 | { | |
69 | struct gpiomtd *gpiomtd = gpio_nand_getpriv(mtd); | |
70 | ||
71 | gpio_nand_dosync(gpiomtd); | |
72 | ||
73 | if (ctrl & NAND_CTRL_CHANGE) { | |
74 | gpio_set_value(gpiomtd->plat.gpio_nce, !(ctrl & NAND_NCE)); | |
75 | gpio_set_value(gpiomtd->plat.gpio_cle, !!(ctrl & NAND_CLE)); | |
76 | gpio_set_value(gpiomtd->plat.gpio_ale, !!(ctrl & NAND_ALE)); | |
77 | gpio_nand_dosync(gpiomtd); | |
78 | } | |
79 | if (cmd == NAND_CMD_NONE) | |
80 | return; | |
81 | ||
82 | writeb(cmd, gpiomtd->nand_chip.IO_ADDR_W); | |
83 | gpio_nand_dosync(gpiomtd); | |
84 | } | |
85 | ||
86 | static void gpio_nand_writebuf(struct mtd_info *mtd, const u_char *buf, int len) | |
87 | { | |
88 | struct nand_chip *this = mtd->priv; | |
89 | ||
90 | writesb(this->IO_ADDR_W, buf, len); | |
91 | } | |
92 | ||
93 | static void gpio_nand_readbuf(struct mtd_info *mtd, u_char *buf, int len) | |
94 | { | |
95 | struct nand_chip *this = mtd->priv; | |
96 | ||
97 | readsb(this->IO_ADDR_R, buf, len); | |
98 | } | |
99 | ||
100 | static int gpio_nand_verifybuf(struct mtd_info *mtd, const u_char *buf, int len) | |
101 | { | |
102 | struct nand_chip *this = mtd->priv; | |
103 | unsigned char read, *p = (unsigned char *) buf; | |
104 | int i, err = 0; | |
105 | ||
106 | for (i = 0; i < len; i++) { | |
107 | read = readb(this->IO_ADDR_R); | |
108 | if (read != p[i]) { | |
109 | pr_debug("%s: err at %d (read %04x vs %04x)\n", | |
110 | __func__, i, read, p[i]); | |
111 | err = -EFAULT; | |
112 | } | |
113 | } | |
114 | return err; | |
115 | } | |
116 | ||
117 | static void gpio_nand_writebuf16(struct mtd_info *mtd, const u_char *buf, | |
118 | int len) | |
119 | { | |
120 | struct nand_chip *this = mtd->priv; | |
121 | ||
122 | if (IS_ALIGNED((unsigned long)buf, 2)) { | |
123 | writesw(this->IO_ADDR_W, buf, len>>1); | |
124 | } else { | |
125 | int i; | |
126 | unsigned short *ptr = (unsigned short *)buf; | |
127 | ||
128 | for (i = 0; i < len; i += 2, ptr++) | |
129 | writew(*ptr, this->IO_ADDR_W); | |
130 | } | |
131 | } | |
132 | ||
133 | static void gpio_nand_readbuf16(struct mtd_info *mtd, u_char *buf, int len) | |
134 | { | |
135 | struct nand_chip *this = mtd->priv; | |
136 | ||
137 | if (IS_ALIGNED((unsigned long)buf, 2)) { | |
138 | readsw(this->IO_ADDR_R, buf, len>>1); | |
139 | } else { | |
140 | int i; | |
141 | unsigned short *ptr = (unsigned short *)buf; | |
142 | ||
143 | for (i = 0; i < len; i += 2, ptr++) | |
144 | *ptr = readw(this->IO_ADDR_R); | |
145 | } | |
146 | } | |
147 | ||
148 | static int gpio_nand_verifybuf16(struct mtd_info *mtd, const u_char *buf, | |
149 | int len) | |
150 | { | |
151 | struct nand_chip *this = mtd->priv; | |
152 | unsigned short read, *p = (unsigned short *) buf; | |
153 | int i, err = 0; | |
154 | len >>= 1; | |
155 | ||
156 | for (i = 0; i < len; i++) { | |
157 | read = readw(this->IO_ADDR_R); | |
158 | if (read != p[i]) { | |
159 | pr_debug("%s: err at %d (read %04x vs %04x)\n", | |
160 | __func__, i, read, p[i]); | |
161 | err = -EFAULT; | |
162 | } | |
163 | } | |
164 | return err; | |
165 | } | |
166 | ||
167 | ||
168 | static int gpio_nand_devready(struct mtd_info *mtd) | |
169 | { | |
170 | struct gpiomtd *gpiomtd = gpio_nand_getpriv(mtd); | |
171 | return gpio_get_value(gpiomtd->plat.gpio_rdy); | |
172 | } | |
173 | ||
174 | static int __devexit gpio_nand_remove(struct platform_device *dev) | |
175 | { | |
176 | struct gpiomtd *gpiomtd = platform_get_drvdata(dev); | |
177 | struct resource *res; | |
178 | ||
179 | nand_release(&gpiomtd->mtd_info); | |
180 | ||
181 | res = platform_get_resource(dev, IORESOURCE_MEM, 1); | |
182 | iounmap(gpiomtd->io_sync); | |
183 | if (res) | |
184 | release_mem_region(res->start, res->end - res->start + 1); | |
185 | ||
186 | res = platform_get_resource(dev, IORESOURCE_MEM, 0); | |
187 | iounmap(gpiomtd->nand_chip.IO_ADDR_R); | |
188 | release_mem_region(res->start, res->end - res->start + 1); | |
189 | ||
190 | if (gpio_is_valid(gpiomtd->plat.gpio_nwp)) | |
191 | gpio_set_value(gpiomtd->plat.gpio_nwp, 0); | |
192 | gpio_set_value(gpiomtd->plat.gpio_nce, 1); | |
193 | ||
194 | gpio_free(gpiomtd->plat.gpio_cle); | |
195 | gpio_free(gpiomtd->plat.gpio_ale); | |
196 | gpio_free(gpiomtd->plat.gpio_nce); | |
197 | if (gpio_is_valid(gpiomtd->plat.gpio_nwp)) | |
198 | gpio_free(gpiomtd->plat.gpio_nwp); | |
199 | gpio_free(gpiomtd->plat.gpio_rdy); | |
200 | ||
201 | kfree(gpiomtd); | |
202 | ||
203 | return 0; | |
204 | } | |
205 | ||
206 | static void __iomem *request_and_remap(struct resource *res, size_t size, | |
207 | const char *name, int *err) | |
208 | { | |
209 | void __iomem *ptr; | |
210 | ||
211 | if (!request_mem_region(res->start, res->end - res->start + 1, name)) { | |
212 | *err = -EBUSY; | |
213 | return NULL; | |
214 | } | |
215 | ||
216 | ptr = ioremap(res->start, size); | |
217 | if (!ptr) { | |
218 | release_mem_region(res->start, res->end - res->start + 1); | |
219 | *err = -ENOMEM; | |
220 | } | |
221 | return ptr; | |
222 | } | |
223 | ||
224 | static int __devinit gpio_nand_probe(struct platform_device *dev) | |
225 | { | |
226 | struct gpiomtd *gpiomtd; | |
227 | struct nand_chip *this; | |
228 | struct resource *res0, *res1; | |
229 | int ret; | |
230 | ||
231 | if (!dev->dev.platform_data) | |
232 | return -EINVAL; | |
233 | ||
234 | res0 = platform_get_resource(dev, IORESOURCE_MEM, 0); | |
235 | if (!res0) | |
236 | return -EINVAL; | |
237 | ||
238 | gpiomtd = kzalloc(sizeof(*gpiomtd), GFP_KERNEL); | |
239 | if (gpiomtd == NULL) { | |
240 | dev_err(&dev->dev, "failed to create NAND MTD\n"); | |
241 | return -ENOMEM; | |
242 | } | |
243 | ||
244 | this = &gpiomtd->nand_chip; | |
245 | this->IO_ADDR_R = request_and_remap(res0, 2, "NAND", &ret); | |
246 | if (!this->IO_ADDR_R) { | |
247 | dev_err(&dev->dev, "unable to map NAND\n"); | |
248 | goto err_map; | |
249 | } | |
250 | ||
251 | res1 = platform_get_resource(dev, IORESOURCE_MEM, 1); | |
252 | if (res1) { | |
253 | gpiomtd->io_sync = request_and_remap(res1, 4, "NAND sync", &ret); | |
254 | if (!gpiomtd->io_sync) { | |
255 | dev_err(&dev->dev, "unable to map sync NAND\n"); | |
256 | goto err_sync; | |
257 | } | |
258 | } | |
259 | ||
260 | memcpy(&gpiomtd->plat, dev->dev.platform_data, sizeof(gpiomtd->plat)); | |
261 | ||
262 | ret = gpio_request(gpiomtd->plat.gpio_nce, "NAND NCE"); | |
263 | if (ret) | |
264 | goto err_nce; | |
265 | gpio_direction_output(gpiomtd->plat.gpio_nce, 1); | |
266 | if (gpio_is_valid(gpiomtd->plat.gpio_nwp)) { | |
267 | ret = gpio_request(gpiomtd->plat.gpio_nwp, "NAND NWP"); | |
268 | if (ret) | |
269 | goto err_nwp; | |
270 | gpio_direction_output(gpiomtd->plat.gpio_nwp, 1); | |
271 | } | |
272 | ret = gpio_request(gpiomtd->plat.gpio_ale, "NAND ALE"); | |
273 | if (ret) | |
274 | goto err_ale; | |
275 | gpio_direction_output(gpiomtd->plat.gpio_ale, 0); | |
276 | ret = gpio_request(gpiomtd->plat.gpio_cle, "NAND CLE"); | |
277 | if (ret) | |
278 | goto err_cle; | |
279 | gpio_direction_output(gpiomtd->plat.gpio_cle, 0); | |
280 | ret = gpio_request(gpiomtd->plat.gpio_rdy, "NAND RDY"); | |
281 | if (ret) | |
282 | goto err_rdy; | |
283 | gpio_direction_input(gpiomtd->plat.gpio_rdy); | |
284 | ||
285 | ||
286 | this->IO_ADDR_W = this->IO_ADDR_R; | |
287 | this->ecc.mode = NAND_ECC_SOFT; | |
288 | this->options = gpiomtd->plat.options; | |
289 | this->chip_delay = gpiomtd->plat.chip_delay; | |
290 | ||
291 | /* install our routines */ | |
292 | this->cmd_ctrl = gpio_nand_cmd_ctrl; | |
293 | this->dev_ready = gpio_nand_devready; | |
294 | ||
295 | if (this->options & NAND_BUSWIDTH_16) { | |
296 | this->read_buf = gpio_nand_readbuf16; | |
297 | this->write_buf = gpio_nand_writebuf16; | |
298 | this->verify_buf = gpio_nand_verifybuf16; | |
299 | } else { | |
300 | this->read_buf = gpio_nand_readbuf; | |
301 | this->write_buf = gpio_nand_writebuf; | |
302 | this->verify_buf = gpio_nand_verifybuf; | |
303 | } | |
304 | ||
305 | /* set the mtd private data for the nand driver */ | |
306 | gpiomtd->mtd_info.priv = this; | |
307 | gpiomtd->mtd_info.owner = THIS_MODULE; | |
308 | ||
309 | if (nand_scan(&gpiomtd->mtd_info, 1)) { | |
310 | dev_err(&dev->dev, "no nand chips found?\n"); | |
311 | ret = -ENXIO; | |
312 | goto err_wp; | |
313 | } | |
314 | ||
315 | if (gpiomtd->plat.adjust_parts) | |
316 | gpiomtd->plat.adjust_parts(&gpiomtd->plat, | |
317 | gpiomtd->mtd_info.size); | |
318 | ||
319 | add_mtd_partitions(&gpiomtd->mtd_info, gpiomtd->plat.parts, | |
320 | gpiomtd->plat.num_parts); | |
321 | platform_set_drvdata(dev, gpiomtd); | |
322 | ||
323 | return 0; | |
324 | ||
325 | err_wp: | |
326 | if (gpio_is_valid(gpiomtd->plat.gpio_nwp)) | |
327 | gpio_set_value(gpiomtd->plat.gpio_nwp, 0); | |
328 | gpio_free(gpiomtd->plat.gpio_rdy); | |
329 | err_rdy: | |
330 | gpio_free(gpiomtd->plat.gpio_cle); | |
331 | err_cle: | |
332 | gpio_free(gpiomtd->plat.gpio_ale); | |
333 | err_ale: | |
334 | if (gpio_is_valid(gpiomtd->plat.gpio_nwp)) | |
335 | gpio_free(gpiomtd->plat.gpio_nwp); | |
336 | err_nwp: | |
337 | gpio_free(gpiomtd->plat.gpio_nce); | |
338 | err_nce: | |
339 | iounmap(gpiomtd->io_sync); | |
340 | if (res1) | |
341 | release_mem_region(res1->start, res1->end - res1->start + 1); | |
342 | err_sync: | |
343 | iounmap(gpiomtd->nand_chip.IO_ADDR_R); | |
344 | release_mem_region(res0->start, res0->end - res0->start + 1); | |
345 | err_map: | |
346 | kfree(gpiomtd); | |
347 | return ret; | |
348 | } | |
349 | ||
350 | static struct platform_driver gpio_nand_driver = { | |
351 | .probe = gpio_nand_probe, | |
352 | .remove = gpio_nand_remove, | |
353 | .driver = { | |
354 | .name = "gpio-nand", | |
355 | }, | |
356 | }; | |
357 | ||
358 | static int __init gpio_nand_init(void) | |
359 | { | |
360 | printk(KERN_INFO "GPIO NAND driver, © 2004 Simtec Electronics\n"); | |
361 | ||
362 | return platform_driver_register(&gpio_nand_driver); | |
363 | } | |
364 | ||
365 | static void __exit gpio_nand_exit(void) | |
366 | { | |
367 | platform_driver_unregister(&gpio_nand_driver); | |
368 | } | |
369 | ||
370 | module_init(gpio_nand_init); | |
371 | module_exit(gpio_nand_exit); | |
372 | ||
373 | MODULE_LICENSE("GPL"); | |
374 | MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); | |
375 | MODULE_DESCRIPTION("GPIO NAND Driver"); |