Linux kernel调试 SPI NORFLASH--W25Q128

W25Q128介绍

W25Q128 是华邦公司推出的一款 SPI 接口的 NOR Flash 芯片,其存储空间为 128Mbit,相当于 16M 字节。W25Q128 可以支持 SPI 的模式 0 和模式 3,也就是 CPOL=0/CPHA=0 和CPOL=1/CPHA=1 这两种模式。
    
Flash 写入数据时和 EEPROM 类似,不能跨页写入,一次最多写入一页,W25Q128的一页是 256 字节。写入数据一旦跨页,必须在写满上一页的时候,等待 Flash 将数据从缓存搬移到非易失区,重新再次往里写。Flash 有一个特点,就是可以将 1 写成 0,但是不能将 0 写成 1,要想将 0 写成 1,必须进行擦除操作。因此通常要改写某部分空间的数据,必须首先进行一定物理存储空间擦除,最小的擦除空间,通常称之为扇区,扇区擦除就是将这整个扇区每个字节全部变成 0xFF。每款 Flash 的扇区大小不一定相同,W25Q128 的一个扇区是 4096 字节。为了提高擦除效率,使用不同的擦除指令还可以一次性进行 32K(8 个扇区)、64K(16 个扇区)以及整片擦除。
    
W25Q128的擦写周期多达10W次,可将数据保存达20年之久,支持2.7~3.6V的电压,支持标准的SPI,还支持双输出/四输出的SPI,最大SPI时钟可达80MHz。

本文使用的硬件平台: RockChip ITX-3588J,在其平台上添加 W25Q128 芯片(SPI 设备),Linux 内核版本为 kernel-5.15.0,采用 DeviceTree 描述硬件连接信息。

硬件连接

在这里插入图片描述

Linux Kernel驱动代码分析

理论–MTD子系统框架

在这里插入图片描述
在这里插入图片描述

MTD(Memory Technology Device)即常说的Flash等使用存储芯片的存储设备,MTD子系统对应的是块设备驱动框架中的设备驱动层,可以说,MTD就是针对Flash设备设计的标准化硬件驱动框架。

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字符设备和块设备。

设备树节点

根据实际的硬件连接情况修改设备树文件。查看rk3588linux/rk3588_repo_sdk_v1.0.2a/kernel/arch/arm64/boot/dts/rockchip下设备树文件(rk3588s.dtsi 和rk3588-firefly-itx-3588j .dtsi)中spi1节点的属性;

	spi1: spi@feb10000 {
		compatible = "rockchip,rk3066-spi", "rockchip,rk3066-spi";
		reg = <0x0 0xfeb10000 0x0 0x1000>;
		interrupts = <GIC_SPI 327 IRQ_TYPE_LEVEL_HIGH>;
		#address-cells = <1>;
		#size-cells = <0>;
		clocks = <&cru CLK_SPI1>, <&cru PCLK_SPI1>;
		clock-names = "spiclk", "apb_pclk";
		dmas = <&dmac0 16>, <&dmac0 17>;
		dma-names = "tx", "rx";
		pinctrl-names = "default";
		pinctrl-0 = <&spi1m1_cs0 &spi1m1_cs1 &spi1m1_pins>;
		num-cs = <2>;
		status = "okay";
	};

而在板级设备树rk3588-firefly-itx-3588j .dtsi中并没有相关的定义,添加如下:

/* spi norflash w25q128 on spi1 controller */
&spi1 {
	status = "okay";
	#address-cells = <1>;
	#size-cells = <0>;
	pinctrl-0 = <&spi1m2_cs0 &spi1m2_cs1 &spi1m2_pins>;
	w25q128@0 {
		compatible = "jedec,spi-nor";
		//label = "spi_nor";
		reg = <0x00>;
		spi-tx-bus-width = <1>;
		spi-rx-bus-width = <4>;
		spi-max-frequency = <50000000>;
		status = "okay";
	};
};

JEDEC是一个定义半导体行业标准的机构,大部分的SPI FLASH都遵循其制定的SFDP标准,软件开发按照标准操作即可。

m25p80.c(https://elixir.bootlin.com/linux/v4.5/source/drivers/mtd/devices/m25p80.c)基于SPI NOR框架提供了对常用flash的支持,包括对W25Q128的支持。历史上,许多闪存设备通过其名称绑定到此驱动程序。但大多数这些flash在某种程度上是兼容的。

在rk3588linux/rk3588_repo_sdk_v1.0.2a/kernel/drivers/mtd/devices下面并没有m25p80.c的驱动代码,但是在rk3588linux/rk3588_repo_sdk_v1.0.2a/kernel/drivers/mtd/spi-nor下面有一个叫core.c的文件,该文件开头注释为

// SPDX-License-Identifier: GPL-2.0
/*
 * Based on m25p80.c, by Mike Lavender (mike@steroidmicros.com), with
 * influence from lart.c (Abraham Van Der Merwe) and mtd_dataflash.c
 *
 * Copyright (C) 2005, Intec Automation Inc.
 * Copyright (C) 2014, Freescale Semiconductor, Inc.
 */

这是一份基于m25p80.c的驱动代码,通过查看ids结构体发现也支持W25Q128。

/*
 * Do NOT add to this array without reading the following:
 *
 * Historically, many flash devices are bound to this driver by their name. But
 * since most of these flash are compatible to some extent, and their
 * differences can often be differentiated by the JEDEC read-ID command, we
 * encourage new users to add support to the spi-nor library, and simply bind
 * against a generic string here (e.g., "jedec,spi-nor").
 *
 * Many flash names are kept here in this list (as well as in spi-nor.c) to
 * keep them available as module aliases for existing platforms.
 */
static const struct spi_device_id spi_nor_dev_ids[] = {
	/*
	 * Allow non-DT platform devices to bind to the "spi-nor" modalias, and
	 * hack around the fact that the SPI core does not provide uevent
	 * matching for .of_match_table
	 */
	{"spi-nor"},

	/*
	 * Entries not used in DTs that should be safe to drop after replacing
	 * them with "spi-nor" in platform data.
	 */
	{"s25sl064a"},	{"w25x16"},	{"m25p10"},	{"m25px64"},

	/*
	 * Entries that were used in DTs without "jedec,spi-nor" fallback and
	 * should be kept for backward compatibility.
	 */
	{"at25df321a"},	{"at25df641"},	{"at26df081a"},
	{"mx25l4005a"},	{"mx25l1606e"},	{"mx25l6405d"},	{"mx25l12805d"},
	{"mx25l25635e"},{"mx66l51235l"},
	{"n25q064"},	{"n25q128a11"},	{"n25q128a13"},	{"n25q512a"},
	{"s25fl256s1"},	{"s25fl512s"},	{"s25sl12801"},	{"s25fl008k"},
	{"s25fl064k"},
	{"sst25vf040b"},{"sst25vf016b"},{"sst25vf032b"},{"sst25wf040"},
	{"m25p40"},	{"m25p80"},	{"m25p16"},	{"m25p32"},
	{"m25p64"},	{"m25p128"},
	{"w25x80"},	{"w25x32"},	{"w25q32"},	{"w25q32dw"},
	{"w25q80bl"},	{"w25q128"},	{"w25q256"},

	/* Flashes that can't be detected using JEDEC */
	{"m25p05-nonjedec"},	{"m25p10-nonjedec"},	{"m25p20-nonjedec"},
	{"m25p40-nonjedec"},	{"m25p80-nonjedec"},	{"m25p16-nonjedec"},
	{"m25p32-nonjedec"},	{"m25p64-nonjedec"},	{"m25p128-nonjedec"},

	/* Everspin MRAMs (non-JEDEC) */
	{ "mr25h128" }, /* 128 Kib, 40 MHz */
	{ "mr25h256" }, /* 256 Kib, 40 MHz */
	{ "mr25h10" },  /*   1 Mib, 40 MHz */
	{ "mr25h40" },  /*   4 Mib, 40 MHz */

	{ },
};

在这里插入图片描述

SPI NorFlash驱动流程的分析
在这里插入图片描述

这里要把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 的兼容设备表,设备表中包含了"w25q128"。

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 函数呢? 我们把 W25Q128注册成 MTD 设备了,所以另外一些操作函数在 drivers/mtd/mtdchar.c 中定义。实际上,它不仅有 mtdchar_open、mtdchar_close 等函数,还有 mtdchar_read 和 mtdchar_write 函数,而它们会调用 m25p80.c 中的 m25p80_read 和 m25p80_write 函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值