RK3568笔记四十九:W25Q64驱动开发(硬件SPI1)

news2024/9/22 3:36:39

若该文为原创文章,转载请注明原文出处。

一、SPI介绍

串行外设接口 (Serial Peripheral interface) 简称 SPI,是一种高速的,全双工,同步的通信总线,并 且在芯片的管脚上只占用四根线,节约了芯片的管脚。

而W25Q64是常见的串行闪存器件,W25Q64将8M字节的容量分为128个块,每个块大小为64K字节,每个块又分为16个扇区,每个扇区4K个字节。W25Q64的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。所以,这需要给W25Q64开辟一个至少4K的缓存区,这样必须要求芯片有4K以上的SRAM才能有很好的操作。
W25Q64的擦写周期多达10W次,可将数据保存达20年之久,支持2.7~3.6V的电压,支持标准的SPI,还支持双输出/四输出的SPI,最大SPI时钟可达80Mhz。

二、spi 基本知识

spi 总线都可以挂载多个设备,spi 支持标准的一主多从,全双工半双工通信等。

其中四根控制线 包括:

• SCK:时钟线,数据收发同步

• MOSI:数据线,主设备数据发送、从设备数据接收

• MISO:数据线,从设备数据发送,主设备数据接收

• NSS:片选信号线

i2c 通过 i2c 设备地址选择通信设备,而 spi 通过片选引脚选中要通信的设备。

spi 接口支持有多个片选引脚,连接多个 SPI 从设备,当然也可以使用外部 GPIO 扩展 SPI 设备的 数量,这样一个 spi 接口可连接的设备数由片选引脚树决定。

• 如果使用 spi 接口提供的片选引脚,spi 总线驱动会处理好什么时候选 spi 设备。

• 如果使用外部 GPIO 作为片选引脚需要我们在 spi 设备驱动中设置什么时候选中 spi。

(或者 在配置 SPI 时指定使用的片选引脚)。

通常情况下无特殊要求我们使用 spi 接口提供的片选引脚。

三、SPI时序

• 起始信号:NSS 信号线由高变低

• 停止信号:NSS 信号由低变高

• 数据传输:在 SCK 的每个时钟周期 MOSI 和 MISO 同时传输一位数据,高/低位传输没有硬 性规定

– 传输单位:8 位或 16 位

– 单位数量:允许无限长的数据传输

四、硬件原理分析

ATK-DLRK3568的外设IO有引出SPI1。

对应W25Q64接线如下:

     W25Q64引脚ATK-DLRK3568
1-CSGPIO3_A1   SPI1_CS0_M1
2-DOGPIO3_C2  SPI1_MISO_M1
3-WP3.3V
4-GNDGND
5-DIGPIO3_C1  SPI1_MOSI_M1
6-CLKGPIO3_C3  SPI1_CLK_M1
7-HOLDNC
8-VCC3.3V

使用杜邦线链接,确保接线正常。

五、创建设备节点

1、设备树节点

修改/home/alientek/rk3568_linux_sdk/kernel/arch/arm64/boot/dts/rockchip/目录下的rk3568-atk-evb1-ddr4-v10.dtsi文件,在文件末添加代码,在spi1设备树下添加w25q64节点。

&spi1 {
	status = "okay";
	pinctrl-names = "default", "high_speed";
	pinctrl-0 = <&spi1m1_cs0 &spi1m1_pins>;
	pinctrl-1 = <&spi1m1_cs0 &spi1m1_pins_hs>;
	
		  
	// 向 spi1 节点追加 w25q64 设备节点
	w25q64: w25q64@0 {
		compatible = "yifeng,w25q64";
		reg = <0>;  // 设置 reg 属性为 0, 表示 spi 连接到 spi1 的通道 0
		spi-max-frequency = <24000000>;  // 设置 SPI 传输的最大频率
		wp-gpio = <&gpio3 RK_PA1 GPIO_ACTIVE_HIGH>;
		pinctrl-0 = <&w25q64_wp>; /*<&w25q64_cs>; */
	};
};

2、创建设备的 pinctrl 节点

修改/home/alientek/rk3568_linux_sdk/kernel/arch/arm64/boot/dts/rockchip/目录下的rk3568-pinctrl.dtsi文件,在最后面增加节点

w25q64 {
		/omit-if-no-ref/
		w25q64_wp: w25q64-wp {
			rockchip,pins = <3 RK_PA1 RK_FUNC_GPIO &pcfg_pull_up>;
		};
	};

设备树修改完成以后在 SDK 顶层目录输入如下命令重新编译一下内核:

# 指定 SDK 的板级配置文件
./build.sh lunch
# 编译内核
./build.sh kernel

编译完成以后得到 boot.img, boot.img 就是编译出来的内核+设备树打包在一起的文件

只需要重新烧写boot.img。

烧写完成以后启动开发板。Linux 启动成功以后进入到/proc/device-tree/目录中查看是否有节点

六、编写驱动

1、spi_drv.c


#include <linux/module.h>//模块加载卸载函数
#include <linux/kernel.h>//内核头文件
#include <linux/types.h>//数据类型定义
#include <linux/fs.h>//file_operations结构体
#include <linux/device.h>//class_create等函数
#include <linux/ioctl.h>
#include <linux/kernel.h>/*包含printk等操作函数*/
#include <linux/of.h>/*设备树操作相关的函数*/
#include <linux/gpio.h>/*gpio接口函数*/
#include <linux/of_gpio.h>
#include <linux/platform_device.h>/*platform device*/
#include <linux/spi/spi.h> /*spi相关api*/
#include <linux/delay.h> /*内核延时函数*/
#include <linux/slab.h> /*kmalloc、kfree函数*/
#include <linux/cdev.h>/*cdev_init cdev_add等函数*/
#include <asm/uaccess.h>/*__copy_from_user 接口函数*/

#include <linux/moduleparam.h>
#include <linux/iio/iio.h>
#include <linux/iio/machine.h>
#include <linux/iio/driver.h>
#include <linux/iio/consumer.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/uaccess.h>
#include <asm/io.h>


#define  DEVICE_NAME             "spi"
#define  W25Qxx_PAGE_SIZE        256        /*页 大小256字节*/
#define  W25QXX_SECTOR_SIZE      4096       /*扇区 大小4096*/

/*W25Qxx 指令*/
#define W25X_WriteEnable         0x06 
#define W25X_WriteDisable        0x04 
#define W25X_ReadStatusReg       0x05 
#define W25X_WriteStatusReg      0x01 
#define W25X_ReadData            0x03 
#define W25X_FastReadData        0x0B 
#define W25X_FastReadDual        0x3B 
#define W25X_PageProgram         0x02 
#define W25X_BlockErase          0xD8 
#define W25X_SectorErase         0x20 
#define W25X_ChipErase           0xC7 
#define W25X_PowerDown           0xB9 
#define W25X_ReleasePowerDown    0xAB 
#define W25X_DeviceID            0xAB 
#define W25X_ManufactDeviceID    0x90 
#define W25X_JedecDeviceID       0x9F 

typedef struct
{
   void *tx_buf;
   void *rx_buf;
   unsigned char cmd;            //w25q64指令
   unsigned int address;         //写入或者读取的地址
   unsigned int tx_len;          //需要写入的字节数
   unsigned int rx_len;          //需要读取的字节数

}w25qxx_data_def;

typedef struct
{
      struct device_node *node;//设备树节点
      struct cdev cdev;       //定义一个cdev结构体
      struct class *class;    //创建一个w25q64类
      struct device *device;  //创建一个w25q64设备 该设备是需要挂在w25q64类下面的
      int major;              //主设备号
      dev_t  dev_id;
      struct spi_device *spi; /*spi设备*/
      int cspin;              /*片选脚*/
      int wppin;
      struct mutex lock;
      w25qxx_data_def data;
}w25qxx_typdef;

static w25qxx_typdef w25qxx_dev;//定义一个w25q64设备

/*函数声明*/
static int w25qxx_read_bytes(w25qxx_typdef *w25q64,unsigned int address,unsigned char* buf,int count);

static int w25q64_spi_read_write(w25qxx_typdef *w25q64)
{
   struct spi_device *spi = w25q64->spi;
   struct spi_transfer xfer[2];
   struct spi_message msg;
   int ret = 0;
   unsigned char *buf,*readbuf;

   memset(&xfer, 0, sizeof(xfer));/*必须清0 否则无法spi_sync函数无法发送数据*/

   xfer[0].tx_buf = w25q64->data.tx_buf;
   xfer[0].len = w25q64->data.tx_len;
   
   buf = (unsigned char *)(w25q64->data.tx_buf);

   xfer[1].rx_buf = w25q64->data.rx_buf;
   xfer[1].len = w25q64->data.rx_len;

   spi_message_init(&msg);
   spi_message_add_tail(&xfer[0], &msg);

   if(w25q64->data.rx_len)
   {
      spi_message_add_tail(&xfer[1], &msg);
   }

   ret = spi_sync(spi, &msg); 
   if(ret != 0)
   {  
      printk("spi_sync failed %d\n", ret);
   }
   
   readbuf = (unsigned char *)(w25q64->data.rx_buf);
   return ret;
}

static void spi_wp_enable(void)
{
    gpio_set_value(w25qxx_dev.wppin, 1); 
}

static void spi_wp_disable(void)
{
   gpio_set_value(w25qxx_dev.wppin, 0); 
}

static void spi_cs_enable(void)
{
   //gpio_set_value(w25qxx_dev.cspin, 0); /* cs = 0 */
}

static void spi_cs_disable(void)
{
   //gpio_set_value(w25qxx_dev.cspin, 1); /* cs = 1 */
}

static void spi_write_enable(void)
{
   int ret;
   unsigned char tx_buf[1];

   spi_cs_enable();
    
   tx_buf[0] = W25X_WriteEnable;/*写使能指令*/
   w25qxx_dev.data.tx_buf= tx_buf; 
   w25qxx_dev.data.tx_len = 1;
   w25qxx_dev.data.rx_len = 0;

   ret = w25q64_spi_read_write(&w25qxx_dev);

   spi_cs_disable();
}

static void spi_write_disable(void)
{

   int ret;
   unsigned char tx_buf[1];
   spi_cs_enable();
    
   tx_buf[0] = W25X_WriteDisable;/*写失能指令*/
   
   w25qxx_dev.data.tx_buf= tx_buf; 
   w25qxx_dev.data.tx_len = 1;
   w25qxx_dev.data.rx_len = 0;

   ret = w25q64_spi_read_write(&w25qxx_dev);

   spi_cs_disable();
}

static int w25qxx_get_sr(w25qxx_typdef *w25q64)
{
   int ret = -EINVAL;
   unsigned char tx_buf[1];
   unsigned char rx_buf[1];
   
   spi_cs_enable();

   tx_buf[0] = W25X_ReadStatusReg;
   
   w25q64->data.tx_buf= tx_buf; 
   w25q64->data.tx_len = 1;

   w25q64->data.rx_buf = rx_buf;
   w25q64->data.rx_len = 1;
 
   ret = w25q64_spi_read_write(w25q64);

   spi_cs_disable();

   if(ret < 0)
   {
      printk("w25qxx_get_sr failed \n");
      return ret;
   }

   return rx_buf[0];
}

static int w25qxx_get_id(w25qxx_typdef *w25q64)
{
   int ret = -EINVAL;
   unsigned char tx_buf[4];
   unsigned char rx_buf[5];
   
   spi_cs_enable();

   tx_buf[0] = W25X_ManufactDeviceID;/*读取ID指令*/
   tx_buf[1] = 0x0;
   tx_buf[2] = 0x0;
   tx_buf[3] = 0x0;
   
   w25q64->data.tx_buf= tx_buf; 
   w25q64->data.tx_len = 4;

   w25q64->data.rx_buf = rx_buf;
   w25q64->data.rx_len = 2;
 
   ret = w25q64_spi_read_write(w25q64);

   spi_cs_disable();

   if(ret != 0)
   {
      printk("w25qxx_get_id failed %d\n",ret);
      return ret;
   }
   printk("rx_buf 0x%x 0x%x 0x%x 0x%x\n\r",rx_buf[0], rx_buf[1], rx_buf[2], rx_buf[3]);
   return (rx_buf[0] << 8 | rx_buf[1]);
}

static void w25qxx_Reset(w25qxx_typdef *w25q64)
{
   int ret = -EINVAL;
   unsigned char tx_buf[4];//
   unsigned char rx_buf[5];//

   spi_wp_disable(); 
   spi_cs_enable();

   udelay(2);
   tx_buf[0] = 0x66;/*读取ID指令*/
   tx_buf[1] = 0x99;

   
   w25q64->data.tx_buf= tx_buf; 
   w25q64->data.tx_len = 2;

   w25q64->data.rx_buf = rx_buf;
   w25q64->data.rx_len = 0;
 
   ret = w25q64_spi_read_write(w25q64);

   spi_cs_disable();
   spi_wp_enable();
   udelay(2);

   if(ret < 0)
   {
      printk("w25qxx_get_id failed %d\n",ret);
   }
   else
   {
       printk("w25qxx_ Init Success %d\n",ret); 
   }
}

static int w25qxx_wait_idle(void)
{
   int ret = -EINVAL; 
   do {
        ret = w25qxx_get_sr(&w25qxx_dev);
        if(ret < 0 )
        {
           return ret;/*通信错误*/
        }
        else
        {
            if(!(ret & 0x01))
            {
               return 0;/*w25q64空闲*/
            }
        }  
        /* REVISIT: at HZ=100, this is sloooow */
        msleep(10);
   } while(1);   
   return 1; 
}

static int w25qxx_erase_sector(w25qxx_typdef *w25q64,unsigned int address)
{
   int ret = -EINVAL;
   unsigned char tx_buf[4];//
   
   spi_write_enable();/*写保护关闭*/
   spi_cs_enable();

   tx_buf[0] = W25X_SectorErase;/*扇区擦除指令*/
   tx_buf[1] = (unsigned char)((address>>16) & 0xFF);
   tx_buf[2] = (unsigned char)((address>>8) & 0xFF);
   tx_buf[3] = (unsigned char)(address & 0xFF);
   
   w25q64->data.tx_buf= tx_buf; 
   w25q64->data.tx_len = 4;

   w25q64->data.rx_len = 0;
 
   ret = w25q64_spi_read_write(w25q64);

   spi_cs_disable();

   if(ret != 0)
   {
      printk("erase sector@%d failed %d\n",address,ret);
      return ret;
   }
   ret = w25qxx_wait_idle();/*等待flash内部操作完成*/
   spi_write_disable();/*写保护打开*/

   return ret;
}

static int w25qxx_erase_chip(w25qxx_typdef *w25q64)
{
   int ret = -EINVAL;
   unsigned char tx_buf[1];//
   
   spi_write_enable();/*写保护关闭*/
   spi_cs_enable();

   tx_buf[0] = W25X_ChipErase;/*扇区擦除指令*/

  
   w25q64->data.tx_buf= tx_buf; 
   w25q64->data.tx_len = 1;

   w25q64->data.rx_len = 0;
 
   ret = w25q64_spi_read_write(w25q64);

   spi_cs_disable();

   if(ret != 0)
   {
      printk("erase chip failed %d\n", ret);
      return ret;
   }
   ret = w25qxx_wait_idle();/*等待flash内部操作完成*/
   spi_write_disable();/*写保护打开*/

   return ret;
}

static int w25qxx_need_erase(unsigned char*old,unsigned char*new,int count)
{
   int i;
   unsigned char p;

   for ( i = 0; i < count; i++)
   {
      p = *old++;
      p = ~p; 
      
      if((p &(*new++))!=0)
      {
         return 1;
      }
   }
   return 0;
}

static int w25qxx_read_bytes(w25qxx_typdef *w25q64,unsigned int address,unsigned char* buf,int count)
{
   int ret = -EINVAL;
   unsigned char tx_buf[4];//
   
   spi_cs_enable();

   tx_buf[0] = W25X_ReadData;/*读取数据指令*/
   tx_buf[1] = (unsigned char)((address>>16) & 0xFF);
   tx_buf[2] = (unsigned char)((address>>8) & 0xFF);
   tx_buf[3] = (unsigned char)(address & 0xFF);
   
   w25q64->data.tx_buf= tx_buf; 
   w25q64->data.tx_len = 4;

   w25q64->data.rx_buf = buf;
   w25q64->data.rx_len = count;
 
   ret = w25q64_spi_read_write(w25q64);

   spi_cs_disable();

   if(ret != 0)
   {
      printk("read@%d ,%d bytes failed %d\n",address,count,ret);
      return ret;
   }

   return ret;
}

static int w25qxx_write_page(w25qxx_typdef *w25q64,unsigned int address,unsigned char* buf,int count)
{
   int ret = -EINVAL;
   unsigned char *tx_buf;/*数据缓冲区*/
   
   tx_buf = (unsigned char*)kzalloc(count+4,GFP_KERNEL);
   if(!tx_buf)
       return -ENOMEM;
 

   spi_write_enable();/*写保护关闭*/
   spi_cs_enable();

   tx_buf[0] = W25X_PageProgram;/*页写指令*/
   tx_buf[1] = (unsigned char)((address>>16) & 0xFF);
   tx_buf[2] = (unsigned char)((address>>8) & 0xFF);
   tx_buf[3] = (unsigned char)(address & 0xFF);
   
   memcpy(&tx_buf[4],buf,count);

   w25q64->data.tx_buf= tx_buf; 
   w25q64->data.tx_len = count+4;

   w25q64->data.rx_len = 0;/*不需要读*/
   
   //printk("tx_data:%d-%d-%d-%d,count=%d\n",tx_buf[4],tx_buf[5],tx_buf[6],tx_buf[7],w25q64->data.tx_len);

   ret = w25q64_spi_read_write(w25q64);

   spi_cs_disable();

   if(ret != 0)
   {
      printk("write page@%d ,%d bytes failed %d\n",address,count,ret);
      kfree(tx_buf);
      spi_write_disable();/*写保护打开*/
      return ret;
   }
   ret = w25qxx_wait_idle();
   kfree(tx_buf); 
   spi_write_disable();/*写保护打开*/
   return ret;
}

static int w25qxx_write_pages(w25qxx_typdef *w25q64,unsigned int address,unsigned char* buf,int count)
{
   int ret = -EINVAL;
   unsigned int remain_of_page,need_to_write;
   unsigned int sector_first_address,sector_offset;
   unsigned char *write_buf;/*数据缓冲区*/

   write_buf = (unsigned char*)kzalloc(4096,GFP_KERNEL);
   if(!write_buf)
       return -ENOMEM;

   /*获取指定地址所在扇区的扇区首地址*/    
   sector_first_address = address & (~(W25Qxx_PAGE_SIZE-1)) ;

   /*获取指定地址在所在扇区内的偏移量*/
   sector_offset = address % 4096;

   ret = w25qxx_read_bytes(w25q64,sector_first_address,write_buf,4096);//读出整个扇区
   if(ret < 0 )
   {
      return ret;
   }
   
   /*判断是否需要擦除*/
   if(w25qxx_need_erase(&write_buf[sector_offset],buf,count))
   {
      printk("erase\n");
      w25qxx_erase_sector(w25q64,sector_first_address);
   }
   
   kfree(write_buf);

   remain_of_page = W25Qxx_PAGE_SIZE - address%W25Qxx_PAGE_SIZE;//获取本页还剩多少个字节空间可写入
   need_to_write = remain_of_page;/*下一次最多可写remain_of_page个字节*/
   
   printk("sector_first_address=%d,sector_offset=%d\n",sector_first_address,sector_offset);

   printk("address=%d,count=%d\n",address,count);

   if(count <= need_to_write) 
   {
      /*需要写入的字节数少于剩余空间  直接写入实际字节数*/
      ret = w25qxx_write_page(w25q64,address,buf,count);
      return ret;
   }
   else
   {    
      do
      {
         printk("address=%d\n,need_to_write=%d\n",address,need_to_write); 
         ret = w25qxx_write_page(w25q64,address,buf,need_to_write);
         if(ret !=0)
         {
            return ret;
         }
         if(need_to_write == count)
         {
             break;
         }
         else
         {
            buf+=need_to_write;
            address+=need_to_write;
            count-=need_to_write;         
            if(count > W25Qxx_PAGE_SIZE)
            {
               need_to_write = W25Qxx_PAGE_SIZE;
            }
            else
            {
               need_to_write = count;
            }
         }        
      } while (1);  
   }
   return ret;
}

static int w25qxx_write_more_bytes(w25qxx_typdef *w25q64,unsigned int address,unsigned char* buf,int count)
{
   int ret = -EINVAL;
   unsigned int num_of_sector,remain_of_sector,sector_offset;
   unsigned int need_to_write;//sector_first_address
   unsigned char *write_buf;/*数据缓冲区*/
   
   write_buf = (unsigned char*)kzalloc(4096,GFP_KERNEL);
   if(!write_buf)
       return -ENOMEM;
   
   num_of_sector = address / W25QXX_SECTOR_SIZE;
   sector_offset = address % W25QXX_SECTOR_SIZE;
   remain_of_sector = W25QXX_SECTOR_SIZE - address % W25QXX_SECTOR_SIZE;/*当前地址所在扇区 还剩下多少空间*/
   
   need_to_write = remain_of_sector;

   if(count <= need_to_write)
   {
      ret = w25qxx_write_pages(w25q64,address,buf,count);
      return ret;
   }
   else
   {
       do
      {
         ret = w25qxx_write_pages(w25q64,address,buf,need_to_write);
         if(ret !=0)
         {
            return ret;
         }
         if(need_to_write == count)
         {
             break;
         }
         else
         {
            buf+=need_to_write;
            address+=need_to_write;
            count-=need_to_write;         
            if(count > W25QXX_SECTOR_SIZE)
            {
               need_to_write = W25QXX_SECTOR_SIZE;
            }
            else
            {
               need_to_write = count;
            }
         }        
      } while (1);  
   }
   return ret;
}

static int w25qxx_open(struct inode *inode, struct file *filp)
{
   filp->private_data = &w25qxx_dev;

   return 0;
}

static int w25qxx_release(struct inode* inode ,struct file *filp)
{
   // w25qxx_typdef *dev = (w25qxx_typdef *) filp->private_data;
   return 0;
}

static ssize_t w25qxx_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{  
  int ret;  
  unsigned char *write_buf;/*数据缓冲区*/
  w25qxx_typdef * dev = (w25qxx_typdef *) filp->private_data;
  unsigned char address = filp->f_pos;

  write_buf = (unsigned char*)kzalloc(count,GFP_KERNEL);
  if(!write_buf )
       return -ENOMEM;
  spi_wp_enable();

  if (copy_from_user(write_buf, buf, count))
  {
      kfree(write_buf);
      return -EFAULT;
  }

  printk("write = %d,count = %d\n", address, (int)count);
  
  ret = w25qxx_write_more_bytes(dev,address,write_buf,count);
  spi_wp_disable();
  kfree(write_buf);
  return ret;
}

static ssize_t w25qxx_read(struct file *filp,char __user *buf, size_t count,loff_t *f_pos)
{
  int ret;  
  unsigned char *read_buf;/*数据缓冲区*/
  w25qxx_typdef * dev = (w25qxx_typdef *) filp->private_data;
  unsigned char address = filp->f_pos;
  
  read_buf = (unsigned char*)kzalloc(count,GFP_KERNEL);
  if(!read_buf )
       return -ENOMEM;

  printk("read@%d,count:%d\n",address, (int)count);

  ret = w25qxx_read_bytes(dev,address,read_buf,count);
  
  if (copy_to_user(buf, read_buf, count))
  {
      ret = -EFAULT;
  }

  kfree(read_buf);
  return ret;
}

loff_t w25qxx_llseek(struct file *file, loff_t offset, int whence)
{
   loff_t ret,pos,oldpos;
   oldpos = file->f_pos;
   switch (whence) 
   {
      case SEEK_SET:
           pos = offset; 
         break;
      case SEEK_CUR:
           pos = oldpos + offset;
         break;
      case SEEK_END:
           pos = W25Qxx_PAGE_SIZE - offset;
         break;   
      default:
          printk("cmd not supported\n");
         break;
   }
   
   if(pos < 0 || pos > W25Qxx_PAGE_SIZE)
   {  
      printk("error: pos > W25Qxx_PAGE_SIZE !\n");
      ret = -EINVAL;
      return ret;
   }
   file->f_pos = pos;
   ret = offset;  
   return ret;
}

static struct file_operations w25qxx_fops={
   .owner      = THIS_MODULE,
   .open       = w25qxx_open,
   .write      = w25qxx_write,
   .read       = w25qxx_read,
   .release    = w25qxx_release,
   .llseek     = w25qxx_llseek,
};

static int w25qxx_probe(struct spi_device *spi)
{
   int ret = -1;
   const char *string = NULL;

    w25qxx_typdef *dev = &w25qxx_dev;
   
   printk("w25q64 probe!\n"); 
   /*获取设备节点*/
   w25qxx_dev.node = of_find_node_by_path("/spi@fe620000/w25q64@0");
   if(w25qxx_dev.node == NULL)
   {
     printk("device-tree:not found w25q64!\r\n"); 
     return -1;
   }
   
   /*读取w25q64设备节点的compatible属性值*/
   ret = of_property_read_string(w25qxx_dev.node,"compatible",&string);
   if(ret == 0)
   {
      printk("%s\n",string);
   }
   
   /*申请gpio 用作片选*/
   w25qxx_dev.wppin = of_get_named_gpio(w25qxx_dev.node,"wp-gpio",0);
   if(!gpio_is_valid(w25qxx_dev.wppin))
   {
     printk("get gpio error\n");
     ret = -EINVAL;
     return ret;
   }
   
   printk("gpio = %d\n",w25qxx_dev.wppin);

   ret = gpio_request(w25qxx_dev.wppin,"spi-wp");
   if(ret < 0) 
   {
      printk("gpio_request %d failed\n",w25qxx_dev.wppin);
      return ret;
   }
   gpio_direction_output(w25qxx_dev.wppin, 1);

   gpio_export(w25qxx_dev.wppin, 1);

   /*申请gpio 用作片选*/
   // w25qxx_dev.cspin = of_get_named_gpio(w25qxx_dev.node,"cs-gpios",0);
   // if(!gpio_is_valid(w25qxx_dev.cspin))
   // {
   //   printk("get gpio error\n");
   //   ret = -EINVAL;
   //   return ret;
   // }
   
   // printk("gpio = %d\n",w25qxx_dev.cspin);

   // ret = gpio_request(w25qxx_dev.cspin,"spi-cs");
   // if(ret < 0) 
   // {
   //    printk("gpio_request %d failed\n",w25qxx_dev.cspin);
   //    return ret;
   // }
   // gpio_direction_output(w25qxx_dev.cspin, 1);

   // gpio_export(w25qxx_dev.cspin, 1);
   
   /*申请设备号*/
   alloc_chrdev_region(&w25qxx_dev.dev_id,0,1,DEVICE_NAME);

   /*初始化一个cdev*/
   cdev_init(&w25qxx_dev.cdev,&w25qxx_fops);

   
   /*向cdev中添加一个设备*/
   cdev_add(&w25qxx_dev.cdev,w25qxx_dev.dev_id,1);

   /*创建一个norflash_class类*/
   w25qxx_dev.class = class_create(THIS_MODULE, "norflash_class");
   if(w25qxx_dev.class == NULL)
   {
      printk("class_create failed\r\n");
     return -1;
   }

   /*在eeprom_class类下创建一个eeprom_class设备*/
   w25qxx_dev.device = device_create(w25qxx_dev.class, NULL, w25qxx_dev.dev_id, NULL, DEVICE_NAME);

   /*获取与本驱动匹配的spi设备*/
   w25qxx_dev.spi = spi;

   //w25qxx_dev.spi->mode = SPI_MODE_3; /*spi flash对应的模式*/

   spi_setup(w25qxx_dev.spi);

   mutex_init(&dev->lock);

   w25qxx_Reset(&w25qxx_dev);

   mdelay(200);

   ret = w25qxx_erase_chip(&w25qxx_dev);
   if(ret < 0)
   {
       printk("w25qxx_erase_chip failed\r\n");
   }   

   ret = w25qxx_get_id(&w25qxx_dev);
   
   printk("id=%04x\n",ret);

   return  0;
}

static int w25qxx_remove(struct spi_device *spi)
{
   
    printk("w25qxx remove!\n"); 

    /*删除w25q64类*/
   cdev_del(&w25qxx_dev.cdev);

   /*释放w25q64设备号*/
   unregister_chrdev_region(w25qxx_dev.dev_id, 1);

   /*注销w25q64设备*/
   device_destroy(w25qxx_dev.class, w25qxx_dev.dev_id);

   /*注销w25q64类*/
   class_destroy(w25qxx_dev.class);

   gpio_free(w25qxx_dev.wppin);
   //gpio_free(w25qxx_dev.cspin);

   return 0;
}

static const struct of_device_id w25qxx_of_match[] = {
   {.compatible = "yifeng,w25q64"},
   {},
};

static const struct spi_device_id w25q64_id[] = {
   { "xxxx", 0 },
   {},
};

static struct spi_driver w25qxx_driver = {

   .driver = {
     .owner = THIS_MODULE,
      .name = "w25q64",
      .of_match_table = w25qxx_of_match,
   },
   .probe = w25qxx_probe,
   .remove  = w25qxx_remove,  
   .id_table   = w25q64_id,      
};

static int __init w25qxx_init(void)
{
   printk("module init ok\n");
   return spi_register_driver(&w25qxx_driver);
}

static void w25qxx_exit(void)
{
   spi_unregister_driver(&w25qxx_driver);
   printk("module exit ok\n");
}

module_init(w25qxx_init);
module_exit(w25qxx_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("w25q64 driver");
MODULE_AUTHOR("yifeng");


代码中获取设备节点需要注意:

w25qxx_dev.node = of_find_node_by_path("/spi@fe620000/w25q64@0");

这里的/spi@fe620000/w25q64@0需要先在开发板上确定。

片选引脚也需要指定。

2、makefile

KERNELDIR := /home/alientek/rk3568_linux_sdk/kernel
ARCH=arm64
CROSS_COMPILE=/opt/atk-dlrk356x-toolchain/usr/bin/aarch64-buildroot-linux-gnu-

export  ARCH  CROSS_COMPILE

CURRENT_PATH := $(shell pwd)
obj-m := spi_drv.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译生成ko文件

七、应用程序编写



// APP应用 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <limits.h>
#include <asm/ioctls.h>
#include <time.h>
#include <pthread.h>
#include<string.h>

#define  num 128

void print_data(const char *title, char *dat, int count)
{
   int i = 0; 

   printf(title);

   for(i = 0; i < count; i++) 
   {
      printf(" 0x%x", dat[i]);
   }
   printf("\n");
}

int main(int argc, char *argv[])
{
   int fd,ret,i;
   int count = num;
   int offset = 0;   
   char write_buf[num],read_buf[num];

   /*判断传入的参数是否合法*/
   if(argc != 2)
   {
      printf("Usage:error\n");
      return -1;
   }
  
   /*解析传入的参数*/
   offset =atoi(argv[1]);
   printf("offset = %d\n", offset);
 
    /*打开设备文件*/
   fd = open("/dev/spi", O_RDWR);
   if(fd < 0)
   {
      printf("open dev fail fd=%d\n",fd); 
      close(fd);
      return fd;
   }

   /*缓存数组赋值*/
   //memset(write_buf, 0x55, num);
   for(i = 0; i < num; i++)
   {
      write_buf[i] = i;
   }

    /*写入数据*/ 
   lseek(fd,offset,SEEK_SET);

   ret = write(fd,write_buf,num);

   if(ret < 0)
   {
      printf("write to w25qxx error\n");
      close(fd);
      return ret;
   }

   /*打印数据*/
   print_data("write to w25qxx: \n\r", write_buf, count);

   /*读取数据*/
   ret = lseek(fd,offset,SEEK_SET);
   printf("lseek = %d\n",ret);
   
   ret = read(fd, read_buf, count);
   if(ret < 0)
   {
      printf("read from w25qxx error\n");
      close(fd);
      return ret;
   }
   
   /*打印数据*/
   print_data("read from w25qxx: \n\r",read_buf, count);

   ret = memcmp(write_buf, read_buf, count);
   if(ret)
   {
      printf("Writing data is different from reading data...\n");
   }
   else
   {
      printf("Write data is the same as read data...\n");
   }
   close(fd);
   return 0;   
}

编译

/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc spiApp.c -o spiApp

八、测试

测试比较简单,写入128个数,在读出来比较。

到此测试完成,使用硬件SPI正常。

但有个疑问使用ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);是怎么处理的,留个问题。

如有侵权,或需要完整代码,请及时联系博主。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1964852.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Word如何设置表格内容的中文和英文字体

1、选中需要设置的表格内容。 2、CtrlD&#xff0c;分别设置中文和英文字体&#xff0c;点确定即可。 提升自己最好的方法就是改变坏习惯&#xff0c;改变坏习惯最好的方法找习惯替代它。坏习惯不改&#xff0c;你永远受到限制&#xff0c;只能原地踏步。To do list&#xf…

爬取指定的天气网站数据

目 录 一、引言 &#xff08;一&#xff09;项目背景 &#xff08;二&#xff09;目标与意义 二、数据获取与处理 &#xff08;一&#xff09;使用的库和模块 &#xff08;二&#xff09;获取天气信息的函数 &#xff08;三&#xff09;数据预处理 三、数据分析…

python np.max怎么用

python np.max的用法&#xff1a; 语法&#xff1a;np.max&#xff1a;(a, axisNone, outNone, keepdimsFalse) 求序列的最值&#xff1b; 最少接收一个参数&#xff1b; axis&#xff1a;默认为列向&#xff08;也即 axis0&#xff09;&#xff0c;axis 1 时为行方向的最…

SQL labs-SQL注入(七,sqlmap对于post传参方式的注入,2)

本文仅作为学习参考使用&#xff0c;本文作者对任何使用本文进行渗透攻击破坏不负任何责任。参考&#xff1a;SQL注入之Header注入_sqlmap header注入-CSDN博客 序言&#xff1a; 本文主要讲解基于SQL labs靶场&#xff0c;sqlmap工具进行的post传参方式的SQL注入&#xff0c…

如何利用大语言模型进行半监督医学图像分割?这篇文章给出了答案

PS&#xff1a;写在前面&#xff0c;近期感谢很多小伙伴关注到我写的论文解读&#xff0c;我也会持续更新吖~同时希望大家多多支持本人的公主号~ 想了解更多医学图像论文资料请移步公主&#x1f478;号哦~~~后期将持续更新&#xff01;&#xff01; 关注我&#xff0c;让我们一…

大模型时代,编程已成为当代大中专学生的必备技能,如何选择编程语言的一些建议

目录 一、具体建议 1. 确定学习目标 &#xff08;1&#xff09;兴趣驱动 &#xff08;2&#xff09;职业规划 2. 评估市场需求 &#xff08;1&#xff09;行业趋势 &#xff08;2&#xff09;就业前景 3. 考虑应用领域 4. 学习资源 &#xff08;1&#xff09;查看官方文档…

idea 常用的快捷键大全 建议收藏!!

IDEA 一款非常优秀的开发工具&#xff0c;本篇博客总结一些在 IDEA 中常用的快捷键&#xff0c;旨在提高开发效率。点击File --> Settings --> keymap便可进入看到 IDEA 提供的快捷键&#xff0c;我们也可以搜索和自定义所有快捷键。下面给出的是IDEA常用操作归纳。 1、…

RK3568平台(触摸篇)串口触摸屏

一.什么是串口屏 串口屏&#xff0c;可组态方式二次开发的智能串口控制显示屏&#xff0c;是指带有串口通信的TFT彩色液晶屏显示控制模组。利用显示屏显示相关数据&#xff0c;通过触摸屏、按键、鼠标等输入单元写入参数或者输入操作指令&#xff0c;进而实现用户与机器进行信…

AI问答:理解CRLF和LF / 两者区别 / 在编程和文件处理中的影响

一、背景 vscode这里的CRLF&#xff0c;点击后有CRLF和LF的两个选项&#xff0c;本文我们理解CRLF 和 LF 二、理解CRLF和LF 2.1、CRLF&#xff1a;起源于早期的打字机和电传打字机&#xff0c;这些设备在打印完一行后&#xff0c;需要先将打印头移回到行首&#xff08;回车&…

【Java题解】杨辉三角—力扣

&#x1f389;欢迎大家收看&#xff0c;请多多支持&#x1f339; &#x1f970;关注小哇&#xff0c;和我一起成长&#x1f680;个人主页&#x1f680; ⭐目前主更 专栏Java ⭐数据结构 ⭐已更专栏有C语言、计算机网络⭐ 题目链接&#xff1a;杨辉三角 目录&#x1f451; ⭐题…

用60行python代码制作一个扫雷

扫雷游戏&#xff08;Minesweeper&#xff09;是一个经典的逻辑游戏&#xff0c;玩家需要在一个包含隐藏地雷的网格中标记出所有地雷的位置&#xff0c;同时避免触发它们。下面&#xff0c;我将提供一个简单的Python扫雷游戏实现&#xff0c;并附带详细的教程。 第一步&#x…

基于cubeMX的STM32的RTC实时时钟实现

1、在仪器仪表的项目开发中&#xff0c;时常需要设备显示当前的日期和时间&#xff0c;这时&#xff0c;可以使用STM32自带的RTC实时时钟模块来实现此功能。这里我们使用STM32F103RCT6单片机芯片为例。 2、cubeMX的设置 &#xff08;1&#xff09;RTC设置 &#xff08;2&…

第十六天内容

上午 静态资源 根据开发者保存在项目资源目录中的路径访问静态资源html 图片 js css 音乐 视频 f12&#xff0c;开发者工具&#xff0c;网络 1、web基本概念 web服务器 &#xff08;web server&#xff09;&#xff1a;也称HTTP服务器&#xff08;HTTP server&…

在线PS懒人快速抠出透明背景(纯色背景+复杂背景抠图操作)

电脑硬盘快爆了&#xff0c;没必要安装个PS了&#xff0c;网上找了几个在线的PS网站&#xff0c;还别说&#xff0c;一般的PS操作都可以满足 我们使用PS通常用的较多的是抠背景操作吧&#xff0c;接下来演示几个在在线PS网站上进行抠背景操作 一、在线PS网站 Photopea&#x…

IDM2024免费绿色纯净下载器,速度提升的秘密!

Internet Download Manager&#xff08;简称IDM&#xff09;是一种高效的下载管理器&#xff0c;它支持多线程下载、断点续传等功能&#xff0c;能够提高下载速度和稳定性。在网络资源日益丰富的今天&#xff0c;一个好用的下载器对于用户来说是非常重要的。本文将介绍IDM的主要…

数据结构初阶(c语言)-排序算法

数据结构初阶我们需要了解掌握的几种排序算法(除了直接选择排序&#xff0c;这个原因我们后面介绍的时候会解释)如下&#xff1a; 其中的堆排序与冒泡排序我们在之前的文章中已经详细介绍过并对堆排序进行了一定的复杂度分析&#xff0c;所以这里我们不再过多介绍。 一&#x…

Redis的集群 高可用

文章目录 Redis基本概念主从复制哨兵模式故障切换集群 Redis基本概念 Redis集群三种模式 主从复制&#xff1a;奇数台 3&#xff1a; 一主两从 哨兵模式&#xff1a;3&#xff1a; 1主两从 cluster&#xff1a;6 主从复制&#xff1a;和mysql的主从复制类似&#xff0c;主…

log4j2漏洞练习(未完成)

log4j2 是Apache的一个java日志框架&#xff0c;我们借助它进行日志相关操作管理&#xff0c;然而在2021年末log4j2爆出了远程代码执行漏洞&#xff0c;属于严重等级的漏洞。apache log4j通过定义每一条日志信息的级别能够更加细致地控制日志生成地过程&#xff0c;受影响的版本…

基于YOLOv8的道路裂缝坑洼检测系统

基于YOLOv8的道路裂缝坑洼检测系统 (价格88) 包含 【“裂缝”,“凹坑”】 2个类 通过PYQT构建UI界面&#xff0c;包含图片检测&#xff0c;视频检测&#xff0c;摄像头实时检测。 &#xff08;该系统可以根据数据训练出的yolov8的权重文件&#xff0c;运用在其他检测系…