1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Normal mappings of chips in physical memory |
4 | * |
5 | * Copyright (C) 2003 MontaVista Software Inc. |
6 | * Author: Jun Sun, jsun@mvista.com or jsun@junsun.net |
7 | * |
8 | * 031022 - [jsun] add run-time configure and partition setup |
9 | * |
10 | * Device tree support: |
11 | * Copyright (C) 2006 MontaVista Software Inc. |
12 | * Author: Vitaly Wool <vwool@ru.mvista.com> |
13 | * |
14 | * Revised to handle newer style flash binding by: |
15 | * Copyright (C) 2007 David Gibson, IBM Corporation. |
16 | * |
17 | * GPIO address extension: |
18 | * Handle the case where a flash device is mostly addressed using physical |
19 | * line and supplemented by GPIOs. This way you can hook up say a 8MiB flash |
20 | * to a 2MiB memory range and use the GPIOs to select a particular range. |
21 | * |
22 | * Copyright © 2000 Nicolas Pitre <nico@cam.org> |
23 | * Copyright © 2005-2009 Analog Devices Inc. |
24 | */ |
25 | |
26 | #include <linux/module.h> |
27 | #include <linux/types.h> |
28 | #include <linux/kernel.h> |
29 | #include <linux/init.h> |
30 | #include <linux/slab.h> |
31 | #include <linux/device.h> |
32 | #include <linux/platform_device.h> |
33 | #include <linux/property.h> |
34 | #include <linux/mtd/mtd.h> |
35 | #include <linux/mtd/map.h> |
36 | #include <linux/mtd/partitions.h> |
37 | #include <linux/mtd/physmap.h> |
38 | #include <linux/mtd/concat.h> |
39 | #include <linux/mtd/cfi_endian.h> |
40 | #include <linux/io.h> |
41 | #include <linux/of.h> |
42 | #include <linux/pm_runtime.h> |
43 | #include <linux/gpio/consumer.h> |
44 | |
45 | #include "physmap-bt1-rom.h" |
46 | #include "physmap-gemini.h" |
47 | #include "physmap-ixp4xx.h" |
48 | #include "physmap-versatile.h" |
49 | |
50 | struct physmap_flash_info { |
51 | unsigned int nmaps; |
52 | struct mtd_info **mtds; |
53 | struct mtd_info *cmtd; |
54 | struct map_info *maps; |
55 | spinlock_t vpp_lock; |
56 | int vpp_refcnt; |
57 | const char *probe_type; |
58 | const char * const *part_types; |
59 | unsigned int nparts; |
60 | const struct mtd_partition *parts; |
61 | struct gpio_descs *gpios; |
62 | unsigned int gpio_values; |
63 | unsigned int win_order; |
64 | }; |
65 | |
66 | static void physmap_flash_remove(struct platform_device *dev) |
67 | { |
68 | struct physmap_flash_info *info; |
69 | struct physmap_flash_data *physmap_data; |
70 | int i; |
71 | |
72 | info = platform_get_drvdata(pdev: dev); |
73 | |
74 | if (info->cmtd) { |
75 | WARN_ON(mtd_device_unregister(info->cmtd)); |
76 | |
77 | if (info->cmtd != info->mtds[0]) |
78 | mtd_concat_destroy(mtd: info->cmtd); |
79 | } |
80 | |
81 | for (i = 0; i < info->nmaps; i++) { |
82 | if (info->mtds[i]) |
83 | map_destroy(mtd: info->mtds[i]); |
84 | } |
85 | |
86 | physmap_data = dev_get_platdata(dev: &dev->dev); |
87 | if (physmap_data && physmap_data->exit) |
88 | physmap_data->exit(dev); |
89 | |
90 | pm_runtime_put(dev: &dev->dev); |
91 | pm_runtime_disable(dev: &dev->dev); |
92 | } |
93 | |
94 | static void physmap_set_vpp(struct map_info *map, int state) |
95 | { |
96 | struct platform_device *pdev; |
97 | struct physmap_flash_data *physmap_data; |
98 | struct physmap_flash_info *info; |
99 | unsigned long flags; |
100 | |
101 | pdev = (struct platform_device *)map->map_priv_1; |
102 | physmap_data = dev_get_platdata(dev: &pdev->dev); |
103 | |
104 | if (!physmap_data->set_vpp) |
105 | return; |
106 | |
107 | info = platform_get_drvdata(pdev); |
108 | |
109 | spin_lock_irqsave(&info->vpp_lock, flags); |
110 | if (state) { |
111 | if (++info->vpp_refcnt == 1) /* first nested 'on' */ |
112 | physmap_data->set_vpp(pdev, 1); |
113 | } else { |
114 | if (--info->vpp_refcnt == 0) /* last nested 'off' */ |
115 | physmap_data->set_vpp(pdev, 0); |
116 | } |
117 | spin_unlock_irqrestore(lock: &info->vpp_lock, flags); |
118 | } |
119 | |
120 | #if IS_ENABLED(CONFIG_MTD_PHYSMAP_GPIO_ADDR) |
121 | static void physmap_set_addr_gpios(struct physmap_flash_info *info, |
122 | unsigned long ofs) |
123 | { |
124 | unsigned int i; |
125 | |
126 | ofs >>= info->win_order; |
127 | if (info->gpio_values == ofs) |
128 | return; |
129 | |
130 | for (i = 0; i < info->gpios->ndescs; i++) { |
131 | if ((BIT(i) & ofs) == (BIT(i) & info->gpio_values)) |
132 | continue; |
133 | |
134 | gpiod_set_value(desc: info->gpios->desc[i], value: !!(BIT(i) & ofs)); |
135 | } |
136 | |
137 | info->gpio_values = ofs; |
138 | } |
139 | |
140 | #define win_mask(order) (BIT(order) - 1) |
141 | |
142 | static map_word physmap_addr_gpios_read(struct map_info *map, |
143 | unsigned long ofs) |
144 | { |
145 | struct platform_device *pdev; |
146 | struct physmap_flash_info *info; |
147 | map_word mw; |
148 | u16 word; |
149 | |
150 | pdev = (struct platform_device *)map->map_priv_1; |
151 | info = platform_get_drvdata(pdev); |
152 | physmap_set_addr_gpios(info, ofs); |
153 | |
154 | word = readw(addr: map->virt + (ofs & win_mask(info->win_order))); |
155 | mw.x[0] = word; |
156 | return mw; |
157 | } |
158 | |
159 | static void physmap_addr_gpios_copy_from(struct map_info *map, void *buf, |
160 | unsigned long ofs, ssize_t len) |
161 | { |
162 | struct platform_device *pdev; |
163 | struct physmap_flash_info *info; |
164 | |
165 | pdev = (struct platform_device *)map->map_priv_1; |
166 | info = platform_get_drvdata(pdev); |
167 | |
168 | while (len) { |
169 | unsigned int winofs = ofs & win_mask(info->win_order); |
170 | unsigned int chunklen = min_t(unsigned int, len, |
171 | BIT(info->win_order) - winofs); |
172 | |
173 | physmap_set_addr_gpios(info, ofs); |
174 | memcpy_fromio(buf, map->virt + winofs, chunklen); |
175 | len -= chunklen; |
176 | buf += chunklen; |
177 | ofs += chunklen; |
178 | } |
179 | } |
180 | |
181 | static void physmap_addr_gpios_write(struct map_info *map, map_word mw, |
182 | unsigned long ofs) |
183 | { |
184 | struct platform_device *pdev; |
185 | struct physmap_flash_info *info; |
186 | u16 word; |
187 | |
188 | pdev = (struct platform_device *)map->map_priv_1; |
189 | info = platform_get_drvdata(pdev); |
190 | physmap_set_addr_gpios(info, ofs); |
191 | |
192 | word = mw.x[0]; |
193 | writew(val: word, addr: map->virt + (ofs & win_mask(info->win_order))); |
194 | } |
195 | |
196 | static void physmap_addr_gpios_copy_to(struct map_info *map, unsigned long ofs, |
197 | const void *buf, ssize_t len) |
198 | { |
199 | struct platform_device *pdev; |
200 | struct physmap_flash_info *info; |
201 | |
202 | pdev = (struct platform_device *)map->map_priv_1; |
203 | info = platform_get_drvdata(pdev); |
204 | |
205 | while (len) { |
206 | unsigned int winofs = ofs & win_mask(info->win_order); |
207 | unsigned int chunklen = min_t(unsigned int, len, |
208 | BIT(info->win_order) - winofs); |
209 | |
210 | physmap_set_addr_gpios(info, ofs); |
211 | memcpy_toio(map->virt + winofs, buf, chunklen); |
212 | len -= chunklen; |
213 | buf += chunklen; |
214 | ofs += chunklen; |
215 | } |
216 | } |
217 | |
218 | static int physmap_addr_gpios_map_init(struct map_info *map) |
219 | { |
220 | map->phys = NO_XIP; |
221 | map->read = physmap_addr_gpios_read; |
222 | map->copy_from = physmap_addr_gpios_copy_from; |
223 | map->write = physmap_addr_gpios_write; |
224 | map->copy_to = physmap_addr_gpios_copy_to; |
225 | |
226 | return 0; |
227 | } |
228 | #else |
229 | static int physmap_addr_gpios_map_init(struct map_info *map) |
230 | { |
231 | return -ENOTSUPP; |
232 | } |
233 | #endif |
234 | |
235 | #if IS_ENABLED(CONFIG_MTD_PHYSMAP_OF) |
236 | static const struct of_device_id of_flash_match[] = { |
237 | { |
238 | .compatible = "cfi-flash" , |
239 | .data = "cfi_probe" , |
240 | }, |
241 | { |
242 | /* |
243 | * FIXME: JEDEC chips can't be safely and reliably |
244 | * probed, although the mtd code gets it right in |
245 | * practice most of the time. We should use the |
246 | * vendor and device ids specified by the binding to |
247 | * bypass the heuristic probe code, but the mtd layer |
248 | * provides, at present, no interface for doing so |
249 | * :(. |
250 | */ |
251 | .compatible = "jedec-flash" , |
252 | .data = "jedec_probe" , |
253 | }, |
254 | { |
255 | .compatible = "mtd-ram" , |
256 | .data = "map_ram" , |
257 | }, |
258 | { |
259 | .compatible = "mtd-rom" , |
260 | .data = "map_rom" , |
261 | }, |
262 | { |
263 | .type = "rom" , |
264 | .compatible = "direct-mapped" |
265 | }, |
266 | { /* sentinel */ }, |
267 | }; |
268 | MODULE_DEVICE_TABLE(of, of_flash_match); |
269 | |
270 | static const char * const of_default_part_probes[] = { |
271 | "cmdlinepart" , "RedBoot" , "ofpart" , "ofoldpart" , NULL |
272 | }; |
273 | |
274 | static const char * const *of_get_part_probes(struct platform_device *dev) |
275 | { |
276 | struct device_node *dp = dev->dev.of_node; |
277 | const char **res; |
278 | int count; |
279 | |
280 | count = of_property_count_strings(np: dp, propname: "linux,part-probe" ); |
281 | if (count < 0) |
282 | return of_default_part_probes; |
283 | |
284 | res = devm_kcalloc(dev: &dev->dev, n: count + 1, size: sizeof(*res), GFP_KERNEL); |
285 | if (!res) |
286 | return NULL; |
287 | |
288 | count = of_property_read_string_array(np: dp, propname: "linux,part-probe" , out_strs: res, |
289 | sz: count); |
290 | if (count < 0) |
291 | return NULL; |
292 | |
293 | return res; |
294 | } |
295 | |
296 | static const char *of_select_probe_type(struct platform_device *dev) |
297 | { |
298 | struct device_node *dp = dev->dev.of_node; |
299 | const char *probe_type; |
300 | |
301 | probe_type = device_get_match_data(dev: &dev->dev); |
302 | if (probe_type) |
303 | return probe_type; |
304 | |
305 | dev_warn(&dev->dev, |
306 | "Device tree uses obsolete \"direct-mapped\" flash binding\n" ); |
307 | |
308 | of_property_read_string(np: dp, propname: "probe-type" , out_string: &probe_type); |
309 | if (!probe_type) |
310 | return NULL; |
311 | |
312 | if (!strcmp(probe_type, "CFI" )) { |
313 | probe_type = "cfi_probe" ; |
314 | } else if (!strcmp(probe_type, "JEDEC" )) { |
315 | probe_type = "jedec_probe" ; |
316 | } else if (!strcmp(probe_type, "ROM" )) { |
317 | probe_type = "map_rom" ; |
318 | } else { |
319 | dev_warn(&dev->dev, |
320 | "obsolete_probe: don't know probe type '%s', mapping as rom\n" , |
321 | probe_type); |
322 | probe_type = "map_rom" ; |
323 | } |
324 | |
325 | return probe_type; |
326 | } |
327 | |
328 | static int physmap_flash_of_init(struct platform_device *dev) |
329 | { |
330 | struct physmap_flash_info *info = platform_get_drvdata(pdev: dev); |
331 | struct device_node *dp = dev->dev.of_node; |
332 | const char *mtd_name = NULL; |
333 | int err, swap = 0; |
334 | bool map_indirect; |
335 | unsigned int i; |
336 | u32 bankwidth; |
337 | |
338 | if (!dp) |
339 | return -EINVAL; |
340 | |
341 | info->probe_type = of_select_probe_type(dev); |
342 | |
343 | info->part_types = of_get_part_probes(dev); |
344 | if (!info->part_types) |
345 | return -ENOMEM; |
346 | |
347 | of_property_read_string(np: dp, propname: "linux,mtd-name" , out_string: &mtd_name); |
348 | |
349 | map_indirect = of_property_read_bool(np: dp, propname: "no-unaligned-direct-access" ); |
350 | |
351 | err = of_property_read_u32(np: dp, propname: "bank-width" , out_value: &bankwidth); |
352 | if (err) { |
353 | dev_err(&dev->dev, "Can't get bank width from device tree\n" ); |
354 | return err; |
355 | } |
356 | |
357 | if (of_property_read_bool(np: dp, propname: "big-endian" )) |
358 | swap = CFI_BIG_ENDIAN; |
359 | else if (of_property_read_bool(np: dp, propname: "little-endian" )) |
360 | swap = CFI_LITTLE_ENDIAN; |
361 | |
362 | for (i = 0; i < info->nmaps; i++) { |
363 | info->maps[i].name = mtd_name; |
364 | info->maps[i].swap = swap; |
365 | info->maps[i].bankwidth = bankwidth; |
366 | info->maps[i].device_node = dp; |
367 | |
368 | err = of_flash_probe_bt1_rom(pdev: dev, np: dp, map: &info->maps[i]); |
369 | if (err) |
370 | return err; |
371 | |
372 | err = of_flash_probe_gemini(pdev: dev, np: dp, map: &info->maps[i]); |
373 | if (err) |
374 | return err; |
375 | |
376 | err = of_flash_probe_ixp4xx(pdev: dev, np: dp, map: &info->maps[i]); |
377 | if (err) |
378 | return err; |
379 | |
380 | err = of_flash_probe_versatile(pdev: dev, np: dp, map: &info->maps[i]); |
381 | if (err) |
382 | return err; |
383 | |
384 | /* |
385 | * On some platforms (e.g. MPC5200) a direct 1:1 mapping |
386 | * may cause problems with JFFS2 usage, as the local bus (LPB) |
387 | * doesn't support unaligned accesses as implemented in the |
388 | * JFFS2 code via memcpy(). By setting NO_XIP, the |
389 | * flash will not be exposed directly to the MTD users |
390 | * (e.g. JFFS2) any more. |
391 | */ |
392 | if (map_indirect) |
393 | info->maps[i].phys = NO_XIP; |
394 | } |
395 | |
396 | return 0; |
397 | } |
398 | #else /* IS_ENABLED(CONFIG_MTD_PHYSMAP_OF) */ |
399 | #define of_flash_match NULL |
400 | |
401 | static int physmap_flash_of_init(struct platform_device *dev) |
402 | { |
403 | return -ENOTSUPP; |
404 | } |
405 | #endif /* IS_ENABLED(CONFIG_MTD_PHYSMAP_OF) */ |
406 | |
407 | static const char * const rom_probe_types[] = { |
408 | "cfi_probe" , "jedec_probe" , "qinfo_probe" , "map_rom" , |
409 | }; |
410 | |
411 | static const char * const part_probe_types[] = { |
412 | "cmdlinepart" , "RedBoot" , "afs" , NULL |
413 | }; |
414 | |
415 | static int physmap_flash_pdata_init(struct platform_device *dev) |
416 | { |
417 | struct physmap_flash_info *info = platform_get_drvdata(pdev: dev); |
418 | struct physmap_flash_data *physmap_data; |
419 | unsigned int i; |
420 | int err; |
421 | |
422 | physmap_data = dev_get_platdata(dev: &dev->dev); |
423 | if (!physmap_data) |
424 | return -EINVAL; |
425 | |
426 | info->probe_type = physmap_data->probe_type; |
427 | info->part_types = physmap_data->part_probe_types ? : part_probe_types; |
428 | info->parts = physmap_data->parts; |
429 | info->nparts = physmap_data->nr_parts; |
430 | |
431 | if (physmap_data->init) { |
432 | err = physmap_data->init(dev); |
433 | if (err) |
434 | return err; |
435 | } |
436 | |
437 | for (i = 0; i < info->nmaps; i++) { |
438 | info->maps[i].bankwidth = physmap_data->width; |
439 | info->maps[i].pfow_base = physmap_data->pfow_base; |
440 | info->maps[i].set_vpp = physmap_set_vpp; |
441 | } |
442 | |
443 | return 0; |
444 | } |
445 | |
446 | static int physmap_flash_probe(struct platform_device *dev) |
447 | { |
448 | struct physmap_flash_info *info; |
449 | int err = 0; |
450 | int i; |
451 | |
452 | if (!dev->dev.of_node && !dev_get_platdata(dev: &dev->dev)) |
453 | return -EINVAL; |
454 | |
455 | info = devm_kzalloc(dev: &dev->dev, size: sizeof(*info), GFP_KERNEL); |
456 | if (!info) |
457 | return -ENOMEM; |
458 | |
459 | while (platform_get_resource(dev, IORESOURCE_MEM, info->nmaps)) |
460 | info->nmaps++; |
461 | |
462 | if (!info->nmaps) |
463 | return -ENODEV; |
464 | |
465 | info->maps = devm_kzalloc(dev: &dev->dev, |
466 | size: sizeof(*info->maps) * info->nmaps, |
467 | GFP_KERNEL); |
468 | if (!info->maps) |
469 | return -ENOMEM; |
470 | |
471 | info->mtds = devm_kzalloc(dev: &dev->dev, |
472 | size: sizeof(*info->mtds) * info->nmaps, |
473 | GFP_KERNEL); |
474 | if (!info->mtds) |
475 | return -ENOMEM; |
476 | |
477 | platform_set_drvdata(pdev: dev, data: info); |
478 | |
479 | info->gpios = devm_gpiod_get_array_optional(dev: &dev->dev, con_id: "addr" , |
480 | flags: GPIOD_OUT_LOW); |
481 | if (IS_ERR(ptr: info->gpios)) |
482 | return PTR_ERR(ptr: info->gpios); |
483 | |
484 | if (info->gpios && info->nmaps > 1) { |
485 | dev_err(&dev->dev, "addr-gpios only supported for nmaps == 1\n" ); |
486 | return -EINVAL; |
487 | } |
488 | |
489 | pm_runtime_enable(dev: &dev->dev); |
490 | pm_runtime_get_sync(dev: &dev->dev); |
491 | |
492 | if (dev->dev.of_node) |
493 | err = physmap_flash_of_init(dev); |
494 | else |
495 | err = physmap_flash_pdata_init(dev); |
496 | |
497 | if (err) { |
498 | pm_runtime_put(dev: &dev->dev); |
499 | pm_runtime_disable(dev: &dev->dev); |
500 | return err; |
501 | } |
502 | |
503 | for (i = 0; i < info->nmaps; i++) { |
504 | struct resource *res; |
505 | |
506 | info->maps[i].virt = devm_platform_get_and_ioremap_resource(pdev: dev, index: i, res: &res); |
507 | if (IS_ERR(ptr: info->maps[i].virt)) { |
508 | err = PTR_ERR(ptr: info->maps[i].virt); |
509 | goto err_out; |
510 | } |
511 | |
512 | dev_notice(&dev->dev, "physmap platform flash device: %pR\n" , |
513 | res); |
514 | |
515 | if (!info->maps[i].name) |
516 | info->maps[i].name = dev_name(dev: &dev->dev); |
517 | |
518 | if (!info->maps[i].phys) |
519 | info->maps[i].phys = res->start; |
520 | |
521 | info->win_order = fls64(x: resource_size(res)) - 1; |
522 | info->maps[i].size = BIT(info->win_order + |
523 | (info->gpios ? |
524 | info->gpios->ndescs : 0)); |
525 | |
526 | info->maps[i].map_priv_1 = (unsigned long)dev; |
527 | |
528 | if (info->gpios) { |
529 | err = physmap_addr_gpios_map_init(map: &info->maps[i]); |
530 | if (err) |
531 | goto err_out; |
532 | } |
533 | |
534 | #ifdef CONFIG_MTD_COMPLEX_MAPPINGS |
535 | /* |
536 | * Only use the simple_map implementation if map hooks are not |
537 | * implemented. Since map->read() is mandatory checking for its |
538 | * presence is enough. |
539 | */ |
540 | if (!info->maps[i].read) |
541 | simple_map_init(&info->maps[i]); |
542 | #else |
543 | simple_map_init(&info->maps[i]); |
544 | #endif |
545 | |
546 | if (info->probe_type) { |
547 | info->mtds[i] = do_map_probe(name: info->probe_type, |
548 | map: &info->maps[i]); |
549 | |
550 | /* Fall back to mapping region as ROM */ |
551 | if (!info->mtds[i] && IS_ENABLED(CONFIG_MTD_ROM) && |
552 | strcmp(info->probe_type, "map_rom" )) { |
553 | dev_warn(&dev->dev, |
554 | "map_probe() failed for type %s\n" , |
555 | info->probe_type); |
556 | |
557 | info->mtds[i] = do_map_probe(name: "map_rom" , |
558 | map: &info->maps[i]); |
559 | } |
560 | } else { |
561 | int j; |
562 | |
563 | for (j = 0; j < ARRAY_SIZE(rom_probe_types); j++) { |
564 | info->mtds[i] = do_map_probe(name: rom_probe_types[j], |
565 | map: &info->maps[i]); |
566 | if (info->mtds[i]) |
567 | break; |
568 | } |
569 | } |
570 | |
571 | if (!info->mtds[i]) { |
572 | dev_err(&dev->dev, "map_probe failed\n" ); |
573 | err = -ENXIO; |
574 | goto err_out; |
575 | } |
576 | info->mtds[i]->dev.parent = &dev->dev; |
577 | } |
578 | |
579 | if (info->nmaps == 1) { |
580 | info->cmtd = info->mtds[0]; |
581 | } else { |
582 | /* |
583 | * We detected multiple devices. Concatenate them together. |
584 | */ |
585 | info->cmtd = mtd_concat_create(subdev: info->mtds, num_devs: info->nmaps, |
586 | name: dev_name(dev: &dev->dev)); |
587 | if (!info->cmtd) |
588 | err = -ENXIO; |
589 | } |
590 | if (err) |
591 | goto err_out; |
592 | |
593 | spin_lock_init(&info->vpp_lock); |
594 | |
595 | mtd_set_of_node(mtd: info->cmtd, np: dev->dev.of_node); |
596 | err = mtd_device_parse_register(mtd: info->cmtd, part_probe_types: info->part_types, NULL, |
597 | defparts: info->parts, defnr_parts: info->nparts); |
598 | if (err) |
599 | goto err_out; |
600 | |
601 | return 0; |
602 | |
603 | err_out: |
604 | physmap_flash_remove(dev); |
605 | return err; |
606 | } |
607 | |
608 | #ifdef CONFIG_PM |
609 | static void physmap_flash_shutdown(struct platform_device *dev) |
610 | { |
611 | struct physmap_flash_info *info = platform_get_drvdata(pdev: dev); |
612 | int i; |
613 | |
614 | for (i = 0; i < info->nmaps && info->mtds[i]; i++) |
615 | if (mtd_suspend(mtd: info->mtds[i]) == 0) |
616 | mtd_resume(mtd: info->mtds[i]); |
617 | } |
618 | #else |
619 | #define physmap_flash_shutdown NULL |
620 | #endif |
621 | |
622 | static struct platform_driver physmap_flash_driver = { |
623 | .probe = physmap_flash_probe, |
624 | .remove_new = physmap_flash_remove, |
625 | .shutdown = physmap_flash_shutdown, |
626 | .driver = { |
627 | .name = "physmap-flash" , |
628 | .of_match_table = of_flash_match, |
629 | }, |
630 | }; |
631 | |
632 | #ifdef CONFIG_MTD_PHYSMAP_COMPAT |
633 | static struct physmap_flash_data physmap_flash_data = { |
634 | .width = CONFIG_MTD_PHYSMAP_BANKWIDTH, |
635 | }; |
636 | |
637 | static struct resource physmap_flash_resource = { |
638 | .start = CONFIG_MTD_PHYSMAP_START, |
639 | .end = CONFIG_MTD_PHYSMAP_START + CONFIG_MTD_PHYSMAP_LEN - 1, |
640 | .flags = IORESOURCE_MEM, |
641 | }; |
642 | |
643 | static struct platform_device physmap_flash = { |
644 | .name = "physmap-flash" , |
645 | .id = 0, |
646 | .dev = { |
647 | .platform_data = &physmap_flash_data, |
648 | }, |
649 | .num_resources = 1, |
650 | .resource = &physmap_flash_resource, |
651 | }; |
652 | #endif |
653 | |
654 | static int __init physmap_init(void) |
655 | { |
656 | int err; |
657 | |
658 | err = platform_driver_register(&physmap_flash_driver); |
659 | #ifdef CONFIG_MTD_PHYSMAP_COMPAT |
660 | if (err == 0) { |
661 | err = platform_device_register(&physmap_flash); |
662 | if (err) |
663 | platform_driver_unregister(&physmap_flash_driver); |
664 | } |
665 | #endif |
666 | |
667 | return err; |
668 | } |
669 | |
670 | static void __exit physmap_exit(void) |
671 | { |
672 | #ifdef CONFIG_MTD_PHYSMAP_COMPAT |
673 | platform_device_unregister(&physmap_flash); |
674 | #endif |
675 | platform_driver_unregister(&physmap_flash_driver); |
676 | } |
677 | |
678 | module_init(physmap_init); |
679 | module_exit(physmap_exit); |
680 | |
681 | MODULE_LICENSE("GPL" ); |
682 | MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>" ); |
683 | MODULE_AUTHOR("Vitaly Wool <vwool@ru.mvista.com>" ); |
684 | MODULE_AUTHOR("Mike Frysinger <vapier@gentoo.org>" ); |
685 | MODULE_DESCRIPTION("Generic configurable MTD map driver" ); |
686 | |
687 | /* legacy platform drivers can't hotplug or coldplg */ |
688 | #ifndef CONFIG_MTD_PHYSMAP_COMPAT |
689 | /* work with hotplug and coldplug */ |
690 | MODULE_ALIAS("platform:physmap-flash" ); |
691 | #endif |
692 | |