diff options
author | Egor Martovetsky <egor@pasemi.com> | 2007-11-28 19:37:31 -0500 |
---|---|---|
committer | David Woodhouse <dwmw2@infradead.org> | 2007-11-28 19:47:13 -0500 |
commit | 846fc31d06e54ad94026da11da0668c050fe777e (patch) | |
tree | c82a86f8897d4c34bbf81b34a6061a46c1b302c6 /drivers/mtd/nand/pasemi_nand.c | |
parent | 03680b1e00d146df718c8a4eac34438566b70c85 (diff) |
[MTD] [NAND] pasemi_nand driver
Plumbing for NAND connected via localbus on PA Semi PWRficient-based
boards.
From: Egor Martovetsky <egor@pasemi.com>
Signed-off-by: Olof Johansson <olof@lixom.net>
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
Diffstat (limited to 'drivers/mtd/nand/pasemi_nand.c')
-rw-r--r-- | drivers/mtd/nand/pasemi_nand.c | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/drivers/mtd/nand/pasemi_nand.c b/drivers/mtd/nand/pasemi_nand.c new file mode 100644 index 000000000000..75c899039023 --- /dev/null +++ b/drivers/mtd/nand/pasemi_nand.c | |||
@@ -0,0 +1,243 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2006-2007 PA Semi, Inc | ||
3 | * | ||
4 | * Author: Egor Martovetsky <egor@pasemi.com> | ||
5 | * Maintained by: Olof Johansson <olof@lixom.net> | ||
6 | * | ||
7 | * Driver for the PWRficient onchip NAND flash interface | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License version 2 as | ||
11 | * published by the Free Software Foundation. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License | ||
19 | * along with this program; if not, write to the Free Software | ||
20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
21 | */ | ||
22 | |||
23 | #undef DEBUG | ||
24 | |||
25 | #include <linux/slab.h> | ||
26 | #include <linux/init.h> | ||
27 | #include <linux/module.h> | ||
28 | #include <linux/mtd/mtd.h> | ||
29 | #include <linux/mtd/nand.h> | ||
30 | #include <linux/mtd/nand_ecc.h> | ||
31 | #include <linux/of_platform.h> | ||
32 | #include <linux/platform_device.h> | ||
33 | #include <linux/pci.h> | ||
34 | |||
35 | #include <asm/io.h> | ||
36 | |||
37 | #define LBICTRL_LPCCTL_NR 0x00004000 | ||
38 | #define CLE_PIN_CTL 15 | ||
39 | #define ALE_PIN_CTL 14 | ||
40 | |||
41 | static unsigned int lpcctl; | ||
42 | static struct mtd_info *pasemi_nand_mtd; | ||
43 | static const char driver_name[] = "pasemi-nand"; | ||
44 | |||
45 | static void pasemi_read_buf(struct mtd_info *mtd, u_char *buf, int len) | ||
46 | { | ||
47 | struct nand_chip *chip = mtd->priv; | ||
48 | |||
49 | while (len > 0x800) { | ||
50 | memcpy_fromio(buf, chip->IO_ADDR_R, 0x800); | ||
51 | buf += 0x800; | ||
52 | len -= 0x800; | ||
53 | } | ||
54 | memcpy_fromio(buf, chip->IO_ADDR_R, len); | ||
55 | } | ||
56 | |||
57 | static void pasemi_write_buf(struct mtd_info *mtd, const u_char *buf, int len) | ||
58 | { | ||
59 | struct nand_chip *chip = mtd->priv; | ||
60 | |||
61 | while (len > 0x800) { | ||
62 | memcpy_toio(chip->IO_ADDR_R, buf, 0x800); | ||
63 | buf += 0x800; | ||
64 | len -= 0x800; | ||
65 | } | ||
66 | memcpy_toio(chip->IO_ADDR_R, buf, len); | ||
67 | } | ||
68 | |||
69 | static void pasemi_hwcontrol(struct mtd_info *mtd, int cmd, | ||
70 | unsigned int ctrl) | ||
71 | { | ||
72 | struct nand_chip *chip = mtd->priv; | ||
73 | |||
74 | if (cmd == NAND_CMD_NONE) | ||
75 | return; | ||
76 | |||
77 | if (ctrl & NAND_CLE) | ||
78 | out_8(chip->IO_ADDR_W + (1 << CLE_PIN_CTL), cmd); | ||
79 | else | ||
80 | out_8(chip->IO_ADDR_W + (1 << ALE_PIN_CTL), cmd); | ||
81 | |||
82 | /* Push out posted writes */ | ||
83 | eieio(); | ||
84 | inl(lpcctl); | ||
85 | } | ||
86 | |||
87 | int pasemi_device_ready(struct mtd_info *mtd) | ||
88 | { | ||
89 | return !!(inl(lpcctl) & LBICTRL_LPCCTL_NR); | ||
90 | } | ||
91 | |||
92 | static int __devinit pasemi_nand_probe(struct of_device *ofdev, | ||
93 | const struct of_device_id *match) | ||
94 | { | ||
95 | struct pci_dev *pdev; | ||
96 | struct device_node *np = ofdev->node; | ||
97 | struct resource res; | ||
98 | struct nand_chip *chip; | ||
99 | int err = 0; | ||
100 | |||
101 | err = of_address_to_resource(np, 0, &res); | ||
102 | |||
103 | if (err) | ||
104 | return -EINVAL; | ||
105 | |||
106 | /* We only support one device at the moment */ | ||
107 | if (pasemi_nand_mtd) | ||
108 | return -ENODEV; | ||
109 | |||
110 | pr_debug("pasemi_nand at %lx-%lx\n", res.start, res.end); | ||
111 | |||
112 | /* Allocate memory for MTD device structure and private data */ | ||
113 | pasemi_nand_mtd = kzalloc(sizeof(struct mtd_info) + | ||
114 | sizeof(struct nand_chip), GFP_KERNEL); | ||
115 | if (!pasemi_nand_mtd) { | ||
116 | printk(KERN_WARNING | ||
117 | "Unable to allocate PASEMI NAND MTD device structure\n"); | ||
118 | err = -ENOMEM; | ||
119 | goto out; | ||
120 | } | ||
121 | |||
122 | /* Get pointer to private data */ | ||
123 | chip = (struct nand_chip *)&pasemi_nand_mtd[1]; | ||
124 | |||
125 | /* Link the private data with the MTD structure */ | ||
126 | pasemi_nand_mtd->priv = chip; | ||
127 | pasemi_nand_mtd->owner = THIS_MODULE; | ||
128 | |||
129 | chip->IO_ADDR_R = of_iomap(np, 0); | ||
130 | chip->IO_ADDR_W = chip->IO_ADDR_R; | ||
131 | |||
132 | if (!chip->IO_ADDR_R) { | ||
133 | err = -EIO; | ||
134 | goto out_mtd; | ||
135 | } | ||
136 | |||
137 | pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa008, NULL); | ||
138 | if (!pdev) { | ||
139 | err = -ENODEV; | ||
140 | goto out_ior; | ||
141 | } | ||
142 | |||
143 | lpcctl = pci_resource_start(pdev, 0); | ||
144 | |||
145 | if (!request_region(lpcctl, 4, driver_name)) { | ||
146 | err = -EBUSY; | ||
147 | goto out_ior; | ||
148 | } | ||
149 | |||
150 | chip->cmd_ctrl = pasemi_hwcontrol; | ||
151 | chip->dev_ready = pasemi_device_ready; | ||
152 | chip->read_buf = pasemi_read_buf; | ||
153 | chip->write_buf = pasemi_write_buf; | ||
154 | chip->chip_delay = 0; | ||
155 | chip->ecc.mode = NAND_ECC_SOFT; | ||
156 | |||
157 | /* Enable the following for a flash based bad block table */ | ||
158 | chip->options = NAND_USE_FLASH_BBT | NAND_NO_AUTOINCR; | ||
159 | |||
160 | /* Scan to find existance of the device */ | ||
161 | if (nand_scan(pasemi_nand_mtd, 1)) { | ||
162 | err = -ENXIO; | ||
163 | goto out_lpc; | ||
164 | } | ||
165 | |||
166 | if (add_mtd_device(pasemi_nand_mtd)) { | ||
167 | printk(KERN_ERR "pasemi_nand: Unable to register MTD device\n"); | ||
168 | err = -ENODEV; | ||
169 | goto out_lpc; | ||
170 | } | ||
171 | |||
172 | printk(KERN_INFO "PA Semi NAND flash at %08lx, control at I/O %x\n", | ||
173 | res.start, lpcctl); | ||
174 | |||
175 | return 0; | ||
176 | |||
177 | out_lpc: | ||
178 | release_region(lpcctl, 4); | ||
179 | out_ior: | ||
180 | iounmap(chip->IO_ADDR_R); | ||
181 | out_mtd: | ||
182 | kfree(pasemi_nand_mtd); | ||
183 | out: | ||
184 | return err; | ||
185 | } | ||
186 | |||
187 | static int __devexit pasemi_nand_remove(struct of_device *ofdev) | ||
188 | { | ||
189 | struct nand_chip *chip; | ||
190 | |||
191 | if (!pasemi_nand_mtd) | ||
192 | return 0; | ||
193 | |||
194 | chip = pasemi_nand_mtd->priv; | ||
195 | |||
196 | /* Release resources, unregister device */ | ||
197 | nand_release(pasemi_nand_mtd); | ||
198 | |||
199 | release_region(lpcctl, 4); | ||
200 | |||
201 | iounmap(chip->IO_ADDR_R); | ||
202 | |||
203 | /* Free the MTD device structure */ | ||
204 | kfree(pasemi_nand_mtd); | ||
205 | |||
206 | pasemi_nand_mtd = NULL; | ||
207 | |||
208 | return 0; | ||
209 | } | ||
210 | |||
211 | static struct of_device_id pasemi_nand_match[] = | ||
212 | { | ||
213 | { | ||
214 | .compatible = "pasemi,localbus-nand", | ||
215 | }, | ||
216 | {}, | ||
217 | }; | ||
218 | |||
219 | MODULE_DEVICE_TABLE(of, pasemi_nand_match); | ||
220 | |||
221 | static struct of_platform_driver pasemi_nand_driver = | ||
222 | { | ||
223 | .name = (char*)driver_name, | ||
224 | .match_table = pasemi_nand_match, | ||
225 | .probe = pasemi_nand_probe, | ||
226 | .remove = pasemi_nand_remove, | ||
227 | }; | ||
228 | |||
229 | static int __init pasemi_nand_init(void) | ||
230 | { | ||
231 | return of_register_platform_driver(&pasemi_nand_driver); | ||
232 | } | ||
233 | module_init(pasemi_nand_init); | ||
234 | |||
235 | static void __exit pasemi_nand_exit(void) | ||
236 | { | ||
237 | of_unregister_platform_driver(&pasemi_nand_driver); | ||
238 | } | ||
239 | module_exit(pasemi_nand_exit); | ||
240 | |||
241 | MODULE_LICENSE("GPL"); | ||
242 | MODULE_AUTHOR("Egor Martovetsky <egor@pasemi.com>"); | ||
243 | MODULE_DESCRIPTION("NAND flash interface driver for PA Semi PWRficient"); | ||