Linux下SPI Flash-W25Q64驱动调试

  

1 简介

    W25Q64是华邦公司推出的大容量SPI FLASH产品,其容量为8M。该25Q系列的器件在灵活性和性能方面远远超过普通的串行闪存器件。W25Q64将8M字节的容量分为128个块(block),每个块大小为64K字节,每个块又分为16个扇区(sector),每个扇区4K字节。W25Q64的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。所以,这需要给W25Q64开辟一个至少4K的缓存区,这样必须要求芯片有4K以上的SRAM才能有很好的操作。W25Q64的擦写周期多达10W次,可将数据保存达20年之久,支持2.7~3.6V的电压,支持标准的SPI,还支持双输出/四输出的SPI,最大SPI时钟可达80MHz。

    本文使用的硬件平台: Atmel SAMA5d4,在其平台上添加 W25Q64 芯片(SPI 设备),Linux 内核版本为 kernel-4.9.87,采用 DeviceTree 描述硬件连接信息。

2 硬件连接

  Atmel SAMA5d4是基于ARM Cortex-A5架构的高性能处理器,它上边有3组SPI接口(SPI0、SPI1、SPI2)。具体可以查看处理器芯片手册。

  处理器中的SPI引脚定义
在这里插入图片描述
  W25Q64与处理器的引脚定义及连接关系:

序号W25Q64芯片引脚定义处理器引脚定义
1MOSIPC1
2MISOPC0
3SCLKPC2
4CSPD31

3 驱动调试分析

3.1 理论基础

  先来看一些 flash的理论知识。
1
在这里插入图片描述

图 2.1 MTD设备的层次结构组成

  
    MTD层为NOR FLASH和NAND FLASH设备提供统一接口。MTD将文件系统与底层FLASH存储器进行了隔离。如图2.1所示,MTD设备通常可分为四层,从上到下依次是:设备节点、MTD设备层、MTD原始设备层、硬件驱动层。
    Flash硬件驱动层:(相当于spi driver/i2c driver),Flash硬件驱动层负责对Flash硬件的读、写和擦除操作。MTD设备的Nand Flash芯片的驱动则在drivers/mtd/nand/子目录下,Nor Flash芯片驱动位于drivers/mtd/chips/子目录下。
    MTD原始设备层:(相当于spi master/i2c client),用于描述MTD原始设备的数据结构是mtd_info,它定义了大量的关于MTD的数据和操作函数。其中mtdcore.c: MTD原始设备接口相关实现,mtdpart.c : MTD分区接口相关实现。
    MTD设备层:基于MTD原始设备,linux系统可以定义出MTD的块设备(主设备号31)和字符设备(设备号90)。其中mtdchar.c : MTD字符设备接口相关实现,mtdblock.c : MTD块设备接口相关实现。
    设备节点:通过mknod在/dev子目录下建立MTD块设备节点(主设备号为31)和MTD字符设备节点(主设备号为90)。通过访问此设备节点即可访问MTD字符设备和块设备。

3.2 设备树节点

    根据实际的硬件连接情况修改设备树文件。
    查看设备树文件(.dts 和 .dtsi)中spi0节点的属性;
    
.dtsi文件中:
在这里插入图片描述
引脚定义:
在这里插入图片描述
.dts文件中:
未修改前:
在这里插入图片描述
    从设备树spi0节点的子节点m25p80可知,内核自带的驱动程序中支持了 SPI FLASH芯片m25p80类型的驱动,原compatible属性对应的是型号为“at25df321a”的SPI串行闪存,进入源码文件m25p80.c(路径:\linux-at91\drivers\mtd\devices\m25p80.c),可以搜索到“at25df321a”在 m25p_ids结构体中,同时该结构体也包含了我们使用的SPI芯片w25q64,说明该驱动也可直接用于w25q64。
在这里插入图片描述
    重新配置设备树节点m25p80,我们这里使用的片选引脚为PD31,需要修改cs-gpios属性对应的值。另外还需修改m25p80节点中的compatible属性,修改为w25q64即可使能该flash。后续重新编译设备树文件生成.dtb,随内核一并烧录,就会成功加载w25q64驱动。
    修改后:
在这里插入图片描述

3.3 spi flash驱动流程分析

    这里要把SPI flash设备注册为MTD设备,MTD子系统实现了SPI flash芯片驱动程序,其驱动 Demo 为:

  • drivers/mtd/devices/mtd_dataflash.c
    (*Atmel AT45xxx DataFlash MTD driver for lightweight SPI framework)
  • drivers/mtd/devices/m25p80.c
    (MTD SPI driver for ST M25Pxx (and similar) serial flash chips)

    我们这里使用的是与m25p80相似的flash,所以套用源码文件m25p80.c,并没有对该文件进行修改。通读 m25p80.c 驱动代码,我们可以找出大概的脉络。首先是通过 module_spi_driver 函数注册 m25p80_driver 驱动,其中实现了 probe 和 remove 函数,分别是 m25p_probe 和 m25p_remove。并且填写了一张名为 m25p_ids 的兼容设备表,设备表中包含了"w25q64"。

    下面对驱动的调用过程进行详细分析。

    1) spi flash硬件驱动层部分
        SPI 协议驱动有些类似平台设备驱动:
在这里插入图片描述
    内核将调用 module_spi_driver() 这个宏来注册和卸载 spi 设备,这个 module_spi_driver 是专门针对于 spi 架构定义的。

    module_spi_driver
在这里插入图片描述
    继续追看 module_driver() 这个宏 ,它将 spi_register/unregister_driver() 与 module_init 和 module_exit 封装了起来。所以说实际上 module_spi_driver() 和 module_init/exit 几乎是没有区别的。之所以直接将其封装的原因是因为这个 SPI 设备本身是不可插拔的,也就不需要 init 和 exit 的过程,系统上电就直接注册了。

    module_driver -> module_init
在这里插入图片描述
spi_register_driver
在这里插入图片描述
__spi_register_driver
在这里插入图片描述
driver_register     * driver_register - register driver with bus
在这里插入图片描述
直接看 bus_add_driver
在这里插入图片描述
这里只截取一部分,最后调用的是driver_attach
在这里插入图片描述
在bus_for_each_dev执行了__driver_attach(dev, data),查看 __driver_attach
在这里插入图片描述
       在static int__driver_attach(struct device *dev, void *data)中先调用了driver_match_device(drv,dev),用于匹配,成功才继续执行,否则直接返回了。driver_match_device(drv, dev)中:
在这里插入图片描述
       如果match函数的指针不为空,则执行此bus的match函数,这也就是为什么老是说总线负责匹配设备和驱动了。这里也传递了参数struct device *dev。

       匹配成功继续执行,driver_attach(dev, data)中有个driver_probe_device(drv,dev),继续跟踪:
在这里插入图片描述
       有个really_probe(dev,drv),linux经常将一个函数传递给另一函数,后一个函数就是在前一个函数前加“do
”、“really
”、“__”,还经常宏定义或inline。
(截取部分代码)
在这里插入图片描述
在这里插入图片描述
       这里如果有总线上的probe函数就调用总线的probe函数,如果没有则调用drv的probe函数。

m25p_probe

/*
 * board specific setup should have ensured the SPI clock used here
 * matches what the READ command supports, at least until this driver
 * understands FAST_READ (for clocks over 25 MHz).
 */
static int m25p_probe(struct spi_device *spi)
{
    struct flash_platform_data  *data;
    struct m25p *flash;
    struct spi_nor *nor;
    struct spi_nor_hwcaps hwcaps = {
        .mask = (SNOR_HWCAPS_READ |
             SNOR_HWCAPS_READ_FAST |
             SNOR_HWCAPS_PP),
    };
    char *flash_name;
    int ret;

    data = dev_get_platdata(&spi->dev);

    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->write_reg = m25p80_write_reg;
    nor->read_reg = m25p80_read_reg;

    nor->dev = &spi->dev;
    spi_nor_set_flash_node(nor, spi->dev.of_node);
    nor->priv = flash;

    spi_set_drvdata(spi, flash);
    flash->spi = spi;

    if (spi->mode & SPI_RX_QUAD) {
        hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;

        if (spi->mode & SPI_TX_QUAD)
            hwcaps.mask |= (SNOR_HWCAPS_READ_1_4_4 |
                    SNOR_HWCAPS_PP_1_1_4 |
                    SNOR_HWCAPS_PP_1_4_4);
    } else if (spi->mode & SPI_RX_DUAL) {
        hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;

        if (spi->mode & SPI_TX_DUAL)
            hwcaps.mask |= SNOR_HWCAPS_READ_1_2_2;
    }

    if (data && data->name)
        nor->mtd.name = data->name;

    /* For some (historical?) reason many platforms provide two different
     * names in flash_platform_data: "name" and "type". Quite often name is
     * set to "m25p80" and then "type" provides a real chip name.
     * If that's the case, respect "type" and ignore a "name".
     */
    if (data && data->type)
        flash_name = data->type;
    else if (!strcmp(spi->modalias, "spi-nor"))
        flash_name = NULL; /* auto-detect */
    else
        flash_name = spi->modalias;

    ret = spi_nor_scan(nor, flash_name, &hwcaps);
    if (ret)
        return ret;

    return mtd_device_register(&nor->mtd, data ? data->parts : NULL,
                   data ? data->nr_parts : 0);
}

       在 m25p_probe 函数中指定了 m25p80_read、m25p80_write 和m25p80_erase 等文件操作函数,当应用程序使用 read、write、ioctl 等接口操作时最终会调用到这里。那 open 和 close 函数呢? 我们把 W25Q64 注册成 MTD 设备了,所以另外一些操作函数在 drivers/mtd/mtdchar.c 中定义。实际上,它不仅有 mtdchar_open、mtdchar_close 等函数,还有 mtdchar_read 和 mtdchar_write 函数,而它们会调用 m25p80.c 中的 m25p80_read 和 m25p80_write 函数。
在这里插入图片描述

在这里插入图片描述

4 编译及开机验证

  
  (1)重新编译 image 和 dtb,更新系统后重新启动,进入系统。
输入命令 dmesg | grep spi,看到如下内容则说明内核已经探测到 w25q64 设备,把设备和驱动程序匹配上了。
在这里插入图片描述

  (2)查看设备文件:
在这里插入图片描述
    可以看到/dev/mtd1 之类的设备节点,其中 /dev/mtd1 是字符设备,/dev/mtdblock1 是块设备,/dev/mtd1ro 是只读字符设备。

  • cat /proc/mtd
  • cat /proc/partitions
  • mtd_debug info /dev/mtd7
  • mtdinfo /dev/mtd7
    在这里插入图片描述
        从上面的命令返回结果可以看到,mtd7对应的设备即为Nor FLASH w25q64。

     (3)挂载 MTD 设备

    因为我们把 SPI Flash 注册成 MTD 设备了,因此,我们可以通过 MTD 子系统和文件系统对其进行操作。

    首先对 Flash 进行格式化,然后挂载,接着就可以通过文件系统操作:

    $ mkfs.vfat /dev/mtdblock7
    $ mount -t vfat /dev/mtdblock7 /home/root/w25q64
    $ cd /home/root/w25q64
    $ echo “Hello W25Q64” > file.txt
    $ sync

    然后断电重启,看看文件及其内容是否还在,并且与断电前一致。


mount命令:
在这里插入图片描述


    (4)“读写擦”测试

     除了通过文件系统操作 W25Q64 设备外,也可以直接打开 /dev/mtd1 设备节点对其进行操作。

     1)通过 mtd_debug 工具操作设备节点。
在这里插入图片描述
     2)通过C语言编写测试程序进行 “读写擦”操作。

    可参考该测试程序进行修改。
https://github.com/luhuadong/Linux-programming/blob/master/driver/mtd/test/mtd_go.c

你可以使用STM32F407的SPI2来驱动W25Q64闪存。首先,确保你已经正确连接了W25Q64和STM32F407的SPI2引脚。然后,按照以下步骤进行驱动配置: 1. 初始化SPI2: 使用CubeMX或手动配置SPI2,并设置合适的时钟、模式、数据大小等参数。 2. 配置GPIO引脚:将SPI2的NSS、SCK、MISO和MOSI引脚连接到W25Q64上的对应引脚。 3. 编写读写函数:根据W25Q64的数据手册,编写读取和写入函数。这些函数将使用SPI2进行通信,并发送相应的指令和地址来读取或写入数据。 以下是一个简单的示例代码,用于向W25Q64写入一个字节数据: ```c #include "stm32f4xx_hal.h" SPI_HandleTypeDef hspi2; void SPI2_Init(void) { /* 初始化SPI2 */ hspi2.Instance = SPI2; hspi2.Init.Mode = SPI_MODE_MASTER; hspi2.Init.Direction = SPI_DIRECTION_2LINES; hspi2.Init.DataSize = SPI_DATASIZE_8BIT; hspi2.Init.CLKPolarity = SPI_POLARITY_LOW; hspi2.Init.CLKPhase = SPI_PHASE_1EDGE; hspi2.Init.NSS = SPI_NSS_SOFT; hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi2.Init.TIMode = SPI_TIMODE_DISABLE; hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi2.Init.CRCPolynomial = 10; HAL_SPI_Init(&hspi2); /* 启用SPI2外设 */ __HAL_SPI_ENABLE(&hspi2); } void W25Q64_WriteByte(uint8_t byte, uint32_t address) { /* 等待SPI2空闲 */ while ((SPI2->SR & SPI_SR_BSY) != 0); /* 选择W25Q64 */ HAL_GPIO_WritePin(GPIOx, GPIO_PIN_x, GPIO_PIN_RESET); /* 发送写入指令和地址 */ HAL_SPI_Transmit(&hspi2, &WRITE_ENABLE, 1, HAL_MAX_DELAY); HAL_SPI_Transmit(&hspi2, (uint8_t*)&address, 3, HAL_MAX_DELAY); /* 发送数据 */ HAL_SPI_Transmit(&hspi2, &byte, 1, HAL_MAX_DELAY); /* 取消选择W25Q64 */ HAL_GPIO_WritePin(GPIOx, GPIO_PIN_x, GPIO_PIN_SET); } ``` 这只是一个简单的示例代码,你还需要根据W25Q64的具体指令和寄存器来编写其他功能的读写函数。希望对你有所帮助!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

heat.huang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值