/*********************************************************************************
* OK335xS GPMC nand device register hacking
* 说明:
* 由于最近遇到No NAND device found这个内核错误,在网络上也没找到很好的
* 解决办法,于是只能自己去跟踪整个设备、驱动的注册流程,试着去理解整个系统
* 的运作流程。
*
* 2015-9-2 雨 深圳 南山平山村 曾剑锋
********************************************************************************/
cat arch/arm/mach-omap2/board-am335xevm.c
MACHINE_START(AM335XEVM, "am335xevm")
/* Maintainer: Texas Instruments */
.atag_offset = 0x100,
.map_io = am335x_evm_map_io,
.init_early = am33xx_init_early,
.init_irq = ti81xx_init_irq,
.handle_irq = omap3_intc_handle_irq,
.timer = &omap3_am33xx_timer,
.init_machine = am335x_evm_init, -------------------+
MACHINE_END |
|
MACHINE_START(AM335XIAEVM, "am335xiaevm") |
/* Maintainer: Texas Instruments */ |
.atag_offset = 0x100, |
.map_io = am335x_evm_map_io, |
.init_irq = ti81xx_init_irq, |
.init_early = am33xx_init_early, |
.timer = &omap3_am33xx_timer, |
.init_machine = am335x_evm_init, -------------------+
MACHINE_END |
|
static void __init am335x_evm_init(void) <------------------+
{
......
setup_ok335xs(); --------+
...... |
} |
|
/* ok335xs */ |
static void setup_ok335xs(void) <-------+
{
pr_info("The board is a ok335xs.\n");
/* Starter Kit has Micro-SD slot which doesn't have Write Protect pin */
am335x_mmc[0].gpio_wp = -EINVAL;
_configure_device(EVM_SK, ok335xs_dev_cfg, PROFILE_NONE); ------------------+
|
am33xx_cpsw_init(AM33XX_CPSW_MODE_RGMII, NULL, NULL); |
/* Atheros Tx Clk delay Phy fixup */ |
phy_register_fixup_for_uid(AM335X_EVM_PHY_ID, AM335X_EVM_PHY_MASK, |
am33xx_evm_tx_clk_dly_phy_fixup); |
} |
|
/*ok335xs*/ |
static struct evm_dev_cfg ok335xs_dev_cfg[] = { <------------------+
......
{evm_nand_init, DEV_ON_BASEBOARD, PROFILE_ALL}, ------------+
{NULL, 0, 0}, |
}; |
|
static void evm_nand_init(int evm_id, int profile) <-----------+
{
struct omap_nand_platform_data *pdata;
struct gpmc_devices_info gpmc_device[2] = {
{ NULL, 0 },
{ NULL, 0 },
};
setup_pin_mux(nand_pin_mux);
pdata = omap_nand_init(am335x_nand_partitions,
ARRAY_SIZE(am335x_nand_partitions), 0, 0,
&am335x_nand_timings);
if (!pdata)
return;
// pdata->ecc_opt =OMAP_ECC_BCH8_CODE_HW;
pdata->ecc_opt =OMAP_ECC_HAMMING_CODE_DEFAULT;
pdata->elm_used = true;
gpmc_device[0].pdata = pdata;
gpmc_device[0].flag = GPMC_DEVICE_NAND; ------------------------------------+
|
omap_init_gpmc(gpmc_device, sizeof(gpmc_device)); --------------------+ |
omap_init_elm(); | |
} | |
| |
int __init omap_init_gpmc(struct gpmc_devices_info *pdata, int pdata_len) <-----+ |
{ |
struct omap_hwmod *oh; |
struct platform_device *pdev; |
char *name = "omap-gpmc"; -----------------------------------------+ |
char *oh_name = "gpmc"; | |
| |
oh = omap_hwmod_lookup(oh_name); | |
if (!oh) { | |
pr_err("Could not look up %s\n", oh_name); | |
return -ENODEV; | |
} | |
| |
pdev = omap_device_build(name, -1, oh, pdata, | |
pdata_len, NULL, 0, 0); | |
if (IS_ERR(pdev)) { | |
WARN(1, "Can't build omap_device for %s:%s.\n", | |
name, oh->name); | |
return PTR_ERR(pdev); | |
} | |
| |
return 0; | |
} | |
| |
| |
| |
cat arch/arm/mach-omap2/gpmc.c | |
static struct platform_driver gpmc_driver = { | |
.probe = gpmc_probe, --------------+ | |
.remove = __devexit_p(gpmc_remove), | | |
.driver = { | | |
.name = DRIVER_NAME, ----------+ | | |
.owner = THIS_MODULE, | | | |
}, | | | |
}; | | | |
| | | |
module_platform_driver(gpmc_driver); | | | |
| | | |
#define DRIVER_NAME "omap-gpmc" <------+--------------------*--------+ |
| |
static int __devinit gpmc_probe(struct platform_device *pdev) <----+ |
{ |
u32 l; |
int ret = -EINVAL; |
struct resource *res = NULL; |
struct gpmc_devices_info *gpmc_device = pdev->dev.platform_data; |
void *p; |
|
/* XXX: This should go away with HWMOD & runtime PM adaptation */ |
gpmc_clk_init(&pdev->dev); |
|
gpmc_dev = &pdev->dev; |
|
gpmc = devm_kzalloc(&pdev->dev, sizeof(struct gpmc), GFP_KERNEL); |
if (!gpmc) |
return -ENOMEM; |
|
gpmc->dev = &pdev->dev; |
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
if (!res) { |
ret = -ENOENT; |
dev_err(gpmc->dev, "Failed to get resource: memory\n"); |
goto err_res; |
} |
gpmc->phys_base = res->start; |
gpmc->memsize = resource_size(res); |
|
if (request_mem_region(gpmc->phys_base, |
gpmc->memsize, DRIVER_NAME) == NULL) { |
ret = -ENOMEM; |
dev_err(gpmc->dev, "Failed to request memory region\n"); |
goto err_mem; |
} |
|
gpmc->io_base = ioremap(gpmc->phys_base, gpmc->memsize); |
if (!gpmc->io_base) { |
ret = -ENOMEM; |
dev_err(gpmc->dev, "Failed to ioremap memory\n"); |
goto err_remap; |
} |
|
gpmc->ecc_used = -EINVAL; |
spin_lock_init(&gpmc->mem_lock); |
platform_set_drvdata(pdev, gpmc); |
|
l = gpmc_read_reg(GPMC_REVISION); |
dev_info(gpmc->dev, "GPMC revision %d.%d\n", (l >> 4) & 0x0f, l & 0x0f); |
|
gpmc_mem_init(); |
|
for (p = gpmc_device->pdata; p; gpmc_device++, p = gpmc_device->pdata) |
if (gpmc_device->flag & GPMC_DEVICE_NAND) <---------------+
gpmc_nand_init((struct omap_nand_platform_data *) p); ----------------+
return 0; |
|
err_remap: |
release_mem_region(gpmc->phys_base, gpmc->memsize); |
err_mem: |
err_res: |
devm_kfree(&pdev->dev, gpmc); |
return ret; |
} |
|
int __devinit gpmc_nand_init(struct omap_nand_platform_data *gpmc_nand_data) <-----+
{
int err = 0;
u8 cs = 0;
struct device *dev = &gpmc_nand_device.dev;
/* if cs not provided, find out the chip-select on which NAND exist */
if (gpmc_nand_data->cs > GPMC_CS_NUM)
while (cs < GPMC_CS_NUM) {
u32 ret = 0;
ret = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG1);
if ((ret & 0xC00) == 0x800) {
printk(KERN_INFO "Found NAND on CS%d\n", cs);
gpmc_nand_data->cs = cs;
break;
}
cs++;
}
if (gpmc_nand_data->cs > GPMC_CS_NUM) {
printk(KERN_INFO "NAND: Unable to find configuration "
"in GPMC\n ");
return -ENODEV;
}
gpmc_nand_device.dev.platform_data = gpmc_nand_data;
gpmc_nand_data->ctrlr_suspend = gpmc_suspend;
gpmc_nand_data->ctrlr_resume = gpmc_resume;
printk(KERN_INFO "Registering NAND on CS%d\n", gpmc_nand_data->cs);
err = gpmc_cs_request(gpmc_nand_data->cs, NAND_IO_SIZE,
&gpmc_nand_data->phys_base);
if (err < 0) {
dev_err(dev, "Cannot request GPMC CS\n");
return err;
}
/* Set timings in GPMC */
err = omap2_nand_gpmc_retime(gpmc_nand_data); ------------------+
if (err < 0) { |
dev_err(dev, "Unable to set gpmc timings: %d\n", err); |
return err; |
} |
|
/* Enable RD PIN Monitoring Reg */ |
if (gpmc_nand_data->dev_ready) { |
gpmc_cs_configure(gpmc_nand_data->cs, GPMC_CONFIG_RDY_BSY, 1); |
} |
|
err = platform_device_register(&gpmc_nand_device); ---------------------------*-+
if (err < 0) { | |
dev_err(dev, "Unable to register NAND device\n"); | |
goto out_free_cs; | |
} | |
| |
return 0; | |
| |
out_free_cs: | |
gpmc_cs_free(gpmc_nand_data->cs); | |
| |
return err; | |
} | |
| |
static int omap2_nand_gpmc_retime(struct omap_nand_platform_data *gpmc_nand_data) <--+ |
{ |
struct gpmc_timings t; |
int err; |
|
if (!gpmc_nand_data->gpmc_t) |
return 0; |
|
memset(&t, 0, sizeof(t)); |
t.sync_clk = gpmc_nand_data->gpmc_t->sync_clk; |
t.cs_on = gpmc_round_ns_to_ticks(gpmc_nand_data->gpmc_t->cs_on); |
t.adv_on = gpmc_round_ns_to_ticks(gpmc_nand_data->gpmc_t->adv_on); |
|
/* Read */ |
t.adv_rd_off = gpmc_round_ns_to_ticks( |
gpmc_nand_data->gpmc_t->adv_rd_off); |
t.oe_on = t.adv_on; |
t.access = gpmc_round_ns_to_ticks(gpmc_nand_data->gpmc_t->access); |
t.oe_off = gpmc_round_ns_to_ticks(gpmc_nand_data->gpmc_t->oe_off); |
t.cs_rd_off = gpmc_round_ns_to_ticks(gpmc_nand_data->gpmc_t->cs_rd_off); |
t.rd_cycle = gpmc_round_ns_to_ticks(gpmc_nand_data->gpmc_t->rd_cycle); |
|
/* Write */ |
t.adv_wr_off = gpmc_round_ns_to_ticks( |
gpmc_nand_data->gpmc_t->adv_wr_off); |
t.we_on = t.oe_on; |
if (cpu_is_omap34xx()) { |
t.wr_data_mux_bus = gpmc_round_ns_to_ticks( |
gpmc_nand_data->gpmc_t->wr_data_mux_bus); |
t.wr_access = gpmc_round_ns_to_ticks( |
gpmc_nand_data->gpmc_t->wr_access); |
} |
t.we_off = gpmc_round_ns_to_ticks(gpmc_nand_data->gpmc_t->we_off); |
t.cs_wr_off = gpmc_round_ns_to_ticks(gpmc_nand_data->gpmc_t->cs_wr_off); |
t.wr_cycle = gpmc_round_ns_to_ticks(gpmc_nand_data->gpmc_t->wr_cycle); --+ |
| |
/* Configure GPMC */ | |
if (gpmc_nand_data->devsize == NAND_BUSWIDTH_16) | |
gpmc_cs_configure(gpmc_nand_data->cs, GPMC_CONFIG_DEV_SIZE, 1); | |
else | |
gpmc_cs_configure(gpmc_nand_data->cs, GPMC_CONFIG_DEV_SIZE, 0); | |
gpmc_cs_configure(gpmc_nand_data->cs, | |
GPMC_CONFIG_DEV_TYPE, GPMC_DEVICETYPE_NAND); | |
err = gpmc_cs_set_timings(gpmc_nand_data->cs, &t); | |
if (err) | |
return err; | |
| |
return 0; | |
} | |
| |
unsigned int gpmc_round_ns_to_ticks(unsigned int time_ns) <-----------+ |
{ |
unsigned long ticks = gpmc_ns_to_ticks(time_ns); ----------+ |
| |
return ticks * gpmc_get_fclk_period() / 1000; | |
} | |
| |
unsigned int gpmc_ns_to_ticks(unsigned int time_ns) <---------+ |
{ |
unsigned long tick_ps; |
|
/* Calculate in picosecs to yield more exact results */ |
tick_ps = gpmc_get_fclk_period(); ----------+ |
| |
return (time_ns * 1000 + tick_ps - 1) / tick_ps; | |
} | |
| |
unsigned long gpmc_get_fclk_period(void) <---------+ |
{ |
unsigned long rate = clk_get_rate(gpmc_l3_clk); |
|
if (rate == 0) { |
printk(KERN_WARNING "gpmc_l3_clk not enabled\n"); |
return 0; |
} |
|
rate /= 1000; |
rate = 1000000000 / rate; /* In picoseconds */ |
|
return rate; |
} |
|
static struct platform_device gpmc_nand_device = { <------------------------------+
.name = "omap2-nand", ------------------------+
.id = 0, |
.num_resources = 1, |
.resource = &gpmc_nand_resource, ---------+ |
}; | |
| |
static struct resource gpmc_nand_resource = { <--------+ |
.flags = IORESOURCE_MEM, ---------+ |
}; | |
| |
#define IORESOURCE_MEM 0x00000200 <--------+ |
|
|
|
cat drivers/mtd/nand/omap2.c |
#define DRIVER_NAME "omap2-nand" <-----------------------+
static struct platform_driver omap_nand_driver = { <-----+
.probe = omap_nand_probe, ----------*-----------+
.remove = omap_nand_remove, | |
#ifdef CONFIG_PM | |
.suspend = omap_nand_suspend, | |
.resume = omap_nand_resume, | |
#endif | |
.driver = { | |
.name = DRIVER_NAME, | |
.owner = THIS_MODULE, | |
}, | |
}; | |
| |
static int __init omap_nand_init(void) <--------+ | |
{ | | |
pr_info("%s driver initializing\n", DRIVER_NAME); | | |
| | |
return platform_driver_register(&omap_nand_driver); -*-+ |
} | |
| |
static void __exit omap_nand_exit(void) | |
{ | |
platform_driver_unregister(&omap_nand_driver); | |
} | |
| |
module_init(omap_nand_init); ---------+ |
module_exit(omap_nand_exit); |
|
static int __devinit omap_nand_probe(struct platform_device *pdev) <---+
{
struct omap_nand_info *info;
struct omap_nand_platform_data *pdata;
int err;
int i, offset;
pdata = pdev->dev.platform_data;
if (pdata == NULL) {
dev_err(&pdev->dev, "platform data missing\n");
return -ENODEV;
}
info = kzalloc(sizeof(struct omap_nand_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
platform_set_drvdata(pdev, info);
spin_lock_init(&info->controller.lock);
init_waitqueue_head(&info->controller.wq);
info->pdev = pdev;
info->gpmc_cs = pdata->cs;
info->phys_base = pdata->phys_base;
info->mtd.priv = &info->nand;
info->mtd.name = dev_name(&pdev->dev);
info->mtd.owner = THIS_MODULE;
info->ecc_opt = pdata->ecc_opt;
info->nand.options = pdata->devsize;
info->nand.options |= NAND_SKIP_BBTSCAN;
/*
* If ELM feature is used in OMAP NAND driver, then configure it
*/
if (pdata->elm_used) {
if (pdata->ecc_opt == OMAP_ECC_BCH8_CODE_HW)
omap_configure_elm(&info->mtd, OMAP_BCH8_ECC);
}
if (pdata->ctrlr_suspend)
info->ctrlr_suspend = pdata->ctrlr_suspend;
if (pdata->ctrlr_resume)
info->ctrlr_resume = pdata->ctrlr_resume;
/* NAND write protect off */
gpmc_cs_configure(info->gpmc_cs, GPMC_CONFIG_WP, 0);
if (!request_mem_region(info->phys_base, NAND_IO_SIZE,
pdev->dev.driver->name)) {
err = -EBUSY;
goto out_free_info;
}
info->nand.IO_ADDR_R = ioremap(info->phys_base, NAND_IO_SIZE);
if (!info->nand.IO_ADDR_R) {
err = -ENOMEM;
goto out_release_mem_region;
}
info->nand.controller = &info->controller;
info->nand.IO_ADDR_W = info->nand.IO_ADDR_R;
info->nand.cmd_ctrl = omap_hwcontrol;
/*
* If RDY/BSY line is connected to OMAP then use the omap ready
* funcrtion and the generic nand_wait function which reads the status
* register after monitoring the RDY/BSY line.Otherwise use a standard
* chip delay which is slightly more than tR (AC Timing) of the NAND
* device and read status register until you get a failure or success
*/
if (pdata->dev_ready) {
info->nand.dev_ready = omap_dev_ready;
info->nand.chip_delay = 0;
} else {
info->nand.waitfunc = omap_wait;
info->nand.chip_delay = 50;
}
switch (pdata->xfer_type) {
case NAND_OMAP_PREFETCH_POLLED:
info->nand.read_buf = omap_read_buf_pref;
info->nand.write_buf = omap_write_buf_pref;
break;
case NAND_OMAP_POLLED:
if (info->nand.options & NAND_BUSWIDTH_16) {
info->nand.read_buf = omap_read_buf16;
info->nand.write_buf = omap_write_buf16;
} else {
info->nand.read_buf = omap_read_buf8;
info->nand.write_buf = omap_write_buf8;
}
break;
case NAND_OMAP_PREFETCH_DMA:
err = omap_request_dma(OMAP24XX_DMA_GPMC, "NAND",
omap_nand_dma_cb, &info->comp, &info->dma_ch);
if (err < 0) {
info->dma_ch = -1;
dev_err(&pdev->dev, "DMA request failed!\n");
goto out_release_mem_region;
} else {
omap_set_dma_dest_burst_mode(info->dma_ch,
OMAP_DMA_DATA_BURST_16);
omap_set_dma_src_burst_mode(info->dma_ch,
OMAP_DMA_DATA_BURST_16);
info->nand.read_buf = omap_read_buf_dma_pref;
info->nand.write_buf = omap_write_buf_dma_pref;
}
break;
case NAND_OMAP_PREFETCH_IRQ:
err = request_irq(pdata->gpmc_irq,
omap_nand_irq, IRQF_SHARED, "gpmc-nand", info);
if (err) {
dev_err(&pdev->dev, "requesting irq(%d) error:%d",
pdata->gpmc_irq, err);
goto out_release_mem_region;
} else {
info->gpmc_irq = pdata->gpmc_irq;
info->nand.read_buf = omap_read_buf_irq_pref;
info->nand.write_buf = omap_write_buf_irq_pref;
}
break;
default:
dev_err(&pdev->dev,
"xfer_type(%d) not supported!\n", pdata->xfer_type);
err = -EINVAL;
goto out_release_mem_region;
}
info->nand.verify_buf = omap_verify_buf;
/* selsect the ecc type */
if (pdata->ecc_opt == OMAP_ECC_HAMMING_CODE_DEFAULT)
info->nand.ecc.mode = NAND_ECC_SOFT;
else {
if (pdata->ecc_opt == OMAP_ECC_BCH4_CODE_HW) {
info->nand.ecc.bytes = 4*7;
info->nand.ecc.size = 4*512;
} else if (pdata->ecc_opt == OMAP_ECC_BCH8_CODE_HW) {
info->nand.ecc.bytes = OMAP_BCH8_ECC_SECT_BYTES;
info->nand.ecc.size = 512;
info->nand.ecc.read_page = omap_read_page_bch;
} else {
info->nand.ecc.bytes = 3;
info->nand.ecc.size = 512;
}
info->nand.ecc.calculate = omap_calculate_ecc;
info->nand.ecc.hwctl = omap_enable_hwecc;
info->nand.ecc.correct = omap_correct_data;
info->nand.ecc.mode = NAND_ECC_HW;
}
/* DIP switches on some boards change between 8 and 16 bit
* bus widths for flash. Try the other width if the first try fails.
*/
if (nand_scan_ident(&info->mtd, 1, NULL)) { --------+
info->nand.options ^= NAND_BUSWIDTH_16; |
if (nand_scan_ident(&info->mtd, 1, NULL)) { |
err = -ENXIO; |
goto out_release_mem_region; |
} |
} |
|
/* select ecc lyout */ |
if (info->nand.ecc.mode != NAND_ECC_SOFT) { |
|
if (!(info->nand.options & NAND_BUSWIDTH_16)) |
info->nand.badblock_pattern = &bb_descrip_flashbased; |
|
offset = JFFS2_CLEAN_MARKER_OFFSET; |
|
omap_oobinfo.eccbytes = info->nand.ecc.bytes * |
info->mtd.writesize / info->nand.ecc.size; |
|
if (pdata->ecc_opt == OMAP_ECC_HAMMING_CODE_HW_ROMCODE) { |
omap_oobinfo.oobfree->offset = |
offset + omap_oobinfo.eccbytes; |
omap_oobinfo.oobfree->length = info->mtd.oobsize - |
(offset + omap_oobinfo.eccbytes); |
} else if (pdata->ecc_opt == OMAP_ECC_BCH8_CODE_HW) { |
offset = BCH_ECC_POS; /* Synchronize with U-boot */ |
|
omap_oobinfo.oobfree->offset = offset + |
omap_oobinfo.eccbytes; |
|
omap_oobinfo.oobfree->length = info->mtd.oobsize - |
offset - omap_oobinfo.eccbytes; |
} else { |
omap_oobinfo.oobfree->offset = offset; |
omap_oobinfo.oobfree->length = info->mtd.oobsize - |
offset - omap_oobinfo.eccbytes; |
/* |
offset is calculated considering the following : |
1) 12 bytes ECC for 512 byte access and 24 bytes ECC for |
256 byte access in OOB_64 can be supported |
2)Ecc bytes lie to the end of OOB area. |
3)Ecc layout must match with u-boot's ECC layout. |
*/ |
offset = info->mtd.oobsize - MAX_HWECC_BYTES_OOB_64; |
} |
|
for (i = 0; i < omap_oobinfo.eccbytes; i++) |
omap_oobinfo.eccpos[i] = i+offset; |
|
info->nand.ecc.layout = &omap_oobinfo; |
} |
|
/* second phase scan */ |
if (nand_scan_tail(&info->mtd)) { |
err = -ENXIO; |
goto out_release_mem_region; |
} |
|
/* Fix sub page size to page size for HW ECC */ |
if (info->nand.ecc.mode == NAND_ECC_HW) { |
/* |
* For HW ECC, subpage size set to page size |
* as subpage operations not supporting. |
*/ |
info->mtd.subpage_sft = 0; |
info->nand.subpagesize = info->mtd.writesize >> |
info->mtd.subpage_sft; |
} |
|
mtd_device_parse_register(&info->mtd, NULL, 0, |
pdata->parts, pdata->nr_parts); |
|
platform_set_drvdata(pdev, &info->mtd); |
|
return 0; |
|
out_release_mem_region: |
release_mem_region(info->phys_base, NAND_IO_SIZE); |
out_free_info: |
kfree(info); |
|
return err; |
} |
|
int nand_scan_ident(struct mtd_info *mtd, int maxchips, <-------------+
struct nand_flash_dev *table)
{
int i, busw, nand_maf_id, nand_dev_id;
struct nand_chip *chip = mtd->priv;
struct nand_flash_dev *type;
/* Get buswidth to select the correct functions */
busw = chip->options & NAND_BUSWIDTH_16;
/* Set the default functions */
nand_set_defaults(chip, busw); ---------+
|
/* Read the flash type */ |
type = nand_get_flash_type(mtd, chip, busw, ---------|-------+
&nand_maf_id, &nand_dev_id, table); | |
| |
if (IS_ERR(type)) { | |
if (!(chip->options & NAND_SCAN_SILENT_NODEV)) | |
pr_warn("No NAND device found\n"); // get the target | |
chip->select_chip(mtd, -1); | |
return PTR_ERR(type); | |
} | |
| |
/* Check for a chip array */ | |
for (i = 1; i < maxchips; i++) { | |
chip->select_chip(mtd, i); | |
/* See comment in nand_get_flash_type for reset */ | |
chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); <----------*--+ |
/* Send the command for reading device ID */ | | |
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); | | |
/* Read manufacturer and device IDs */ | | |
if (nand_maf_id != chip->read_byte(mtd) || | | |
nand_dev_id != chip->read_byte(mtd)) | | |
break; | | |
} | | |
if (i > 1) | | |
pr_info("%d NAND chips detected\n", i); | | |
| | |
/* Store the number of chips and calc total size for mtd */ | | |
chip->numchips = i; | | |
mtd->size = i * chip->chipsize; | | |
| | |
return 0; | | |
} | | |
EXPORT_SYMBOL(nand_scan_ident); | | |
| | |
/* Set default functions */ | | |
static void nand_set_defaults(struct nand_chip *chip, int busw) <----+ | |
{ | |
/* check for proper chip_delay setup, set 20us if not */ | |
if (!chip->chip_delay) | |
chip->chip_delay = 20; | |
| |
/* check, if a user supplied command function given */ | |
if (chip->cmdfunc == NULL) | |
chip->cmdfunc = nand_command; ---------------------+ |
|
/* check, if a user supplied wait function given */ |
if (chip->waitfunc == NULL) |
chip->waitfunc = nand_wait; |
|
if (!chip->select_chip) |
chip->select_chip = nand_select_chip; |
if (!chip->read_byte) |
chip->read_byte = busw ? nand_read_byte16 : nand_read_byte; |
if (!chip->read_word) |
chip->read_word = nand_read_word; |
if (!chip->block_bad) |
chip->block_bad = nand_block_bad; |
if (!chip->block_markbad) |
chip->block_markbad = nand_default_block_markbad; |
if (!chip->write_buf) |
chip->write_buf = busw ? nand_write_buf16 : nand_write_buf; |
if (!chip->read_buf) |
chip->read_buf = busw ? nand_read_buf16 : nand_read_buf; |
if (!chip->verify_buf) |
chip->verify_buf = busw ? nand_verify_buf16 : nand_verify_buf; |
if (!chip->scan_bbt) |
chip->scan_bbt = nand_default_bbt; |
|
if (!chip->controller) { |
chip->controller = &chip->hwcontrol; |
spin_lock_init(&chip->controller->lock); |
init_waitqueue_head(&chip->controller->wq); |
} |
|
} |
|
static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd, <-----+
struct nand_chip *chip,
int busw,
int *maf_id, int *dev_id,
struct nand_flash_dev *type)
{
int i, maf_idx;
u8 id_data[8];
int ret;
/* Select the device */
chip->select_chip(mtd, 0);
/*
* Reset the chip, required by some chips (e.g. Micron MT29FxGxxxxx)
* after power-up.
*/
chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
/* Send the command for reading device ID */
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
/* Read manufacturer and device IDs */
*maf_id = chip->read_byte(mtd);
*dev_id = chip->read_byte(mtd);
/*
* Try again to make sure, as some systems the bus-hold or other
* interface concerns can cause random data which looks like a
* possibly credible NAND flash to appear. If the two results do
* not match, ignore the device completely.
*/
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
for (i = 0; i < 2; i++)
id_data[i] = chip->read_byte(mtd);
if (id_data[0] != *maf_id || id_data[1] != *dev_id) {
pr_info("%s: second ID read did not match "
"%02x,%02x against %02x,%02x\n", __func__,
*maf_id, *dev_id, id_data[0], id_data[1]);
return ERR_PTR(-ENODEV);
}
if (!type)
type = nand_flash_ids;
for (; type->name != NULL; type++)
if (*dev_id == type->id)
break;
chip->onfi_version = 0;
if (!type->name || !type->pagesize) {
/* Check is chip is ONFI compliant */
ret = nand_flash_detect_onfi(mtd, chip, &busw);
if (ret)
goto ident_done;
}
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
/* Read entire ID string */
for (i = 0; i < 8; i++)
id_data[i] = chip->read_byte(mtd);
if (!type->name)
return ERR_PTR(-ENODEV);
if (!mtd->name)
mtd->name = type->name;
chip->chipsize = (uint64_t)type->chipsize << 20;
if (!type->pagesize && chip->init_size) {
/* Set the pagesize, oobsize, erasesize by the driver */
busw = chip->init_size(mtd, chip, id_data);
} else if (!type->pagesize) {
int extid;
/* The 3rd id byte holds MLC / multichip data */
chip->cellinfo = id_data[2];
/* The 4th id byte is the important one */
extid = id_data[3];
/*
* Field definitions are in the following datasheets:
* Old style (4,5 byte ID): Samsung K9GAG08U0M (p.32)
* New style (6 byte ID): Samsung K9GBG08U0M (p.40)
*
* Check for wraparound + Samsung ID + nonzero 6th byte
* to decide what to do.
*/
if (id_data[0] == id_data[6] && id_data[1] == id_data[7] &&
id_data[0] == NAND_MFR_SAMSUNG &&
(chip->cellinfo & NAND_CI_CELLTYPE_MSK) &&
id_data[5] != 0x00) {
/* Calc pagesize */
mtd->writesize = 2048 << (extid & 0x03);
extid >>= 2;
/* Calc oobsize */
switch (extid & 0x03) {
case 1:
mtd->oobsize = 128;
break;
case 2:
mtd->oobsize = 218;
break;
case 3:
mtd->oobsize = 400;
break;
default:
mtd->oobsize = 436;
break;
}
extid >>= 2;
/* Calc blocksize */
mtd->erasesize = (128 * 1024) <<
(((extid >> 1) & 0x04) | (extid & 0x03));
busw = 0;
} else {
/* Calc pagesize */
mtd->writesize = 1024 << (extid & 0x03);
extid >>= 2;
/* Calc oobsize */
mtd->oobsize = (8 << (extid & 0x01)) *
(mtd->writesize >> 9);
extid >>= 2;
/* Calc blocksize. Blocksize is multiples of 64KiB */
mtd->erasesize = (64 * 1024) << (extid & 0x03);
extid >>= 2;
/* Get buswidth information */
busw = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0;
}
} else {
/*
* Old devices have chip data hardcoded in the device id table.
*/
mtd->erasesize = type->erasesize;
mtd->writesize = type->pagesize;
mtd->oobsize = mtd->writesize / 32;
busw = type->options & NAND_BUSWIDTH_16;
/*
* Check for Spansion/AMD ID + repeating 5th, 6th byte since
* some Spansion chips have erasesize that conflicts with size
* listed in nand_ids table.
* Data sheet (5 byte ID): Spansion S30ML-P ORNAND (p.39)
*/
if (*maf_id == NAND_MFR_AMD && id_data[4] != 0x00 &&
id_data[5] == 0x00 && id_data[6] == 0x00 &&
id_data[7] == 0x00 && mtd->writesize == 512) {
mtd->erasesize = 128 * 1024;
mtd->erasesize <<= ((id_data[3] & 0x03) << 1);
}
}
/* Get chip options, preserve non chip based options */
chip->options &= ~NAND_CHIPOPTIONS_MSK;
chip->options |= type->options & NAND_CHIPOPTIONS_MSK;
/*
* Check if chip is not a Samsung device. Do not clear the
* options for chips which do not have an extended id.
*/
if (*maf_id != NAND_MFR_SAMSUNG && !type->pagesize)
chip->options &= ~NAND_SAMSUNG_LP_OPTIONS;
ident_done:
/*
* Set chip as a default. Board drivers can override it, if necessary.
*/
chip->options |= NAND_NO_AUTOINCR;
/* Try to identify manufacturer */
for (maf_idx = 0; nand_manuf_ids[maf_idx].id != 0x0; maf_idx++) {
if (nand_manuf_ids[maf_idx].id == *maf_id)
break;
}
/*
* Check, if buswidth is correct. Hardware drivers should set
* chip correct!
*/
if (busw != (chip->options & NAND_BUSWIDTH_16)) {
pr_info("NAND device: Manufacturer ID:"
" 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id,
*dev_id, nand_manuf_ids[maf_idx].name, mtd->name);
pr_warn("NAND bus width %d instead %d bit\n",
(chip->options & NAND_BUSWIDTH_16) ? 16 : 8,
busw ? 16 : 8);
return ERR_PTR(-EINVAL);
}
/* Calculate the address shift from the page size */
chip->page_shift = ffs(mtd->writesize) - 1;
/* Convert chipsize to number of pages per chip -1 */
chip->pagemask = (chip->chipsize >> chip->page_shift) - 1;
chip->bbt_erase_shift = chip->phys_erase_shift =
ffs(mtd->erasesize) - 1;
if (chip->chipsize & 0xffffffff)
chip->chip_shift = ffs((unsigned)chip->chipsize) - 1;
else {
chip->chip_shift = ffs((unsigned)(chip->chipsize >> 32));
chip->chip_shift += 32 - 1;
}
chip->badblockbits = 8;
/* Set the bad block position */
if (mtd->writesize > 512 || (busw & NAND_BUSWIDTH_16))
chip->badblockpos = NAND_LARGE_BADBLOCK_POS;
else
chip->badblockpos = NAND_SMALL_BADBLOCK_POS;
/*
* Bad block marker is stored in the last page of each block
* on Samsung and Hynix MLC devices; stored in first two pages
* of each block on Micron devices with 2KiB pages and on
* SLC Samsung, Hynix, Toshiba and AMD/Spansion. All others scan
* only the first page.
*/
if ((chip->cellinfo & NAND_CI_CELLTYPE_MSK) &&
(*maf_id == NAND_MFR_SAMSUNG ||
*maf_id == NAND_MFR_HYNIX))
chip->bbt_options |= NAND_BBT_SCANLASTPAGE;
else if ((!(chip->cellinfo & NAND_CI_CELLTYPE_MSK) &&
(*maf_id == NAND_MFR_SAMSUNG ||
*maf_id == NAND_MFR_HYNIX ||
*maf_id == NAND_MFR_TOSHIBA ||
*maf_id == NAND_MFR_AMD)) ||
(mtd->writesize == 2048 &&
*maf_id == NAND_MFR_MICRON))
chip->bbt_options |= NAND_BBT_SCAN2NDPAGE;
/* Check for AND chips with 4 page planes */
if (chip->options & NAND_4PAGE_ARRAY)
chip->erase_cmd = multi_erase_cmd;
else
chip->erase_cmd = single_erase_cmd;
/* Do not replace user supplied command function! */
if (mtd->writesize > 512 && chip->cmdfunc == nand_command)
chip->cmdfunc = nand_command_lp;
pr_info("NAND device: Manufacturer ID:"
" 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id, *dev_id,
nand_manuf_ids[maf_idx].name,
chip->onfi_version ? chip->onfi_params.model : type->name);
return type;
}