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

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

一、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 的通道 0spi-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_addressunsigned 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_COMPILECURRENT_PATH := $(shell pwd)
obj-m := spi_drv.obuild: kernel_moduleskernel_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 128void 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.rhkb.cn/news/388566.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python写UI自动化--playwright(在UI元素上悬停)

要在UI元素上执行鼠标悬停&#xff08;hover&#xff09;动作&#xff0c;可以使用page.hover()方法。这个方法使鼠标指针移动到指定的元素上&#xff0c;就像用户将鼠标悬停在该元素上一样。这对于触发那些依赖于鼠标悬停事件的行为&#xff08;如显示下拉菜单、提示框或其他动…

[极客大挑战 2019]PHP1

打开题目 游戏界面 猜测php里面有文件&#xff0c;我们可以用dirsearch 扫描一下这个服务器 执行命令 dirsearch -u http://2999dfd5-1d43-4a81-a088-9e41c9dccab4.node4.buuoj.cn/ -e php > test.log 最后在log文件中找到一个 200 www.zip 浏览器输入payload下载下来 …

操作系统_内存管理学习心得

1. 操作系统结构 1.1 内核 计算机是由各种外部硬件设备组成的,比如内存、cpu、 硬盘等,如果每个应用都要和这些硬件设备对接通信协议&#xff0c;那这样太累了&#xff0c;所以这个中间人就由内核来负责,让内核作为应用连接硬件设备的桥梁,应用程序只需关心与内核交写&#x…

C++ | Leetcode C++题解之第283题移动零

题目&#xff1a; 题解&#xff1a; class Solution { public:void moveZeroes(vector<int>& nums) {int n nums.size(), left 0, right 0;while (right < n) {if (nums[right]) {swap(nums[left], nums[right]);left;}right;}} };

SpringBoot集成GraalVM创建高性能原生镜像

1. GraalVM 原生镜像的介绍 GraalVM原生镜像为部署和运行Java应用程序提供了一种新的方式。与Java虚拟机相比&#xff0c;原生镜像可以以更小的内存占用和更快的启动时间运行。 它们非常适用于使用容器镜像部署的应用程序&#xff0c;当与 "功能即服务"&#xff08…

短剧系统源码分享,快速搭建部署上线教程

一、短剧系统是什么&#xff1f; 短剧制作平台&#xff0c;作为一站式综合解决方案&#xff0c;集剧本创作、角色设计、场景搭建、视频编辑、便捷发布及深度数据分析能力于一身。该平台精准定位于助力企业利用短剧形式强化品牌传播力并驱动商业价值增长&#xff0c;无论企业是…

什么是IO多路复用?其原理和用途是什么?

什么是IO&#xff1f; IO&#xff1a;Input/Output&#xff0c;即数据的读取&#xff08;接收&#xff09;/写入&#xff08;发送&#xff09;操作&#xff0c;针对不同的数据存储媒介&#xff0c;大致可以分为网络 IO 和磁盘 IO 两种。在 Linux 系统中&#xff0c;为了保证系…

关于Excel表格隔行取列的方法

关于Excel表格隔行取列的方法 1、场景显示2、参考文章 1、场景显示 ①处的公式&#xff1a; INDEX($B3:$G3,(COLUMN(A1)*2)) $B与$G可以限制列不变&#xff1b; COLUMN(A1)返回1&#xff1b; 含义&#xff1a; 在选定区域选择偶数列的数据&#xff1b; 如果是奇数列的话是(COL…

查看RAM和Flash

0 Preface/Foreword 1 查看方法 1.1 map文件中查看 1.1.1 RAM可用情况 在map文件中&#xff0c;搜索字符串&#xff1a;free_ramcp 该字段表示剩余可用的RAM大小&#xff0c;前面对应的是hexadecimal的数值&#xff08;单位Byte&#xff09;&#xff0c;就是剩余可用的RA…

乱弹篇(39)请珍惜懂你的人

今日清晨&#xff0c;笔者照常去到古镇味江河畔垂钓&#xff0c;呼吸着凉爽晨风轻轻吹拂而来的大自然氧吧生产出的优质氧气......忽地&#xff0c;记起已经许久未履行义务了&#xff0c;所以本“人民体验官”今天要推广人民日报官方微博文化产品《有个真朋友是一生的福气》。 截…

Redis:十大数据类型

键&#xff08;key&#xff09; 常用命令 1. 字符串&#xff08;String&#xff09; 1.1 基本命令 set key value 如下&#xff1a;设置kv键值对&#xff0c;存货时长为30秒 get key mset key value [key value ...]mget key [key ...] 同时设置或者获取多个键值对 getrange…

实验21.实现 printf

已完成实验 已完成实验链接 简介 实验 21. 实现 printf 总结 简化系统调用和中断&#xff0c;用 eax 代表调用号参数&#xff0c;ebx,ecx,edx 来代表参数(syscall.c kernel.s) 添加 write 的系统调用接口(syscall.c, syscall-init.c, print.s) 注意&#xff1a;要更改 p…

基于N32L406MB EasyFlash参数(key-value)记录库移植

EasyFlash 感谢作者的分享https://github.com/armink/EasyFlash EasyFlash是一款开源的轻量级嵌入式Flash存储器库&#xff0c;方便开发者更加轻松的实现基于Flash存储器的常见应用开发 三大实用功能 ENV快速保存产品参数(key-value)&#xff0c;支持 写平衡&#xff08;磨…

最小例程上加OLED显示

最小例程上加OLED显示 本工程代码链接: https://ww0.lanzoul.com/i8lNa265gj7g 失效联系:qq2958360390 我们其实就加上这几个文件, 然后会调用就可以了, 具体的就看江协科技的OLED, 讲的很清楚, 我们这里只说应用, 我们的重点在使用. 下面跟着我来, 复制黏贴: 更详细请看哔哩…

从零开始学习机器学习,掌握AI未来的关键!

从零开始学习机器学习 1. 介绍1.1 人工智能&#xff08;AI&#xff09;概述1.2 机器学习在人工智能中的应用1.3 机器学习基础概念 2. 监督学习2.1 什么是监督学习2.2 回归分析2.3 分类问题2.4 模型评估和选择 3. 无监督学习3.1 什么是无监督学习3.2 聚类算法3.3 降维技术 4. 深…

(39)智能电池

文章目录 前言 1 通过任务规划器进行设置 2 补充信息 3 限制条件 4 参数说明 前言 虽然还不是很普遍&#xff0c;但智能电池更容易从飞行器上安装和拆卸&#xff0c;并且能够提供更多关于电池状态的信息&#xff0c;包括容量、单个电池电压、温度等。 ArduPilot 支持几种…

qt的信号槽连接成功,但是就是无法触发槽函数。你必须使用connect的第5个参数,Qt::QueuedConnection

signals:void sends(); public slots:void sl();//这种是默认自动连接&#xff0c;故第五个参数不用写connect(this,&MainWindow::sends,this,&MainWindow::sl);emit sends();void sl() {}如果connect连接成功&#xff0c;但是无法触发槽函数。你应该使用第五个参数&…

【vue-cli】vue-cli@2源码学习

vue-cli 2 源码 @vue/cli: 3.11.0创建项目 vue create 项目名称 @vue/cli: 2.x.x 创建项目 vue init webpack yhh-project 脚手架初始化项目流程: 下载vue/cli@2 源码 下载完成后初始化 npm i 创建项目 vue init webpack yhh-project vue-init: bin/vue-init #!/usr/bin/e…

与Zoom集成获取会议开始和结束事件

一、注册一个Zoom免费帐号&#xff08;需要在国外注册&#xff0c;国内不允许&#xff09; 二、进入Zoom应用市场创建一个应用 点击”发展”&#xff08;开发&#xff09;菜单&#xff0c;选择构建应用。 同意条款&#xff1a; 选择应用类型&#xff1a; 设置应用信息&#x…

Linux进程控制——进程程序替换、bash的模拟实现

文章目录 exec系列函数execlexeclp和execle execv系列函数bash的模拟实现实现思路完整代码其他问题 在学习进程的时候&#xff0c;我们想fork一个子进程&#xff0c;然后就可以给他布置任务了 但是如果我们分成两个人开发&#xff0c;父子进程分别负责不同的任务&#xff0c;等…