Commit | Line | Data |
---|---|---|
8bff82cb | 1 | /* |
bb6a7755 | 2 | * Copyright © 2009 Nuvoton technology corporation. |
8bff82cb WZ |
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 | ||
bb6a7755 | 58 | struct nuc900_nand { |
8bff82cb WZ |
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 | ||
bb6a7755 | 79 | static unsigned char nuc900_nand_read_byte(struct mtd_info *mtd) |
8bff82cb WZ |
80 | { |
81 | unsigned char ret; | |
bb6a7755 | 82 | struct nuc900_nand *nand; |
8bff82cb | 83 | |
bb6a7755 | 84 | nand = container_of(mtd, struct nuc900_nand, mtd); |
8bff82cb WZ |
85 | |
86 | ret = (unsigned char)read_data_reg(nand); | |
87 | ||
88 | return ret; | |
89 | } | |
90 | ||
bb6a7755 DW |
91 | static void nuc900_nand_read_buf(struct mtd_info *mtd, |
92 | unsigned char *buf, int len) | |
8bff82cb WZ |
93 | { |
94 | int i; | |
bb6a7755 | 95 | struct nuc900_nand *nand; |
8bff82cb | 96 | |
bb6a7755 | 97 | nand = container_of(mtd, struct nuc900_nand, mtd); |
8bff82cb WZ |
98 | |
99 | for (i = 0; i < len; i++) | |
100 | buf[i] = (unsigned char)read_data_reg(nand); | |
101 | } | |
102 | ||
bb6a7755 DW |
103 | static void nuc900_nand_write_buf(struct mtd_info *mtd, |
104 | const unsigned char *buf, int len) | |
8bff82cb WZ |
105 | { |
106 | int i; | |
bb6a7755 | 107 | struct nuc900_nand *nand; |
8bff82cb | 108 | |
bb6a7755 | 109 | nand = container_of(mtd, struct nuc900_nand, mtd); |
8bff82cb WZ |
110 | |
111 | for (i = 0; i < len; i++) | |
112 | write_data_reg(nand, buf[i]); | |
113 | } | |
114 | ||
bb6a7755 DW |
115 | static int nuc900_verify_buf(struct mtd_info *mtd, |
116 | const unsigned char *buf, int len) | |
8bff82cb WZ |
117 | { |
118 | int i; | |
bb6a7755 | 119 | struct nuc900_nand *nand; |
8bff82cb | 120 | |
bb6a7755 | 121 | nand = container_of(mtd, struct nuc900_nand, mtd); |
8bff82cb WZ |
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 | ||
bb6a7755 | 131 | static int nuc900_check_rb(struct nuc900_nand *nand) |
8bff82cb WZ |
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 | ||
bb6a7755 | 142 | static int nuc900_nand_devready(struct mtd_info *mtd) |
8bff82cb | 143 | { |
bb6a7755 | 144 | struct nuc900_nand *nand; |
8bff82cb WZ |
145 | int ready; |
146 | ||
bb6a7755 | 147 | nand = container_of(mtd, struct nuc900_nand, mtd); |
8bff82cb | 148 | |
bb6a7755 | 149 | ready = (nuc900_check_rb(nand)) ? 1 : 0; |
8bff82cb WZ |
150 | return ready; |
151 | } | |
152 | ||
bb6a7755 DW |
153 | static void nuc900_nand_command_lp(struct mtd_info *mtd, unsigned int command, |
154 | int column, int page_addr) | |
8bff82cb WZ |
155 | { |
156 | register struct nand_chip *chip = mtd->priv; | |
bb6a7755 | 157 | struct nuc900_nand *nand; |
8bff82cb | 158 | |
bb6a7755 | 159 | nand = container_of(mtd, struct nuc900_nand, mtd); |
8bff82cb WZ |
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 | ||
bb6a7755 | 215 | while (!nuc900_check_rb(nand)) |
8bff82cb WZ |
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 | ||
bb6a7755 | 244 | static void nuc900_nand_enable(struct nuc900_nand *nand) |
8bff82cb WZ |
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 | ||
bb6a7755 | 265 | static int __devinit nuc900_nand_probe(struct platform_device *pdev) |
8bff82cb | 266 | { |
bb6a7755 | 267 | struct nuc900_nand *nuc900_nand; |
8bff82cb WZ |
268 | struct nand_chip *chip; |
269 | int retval; | |
270 | struct resource *res; | |
271 | ||
272 | retval = 0; | |
273 | ||
bb6a7755 DW |
274 | nuc900_nand = kzalloc(sizeof(struct nuc900_nand), GFP_KERNEL); |
275 | if (!nuc900_nand) | |
8bff82cb | 276 | return -ENOMEM; |
bb6a7755 | 277 | chip = &(nuc900_nand->chip); |
8bff82cb | 278 | |
bb6a7755 DW |
279 | nuc900_nand->mtd.priv = chip; |
280 | nuc900_nand->mtd.owner = THIS_MODULE; | |
281 | spin_lock_init(&nuc900_nand->lock); | |
8bff82cb | 282 | |
bb6a7755 DW |
283 | nuc900_nand->clk = clk_get(&pdev->dev, NULL); |
284 | if (IS_ERR(nuc900_nand->clk)) { | |
8bff82cb WZ |
285 | retval = -ENOENT; |
286 | goto fail1; | |
287 | } | |
bb6a7755 DW |
288 | clk_enable(nuc900_nand->clk); |
289 | ||
290 | chip->cmdfunc = nuc900_nand_command_lp; | |
291 | chip->dev_ready = nuc900_nand_devready; | |
292 | chip->read_byte = nuc900_nand_read_byte; | |
293 | chip->write_buf = nuc900_nand_write_buf; | |
294 | chip->read_buf = nuc900_nand_read_buf; | |
295 | chip->verify_buf = nuc900_verify_buf; | |
8bff82cb WZ |
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 | ||
bb6a7755 DW |
311 | nuc900_nand->reg = ioremap(res->start, resource_size(res)); |
312 | if (!nuc900_nand->reg) { | |
8bff82cb WZ |
313 | retval = -ENOMEM; |
314 | goto fail2; | |
315 | } | |
316 | ||
bb6a7755 | 317 | nuc900_nand_enable(nuc900_nand); |
8bff82cb | 318 | |
bb6a7755 | 319 | if (nand_scan(&(nuc900_nand->mtd), 1)) { |
8bff82cb WZ |
320 | retval = -ENXIO; |
321 | goto fail3; | |
322 | } | |
323 | ||
ee0e87b1 JI |
324 | mtd_device_register(&(nuc900_nand->mtd), partitions, |
325 | ARRAY_SIZE(partitions)); | |
8bff82cb | 326 | |
bb6a7755 | 327 | platform_set_drvdata(pdev, nuc900_nand); |
8bff82cb WZ |
328 | |
329 | return retval; | |
330 | ||
bb6a7755 | 331 | fail3: iounmap(nuc900_nand->reg); |
8bff82cb | 332 | fail2: release_mem_region(res->start, resource_size(res)); |
bb6a7755 | 333 | fail1: kfree(nuc900_nand); |
8bff82cb WZ |
334 | return retval; |
335 | } | |
336 | ||
bb6a7755 | 337 | static int __devexit nuc900_nand_remove(struct platform_device *pdev) |
8bff82cb | 338 | { |
bb6a7755 | 339 | struct nuc900_nand *nuc900_nand = platform_get_drvdata(pdev); |
8bff82cb WZ |
340 | struct resource *res; |
341 | ||
43c6871c | 342 | nand_release(&nuc900_nand->mtd); |
bb6a7755 | 343 | iounmap(nuc900_nand->reg); |
8bff82cb WZ |
344 | |
345 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
346 | release_mem_region(res->start, resource_size(res)); | |
347 | ||
bb6a7755 DW |
348 | clk_disable(nuc900_nand->clk); |
349 | clk_put(nuc900_nand->clk); | |
8bff82cb | 350 | |
bb6a7755 | 351 | kfree(nuc900_nand); |
8bff82cb WZ |
352 | |
353 | platform_set_drvdata(pdev, NULL); | |
354 | ||
355 | return 0; | |
356 | } | |
357 | ||
bb6a7755 DW |
358 | static struct platform_driver nuc900_nand_driver = { |
359 | .probe = nuc900_nand_probe, | |
360 | .remove = __devexit_p(nuc900_nand_remove), | |
8bff82cb | 361 | .driver = { |
49f37b74 | 362 | .name = "nuc900-fmi", |
8bff82cb WZ |
363 | .owner = THIS_MODULE, |
364 | }, | |
365 | }; | |
366 | ||
bb6a7755 | 367 | static int __init nuc900_nand_init(void) |
8bff82cb | 368 | { |
bb6a7755 | 369 | return platform_driver_register(&nuc900_nand_driver); |
8bff82cb WZ |
370 | } |
371 | ||
bb6a7755 | 372 | static void __exit nuc900_nand_exit(void) |
8bff82cb | 373 | { |
bb6a7755 | 374 | platform_driver_unregister(&nuc900_nand_driver); |
8bff82cb WZ |
375 | } |
376 | ||
bb6a7755 DW |
377 | module_init(nuc900_nand_init); |
378 | module_exit(nuc900_nand_exit); | |
8bff82cb WZ |
379 | |
380 | MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>"); | |
bb6a7755 | 381 | MODULE_DESCRIPTION("w90p910/NUC9xx nand driver!"); |
8bff82cb | 382 | MODULE_LICENSE("GPL"); |
49f37b74 | 383 | MODULE_ALIAS("platform:nuc900-fmi"); |