Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /*====================================================================== |
2 | ||
3 | drivers/mtd/maps/integrator-flash.c: ARM Integrator flash map driver | |
69f34c98 | 4 | |
1da177e4 LT |
5 | Copyright (C) 2000 ARM Limited |
6 | Copyright (C) 2003 Deep Blue Solutions Ltd. | |
69f34c98 | 7 | |
1da177e4 LT |
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. | |
69f34c98 | 12 | |
1da177e4 LT |
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. | |
69f34c98 | 17 | |
1da177e4 LT |
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 | |
69f34c98 TG |
21 | |
22 | This is access code for flashes using ARM's flash partitioning | |
1da177e4 LT |
23 | standards. |
24 | ||
1da177e4 LT |
25 | ======================================================================*/ |
26 | ||
1da177e4 LT |
27 | #include <linux/module.h> |
28 | #include <linux/types.h> | |
29 | #include <linux/kernel.h> | |
30 | #include <linux/slab.h> | |
31 | #include <linux/ioport.h> | |
d052d1be | 32 | #include <linux/platform_device.h> |
1da177e4 | 33 | #include <linux/init.h> |
99730225 | 34 | #include <linux/io.h> |
1da177e4 LT |
35 | |
36 | #include <linux/mtd/mtd.h> | |
37 | #include <linux/mtd/map.h> | |
38 | #include <linux/mtd/partitions.h> | |
9fd1e8f9 | 39 | #include <linux/mtd/concat.h> |
1da177e4 LT |
40 | |
41 | #include <asm/mach/flash.h> | |
a09e64fb | 42 | #include <mach/hardware.h> |
1da177e4 LT |
43 | #include <asm/system.h> |
44 | ||
9fd1e8f9 | 45 | struct armflash_subdev_info { |
17659c60 | 46 | char *name; |
9fd1e8f9 CM |
47 | struct mtd_info *mtd; |
48 | struct map_info map; | |
1da177e4 | 49 | struct flash_platform_data *plat; |
9fd1e8f9 CM |
50 | }; |
51 | ||
52 | struct armflash_info { | |
1da177e4 LT |
53 | struct resource *res; |
54 | struct mtd_partition *parts; | |
55 | struct mtd_info *mtd; | |
9fd1e8f9 CM |
56 | int nr_subdev; |
57 | struct armflash_subdev_info subdev[0]; | |
1da177e4 LT |
58 | }; |
59 | ||
60 | static void armflash_set_vpp(struct map_info *map, int on) | |
61 | { | |
9fd1e8f9 CM |
62 | struct armflash_subdev_info *info = |
63 | container_of(map, struct armflash_subdev_info, map); | |
1da177e4 LT |
64 | |
65 | if (info->plat && info->plat->set_vpp) | |
66 | info->plat->set_vpp(on); | |
67 | } | |
68 | ||
69 | static const char *probes[] = { "cmdlinepart", "RedBoot", "afs", NULL }; | |
70 | ||
9fd1e8f9 CM |
71 | static int armflash_subdev_probe(struct armflash_subdev_info *subdev, |
72 | struct resource *res) | |
1da177e4 | 73 | { |
9fd1e8f9 CM |
74 | struct flash_platform_data *plat = subdev->plat; |
75 | resource_size_t size = res->end - res->start + 1; | |
1da177e4 | 76 | void __iomem *base; |
9fd1e8f9 | 77 | int err = 0; |
1da177e4 | 78 | |
9fd1e8f9 | 79 | if (!request_mem_region(res->start, size, subdev->name)) { |
1da177e4 | 80 | err = -EBUSY; |
9fd1e8f9 | 81 | goto out; |
1da177e4 LT |
82 | } |
83 | ||
84 | base = ioremap(res->start, size); | |
85 | if (!base) { | |
86 | err = -ENOMEM; | |
87 | goto no_mem; | |
88 | } | |
89 | ||
90 | /* | |
91 | * look for CFI based flash parts fitted to this board | |
92 | */ | |
9fd1e8f9 CM |
93 | subdev->map.size = size; |
94 | subdev->map.bankwidth = plat->width; | |
95 | subdev->map.phys = res->start; | |
96 | subdev->map.virt = base; | |
97 | subdev->map.name = subdev->name; | |
98 | subdev->map.set_vpp = armflash_set_vpp; | |
1da177e4 | 99 | |
9fd1e8f9 | 100 | simple_map_init(&subdev->map); |
1da177e4 LT |
101 | |
102 | /* | |
103 | * Also, the CFI layer automatically works out what size | |
104 | * of chips we have, and does the necessary identification | |
105 | * for us automatically. | |
106 | */ | |
9fd1e8f9 CM |
107 | subdev->mtd = do_map_probe(plat->map_name, &subdev->map); |
108 | if (!subdev->mtd) { | |
1da177e4 LT |
109 | err = -ENXIO; |
110 | goto no_device; | |
111 | } | |
112 | ||
9fd1e8f9 CM |
113 | subdev->mtd->owner = THIS_MODULE; |
114 | ||
115 | /* Successful? */ | |
116 | if (err == 0) | |
117 | return err; | |
118 | ||
119 | if (subdev->mtd) | |
120 | map_destroy(subdev->mtd); | |
121 | no_device: | |
122 | iounmap(base); | |
123 | no_mem: | |
124 | release_mem_region(res->start, size); | |
125 | out: | |
126 | return err; | |
127 | } | |
128 | ||
129 | static void armflash_subdev_remove(struct armflash_subdev_info *subdev) | |
130 | { | |
131 | if (subdev->mtd) | |
132 | map_destroy(subdev->mtd); | |
133 | if (subdev->map.virt) | |
134 | iounmap(subdev->map.virt); | |
17659c60 DW |
135 | kfree(subdev->name); |
136 | subdev->name = NULL; | |
9fd1e8f9 CM |
137 | release_mem_region(subdev->map.phys, subdev->map.size); |
138 | } | |
139 | ||
140 | static int armflash_probe(struct platform_device *dev) | |
141 | { | |
142 | struct flash_platform_data *plat = dev->dev.platform_data; | |
143 | unsigned int size; | |
144 | struct armflash_info *info; | |
145 | int i, nr, err; | |
146 | ||
147 | /* Count the number of devices */ | |
148 | for (nr = 0; ; nr++) | |
149 | if (!platform_get_resource(dev, IORESOURCE_MEM, nr)) | |
150 | break; | |
151 | if (nr == 0) { | |
152 | err = -ENODEV; | |
153 | goto out; | |
154 | } | |
155 | ||
156 | size = sizeof(struct armflash_info) + | |
157 | sizeof(struct armflash_subdev_info) * nr; | |
158 | info = kzalloc(size, GFP_KERNEL); | |
159 | if (!info) { | |
160 | err = -ENOMEM; | |
161 | goto out; | |
162 | } | |
163 | ||
164 | if (plat && plat->init) { | |
165 | err = plat->init(); | |
166 | if (err) | |
167 | goto no_resource; | |
168 | } | |
169 | ||
170 | for (i = 0; i < nr; i++) { | |
171 | struct armflash_subdev_info *subdev = &info->subdev[i]; | |
172 | struct resource *res; | |
173 | ||
174 | res = platform_get_resource(dev, IORESOURCE_MEM, i); | |
175 | if (!res) | |
176 | break; | |
177 | ||
178 | if (nr == 1) | |
179 | /* No MTD concatenation, just use the default name */ | |
17659c60 | 180 | subdev->name = kstrdup(dev_name(&dev->dev), GFP_KERNEL); |
9fd1e8f9 | 181 | else |
17659c60 DW |
182 | subdev->name = kasprintf(GFP_KERNEL, "%s-%d", |
183 | dev_name(&dev->dev), i); | |
184 | if (!subdev->name) { | |
185 | err = -ENOMEM; | |
186 | break; | |
187 | } | |
9fd1e8f9 CM |
188 | subdev->plat = plat; |
189 | ||
190 | err = armflash_subdev_probe(subdev, res); | |
17659c60 DW |
191 | if (err) { |
192 | kfree(subdev->name); | |
193 | subdev->name = NULL; | |
9fd1e8f9 | 194 | break; |
17659c60 | 195 | } |
9fd1e8f9 CM |
196 | } |
197 | info->nr_subdev = i; | |
198 | ||
199 | if (err) | |
200 | goto subdev_err; | |
201 | ||
202 | if (info->nr_subdev == 1) | |
203 | info->mtd = info->subdev[0].mtd; | |
204 | else if (info->nr_subdev > 1) { | |
205 | #ifdef CONFIG_MTD_CONCAT | |
206 | struct mtd_info *cdev[info->nr_subdev]; | |
207 | ||
208 | /* | |
209 | * We detected multiple devices. Concatenate them together. | |
210 | */ | |
211 | for (i = 0; i < info->nr_subdev; i++) | |
212 | cdev[i] = info->subdev[i].mtd; | |
213 | ||
214 | info->mtd = mtd_concat_create(cdev, info->nr_subdev, | |
215 | dev_name(&dev->dev)); | |
216 | if (info->mtd == NULL) | |
217 | err = -ENXIO; | |
218 | #else | |
219 | printk(KERN_ERR "armflash: multiple devices found but " | |
220 | "MTD concat support disabled.\n"); | |
221 | err = -ENXIO; | |
222 | #endif | |
223 | } | |
224 | ||
225 | if (err < 0) | |
226 | goto cleanup; | |
1da177e4 LT |
227 | |
228 | err = parse_mtd_partitions(info->mtd, probes, &info->parts, 0); | |
229 | if (err > 0) { | |
230 | err = add_mtd_partitions(info->mtd, info->parts, err); | |
231 | if (err) | |
232 | printk(KERN_ERR | |
233 | "mtd partition registration failed: %d\n", err); | |
234 | } | |
235 | ||
9fd1e8f9 | 236 | if (err == 0) { |
3ae5eaec | 237 | platform_set_drvdata(dev, info); |
9fd1e8f9 CM |
238 | return err; |
239 | } | |
1da177e4 LT |
240 | |
241 | /* | |
9fd1e8f9 | 242 | * We got an error, free all resources. |
1da177e4 | 243 | */ |
9fd1e8f9 CM |
244 | cleanup: |
245 | if (info->mtd) { | |
246 | del_mtd_partitions(info->mtd); | |
247 | #ifdef CONFIG_MTD_CONCAT | |
248 | if (info->mtd != info->subdev[0].mtd) | |
249 | mtd_concat_destroy(info->mtd); | |
250 | #endif | |
1da177e4 | 251 | } |
9fd1e8f9 CM |
252 | kfree(info->parts); |
253 | subdev_err: | |
254 | for (i = info->nr_subdev - 1; i >= 0; i--) | |
255 | armflash_subdev_remove(&info->subdev[i]); | |
256 | no_resource: | |
257 | if (plat && plat->exit) | |
258 | plat->exit(); | |
259 | kfree(info); | |
1da177e4 LT |
260 | out: |
261 | return err; | |
262 | } | |
263 | ||
3ae5eaec | 264 | static int armflash_remove(struct platform_device *dev) |
1da177e4 | 265 | { |
3ae5eaec | 266 | struct armflash_info *info = platform_get_drvdata(dev); |
9fd1e8f9 CM |
267 | struct flash_platform_data *plat = dev->dev.platform_data; |
268 | int i; | |
1da177e4 | 269 | |
3ae5eaec | 270 | platform_set_drvdata(dev, NULL); |
1da177e4 LT |
271 | |
272 | if (info) { | |
273 | if (info->mtd) { | |
274 | del_mtd_partitions(info->mtd); | |
9fd1e8f9 CM |
275 | #ifdef CONFIG_MTD_CONCAT |
276 | if (info->mtd != info->subdev[0].mtd) | |
277 | mtd_concat_destroy(info->mtd); | |
278 | #endif | |
1da177e4 | 279 | } |
fa671646 | 280 | kfree(info->parts); |
1da177e4 | 281 | |
9fd1e8f9 CM |
282 | for (i = info->nr_subdev - 1; i >= 0; i--) |
283 | armflash_subdev_remove(&info->subdev[i]); | |
1da177e4 | 284 | |
9fd1e8f9 CM |
285 | if (plat && plat->exit) |
286 | plat->exit(); | |
1da177e4 LT |
287 | |
288 | kfree(info); | |
289 | } | |
290 | ||
291 | return 0; | |
292 | } | |
293 | ||
3ae5eaec | 294 | static struct platform_driver armflash_driver = { |
1da177e4 LT |
295 | .probe = armflash_probe, |
296 | .remove = armflash_remove, | |
3ae5eaec RK |
297 | .driver = { |
298 | .name = "armflash", | |
41d867c9 | 299 | .owner = THIS_MODULE, |
3ae5eaec | 300 | }, |
1da177e4 LT |
301 | }; |
302 | ||
303 | static int __init armflash_init(void) | |
304 | { | |
3ae5eaec | 305 | return platform_driver_register(&armflash_driver); |
1da177e4 LT |
306 | } |
307 | ||
308 | static void __exit armflash_exit(void) | |
309 | { | |
3ae5eaec | 310 | platform_driver_unregister(&armflash_driver); |
1da177e4 LT |
311 | } |
312 | ||
313 | module_init(armflash_init); | |
314 | module_exit(armflash_exit); | |
315 | ||
316 | MODULE_AUTHOR("ARM Ltd"); | |
317 | MODULE_DESCRIPTION("ARM Integrator CFI map driver"); | |
318 | MODULE_LICENSE("GPL"); | |
41d867c9 | 319 | MODULE_ALIAS("platform:armflash"); |