OK335xS GPMC nand device register hacking

/*********************************************************************************
 *              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;
}

 

posted on 2015-09-02 19:38  zengjf  阅读(978)  评论(0编辑  收藏  举报

导航