开发板:aio-rk3288j
SDK版本:rk3288_linux_release_20210304
下载工具:Linux_Upgrade_Tool_v2.1
内核版本:4.4.194
文件系统:buildroot
Ubuntu版本:18.04
交叉编译工具:gcc version 6.3.1 20170404
SPI 是一种高速的,全双工,同步串行通信接口,用于连接微控制器、传感器、存储设备等,本文以W25Q64模块为例简单介绍 SPI 使用。
SPI 以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少 4 根线,分别是:
CS 片选信号
SCLK 时钟信号
MOSI 主设备数据输出、从设备数据输入
MISO 主设备数据输入,从设备数据输出
Linux 内核用 CPOL 和 CPHA 的组合来表示当前 SPI 的四种工作模式:
CPOL=0,CPHA=0 SPI_MODE_0
CPOL=0,CPHA=1 SPI_MODE_1
CPOL=1,CPHA=0 SPI_MODE_2
CPOL=1,CPHA=1 SPI_MODE_3
SPI的CPOL,表示当SCLK空闲idle的时候,其电平的值是低电平0还是高电平1:CPOL=0,时钟空闲idle时候的电平是低电平,所以当SCLK有效的时候,就是高电平,就是所谓的active-high。
CPOL=1,时钟空闲idle时候的电平是高电平,所以当SCLK有效的时候,就是低电平,就是所谓的active-low。
CPHA=0,表示第一个边沿:
对于CPOL=0,idle时候的是低电平,第一个边沿就是从低变到高,所以是上升沿;
对于CPOL=1,idle时候的是高电平,第一个边沿就是从高变到低,所以是下降沿;
CPHA=1,表示第二个边沿:
对于CPOL=0,idle时候的是低电平,第二个边沿就是从高变到低,所以是下降沿;
对于CPOL=1,idle时候的是高电平,第一个边沿就是从低变到高,所以是上升沿;
spi_alloc_master 函数用于申请 spi_master
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
注册函数
int spi_register_master(struct spi_master *master)
注销函数
void spi_unregister_master(struct spi_master *master)
设备树文件位于内核kernel/arch/arm/boot/dts目录下,我们需要打开rk3288.dtsi、rk3288-linux.dtsi、rk3288-firefly-port.dtsi、rk3288-firefly-aio.dtsi.d打开rk3288-firefly-aio.dtsi文件,添加spi设备节点:
&spi2 {
status = "okay";
w25q64: w25q64@0{
compatible = "firefly,w25q64";
spi-max-frequency = <24000000>;
reg = <0>;
wp-gpio = <&gpio7 2 GPIO_ACTIVE_HIGH>;
/*cs-gpios = <&gpio8 7 GPIO_ACTIVE_LOW>;*/
pinctrl-names = "default";
pinctrl-0 = <&w25q64_wp>; /*<&w25q64_cs>; */
spi-cpha;
spi-cpol;
};
};
&pinctrl {
w25q64 {
/*w25q64_cs: w25q64-cs {
rockchip,pins = <8 7 RK_FUNC_GPIO &pcfg_pull_up>;
};*/
w25q64_wp: w25q64-wp {
rockchip,pins = <7 2 RK_FUNC_GPIO &pcfg_pull_up>;
};
};
}
编译内核,输入如下命令
./build.sh kernel
./build.sh updateimg
#include //模块加载卸载函数
#include //内核头文件
#include //数据类型定义
#include //file_operations结构体
#include //class_create等函数
#include
#include /*包含printk等操作函数*/
#include /*设备树操作相关的函数*/
#include /*gpio接口函数*/
#include
#include /*platform device*/
#include /*spi相关api*/
#include /*内核延时函数*/
#include /*kmalloc、kfree函数*/
#include /*cdev_init cdev_add等函数*/
#include /*gpio接口函数*/
#include /*__copy_from_user 接口函数*/
#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_set_sr(w25qxx_typdef *w25q64,unsigned char value)
// {
// int ret = -EINVAL;
// unsigned char tx_buf[2];//
// spi_cs_enable();
// tx_buf[0] = W25X_ReadStatusReg;
// tx_buf[1] = value;
// w25q64->data.tx_buf= tx_buf;
// w25q64->data.tx_len = 2;
// w25q64->data.rx_len = 0;/*rx_len设置为0 表示不需要接收数据*/
// ret = w25q64_spi_read_write(w25q64);
// spi_cs_disable();
// if(ret != 0)
// {
// printk("w25qxx_set_sr failed\n");
// return ret;
// }
// return 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 int 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, 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,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@ff130000/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 = "firefly,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("lsjml2022");
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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;
}
KDIR:=/rk3288_linux/rk3288_linux_release_20220607/kernel
obj-m:=w25q64.o
PWD:=$(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD)
clean:
rm -rf *.ko *.order *.symvers *.cmd *.o *.mod.c *.tmp_versions .*.cmd .tmp_versions
输入如下命令编译出驱动模块文件:
make -j8
编译成功后会生成一个.ko文件拷贝到开发板上并加载
输入如下命令编译测试 norflashApp.c 这个测试程序:
arm-linux-gnueabihf-gcc norflashApp.c -o norflashApp
编译成功以后就会生成 norflashApp 这个应用程序
编译出来的.ko 和 norflashApp 这两个文件拷贝到/lib/modules/4.4.194目录中,重启开发板,进入目录/lib/modules/4.4.194中输入加载.ko驱动模块:
insmod w25q64.ko
驱动加载成功以后就可以使用norflashApp 软件来测试驱动是否正常,输入如下命令:
./norflashApp /dev/spi
rmmod w25q64.ko //卸载驱动模块