Commit | Line | Data |
---|---|---|
ce4c61f1 TG |
1 | /* |
2 | * drivers/mtd/ndfc.c | |
3 | * | |
4 | * Overview: | |
a808ad3b | 5 | * Platform independent driver for NDFC (NanD Flash Controller) |
ce4c61f1 TG |
6 | * integrated into EP440 cores |
7 | * | |
a808ad3b SM |
8 | * Ported to an OF platform driver by Sean MacLennan |
9 | * | |
10 | * The NDFC supports multiple chips, but this driver only supports a | |
11 | * single chip since I do not have access to any boards with | |
12 | * multiple chips. | |
13 | * | |
ce4c61f1 TG |
14 | * Author: Thomas Gleixner |
15 | * | |
16 | * Copyright 2006 IBM | |
a808ad3b SM |
17 | * Copyright 2008 PIKA Technologies |
18 | * Sean MacLennan <smaclennan@pikatech.com> | |
ce4c61f1 TG |
19 | * |
20 | * This program is free software; you can redistribute it and/or modify it | |
21 | * under the terms of the GNU General Public License as published by the | |
22 | * Free Software Foundation; either version 2 of the License, or (at your | |
23 | * option) any later version. | |
24 | * | |
25 | */ | |
26 | #include <linux/module.h> | |
27 | #include <linux/mtd/nand.h> | |
28 | #include <linux/mtd/nand_ecc.h> | |
29 | #include <linux/mtd/partitions.h> | |
30 | #include <linux/mtd/ndfc.h> | |
ce4c61f1 | 31 | #include <linux/mtd/mtd.h> |
a808ad3b | 32 | #include <linux/of_platform.h> |
ce4c61f1 | 33 | #include <asm/io.h> |
ce4c61f1 | 34 | |
ce4c61f1 TG |
35 | |
36 | struct ndfc_controller { | |
a808ad3b SM |
37 | struct of_device *ofdev; |
38 | void __iomem *ndfcbase; | |
39 | struct mtd_info mtd; | |
40 | struct nand_chip chip; | |
41 | int chip_select; | |
42 | struct nand_hw_control ndfc_control; | |
43 | #ifdef CONFIG_MTD_PARTITIONS | |
44 | struct mtd_partition *parts; | |
45 | #endif | |
ce4c61f1 TG |
46 | }; |
47 | ||
48 | static struct ndfc_controller ndfc_ctrl; | |
49 | ||
50 | static void ndfc_select_chip(struct mtd_info *mtd, int chip) | |
51 | { | |
52 | uint32_t ccr; | |
53 | struct ndfc_controller *ndfc = &ndfc_ctrl; | |
ce4c61f1 | 54 | |
a808ad3b | 55 | ccr = in_be32(ndfc->ndfcbase + NDFC_CCR); |
ce4c61f1 TG |
56 | if (chip >= 0) { |
57 | ccr &= ~NDFC_CCR_BS_MASK; | |
a808ad3b | 58 | ccr |= NDFC_CCR_BS(chip + ndfc->chip_select); |
ce4c61f1 TG |
59 | } else |
60 | ccr |= NDFC_CCR_RESET_CE; | |
a808ad3b | 61 | out_be32(ndfc->ndfcbase + NDFC_CCR, ccr); |
ce4c61f1 TG |
62 | } |
63 | ||
7abd3ef9 | 64 | static void ndfc_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) |
ce4c61f1 | 65 | { |
1794c130 | 66 | struct ndfc_controller *ndfc = &ndfc_ctrl; |
ce4c61f1 | 67 | |
7abd3ef9 TG |
68 | if (cmd == NAND_CMD_NONE) |
69 | return; | |
70 | ||
71 | if (ctrl & NAND_CLE) | |
1794c130 | 72 | writel(cmd & 0xFF, ndfc->ndfcbase + NDFC_CMD); |
7abd3ef9 | 73 | else |
1794c130 | 74 | writel(cmd & 0xFF, ndfc->ndfcbase + NDFC_ALE); |
ce4c61f1 TG |
75 | } |
76 | ||
77 | static int ndfc_ready(struct mtd_info *mtd) | |
78 | { | |
79 | struct ndfc_controller *ndfc = &ndfc_ctrl; | |
80 | ||
a808ad3b | 81 | return in_be32(ndfc->ndfcbase + NDFC_STAT) & NDFC_STAT_IS_READY; |
ce4c61f1 TG |
82 | } |
83 | ||
84 | static void ndfc_enable_hwecc(struct mtd_info *mtd, int mode) | |
85 | { | |
86 | uint32_t ccr; | |
87 | struct ndfc_controller *ndfc = &ndfc_ctrl; | |
88 | ||
a808ad3b | 89 | ccr = in_be32(ndfc->ndfcbase + NDFC_CCR); |
ce4c61f1 | 90 | ccr |= NDFC_CCR_RESET_ECC; |
a808ad3b | 91 | out_be32(ndfc->ndfcbase + NDFC_CCR, ccr); |
ce4c61f1 TG |
92 | wmb(); |
93 | } | |
94 | ||
95 | static int ndfc_calculate_ecc(struct mtd_info *mtd, | |
96 | const u_char *dat, u_char *ecc_code) | |
97 | { | |
98 | struct ndfc_controller *ndfc = &ndfc_ctrl; | |
99 | uint32_t ecc; | |
100 | uint8_t *p = (uint8_t *)&ecc; | |
101 | ||
102 | wmb(); | |
a808ad3b SM |
103 | ecc = in_be32(ndfc->ndfcbase + NDFC_ECC); |
104 | /* The NDFC uses Smart Media (SMC) bytes order */ | |
105 | ecc_code[0] = p[2]; | |
106 | ecc_code[1] = p[1]; | |
ce4c61f1 TG |
107 | ecc_code[2] = p[3]; |
108 | ||
109 | return 0; | |
110 | } | |
111 | ||
112 | /* | |
113 | * Speedups for buffer read/write/verify | |
114 | * | |
115 | * NDFC allows 32bit read/write of data. So we can speed up the buffer | |
116 | * functions. No further checking, as nand_base will always read/write | |
117 | * page aligned. | |
118 | */ | |
119 | static void ndfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) | |
120 | { | |
121 | struct ndfc_controller *ndfc = &ndfc_ctrl; | |
122 | uint32_t *p = (uint32_t *) buf; | |
123 | ||
124 | for(;len > 0; len -= 4) | |
a808ad3b | 125 | *p++ = in_be32(ndfc->ndfcbase + NDFC_DATA); |
ce4c61f1 TG |
126 | } |
127 | ||
128 | static void ndfc_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) | |
129 | { | |
130 | struct ndfc_controller *ndfc = &ndfc_ctrl; | |
131 | uint32_t *p = (uint32_t *) buf; | |
132 | ||
133 | for(;len > 0; len -= 4) | |
a808ad3b | 134 | out_be32(ndfc->ndfcbase + NDFC_DATA, *p++); |
ce4c61f1 TG |
135 | } |
136 | ||
137 | static int ndfc_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len) | |
138 | { | |
139 | struct ndfc_controller *ndfc = &ndfc_ctrl; | |
140 | uint32_t *p = (uint32_t *) buf; | |
141 | ||
142 | for(;len > 0; len -= 4) | |
a808ad3b | 143 | if (*p++ != in_be32(ndfc->ndfcbase + NDFC_DATA)) |
ce4c61f1 TG |
144 | return -EFAULT; |
145 | return 0; | |
146 | } | |
147 | ||
148 | /* | |
149 | * Initialize chip structure | |
150 | */ | |
a808ad3b SM |
151 | static int ndfc_chip_init(struct ndfc_controller *ndfc, |
152 | struct device_node *node) | |
ce4c61f1 | 153 | { |
a808ad3b SM |
154 | #ifdef CONFIG_MTD_PARTITIONS |
155 | #ifdef CONFIG_MTD_CMDLINE_PARTS | |
156 | static const char *part_types[] = { "cmdlinepart", NULL }; | |
157 | #else | |
158 | static const char *part_types[] = { NULL }; | |
159 | #endif | |
160 | #endif | |
161 | struct device_node *flash_np; | |
162 | struct nand_chip *chip = &ndfc->chip; | |
163 | int ret; | |
ce4c61f1 TG |
164 | |
165 | chip->IO_ADDR_R = ndfc->ndfcbase + NDFC_DATA; | |
166 | chip->IO_ADDR_W = ndfc->ndfcbase + NDFC_DATA; | |
7abd3ef9 | 167 | chip->cmd_ctrl = ndfc_hwcontrol; |
ce4c61f1 TG |
168 | chip->dev_ready = ndfc_ready; |
169 | chip->select_chip = ndfc_select_chip; | |
170 | chip->chip_delay = 50; | |
ce4c61f1 TG |
171 | chip->controller = &ndfc->ndfc_control; |
172 | chip->read_buf = ndfc_read_buf; | |
173 | chip->write_buf = ndfc_write_buf; | |
174 | chip->verify_buf = ndfc_verify_buf; | |
6dfc6d25 TG |
175 | chip->ecc.correct = nand_correct_data; |
176 | chip->ecc.hwctl = ndfc_enable_hwecc; | |
177 | chip->ecc.calculate = ndfc_calculate_ecc; | |
178 | chip->ecc.mode = NAND_ECC_HW; | |
179 | chip->ecc.size = 256; | |
180 | chip->ecc.bytes = 3; | |
ce4c61f1 | 181 | |
a808ad3b SM |
182 | ndfc->mtd.priv = chip; |
183 | ndfc->mtd.owner = THIS_MODULE; | |
ce4c61f1 | 184 | |
a808ad3b SM |
185 | flash_np = of_get_next_child(node, NULL); |
186 | if (!flash_np) | |
ce4c61f1 | 187 | return -ENODEV; |
a808ad3b SM |
188 | |
189 | ndfc->mtd.name = kasprintf(GFP_KERNEL, "%s.%s", | |
190 | ndfc->ofdev->dev.bus_id, flash_np->name); | |
191 | if (!ndfc->mtd.name) { | |
192 | ret = -ENOMEM; | |
193 | goto err; | |
ce4c61f1 TG |
194 | } |
195 | ||
a808ad3b SM |
196 | ret = nand_scan(&ndfc->mtd, 1); |
197 | if (ret) | |
198 | goto err; | |
ce4c61f1 | 199 | |
a808ad3b SM |
200 | #ifdef CONFIG_MTD_PARTITIONS |
201 | ret = parse_mtd_partitions(&ndfc->mtd, part_types, &ndfc->parts, 0); | |
202 | if (ret < 0) | |
203 | goto err; | |
204 | ||
205 | #ifdef CONFIG_MTD_OF_PARTS | |
206 | if (ret == 0) { | |
207 | ret = of_mtd_parse_partitions(&ndfc->ofdev->dev, flash_np, | |
208 | &ndfc->parts); | |
209 | if (ret < 0) | |
210 | goto err; | |
211 | } | |
ce4c61f1 TG |
212 | #endif |
213 | ||
a808ad3b SM |
214 | if (ret > 0) |
215 | ret = add_mtd_partitions(&ndfc->mtd, ndfc->parts, ret); | |
216 | else | |
217 | #endif | |
218 | ret = add_mtd_device(&ndfc->mtd); | |
ce4c61f1 | 219 | |
a808ad3b SM |
220 | err: |
221 | of_node_put(flash_np); | |
222 | if (ret) | |
223 | kfree(ndfc->mtd.name); | |
224 | return ret; | |
ce4c61f1 TG |
225 | } |
226 | ||
a808ad3b SM |
227 | static int __devinit ndfc_probe(struct of_device *ofdev, |
228 | const struct of_device_id *match) | |
ce4c61f1 | 229 | { |
ce4c61f1 | 230 | struct ndfc_controller *ndfc = &ndfc_ctrl; |
a808ad3b SM |
231 | const u32 *reg; |
232 | u32 ccr; | |
233 | int err, len; | |
ce4c61f1 | 234 | |
a808ad3b SM |
235 | spin_lock_init(&ndfc->ndfc_control.lock); |
236 | init_waitqueue_head(&ndfc->ndfc_control.wq); | |
237 | ndfc->ofdev = ofdev; | |
238 | dev_set_drvdata(&ofdev->dev, ndfc); | |
239 | ||
240 | /* Read the reg property to get the chip select */ | |
241 | reg = of_get_property(ofdev->node, "reg", &len); | |
242 | if (reg == NULL || len != 12) { | |
243 | dev_err(&ofdev->dev, "unable read reg property (%d)\n", len); | |
244 | return -ENOENT; | |
245 | } | |
246 | ndfc->chip_select = reg[0]; | |
247 | ||
248 | ndfc->ndfcbase = of_iomap(ofdev->node, 0); | |
ce4c61f1 | 249 | if (!ndfc->ndfcbase) { |
a808ad3b | 250 | dev_err(&ofdev->dev, "failed to get memory\n"); |
ce4c61f1 TG |
251 | return -EIO; |
252 | } | |
253 | ||
a808ad3b | 254 | ccr = NDFC_CCR_BS(ndfc->chip_select); |
ce4c61f1 | 255 | |
a808ad3b SM |
256 | /* It is ok if ccr does not exist - just default to 0 */ |
257 | reg = of_get_property(ofdev->node, "ccr", NULL); | |
258 | if (reg) | |
259 | ccr |= *reg; | |
ce4c61f1 | 260 | |
a808ad3b | 261 | out_be32(ndfc->ndfcbase + NDFC_CCR, ccr); |
ce4c61f1 | 262 | |
a808ad3b SM |
263 | /* Set the bank settings if given */ |
264 | reg = of_get_property(ofdev->node, "bank-settings", NULL); | |
265 | if (reg) { | |
266 | int offset = NDFC_BCFG0 + (ndfc->chip_select << 2); | |
267 | out_be32(ndfc->ndfcbase + offset, *reg); | |
268 | } | |
269 | ||
270 | err = ndfc_chip_init(ndfc, ofdev->node); | |
271 | if (err) { | |
272 | iounmap(ndfc->ndfcbase); | |
273 | return err; | |
274 | } | |
ce4c61f1 TG |
275 | |
276 | return 0; | |
277 | } | |
278 | ||
a808ad3b | 279 | static int __devexit ndfc_remove(struct of_device *ofdev) |
ce4c61f1 | 280 | { |
a808ad3b | 281 | struct ndfc_controller *ndfc = dev_get_drvdata(&ofdev->dev); |
ce4c61f1 | 282 | |
a808ad3b | 283 | nand_release(&ndfc->mtd); |
ce4c61f1 | 284 | |
ce4c61f1 TG |
285 | return 0; |
286 | } | |
287 | ||
a808ad3b SM |
288 | static const struct of_device_id ndfc_match[] = { |
289 | { .compatible = "ibm,ndfc", }, | |
290 | {} | |
ce4c61f1 | 291 | }; |
a808ad3b | 292 | MODULE_DEVICE_TABLE(of, ndfc_match); |
ce4c61f1 | 293 | |
a808ad3b SM |
294 | static struct of_platform_driver ndfc_driver = { |
295 | .driver = { | |
296 | .name = "ndfc", | |
ce4c61f1 | 297 | }, |
a808ad3b SM |
298 | .match_table = ndfc_match, |
299 | .probe = ndfc_probe, | |
300 | .remove = __devexit_p(ndfc_remove), | |
ce4c61f1 TG |
301 | }; |
302 | ||
303 | static int __init ndfc_nand_init(void) | |
304 | { | |
a808ad3b | 305 | return of_register_platform_driver(&ndfc_driver); |
ce4c61f1 TG |
306 | } |
307 | ||
308 | static void __exit ndfc_nand_exit(void) | |
309 | { | |
a808ad3b | 310 | of_unregister_platform_driver(&ndfc_driver); |
ce4c61f1 TG |
311 | } |
312 | ||
313 | module_init(ndfc_nand_init); | |
314 | module_exit(ndfc_nand_exit); | |
315 | ||
316 | MODULE_LICENSE("GPL"); | |
317 | MODULE_AUTHOR("Thomas Gleixner <tglx@linutronix.de>"); | |
a808ad3b | 318 | MODULE_DESCRIPTION("OF Platform driver for NDFC"); |