当前位置:首页 > 嵌入式 > 嵌入式分享
[导读]在嵌入式Linux系统中,SPI NOR Flash常用于存放Bootloader、内核镜像、文件系统及关键配置数据。其容量适中(通常4MB~64MB)、读取速度快、接口简单,但驱动开发中最棘手的并非读写本身,而是如何保证在意外掉电、擦写磨损等场景下的数据可靠性。本文以W25Q64(8MB)为例,展示基于MTD(Memory Technology Device)子系统的SPI NOR Flash驱动开发全流程。


在嵌入式Linux系统中,SPI NOR Flash常用于存放Bootloader、内核镜像、文件系统及关键配置数据。其容量适中(通常4MB~64MB)、读取速度快、接口简单,但驱动开发中最棘手的并非读写本身,而是如何保证在意外掉电、擦写磨损等场景下的数据可靠性。本文以W25Q64(8MB)为例,展示基于MTD(Memory Technology Device)子系统的SPI NOR Flash驱动开发全流程。

一、硬件连接与设备树配置

SPI NOR Flash通过四线SPI接口(CS、CLK、MOSI、MISO)连接SoC。在设备树中,将其描述为SPI总线下的从设备:

&spi1 {

   status = "okay";

   pinctrl-names = "default";

   pinctrl-0 = <&spi1_pins>;


   flash: w25q64@0 {

       compatible = "winbond,w25q64", "jedec,spi-nor";

       reg = <0>;

       spi-max-frequency = <80000000>;

       m25p,fast-read;

       partitions {

           compatible = "fixed-partitions";

           #address-cells = <1>;

           #size-cells = <1>;

           bootloader@0 {

               label = "u-boot";

               reg = <0x000000 0x100000>;  // 1MB

           };

           kernel@100000 {

               label = "kernel";

               reg = <0x100000 0x500000>;  // 5MB

           };

           rootfs@600000 {

               label = "rootfs";

               reg = <0x600000 0x200000>;  // 2MB

           };

       };

   };

};

compatible属性使用"jedec,spi-nor"可以自动匹配内核自带的SPI NOR驱动框架(drivers/mtd/spi-nor),无需从头编写。但如果需要定制化操作(如特殊写保护策略),则需编写独立的驱动。

二、驱动框架与probe函数

内核MTD子系统为NOR Flash提供了统一的抽象层。驱动只需实现struct mtd_info中的必要回调,并调用mtd_device_register注册分区。

#include <linux/module.h>

#include <linux/spi/spi.h>

#include <linux/mtd/mtd.h>

#include <linux/mtd/partitions.h>


struct nor_flash {

   struct spi_device *spi;

   struct mtd_info mtd;

   u8 *buffer;  // 临时缓冲区

};


static int nor_read(struct mtd_info *mtd, loff_t from, size_t len,

                   size_t *retlen, u_char *buf) {

   struct nor_flash *flash = mtd_to_nor(mtd);

   struct spi_transfer t = {

       .tx_buf = flash->buffer,

       .rx_buf = buf,

       .len = 5 + len,  // 指令+地址+数据

   };

   // 构造READ命令 (0x03) + 3字节地址

   flash->buffer[0] = 0x03;

   flash->buffer[1] = (from >> 16) & 0xff;

   flash->buffer[2] = (from >> 8) & 0xff;

   flash->buffer[3] = from & 0xff;

   spi_sync_transfer(flash->spi, &t, 1);

   *retlen = len;

   return 0;

}


static int nor_write(struct mtd_info *mtd, loff_t to, size_t len,

                    size_t *retlen, const u_char *buf) {

   struct nor_flash *flash = mtd_to_nor(mtd);

   // 先写使能 (WREN 0x06)

   // 再发页编程命令 (0x02) + 地址 + 数据

   // 等待状态寄存器BUSY位清零

   // 实际代码需处理跨页写、擦除等

   return 0;

}


static int nor_erase(struct mtd_info *mtd, struct erase_info *instr) {

   // 发送扇区擦除命令 (0x20) 或 块擦除 (0xD8)

   // 等待擦除完成

   return 0;

}


static int nor_probe(struct spi_device *spi) {

   struct nor_flash *flash;

   flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL);

   flash->spi = spi;

   flash->mtd.type = MTD_NORFLASH;

   flash->mtd.flags = MTD_CAP_NORFLASH;

   flash->mtd.size = 8 * 1024 * 1024;  // 8MB

   flash->mtd.erasesize = 4096;         // 4KB扇区

   flash->mtd.writesize = 1;

   flash->mtd._read = nor_read;

   flash->mtd._write = nor_write;

   flash->mtd._erase = nor_erase;

   mtd_device_register(&flash->mtd, NULL, 0);

   return 0;

}


static int nor_remove(struct spi_device *spi) {

   struct nor_flash *flash = spi_get_drvdata(spi);

   mtd_device_unregister(&flash->mtd);

   return 0;

}


static struct spi_driver nor_driver = {

   .driver = {

       .name = "my_nor_flash",

       .of_match_table = of_match_ptr(of_match_table),

   },

   .probe = nor_probe,

   .remove = nor_remove,

};

module_spi_driver(nor_driver);

三、可靠性保障措施

写保护:NOR Flash通常有状态寄存器中的BP位控制区块保护。驱动在初始化时应读取并设置保护范围,防止意外擦写关键区域。

掉电保护:页编程和擦除操作期间掉电可能导致数据损坏。驱动应在操作前关闭中断,操作完成后恢复;对于关键数据,可写入两次并校验。

磨损均衡:对于频繁更新的配置区,驱动层可做简单的动态映射,但通常交由上层UBIFS或JFFS2处理。

错误处理:每次擦写后读取状态寄存器,确认操作成功;若多次失败,标记坏块(NOR坏块极少,但依然存在)。

四、测试与调试

驱动加载后,/dev/mtd0等设备节点出现。使用mtd_debug工具验证读写:

# 擦除整个设备

flash_erase /dev/mtd0 0 0

# 写入数据

dd if=/dev/urandom of=/tmp/test.bin bs=1024 count=1

mtd_debug write /dev/mtd0 0 1024 /tmp/test.bin

# 读出对比

mtd_debug read /dev/mtd0 0 1024 /tmp/read.bin

cmp /tmp/test.bin /tmp/read.bin

若一致,说明驱动基本功能正常。进一步可用mtd-utils中的flashcp测试擦写循环稳定性。

五、写在最后

嵌入式Linux下SPI NOR Flash驱动的核心是围绕MTD子系统实现_read、_write、_erase三个回调,并处理好SPI通信时序与状态寄存器轮询。可靠性则依赖于写保护配置、掉电防护和操作确认。实际项目中,大多数情况下直接使用内核的spi-nor通用驱动即可,只有在需要特殊分区策略或硬件定制时才需自行开发。掌握这套流程后,无论是更换Flash型号还是调整分区布局,都能快速适配。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭