diff options
author | Thomas Gleixner <tglx@cruncher.tec.linutronix.de> | 2006-05-23 05:43:28 -0400 |
---|---|---|
committer | Thomas Gleixner <tglx@cruncher.tec.linutronix.de> | 2006-05-23 05:43:28 -0400 |
commit | ce4c61f184864991881ec789f7524f4b332eaafc (patch) | |
tree | 405bc1011d919c3b83f252c49a51f40e7d5de536 /drivers/mtd/nand/ndfc.c | |
parent | 41796c2ea9b74cdf3bc2c368193d15b8ae8950ca (diff) |
[MTD] Add support for NDFC NAND controller
NDFC NAND Flash controller is embedded in PPC EP44x SoCs.
Add platform driver based support.
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Diffstat (limited to 'drivers/mtd/nand/ndfc.c')
-rw-r--r-- | drivers/mtd/nand/ndfc.c | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/drivers/mtd/nand/ndfc.c b/drivers/mtd/nand/ndfc.c new file mode 100644 index 000000000000..22fd682b70ca --- /dev/null +++ b/drivers/mtd/nand/ndfc.c | |||
@@ -0,0 +1,317 @@ | |||
1 | /* | ||
2 | * drivers/mtd/ndfc.c | ||
3 | * | ||
4 | * Overview: | ||
5 | * Platform independend driver for NDFC (NanD Flash Controller) | ||
6 | * integrated into EP440 cores | ||
7 | * | ||
8 | * Author: Thomas Gleixner | ||
9 | * | ||
10 | * Copyright 2006 IBM | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or modify it | ||
13 | * under the terms of the GNU General Public License as published by the | ||
14 | * Free Software Foundation; either version 2 of the License, or (at your | ||
15 | * option) any later version. | ||
16 | * | ||
17 | */ | ||
18 | #include <linux/module.h> | ||
19 | #include <linux/mtd/nand.h> | ||
20 | #include <linux/mtd/nand_ecc.h> | ||
21 | #include <linux/mtd/partitions.h> | ||
22 | #include <linux/mtd/ndfc.h> | ||
23 | #include <linux/mtd/ubi.h> | ||
24 | #include <linux/mtd/mtd.h> | ||
25 | #include <linux/platform_device.h> | ||
26 | |||
27 | #include <asm/io.h> | ||
28 | #include <asm/ibm44x.h> | ||
29 | |||
30 | struct ndfc_nand_mtd { | ||
31 | struct mtd_info mtd; | ||
32 | struct nand_chip chip; | ||
33 | struct platform_nand_chip *pl_chip; | ||
34 | }; | ||
35 | |||
36 | static struct ndfc_nand_mtd ndfc_mtd[NDFC_MAX_BANKS]; | ||
37 | |||
38 | struct ndfc_controller { | ||
39 | void __iomem *ndfcbase; | ||
40 | struct nand_hw_control ndfc_control; | ||
41 | atomic_t childs_active; | ||
42 | }; | ||
43 | |||
44 | static struct ndfc_controller ndfc_ctrl; | ||
45 | |||
46 | static void ndfc_select_chip(struct mtd_info *mtd, int chip) | ||
47 | { | ||
48 | uint32_t ccr; | ||
49 | struct ndfc_controller *ndfc = &ndfc_ctrl; | ||
50 | struct nand_chip *nandchip = mtd->priv; | ||
51 | struct ndfc_nand_mtd *nandmtd = nandchip->priv; | ||
52 | struct platform_nand_chip *pchip = nandmtd->pl_chip; | ||
53 | |||
54 | ccr = __raw_readl(ndfc->ndfcbase + NDFC_CCR); | ||
55 | if (chip >= 0) { | ||
56 | ccr &= ~NDFC_CCR_BS_MASK; | ||
57 | ccr |= NDFC_CCR_BS(chip + pchip->chip_offset); | ||
58 | } else | ||
59 | ccr |= NDFC_CCR_RESET_CE; | ||
60 | writel(ccr, ndfc->ndfcbase + NDFC_CCR); | ||
61 | } | ||
62 | |||
63 | static void ndfc_hwcontrol(struct mtd_info *mtd, int cmd) | ||
64 | { | ||
65 | struct ndfc_controller *ndfc = &ndfc_ctrl; | ||
66 | struct nand_chip *chip = mtd->priv; | ||
67 | |||
68 | switch (cmd) { | ||
69 | case NAND_CTL_SETCLE: | ||
70 | chip->IO_ADDR_W = ndfc->ndfcbase + NDFC_CMD; | ||
71 | break; | ||
72 | case NAND_CTL_SETALE: | ||
73 | chip->IO_ADDR_W = ndfc->ndfcbase + NDFC_ALE; | ||
74 | break; | ||
75 | default: | ||
76 | chip->IO_ADDR_W = ndfc->ndfcbase + NDFC_DATA; | ||
77 | break; | ||
78 | } | ||
79 | } | ||
80 | |||
81 | static int ndfc_ready(struct mtd_info *mtd) | ||
82 | { | ||
83 | struct ndfc_controller *ndfc = &ndfc_ctrl; | ||
84 | |||
85 | return __raw_readl(ndfc->ndfcbase + NDFC_STAT) & NDFC_STAT_IS_READY; | ||
86 | } | ||
87 | |||
88 | static void ndfc_enable_hwecc(struct mtd_info *mtd, int mode) | ||
89 | { | ||
90 | uint32_t ccr; | ||
91 | struct ndfc_controller *ndfc = &ndfc_ctrl; | ||
92 | |||
93 | ccr = __raw_readl(ndfc->ndfcbase + NDFC_CCR); | ||
94 | ccr |= NDFC_CCR_RESET_ECC; | ||
95 | __raw_writel(ccr, ndfc->ndfcbase + NDFC_CCR); | ||
96 | wmb(); | ||
97 | } | ||
98 | |||
99 | static int ndfc_calculate_ecc(struct mtd_info *mtd, | ||
100 | const u_char *dat, u_char *ecc_code) | ||
101 | { | ||
102 | struct ndfc_controller *ndfc = &ndfc_ctrl; | ||
103 | uint32_t ecc; | ||
104 | uint8_t *p = (uint8_t *)&ecc; | ||
105 | |||
106 | wmb(); | ||
107 | ecc = __raw_readl(ndfc->ndfcbase + NDFC_ECC); | ||
108 | ecc_code[0] = p[1]; | ||
109 | ecc_code[1] = p[2]; | ||
110 | ecc_code[2] = p[3]; | ||
111 | |||
112 | return 0; | ||
113 | } | ||
114 | |||
115 | /* | ||
116 | * Speedups for buffer read/write/verify | ||
117 | * | ||
118 | * NDFC allows 32bit read/write of data. So we can speed up the buffer | ||
119 | * functions. No further checking, as nand_base will always read/write | ||
120 | * page aligned. | ||
121 | */ | ||
122 | static void ndfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) | ||
123 | { | ||
124 | struct ndfc_controller *ndfc = &ndfc_ctrl; | ||
125 | uint32_t *p = (uint32_t *) buf; | ||
126 | |||
127 | for(;len > 0; len -= 4) | ||
128 | *p++ = __raw_readl(ndfc->ndfcbase + NDFC_DATA); | ||
129 | } | ||
130 | |||
131 | static void ndfc_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) | ||
132 | { | ||
133 | struct ndfc_controller *ndfc = &ndfc_ctrl; | ||
134 | uint32_t *p = (uint32_t *) buf; | ||
135 | |||
136 | for(;len > 0; len -= 4) | ||
137 | __raw_writel(*p++, ndfc->ndfcbase + NDFC_DATA); | ||
138 | } | ||
139 | |||
140 | static int ndfc_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len) | ||
141 | { | ||
142 | struct ndfc_controller *ndfc = &ndfc_ctrl; | ||
143 | uint32_t *p = (uint32_t *) buf; | ||
144 | |||
145 | for(;len > 0; len -= 4) | ||
146 | if (*p++ != __raw_readl(ndfc->ndfcbase + NDFC_DATA)) | ||
147 | return -EFAULT; | ||
148 | return 0; | ||
149 | } | ||
150 | |||
151 | /* | ||
152 | * Initialize chip structure | ||
153 | */ | ||
154 | static void ndfc_chip_init(struct ndfc_nand_mtd *mtd) | ||
155 | { | ||
156 | struct ndfc_controller *ndfc = &ndfc_ctrl; | ||
157 | struct nand_chip *chip = &mtd->chip; | ||
158 | |||
159 | chip->IO_ADDR_R = ndfc->ndfcbase + NDFC_DATA; | ||
160 | chip->IO_ADDR_W = ndfc->ndfcbase + NDFC_DATA; | ||
161 | chip->hwcontrol = ndfc_hwcontrol; | ||
162 | chip->dev_ready = ndfc_ready; | ||
163 | chip->select_chip = ndfc_select_chip; | ||
164 | chip->chip_delay = 50; | ||
165 | chip->priv = mtd; | ||
166 | chip->options = mtd->pl_chip->options; | ||
167 | chip->controller = &ndfc->ndfc_control; | ||
168 | chip->read_buf = ndfc_read_buf; | ||
169 | chip->write_buf = ndfc_write_buf; | ||
170 | chip->verify_buf = ndfc_verify_buf; | ||
171 | chip->correct_data = nand_correct_data; | ||
172 | chip->enable_hwecc = ndfc_enable_hwecc; | ||
173 | chip->calculate_ecc = ndfc_calculate_ecc; | ||
174 | chip->eccmode = NAND_ECC_HW3_256; | ||
175 | chip->autooob = mtd->pl_chip->autooob; | ||
176 | mtd->mtd.priv = chip; | ||
177 | mtd->mtd.owner = THIS_MODULE; | ||
178 | } | ||
179 | |||
180 | static int ndfc_chip_probe(struct platform_device *pdev) | ||
181 | { | ||
182 | int rc; | ||
183 | struct platform_nand_chip *nc = pdev->dev.platform_data; | ||
184 | struct ndfc_chip_settings *settings = nc->priv; | ||
185 | struct ndfc_controller *ndfc = &ndfc_ctrl; | ||
186 | struct ndfc_nand_mtd *nandmtd; | ||
187 | |||
188 | if (nc->chip_offset >= NDFC_MAX_BANKS || nc->nr_chips > NDFC_MAX_BANKS) | ||
189 | return -EINVAL; | ||
190 | |||
191 | /* Set the bank settings */ | ||
192 | __raw_writel(settings->bank_settings, | ||
193 | ndfc->ndfcbase + NDFC_BCFG0 + (nc->chip_offset << 2)); | ||
194 | |||
195 | nandmtd = &ndfc_mtd[pdev->id]; | ||
196 | if (nandmtd->pl_chip) | ||
197 | return -EBUSY; | ||
198 | |||
199 | nandmtd->pl_chip = nc; | ||
200 | ndfc_chip_init(nandmtd); | ||
201 | |||
202 | /* Scan for chips */ | ||
203 | if (nand_scan(&nandmtd->mtd, nc->nr_chips)) { | ||
204 | nandmtd->pl_chip = NULL; | ||
205 | return -ENODEV; | ||
206 | } | ||
207 | |||
208 | #ifdef CONFIG_MTD_PARTITIONS | ||
209 | printk("Number of partitions %d\n", nc->nr_partitions); | ||
210 | if (nc->nr_partitions) { | ||
211 | struct mtd_info *mtd_ubi; | ||
212 | nc->partitions[NAND_PARTS_CONTENT_IDX].mtdp = &mtd_ubi; | ||
213 | |||
214 | add_mtd_device(&nandmtd->mtd); /* for testing */ | ||
215 | add_mtd_partitions(&nandmtd->mtd, | ||
216 | nc->partitions, | ||
217 | nc->nr_partitions); | ||
218 | |||
219 | add_mtd_device(mtd_ubi); | ||
220 | |||
221 | } else | ||
222 | #else | ||
223 | add_mtd_device(&nandmtd->mtd); | ||
224 | #endif | ||
225 | |||
226 | atomic_inc(&ndfc->childs_active); | ||
227 | return 0; | ||
228 | } | ||
229 | |||
230 | static int ndfc_chip_remove(struct platform_device *pdev) | ||
231 | { | ||
232 | return 0; | ||
233 | } | ||
234 | |||
235 | static int ndfc_nand_probe(struct platform_device *pdev) | ||
236 | { | ||
237 | struct platform_nand_ctrl *nc = pdev->dev.platform_data; | ||
238 | struct ndfc_controller_settings *settings = nc->priv; | ||
239 | struct resource *res = pdev->resource; | ||
240 | struct ndfc_controller *ndfc = &ndfc_ctrl; | ||
241 | unsigned long long phys = NDFC_PHYSADDR_OFFS | res->start; | ||
242 | |||
243 | ndfc->ndfcbase = ioremap64(phys, res->end - res->start + 1); | ||
244 | if (!ndfc->ndfcbase) { | ||
245 | printk(KERN_ERR "NDFC: ioremap failed\n"); | ||
246 | return -EIO; | ||
247 | } | ||
248 | |||
249 | __raw_writel(settings->ccr_settings, ndfc->ndfcbase + NDFC_CCR); | ||
250 | |||
251 | spin_lock_init(&ndfc->ndfc_control.lock); | ||
252 | init_waitqueue_head(&ndfc->ndfc_control.wq); | ||
253 | |||
254 | platform_set_drvdata(pdev, ndfc); | ||
255 | |||
256 | printk("NDFC NAND Driver initialized. Chip-Rev: 0x%08x\n", | ||
257 | __raw_readl(ndfc->ndfcbase + NDFC_REVID)); | ||
258 | |||
259 | return 0; | ||
260 | } | ||
261 | |||
262 | static int ndfc_nand_remove(struct platform_device *pdev) | ||
263 | { | ||
264 | struct ndfc_controller *ndfc = platform_get_drvdata(pdev); | ||
265 | |||
266 | if (atomic_read(&ndfc->childs_active)) | ||
267 | return -EBUSY; | ||
268 | |||
269 | if (ndfc) { | ||
270 | platform_set_drvdata(pdev, NULL); | ||
271 | iounmap(ndfc_ctrl.ndfcbase); | ||
272 | ndfc_ctrl.ndfcbase = NULL; | ||
273 | } | ||
274 | return 0; | ||
275 | } | ||
276 | |||
277 | /* driver device registration */ | ||
278 | |||
279 | static struct platform_driver ndfc_chip_driver = { | ||
280 | .probe = ndfc_chip_probe, | ||
281 | .remove = ndfc_chip_remove, | ||
282 | .driver = { | ||
283 | .name = "ndfc-chip", | ||
284 | .owner = THIS_MODULE, | ||
285 | }, | ||
286 | }; | ||
287 | |||
288 | static struct platform_driver ndfc_nand_driver = { | ||
289 | .probe = ndfc_nand_probe, | ||
290 | .remove = ndfc_nand_remove, | ||
291 | .driver = { | ||
292 | .name = "ndfc-nand", | ||
293 | .owner = THIS_MODULE, | ||
294 | }, | ||
295 | }; | ||
296 | |||
297 | static int __init ndfc_nand_init(void) | ||
298 | { | ||
299 | int ret = platform_driver_register(&ndfc_nand_driver); | ||
300 | |||
301 | if (!ret) | ||
302 | ret = platform_driver_register(&ndfc_chip_driver); | ||
303 | return ret; | ||
304 | } | ||
305 | |||
306 | static void __exit ndfc_nand_exit(void) | ||
307 | { | ||
308 | platform_driver_unregister(&ndfc_chip_driver); | ||
309 | platform_driver_unregister(&ndfc_nand_driver); | ||
310 | } | ||
311 | |||
312 | module_init(ndfc_nand_init); | ||
313 | module_exit(ndfc_nand_exit); | ||
314 | |||
315 | MODULE_LICENSE("GPL"); | ||
316 | MODULE_AUTHOR("Thomas Gleixner <tglx@linutronix.de>"); | ||
317 | MODULE_DESCRIPTION("Platform driver for NDFC"); | ||