Commit | Line | Data |
---|---|---|
5c249c5a AV |
1 | /* |
2 | * Freescale UPM NAND driver. | |
3 | * | |
4 | * Copyright © 2007-2008 MontaVista Software, Inc. | |
5 | * | |
6 | * Author: Anton Vorontsov <avorontsov@ru.mvista.com> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
12 | */ | |
13 | ||
14 | #include <linux/kernel.h> | |
15 | #include <linux/module.h> | |
13f53697 | 16 | #include <linux/delay.h> |
5c249c5a AV |
17 | #include <linux/mtd/nand.h> |
18 | #include <linux/mtd/nand_ecc.h> | |
19 | #include <linux/mtd/partitions.h> | |
20 | #include <linux/mtd/mtd.h> | |
21 | #include <linux/of_platform.h> | |
22 | #include <linux/of_gpio.h> | |
23 | #include <linux/io.h> | |
24 | #include <asm/fsl_lbc.h> | |
25 | ||
26 | struct fsl_upm_nand { | |
27 | struct device *dev; | |
28 | struct mtd_info mtd; | |
29 | struct nand_chip chip; | |
30 | int last_ctrl; | |
31 | #ifdef CONFIG_MTD_PARTITIONS | |
32 | struct mtd_partition *parts; | |
33 | #endif | |
34 | ||
35 | struct fsl_upm upm; | |
36 | uint8_t upm_addr_offset; | |
37 | uint8_t upm_cmd_offset; | |
38 | void __iomem *io_base; | |
39 | int rnb_gpio; | |
13f53697 | 40 | int chip_delay; |
5c249c5a AV |
41 | }; |
42 | ||
43 | #define to_fsl_upm_nand(mtd) container_of(mtd, struct fsl_upm_nand, mtd) | |
44 | ||
45 | static int fun_chip_ready(struct mtd_info *mtd) | |
46 | { | |
47 | struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd); | |
48 | ||
49 | if (gpio_get_value(fun->rnb_gpio)) | |
50 | return 1; | |
51 | ||
52 | dev_vdbg(fun->dev, "busy\n"); | |
53 | return 0; | |
54 | } | |
55 | ||
56 | static void fun_wait_rnb(struct fsl_upm_nand *fun) | |
57 | { | |
58 | int cnt = 1000000; | |
59 | ||
60 | if (fun->rnb_gpio >= 0) { | |
61 | while (--cnt && !fun_chip_ready(&fun->mtd)) | |
62 | cpu_relax(); | |
13f53697 WG |
63 | if (!cnt) |
64 | dev_err(fun->dev, "tired waiting for RNB\n"); | |
65 | } else { | |
66 | ndelay(100); | |
5c249c5a | 67 | } |
5c249c5a AV |
68 | } |
69 | ||
70 | static void fun_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl) | |
71 | { | |
72 | struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd); | |
73 | ||
74 | if (!(ctrl & fun->last_ctrl)) { | |
75 | fsl_upm_end_pattern(&fun->upm); | |
76 | ||
77 | if (cmd == NAND_CMD_NONE) | |
78 | return; | |
79 | ||
80 | fun->last_ctrl = ctrl & (NAND_ALE | NAND_CLE); | |
81 | } | |
82 | ||
83 | if (ctrl & NAND_CTRL_CHANGE) { | |
84 | if (ctrl & NAND_ALE) | |
85 | fsl_upm_start_pattern(&fun->upm, fun->upm_addr_offset); | |
86 | else if (ctrl & NAND_CLE) | |
87 | fsl_upm_start_pattern(&fun->upm, fun->upm_cmd_offset); | |
88 | } | |
89 | ||
90 | fsl_upm_run_pattern(&fun->upm, fun->io_base, cmd); | |
91 | ||
95ebffd7 | 92 | fun_wait_rnb(fun); |
5c249c5a AV |
93 | } |
94 | ||
95 | static uint8_t fun_read_byte(struct mtd_info *mtd) | |
96 | { | |
97 | struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd); | |
98 | ||
99 | return in_8(fun->chip.IO_ADDR_R); | |
100 | } | |
101 | ||
102 | static void fun_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) | |
103 | { | |
104 | struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd); | |
105 | int i; | |
106 | ||
107 | for (i = 0; i < len; i++) | |
108 | buf[i] = in_8(fun->chip.IO_ADDR_R); | |
109 | } | |
110 | ||
111 | static void fun_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) | |
112 | { | |
113 | struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd); | |
114 | int i; | |
115 | ||
116 | for (i = 0; i < len; i++) { | |
117 | out_8(fun->chip.IO_ADDR_W, buf[i]); | |
95ebffd7 | 118 | fun_wait_rnb(fun); |
5c249c5a AV |
119 | } |
120 | } | |
121 | ||
95ebffd7 AV |
122 | static int __devinit fun_chip_init(struct fsl_upm_nand *fun, |
123 | const struct device_node *upm_np, | |
124 | const struct resource *io_res) | |
5c249c5a AV |
125 | { |
126 | int ret; | |
95ebffd7 | 127 | struct device_node *flash_np; |
5c249c5a AV |
128 | #ifdef CONFIG_MTD_PARTITIONS |
129 | static const char *part_types[] = { "cmdlinepart", NULL, }; | |
130 | #endif | |
131 | ||
132 | fun->chip.IO_ADDR_R = fun->io_base; | |
133 | fun->chip.IO_ADDR_W = fun->io_base; | |
134 | fun->chip.cmd_ctrl = fun_cmd_ctrl; | |
13f53697 | 135 | fun->chip.chip_delay = fun->chip_delay; |
5c249c5a AV |
136 | fun->chip.read_byte = fun_read_byte; |
137 | fun->chip.read_buf = fun_read_buf; | |
138 | fun->chip.write_buf = fun_write_buf; | |
139 | fun->chip.ecc.mode = NAND_ECC_SOFT; | |
140 | ||
141 | if (fun->rnb_gpio >= 0) | |
142 | fun->chip.dev_ready = fun_chip_ready; | |
143 | ||
144 | fun->mtd.priv = &fun->chip; | |
145 | fun->mtd.owner = THIS_MODULE; | |
146 | ||
95ebffd7 AV |
147 | flash_np = of_get_next_child(upm_np, NULL); |
148 | if (!flash_np) | |
149 | return -ENODEV; | |
150 | ||
151 | fun->mtd.name = kasprintf(GFP_KERNEL, "%x.%s", io_res->start, | |
152 | flash_np->name); | |
153 | if (!fun->mtd.name) { | |
154 | ret = -ENOMEM; | |
155 | goto err; | |
156 | } | |
157 | ||
5c249c5a AV |
158 | ret = nand_scan(&fun->mtd, 1); |
159 | if (ret) | |
95ebffd7 | 160 | goto err; |
5c249c5a AV |
161 | |
162 | #ifdef CONFIG_MTD_PARTITIONS | |
163 | ret = parse_mtd_partitions(&fun->mtd, part_types, &fun->parts, 0); | |
95ebffd7 AV |
164 | |
165 | #ifdef CONFIG_MTD_OF_PARTS | |
29b65861 WG |
166 | if (ret == 0) { |
167 | ret = of_mtd_parse_partitions(fun->dev, flash_np, &fun->parts); | |
168 | if (ret < 0) | |
169 | goto err; | |
170 | } | |
95ebffd7 | 171 | #endif |
5c249c5a | 172 | if (ret > 0) |
95ebffd7 AV |
173 | ret = add_mtd_partitions(&fun->mtd, fun->parts, ret); |
174 | else | |
5c249c5a | 175 | #endif |
95ebffd7 AV |
176 | ret = add_mtd_device(&fun->mtd); |
177 | err: | |
178 | of_node_put(flash_np); | |
179 | return ret; | |
5c249c5a AV |
180 | } |
181 | ||
182 | static int __devinit fun_probe(struct of_device *ofdev, | |
183 | const struct of_device_id *ofid) | |
184 | { | |
185 | struct fsl_upm_nand *fun; | |
186 | struct resource io_res; | |
187 | const uint32_t *prop; | |
188 | int ret; | |
189 | int size; | |
190 | ||
191 | fun = kzalloc(sizeof(*fun), GFP_KERNEL); | |
192 | if (!fun) | |
193 | return -ENOMEM; | |
194 | ||
195 | ret = of_address_to_resource(ofdev->node, 0, &io_res); | |
196 | if (ret) { | |
197 | dev_err(&ofdev->dev, "can't get IO base\n"); | |
198 | goto err1; | |
199 | } | |
200 | ||
201 | ret = fsl_upm_find(io_res.start, &fun->upm); | |
202 | if (ret) { | |
203 | dev_err(&ofdev->dev, "can't find UPM\n"); | |
204 | goto err1; | |
205 | } | |
206 | ||
207 | prop = of_get_property(ofdev->node, "fsl,upm-addr-offset", &size); | |
208 | if (!prop || size != sizeof(uint32_t)) { | |
209 | dev_err(&ofdev->dev, "can't get UPM address offset\n"); | |
210 | ret = -EINVAL; | |
211 | goto err2; | |
212 | } | |
213 | fun->upm_addr_offset = *prop; | |
214 | ||
215 | prop = of_get_property(ofdev->node, "fsl,upm-cmd-offset", &size); | |
216 | if (!prop || size != sizeof(uint32_t)) { | |
217 | dev_err(&ofdev->dev, "can't get UPM command offset\n"); | |
218 | ret = -EINVAL; | |
219 | goto err2; | |
220 | } | |
221 | fun->upm_cmd_offset = *prop; | |
222 | ||
223 | fun->rnb_gpio = of_get_gpio(ofdev->node, 0); | |
224 | if (fun->rnb_gpio >= 0) { | |
475b44c1 | 225 | ret = gpio_request(fun->rnb_gpio, dev_name(&ofdev->dev)); |
5c249c5a AV |
226 | if (ret) { |
227 | dev_err(&ofdev->dev, "can't request RNB gpio\n"); | |
228 | goto err2; | |
229 | } | |
230 | gpio_direction_input(fun->rnb_gpio); | |
231 | } else if (fun->rnb_gpio == -EINVAL) { | |
232 | dev_err(&ofdev->dev, "specified RNB gpio is invalid\n"); | |
233 | goto err2; | |
234 | } | |
235 | ||
13f53697 WG |
236 | prop = of_get_property(ofdev->node, "chip-delay", NULL); |
237 | if (prop) | |
238 | fun->chip_delay = *prop; | |
239 | else | |
240 | fun->chip_delay = 50; | |
241 | ||
5c249c5a AV |
242 | fun->io_base = devm_ioremap_nocache(&ofdev->dev, io_res.start, |
243 | io_res.end - io_res.start + 1); | |
244 | if (!fun->io_base) { | |
245 | ret = -ENOMEM; | |
246 | goto err2; | |
247 | } | |
248 | ||
249 | fun->dev = &ofdev->dev; | |
250 | fun->last_ctrl = NAND_CLE; | |
5c249c5a | 251 | |
95ebffd7 | 252 | ret = fun_chip_init(fun, ofdev->node, &io_res); |
5c249c5a AV |
253 | if (ret) |
254 | goto err2; | |
255 | ||
256 | dev_set_drvdata(&ofdev->dev, fun); | |
257 | ||
258 | return 0; | |
259 | err2: | |
260 | if (fun->rnb_gpio >= 0) | |
261 | gpio_free(fun->rnb_gpio); | |
262 | err1: | |
263 | kfree(fun); | |
264 | ||
265 | return ret; | |
266 | } | |
267 | ||
268 | static int __devexit fun_remove(struct of_device *ofdev) | |
269 | { | |
270 | struct fsl_upm_nand *fun = dev_get_drvdata(&ofdev->dev); | |
271 | ||
272 | nand_release(&fun->mtd); | |
95ebffd7 | 273 | kfree(fun->mtd.name); |
5c249c5a AV |
274 | |
275 | if (fun->rnb_gpio >= 0) | |
276 | gpio_free(fun->rnb_gpio); | |
277 | ||
278 | kfree(fun); | |
279 | ||
280 | return 0; | |
281 | } | |
282 | ||
283 | static struct of_device_id of_fun_match[] = { | |
284 | { .compatible = "fsl,upm-nand" }, | |
285 | {}, | |
286 | }; | |
287 | MODULE_DEVICE_TABLE(of, of_fun_match); | |
288 | ||
289 | static struct of_platform_driver of_fun_driver = { | |
290 | .name = "fsl,upm-nand", | |
291 | .match_table = of_fun_match, | |
292 | .probe = fun_probe, | |
293 | .remove = __devexit_p(fun_remove), | |
294 | }; | |
295 | ||
296 | static int __init fun_module_init(void) | |
297 | { | |
298 | return of_register_platform_driver(&of_fun_driver); | |
299 | } | |
300 | module_init(fun_module_init); | |
301 | ||
302 | static void __exit fun_module_exit(void) | |
303 | { | |
304 | of_unregister_platform_driver(&of_fun_driver); | |
305 | } | |
306 | module_exit(fun_module_exit); | |
307 | ||
308 | MODULE_LICENSE("GPL"); | |
309 | MODULE_AUTHOR("Anton Vorontsov <avorontsov@ru.mvista.com>"); | |
310 | MODULE_DESCRIPTION("Driver for NAND chips working through Freescale " | |
311 | "LocalBus User-Programmable Machine"); |