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 函数。