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 | 115 | static int nuc900_check_rb(struct nuc900_nand *nand) |
8bff82cb WZ |
116 | { |
117 | unsigned int val; | |
118 | spin_lock(&nand->lock); | |
119 | val = __raw_readl(REG_SMISR); | |
120 | val &= READYBUSY; | |
121 | spin_unlock(&nand->lock); | |
122 | ||
123 | return val; | |
124 | } | |
125 | ||
bb6a7755 | 126 | static int nuc900_nand_devready(struct mtd_info *mtd) |
8bff82cb | 127 | { |
bb6a7755 | 128 | struct nuc900_nand *nand; |
8bff82cb WZ |
129 | int ready; |
130 | ||
bb6a7755 | 131 | nand = container_of(mtd, struct nuc900_nand, mtd); |
8bff82cb | 132 | |
bb6a7755 | 133 | ready = (nuc900_check_rb(nand)) ? 1 : 0; |
8bff82cb WZ |
134 | return ready; |
135 | } | |
136 | ||
bb6a7755 DW |
137 | static void nuc900_nand_command_lp(struct mtd_info *mtd, unsigned int command, |
138 | int column, int page_addr) | |
8bff82cb WZ |
139 | { |
140 | register struct nand_chip *chip = mtd->priv; | |
bb6a7755 | 141 | struct nuc900_nand *nand; |
8bff82cb | 142 | |
bb6a7755 | 143 | nand = container_of(mtd, struct nuc900_nand, mtd); |
8bff82cb WZ |
144 | |
145 | if (command == NAND_CMD_READOOB) { | |
146 | column += mtd->writesize; | |
147 | command = NAND_CMD_READ0; | |
148 | } | |
149 | ||
150 | write_cmd_reg(nand, command & 0xff); | |
151 | ||
152 | if (column != -1 || page_addr != -1) { | |
153 | ||
154 | if (column != -1) { | |
155 | if (chip->options & NAND_BUSWIDTH_16) | |
156 | column >>= 1; | |
157 | write_addr_reg(nand, column); | |
158 | write_addr_reg(nand, column >> 8 | ENDADDR); | |
159 | } | |
160 | if (page_addr != -1) { | |
161 | write_addr_reg(nand, page_addr); | |
162 | ||
163 | if (chip->chipsize > (128 << 20)) { | |
164 | write_addr_reg(nand, page_addr >> 8); | |
165 | write_addr_reg(nand, page_addr >> 16 | ENDADDR); | |
166 | } else { | |
167 | write_addr_reg(nand, page_addr >> 8 | ENDADDR); | |
168 | } | |
169 | } | |
170 | } | |
171 | ||
172 | switch (command) { | |
173 | case NAND_CMD_CACHEDPROG: | |
174 | case NAND_CMD_PAGEPROG: | |
175 | case NAND_CMD_ERASE1: | |
176 | case NAND_CMD_ERASE2: | |
177 | case NAND_CMD_SEQIN: | |
178 | case NAND_CMD_RNDIN: | |
179 | case NAND_CMD_STATUS: | |
180 | case NAND_CMD_DEPLETE1: | |
181 | return; | |
182 | ||
183 | case NAND_CMD_STATUS_ERROR: | |
184 | case NAND_CMD_STATUS_ERROR0: | |
185 | case NAND_CMD_STATUS_ERROR1: | |
186 | case NAND_CMD_STATUS_ERROR2: | |
187 | case NAND_CMD_STATUS_ERROR3: | |
188 | udelay(chip->chip_delay); | |
189 | return; | |
190 | ||
191 | case NAND_CMD_RESET: | |
192 | if (chip->dev_ready) | |
193 | break; | |
194 | udelay(chip->chip_delay); | |
195 | ||
196 | write_cmd_reg(nand, NAND_CMD_STATUS); | |
197 | write_cmd_reg(nand, command); | |
198 | ||
bb6a7755 | 199 | while (!nuc900_check_rb(nand)) |
8bff82cb WZ |
200 | ; |
201 | ||
202 | return; | |
203 | ||
204 | case NAND_CMD_RNDOUT: | |
205 | write_cmd_reg(nand, NAND_CMD_RNDOUTSTART); | |
206 | return; | |
207 | ||
208 | case NAND_CMD_READ0: | |
209 | ||
210 | write_cmd_reg(nand, NAND_CMD_READSTART); | |
211 | default: | |
212 | ||
213 | if (!chip->dev_ready) { | |
214 | udelay(chip->chip_delay); | |
215 | return; | |
216 | } | |
217 | } | |
218 | ||
219 | /* Apply this short delay always to ensure that we do wait tWB in | |
220 | * any case on any machine. */ | |
221 | ndelay(100); | |
222 | ||
223 | while (!chip->dev_ready(mtd)) | |
224 | ; | |
225 | } | |
226 | ||
227 | ||
bb6a7755 | 228 | static void nuc900_nand_enable(struct nuc900_nand *nand) |
8bff82cb WZ |
229 | { |
230 | unsigned int val; | |
231 | spin_lock(&nand->lock); | |
232 | __raw_writel(RESET_FMI, (nand->reg + REG_FMICSR)); | |
233 | ||
234 | val = __raw_readl(nand->reg + REG_FMICSR); | |
235 | ||
236 | if (!(val & NAND_EN)) | |
237 | __raw_writel(val | NAND_EN, REG_FMICSR); | |
238 | ||
239 | val = __raw_readl(nand->reg + REG_SMCSR); | |
240 | ||
241 | val &= ~(SWRST|PSIZE|DMARWEN|BUSWID|ECC4EN|NANDCS); | |
242 | val |= WP; | |
243 | ||
244 | __raw_writel(val, nand->reg + REG_SMCSR); | |
245 | ||
246 | spin_unlock(&nand->lock); | |
247 | } | |
248 | ||
bb6a7755 | 249 | static int __devinit nuc900_nand_probe(struct platform_device *pdev) |
8bff82cb | 250 | { |
bb6a7755 | 251 | struct nuc900_nand *nuc900_nand; |
8bff82cb WZ |
252 | struct nand_chip *chip; |
253 | int retval; | |
254 | struct resource *res; | |
255 | ||
256 | retval = 0; | |
257 | ||
bb6a7755 DW |
258 | nuc900_nand = kzalloc(sizeof(struct nuc900_nand), GFP_KERNEL); |
259 | if (!nuc900_nand) | |
8bff82cb | 260 | return -ENOMEM; |
bb6a7755 | 261 | chip = &(nuc900_nand->chip); |
8bff82cb | 262 | |
bb6a7755 DW |
263 | nuc900_nand->mtd.priv = chip; |
264 | nuc900_nand->mtd.owner = THIS_MODULE; | |
265 | spin_lock_init(&nuc900_nand->lock); | |
8bff82cb | 266 | |
bb6a7755 DW |
267 | nuc900_nand->clk = clk_get(&pdev->dev, NULL); |
268 | if (IS_ERR(nuc900_nand->clk)) { | |
8bff82cb WZ |
269 | retval = -ENOENT; |
270 | goto fail1; | |
271 | } | |
bb6a7755 DW |
272 | clk_enable(nuc900_nand->clk); |
273 | ||
274 | chip->cmdfunc = nuc900_nand_command_lp; | |
275 | chip->dev_ready = nuc900_nand_devready; | |
276 | chip->read_byte = nuc900_nand_read_byte; | |
277 | chip->write_buf = nuc900_nand_write_buf; | |
278 | chip->read_buf = nuc900_nand_read_buf; | |
8bff82cb WZ |
279 | chip->chip_delay = 50; |
280 | chip->options = 0; | |
281 | chip->ecc.mode = NAND_ECC_SOFT; | |
282 | ||
283 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
284 | if (!res) { | |
285 | retval = -ENXIO; | |
286 | goto fail1; | |
287 | } | |
288 | ||
289 | if (!request_mem_region(res->start, resource_size(res), pdev->name)) { | |
290 | retval = -EBUSY; | |
291 | goto fail1; | |
292 | } | |
293 | ||
bb6a7755 DW |
294 | nuc900_nand->reg = ioremap(res->start, resource_size(res)); |
295 | if (!nuc900_nand->reg) { | |
8bff82cb WZ |
296 | retval = -ENOMEM; |
297 | goto fail2; | |
298 | } | |
299 | ||
bb6a7755 | 300 | nuc900_nand_enable(nuc900_nand); |
8bff82cb | 301 | |
bb6a7755 | 302 | if (nand_scan(&(nuc900_nand->mtd), 1)) { |
8bff82cb WZ |
303 | retval = -ENXIO; |
304 | goto fail3; | |
305 | } | |
306 | ||
ee0e87b1 JI |
307 | mtd_device_register(&(nuc900_nand->mtd), partitions, |
308 | ARRAY_SIZE(partitions)); | |
8bff82cb | 309 | |
bb6a7755 | 310 | platform_set_drvdata(pdev, nuc900_nand); |
8bff82cb WZ |
311 | |
312 | return retval; | |
313 | ||
bb6a7755 | 314 | fail3: iounmap(nuc900_nand->reg); |
8bff82cb | 315 | fail2: release_mem_region(res->start, resource_size(res)); |
bb6a7755 | 316 | fail1: kfree(nuc900_nand); |
8bff82cb WZ |
317 | return retval; |
318 | } | |
319 | ||
bb6a7755 | 320 | static int __devexit nuc900_nand_remove(struct platform_device *pdev) |
8bff82cb | 321 | { |
bb6a7755 | 322 | struct nuc900_nand *nuc900_nand = platform_get_drvdata(pdev); |
8bff82cb WZ |
323 | struct resource *res; |
324 | ||
43c6871c | 325 | nand_release(&nuc900_nand->mtd); |
bb6a7755 | 326 | iounmap(nuc900_nand->reg); |
8bff82cb WZ |
327 | |
328 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
329 | release_mem_region(res->start, resource_size(res)); | |
330 | ||
bb6a7755 DW |
331 | clk_disable(nuc900_nand->clk); |
332 | clk_put(nuc900_nand->clk); | |
8bff82cb | 333 | |
bb6a7755 | 334 | kfree(nuc900_nand); |
8bff82cb WZ |
335 | |
336 | platform_set_drvdata(pdev, NULL); | |
337 | ||
338 | return 0; | |
339 | } | |
340 | ||
bb6a7755 DW |
341 | static struct platform_driver nuc900_nand_driver = { |
342 | .probe = nuc900_nand_probe, | |
343 | .remove = __devexit_p(nuc900_nand_remove), | |
8bff82cb | 344 | .driver = { |
49f37b74 | 345 | .name = "nuc900-fmi", |
8bff82cb WZ |
346 | .owner = THIS_MODULE, |
347 | }, | |
348 | }; | |
349 | ||
f99640de | 350 | module_platform_driver(nuc900_nand_driver); |
8bff82cb WZ |
351 | |
352 | MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>"); | |
bb6a7755 | 353 | MODULE_DESCRIPTION("w90p910/NUC9xx nand driver!"); |
8bff82cb | 354 | MODULE_LICENSE("GPL"); |
49f37b74 | 355 | MODULE_ALIAS("platform:nuc900-fmi"); |