Linux 驱动 SPI Flash(W25Q80DV)

  W25Q80DV 是 Winbond 的一款 SPI Flash,容量大小为 8M bit。如果还没看 W25Q80DV 的数据手册,赶紧去看!
  https://blog.csdn.net/lu_embedded/article/details/80682374

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


硬件连接

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

Linux 驱动 SPI Flash(W25Q80DV)_第1张图片

  管脚描述:

Linux 驱动 SPI Flash(W25Q80DV)_第2张图片

驱动模型

Linux 驱动 SPI Flash(W25Q80DV)_第3张图片

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_probem25p_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_readm25p80_writem25p80_erase 等文件操作函数,当应用程序使用 read、write、ioctl 等接口操作时最终会调用到这里。

  额,那 open 和 close 函数呢?
  还记得我们把 W25Q80DV 注册成 MTD 设备了嘛,所以另外一些操作函数在 drivers/mtd/mtdchar.c 中定义。实际上,它不仅有 mtdchar_openmtdchar_close 等函数,还有 mtdchar_readmtdchar_write 函数,而它们会调用 m25p80.c 中的 m25p80_readm25p80_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

你可能感兴趣的:(嵌入式Linux开发)