Commit | Line | Data |
---|---|---|
18d54e55 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
1da177e4 | 2 | /* |
1da177e4 LT |
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 | |
642b1e8d BB |
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. | |
ba32ce95 BB |
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. | |
1da177e4 LT |
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> | |
73566edf LB |
31 | #include <linux/device.h> |
32 | #include <linux/platform_device.h> | |
1da177e4 LT |
33 | #include <linux/mtd/mtd.h> |
34 | #include <linux/mtd/map.h> | |
1da177e4 | 35 | #include <linux/mtd/partitions.h> |
2b9175c1 | 36 | #include <linux/mtd/physmap.h> |
df66e716 | 37 | #include <linux/mtd/concat.h> |
642b1e8d | 38 | #include <linux/mtd/cfi_endian.h> |
3136e903 | 39 | #include <linux/io.h> |
642b1e8d | 40 | #include <linux/of_device.h> |
0bc448b4 | 41 | #include <linux/pm_runtime.h> |
ba32ce95 | 42 | #include <linux/gpio/consumer.h> |
642b1e8d | 43 | |
b3e79e76 | 44 | #include "physmap-bt1-rom.h" |
6ca15cfa | 45 | #include "physmap-gemini.h" |
2aba2f2a | 46 | #include "physmap-ixp4xx.h" |
6ca15cfa | 47 | #include "physmap-versatile.h" |
1da177e4 | 48 | |
73566edf | 49 | struct physmap_flash_info { |
51b436a2 BB |
50 | unsigned int nmaps; |
51 | struct mtd_info **mtds; | |
df66e716 | 52 | struct mtd_info *cmtd; |
51b436a2 | 53 | struct map_info *maps; |
876fe76d PP |
54 | spinlock_t vpp_lock; |
55 | int vpp_refcnt; | |
642b1e8d BB |
56 | const char *probe_type; |
57 | const char * const *part_types; | |
58 | unsigned int nparts; | |
59 | const struct mtd_partition *parts; | |
ba32ce95 BB |
60 | struct gpio_descs *gpios; |
61 | unsigned int gpio_values; | |
62 | unsigned int win_order; | |
1da177e4 LT |
63 | }; |
64 | ||
73566edf LB |
65 | static int physmap_flash_remove(struct platform_device *dev) |
66 | { | |
67 | struct physmap_flash_info *info; | |
68 | struct physmap_flash_data *physmap_data; | |
bb823654 | 69 | int i; |
73566edf LB |
70 | |
71 | info = platform_get_drvdata(dev); | |
73566edf | 72 | |
8ce110ac | 73 | if (info->cmtd) { |
bb823654 | 74 | WARN_ON(mtd_device_unregister(info->cmtd)); |
1e4f4208 | 75 | |
00142d6f | 76 | if (info->cmtd != info->mtds[0]) |
8ce110ac | 77 | mtd_concat_destroy(info->cmtd); |
8ce110ac | 78 | } |
df66e716 | 79 | |
51b436a2 | 80 | for (i = 0; i < info->nmaps; i++) { |
cb946bf6 | 81 | if (info->mtds[i]) |
00142d6f | 82 | map_destroy(info->mtds[i]); |
73566edf | 83 | } |
b7281ca2 | 84 | |
642b1e8d BB |
85 | physmap_data = dev_get_platdata(&dev->dev); |
86 | if (physmap_data && physmap_data->exit) | |
b7281ca2 MZ |
87 | physmap_data->exit(dev); |
88 | ||
0bc448b4 GU |
89 | pm_runtime_put(&dev->dev); |
90 | pm_runtime_disable(&dev->dev); | |
bb823654 | 91 | return 0; |
1da177e4 | 92 | } |
1da177e4 | 93 | |
667f390b MZ |
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; | |
876fe76d PP |
98 | struct physmap_flash_info *info; |
99 | unsigned long flags; | |
667f390b MZ |
100 | |
101 | pdev = (struct platform_device *)map->map_priv_1; | |
d20d5a57 | 102 | physmap_data = dev_get_platdata(&pdev->dev); |
667f390b | 103 | |
876fe76d PP |
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(&info->vpp_lock, flags); | |
667f390b MZ |
118 | } |
119 | ||
ba32ce95 BB |
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(info->gpios->desc[i], !!(BIT(i) & ofs)); | |
135 | } | |
64d14c6f CP |
136 | |
137 | info->gpio_values = ofs; | |
ba32ce95 BB |
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(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(word, 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 | ||
642b1e8d BB |
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(dp, "linux,part-probe"); | |
281 | if (count < 0) | |
282 | return of_default_part_probes; | |
283 | ||
284 | res = devm_kcalloc(&dev->dev, count + 1, sizeof(*res), GFP_KERNEL); | |
285 | if (!res) | |
286 | return NULL; | |
287 | ||
288 | count = of_property_read_string_array(dp, "linux,part-probe", res, | |
289 | 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 struct of_device_id *match; | |
300 | const char *probe_type; | |
301 | ||
302 | match = of_match_device(of_flash_match, &dev->dev); | |
8b740c08 ZJ |
303 | if (!match) |
304 | return NULL; | |
305 | ||
642b1e8d BB |
306 | probe_type = match->data; |
307 | if (probe_type) | |
308 | return probe_type; | |
309 | ||
310 | dev_warn(&dev->dev, | |
311 | "Device tree uses obsolete \"direct-mapped\" flash binding\n"); | |
312 | ||
313 | of_property_read_string(dp, "probe-type", &probe_type); | |
314 | if (!probe_type) | |
315 | return NULL; | |
316 | ||
317 | if (!strcmp(probe_type, "CFI")) { | |
318 | probe_type = "cfi_probe"; | |
319 | } else if (!strcmp(probe_type, "JEDEC")) { | |
320 | probe_type = "jedec_probe"; | |
321 | } else if (!strcmp(probe_type, "ROM")) { | |
322 | probe_type = "map_rom"; | |
323 | } else { | |
324 | dev_warn(&dev->dev, | |
325 | "obsolete_probe: don't know probe type '%s', mapping as rom\n", | |
326 | probe_type); | |
327 | probe_type = "map_rom"; | |
328 | } | |
329 | ||
330 | return probe_type; | |
331 | } | |
332 | ||
333 | static int physmap_flash_of_init(struct platform_device *dev) | |
334 | { | |
335 | struct physmap_flash_info *info = platform_get_drvdata(dev); | |
336 | struct device_node *dp = dev->dev.of_node; | |
337 | const char *mtd_name = NULL; | |
338 | int err, swap = 0; | |
339 | bool map_indirect; | |
340 | unsigned int i; | |
341 | u32 bankwidth; | |
342 | ||
343 | if (!dp) | |
344 | return -EINVAL; | |
345 | ||
346 | info->probe_type = of_select_probe_type(dev); | |
347 | ||
348 | info->part_types = of_get_part_probes(dev); | |
349 | if (!info->part_types) | |
350 | return -ENOMEM; | |
351 | ||
352 | of_property_read_string(dp, "linux,mtd-name", &mtd_name); | |
353 | ||
354 | map_indirect = of_property_read_bool(dp, "no-unaligned-direct-access"); | |
355 | ||
356 | err = of_property_read_u32(dp, "bank-width", &bankwidth); | |
357 | if (err) { | |
358 | dev_err(&dev->dev, "Can't get bank width from device tree\n"); | |
359 | return err; | |
360 | } | |
361 | ||
362 | if (of_property_read_bool(dp, "big-endian")) | |
363 | swap = CFI_BIG_ENDIAN; | |
364 | else if (of_property_read_bool(dp, "little-endian")) | |
365 | swap = CFI_LITTLE_ENDIAN; | |
366 | ||
367 | for (i = 0; i < info->nmaps; i++) { | |
368 | info->maps[i].name = mtd_name; | |
369 | info->maps[i].swap = swap; | |
370 | info->maps[i].bankwidth = bankwidth; | |
371 | info->maps[i].device_node = dp; | |
372 | ||
b3e79e76 SS |
373 | err = of_flash_probe_bt1_rom(dev, dp, &info->maps[i]); |
374 | if (err) | |
375 | return err; | |
376 | ||
642b1e8d BB |
377 | err = of_flash_probe_gemini(dev, dp, &info->maps[i]); |
378 | if (err) | |
379 | return err; | |
380 | ||
2aba2f2a LW |
381 | err = of_flash_probe_ixp4xx(dev, dp, &info->maps[i]); |
382 | if (err) | |
383 | return err; | |
384 | ||
642b1e8d BB |
385 | err = of_flash_probe_versatile(dev, dp, &info->maps[i]); |
386 | if (err) | |
387 | return err; | |
388 | ||
389 | /* | |
390 | * On some platforms (e.g. MPC5200) a direct 1:1 mapping | |
391 | * may cause problems with JFFS2 usage, as the local bus (LPB) | |
392 | * doesn't support unaligned accesses as implemented in the | |
393 | * JFFS2 code via memcpy(). By setting NO_XIP, the | |
394 | * flash will not be exposed directly to the MTD users | |
395 | * (e.g. JFFS2) any more. | |
396 | */ | |
397 | if (map_indirect) | |
398 | info->maps[i].phys = NO_XIP; | |
399 | } | |
400 | ||
401 | return 0; | |
402 | } | |
403 | #else /* IS_ENABLED(CONFIG_MTD_PHYSMAP_OF) */ | |
404 | #define of_flash_match NULL | |
405 | ||
406 | static int physmap_flash_of_init(struct platform_device *dev) | |
407 | { | |
408 | return -ENOTSUPP; | |
409 | } | |
410 | #endif /* IS_ENABLED(CONFIG_MTD_PHYSMAP_OF) */ | |
411 | ||
f39cf6c7 | 412 | static const char * const rom_probe_types[] = { |
99f732b3 | 413 | "cfi_probe", "jedec_probe", "qinfo_probe", "map_rom", |
cb946bf6 | 414 | }; |
f39cf6c7 AB |
415 | |
416 | static const char * const part_probe_types[] = { | |
cb946bf6 BB |
417 | "cmdlinepart", "RedBoot", "afs", NULL |
418 | }; | |
73566edf | 419 | |
642b1e8d | 420 | static int physmap_flash_pdata_init(struct platform_device *dev) |
1da177e4 | 421 | { |
642b1e8d | 422 | struct physmap_flash_info *info = platform_get_drvdata(dev); |
73566edf | 423 | struct physmap_flash_data *physmap_data; |
642b1e8d BB |
424 | unsigned int i; |
425 | int err; | |
426 | ||
427 | physmap_data = dev_get_platdata(&dev->dev); | |
428 | if (!physmap_data) | |
429 | return -EINVAL; | |
430 | ||
431 | info->probe_type = physmap_data->probe_type; | |
432 | info->part_types = physmap_data->part_probe_types ? : part_probe_types; | |
433 | info->parts = physmap_data->parts; | |
434 | info->nparts = physmap_data->nr_parts; | |
435 | ||
436 | if (physmap_data->init) { | |
437 | err = physmap_data->init(dev); | |
438 | if (err) | |
439 | return err; | |
440 | } | |
441 | ||
442 | for (i = 0; i < info->nmaps; i++) { | |
443 | info->maps[i].bankwidth = physmap_data->width; | |
444 | info->maps[i].pfow_base = physmap_data->pfow_base; | |
445 | info->maps[i].set_vpp = physmap_set_vpp; | |
446 | } | |
447 | ||
448 | return 0; | |
449 | } | |
450 | ||
451 | static int physmap_flash_probe(struct platform_device *dev) | |
452 | { | |
73566edf | 453 | struct physmap_flash_info *info; |
df66e716 SR |
454 | int err = 0; |
455 | int i; | |
73566edf | 456 | |
642b1e8d BB |
457 | if (!dev->dev.of_node && !dev_get_platdata(&dev->dev)) |
458 | return -EINVAL; | |
73566edf | 459 | |
cb946bf6 | 460 | info = devm_kzalloc(&dev->dev, sizeof(*info), GFP_KERNEL); |
7abe5309 BB |
461 | if (!info) |
462 | return -ENOMEM; | |
1da177e4 | 463 | |
51b436a2 BB |
464 | while (platform_get_resource(dev, IORESOURCE_MEM, info->nmaps)) |
465 | info->nmaps++; | |
466 | ||
467 | if (!info->nmaps) | |
468 | return -ENODEV; | |
469 | ||
470 | info->maps = devm_kzalloc(&dev->dev, | |
471 | sizeof(*info->maps) * info->nmaps, | |
472 | GFP_KERNEL); | |
473 | if (!info->maps) | |
474 | return -ENOMEM; | |
475 | ||
476 | info->mtds = devm_kzalloc(&dev->dev, | |
477 | sizeof(*info->mtds) * info->nmaps, | |
478 | GFP_KERNEL); | |
479 | if (!info->mtds) | |
480 | return -ENOMEM; | |
481 | ||
73566edf | 482 | platform_set_drvdata(dev, info); |
1da177e4 | 483 | |
ba32ce95 BB |
484 | info->gpios = devm_gpiod_get_array_optional(&dev->dev, "addr", |
485 | GPIOD_OUT_LOW); | |
486 | if (IS_ERR(info->gpios)) | |
487 | return PTR_ERR(info->gpios); | |
488 | ||
489 | if (info->gpios && info->nmaps > 1) { | |
490 | dev_err(&dev->dev, "addr-gpios only supported for nmaps == 1\n"); | |
491 | return -EINVAL; | |
492 | } | |
493 | ||
0bc448b4 GU |
494 | pm_runtime_enable(&dev->dev); |
495 | pm_runtime_get_sync(&dev->dev); | |
496 | ||
642b1e8d BB |
497 | if (dev->dev.of_node) |
498 | err = physmap_flash_of_init(dev); | |
499 | else | |
500 | err = physmap_flash_pdata_init(dev); | |
501 | ||
0bc448b4 GU |
502 | if (err) { |
503 | pm_runtime_put(&dev->dev); | |
504 | pm_runtime_disable(&dev->dev); | |
642b1e8d | 505 | return err; |
0bc448b4 | 506 | } |
642b1e8d | 507 | |
51b436a2 | 508 | for (i = 0; i < info->nmaps; i++) { |
c7f6dc60 BB |
509 | struct resource *res; |
510 | ||
e1666cfd | 511 | info->maps[i].virt = devm_platform_get_and_ioremap_resource(dev, i, &res); |
ea5bc54b BB |
512 | if (IS_ERR(info->maps[i].virt)) { |
513 | err = PTR_ERR(info->maps[i].virt); | |
df66e716 SR |
514 | goto err_out; |
515 | } | |
1da177e4 | 516 | |
ea5bc54b BB |
517 | dev_notice(&dev->dev, "physmap platform flash device: %pR\n", |
518 | res); | |
519 | ||
9b210842 CP |
520 | if (!info->maps[i].name) |
521 | info->maps[i].name = dev_name(&dev->dev); | |
642b1e8d BB |
522 | |
523 | if (!info->maps[i].phys) | |
524 | info->maps[i].phys = res->start; | |
525 | ||
ba32ce95 BB |
526 | info->win_order = get_bitmask_order(resource_size(res)) - 1; |
527 | info->maps[i].size = BIT(info->win_order + | |
528 | (info->gpios ? | |
529 | info->gpios->ndescs : 0)); | |
530 | ||
00142d6f BB |
531 | info->maps[i].map_priv_1 = (unsigned long)dev; |
532 | ||
ba32ce95 BB |
533 | if (info->gpios) { |
534 | err = physmap_addr_gpios_map_init(&info->maps[i]); | |
535 | if (err) | |
536 | goto err_out; | |
ba32ce95 | 537 | } |
1da177e4 | 538 | |
b3dd9303 LW |
539 | #ifdef CONFIG_MTD_COMPLEX_MAPPINGS |
540 | /* | |
541 | * Only use the simple_map implementation if map hooks are not | |
542 | * implemented. Since map->read() is mandatory checking for its | |
543 | * presence is enough. | |
544 | */ | |
545 | if (!info->maps[i].read) | |
546 | simple_map_init(&info->maps[i]); | |
547 | #else | |
548 | simple_map_init(&info->maps[i]); | |
549 | #endif | |
550 | ||
99f732b3 | 551 | if (info->probe_type) { |
642b1e8d | 552 | info->mtds[i] = do_map_probe(info->probe_type, |
cb946bf6 | 553 | &info->maps[i]); |
6792b7fc GU |
554 | |
555 | /* Fall back to mapping region as ROM */ | |
556 | if (!info->mtds[i] && IS_ENABLED(CONFIG_MTD_ROM) && | |
557 | strcmp(info->probe_type, "map_rom")) { | |
558 | dev_warn(&dev->dev, | |
559 | "map_probe() failed for type %s\n", | |
560 | info->probe_type); | |
561 | ||
562 | info->mtds[i] = do_map_probe("map_rom", | |
563 | &info->maps[i]); | |
564 | } | |
99f732b3 RRD |
565 | } else { |
566 | int j; | |
567 | ||
568 | for (j = 0; j < ARRAY_SIZE(rom_probe_types); j++) { | |
569 | info->mtds[i] = do_map_probe(rom_probe_types[j], | |
570 | &info->maps[i]); | |
571 | if (info->mtds[i]) | |
572 | break; | |
573 | } | |
cb946bf6 | 574 | } |
78ef7fab | 575 | |
cb946bf6 | 576 | if (!info->mtds[i]) { |
df66e716 SR |
577 | dev_err(&dev->dev, "map_probe failed\n"); |
578 | err = -ENXIO; | |
579 | goto err_out; | |
df66e716 | 580 | } |
00142d6f | 581 | info->mtds[i]->dev.parent = &dev->dev; |
df66e716 | 582 | } |
73566edf | 583 | |
51b436a2 | 584 | if (info->nmaps == 1) { |
00142d6f | 585 | info->cmtd = info->mtds[0]; |
c7f6dc60 | 586 | } else { |
df66e716 SR |
587 | /* |
588 | * We detected multiple devices. Concatenate them together. | |
589 | */ | |
51b436a2 BB |
590 | info->cmtd = mtd_concat_create(info->mtds, info->nmaps, |
591 | dev_name(&dev->dev)); | |
cb946bf6 | 592 | if (!info->cmtd) |
df66e716 | 593 | err = -ENXIO; |
1da177e4 | 594 | } |
df66e716 SR |
595 | if (err) |
596 | goto err_out; | |
1da177e4 | 597 | |
876fe76d PP |
598 | spin_lock_init(&info->vpp_lock); |
599 | ||
642b1e8d BB |
600 | mtd_set_of_node(info->cmtd, dev->dev.of_node); |
601 | err = mtd_device_parse_register(info->cmtd, info->part_types, NULL, | |
602 | info->parts, info->nparts); | |
1e4f4208 BB |
603 | if (err) |
604 | goto err_out; | |
605 | ||
73566edf LB |
606 | return 0; |
607 | ||
608 | err_out: | |
609 | physmap_flash_remove(dev); | |
610 | return err; | |
611 | } | |
612 | ||
17c2dae3 | 613 | #ifdef CONFIG_PM |
17c2dae3 LB |
614 | static void physmap_flash_shutdown(struct platform_device *dev) |
615 | { | |
616 | struct physmap_flash_info *info = platform_get_drvdata(dev); | |
df66e716 SR |
617 | int i; |
618 | ||
51b436a2 | 619 | for (i = 0; i < info->nmaps && info->mtds[i]; i++) |
00142d6f BB |
620 | if (mtd_suspend(info->mtds[i]) == 0) |
621 | mtd_resume(info->mtds[i]); | |
17c2dae3 | 622 | } |
d5476689 | 623 | #else |
d5476689 | 624 | #define physmap_flash_shutdown NULL |
17c2dae3 LB |
625 | #endif |
626 | ||
73566edf LB |
627 | static struct platform_driver physmap_flash_driver = { |
628 | .probe = physmap_flash_probe, | |
629 | .remove = physmap_flash_remove, | |
17c2dae3 | 630 | .shutdown = physmap_flash_shutdown, |
73566edf LB |
631 | .driver = { |
632 | .name = "physmap-flash", | |
642b1e8d | 633 | .of_match_table = of_flash_match, |
73566edf LB |
634 | }, |
635 | }; | |
1da177e4 | 636 | |
dcb3e137 | 637 | #ifdef CONFIG_MTD_PHYSMAP_COMPAT |
73566edf LB |
638 | static struct physmap_flash_data physmap_flash_data = { |
639 | .width = CONFIG_MTD_PHYSMAP_BANKWIDTH, | |
640 | }; | |
1da177e4 | 641 | |
73566edf LB |
642 | static struct resource physmap_flash_resource = { |
643 | .start = CONFIG_MTD_PHYSMAP_START, | |
6d4f8224 | 644 | .end = CONFIG_MTD_PHYSMAP_START + CONFIG_MTD_PHYSMAP_LEN - 1, |
73566edf LB |
645 | .flags = IORESOURCE_MEM, |
646 | }; | |
1da177e4 | 647 | |
73566edf LB |
648 | static struct platform_device physmap_flash = { |
649 | .name = "physmap-flash", | |
650 | .id = 0, | |
651 | .dev = { | |
652 | .platform_data = &physmap_flash_data, | |
653 | }, | |
654 | .num_resources = 1, | |
655 | .resource = &physmap_flash_resource, | |
656 | }; | |
73566edf | 657 | #endif |
1da177e4 | 658 | |
73566edf LB |
659 | static int __init physmap_init(void) |
660 | { | |
661 | int err; | |
662 | ||
663 | err = platform_driver_register(&physmap_flash_driver); | |
dcb3e137 | 664 | #ifdef CONFIG_MTD_PHYSMAP_COMPAT |
1ca5d2f0 HS |
665 | if (err == 0) { |
666 | err = platform_device_register(&physmap_flash); | |
667 | if (err) | |
668 | platform_driver_unregister(&physmap_flash_driver); | |
669 | } | |
73566edf LB |
670 | #endif |
671 | ||
672 | return err; | |
1da177e4 LT |
673 | } |
674 | ||
73566edf LB |
675 | static void __exit physmap_exit(void) |
676 | { | |
dcb3e137 | 677 | #ifdef CONFIG_MTD_PHYSMAP_COMPAT |
73566edf LB |
678 | platform_device_unregister(&physmap_flash); |
679 | #endif | |
680 | platform_driver_unregister(&physmap_flash_driver); | |
681 | } | |
1da177e4 | 682 | |
73566edf LB |
683 | module_init(physmap_init); |
684 | module_exit(physmap_exit); | |
1da177e4 LT |
685 | |
686 | MODULE_LICENSE("GPL"); | |
687 | MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); | |
642b1e8d | 688 | MODULE_AUTHOR("Vitaly Wool <vwool@ru.mvista.com>"); |
ba32ce95 | 689 | MODULE_AUTHOR("Mike Frysinger <vapier@gentoo.org>"); |
1da177e4 | 690 | MODULE_DESCRIPTION("Generic configurable MTD map driver"); |
41d867c9 KS |
691 | |
692 | /* legacy platform drivers can't hotplug or coldplg */ | |
dcb3e137 | 693 | #ifndef CONFIG_MTD_PHYSMAP_COMPAT |
41d867c9 KS |
694 | /* work with hotplug and coldplug */ |
695 | MODULE_ALIAS("platform:physmap-flash"); | |
696 | #endif |