W25Q80DV 是 Winbond 的一款 SPI Flash,容量大小为 8M bit。如果还没看 W25Q80DV 的数据手册,赶紧去看!

本文描述的是在 i.MX6q 硬件平台上添加 W25Q80DV 芯片(SPI 设备),Linux 内核版本为 kernel-3.10.17,采用 DeviceTree 描述硬件连接信息。


##硬件连接

i.MX6q 是基于 NXP 四核 ARM Cortex-A9 架构的高性能处理器,它上面有 5 个 SPI 控制器,分别是 ECSPI1~5。在我们这里的测试平台上的硬件连接的情况是这样的:

管脚描述:

##驱动模型

##MTD

MTD设备分为四层(从设备节点直到底层硬件驱动),这四层从上到下依次是:

  • 设备节点
  • MTD设备层
  • MTD原始设备层
  • 硬件驱动层

MTD 子系统实现了 SPI flash 芯片驱动程序,其驱动 Demo 为:
  drivers/mtd/devices/mtd_dataflash.c
  drivers/mtd/devices/m25p80.c

##驱动文件

对于我们这里的 W25Q80DV 设备,重点关注的驱动文件是:
  drivers/mtd/devices/m25p80.c
  其主要代码框架如下:


static int m25p80_erase(struct mtd_info *mtd, struct erase_info *instr)
{
/* 省略 */
}

static int m25p80_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
{
/* 省略 */
}

static int m25p80_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf)
{
/* 省略 */
}

static const struct spi_device_id m25p_ids[] = {
#ifdef CONFIG_ARCH_ADVANTECH
/* Micron N25Q */
{ "n25q", INFO(0x20ba16, 0, 64 * 1024, 64, SECT_4K) },
{ "n25q", INFO(0x20bb16, 0, 64 * 1024, 64, SECT_4K) },
#endif
/* Atmel -- some are (confusingly) marketed as "DataFlash" */
{ "at25fs010", INFO(0x1f6601, 0, 32 * 1024, 4, SECT_4K) },
{ "at25fs040", INFO(0x1f6604, 0, 64 * 1024, 8, SECT_4K) },

/* ST Microelectronics -- newer production may have feature updates */
{ "m25p40", INFO(0x202013, 0, 64 * 1024, 8, 0) },
{ "m25p80", INFO(0x202014, 0, 64 * 1024, 16, 0) },

/* Winbond -- w25x "blocks" are 64K, "sectors" are 4KiB */
{ "w25q64", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) },
{ "w25q80", INFO(0xef5014, 0, 64 * 1024, 16, SECT_4K) },
{ "w25q80bl", INFO(0xef4014, 0, 64 * 1024, 16, SECT_4K) },
{ "w25q128", INFO(0xef4018, 0, 64 * 1024, 256, SECT_4K) },
{ "w25q256", INFO(0xef4019, 0, 64 * 1024, 512, SECT_4K) },
{ },
};
MODULE_DEVICE_TABLE(spi, m25p_ids);

static const struct spi_device_id *jedec_probe(struct spi_device *spi)
{
/* 省略 */
}

static int m25p_probe(struct spi_device *spi)
{
const struct spi_device_id *id = spi_get_device_id(spi);
struct flash_platform_data *data;
struct m25p *flash;
struct flash_info *info;
unsigned i;
struct mtd_part_parser_data ppdata;
struct device_node __maybe_unused *np = spi->dev.of_node;

/* 省略 */

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

jid = jedec_probe(spi);
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(&spi->dev, "found %s, expected %s\n",
jid->name, id->name);
id = jid;
info = (void *)jid->driver_data;
}
}

/* 省略 */

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

flash->mtd.type = MTD_NORFLASH;
flash->mtd.writesize = 1;
flash->mtd.flags = MTD_CAP_NORFLASH;
flash->mtd.size = info->sector_size * info->n_sectors;
flash->mtd._erase = m25p80_erase;
flash->mtd._read = m25p80_read;

/* flash protection support for STmicro chips */
if (JEDEC_MFR(info->jedec_id) == CFI_MFR_ST) {
flash->mtd._lock = m25p80_lock;
flash->mtd._unlock = m25p80_unlock;
}

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

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

/* 省略 */
}

static int m25p_remove(struct spi_device *spi)
{
struct m25p *flash = dev_get_drvdata(&spi->dev);
int status;

/* Clean up MTD stuff. */
status = mtd_device_unregister(&flash->mtd);
if (status == 0) {
kfree(flash->command);
kfree(flash);
}
return 0;
}

static struct spi_driver m25p80_driver = {
.driver = {
.name = "m25p80",
.owner = THIS_MODULE,
},
.id_table = m25p_ids,
.probe = m25p_probe,
.remove = m25p_remove,

/* REVISIT: many of these chips have deep power-down modes, which
* should clearly be entered on suspend() to minimize power use.
* And also when they're otherwise idle...
*/
};

module_spi_driver(m25p80_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mike Lavender");
MODULE_DESCRIPTION("MTD SPI driver for ST M25Pxx flash chips");

通读 m25p80.c 驱动代码,我们可以找出大概的脉络。首先是通过 ​​module_spi_driver​​​ 函数注册 m25p80_driver 驱动,其中实现了 probe 和 remove 函数,分别是 ​​m25p_probe​​​ 和 ​​m25p_remove​​​。并且填写了一张名为 m25p_ids 的兼容设备表,通过查看 ​​W25Q80DV​​ 的数据手册,JEDEC 设备 ID 为 0xef4014,也就是对应 m25p_ids 中的 w25q80bl。

//INFO ( _jedec_id, _ext_id, _sector_size, _n_sectors, _flags )
{ "w25q80bl", INFO(0xef4014, 0, 64 * 1024, 16, SECT_4K) }

其中 INFO 是一个宏定义,其作用是将设备参数填写到内部的 flash_info 结构体实例中,在设备匹配成功后使用。
  在 ​​​m25p_probe​​​ 函数中指定了 ​​m25p80_read​​​、​​m25p80_write​​​ 和 ​​m25p80_erase​​ 等文件操作函数,当应用程序使用 read、write、ioctl 等接口操作时最终会调用到这里。

额,那 open 和 close 函数呢?
  还记得我们把 W25Q80DV 注册成 MTD 设备了嘛,所以另外一些操作函数在 drivers/mtd/mtdchar.c 中定义。实际上,它不仅有 ​​mtdchar_open​​​、​​mtdchar_close​​​ 等函数,还有 ​​mtdchar_read​​​ 和 ​​mtdchar_write​​​ 函数,而它们会调用 m25p80.c 中的 ​​m25p80_read​​​ 和 ​​m25p80_write​​ 函数。

static int mtdchar_open(struct inode *inode, struct file *file)
{
int minor = iminor(inode);
int devnum = minor >> 1;
int ret = 0;
struct mtd_info *mtd;
struct mtd_file_info *mfi;
struct inode *mtd_ino;

// ...
}

static int mtdchar_close(struct inode *inode, struct file *file)
{
// ...
}

static ssize_t mtdchar_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
// ...
}

static ssize_t mtdchar_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
// ...
}

##设备树节点

接下来要根据实际的硬件连接情况修改设备树文件,不用担心不会写,因为芯片厂商一定会提供参考信息的,比如这里的 SPI Flash,参考以下文件:
  Documentation/devicetree/bindings/mtd/m25p80.txt
  我这里的 W25Q80DV 连接到 i.MX6Q 的 ECSPI4,具体如下:

&ecspi4 {
fsl,spi-num-chipselects = <1>;
cs-gpios = <&gpio3 20 0>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi4_1 &pinctrl_ecspi4_cs_0>;
status = "okay";

flash: m25p80@0 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "winbond,w25q80bl";
spi-max-frequency = <20000000>;
reg = <0>;
};
};

&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1 &pinctrl_hog_2>;

spi4 {
pinctrl_ecspi4_cs_0: ecspi4_cs_grp-0 {
fsl,pins = <
MX6QDL_PAD_EIM_D20__GPIO3_IO20 0x80000000 /* ECSPI4_CS0 */
>;
};

pinctrl_ecspi4_cs_1: ecspi4_cs_grp-1 {
fsl,pins = <
MX6QDL_PAD_EIM_A25__GPIO5_IO02 0x80000000 /* ECSPI4_CS1 */
>;
};

pinctrl_ecspi4_1: ecspi4grp-1 {
fsl,pins = <
MX6QDL_PAD_EIM_D22__ECSPI4_MISO 0x170f1
MX6QDL_PAD_EIM_D28__ECSPI4_MOSI 0x1B008
MX6QDL_PAD_EIM_D21__ECSPI4_SCLK 0x170f1
>;
};
};
};

##编译&验证

重新编译 image 和 dtb:

$ source /opt/poky/1.5.3/environment-setup-cortexa9hf-vfp-neon-poky-linux-gnueabi
$ make uImage LOADADDR=0x10008000
$ make imx6q-rom5420-b1.dtb

更新系统后重新启动,进入 shell。输入命令 ​​dmesg | grep spi​​,看到如下内容则说明内核已经探测到 w25q80bl 设备,把设备和驱动程序匹配上了。

[    1.931184] m25p80 spi32765.0: w25q80bl (1024 Kbytes)
[ 1.935767] spi_imx 2014000.ecspi: probed

查看设备文件:

# ls /dev/mtd*
/dev/mtd0 /dev/mtd1 /dev/mtdblock0
/dev/mtd0ro /dev/mtd1ro /dev/mtdblock1

可以看到多了 /dev/mtd1 之类的设备节点,其中 /dev/mtd1 是字符设备,/dev/mtdblock1 是块设备,/dev/mtd1ro 是只读字符设备。

##挂载 MTD 设备挂载

因为我们把 SPI Flash 注册成 MTD 设备了,因此,我们可以通过 MTD 子系统和文件系统对其进行操作。
  首先对 Flash 进行格式化,然后挂载,接着就可以通过文件系统操作:

# mkfs.vfat /dev/mtdblock1
# mount -t vfat /dev/mtdblock1 /home/root/w25q80
# cd /home/root/w25q80
# echo "Hello W25Q80" > file.txt
# sync

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

##“读写擦”测试程序

除了通过文件系统操作 W25Q80DV 设备外,也可以直接打开 /dev/mtd1 设备节点对其进行操作。
  测试程序有点长,我放在 GitHub 上了,大家可以参考下:
​​​ https://github.com/luhuadong/Linux-programming/blob/master/driver/mtd/test/mtd_go.c​