brcm47xx: fix partition layout on SimpleShare devices.
[openwrt.git] / target / linux / brcm47xx / files / drivers / mtd / maps / bcm47xx-flash.c
1 /*
2  *  Copyright (C) 2006 Felix Fietkau <nbd@openwrt.org>
3  *  Copyright (C) 2005 Waldemar Brodkorb <wbx@openwrt.org>
4  *  Copyright (C) 2004 Florian Schirmer (jolt@tuxbox.org)
5  *
6  *  original functions for finding root filesystem from Mike Baker 
7  *
8  *  This program is free software; you can redistribute  it and/or modify it
9  *  under  the terms of  the GNU General  Public License as published by the
10  *  Free Software Foundation;  either version 2 of the  License, or (at your
11  *  option) any later version.
12  *
13  *  THIS  SOFTWARE  IS PROVIDED   ``AS  IS'' AND   ANY  EXPRESS OR IMPLIED
14  *  WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
15  *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
16  *  NO  EVENT  SHALL   THE AUTHOR  BE    LIABLE FOR ANY   DIRECT, INDIRECT,
17  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18  *  NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF
19  *  USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20  *  ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT
21  *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22  *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  *
24  *  You should have received a copy of the  GNU General Public License along
25  *  with this program; if not, write  to the Free Software Foundation, Inc.,
26  *  675 Mass Ave, Cambridge, MA 02139, USA.
27  * 
28  *  Copyright 2001-2003, Broadcom Corporation
29  *  All Rights Reserved.
30  * 
31  *  THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY
32  *  KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM
33  *  SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
34  *  FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE.
35  *
36  *  Flash mapping for BCM947XX boards
37  */
38
39 #include <linux/init.h>
40 #include <linux/module.h>
41 #include <linux/types.h>
42 #include <linux/kernel.h>
43 #include <linux/sched.h>
44 #include <linux/wait.h>
45 #include <linux/mtd/mtd.h>
46 #include <linux/mtd/map.h>
47 #ifdef CONFIG_MTD_PARTITIONS
48 #include <linux/mtd/partitions.h>
49 #endif
50 #include <linux/crc32.h>
51 #ifdef CONFIG_SSB
52 #include <linux/ssb/ssb.h>
53 #endif
54 #include <asm/io.h>
55 #include <asm/mach-bcm47xx/nvram.h>
56 #include <asm/fw/cfe/cfe_api.h>
57
58
59 #define TRX_MAGIC       0x30524448      /* "HDR0" */
60 #define TRX_VERSION     1
61 #define TRX_MAX_LEN     0x3A0000
62 #define TRX_NO_HEADER   1               /* Do not write TRX header */   
63 #define TRX_GZ_FILES    0x2     /* Contains up to TRX_MAX_OFFSET individual gzip files */
64 #define TRX_MAX_OFFSET  3
65
66 struct trx_header {
67         u32 magic;              /* "HDR0" */
68         u32 len;                /* Length of file including header */
69         u32 crc32;              /* 32-bit CRC from flag_version to end of file */
70         u32 flag_version;       /* 0:15 flags, 16:31 version */
71         u32 offsets[TRX_MAX_OFFSET];    /* Offsets of partitions from start of header */
72 };
73
74 /* for Edimax Print servers which use an additional header
75  * then the firmware on flash looks like :
76  * EDIMAX HEADER | TRX HEADER
77  * As this header is 12 bytes long we have to handle it
78  * and skip it to find the TRX header
79  */
80 #define EDIMAX_PS_HEADER_MAGIC  0x36315350 /*  "PS16"  */
81 #define EDIMAX_PS_HEADER_LEN    0xc /* 12 bytes long for edimax header */
82
83 #define ROUNDUP(x, y) ((((x)+((y)-1))/(y))*(y))
84 #define NVRAM_SPACE 0x8000
85 #define WINDOW_ADDR 0x1fc00000
86 #define WINDOW_SIZE 0x400000
87 #define BUSWIDTH 2
88
89 #define ROUTER_NETGEAR_WGR614L         1
90 #define ROUTER_NETGEAR_WNR834B         2
91 #define ROUTER_NETGEAR_WNDR3300        3
92 #define ROUTER_NETGEAR_WNR3500L        4
93 #define ROUTER_SIMPLETECH_SIMPLESHARE  5
94
95 #ifdef CONFIG_SSB
96 extern struct ssb_bus ssb_bcm47xx;
97 #endif
98 static struct mtd_info *bcm47xx_mtd;
99
100 static void bcm47xx_map_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len)
101 {
102         if (len==1) {
103                 memcpy_fromio(to, map->virt + from, len);
104         } else {
105                 int i;
106                 u16 *dest = (u16 *) to;
107                 u16 *src  = (u16 *) (map->virt + from);
108                 for (i = 0; i < (len / 2); i++) {
109                         dest[i] = src[i];
110                 }
111                 if (len & 1)
112                         *((u8 *)dest+len-1) = src[i] & 0xff;
113         }
114 }
115
116 static struct map_info bcm47xx_map = {
117         name: "Physically mapped flash",
118         size: WINDOW_SIZE,
119         bankwidth: BUSWIDTH,
120         phys: WINDOW_ADDR,
121 };
122
123 #ifdef CONFIG_MTD_PARTITIONS
124
125 static struct mtd_partition bcm47xx_parts[] = {
126         { name: "cfe",  offset: 0, size: 0, mask_flags: MTD_WRITEABLE, },
127         { name: "linux", offset: 0, size: 0, },
128         { name: "rootfs", offset: 0, size: 0, },
129         { name: "nvram", offset: 0, size: 0, },
130         { name: NULL, }, /* Used to create custom partitons with the function get_router() */
131         { name: NULL, },
132 };
133
134 static int __init
135 find_cfe_size(struct mtd_info *mtd, size_t size)
136 {
137         struct trx_header *trx;
138         unsigned char buf[512];
139         int off;
140         size_t len;
141         int blocksize;
142
143         trx = (struct trx_header *) buf;
144
145         blocksize = mtd->erasesize;
146         if (blocksize < 0x10000)
147                 blocksize = 0x10000;
148
149         for (off = (128*1024); off < size; off += blocksize) {
150                 memset(buf, 0xe5, sizeof(buf));
151
152                 /*
153                  * Read into buffer 
154                  */
155                 if (mtd->read(mtd, off, sizeof(buf), &len, buf) ||
156                     len != sizeof(buf))
157                         continue;
158
159                 if (le32_to_cpu(trx->magic) == EDIMAX_PS_HEADER_MAGIC) {
160                         if (mtd->read(mtd, off + EDIMAX_PS_HEADER_LEN,
161                             sizeof(buf), &len, buf) || len != sizeof(buf)) {
162                                 continue;
163                         } else {
164                                 printk(KERN_NOTICE"Found edimax header\n");
165                         }
166                 }
167
168                 /* found a TRX header */
169                 if (le32_to_cpu(trx->magic) == TRX_MAGIC) {
170                         goto found;
171                 }
172         }
173
174         printk(KERN_NOTICE
175                "%s: Couldn't find bootloader size\n",
176                mtd->name);
177         return -1;
178
179  found:
180         printk(KERN_NOTICE "bootloader size: %d\n", off);
181         return off;
182
183 }
184
185 /*
186  * Copied from mtdblock.c
187  *
188  * Cache stuff...
189  * 
190  * Since typical flash erasable sectors are much larger than what Linux's
191  * buffer cache can handle, we must implement read-modify-write on flash
192  * sectors for each block write requests.  To avoid over-erasing flash sectors
193  * and to speed things up, we locally cache a whole flash sector while it is
194  * being written to until a different sector is required.
195  */
196
197 static void erase_callback(struct erase_info *done)
198 {
199         wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv;
200         wake_up(wait_q);
201 }
202
203 static int erase_write (struct mtd_info *mtd, unsigned long pos, 
204                         int len, const char *buf)
205 {
206         struct erase_info erase;
207         DECLARE_WAITQUEUE(wait, current);
208         wait_queue_head_t wait_q;
209         size_t retlen;
210         int ret;
211
212         /*
213          * First, let's erase the flash block.
214          */
215
216         init_waitqueue_head(&wait_q);
217         erase.mtd = mtd;
218         erase.callback = erase_callback;
219         erase.addr = pos;
220         erase.len = len;
221         erase.priv = (u_long)&wait_q;
222
223         set_current_state(TASK_INTERRUPTIBLE);
224         add_wait_queue(&wait_q, &wait);
225
226         ret = mtd->erase(mtd, &erase);
227         if (ret) {
228                 set_current_state(TASK_RUNNING);
229                 remove_wait_queue(&wait_q, &wait);
230                 printk (KERN_WARNING "erase of region [0x%lx, 0x%x] "
231                                      "on \"%s\" failed\n",
232                         pos, len, mtd->name);
233                 return ret;
234         }
235
236         schedule();  /* Wait for erase to finish. */
237         remove_wait_queue(&wait_q, &wait);
238
239         /*
240          * Next, write data to flash.
241          */
242
243         ret = mtd->write (mtd, pos, len, &retlen, buf);
244         if (ret)
245                 return ret;
246         if (retlen != len)
247                 return -EIO;
248         return 0;
249 }
250
251
252 static int __init
253 find_dual_image_off (struct mtd_info *mtd, size_t size)
254 {
255         struct trx_header trx;
256         int off, blocksize;
257         size_t len;
258
259         blocksize = mtd->erasesize;
260         if (blocksize < 0x10000)
261                 blocksize = 0x10000;
262
263         for (off = (128*1024); off < size; off += blocksize) {
264                 memset(&trx, 0xe5, sizeof(trx));
265                 /*
266                 * Read into buffer 
267                 */
268                 if (mtd->read(mtd, off, sizeof(trx), &len, (char *) &trx) ||
269                     len != sizeof(trx))
270                         continue;
271                 /* found last TRX header */
272                 if (le32_to_cpu(trx.magic) == TRX_MAGIC){ 
273                         if (le32_to_cpu(trx.flag_version >> 16)==2){
274                                 printk("dual image TRX header found\n");
275                                 return size/2;
276                         } else {
277                                 return 0;
278                         }
279                 }
280         }
281         return 0;
282 }
283
284
285 static int __init
286 find_root(struct mtd_info *mtd, size_t size, struct mtd_partition *part)
287 {
288         struct trx_header trx, *trx2;
289         unsigned char buf[512], *block;
290         int off, blocksize, trxoff = 0;
291         u32 i, crc = ~0;
292         size_t len;
293         bool edimax = false;
294
295         blocksize = mtd->erasesize;
296         if (blocksize < 0x10000)
297                 blocksize = 0x10000;
298
299         for (off = (128*1024); off < size; off += blocksize) {
300                 memset(&trx, 0xe5, sizeof(trx));
301
302                 /*
303                  * Read into buffer 
304                  */
305                 if (mtd->read(mtd, off, sizeof(trx), &len, (char *) &trx) ||
306                     len != sizeof(trx))
307                         continue;
308
309                 /* found an edimax header */
310                 if (le32_to_cpu(trx.magic) == EDIMAX_PS_HEADER_MAGIC) {
311                         /* read the correct trx header */
312                         if (mtd->read(mtd, off + EDIMAX_PS_HEADER_LEN,
313                             sizeof(trx), &len, (char *) &trx) ||
314                             len != sizeof(trx)) {
315                                 continue;
316                         } else {
317                                 printk(KERN_NOTICE"Found an edimax ps header\n");
318                                 edimax = true;
319                         }
320                 }
321
322                 /* found a TRX header */
323                 if (le32_to_cpu(trx.magic) == TRX_MAGIC) {
324                         part->offset = le32_to_cpu(trx.offsets[2]) ? : 
325                                 le32_to_cpu(trx.offsets[1]);
326                         part->size = le32_to_cpu(trx.len); 
327
328                         part->size -= part->offset;
329                         part->offset += off;
330                         if (edimax) {
331                                 off += EDIMAX_PS_HEADER_LEN;
332                                 trxoff = EDIMAX_PS_HEADER_LEN;
333                         }
334
335                         goto found;
336                 }
337         }
338
339         printk(KERN_NOTICE
340                "%s: Couldn't find root filesystem\n",
341                mtd->name);
342         return -1;
343
344  found:
345         printk(KERN_NOTICE"TRX offset : %x\n", trxoff);
346         if (part->size == 0)
347                 return 0;
348         
349         if (mtd->read(mtd, part->offset, sizeof(buf), &len, buf) || len != sizeof(buf))
350                 return 0;
351
352         /* Move the fs outside of the trx */
353         part->size = 0;
354
355         if (trx.len != part->offset + part->size - off) {
356                 /* Update the trx offsets and length */
357                 trx.len = part->offset + part->size - off;
358         
359                 /* Update the trx crc32 */
360                 for (i = (u32) &(((struct trx_header *)NULL)->flag_version); i <= trx.len; i += sizeof(buf)) {
361                         if (mtd->read(mtd, off + i, sizeof(buf), &len, buf) || len != sizeof(buf))
362                                 return 0;
363                         crc = crc32_le(crc, buf, min(sizeof(buf), trx.len - i));
364                 }
365                 trx.crc32 = crc;
366
367                 /* read first eraseblock from the trx */
368                 block = kmalloc(mtd->erasesize, GFP_KERNEL);
369                 trx2 = (struct trx_header *) block;
370                 if (mtd->read(mtd, off - trxoff, mtd->erasesize, &len, block) || len != mtd->erasesize) {
371                         printk("Error accessing the first trx eraseblock\n");
372                         return 0;
373                 }
374                 
375                 printk("Updating TRX offsets and length:\n");
376                 printk("old trx = [0x%08x, 0x%08x, 0x%08x], len=0x%08x crc32=0x%08x\n", trx2->offsets[0], trx2->offsets[1], trx2->offsets[2], trx2->len, trx2->crc32);
377                 printk("new trx = [0x%08x, 0x%08x, 0x%08x], len=0x%08x crc32=0x%08x\n",   trx.offsets[0],   trx.offsets[1],   trx.offsets[2],   trx.len, trx.crc32);
378
379                 /* Write updated trx header to the flash */
380                 memcpy(block + trxoff, &trx, sizeof(trx));
381                 if (mtd->unlock)
382                         mtd->unlock(mtd, off - trxoff, mtd->erasesize);
383                 erase_write(mtd, off - trxoff, mtd->erasesize, block);
384                 if (mtd->sync)
385                         mtd->sync(mtd);
386                 kfree(block);
387                 printk("Done\n");
388         }
389         
390         return part->size;
391 }
392
393 static int get_router(void)
394 {
395         char buf[20];
396         u32 boardnum = 0;
397         u16 boardtype = 0;
398         u16 boardrev = 0;
399         u32 boardflags = 0;
400         u16 sdram_init = 0;
401         u16 cardbus = 0;
402         u16 strev = 0;
403
404         if (nvram_getenv("boardnum", buf, sizeof(buf)) >= 0)
405                 boardnum = simple_strtoul(buf, NULL, 0);
406         if (nvram_getenv("boardtype", buf, sizeof(buf)) >= 0)
407                 boardtype = simple_strtoul(buf, NULL, 0);
408         if (nvram_getenv("boardrev", buf, sizeof(buf)) >= 0)
409                 boardrev = simple_strtoul(buf, NULL, 0);
410         if (nvram_getenv("boardflags", buf, sizeof(buf)) >= 0)
411                 boardflags = simple_strtoul(buf, NULL, 0);
412         if (nvram_getenv("sdram_init", buf, sizeof(buf)) >= 0)
413                 sdram_init = simple_strtoul(buf, NULL, 0);
414         if (nvram_getenv("cardbus", buf, sizeof(buf)) >= 0)
415                 cardbus = simple_strtoul(buf, NULL, 0);
416         if (nvram_getenv("st_rev", buf, sizeof(buf)) >= 0)
417                 strev = simple_strtoul(buf, NULL, 0);
418
419         if ((boardnum == 8 || boardnum == 01)
420           && boardtype == 0x0472 && cardbus == 1) {
421                 /* Netgear WNR834B, Netgear WNR834Bv2 */
422                 return ROUTER_NETGEAR_WNR834B;
423         }
424
425         if (boardnum == 01 && boardtype == 0x0472 && boardrev == 0x23) {
426                 /* Netgear WNDR-3300 */
427                 return ROUTER_NETGEAR_WNDR3300;
428         }
429
430         if ((boardnum == 83258 || boardnum == 01)
431           && boardtype == 0x048e
432           && (boardrev == 0x11 || boardrev == 0x10)
433           && boardflags == 0x750
434           && sdram_init == 0x000A) {
435                 /* Netgear WGR614v8/L/WW 16MB ram, cfe v1.3 or v1.5 */
436                 return ROUTER_NETGEAR_WGR614L;
437         }
438
439         if ((boardnum == 1 || boardnum == 3500)
440           && boardtype == 0x04CF
441           && (boardrev == 0x1213 || boardrev == 02)) {
442                 /* Netgear WNR3500v2/U/L */
443                 return ROUTER_NETGEAR_WNR3500L;
444         }
445
446         if (boardtype == 0x042f
447           && boardrev == 0x10
448           && boardflags == 0 
449           && strev == 0x11) { 
450                 /* Simpletech Simpleshare */
451                 return ROUTER_SIMPLETECH_SIMPLESHARE;
452         }
453
454         return 0;
455 }
456
457 struct mtd_partition * __init
458 init_mtd_partitions(struct mtd_info *mtd, size_t size)
459 {
460         int cfe_size;
461         int dual_image_offset = 0;
462         /* e.g Netgear 0x003e0000-0x003f0000 : "board_data", we exclude this
463          * part from our mapping to prevent overwriting len/checksum on e.g.
464          * Netgear WGR614v8/L/WW
465          */
466         int custom_data_size = 0;
467
468         if ((cfe_size = find_cfe_size(mtd,size)) < 0)
469                 return NULL;
470
471         /* boot loader */
472         bcm47xx_parts[0].offset = 0;
473         bcm47xx_parts[0].size   = cfe_size;
474
475         /* nvram */
476         if (cfe_size != 384 * 1024) {
477
478                 switch (get_router()) {
479                 case ROUTER_NETGEAR_WGR614L:
480                 case ROUTER_NETGEAR_WNR834B:
481                 case ROUTER_NETGEAR_WNDR3300:
482                 case ROUTER_NETGEAR_WNR3500L:
483                         /* Netgear: checksum is @ 0x003AFFF8 for 4M flash or checksum
484                          * is @ 0x007AFFF8 for 8M flash
485                          */
486                         custom_data_size = mtd->erasesize;
487
488                         bcm47xx_parts[3].offset = size - ROUNDUP(NVRAM_SPACE, mtd->erasesize);
489                         bcm47xx_parts[3].size   = ROUNDUP(NVRAM_SPACE, mtd->erasesize);
490
491                         /* Place CFE board_data into a partition */
492                         bcm47xx_parts[4].name = "board_data";
493                         bcm47xx_parts[4].offset = bcm47xx_parts[3].offset - custom_data_size;
494                         bcm47xx_parts[4].size   =  custom_data_size;
495                         break;
496
497                 case ROUTER_SIMPLETECH_SIMPLESHARE:
498                         /* Fixup Simpletech Simple share nvram  */
499
500                         printk("Setting up simpletech nvram\n");
501                         custom_data_size = mtd->erasesize;
502
503                         bcm47xx_parts[3].offset = size - ROUNDUP(NVRAM_SPACE, mtd->erasesize) * 2;
504                         bcm47xx_parts[3].size   = ROUNDUP(NVRAM_SPACE, mtd->erasesize);
505
506                         /* Place backup nvram into a partition */
507                         bcm47xx_parts[4].name = "nvram_copy";
508                         bcm47xx_parts[4].offset = size - ROUNDUP(NVRAM_SPACE, mtd->erasesize);
509                         bcm47xx_parts[4].size   = ROUNDUP(NVRAM_SPACE, mtd->erasesize);
510                         break;
511
512                 default:
513                         bcm47xx_parts[3].offset = size - ROUNDUP(NVRAM_SPACE, mtd->erasesize);
514                         bcm47xx_parts[3].size   = ROUNDUP(NVRAM_SPACE, mtd->erasesize);
515                 }
516
517         } else {
518                 /* nvram (old 128kb config partition on netgear wgt634u) */
519                 bcm47xx_parts[3].offset = bcm47xx_parts[0].size;
520                 bcm47xx_parts[3].size   = ROUNDUP(NVRAM_SPACE, mtd->erasesize);
521         }
522
523         /* dual image offset*/
524         printk("Looking for dual image\n");
525         dual_image_offset=find_dual_image_off(mtd,size);
526         /* linux (kernel and rootfs) */
527         if (cfe_size != 384 * 1024) {
528                 if (get_router() == ROUTER_SIMPLETECH_SIMPLESHARE) {
529                         bcm47xx_parts[1].offset = bcm47xx_parts[0].size;
530                         bcm47xx_parts[1].size   = bcm47xx_parts[4].offset - dual_image_offset -
531                                 bcm47xx_parts[1].offset - custom_data_size;
532                 } else {
533                         bcm47xx_parts[1].offset = bcm47xx_parts[0].size;
534                         bcm47xx_parts[1].size   = bcm47xx_parts[3].offset - dual_image_offset -
535                                 bcm47xx_parts[1].offset - custom_data_size;
536                 }
537         } else {
538                 /* do not count the elf loader, which is on one block */
539                 bcm47xx_parts[1].offset = bcm47xx_parts[0].size + 
540                         bcm47xx_parts[3].size + mtd->erasesize;
541                 bcm47xx_parts[1].size   = size - 
542                         bcm47xx_parts[0].size - 
543                         (2*bcm47xx_parts[3].size) - 
544                         mtd->erasesize - custom_data_size;
545         }
546
547         /* find and size rootfs */
548         find_root(mtd,size,&bcm47xx_parts[2]);
549         bcm47xx_parts[2].size = size - dual_image_offset -
550                                 bcm47xx_parts[2].offset -
551                                 bcm47xx_parts[3].size - custom_data_size;
552
553         return bcm47xx_parts;
554 }
555 #endif
556
557 int __init init_bcm47xx_map(void)
558 {
559 #ifdef CONFIG_SSB
560         struct ssb_mipscore *mcore = &ssb_bcm47xx.mipscore;
561 #endif
562         size_t size;
563         int ret = 0;
564 #ifdef CONFIG_MTD_PARTITIONS
565         struct mtd_partition *parts;
566         int i;
567 #endif
568
569 #ifdef CONFIG_SSB
570         u32 window = mcore->flash_window;
571         u32 window_size = mcore->flash_window_size;
572
573         printk("flash init: 0x%08x 0x%08x\n", window, window_size);
574         bcm47xx_map.phys = window;
575         bcm47xx_map.size = window_size;
576         bcm47xx_map.bankwidth = mcore->flash_buswidth;
577         bcm47xx_map.virt = ioremap_nocache(window, window_size);
578 #else
579         printk("flash init: 0x%08x 0x%08x\n", WINDOW_ADDR, WINDOW_SIZE);
580         bcm47xx_map.virt = ioremap_nocache(WINDOW_ADDR, WINDOW_SIZE);
581 #endif
582
583         if (!bcm47xx_map.virt) {
584                 printk("Failed to ioremap\n");
585                 return -EIO;
586         }
587
588         simple_map_init(&bcm47xx_map);
589         
590         if (!(bcm47xx_mtd = do_map_probe("cfi_probe", &bcm47xx_map))) {
591                 printk("Failed to do_map_probe\n");
592                 iounmap((void *)bcm47xx_map.virt);
593                 return -ENXIO;
594         }
595
596         /* override copy_from routine */
597         bcm47xx_map.copy_from = bcm47xx_map_copy_from;
598
599         bcm47xx_mtd->owner = THIS_MODULE;
600
601         size = bcm47xx_mtd->size;
602
603         printk(KERN_NOTICE "Flash device: 0x%x at 0x%x\n", size, WINDOW_ADDR);
604
605 #ifdef CONFIG_MTD_PARTITIONS
606         parts = init_mtd_partitions(bcm47xx_mtd, size);
607         for (i = 0; parts[i].name; i++);
608         ret = add_mtd_partitions(bcm47xx_mtd, parts, i);
609         if (ret) {
610                 printk(KERN_ERR "Flash: add_mtd_partitions failed\n");
611                 goto fail;
612         }
613 #endif
614         return 0;
615
616  fail:
617         if (bcm47xx_mtd)
618                 map_destroy(bcm47xx_mtd);
619         if (bcm47xx_map.virt)
620                 iounmap((void *)bcm47xx_map.virt);
621         bcm47xx_map.virt = 0;
622         return ret;
623 }
624
625 void __exit cleanup_bcm47xx_map(void)
626 {
627 #ifdef CONFIG_MTD_PARTITIONS
628         del_mtd_partitions(bcm47xx_mtd);
629 #endif
630         map_destroy(bcm47xx_mtd);
631         iounmap((void *)bcm47xx_map.virt);
632 }
633
634 module_init(init_bcm47xx_map);
635 module_exit(cleanup_bcm47xx_map);