]>
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: | |
8bff82cb WZ |
180 | return; |
181 | ||
182 | case NAND_CMD_RESET: | |
183 | if (chip->dev_ready) | |
184 | break; | |
185 | udelay(chip->chip_delay); | |
186 | ||
187 | write_cmd_reg(nand, NAND_CMD_STATUS); | |
188 | write_cmd_reg(nand, command); | |
189 | ||
bb6a7755 | 190 | while (!nuc900_check_rb(nand)) |
8bff82cb WZ |
191 | ; |
192 | ||
193 | return; | |
194 | ||
195 | case NAND_CMD_RNDOUT: | |
196 | write_cmd_reg(nand, NAND_CMD_RNDOUTSTART); | |
197 | return; | |
198 | ||
199 | case NAND_CMD_READ0: | |
200 | ||
201 | write_cmd_reg(nand, NAND_CMD_READSTART); | |
202 | default: | |
203 | ||
204 | if (!chip->dev_ready) { | |
205 | udelay(chip->chip_delay); | |
206 | return; | |
207 | } | |
208 | } | |
209 | ||
210 | /* Apply this short delay always to ensure that we do wait tWB in | |
211 | * any case on any machine. */ | |
212 | ndelay(100); | |
213 | ||
214 | while (!chip->dev_ready(mtd)) | |
215 | ; | |
216 | } | |
217 | ||
218 | ||
bb6a7755 | 219 | static void nuc900_nand_enable(struct nuc900_nand *nand) |
8bff82cb WZ |
220 | { |
221 | unsigned int val; | |
222 | spin_lock(&nand->lock); | |
223 | __raw_writel(RESET_FMI, (nand->reg + REG_FMICSR)); | |
224 | ||
225 | val = __raw_readl(nand->reg + REG_FMICSR); | |
226 | ||
227 | if (!(val & NAND_EN)) | |
228 | __raw_writel(val | NAND_EN, REG_FMICSR); | |
229 | ||
230 | val = __raw_readl(nand->reg + REG_SMCSR); | |
231 | ||
232 | val &= ~(SWRST|PSIZE|DMARWEN|BUSWID|ECC4EN|NANDCS); | |
233 | val |= WP; | |
234 | ||
235 | __raw_writel(val, nand->reg + REG_SMCSR); | |
236 | ||
237 | spin_unlock(&nand->lock); | |
238 | } | |
239 | ||
06f25510 | 240 | static int nuc900_nand_probe(struct platform_device *pdev) |
8bff82cb | 241 | { |
bb6a7755 | 242 | struct nuc900_nand *nuc900_nand; |
8bff82cb WZ |
243 | struct nand_chip *chip; |
244 | int retval; | |
245 | struct resource *res; | |
246 | ||
247 | retval = 0; | |
248 | ||
bb6a7755 DW |
249 | nuc900_nand = kzalloc(sizeof(struct nuc900_nand), GFP_KERNEL); |
250 | if (!nuc900_nand) | |
8bff82cb | 251 | return -ENOMEM; |
bb6a7755 | 252 | chip = &(nuc900_nand->chip); |
8bff82cb | 253 | |
bb6a7755 DW |
254 | nuc900_nand->mtd.priv = chip; |
255 | nuc900_nand->mtd.owner = THIS_MODULE; | |
256 | spin_lock_init(&nuc900_nand->lock); | |
8bff82cb | 257 | |
bb6a7755 DW |
258 | nuc900_nand->clk = clk_get(&pdev->dev, NULL); |
259 | if (IS_ERR(nuc900_nand->clk)) { | |
8bff82cb WZ |
260 | retval = -ENOENT; |
261 | goto fail1; | |
262 | } | |
bb6a7755 DW |
263 | clk_enable(nuc900_nand->clk); |
264 | ||
265 | chip->cmdfunc = nuc900_nand_command_lp; | |
266 | chip->dev_ready = nuc900_nand_devready; | |
267 | chip->read_byte = nuc900_nand_read_byte; | |
268 | chip->write_buf = nuc900_nand_write_buf; | |
269 | chip->read_buf = nuc900_nand_read_buf; | |
8bff82cb WZ |
270 | chip->chip_delay = 50; |
271 | chip->options = 0; | |
272 | chip->ecc.mode = NAND_ECC_SOFT; | |
273 | ||
274 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
275 | if (!res) { | |
276 | retval = -ENXIO; | |
277 | goto fail1; | |
278 | } | |
279 | ||
280 | if (!request_mem_region(res->start, resource_size(res), pdev->name)) { | |
281 | retval = -EBUSY; | |
282 | goto fail1; | |
283 | } | |
284 | ||
bb6a7755 DW |
285 | nuc900_nand->reg = ioremap(res->start, resource_size(res)); |
286 | if (!nuc900_nand->reg) { | |
8bff82cb WZ |
287 | retval = -ENOMEM; |
288 | goto fail2; | |
289 | } | |
290 | ||
bb6a7755 | 291 | nuc900_nand_enable(nuc900_nand); |
8bff82cb | 292 | |
bb6a7755 | 293 | if (nand_scan(&(nuc900_nand->mtd), 1)) { |
8bff82cb WZ |
294 | retval = -ENXIO; |
295 | goto fail3; | |
296 | } | |
297 | ||
ee0e87b1 JI |
298 | mtd_device_register(&(nuc900_nand->mtd), partitions, |
299 | ARRAY_SIZE(partitions)); | |
8bff82cb | 300 | |
bb6a7755 | 301 | platform_set_drvdata(pdev, nuc900_nand); |
8bff82cb WZ |
302 | |
303 | return retval; | |
304 | ||
bb6a7755 | 305 | fail3: iounmap(nuc900_nand->reg); |
8bff82cb | 306 | fail2: release_mem_region(res->start, resource_size(res)); |
bb6a7755 | 307 | fail1: kfree(nuc900_nand); |
8bff82cb WZ |
308 | return retval; |
309 | } | |
310 | ||
810b7e06 | 311 | static int nuc900_nand_remove(struct platform_device *pdev) |
8bff82cb | 312 | { |
bb6a7755 | 313 | struct nuc900_nand *nuc900_nand = platform_get_drvdata(pdev); |
8bff82cb WZ |
314 | struct resource *res; |
315 | ||
43c6871c | 316 | nand_release(&nuc900_nand->mtd); |
bb6a7755 | 317 | iounmap(nuc900_nand->reg); |
8bff82cb WZ |
318 | |
319 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
320 | release_mem_region(res->start, resource_size(res)); | |
321 | ||
bb6a7755 DW |
322 | clk_disable(nuc900_nand->clk); |
323 | clk_put(nuc900_nand->clk); | |
8bff82cb | 324 | |
bb6a7755 | 325 | kfree(nuc900_nand); |
8bff82cb | 326 | |
8bff82cb WZ |
327 | return 0; |
328 | } | |
329 | ||
bb6a7755 DW |
330 | static struct platform_driver nuc900_nand_driver = { |
331 | .probe = nuc900_nand_probe, | |
5153b88c | 332 | .remove = nuc900_nand_remove, |
8bff82cb | 333 | .driver = { |
49f37b74 | 334 | .name = "nuc900-fmi", |
8bff82cb WZ |
335 | .owner = THIS_MODULE, |
336 | }, | |
337 | }; | |
338 | ||
f99640de | 339 | module_platform_driver(nuc900_nand_driver); |
8bff82cb WZ |
340 | |
341 | MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>"); | |
bb6a7755 | 342 | MODULE_DESCRIPTION("w90p910/NUC9xx nand driver!"); |
8bff82cb | 343 | MODULE_LICENSE("GPL"); |
49f37b74 | 344 | MODULE_ALIAS("platform:nuc900-fmi"); |