Norflash 驱动

一.Norflash简单介绍
从名称上来看norflash便可一窥其特点,也即是异或门构成的存储单元,相比于Nandflash(与非门)其特点为以下几个方面。
1.Norflash写入前必须进行擦除操作,NOR读的速度比NAND稍快
2.NOR带有SRAM接口,有足够的地址引脚来寻址,可以很容易的存取数据,NAND器件试验复杂的i/o穿行存取数据,各个产品或厂商方法可能各有不同。
3.NOR可以像其他存储器那样链接,并可以在上面直接运行代码,NAND必须写入驱动程序,才能执行其他操作。

二.Norflash Linux驱动
这一部分主要结合自己开发的SPANSION S25FL132K芯片做实例做相应的说明。简单做一下背景说明,笔者开发的车机项目应用到这款芯片,按领导所说在我开发之前这款芯片并未发挥其价值,也就是说该芯片可有可无,不用该芯片可以给每台产品省几块大洋,这样一来一年若以十万台的销量来算确实能省下不少钱。该芯片主要设计为存储uboot,也就是开机引导程序,我们项目是基于飞思卡尔imx6系列芯片,该芯片支持多设备boot,有norflash, nandflash, sd card, emmc等,如果单纯的装载引导程序norflash完全可以被机器已有的其他硬件所取代。于是笔者开发的feature就应运而生:feature1.支持设备能够在多启动源对kernel进行引导启动,当norflash中的uboot被破坏时,设备能够从其他的启动源重新启机,这里的破坏有以下几种可能:在向uboot升级的过程中,也就是说我们对norflash进行擦除写操作的过程,突然对设备进行断电,这一情况还真出现在4s店的哪些操作人员,对设备进行升级中断电,这样一来引导程序没有烧写完整,设备当然不能正常起机,第二种情况也就是我最反感领导一直霹雳呼啦的说的电磁干扰,什么来了一个读信号,在电磁干扰情况下变成写信号,把norflash中的uboot给破坏了,虽然这概率很低但是用户数量到了,而且车机属于耐用品,每次开机都会读uboot,这样一年下来…….总之到目前为止第二种情况还没有碰到过。feature2.充分挖掘norflash芯片的功能,将保护功能做到极致。
关于feature1这里就不做过多笔墨,其特色和使用的CPU相关,feature2是我们重点研究的对象。
norflash使用的是SPI接口,因此其通信还是遵循SPI协议,底层驱动也依赖于SPI接口。目前几乎大多数norflash都是遵循JEDEC(Joint Electron Device Engineering Council) standard manufacturer,这样就有很大的一个有优势就是linux底层SPI就能做成一个通用的驱动协议,开发者甚至不需要做任何的改动就能够直接使用其底层大部分驱动代码,除非是做一些定制特色的功能。下面我先给出S25FL132K芯片的一些常用的命令。
根据上表的一些命令可以得知和norflash进行交互实质上通过这些命令实现的,包括操作寄存器,擦除,写入等。大致清楚这些命令后我们再来看一下关于该芯片的一下保护feature.
这里写图片描述
这里我不过多的解读该寄存器功能,简单的说一下我们所应用到的features,norflash保护功能是我们关注的,block protection 和status register protection.通俗点讲就是,status register决定block protection包括保护块的大小等,同时norflash status register的保护又是受WP管脚电平和相应status register联合控制。当SRP0=1 SRP1=0,WP为低电平那么状态寄存器就无法写入,而同时如果将CMP写入1那么正片norflash将处于只读状态,既不能擦除,也不能写入,这正是我们所想要利用的features.

有了上面一些基本知识后我们在来到Kernel里看看底层到底如何操作norflash,norflash通用驱动在driver/mtd/m25p80.c大多数norflash驱动都是基于该c文件改进而来,而该c文件也主要是利用了SPI的一些通用接口。

static int m25p_probe(struct spi_device *spi)
{
    struct mtd_part_parser_data ppdata;
    struct flash_platform_data  *data;
    struct m25p *flash;
    struct spi_nor *nor;
    enum read_mode mode = SPI_NOR_NORMAL;
    int ret;

    flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL);
    if (!flash)
        return -ENOMEM;

    nor = &flash->spi_nor;

    /* install the hooks */
    nor->read = m25p80_read;
    nor->write = m25p80_write;
    nor->erase = m25p80_erase;
    nor->write_reg = m25p80_write_reg;
    nor->read_reg = m25p80_read_reg;

    nor->dev = &spi->dev;
    nor->mtd = &flash->mtd;
    nor->priv = flash;

    spi_set_drvdata(spi, flash);
    flash->mtd.priv = nor;
    flash->spi = spi;

    if (spi->mode & SPI_RX_QUAD)
        mode = SPI_NOR_QUAD;
    ret = spi_nor_scan(nor, spi_get_device_id(spi), mode);
    if (ret)
        return ret;

    data = dev_get_platdata(&spi->dev);
    ppdata.of_node = spi->dev.of_node;

    return mtd_device_parse_register(&flash->mtd, NULL, &ppdata,
            data ? data->parts : NULL,
            data ? data->nr_parts : 0);
}

该文件主要封装了一些常用的norflash操作,如erase ,write register, read register等,并未完成封装到mtd层的读写擦除等功能,这些功能封装在spi-nor.c中,说白了前面的一些列操作还是属于SPI操作范畴,并未真正对norflash进行实质性操作。真正的操作在spi-nor.c当然有些底层驱动还是在m25p80.c中。

int spi_nor_scan(struct spi_nor *nor, const struct spi_device_id *id,
            enum read_mode mode)
{
    struct flash_info       *info;
    struct flash_platform_data  *data;
    struct device *dev = nor->dev;
    struct mtd_info *mtd = nor->mtd;
    struct device_node *np = dev->of_node;
    int ret;
    int i;

    ret = spi_nor_check(nor);
    if (ret)
        return ret;

    /* Platform data helps sort out which chip type we have, as
     * well as how this board partitions it.  If we don't have
     * a chip ID, try the JEDEC id commands; they'll work for most
     * newer chips, even if we don't recognize the particular chip.
     */
    data = dev_get_platdata(dev);
    if (data && data->type) {
        const struct spi_device_id *plat_id;

        for (i = 0; i < ARRAY_SIZE(spi_nor_ids) - 1; i++) {
            plat_id = &spi_nor_ids[i];
            if (strcmp(data->type, plat_id->name))
                continue;
            break;
        }

        if (i < ARRAY_SIZE(spi_nor_ids) - 1)
            id = plat_id;
        else
            dev_warn(dev, "unrecognized id %s\n", data->type);
    }

    info = (void *)id->driver_data;

    if (info->jedec_id) {
        const struct spi_device_id *jid;

        jid = jedec_probe(nor);
        if (IS_ERR(jid)) {
            return PTR_ERR(jid);
        } else if (jid != id) {
            /*
             * JEDEC knows better, so overwrite platform ID. We
             * can't trust partitions any longer, but we'll let
             * mtd apply them anyway, since some partitions may be
             * marked read-only, and we don't want to lose that
             * information, even if it's not 100% accurate.
             */
            dev_warn(dev, "found %s, expected %s\n",
                 jid->name, id->name);
            id = jid;
            info = (void *)jid->driver_data;
        }
    }

    mutex_init(&nor->lock);

    /*
     * Atmel, SST and Intel/Numonyx serial nor tend to power
     * up with the software protection bits set
     */

    if (JEDEC_MFR(info->jedec_id) == CFI_MFR_ATMEL ||
        JEDEC_MFR(info->jedec_id) == CFI_MFR_INTEL ||
        JEDEC_MFR(info->jedec_id) == CFI_MFR_SST) {
        write_enable(nor);
        write_sr(nor, 0);
    }

    if (data && data->name)
        mtd->name = data->name;
    else
        mtd->name = dev_name(dev);

    mtd->type = MTD_NORFLASH;
    mtd->writesize = 1;
    mtd->flags = MTD_CAP_NORFLASH;
    mtd->size = info->sector_size * info->n_sectors;
    mtd->_erase = spi_nor_erase;
    mtd->_read = spi_nor_read;

    /* nor protection support for STmicro chips */
    if (JEDEC_MFR(info->jedec_id) == CFI_MFR_ST) {
        mtd->_lock = spi_nor_lock;
        mtd->_unlock = spi_nor_unlock;
    }

    /* sst nor chips use AAI word program */
    if (info->flags & SST_WRITE)
        mtd->_write = sst_write;
    else
        mtd->_write = spi_nor_write;

    /* prefer "small sector" erase if possible */
    if (info->flags & SECT_4K) {
        nor->erase_opcode = OPCODE_BE_4K;
        mtd->erasesize = 4096;
    } else if (info->flags & SECT_4K_PMC) {
        nor->erase_opcode = OPCODE_BE_4K_PMC;
        mtd->erasesize = 4096;
    } else {
        nor->erase_opcode = OPCODE_SE;
        mtd->erasesize = info->sector_size;
    }

    if (info->flags & SPI_NOR_NO_ERASE)
        mtd->flags |= MTD_NO_ERASE;

    mtd->dev.parent = dev;
    nor->page_size = info->page_size;
    mtd->writebufsize = nor->page_size;

    if (np) {
        /* If we were instantiated by DT, use it */
        if (of_property_read_bool(np, "m25p,fast-read"))
            nor->flash_read = SPI_NOR_FAST;
        else
            nor->flash_read = SPI_NOR_NORMAL;
    } else {
        /* If we weren't instantiated by DT, default to fast-read */
        nor->flash_read = SPI_NOR_FAST;
    }

    /* Some devices cannot do fast-read, no matter what DT tells us */
    if (info->flags & SPI_NOR_NO_FR)
        nor->flash_read = SPI_NOR_NORMAL;

    /* Quad/Dual-read mode takes precedence over fast/normal */
    if (mode == SPI_NOR_QUAD && info->flags & SPI_NOR_QUAD_READ) {
        ret = set_quad_mode(nor, info->jedec_id);
        if (ret) {
            dev_err(dev, "quad mode not supported\n");
            return ret;
        }
        nor->flash_read = SPI_NOR_QUAD;
    } else if (mode == SPI_NOR_DUAL && info->flags & SPI_NOR_DUAL_READ) {
        nor->flash_read = SPI_NOR_DUAL;
    }

    /* Default commands */
    switch (nor->flash_read) {
    case SPI_NOR_QUAD:
        nor->read_opcode = OPCODE_QUAD_READ;
        break;
    case SPI_NOR_DUAL:
        nor->read_opcode = OPCODE_DUAL_READ;
        break;
    case SPI_NOR_FAST:
        nor->read_opcode = OPCODE_FAST_READ;
        break;
    case SPI_NOR_NORMAL:
        nor->read_opcode = OPCODE_NORM_READ;
        break;
    default:
        dev_err(dev, "No Read opcode defined\n");
        return -EINVAL;
    }

    nor->program_opcode = OPCODE_PP;

    if (info->addr_width)
        nor->addr_width = info->addr_width;
    else if (mtd->size > 0x1000000) {
        /* enable 4-byte addressing if the device exceeds 16MiB */
        nor->addr_width = 4;
        if (JEDEC_MFR(info->jedec_id) == CFI_MFR_AMD) {
            /* Dedicated 4-byte command set */
            switch (nor->flash_read) {
            case SPI_NOR_QUAD:
                nor->read_opcode = OPCODE_QUAD_READ_4B;
                break;
            case SPI_NOR_DUAL:
                nor->read_opcode = OPCODE_DUAL_READ_4B;
                break;
            case SPI_NOR_FAST:
                nor->read_opcode = OPCODE_FAST_READ_4B;
                break;
            case SPI_NOR_NORMAL:
                nor->read_opcode = OPCODE_NORM_READ_4B;
                break;
            }
            nor->program_opcode = OPCODE_PP_4B;
            /* No small sector erase for 4-byte command set */
            nor->erase_opcode = OPCODE_SE_4B;
            mtd->erasesize = info->sector_size;
        } else
            set_4byte(nor, info->jedec_id, 1);
    } else {
        nor->addr_width = 3;
    }

    nor->read_dummy = spi_nor_read_dummy_cycles(nor);

    dev_info(dev, "%s (%lld Kbytes)\n", id->name,
            (long long)mtd->size >> 10);

    dev_dbg(dev,
        "mtd .name = %s, .size = 0x%llx (%lldMiB), "
        ".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n",
        mtd->name, (long long)mtd->size, (long long)(mtd->size >> 20),
        mtd->erasesize, mtd->erasesize / 1024, mtd->numeraseregions);

    if (mtd->numeraseregions)
        for (i = 0; i < mtd->numeraseregions; i++)
            dev_dbg(dev,
                "mtd.eraseregions[%d] = { .offset = 0x%llx, "
                ".erasesize = 0x%.8x (%uKiB), "
                ".numblocks = %d }\n",
                i, (long long)mtd->eraseregions[i].offset,
                mtd->eraseregions[i].erasesize,
                mtd->eraseregions[i].erasesize / 1024,
                mtd->eraseregions[i].numblocks);
    return 0;
}
EXPORT_SYMBOL_GPL(spi_nor_scan);

这段代码也就是向mtd层封装函数,一直于我们可以利用上层函数轻松能够实现读写擦除等常规功能。至此其实norflash的常规功能也就基本差不多,但是距离我们想要的features还有一段距离。
上面说到要想实现norflash芯片的保护功能,有两个问题,一是向状态寄存器相应的位写入数值,二是要对WP管脚进行控制。向状态寄存器写入值相对较容易,只要遵循SPI的操作规范向寄存器写入数值是没有任何问题的。

static int wp_setlow(void)
{
    int  err;
    err = gpio_is_valid(84);
    if(!err){
        printk("AJ: GPIO invalid err %d\n", err);
        return err;
    }
//  value = gpio_get_value(84);
//  printk("AJ: GPIO_wp value %d\n", value);
    err = gpio_request(84, "wpgpio");
    if(err){
        if(err == -16)
            printk("AJ: GPIO has already requested\n ");
        else{
            printk("AJ: request err %d\n", err);
            return err;
        }
    }
    err = gpio_direction_output(84,0);
    if(!err){
        printk("AJ: gpio direction err %d\n", err);
        return err;
    }
    gpio_set_value(84, 0);
    gpio_free(84);
    return 0;
}

static int wp_sethigh(void)
{
    int err;
    err = gpio_is_valid(84);
    if(!err){
        printk("AJ: GPIO invalid err %d\n", err);
        return err;
    }
//  value = gpio_get_value(84);
//  printk("AJ: GPIO_wp value %d\n", value);
    err = gpio_request(84, "wp-gpio");
    if(err){
        if(err == -16)
            printk("AJ: GPIO has already requested\n ");
        else{
            printk("AJ: request err %d\n", err);
            return err;
        }
    }
    err = gpio_direction_output(84,1);
    if(!err){
        printk("AJ: gpio direction err %d\n", err);
        return err;
    }
    gpio_set_value(84, 1);
    gpio_free(84);
    return 0;
}

static int wp_disable(void)
{
    struct mtd_info *mtd = NULL;
    struct spi_nor *nor = NULL;
    u8 cmd_buf[8];
    int  err;
    u8 val;
    mtd = get_mtd_device(NULL, 0);
    if (IS_ERR(mtd)) {
        err = PTR_ERR(mtd);
        pr_err("AJ: cannot get MTD device\n");
        return err;
    }
    nor = mtd->priv;
    err = m25p80_read_reg(nor, 0x05, &val, 1);

    printk("AJ:status reg1: %x, err=%d\n", val, err);

    err = m25p80_read_reg(nor, 0x35, &val, 1);

    printk("AJ:status reg2: %x, err=%d\n", val, err);
//  err = m25p80_read_reg(nor, 0x33, &val, 1);
//  
//  printk("AJ:status reg3: %x, err=%d\n", val, err);
    cmd_buf[0] = 0x00;
    cmd_buf[1] = 0x00;
    //write enable 0x06  disable 0x04
    //read regiter1 0x05 0x35 0x33
    //write status register 0x01
    //status register write enable voilate 0x50
    err = m25p80_write_reg(nor, 0x06,NULL,0, 0);
    if(err){
        printk("AJ: norflash reg write err:%d\n", err);
        return err;
    }
    err = m25p80_write_reg(nor, 0x01,cmd_buf,2, 0);
    if(err){
        printk("AJ: norflash reg write err:%d\n", err);
        return err;
    }

//  err = m25p80_read_reg(nor, 0x05, &val, 1);

//  printk("AJ:status reg1: %x, err=%d\n", val, err);

//  err = m25p80_read_reg(nor, 0x35, &val, 1);
//  
//  printk("AJ:status reg2: %x, err=%d\n", val, err);
//  err = m25p80_read_reg(nor, 0x33, &val, 1);
//  
//  printk("AJ:status reg3: %x, err=%d\n", val, err);

    err = m25p80_write_reg(nor, 0x04,NULL,0, 0);
    if(err){
        printk("AJ: norflash reg write err:%d\n", err);
        return err;
    }
    return 0;
}

static int wp_enable(void)
{
    struct mtd_info *mtd = NULL;
    struct spi_nor *nor = NULL;
    u8 cmd_buf[8];
    int err;
    u8 val;
    mtd = get_mtd_device(NULL, 0);
    if (IS_ERR(mtd)) {
        err = PTR_ERR(mtd);
        pr_err("AJ: cannot get MTD device\n");
        return err;
    }
    nor = mtd->priv;
//  err = m25p80_read_reg(nor, 0x05, &val, 1);

//  printk("AJ:status reg1: %x, err=%d\n", val, err);

//  err = m25p80_read_reg(nor, 0x35, &val, 1);
//  
//  printk("AJ:status reg2: %x, err=%d\n", val, err);
//  err = m25p80_read_reg(nor, 0x33, &val, 1);
//  
//  printk("AJ:status reg3: %x, err=%d\n", val, err);
    cmd_buf[0] = 0x80;
    cmd_buf[1] = 0x40;
    //write enable 0x06  disable 0x04
    //read regiter1 0x05 0x35 0x33
    //write status register 0x01
    //status register write enable voilate 0x50
    err = m25p80_write_reg(nor, 0x06,NULL,0, 0);
    if(err){
        printk("AJ: norflash reg write err:%d\n", err);
        return err;
    }
    err = m25p80_write_reg(nor, 0x01,cmd_buf,2, 0);
    if(err){
        printk("AJ: norflash reg write err:%d\n", err);
        return err;
    }


//  err = m25p80_read_reg(nor, 0x05, &val, 1);

//  printk("AJ:status reg1: %x, err=%d\n", val, err);

//  err = m25p80_read_reg(nor, 0x35, &val, 1);
//  
//  printk("AJ:status reg2: %x, err=%d\n", val, err);
//  err = m25p80_read_reg(nor, 0x33, &val, 1);
//  
//  printk("AJ:status reg3: %x, err=%d\n", val, err);

    err = m25p80_write_reg(nor, 0x04,NULL,0, 0);
    if(err){
        printk("AJ: norflash reg write err:%d\n", err);
        return err;
    }
    return 0;
}

static int wp_check(int *flag)
{
    struct mtd_info *mtd = NULL;
    struct spi_nor *nor = NULL;
    int  err;
    u8 reg1, reg2;
    mtd = get_mtd_device(NULL, 0);
    if (IS_ERR(mtd)) {
        err = PTR_ERR(mtd);
        pr_err("AJ: cannot get MTD device\n");
        return err;
    }
    nor = mtd->priv;
    err = m25p80_read_reg(nor, 0x05, &reg1, 1);
    if(err){
        printk("AJ:status reg1: %x, err=%d\n", reg1, err);
        return err;
    }
    err = m25p80_read_reg(nor, 0x35, &reg2, 1);
    if(err){
        printk("AJ:status reg2: %x, err=%d\n", reg2, err);
        return err;
    }

    if( reg1 & 0x80 && reg2 & 0x40){
        *flag = 0;
    }
    else{
        *flag = 1;
    }
    return err;
}

上面一部分代码是自己调试以及测试的代码,主要实现功能包括这种GPIO管脚拉高拉低,保护寄存器的读写等。寄存器的读写同时还和norflash管脚的高低有关联,因此在具体实际操作时应当设置相应电平。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值