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-CS | GPIO3_A1 SPI1_CS0_M1 |
2-DO | GPIO3_C2 SPI1_MISO_M1 |
3-WP | 3.3V |
4-GND | GND |
5-DI | GPIO3_C1 SPI1_MOSI_M1 |
6-CLK | GPIO3_C3 SPI1_CLK_M1 |
7-HOLD | NC |
8-VCC | 3.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);是怎么处理的,留个问题。
如有侵权,或需要完整代码,请及时联系博主。
相关文章:
RK3568笔记四十九:W25Q64驱动开发(硬件SPI1)
若该文为原创文章,转载请注明原文出处。 一、SPI介绍 串行外设接口 (Serial Peripheral interface) 简称 SPI,是一种高速的,全双工,同步的通信总线,并 且在芯片的管脚上只占用四根线,节约了芯片的管脚。 …...
TypeScript 定义不同的类型(详细示例)
还是大剑师兰特:曾是美国某知名大学计算机专业研究生,现为航空航海领域高级前端工程师;CSDN知名博主,GIS领域优质创作者,深耕openlayers、leaflet、mapbox、cesium,canvas,webgl,ech…...
[工具推荐]前端加解密之Burp插件Galaxy
如果觉得该文章有帮助的,麻烦师傅们可以搜索下微信公众号:良月安全。点个关注,感谢师傅们的支持。 免责声明 本号所发布的所有内容,包括但不限于信息、工具、项目以及文章,均旨在提供学习与研究之用。所有工具安全性…...
课题项目结题测试的作用
课题项目结题测试是课题项目研究过程中的一个重要环节,它对于确保课题项目的质量和成果具有重要的作用。本文将详细介绍课题项目结题测试的作用。 一、确保课题项目质量 课题项目结题测试是对课题项目研究成果的全面评估和检测。通过结题测试,可以对课…...
中国工商银行长春分行开展“工驿幸福 健康财富”长辈客群康养活动
中国工商银行长春分行作为国有大行,持续完善有温度、专业化、安全稳健的养老场景服务,以工行驿站为依托、以长辈客群养老需求为中心,积极对接社区构建敬老、康养的“金融泛金融”工行驿站服务生态,进一步提升长辈客群的到店体验。…...
机器学习 第十四章
目录 前言 一、隐马尔可夫模型 二、马尔可夫随机场 三、条件随机场 四、学习和推断 1.变量消去 2.信念传播 五、近似推断 1.MCMC采样 2.变分推断 六、话题模型 总结 前言 机器学习最重要的任务是根据一些已观察到的证据来对感兴趣的未知变量进行估计和推测。概率模…...
未来RPA财税的发展前景
近年来,全球数字化进程持续提速,越来越多企业受到效率及运营成本的压力,正努力寻求企业增长发展的新路径,而财务作为企业战略的“大脑”,成为企业数字化转型的重要突破口。RPA技术由于能够自动化各种重复性和繁琐的任务…...
快速设置 terminator 透明背景
看图,按步骤设置后⭐重启一个终端则为透明效果 效果展示:...
Redis+Unity 数据库搭建
游戏中需要存放排行榜等数据,而且是实时存放,所以就涉及到数据库的问题。这里找服务器大神了解到可以用Redis来做存储,免费的效率极高。 Redis的搭建参考上文的文章,同时也感谢这位网友。 搭建Redis 并测试数据 搭建Redis 1.下…...
WebTracing:如何使用一款SDK实现前端全链路监控
引言 在产品的开发和运营过程中我们经常会遇到一些问题,如用户反馈说无法对某某商品下单,而另一位负责运营的同事也提到某某广告在手机上打不开。尽管这些问题被多次报告,但我们却难以复现这些故障,这让团队感到十分棘手。如何有效地记录项目中的错误并能够重现这些问题,…...
【Story】编程迷航:从 “ 我怎么才学会 ? ” 到 “ 我怎么这么厉害 ! ”
目录 大学生编程入门指南:选择语言、制定计划与避坑技巧1. 选择适合的编程语言1.1 Python1.2 Java1.3 C/C1.4 JavaScript1.5 SQL 2. 制定有效的学习计划2.1 设定明确的目标2.2 制定学习时间表2.3 选择学习资源2.4 实践和项目 3. 避免常见学习陷阱3.1 避免过度焦虑3.…...
基于“日志审计应用”的 DNS 日志洞察实践
作者:羿莉 (萧羿) 基础背景 DNS(Domain Name System) [ 1] 是任何网络活动的基础。它将易于记忆的域名转换为机器能够理解的 IP 地址。监控 DNS 服务可以帮助用户识别网络活动并保持系统安全。出于合规和安全性的考虑,公司通常要求对网络日志进行存储和…...
大学按照学科类别、办学层次、教育性质分类有哪些?创龙教仪一文带您了解
大学的分类多种多样,主要可以从学科类别、办学层次、教育性质等方面进行划分。 一、按学科类别划分 综合类大学 特点:学科门类齐全,文理渗透,科研实力强。 优势:拥有较多的国家级重点学科和实验室,师资…...
数据结构与算法 - 递归
一、递归 1. 概述 定义:在计算机科学中,递归是一种解决计算问题的方法,其中解决方案取决于同一类问题的更小子集。 比如单链表递归遍历的例子: void f(Node node) {if(node null) {return;}println("before:" node…...
python:plotly 网页交互式数据可视化工具
pip install plotly plotly-5.22.0-py3-none-any.whl pip install plotly_express 包含:GDP数据、餐厅的订单流水数据、鸢尾花 Iris数据集 等等 pip show plotly Name: plotly Version: 5.22.0 Summary: An open-source, interactive data visualization librar…...
聊一聊 webpack5性能优化有哪些?
介绍 此文章基于webpack5来阐述 webpack性能优化较多,可以对其进行分类 优化打包速度,开发或者构建时优化打包速度(比如exclude、catch等)优化打包后的结果,上线时的优化(比如分包处理、减小包体积、CDN…...
公布一批神马爬虫IP地址,真实采集数据
一、数据来源: 1、这批神马爬虫IP来源于尚贤达猎头公司网站采集数据; 2、数据采集时间段:2023年10月-2024年1月; 3、判断标准:主要根据用户代理是否包含“YisouSpider”,具体IP没做核实。 二、神马爬虫主…...
uni-app全局文件与常用API
文章目录 rpx响应式单位import导入css样式及scss变量用法与static目录import导入css样式uni.scss变量用法 pages.json页面路由globalStyle的属性pages设置页面路径及窗口表现tabBar设置底部菜单选项及iconfont图标 vite.config中安装插件unplugin-auto-import自动导入vue和unia…...
连接器表面缺陷检测方案
连接器是一种用于连接电子设备或电路中不同部件之间的组件,通常用于传输电力、信号或数据。连接器的设计和类型各不相同,以适应不同设备和应用的需求。连接器用于连接电子设备之间的电线、电缆或电路板,实现信号传输和电力供应。连接器设计应…...
React项目动态设置index.html中的<title>标签内容
1. 安装react-helmet-async npm install react-helmet-async -S2. 如下修改App.tsx即可 import { ConfigProvider } from "antd"; import { RouterProvider } from "react-router-dom"; import { router } from "//router"; import { Helmet, …...
大龄程序员转型攻略:拥抱人工智能,开启新征程
前言 随着科技的飞速发展,人工智能浪潮席卷全球,相关岗位炙手可热。在这个背景下,许多大龄程序员开始思考如何转型,以适应时代的变化。结合自身编程基础,大龄程序员可以学习机器学习、深度学习算法,投身于…...
Jenkins保姆笔记(1)——基于Java8的Jenkins安装部署
前言 记录分享下Jenkins的相关干货知识。分2-3篇来介绍Jenkins的安装部署以及使用。还是和以前一样,文章不介绍较多概念和细节,多介绍实践过程,以战代练,来供大家学习和理解Jenkins 概念 Jenkins是一个开源的自动化服务器&…...
学习c语言第18天(字符串和内存函数)
1.函数介绍 1.1 strlen size_t(就是无符号整形) strlen(const char * str); 字符串已经\0作为结束标志,strlen函数返回的是在字符串中\0前面出现的字符个数(不包 含\0) 参数指向的字符串必须要以\0结束。 注意函数的返回值为size_t,…...
无心剑七绝《潘展乐神》
七绝潘展乐神 潘江陆海忘情游 展志凌云筑玉楼 乐创全球新纪录 神姿英发舞金钩 2024年8月1日 平水韵十一尤平韵 潘展乐神,这四个字,如同四座矗立的丰碑,分别代表了潘展乐在游泳领域的卓越成就、豪情壮志、快乐创新和非凡风采。无心剑的这首…...
Linux C++ 开发1 - 搭建C++开发环境
1. 安装GCC/GDB 1.1. 安装1.2. 校验 2. 安装CMake 2.1. 安装2.2. 校验 3. 安装IDE 3.1. VSCode3.2. CLion 1. 安装GCC/GDB 1.1. 安装 # 更新软件源 sudo apt update # 通过以下命令安装编译器和调试器 sudo apt install build-essential gdb Ubuntu 默认情况下没有提供C/C…...
吴恩达老师机器学习-ex4
梯度检测没有实现。有借鉴网上的部分 导入相关库,读取数据 因为这次的数据是mat文件,需要使用scipy库中的loadmat进行读取数据。 通过对数据类型的分析,发现是字典类型,查看该字典的键,可以发现又X,y等关…...
C语言-函数例题
函数经典例题 1、编写一个函数实现该功能:从键盘输入一个字串符, 再输入两个正整数 m 和 n, 输出字符串中从 m 开始, 连续 n 个字符。例如, 输入 abcdefg,2,3,输出 bcd. #include <stdio.h> /*作者: zcy日期:功能描述:编写…...
鸿蒙应用框架开发【多HAP】程序框架
多HAP 介绍 本示例展示多HAP开发,简单介绍了多HAP的使用场景,应用包含了一个entry HAP和两个feature HAP,两个feature HAP分别提供了音频和视频播放组件,entry中使用了音频和视频播放组件。 三个模块需要安装三个hap包ÿ…...
PG如何实现跨大版本升级
数据库进行升级,是一个再正常不过的功能,比如功能的需要,遇到BUG,安全漏洞等等,具体升级包含子版本升级,主版本升级。如果用过ORACLE的朋友,一定知道,在ORACLE中,如果要实…...
JDK 8 升级 17 及 springboot 2.x 升级 3.x 指南
JDK 8 升级 17 简介 从 JDK 8 升级到 JDK 17 的过程中,有几个主要的变化,特别是 Java Platform Module System (JPMS) 的引入,以及一些包路径的调整。以下是与 JDK 17 相关的一些重要变化: Java Platform Module System (JPMS) …...
网站建设客户说没用/广告网
1.“我可以向你问路吗?” “到那里?” “到你心里。” 2.“我可以向你借一块钱吗?” “为什么?” “我想打电话告诉我妈,我刚遇到我的梦中情人。”或“我要打电话给你妈妈谢谢她。” 3.“你爸爸是小偷吗?” “不是。” “那他怎么能把灿烂的星星偷来放在你双眸…...
做网站选大公司好还是小公司好/网站策划是做什么的
以下文字转载自:http://blog.csdn.net/lbj05/article/details/6297214,对原作者表示感谢和敬意。iPhone SDK提供了多种动画手段,UIView、UIImageView和CALayer都支持动画。但如何处理常见的gif动画呢?UIWebView提供了答案…...
在网页做动态图片的网站/杭州网络推广有限公司
虚拟机快照操作 1.什么是Bash shell? 它就是命令解释器,将用户输入的指令翻译给内核程序,内核处理完成之后将结果返回给Bash 2.Bash shell的用途? 几乎能完成所有的操作: 文件管理 (创建 移动 复制 删除 编…...
做网站推广费用/网站模板哪里好
EnvironmentRepository的默认实现使用Git后端,这对于管理升级和物理环境以及审核更改非常方便。要更改存储库的位置,可以在Config Server中设置“spring.cloud.config.server.git.uri”配置属性(例如application.yml)。如果您使用…...
门户网站盈利模式/关键词优化系统
中国是个拥有5000年文明史的多民族国家,地域与文化延伸亚洲大部,汉风与汉字多被日本,韩国,越南等邻国采用。中国曾经兴衰,如今大门重开,接 纳来自世界的风潮,概念与技术,也包括 Web …...
做党建需要关注网站/seo职业规划
是的,你没看错。我看“HTML5是Flash杀手”,“Flash过时了”之类的句子都看到想吐了。所以我在这里说说自己对“Apple vs Adobe”之间的口角,以及“Flash vs HTML5”谁更优秀的看法。并且解释一下,为什么在我看来,应是F…...