aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mtd
diff options
context:
space:
mode:
authorAnton Vorontsov <avorontsov@ru.mvista.com>2008-03-11 15:33:13 -0400
committerDavid Woodhouse <dwmw2@infradead.org>2008-04-22 15:46:30 -0400
commit5c249c5a57dce2b47f1fb92093201b3a7013cb57 (patch)
tree5a68dde0373bb366646c93bdcfdefa63b7e74468 /drivers/mtd
parenta1c06ee11f0b83e372c958b165338f579d17e3d4 (diff)
[MTD] [NAND] FSL UPM NAND driver
This is very simple driver, NAND is connected through localbus, and User-Programmable Machine is doing various adjustments to speak NAND. No special efforts needed to do read and write cycles, though to control ALE and CLE phases, we ask UPM to generate exact pre-programmed signals on the localbus lines. Signed-off-by: Anton Vorontsov <avorontsov@ru.mvista.com> Signed-off-by: David Woodhouse <dwmw2@infradead.org>
Diffstat (limited to 'drivers/mtd')
-rw-r--r--drivers/mtd/nand/Kconfig8
-rw-r--r--drivers/mtd/nand/Makefile1
-rw-r--r--drivers/mtd/nand/fsl_upm.c291
3 files changed, 300 insertions, 0 deletions
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index 180fc7be1826..dcbb0decd465 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -337,4 +337,12 @@ config MTD_NAND_FSL_ELBC
337 Enabling this option will enable you to use this to control 337 Enabling this option will enable you to use this to control
338 external NAND devices. 338 external NAND devices.
339 339
340config MTD_NAND_FSL_UPM
341 tristate "Support for NAND on Freescale UPM"
342 depends on MTD_NAND && OF_GPIO && (PPC_83xx || PPC_85xx)
343 select FSL_LBC
344 help
345 Enables support for NAND Flash chips wired onto Freescale PowerPC
346 processor localbus with User-Programmable Machine support.
347
340endif # MTD_NAND 348endif # MTD_NAND
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index 54a332bda3da..a6e74a46992a 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -33,5 +33,6 @@ obj-$(CONFIG_MTD_ALAUDA) += alauda.o
33obj-$(CONFIG_MTD_NAND_PASEMI) += pasemi_nand.o 33obj-$(CONFIG_MTD_NAND_PASEMI) += pasemi_nand.o
34obj-$(CONFIG_MTD_NAND_ORION) += orion_nand.o 34obj-$(CONFIG_MTD_NAND_ORION) += orion_nand.o
35obj-$(CONFIG_MTD_NAND_FSL_ELBC) += fsl_elbc_nand.o 35obj-$(CONFIG_MTD_NAND_FSL_ELBC) += fsl_elbc_nand.o
36obj-$(CONFIG_MTD_NAND_FSL_UPM) += fsl_upm.o
36 37
37nand-objs := nand_base.o nand_bbt.o 38nand-objs := nand_base.o nand_bbt.o
diff --git a/drivers/mtd/nand/fsl_upm.c b/drivers/mtd/nand/fsl_upm.c
new file mode 100644
index 000000000000..1ebfd87f00b4
--- /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
25struct 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
46static 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
57static 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
70static 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
96static 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
103static 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
112static 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
124static 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
160static 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;
240err2:
241 if (fun->rnb_gpio >= 0)
242 gpio_free(fun->rnb_gpio);
243err1:
244 kfree(fun);
245
246 return ret;
247}
248
249static 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
263static struct of_device_id of_fun_match[] = {
264 { .compatible = "fsl,upm-nand" },
265 {},
266};
267MODULE_DEVICE_TABLE(of, of_fun_match);
268
269static 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
276static int __init fun_module_init(void)
277{
278 return of_register_platform_driver(&of_fun_driver);
279}
280module_init(fun_module_init);
281
282static void __exit fun_module_exit(void)
283{
284 of_unregister_platform_driver(&of_fun_driver);
285}
286module_exit(fun_module_exit);
287
288MODULE_LICENSE("GPL");
289MODULE_AUTHOR("Anton Vorontsov <avorontsov@ru.mvista.com>");
290MODULE_DESCRIPTION("Driver for NAND chips working through Freescale "
291 "LocalBus User-Programmable Machine");