Root/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
66struct 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
96extern struct ssb_bus ssb_bcm47xx;
97#endif
98static struct mtd_info *bcm47xx_mtd;
99
100static 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
116static 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
125static 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
134static int __init
135find_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
197static 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
203static 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
252static int __init
253find_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
285static int __init
286find_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
393static 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
457struct mtd_partition * __init
458init_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
557int __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
625void __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
634module_init(init_bcm47xx_map);
635module_exit(cleanup_bcm47xx_map);
636

Archive Download this file



interactive