diff options
Diffstat (limited to 'drivers/mtd')
-rw-r--r-- | drivers/mtd/maps/Kconfig | 2 | ||||
-rw-r--r-- | drivers/mtd/maps/physmap_of.c | 349 |
2 files changed, 218 insertions, 133 deletions
diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index cc6c73442435..6cd132c75187 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig | |||
@@ -362,7 +362,7 @@ config MTD_WALNUT | |||
362 | 362 | ||
363 | config MTD_EBONY | 363 | config MTD_EBONY |
364 | tristate "Flash devices mapped on IBM 440GP Ebony" | 364 | tristate "Flash devices mapped on IBM 440GP Ebony" |
365 | depends on MTD_JEDECPROBE && EBONY | 365 | depends on MTD_JEDECPROBE && EBONY && !PPC_MERGE |
366 | help | 366 | help |
367 | This enables access routines for the flash chips on the IBM 440GP | 367 | This enables access routines for the flash chips on the IBM 440GP |
368 | Ebony board. If you have one of these boards and would like to | 368 | Ebony board. If you have one of these boards and would like to |
diff --git a/drivers/mtd/maps/physmap_of.c b/drivers/mtd/maps/physmap_of.c index bbb42c35b69b..cf75a566442e 100644 --- a/drivers/mtd/maps/physmap_of.c +++ b/drivers/mtd/maps/physmap_of.c | |||
@@ -1,9 +1,12 @@ | |||
1 | /* | 1 | /* |
2 | * Normal mappings of chips in physical memory for OF devices | 2 | * Flash mappings described by the OF (or flattened) device tree |
3 | * | 3 | * |
4 | * Copyright (C) 2006 MontaVista Software Inc. | 4 | * Copyright (C) 2006 MontaVista Software Inc. |
5 | * Author: Vitaly Wool <vwool@ru.mvista.com> | 5 | * Author: Vitaly Wool <vwool@ru.mvista.com> |
6 | * | 6 | * |
7 | * Revised to handle newer style flash binding by: | ||
8 | * Copyright (C) 2007 David Gibson, IBM Corporation. | ||
9 | * | ||
7 | * This program is free software; you can redistribute it and/or modify it | 10 | * This program is free software; you can redistribute it and/or modify it |
8 | * under the terms of the GNU General Public License as published by the | 11 | * under the terms of the GNU General Public License as published by the |
9 | * Free Software Foundation; either version 2 of the License, or (at your | 12 | * Free Software Foundation; either version 2 of the License, or (at your |
@@ -12,102 +15,157 @@ | |||
12 | 15 | ||
13 | #include <linux/module.h> | 16 | #include <linux/module.h> |
14 | #include <linux/types.h> | 17 | #include <linux/types.h> |
15 | #include <linux/kernel.h> | ||
16 | #include <linux/init.h> | 18 | #include <linux/init.h> |
17 | #include <linux/slab.h> | ||
18 | #include <linux/device.h> | 19 | #include <linux/device.h> |
19 | #include <linux/mtd/mtd.h> | 20 | #include <linux/mtd/mtd.h> |
20 | #include <linux/mtd/map.h> | 21 | #include <linux/mtd/map.h> |
21 | #include <linux/mtd/partitions.h> | 22 | #include <linux/mtd/partitions.h> |
22 | #include <linux/mtd/physmap.h> | 23 | #include <linux/of.h> |
23 | #include <asm/io.h> | 24 | #include <linux/of_platform.h> |
24 | #include <asm/prom.h> | ||
25 | #include <asm/of_device.h> | ||
26 | #include <asm/of_platform.h> | ||
27 | 25 | ||
28 | struct physmap_flash_info { | 26 | struct of_flash { |
29 | struct mtd_info *mtd; | 27 | struct mtd_info *mtd; |
30 | struct map_info map; | 28 | struct map_info map; |
31 | struct resource *res; | 29 | struct resource *res; |
32 | #ifdef CONFIG_MTD_PARTITIONS | 30 | #ifdef CONFIG_MTD_PARTITIONS |
33 | int nr_parts; | ||
34 | struct mtd_partition *parts; | 31 | struct mtd_partition *parts; |
35 | #endif | 32 | #endif |
36 | }; | 33 | }; |
37 | 34 | ||
38 | static const char *rom_probe_types[] = { "cfi_probe", "jedec_probe", "map_rom", NULL }; | ||
39 | #ifdef CONFIG_MTD_PARTITIONS | 35 | #ifdef CONFIG_MTD_PARTITIONS |
40 | static const char *part_probe_types[] = { "cmdlinepart", "RedBoot", NULL }; | 36 | #define OF_FLASH_PARTS(info) ((info)->parts) |
41 | #endif | ||
42 | 37 | ||
43 | #ifdef CONFIG_MTD_PARTITIONS | 38 | static int parse_obsolete_partitions(struct of_device *dev, |
44 | static int parse_flash_partitions(struct device_node *node, | 39 | struct of_flash *info, |
45 | struct mtd_partition **parts) | 40 | struct device_node *dp) |
46 | { | 41 | { |
47 | int i, plen, retval = -ENOMEM; | 42 | int i, plen, nr_parts; |
48 | const u32 *part; | 43 | const struct { |
49 | const char *name; | 44 | u32 offset, len; |
50 | 45 | } *part; | |
51 | part = of_get_property(node, "partitions", &plen); | 46 | const char *names; |
52 | if (part == NULL) | 47 | |
53 | goto err; | 48 | part = of_get_property(dp, "partitions", &plen); |
54 | 49 | if (!part) | |
55 | retval = plen / (2 * sizeof(u32)); | 50 | return 0; /* No partitions found */ |
56 | *parts = kzalloc(retval * sizeof(struct mtd_partition), GFP_KERNEL); | 51 | |
57 | if (*parts == NULL) { | 52 | dev_warn(&dev->dev, "Device tree uses obsolete partition map binding\n"); |
58 | printk(KERN_ERR "Can't allocate the flash partition data!\n"); | 53 | |
59 | goto err; | 54 | nr_parts = plen / sizeof(part[0]); |
60 | } | 55 | |
56 | info->parts = kzalloc(nr_parts * sizeof(*info->parts), GFP_KERNEL); | ||
57 | if (!info->parts) | ||
58 | return -ENOMEM; | ||
61 | 59 | ||
62 | name = of_get_property(node, "partition-names", &plen); | 60 | names = of_get_property(dp, "partition-names", &plen); |
63 | 61 | ||
64 | for (i = 0; i < retval; i++) { | 62 | for (i = 0; i < nr_parts; i++) { |
65 | (*parts)[i].offset = *part++; | 63 | info->parts[i].offset = part->offset; |
66 | (*parts)[i].size = *part & ~1; | 64 | info->parts[i].size = part->len & ~1; |
67 | if (*part++ & 1) /* bit 0 set signifies read only partition */ | 65 | if (part->len & 1) /* bit 0 set signifies read only partition */ |
68 | (*parts)[i].mask_flags = MTD_WRITEABLE; | 66 | info->parts[i].mask_flags = MTD_WRITEABLE; |
69 | 67 | ||
70 | if (name != NULL && plen > 0) { | 68 | if (names && (plen > 0)) { |
71 | int len = strlen(name) + 1; | 69 | int len = strlen(names) + 1; |
72 | 70 | ||
73 | (*parts)[i].name = (char *)name; | 71 | info->parts[i].name = (char *)names; |
74 | plen -= len; | 72 | plen -= len; |
75 | name += len; | 73 | names += len; |
76 | } else | 74 | } else { |
77 | (*parts)[i].name = "unnamed"; | 75 | info->parts[i].name = "unnamed"; |
76 | } | ||
77 | |||
78 | part++; | ||
78 | } | 79 | } |
79 | err: | 80 | |
80 | return retval; | 81 | return nr_parts; |
81 | } | 82 | } |
82 | #endif | ||
83 | 83 | ||
84 | static int of_physmap_remove(struct of_device *dev) | 84 | static int __devinit parse_partitions(struct of_flash *info, |
85 | struct of_device *dev) | ||
86 | { | ||
87 | const char *partname; | ||
88 | static const char *part_probe_types[] | ||
89 | = { "cmdlinepart", "RedBoot", NULL }; | ||
90 | struct device_node *dp = dev->node, *pp; | ||
91 | int nr_parts, i; | ||
92 | |||
93 | /* First look for RedBoot table or partitions on the command | ||
94 | * line, these take precedence over device tree information */ | ||
95 | nr_parts = parse_mtd_partitions(info->mtd, part_probe_types, | ||
96 | &info->parts, 0); | ||
97 | if (nr_parts > 0) { | ||
98 | add_mtd_partitions(info->mtd, info->parts, nr_parts); | ||
99 | return 0; | ||
100 | } | ||
101 | |||
102 | /* First count the subnodes */ | ||
103 | nr_parts = 0; | ||
104 | for (pp = dp->child; pp; pp = pp->sibling) | ||
105 | nr_parts++; | ||
106 | |||
107 | if (nr_parts == 0) | ||
108 | return parse_obsolete_partitions(dev, info, dp); | ||
109 | |||
110 | info->parts = kzalloc(nr_parts * sizeof(*info->parts), | ||
111 | GFP_KERNEL); | ||
112 | if (!info->parts) | ||
113 | return -ENOMEM; | ||
114 | |||
115 | for (pp = dp->child, i = 0; pp; pp = pp->sibling, i++) { | ||
116 | const u32 *reg; | ||
117 | int len; | ||
118 | |||
119 | reg = of_get_property(pp, "reg", &len); | ||
120 | if (!reg || (len != 2*sizeof(u32))) { | ||
121 | dev_err(&dev->dev, "Invalid 'reg' on %s\n", | ||
122 | dp->full_name); | ||
123 | kfree(info->parts); | ||
124 | info->parts = NULL; | ||
125 | return -EINVAL; | ||
126 | } | ||
127 | info->parts[i].offset = reg[0]; | ||
128 | info->parts[i].size = reg[1]; | ||
129 | |||
130 | partname = of_get_property(pp, "label", &len); | ||
131 | if (!partname) | ||
132 | partname = of_get_property(pp, "name", &len); | ||
133 | info->parts[i].name = (char *)partname; | ||
134 | |||
135 | if (of_get_property(pp, "read-only", &len)) | ||
136 | info->parts[i].mask_flags = MTD_WRITEABLE; | ||
137 | } | ||
138 | |||
139 | return nr_parts; | ||
140 | } | ||
141 | #else /* MTD_PARTITIONS */ | ||
142 | #define OF_FLASH_PARTS(info) (0) | ||
143 | #define parse_partitions(info, dev) (0) | ||
144 | #endif /* MTD_PARTITIONS */ | ||
145 | |||
146 | static int of_flash_remove(struct of_device *dev) | ||
85 | { | 147 | { |
86 | struct physmap_flash_info *info; | 148 | struct of_flash *info; |
87 | 149 | ||
88 | info = dev_get_drvdata(&dev->dev); | 150 | info = dev_get_drvdata(&dev->dev); |
89 | if (info == NULL) | 151 | if (!info) |
90 | return 0; | 152 | return 0; |
91 | dev_set_drvdata(&dev->dev, NULL); | 153 | dev_set_drvdata(&dev->dev, NULL); |
92 | 154 | ||
93 | if (info->mtd != NULL) { | 155 | if (info->mtd) { |
94 | #ifdef CONFIG_MTD_PARTITIONS | 156 | if (OF_FLASH_PARTS(info)) { |
95 | if (info->nr_parts) { | ||
96 | del_mtd_partitions(info->mtd); | 157 | del_mtd_partitions(info->mtd); |
97 | kfree(info->parts); | 158 | kfree(OF_FLASH_PARTS(info)); |
98 | } else { | 159 | } else { |
99 | del_mtd_device(info->mtd); | 160 | del_mtd_device(info->mtd); |
100 | } | 161 | } |
101 | #else | ||
102 | del_mtd_device(info->mtd); | ||
103 | #endif | ||
104 | map_destroy(info->mtd); | 162 | map_destroy(info->mtd); |
105 | } | 163 | } |
106 | 164 | ||
107 | if (info->map.virt != NULL) | 165 | if (info->map.virt) |
108 | iounmap(info->map.virt); | 166 | iounmap(info->map.virt); |
109 | 167 | ||
110 | if (info->res != NULL) { | 168 | if (info->res) { |
111 | release_resource(info->res); | 169 | release_resource(info->res); |
112 | kfree(info->res); | 170 | kfree(info->res); |
113 | } | 171 | } |
@@ -115,48 +173,79 @@ static int of_physmap_remove(struct of_device *dev) | |||
115 | return 0; | 173 | return 0; |
116 | } | 174 | } |
117 | 175 | ||
118 | static int __devinit of_physmap_probe(struct of_device *dev, const struct of_device_id *match) | 176 | /* Helper function to handle probing of the obsolete "direct-mapped" |
177 | * compatible binding, which has an extra "probe-type" property | ||
178 | * describing the type of flash probe necessary. */ | ||
179 | static struct mtd_info * __devinit obsolete_probe(struct of_device *dev, | ||
180 | struct map_info *map) | ||
119 | { | 181 | { |
120 | struct device_node *dp = dev->node; | 182 | struct device_node *dp = dev->node; |
121 | struct resource res; | ||
122 | struct physmap_flash_info *info; | ||
123 | const char **probe_type; | ||
124 | const char *of_probe; | 183 | const char *of_probe; |
184 | struct mtd_info *mtd; | ||
185 | static const char *rom_probe_types[] | ||
186 | = { "cfi_probe", "jedec_probe", "map_rom"}; | ||
187 | int i; | ||
188 | |||
189 | dev_warn(&dev->dev, "Device tree uses obsolete \"direct-mapped\" " | ||
190 | "flash binding\n"); | ||
191 | |||
192 | of_probe = of_get_property(dp, "probe-type", NULL); | ||
193 | if (!of_probe) { | ||
194 | for (i = 0; i < ARRAY_SIZE(rom_probe_types); i++) { | ||
195 | mtd = do_map_probe(rom_probe_types[i], map); | ||
196 | if (mtd) | ||
197 | return mtd; | ||
198 | } | ||
199 | return NULL; | ||
200 | } else if (strcmp(of_probe, "CFI") == 0) { | ||
201 | return do_map_probe("cfi_probe", map); | ||
202 | } else if (strcmp(of_probe, "JEDEC") == 0) { | ||
203 | return do_map_probe("jedec_probe", map); | ||
204 | } else { | ||
205 | if (strcmp(of_probe, "ROM") != 0) | ||
206 | dev_warn(&dev->dev, "obsolete_probe: don't know probe " | ||
207 | "type '%s', mapping as rom\n", of_probe); | ||
208 | return do_map_probe("mtd_rom", map); | ||
209 | } | ||
210 | } | ||
211 | |||
212 | static int __devinit of_flash_probe(struct of_device *dev, | ||
213 | const struct of_device_id *match) | ||
214 | { | ||
215 | struct device_node *dp = dev->node; | ||
216 | struct resource res; | ||
217 | struct of_flash *info; | ||
218 | const char *probe_type = match->data; | ||
125 | const u32 *width; | 219 | const u32 *width; |
126 | int err; | 220 | int err; |
127 | 221 | ||
128 | 222 | err = -ENXIO; | |
129 | if (of_address_to_resource(dp, 0, &res)) { | 223 | if (of_address_to_resource(dp, 0, &res)) { |
130 | dev_err(&dev->dev, "Can't get the flash mapping!\n"); | 224 | dev_err(&dev->dev, "Can't get IO address from device tree\n"); |
131 | err = -EINVAL; | ||
132 | goto err_out; | 225 | goto err_out; |
133 | } | 226 | } |
134 | 227 | ||
135 | dev_dbg(&dev->dev, "physmap flash device: %.8llx at %.8llx\n", | 228 | dev_dbg(&dev->dev, "of_flash device: %.8llx-%.8llx\n", |
136 | (unsigned long long)res.end - res.start + 1, | 229 | (unsigned long long)res.start, (unsigned long long)res.end); |
137 | (unsigned long long)res.start); | ||
138 | 230 | ||
139 | info = kzalloc(sizeof(struct physmap_flash_info), GFP_KERNEL); | 231 | err = -ENOMEM; |
140 | if (info == NULL) { | 232 | info = kzalloc(sizeof(*info), GFP_KERNEL); |
141 | err = -ENOMEM; | 233 | if (!info) |
142 | goto err_out; | 234 | goto err_out; |
143 | } | ||
144 | memset(info, 0, sizeof(*info)); | 235 | memset(info, 0, sizeof(*info)); |
145 | 236 | ||
146 | dev_set_drvdata(&dev->dev, info); | 237 | dev_set_drvdata(&dev->dev, info); |
147 | 238 | ||
239 | err = -EBUSY; | ||
148 | info->res = request_mem_region(res.start, res.end - res.start + 1, | 240 | info->res = request_mem_region(res.start, res.end - res.start + 1, |
149 | dev->dev.bus_id); | 241 | dev->dev.bus_id); |
150 | if (info->res == NULL) { | 242 | if (!info->res) |
151 | dev_err(&dev->dev, "Could not reserve memory region\n"); | ||
152 | err = -ENOMEM; | ||
153 | goto err_out; | 243 | goto err_out; |
154 | } | ||
155 | 244 | ||
245 | err = -ENXIO; | ||
156 | width = of_get_property(dp, "bank-width", NULL); | 246 | width = of_get_property(dp, "bank-width", NULL); |
157 | if (width == NULL) { | 247 | if (!width) { |
158 | dev_err(&dev->dev, "Can't get the flash bank width!\n"); | 248 | dev_err(&dev->dev, "Can't get bank width from device tree\n"); |
159 | err = -EINVAL; | ||
160 | goto err_out; | 249 | goto err_out; |
161 | } | 250 | } |
162 | 251 | ||
@@ -165,91 +254,87 @@ static int __devinit of_physmap_probe(struct of_device *dev, const struct of_dev | |||
165 | info->map.size = res.end - res.start + 1; | 254 | info->map.size = res.end - res.start + 1; |
166 | info->map.bankwidth = *width; | 255 | info->map.bankwidth = *width; |
167 | 256 | ||
257 | err = -ENOMEM; | ||
168 | info->map.virt = ioremap(info->map.phys, info->map.size); | 258 | info->map.virt = ioremap(info->map.phys, info->map.size); |
169 | if (info->map.virt == NULL) { | 259 | if (!info->map.virt) { |
170 | dev_err(&dev->dev, "Failed to ioremap flash region\n"); | 260 | dev_err(&dev->dev, "Failed to ioremap() flash region\n"); |
171 | err = EIO; | ||
172 | goto err_out; | 261 | goto err_out; |
173 | } | 262 | } |
174 | 263 | ||
175 | simple_map_init(&info->map); | 264 | simple_map_init(&info->map); |
176 | 265 | ||
177 | of_probe = of_get_property(dp, "probe-type", NULL); | 266 | if (probe_type) |
178 | if (of_probe == NULL) { | 267 | info->mtd = do_map_probe(probe_type, &info->map); |
179 | probe_type = rom_probe_types; | 268 | else |
180 | for (; info->mtd == NULL && *probe_type != NULL; probe_type++) | 269 | info->mtd = obsolete_probe(dev, &info->map); |
181 | info->mtd = do_map_probe(*probe_type, &info->map); | 270 | |
182 | } else if (!strcmp(of_probe, "CFI")) | 271 | err = -ENXIO; |
183 | info->mtd = do_map_probe("cfi_probe", &info->map); | 272 | if (!info->mtd) { |
184 | else if (!strcmp(of_probe, "JEDEC")) | 273 | dev_err(&dev->dev, "do_map_probe() failed\n"); |
185 | info->mtd = do_map_probe("jedec_probe", &info->map); | ||
186 | else { | ||
187 | if (strcmp(of_probe, "ROM")) | ||
188 | dev_dbg(&dev->dev, "map_probe: don't know probe type " | ||
189 | "'%s', mapping as rom\n", of_probe); | ||
190 | info->mtd = do_map_probe("mtd_rom", &info->map); | ||
191 | } | ||
192 | if (info->mtd == NULL) { | ||
193 | dev_err(&dev->dev, "map_probe failed\n"); | ||
194 | err = -ENXIO; | ||
195 | goto err_out; | 274 | goto err_out; |
196 | } | 275 | } |
197 | info->mtd->owner = THIS_MODULE; | 276 | info->mtd->owner = THIS_MODULE; |
198 | 277 | ||
199 | #ifdef CONFIG_MTD_PARTITIONS | 278 | err = parse_partitions(info, dev); |
200 | err = parse_mtd_partitions(info->mtd, part_probe_types, &info->parts, 0); | 279 | if (err < 0) |
201 | if (err > 0) { | 280 | goto err_out; |
202 | add_mtd_partitions(info->mtd, info->parts, err); | 281 | |
203 | } else if ((err = parse_flash_partitions(dp, &info->parts)) > 0) { | 282 | if (err > 0) |
204 | dev_info(&dev->dev, "Using OF partition information\n"); | 283 | add_mtd_partitions(info->mtd, OF_FLASH_PARTS(info), err); |
205 | add_mtd_partitions(info->mtd, info->parts, err); | 284 | else |
206 | info->nr_parts = err; | 285 | add_mtd_device(info->mtd); |
207 | } else | ||
208 | #endif | ||
209 | 286 | ||
210 | add_mtd_device(info->mtd); | ||
211 | return 0; | 287 | return 0; |
212 | 288 | ||
213 | err_out: | 289 | err_out: |
214 | of_physmap_remove(dev); | 290 | of_flash_remove(dev); |
215 | return err; | 291 | return err; |
216 | |||
217 | return 0; | ||
218 | |||
219 | |||
220 | } | 292 | } |
221 | 293 | ||
222 | static struct of_device_id of_physmap_match[] = { | 294 | static struct of_device_id of_flash_match[] = { |
295 | { | ||
296 | .compatible = "cfi-flash", | ||
297 | .data = (void *)"cfi_probe", | ||
298 | }, | ||
299 | { | ||
300 | /* FIXME: JEDEC chips can't be safely and reliably | ||
301 | * probed, although the mtd code gets it right in | ||
302 | * practice most of the time. We should use the | ||
303 | * vendor and device ids specified by the binding to | ||
304 | * bypass the heuristic probe code, but the mtd layer | ||
305 | * provides, at present, no interface for doing so | ||
306 | * :(. */ | ||
307 | .compatible = "jedec-flash", | ||
308 | .data = (void *)"jedec_probe", | ||
309 | }, | ||
223 | { | 310 | { |
224 | .type = "rom", | 311 | .type = "rom", |
225 | .compatible = "direct-mapped" | 312 | .compatible = "direct-mapped" |
226 | }, | 313 | }, |
227 | { }, | 314 | { }, |
228 | }; | 315 | }; |
316 | MODULE_DEVICE_TABLE(of, of_flash_match); | ||
229 | 317 | ||
230 | MODULE_DEVICE_TABLE(of, of_physmap_match); | 318 | static struct of_platform_driver of_flash_driver = { |
231 | 319 | .name = "of-flash", | |
232 | 320 | .match_table = of_flash_match, | |
233 | static struct of_platform_driver of_physmap_flash_driver = { | 321 | .probe = of_flash_probe, |
234 | .name = "physmap-flash", | 322 | .remove = of_flash_remove, |
235 | .match_table = of_physmap_match, | ||
236 | .probe = of_physmap_probe, | ||
237 | .remove = of_physmap_remove, | ||
238 | }; | 323 | }; |
239 | 324 | ||
240 | static int __init of_physmap_init(void) | 325 | static int __init of_flash_init(void) |
241 | { | 326 | { |
242 | return of_register_platform_driver(&of_physmap_flash_driver); | 327 | return of_register_platform_driver(&of_flash_driver); |
243 | } | 328 | } |
244 | 329 | ||
245 | static void __exit of_physmap_exit(void) | 330 | static void __exit of_flash_exit(void) |
246 | { | 331 | { |
247 | of_unregister_platform_driver(&of_physmap_flash_driver); | 332 | of_unregister_platform_driver(&of_flash_driver); |
248 | } | 333 | } |
249 | 334 | ||
250 | module_init(of_physmap_init); | 335 | module_init(of_flash_init); |
251 | module_exit(of_physmap_exit); | 336 | module_exit(of_flash_exit); |
252 | 337 | ||
253 | MODULE_LICENSE("GPL"); | 338 | MODULE_LICENSE("GPL"); |
254 | MODULE_AUTHOR("Vitaly Wool <vwool@ru.mvista.com>"); | 339 | MODULE_AUTHOR("Vitaly Wool <vwool@ru.mvista.com>"); |
255 | MODULE_DESCRIPTION("Configurable MTD map driver for OF"); | 340 | MODULE_DESCRIPTION("Device tree based MTD map driver"); |