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
然後斷電重新開機,看看檔案及其内容是否還在,并且與斷電前一緻。