天天看點

Linux 驅動 SPI Flash(W25Q80DV)

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      

然後斷電重新開機,看看檔案及其内容是否還在,并且與斷電前一緻。

繼續閱讀