NAND驱动初始化分析

NAND驱动初始化分析

一. 版本说明

代码分析基于龙芯2K1000平台,内核版本为3.10。

二. NAND驱动初始化分析

龙芯平台的NAND驱动初始化代码入口在 drivers/mtd/nand/ls-nand.c 文件中,通过 module_init() 向内核注册初始化函数 ls_nand_init()

ls_nand_init() 负责注册平台驱动:

static int __init ls_nand_init(void)
{
    pr_info("%s driver initializing\n", DRIVER_NAME);
    return platform_driver_register(&ls_nand_driver);
}

内核启动时,将调用注册的probe回调进行驱动初始化,probe函数的关键代码分析如下:

static int ls_nand_probe(struct platform_device *pdev)
{   
    struct ls2k_nand_plat_data *pdata;
    struct mtd_part_parser_data ppdata;
    struct ls_nand_info *info;
    struct nand_chip *this;
    struct mtd_info *mtd;
    struct resource *r;

    ...
    //从设备树中获取DMA信息等
    ...
    
    pdata = devm_kzalloc(&pdev->dev,
		sizeof(struct ls2k_nand_plat_data),
		GFP_KERNEL);  //为ls2k_nand_plat_data结构体分配存储空间

    //设置相关参数
    pdata->chip_ver = LS2K_VER3;
    pdata->cs = 0;
    pdata->csrdy = 0x88442200;
    pdata->nr_parts = data;
    pdata->enable_arbiter = 1;

    mtd = kzalloc(sizeof(struct mtd_info) + sizeof(struct ls_nand_info), 
        GFP_KERNEL);  //为mtd_info、ls_nand_info结构体分配空间

    //设置相关参数
    info = (struct ls_nand_info *)(&mtd[1]);
    info->pdev = pdev;
    info->chip_version = pdata->chip_ver;
    info->cs = pdata->cs;
    info->csrdy = pdata->csrdy;
    ...
    
    ...
    //获取并保存控制器及DMA的地址 
    ...
    
    ret = ls_nand_init_buff(info);  //初始化DMA Buffer
    
    irq = platform_get_irq(pdev, 0);  //获取中断号
    info->irq = irq;

    ls_nand_init_mtd(mtd, info);  //**初始化NAND操作回调及ECC,详细分析1
    ls_nand_init_info(info);  //初始化部分参数及寄存器
    dma_desc_init(info);  //DMA相关信息初始化
    platform_set_drvdata(pdev, mtd);  //将mtd_info结构保存到platform_device结构中

    //**扫描NAND Flash,详细分析2
	if (nand_scan(mtd, 1)) {
        goto fail_free_io;
    }
    
    //注册MTD设备
    ret = mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0);
    
    return ret;
}

详细分析1:

nand_scan()nand_base.c 中定义,主要负责扫描NAND设备并设置mtd_info 结构体。

int nand_scan(struct mtd_info *mtd, int maxchips)
{
    int ret;

	//检查错误调用
    if (!mtd->owner && caller_is_module()) {...}

    ret = nand_scan_ident(mtd, maxchips, NULL);
    if (!ret)
        ret = nand_scan_tail(mtd);
    return ret;
}

nand_scan_ident() 作为 nand_scan() 的第一阶段,负责读取flash ID并根据它设置MTD参数。

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;

    busw = chip->options & NAND_BUSWIDTH_16;  //获取总线宽度
    nand_set_defaults(chip, busw);  //设置默认回调函数
    
    //**读取flash ID并检查型号是否支持,详细分析1.1
    type = nand_get_flash_type(mtd, chip, busw,
        &nand_maf_id, &nand_dev_id, table);

    ...
    //检查多片flash
    ...
    
    //保存芯片数及空间大小
    chip->numchips = i;
    mtd->size = i * chip->chipsize;

    return 0;
}

nand_scan_tail() 作为 nand_scan() 的第二阶段,负责使用默认值填充为初始化的函数指针及建立坏块表。

int nand_scan_tail(struct mtd_info *mtd)
{
    struct nand_chip *chip = mtd->priv;

    //设置内部oob buffer位置
    chip->oob_poi = chip->buffers->databuf + mtd->writesize;
    
    //未指定ecc layout时,为其设置合适的值
    if (!chip->ecc.layout && (chip->ecc.mode != NAND_ECC_SOFT_BCH)) {
        switch (mtd->oobsize) {
        case 8:
            chip->ecc.layout = &nand_oob_8;
            break;
        case 16:
            chip->ecc.layout = &nand_oob_16;
            break;
        case 64:
            chip->ecc.layout = &nand_oob_64;
            break;
        case 128:
            chip->ecc.layout = &nand_oob_128;
            break;
        default:
            pr_warn("No oob scheme defined for oobsize %d\n",
                   mtd->oobsize);
            BUG();
        }
    }

    //设置默认回调函数
    if (!chip->write_page)
        chip->write_page = nand_write_page;

    ...
    
    switch (chip->ecc.mode) {
    case NAND_ECC_HW_OOB_FIRST:
        ...
    case NAND_ECC_HW:
                 pr_warn("********* ecc.mode = NAND_ECC_HW. ***************** \n");
        if (!chip->ecc.read_page)
            chip->ecc.read_page = nand_read_page_hwecc;
        if (!chip->ecc.write_page)
            chip->ecc.write_page = nand_write_page_hwecc;
        if (!chip->ecc.read_page_raw)
            chip->ecc.read_page_raw = nand_read_page_raw;
        if (!chip->ecc.write_page_raw)
            chip->ecc.write_page_raw = nand_write_page_raw;
        ...
    default:
        ...
    }
    
    //计算剩余的oob空间大小
    chip->ecc.layout->oobavail = 0;
    for (i = 0; chip->ecc.layout->oobfree[i].length
            && i < ARRAY_SIZE(chip->ecc.layout->oobfree); i++)
        chip->ecc.layout->oobavail +=
            chip->ecc.layout->oobfree[i].length;
    mtd->oobavail = chip->ecc.layout->oobavail;

    //设置ecc相关参数
    chip->ecc.steps = mtd->writesize / chip->ecc.size;
    if (chip->ecc.steps * chip->ecc.size != mtd->writesize) {
        pr_warn("Invalid ECC parameters\n");
        BUG();
    }
    chip->ecc.total = chip->ecc.steps * chip->ecc.bytes;

    ...
    
    //填充其余的mtd结构数据
    mtd->type = MTD_NANDFLASH;
    mtd->flags = (chip->options & NAND_ROM) ? MTD_CAP_ROM :
                        MTD_CAP_NANDFLASH;
    mtd->_erase = nand_erase;
	...
    
    if (chip->options & NAND_SKIP_BBTSCAN)
        return 0;

    //建立坏块表
    return chip->scan_bbt(mtd);
}   

详细分析1.1:

nand_get_flash_type() 负责读取flash ID并检查型号是否支持。

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];

    chip->select_chip(mtd, 0);
    chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
    
    //读取制造商和设备ID
    chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);

    *maf_id = chip->read_byte(mtd);
    *dev_id = chip->read_byte(mtd);

    //再次读取ID,如果两次不匹配则忽略此设备
    chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);

    for (i = 0; i < 8; 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);
    }

    //默认在nand_flash_ids结构体中匹配ID
    if (!type)
        type = nand_flash_ids;

    for (; type->name != NULL; type++) {
        if (is_full_id_nand(type)) {
            if (find_full_id_nand(mtd, chip, type, id_data, &busw))
                goto ident_done;
        } else if (*dev_id == type->dev_id) {
                break;
        }
    }
    
    ...
    //根据匹配的结果设置chipsize等部分芯片参数
    ...

    if (!type->pagesize && chip->init_size) {
        //由驱动设置pagesize, oobsize, erasesize
        busw = chip->init_size(mtd, chip, id_data);
    } else if (!type->pagesize) {
        //根据ext id解析参数
        nand_decode_ext_id(mtd, chip, id_data, &busw);
    } else {
        nand_decode_id(mtd, chip, type, id_data, &busw);
    }
    
    ...
        
ident_done:
    
    ...
    
    return type;
}   

注:根据上述分析可知,如需增加支持的flash型号,可以将flash的ID信息加入 nand_flash_ids 结构体,它位于 nand_ids.c 文件中。另外flash的部分参数可能较特殊,可在 nand_decode_ext_id()nand_decode_id() 中加入特殊判断进行配置。

三. 向MTD层注册

ls_nand_probe() 最后,调用 mtd_device_parse_register() 向MTD层注册分区,MTD层相关代码此处不作详细分析。

至此,驱动的初始化过程基本完成。

上一篇:centos修改ip地址快捷脚本


下一篇:Ubuntu 上基于 esp-idf release_v4.2 SDK 搭建 Connectedhomeip(CHIP)的编译环境