Files
Kernel/drivers/mtd/brcmnand/bcm63xx-nand.c

440 lines
13 KiB
C

/*
*
* drivers/mtd/brcmnand/bcm7xxx-nand.c
*
<:copyright-BRCM:2002:GPL/GPL:standard
*
Copyright (c) 2005-2006 Broadcom Corporation
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
File: bcm7xxx-nand.c
Description:
This is a device driver for the Broadcom NAND flash for bcm97xxx boards.
when who what
----- --- ----
051011 tht codings derived from OneNand generic.c implementation.
:>
* THIS DRIVER WAS PORTED FROM THE 2.6.18-7.2 KERNEL RELEASE
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <asm/io.h>
#include <bcm_map_part.h>
#include <board.h>
#include "brcmnand_priv.h"
#define PRINTK(...)
//#define PRINTK printk
#define DRIVER_NAME "brcmnand"
#define DRIVER_INFO "Broadcom DSL NAND controller"
static int __devinit brcmnanddrv_probe(struct platform_device *pdev);
static int __devexit brcmnanddrv_remove(struct platform_device *pdev);
static struct mtd_partition bcm63XX_nand_parts[] =
{
{name: "rootfs", offset: 0, size: 0},
{name: "rootfs_update", offset: 0, size: 0},
{name: "data", offset: 0, size: 0},
{name: "nvram", offset: 0, size: 0},
{name: NULL, offset: 0, size: 0}
};
static struct platform_driver brcmnand_platform_driver =
{
.probe = brcmnanddrv_probe,
.remove = __devexit_p(brcmnanddrv_remove),
.driver =
{
.name = DRIVER_NAME,
},
};
static struct resource brcmnand_resources[] =
{
[0] = {
.name = DRIVER_NAME,
.start = BPHYSADDR(BCHP_NAND_REG_START),
.end = BPHYSADDR(BCHP_NAND_REG_END) + 3,
.flags = IORESOURCE_MEM,
},
};
static struct brcmnand_info
{
struct mtd_info mtd;
struct brcmnand_chip brcmnand;
int nr_parts;
struct mtd_partition* parts;
} *gNandInfo[NUM_NAND_CS];
int gNandCS[NAND_MAX_CS];
/* Number of NAND chips, only applicable to v1.0+ NAND controller */
int gNumNand = 0;
int gClearBBT = 0;
char gClearCET = 0;
uint32_t gNandTiming1[NAND_MAX_CS], gNandTiming2[NAND_MAX_CS];
uint32_t gAccControl[NAND_MAX_CS], gNandConfig[NAND_MAX_CS];
static unsigned long t1[NAND_MAX_CS] = {0};
static int nt1 = 0;
static unsigned long t2[NAND_MAX_CS] = {0};
static int nt2 = 0;
static unsigned long acc[NAND_MAX_CS] = {0};
static int nacc = 0;
static unsigned long nandcfg[NAND_MAX_CS] = {0};
static int ncfg = 0;
static void* gPageBuffer = NULL;
static void __devinit
brcmnanddrv_setup_mtd_partitions(struct brcmnand_info* nandinfo)
{
int boot_from_nand = 1;
#if defined(CONFIG_BCM96368)
if( ((GPIO->StrapBus & MISC_STRAP_BUS_BOOT_SEL_MASK) >>
MISC_STRAP_BUS_BOOT_SEL_SHIFT) != MISC_STRAP_BUS_BOOT_NAND )
{
boot_from_nand = 0;
}
#elif defined(CONFIG_BCM96828)
if( ((MISC->miscStrapBus & MISC_STRAP_BUS_BOOT_SEL_MASK) >>
MISC_STRAP_BUS_BOOT_SEL_SHIFT) == MISC_STRAP_BUS_BOOT_SERIAL )
{
boot_from_nand = 0;
}
#elif defined(CONFIG_BCM96362) || defined(CONFIG_BCM96328) || defined(CONFIG_BCM96816) || defined(CONFIG_BCM96818) ||defined(CONFIG_BCM963268)
if( ((MISC->miscStrapBus & MISC_STRAP_BUS_BOOT_SEL_MASK) >>
MISC_STRAP_BUS_BOOT_SEL_SHIFT) != MISC_STRAP_BUS_BOOT_NAND )
{
boot_from_nand = 0;
}
#endif
if( boot_from_nand == 0 )
{
nandinfo->nr_parts = 1;
nandinfo->parts = bcm63XX_nand_parts;
bcm63XX_nand_parts[0].name = "data";
bcm63XX_nand_parts[0].offset = 0;
if( device_size(&(nandinfo->mtd)) < NAND_BBT_THRESHOLD_KB )
{
bcm63XX_nand_parts[0].size =
device_size(&(nandinfo->mtd)) - (NAND_BBT_SMALL_SIZE_KB*1024);
}
else
{
bcm63XX_nand_parts[0].size =
device_size(&(nandinfo->mtd)) - (NAND_BBT_BIG_SIZE_KB*1024);
}
bcm63XX_nand_parts[0].ecclayout = nandinfo->mtd.ecclayout;
PRINTK("Part[0] name=%s, size=%llx, ofs=%llx\n", bcm63XX_nand_parts[0].name,
bcm63XX_nand_parts[0].size, bcm63XX_nand_parts[0].offset);
}
else
{
static NVRAM_DATA nvram;
struct mtd_info* mtd = &nandinfo->mtd;
unsigned long rootfs_ofs;
int rootfs, rootfs_update;
kerSysBlParmsGetInt(NAND_RFS_OFS_NAME, (int *) &rootfs_ofs);
kerSysNvRamGet((char *)&nvram, sizeof(nvram), 0);
nandinfo->nr_parts = 4;
nandinfo->parts = bcm63XX_nand_parts;
/* Root FS. The CFE RAM boot loader saved the rootfs offset that the
* Linux image was loaded from.
*/
PRINTK("rootfs_ofs=0x%8.8lx, part1ofs=0x%8.8lx, part2ofs=0x%8.8lx\n",
rootfs_ofs, nvram.ulNandPartOfsKb[NP_ROOTFS_1],
nvram.ulNandPartOfsKb[NP_ROOTFS_2]);
if( rootfs_ofs == nvram.ulNandPartOfsKb[NP_ROOTFS_1] )
{
rootfs = NP_ROOTFS_1;
rootfs_update = NP_ROOTFS_2;
}
else
{
if( rootfs_ofs == nvram.ulNandPartOfsKb[NP_ROOTFS_2] )
{
rootfs = NP_ROOTFS_2;
rootfs_update = NP_ROOTFS_1;
}
else
{
/* Backward compatibility with old cferam. */
extern unsigned char _text;
unsigned long rootfs_ofs = *(unsigned long *) (&_text - 4);
if( rootfs_ofs == nvram.ulNandPartOfsKb[NP_ROOTFS_1] )
{
rootfs = NP_ROOTFS_1;
rootfs_update = NP_ROOTFS_2;
}
else
{
rootfs = NP_ROOTFS_2;
rootfs_update = NP_ROOTFS_1;
}
}
}
bcm63XX_nand_parts[0].offset = nvram.ulNandPartOfsKb[rootfs]*1024;
bcm63XX_nand_parts[0].size = nvram.ulNandPartSizeKb[rootfs]*1024;
bcm63XX_nand_parts[0].ecclayout = mtd->ecclayout;
bcm63XX_nand_parts[1].offset = nvram.ulNandPartOfsKb[rootfs_update]*1024;
bcm63XX_nand_parts[1].size = nvram.ulNandPartSizeKb[rootfs_update]*1024;
bcm63XX_nand_parts[1].ecclayout = mtd->ecclayout;
/* Data (psi, scratch pad) */
bcm63XX_nand_parts[2].offset = nvram.ulNandPartOfsKb[NP_DATA] * 1024;
bcm63XX_nand_parts[2].size = nvram.ulNandPartSizeKb[NP_DATA] * 1024;
bcm63XX_nand_parts[2].ecclayout = mtd->ecclayout;
/* Boot and NVRAM data */
bcm63XX_nand_parts[3].offset = nvram.ulNandPartOfsKb[NP_BOOT] * 1024;
bcm63XX_nand_parts[3].size = nvram.ulNandPartSizeKb[NP_BOOT] * 1024;
bcm63XX_nand_parts[3].ecclayout = mtd->ecclayout;
PRINTK("Part[0] name=%s, size=%llx, ofs=%llx\n", bcm63XX_nand_parts[0].name,
bcm63XX_nand_parts[0].size, bcm63XX_nand_parts[0].offset);
PRINTK("Part[1] name=%s, size=%llx, ofs=%llx\n", bcm63XX_nand_parts[1].name,
bcm63XX_nand_parts[1].size, bcm63XX_nand_parts[1].offset);
PRINTK("Part[2] name=%s, size=%llx, ofs=%llx\n", bcm63XX_nand_parts[2].name,
bcm63XX_nand_parts[2].size, bcm63XX_nand_parts[2].offset);
PRINTK("Part[3] name=%s, size=%llx, ofs=%llx\n", bcm63XX_nand_parts[3].name,
bcm63XX_nand_parts[3].size, bcm63XX_nand_parts[3].offset);
}
}
static int __devinit brcmnanddrv_probe(struct platform_device *pdev)
{
static int csi = 0; // Index into dev/nandInfo array
int cs = 0; // Chip Select
int err = 0;
struct brcmnand_info* info = NULL;
static struct brcmnand_ctrl* ctrl = (struct brcmnand_ctrl*) 0;
if(!gPageBuffer &&
(gPageBuffer = kmalloc(sizeof(struct nand_buffers),GFP_KERNEL)) == NULL)
{
err = -ENOMEM;
}
else
{
if( (ctrl = kmalloc(sizeof(struct brcmnand_ctrl), GFP_KERNEL)) != NULL)
{
memset(ctrl, 0, sizeof(struct brcmnand_ctrl));
ctrl->state = FL_READY;
init_waitqueue_head(&ctrl->wq);
spin_lock_init(&ctrl->chip_lock);
if((info=kmalloc(sizeof(struct brcmnand_info),GFP_KERNEL)) != NULL)
{
gNandInfo[csi] = info;
memset(info, 0, sizeof(struct brcmnand_info));
info->brcmnand.ctrl = ctrl;
info->brcmnand.ctrl->numchips = gNumNand = 1;
info->brcmnand.csi = csi;
/* For now all devices share the same buffer */
info->brcmnand.ctrl->buffers =
(struct nand_buffers*) gPageBuffer;
info->brcmnand.ctrl->numchips = gNumNand;
info->brcmnand.chip_shift = 0; // Only 1 chip
info->brcmnand.priv = &info->mtd;
info->mtd.name = dev_name(&pdev->dev);
info->mtd.priv = &info->brcmnand;
info->mtd.owner = THIS_MODULE;
/* Enable the following for a flash based bad block table */
info->brcmnand.options |= NAND_USE_FLASH_BBT;
/* Each chip now will have its own BBT (per mtd handle) */
if (brcmnand_scan(&info->mtd, cs, gNumNand) == 0)
{
PRINTK("Master size=%08llx\n", info->mtd.size);
brcmnanddrv_setup_mtd_partitions(info);
add_mtd_partitions(&info->mtd, info->parts, info->nr_parts);
dev_set_drvdata(&pdev->dev, info);
}
else
err = -ENXIO;
}
else
err = -ENOMEM;
}
else
err = -ENOMEM;
}
if( err )
{
if( gPageBuffer )
{
kfree(gPageBuffer);
gPageBuffer = NULL;
}
if( ctrl )
{
kfree(ctrl);
ctrl = NULL;
}
if( info )
{
kfree(info);
info = NULL;
}
}
return( err );
}
static int __devexit brcmnanddrv_remove(struct platform_device *pdev)
{
struct brcmnand_info *info = dev_get_drvdata(&pdev->dev);
dev_set_drvdata(&pdev->dev, NULL);
if (info)
{
del_mtd_partitions(&info->mtd);
brcmnand_release(&info->mtd);
kfree(gPageBuffer);
kfree(info);
}
return 0;
}
static int __init brcmnanddrv_init(void)
{
int ret = 0;
int csi;
int ncsi;
char cmd[32] = "\0";
struct platform_device *pdev;
kerSysBlParmsGetStr(NAND_COMMAND_NAME, cmd, sizeof(cmd));
PRINTK("%s: brcmnanddrv_init - NANDCMD='%s'\n", __FUNCTION__, cmd);
if (cmd[0])
{
if (strcmp(cmd, "rescan") == 0)
gClearBBT = 1;
else if (strcmp(cmd, "showbbt") == 0)
gClearBBT = 2;
else if (strcmp(cmd, "eraseall") == 0)
gClearBBT = 8;
else if (strcmp(cmd, "erase") == 0)
gClearBBT = 7;
else if (strcmp(cmd, "clearbbt") == 0)
gClearBBT = 9;
else if (strcmp(cmd, "showcet") == 0)
gClearCET = 1;
else if (strcmp(cmd, "resetcet") == 0)
gClearCET = 2;
else if (strcmp(cmd, "disablecet") == 0)
gClearCET = 3;
else
printk(KERN_WARNING "%s: unknown command '%s'\n",
__FUNCTION__, cmd);
}
for (csi=0; csi<NAND_MAX_CS; csi++)
{
gNandTiming1[csi] = 0;
gNandTiming2[csi] = 0;
gAccControl[csi] = 0;
gNandConfig[csi] = 0;
}
if (nacc == 1)
PRINTK("%s: nacc=%d, gAccControl[0]=%08lx, gNandConfig[0]=%08lx\n", \
__FUNCTION__, nacc, acc[0], nandcfg[0]);
if (nacc>1)
PRINTK("%s: nacc=%d, gAccControl[1]=%08lx, gNandConfig[1]=%08lx\n", \
__FUNCTION__, nacc, acc[1], nandcfg[1]);
for (csi=0; csi<nacc; csi++)
gAccControl[csi] = acc[csi];
for (csi=0; csi<ncfg; csi++)
gNandConfig[csi] = nandcfg[csi];
ncsi = max(nt1, nt2);
for (csi=0; csi<ncsi; csi++)
{
if (nt1 && csi < nt1)
gNandTiming1[csi] = t1[csi];
if (nt2 && csi < nt2)
gNandTiming2[csi] = t2[csi];
}
printk (KERN_INFO DRIVER_INFO " (BrcmNand Controller)\n");
if( (pdev = platform_device_alloc(DRIVER_NAME, 0)) != NULL )
{
platform_device_add(pdev);
platform_device_put(pdev);
ret = platform_driver_register(&brcmnand_platform_driver);
if (ret >= 0)
request_resource(&iomem_resource, &brcmnand_resources[0]);
else
printk("brcmnanddrv_init: driver_register failed, err=%d\n", ret);
}
else
ret = -ENODEV;
return ret;
}
static void __exit brcmnanddrv_exit(void)
{
release_resource(&brcmnand_resources[0]);
platform_driver_unregister(&brcmnand_platform_driver);
}
module_init(brcmnanddrv_init);
module_exit(brcmnanddrv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ton Truong <ttruong@broadcom.com>");
MODULE_DESCRIPTION("Broadcom NAND flash driver");