diff options
Diffstat (limited to 'drivers/mtd/nand/w90p910_nand.c')
-rw-r--r-- | drivers/mtd/nand/w90p910_nand.c | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/drivers/mtd/nand/w90p910_nand.c b/drivers/mtd/nand/w90p910_nand.c new file mode 100644 index 000000000000..7680e731348a --- /dev/null +++ b/drivers/mtd/nand/w90p910_nand.c | |||
@@ -0,0 +1,382 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2009 Nuvoton technology corporation. | ||
3 | * | ||
4 | * Wan ZongShun <mcuos.com@gmail.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation;version 2 of the License. | ||
9 | * | ||
10 | */ | ||
11 | |||
12 | #include <linux/slab.h> | ||
13 | #include <linux/init.h> | ||
14 | #include <linux/module.h> | ||
15 | #include <linux/interrupt.h> | ||
16 | #include <linux/io.h> | ||
17 | #include <linux/platform_device.h> | ||
18 | #include <linux/delay.h> | ||
19 | #include <linux/clk.h> | ||
20 | #include <linux/err.h> | ||
21 | |||
22 | #include <linux/mtd/mtd.h> | ||
23 | #include <linux/mtd/nand.h> | ||
24 | #include <linux/mtd/partitions.h> | ||
25 | |||
26 | #define REG_FMICSR 0x00 | ||
27 | #define REG_SMCSR 0xa0 | ||
28 | #define REG_SMISR 0xac | ||
29 | #define REG_SMCMD 0xb0 | ||
30 | #define REG_SMADDR 0xb4 | ||
31 | #define REG_SMDATA 0xb8 | ||
32 | |||
33 | #define RESET_FMI 0x01 | ||
34 | #define NAND_EN 0x08 | ||
35 | #define READYBUSY (0x01 << 18) | ||
36 | |||
37 | #define SWRST 0x01 | ||
38 | #define PSIZE (0x01 << 3) | ||
39 | #define DMARWEN (0x03 << 1) | ||
40 | #define BUSWID (0x01 << 4) | ||
41 | #define ECC4EN (0x01 << 5) | ||
42 | #define WP (0x01 << 24) | ||
43 | #define NANDCS (0x01 << 25) | ||
44 | #define ENDADDR (0x01 << 31) | ||
45 | |||
46 | #define read_data_reg(dev) \ | ||
47 | __raw_readl((dev)->reg + REG_SMDATA) | ||
48 | |||
49 | #define write_data_reg(dev, val) \ | ||
50 | __raw_writel((val), (dev)->reg + REG_SMDATA) | ||
51 | |||
52 | #define write_cmd_reg(dev, val) \ | ||
53 | __raw_writel((val), (dev)->reg + REG_SMCMD) | ||
54 | |||
55 | #define write_addr_reg(dev, val) \ | ||
56 | __raw_writel((val), (dev)->reg + REG_SMADDR) | ||
57 | |||
58 | struct w90p910_nand { | ||
59 | struct mtd_info mtd; | ||
60 | struct nand_chip chip; | ||
61 | void __iomem *reg; | ||
62 | struct clk *clk; | ||
63 | spinlock_t lock; | ||
64 | }; | ||
65 | |||
66 | static const struct mtd_partition partitions[] = { | ||
67 | { | ||
68 | .name = "NAND FS 0", | ||
69 | .offset = 0, | ||
70 | .size = 8 * 1024 * 1024 | ||
71 | }, | ||
72 | { | ||
73 | .name = "NAND FS 1", | ||
74 | .offset = MTDPART_OFS_APPEND, | ||
75 | .size = MTDPART_SIZ_FULL | ||
76 | } | ||
77 | }; | ||
78 | |||
79 | static unsigned char w90p910_nand_read_byte(struct mtd_info *mtd) | ||
80 | { | ||
81 | unsigned char ret; | ||
82 | struct w90p910_nand *nand; | ||
83 | |||
84 | nand = container_of(mtd, struct w90p910_nand, mtd); | ||
85 | |||
86 | ret = (unsigned char)read_data_reg(nand); | ||
87 | |||
88 | return ret; | ||
89 | } | ||
90 | |||
91 | static void w90p910_nand_read_buf(struct mtd_info *mtd, | ||
92 | unsigned char *buf, int len) | ||
93 | { | ||
94 | int i; | ||
95 | struct w90p910_nand *nand; | ||
96 | |||
97 | nand = container_of(mtd, struct w90p910_nand, mtd); | ||
98 | |||
99 | for (i = 0; i < len; i++) | ||
100 | buf[i] = (unsigned char)read_data_reg(nand); | ||
101 | } | ||
102 | |||
103 | static void w90p910_nand_write_buf(struct mtd_info *mtd, | ||
104 | const unsigned char *buf, int len) | ||
105 | { | ||
106 | int i; | ||
107 | struct w90p910_nand *nand; | ||
108 | |||
109 | nand = container_of(mtd, struct w90p910_nand, mtd); | ||
110 | |||
111 | for (i = 0; i < len; i++) | ||
112 | write_data_reg(nand, buf[i]); | ||
113 | } | ||
114 | |||
115 | static int w90p910_verify_buf(struct mtd_info *mtd, | ||
116 | const unsigned char *buf, int len) | ||
117 | { | ||
118 | int i; | ||
119 | struct w90p910_nand *nand; | ||
120 | |||
121 | nand = container_of(mtd, struct w90p910_nand, mtd); | ||
122 | |||
123 | for (i = 0; i < len; i++) { | ||
124 | if (buf[i] != (unsigned char)read_data_reg(nand)) | ||
125 | return -EFAULT; | ||
126 | } | ||
127 | |||
128 | return 0; | ||
129 | } | ||
130 | |||
131 | static int w90p910_check_rb(struct w90p910_nand *nand) | ||
132 | { | ||
133 | unsigned int val; | ||
134 | spin_lock(&nand->lock); | ||
135 | val = __raw_readl(REG_SMISR); | ||
136 | val &= READYBUSY; | ||
137 | spin_unlock(&nand->lock); | ||
138 | |||
139 | return val; | ||
140 | } | ||
141 | |||
142 | static int w90p910_nand_devready(struct mtd_info *mtd) | ||
143 | { | ||
144 | struct w90p910_nand *nand; | ||
145 | int ready; | ||
146 | |||
147 | nand = container_of(mtd, struct w90p910_nand, mtd); | ||
148 | |||
149 | ready = (w90p910_check_rb(nand)) ? 1 : 0; | ||
150 | return ready; | ||
151 | } | ||
152 | |||
153 | static void w90p910_nand_command_lp(struct mtd_info *mtd, | ||
154 | unsigned int command, int column, int page_addr) | ||
155 | { | ||
156 | register struct nand_chip *chip = mtd->priv; | ||
157 | struct w90p910_nand *nand; | ||
158 | |||
159 | nand = container_of(mtd, struct w90p910_nand, mtd); | ||
160 | |||
161 | if (command == NAND_CMD_READOOB) { | ||
162 | column += mtd->writesize; | ||
163 | command = NAND_CMD_READ0; | ||
164 | } | ||
165 | |||
166 | write_cmd_reg(nand, command & 0xff); | ||
167 | |||
168 | if (column != -1 || page_addr != -1) { | ||
169 | |||
170 | if (column != -1) { | ||
171 | if (chip->options & NAND_BUSWIDTH_16) | ||
172 | column >>= 1; | ||
173 | write_addr_reg(nand, column); | ||
174 | write_addr_reg(nand, column >> 8 | ENDADDR); | ||
175 | } | ||
176 | if (page_addr != -1) { | ||
177 | write_addr_reg(nand, page_addr); | ||
178 | |||
179 | if (chip->chipsize > (128 << 20)) { | ||
180 | write_addr_reg(nand, page_addr >> 8); | ||
181 | write_addr_reg(nand, page_addr >> 16 | ENDADDR); | ||
182 | } else { | ||
183 | write_addr_reg(nand, page_addr >> 8 | ENDADDR); | ||
184 | } | ||
185 | } | ||
186 | } | ||
187 | |||
188 | switch (command) { | ||
189 | case NAND_CMD_CACHEDPROG: | ||
190 | case NAND_CMD_PAGEPROG: | ||
191 | case NAND_CMD_ERASE1: | ||
192 | case NAND_CMD_ERASE2: | ||
193 | case NAND_CMD_SEQIN: | ||
194 | case NAND_CMD_RNDIN: | ||
195 | case NAND_CMD_STATUS: | ||
196 | case NAND_CMD_DEPLETE1: | ||
197 | return; | ||
198 | |||
199 | case NAND_CMD_STATUS_ERROR: | ||
200 | case NAND_CMD_STATUS_ERROR0: | ||
201 | case NAND_CMD_STATUS_ERROR1: | ||
202 | case NAND_CMD_STATUS_ERROR2: | ||
203 | case NAND_CMD_STATUS_ERROR3: | ||
204 | udelay(chip->chip_delay); | ||
205 | return; | ||
206 | |||
207 | case NAND_CMD_RESET: | ||
208 | if (chip->dev_ready) | ||
209 | break; | ||
210 | udelay(chip->chip_delay); | ||
211 | |||
212 | write_cmd_reg(nand, NAND_CMD_STATUS); | ||
213 | write_cmd_reg(nand, command); | ||
214 | |||
215 | while (!w90p910_check_rb(nand)) | ||
216 | ; | ||
217 | |||
218 | return; | ||
219 | |||
220 | case NAND_CMD_RNDOUT: | ||
221 | write_cmd_reg(nand, NAND_CMD_RNDOUTSTART); | ||
222 | return; | ||
223 | |||
224 | case NAND_CMD_READ0: | ||
225 | |||
226 | write_cmd_reg(nand, NAND_CMD_READSTART); | ||
227 | default: | ||
228 | |||
229 | if (!chip->dev_ready) { | ||
230 | udelay(chip->chip_delay); | ||
231 | return; | ||
232 | } | ||
233 | } | ||
234 | |||
235 | /* Apply this short delay always to ensure that we do wait tWB in | ||
236 | * any case on any machine. */ | ||
237 | ndelay(100); | ||
238 | |||
239 | while (!chip->dev_ready(mtd)) | ||
240 | ; | ||
241 | } | ||
242 | |||
243 | |||
244 | static void w90p910_nand_enable(struct w90p910_nand *nand) | ||
245 | { | ||
246 | unsigned int val; | ||
247 | spin_lock(&nand->lock); | ||
248 | __raw_writel(RESET_FMI, (nand->reg + REG_FMICSR)); | ||
249 | |||
250 | val = __raw_readl(nand->reg + REG_FMICSR); | ||
251 | |||
252 | if (!(val & NAND_EN)) | ||
253 | __raw_writel(val | NAND_EN, REG_FMICSR); | ||
254 | |||
255 | val = __raw_readl(nand->reg + REG_SMCSR); | ||
256 | |||
257 | val &= ~(SWRST|PSIZE|DMARWEN|BUSWID|ECC4EN|NANDCS); | ||
258 | val |= WP; | ||
259 | |||
260 | __raw_writel(val, nand->reg + REG_SMCSR); | ||
261 | |||
262 | spin_unlock(&nand->lock); | ||
263 | } | ||
264 | |||
265 | static int __devinit w90p910_nand_probe(struct platform_device *pdev) | ||
266 | { | ||
267 | struct w90p910_nand *w90p910_nand; | ||
268 | struct nand_chip *chip; | ||
269 | int retval; | ||
270 | struct resource *res; | ||
271 | |||
272 | retval = 0; | ||
273 | |||
274 | w90p910_nand = kzalloc(sizeof(struct w90p910_nand), GFP_KERNEL); | ||
275 | if (!w90p910_nand) | ||
276 | return -ENOMEM; | ||
277 | chip = &(w90p910_nand->chip); | ||
278 | |||
279 | w90p910_nand->mtd.priv = chip; | ||
280 | w90p910_nand->mtd.owner = THIS_MODULE; | ||
281 | spin_lock_init(&w90p910_nand->lock); | ||
282 | |||
283 | w90p910_nand->clk = clk_get(&pdev->dev, NULL); | ||
284 | if (IS_ERR(w90p910_nand->clk)) { | ||
285 | retval = -ENOENT; | ||
286 | goto fail1; | ||
287 | } | ||
288 | clk_enable(w90p910_nand->clk); | ||
289 | |||
290 | chip->cmdfunc = w90p910_nand_command_lp; | ||
291 | chip->dev_ready = w90p910_nand_devready; | ||
292 | chip->read_byte = w90p910_nand_read_byte; | ||
293 | chip->write_buf = w90p910_nand_write_buf; | ||
294 | chip->read_buf = w90p910_nand_read_buf; | ||
295 | chip->verify_buf = w90p910_verify_buf; | ||
296 | chip->chip_delay = 50; | ||
297 | chip->options = 0; | ||
298 | chip->ecc.mode = NAND_ECC_SOFT; | ||
299 | |||
300 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
301 | if (!res) { | ||
302 | retval = -ENXIO; | ||
303 | goto fail1; | ||
304 | } | ||
305 | |||
306 | if (!request_mem_region(res->start, resource_size(res), pdev->name)) { | ||
307 | retval = -EBUSY; | ||
308 | goto fail1; | ||
309 | } | ||
310 | |||
311 | w90p910_nand->reg = ioremap(res->start, resource_size(res)); | ||
312 | if (!w90p910_nand->reg) { | ||
313 | retval = -ENOMEM; | ||
314 | goto fail2; | ||
315 | } | ||
316 | |||
317 | w90p910_nand_enable(w90p910_nand); | ||
318 | |||
319 | if (nand_scan(&(w90p910_nand->mtd), 1)) { | ||
320 | retval = -ENXIO; | ||
321 | goto fail3; | ||
322 | } | ||
323 | |||
324 | add_mtd_partitions(&(w90p910_nand->mtd), partitions, | ||
325 | ARRAY_SIZE(partitions)); | ||
326 | |||
327 | platform_set_drvdata(pdev, w90p910_nand); | ||
328 | |||
329 | return retval; | ||
330 | |||
331 | fail3: iounmap(w90p910_nand->reg); | ||
332 | fail2: release_mem_region(res->start, resource_size(res)); | ||
333 | fail1: kfree(w90p910_nand); | ||
334 | return retval; | ||
335 | } | ||
336 | |||
337 | static int __devexit w90p910_nand_remove(struct platform_device *pdev) | ||
338 | { | ||
339 | struct w90p910_nand *w90p910_nand = platform_get_drvdata(pdev); | ||
340 | struct resource *res; | ||
341 | |||
342 | iounmap(w90p910_nand->reg); | ||
343 | |||
344 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
345 | release_mem_region(res->start, resource_size(res)); | ||
346 | |||
347 | clk_disable(w90p910_nand->clk); | ||
348 | clk_put(w90p910_nand->clk); | ||
349 | |||
350 | kfree(w90p910_nand); | ||
351 | |||
352 | platform_set_drvdata(pdev, NULL); | ||
353 | |||
354 | return 0; | ||
355 | } | ||
356 | |||
357 | static struct platform_driver w90p910_nand_driver = { | ||
358 | .probe = w90p910_nand_probe, | ||
359 | .remove = __devexit_p(w90p910_nand_remove), | ||
360 | .driver = { | ||
361 | .name = "w90p910-fmi", | ||
362 | .owner = THIS_MODULE, | ||
363 | }, | ||
364 | }; | ||
365 | |||
366 | static int __init w90p910_nand_init(void) | ||
367 | { | ||
368 | return platform_driver_register(&w90p910_nand_driver); | ||
369 | } | ||
370 | |||
371 | static void __exit w90p910_nand_exit(void) | ||
372 | { | ||
373 | platform_driver_unregister(&w90p910_nand_driver); | ||
374 | } | ||
375 | |||
376 | module_init(w90p910_nand_init); | ||
377 | module_exit(w90p910_nand_exit); | ||
378 | |||
379 | MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>"); | ||
380 | MODULE_DESCRIPTION("w90p910 nand driver!"); | ||
381 | MODULE_LICENSE("GPL"); | ||
382 | MODULE_ALIAS("platform:w90p910-fmi"); | ||