diff options
Diffstat (limited to 'drivers/mtd/nand/fsl_upm.c')
-rw-r--r-- | drivers/mtd/nand/fsl_upm.c | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/drivers/mtd/nand/fsl_upm.c b/drivers/mtd/nand/fsl_upm.c new file mode 100644 index 00000000000..1ebfd87f00b --- /dev/null +++ b/drivers/mtd/nand/fsl_upm.c | |||
@@ -0,0 +1,291 @@ | |||
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> | ||
16 | #include <linux/mtd/nand.h> | ||
17 | #include <linux/mtd/nand_ecc.h> | ||
18 | #include <linux/mtd/partitions.h> | ||
19 | #include <linux/mtd/mtd.h> | ||
20 | #include <linux/of_platform.h> | ||
21 | #include <linux/of_gpio.h> | ||
22 | #include <linux/io.h> | ||
23 | #include <asm/fsl_lbc.h> | ||
24 | |||
25 | struct fsl_upm_nand { | ||
26 | struct device *dev; | ||
27 | struct mtd_info mtd; | ||
28 | struct nand_chip chip; | ||
29 | int last_ctrl; | ||
30 | #ifdef CONFIG_MTD_PARTITIONS | ||
31 | struct mtd_partition *parts; | ||
32 | #endif | ||
33 | |||
34 | struct fsl_upm upm; | ||
35 | uint8_t upm_addr_offset; | ||
36 | uint8_t upm_cmd_offset; | ||
37 | void __iomem *io_base; | ||
38 | int rnb_gpio; | ||
39 | const uint32_t *wait_pattern; | ||
40 | const uint32_t *wait_write; | ||
41 | int chip_delay; | ||
42 | }; | ||
43 | |||
44 | #define to_fsl_upm_nand(mtd) container_of(mtd, struct fsl_upm_nand, mtd) | ||
45 | |||
46 | static int fun_chip_ready(struct mtd_info *mtd) | ||
47 | { | ||
48 | struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd); | ||
49 | |||
50 | if (gpio_get_value(fun->rnb_gpio)) | ||
51 | return 1; | ||
52 | |||
53 | dev_vdbg(fun->dev, "busy\n"); | ||
54 | return 0; | ||
55 | } | ||
56 | |||
57 | static void fun_wait_rnb(struct fsl_upm_nand *fun) | ||
58 | { | ||
59 | int cnt = 1000000; | ||
60 | |||
61 | if (fun->rnb_gpio >= 0) { | ||
62 | while (--cnt && !fun_chip_ready(&fun->mtd)) | ||
63 | cpu_relax(); | ||
64 | } | ||
65 | |||
66 | if (!cnt) | ||
67 | dev_err(fun->dev, "tired waiting for RNB\n"); | ||
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 | |||
92 | if (fun->wait_pattern) | ||
93 | fun_wait_rnb(fun); | ||
94 | } | ||
95 | |||
96 | static uint8_t fun_read_byte(struct mtd_info *mtd) | ||
97 | { | ||
98 | struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd); | ||
99 | |||
100 | return in_8(fun->chip.IO_ADDR_R); | ||
101 | } | ||
102 | |||
103 | static void fun_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) | ||
104 | { | ||
105 | struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd); | ||
106 | int i; | ||
107 | |||
108 | for (i = 0; i < len; i++) | ||
109 | buf[i] = in_8(fun->chip.IO_ADDR_R); | ||
110 | } | ||
111 | |||
112 | static void fun_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) | ||
113 | { | ||
114 | struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd); | ||
115 | int i; | ||
116 | |||
117 | for (i = 0; i < len; i++) { | ||
118 | out_8(fun->chip.IO_ADDR_W, buf[i]); | ||
119 | if (fun->wait_write) | ||
120 | fun_wait_rnb(fun); | ||
121 | } | ||
122 | } | ||
123 | |||
124 | static int __devinit fun_chip_init(struct fsl_upm_nand *fun) | ||
125 | { | ||
126 | int ret; | ||
127 | #ifdef CONFIG_MTD_PARTITIONS | ||
128 | static const char *part_types[] = { "cmdlinepart", NULL, }; | ||
129 | #endif | ||
130 | |||
131 | fun->chip.IO_ADDR_R = fun->io_base; | ||
132 | fun->chip.IO_ADDR_W = fun->io_base; | ||
133 | fun->chip.cmd_ctrl = fun_cmd_ctrl; | ||
134 | fun->chip.chip_delay = fun->chip_delay; | ||
135 | fun->chip.read_byte = fun_read_byte; | ||
136 | fun->chip.read_buf = fun_read_buf; | ||
137 | fun->chip.write_buf = fun_write_buf; | ||
138 | fun->chip.ecc.mode = NAND_ECC_SOFT; | ||
139 | |||
140 | if (fun->rnb_gpio >= 0) | ||
141 | fun->chip.dev_ready = fun_chip_ready; | ||
142 | |||
143 | fun->mtd.priv = &fun->chip; | ||
144 | fun->mtd.owner = THIS_MODULE; | ||
145 | |||
146 | ret = nand_scan(&fun->mtd, 1); | ||
147 | if (ret) | ||
148 | return ret; | ||
149 | |||
150 | fun->mtd.name = fun->dev->bus_id; | ||
151 | |||
152 | #ifdef CONFIG_MTD_PARTITIONS | ||
153 | ret = parse_mtd_partitions(&fun->mtd, part_types, &fun->parts, 0); | ||
154 | if (ret > 0) | ||
155 | return add_mtd_partitions(&fun->mtd, fun->parts, ret); | ||
156 | #endif | ||
157 | return add_mtd_device(&fun->mtd); | ||
158 | } | ||
159 | |||
160 | static int __devinit fun_probe(struct of_device *ofdev, | ||
161 | const struct of_device_id *ofid) | ||
162 | { | ||
163 | struct fsl_upm_nand *fun; | ||
164 | struct resource io_res; | ||
165 | const uint32_t *prop; | ||
166 | int ret; | ||
167 | int size; | ||
168 | |||
169 | fun = kzalloc(sizeof(*fun), GFP_KERNEL); | ||
170 | if (!fun) | ||
171 | return -ENOMEM; | ||
172 | |||
173 | ret = of_address_to_resource(ofdev->node, 0, &io_res); | ||
174 | if (ret) { | ||
175 | dev_err(&ofdev->dev, "can't get IO base\n"); | ||
176 | goto err1; | ||
177 | } | ||
178 | |||
179 | ret = fsl_upm_find(io_res.start, &fun->upm); | ||
180 | if (ret) { | ||
181 | dev_err(&ofdev->dev, "can't find UPM\n"); | ||
182 | goto err1; | ||
183 | } | ||
184 | |||
185 | prop = of_get_property(ofdev->node, "fsl,upm-addr-offset", &size); | ||
186 | if (!prop || size != sizeof(uint32_t)) { | ||
187 | dev_err(&ofdev->dev, "can't get UPM address offset\n"); | ||
188 | ret = -EINVAL; | ||
189 | goto err2; | ||
190 | } | ||
191 | fun->upm_addr_offset = *prop; | ||
192 | |||
193 | prop = of_get_property(ofdev->node, "fsl,upm-cmd-offset", &size); | ||
194 | if (!prop || size != sizeof(uint32_t)) { | ||
195 | dev_err(&ofdev->dev, "can't get UPM command offset\n"); | ||
196 | ret = -EINVAL; | ||
197 | goto err2; | ||
198 | } | ||
199 | fun->upm_cmd_offset = *prop; | ||
200 | |||
201 | fun->rnb_gpio = of_get_gpio(ofdev->node, 0); | ||
202 | if (fun->rnb_gpio >= 0) { | ||
203 | ret = gpio_request(fun->rnb_gpio, ofdev->dev.bus_id); | ||
204 | if (ret) { | ||
205 | dev_err(&ofdev->dev, "can't request RNB gpio\n"); | ||
206 | goto err2; | ||
207 | } | ||
208 | gpio_direction_input(fun->rnb_gpio); | ||
209 | } else if (fun->rnb_gpio == -EINVAL) { | ||
210 | dev_err(&ofdev->dev, "specified RNB gpio is invalid\n"); | ||
211 | goto err2; | ||
212 | } | ||
213 | |||
214 | fun->io_base = devm_ioremap_nocache(&ofdev->dev, io_res.start, | ||
215 | io_res.end - io_res.start + 1); | ||
216 | if (!fun->io_base) { | ||
217 | ret = -ENOMEM; | ||
218 | goto err2; | ||
219 | } | ||
220 | |||
221 | fun->dev = &ofdev->dev; | ||
222 | fun->last_ctrl = NAND_CLE; | ||
223 | fun->wait_pattern = of_get_property(ofdev->node, "fsl,wait-pattern", | ||
224 | NULL); | ||
225 | fun->wait_write = of_get_property(ofdev->node, "fsl,wait-write", NULL); | ||
226 | |||
227 | prop = of_get_property(ofdev->node, "chip-delay", NULL); | ||
228 | if (prop) | ||
229 | fun->chip_delay = *prop; | ||
230 | else | ||
231 | fun->chip_delay = 50; | ||
232 | |||
233 | ret = fun_chip_init(fun); | ||
234 | if (ret) | ||
235 | goto err2; | ||
236 | |||
237 | dev_set_drvdata(&ofdev->dev, fun); | ||
238 | |||
239 | return 0; | ||
240 | err2: | ||
241 | if (fun->rnb_gpio >= 0) | ||
242 | gpio_free(fun->rnb_gpio); | ||
243 | err1: | ||
244 | kfree(fun); | ||
245 | |||
246 | return ret; | ||
247 | } | ||
248 | |||
249 | static int __devexit fun_remove(struct of_device *ofdev) | ||
250 | { | ||
251 | struct fsl_upm_nand *fun = dev_get_drvdata(&ofdev->dev); | ||
252 | |||
253 | nand_release(&fun->mtd); | ||
254 | |||
255 | if (fun->rnb_gpio >= 0) | ||
256 | gpio_free(fun->rnb_gpio); | ||
257 | |||
258 | kfree(fun); | ||
259 | |||
260 | return 0; | ||
261 | } | ||
262 | |||
263 | static struct of_device_id of_fun_match[] = { | ||
264 | { .compatible = "fsl,upm-nand" }, | ||
265 | {}, | ||
266 | }; | ||
267 | MODULE_DEVICE_TABLE(of, of_fun_match); | ||
268 | |||
269 | static struct of_platform_driver of_fun_driver = { | ||
270 | .name = "fsl,upm-nand", | ||
271 | .match_table = of_fun_match, | ||
272 | .probe = fun_probe, | ||
273 | .remove = __devexit_p(fun_remove), | ||
274 | }; | ||
275 | |||
276 | static int __init fun_module_init(void) | ||
277 | { | ||
278 | return of_register_platform_driver(&of_fun_driver); | ||
279 | } | ||
280 | module_init(fun_module_init); | ||
281 | |||
282 | static void __exit fun_module_exit(void) | ||
283 | { | ||
284 | of_unregister_platform_driver(&of_fun_driver); | ||
285 | } | ||
286 | module_exit(fun_module_exit); | ||
287 | |||
288 | MODULE_LICENSE("GPL"); | ||
289 | MODULE_AUTHOR("Anton Vorontsov <avorontsov@ru.mvista.com>"); | ||
290 | MODULE_DESCRIPTION("Driver for NAND chips working through Freescale " | ||
291 | "LocalBus User-Programmable Machine"); | ||