一、Linux SPI驱动框架
Linux下的spi驱动和i2c驱动十分类似,也可以分为三个部分:SPI核心,spi主机控制器(i2c中叫做i2c适配器),spi设备。
1.spi核心
spi核心提供了主机控制器的注册与注销方法、spi设备注册与注销方法、以及spi通信方法。源码位置位于kernel/drivers/spi/spi.c
2.spi主机控制器
spi的主机控制器用spi_master结构体描述:
//有部分删减
struct spi_master {
struct device dev;
struct list_head list;
s16 bus_num;
u16 num_chipselect;
u16 dma_alignment;
u16 mode_bits;
/* bitmask of supported bits_per_word for transfers */
u32 bits_per_word_mask;
#define SPI_BPW_MASK(bits) BIT((bits) - 1)
#define SPI_BIT_MASK(bits) (((bits) == 32) ? ~0U : (BIT(bits) - 1))
#define SPI_BPW_RANGE_MASK(min, max) (SPI_BIT_MASK(max) - SPI_BIT_MASK(min - 1))
/* limits on transfer speed */
u32 min_speed_hz;
u32 max_speed_hz;
/* other constraints relevant to this driver */
u16 flags;
#define SPI_MASTER_HALF_DUPLEX BIT(0) /* can't do full duplex */
#define SPI_MASTER_NO_RX BIT(1) /* can't do buffer read */
#define SPI_MASTER_NO_TX BIT(2) /* can't do buffer write */
#define SPI_MASTER_MUST_RX BIT(3) /* requires rx */
#define SPI_MASTER_MUST_TX BIT(4) /* requires tx */
/* lock and mutex for SPI bus locking */
spinlock_t bus_lock_spinlock;
struct mutex bus_lock_mutex;
/* flag indicating that the SPI bus is locked for exclusive use */
bool bus_lock_flag;
int (*setup)(struct spi_device *spi);
int (*transfer)(struct spi_device *spi,
struct spi_message *mesg);
bool queued;
struct kthread_worker kworker;
struct task_struct *kworker_task;
struct kthread_work pump_messages;
spinlock_t queue_lock;
struct list_head queue;
struct spi_message *cur_msg;
bool idling;
bool busy;
bool running;
bool rt;
bool auto_runtime_pm;
bool cur_msg_prepared;
bool cur_msg_mapped;
struct completion xfer_completion;
size_t max_dma_len;
void (*set_cs)(struct spi_device *spi, bool enable);
int (*transfer_one)(struct spi_master *master, struct spi_device *spi,
struct spi_transfer *transfer);
void (*handle_err)(struct spi_master *master,
struct spi_message *message);
/* gpio chip select */
int *cs_gpios;
/* statistics */
struct spi_statistics statistics;
/* DMA channels for use with core dmaengine helpers */
struct dma_chan *dma_tx;
struct dma_chan *dma_rx;
/* dummy data for full duplex devices */
void *dummy_rx;
void *dummy_tx;
};
transfer函数就是spi的通信函数,和 i2c_algorithm 中的 master_xfer 函数一样,控制器数据传输函数。
spi的主机端最终会和硬件上的spi控制器打交道,因此spi主机控制器的驱动一般都是由芯片厂商完成的,他们的主要工作就是实现transfer函数。
spi核心提供的常用spi主机控制器的API函数如下:
/*申请一个spi主机控制器*/
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
/*释放一个spi主机控制器*/
static inline void spi_master_put(struct spi_master *master)
/*向内核注册一个spi主机控制器*/
int spi_register_master(struct spi_master *master)
/*从内核注销一个spi主机控制器*/
void spi_unregister_master(struct spi_master *master)
RK3288开发板的spi0主机控制器的设备树节点如下:
spi0: spi@ff110000 {
compatible = "rockchip,rk3288-spi", "rockchip,rk3066-spi";
clocks = <&cru SCLK_SPI0>, <&cru PCLK_SPI0>;
clock-names = "spiclk", "apb_pclk";
dmas = <&dmac_peri 11>, <&dmac_peri 12>;
dma-names = "tx", "rx";
interrupts = <GIC_SPI 44 IRQ_TYPE_LEVEL_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&spi0_clk &spi0_tx &spi0_rx &spi0_cs0>;
reg = <0x0 0xff110000 0x0 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
/*spi0引脚配置节点*/
spi0 {
spi0_clk: spi0-clk {
rockchip,pins = <5 12 RK_FUNC_1 &pcfg_pull_up>;
};
spi0_cs0: spi0-cs0 {
rockchip,pins = <5 13 RK_FUNC_1 &pcfg_pull_up>;
};
spi0_tx: spi0-tx {
rockchip,pins = <5 14 RK_FUNC_1 &pcfg_pull_up>;
};
spi0_rx: spi0-rx {
rockchip,pins = <5 15 RK_FUNC_1 &pcfg_pull_up>;
};
spi0_cs1: spi0-cs1 {
rockchip,pins = <5 16 RK_FUNC_1 &pcfg_pull_up>;
};
};
与之对应的spi0主机控制器的驱动源码位于kernel/drivers/spi/spi-rockchip.c
static const struct of_device_id rockchip_spi_dt_match[] = {
{ .compatible = "rockchip,px30-spi", },
{ .compatible = "rockchip,rv1108-spi", },
{ .compatible = "rockchip,rk3036-spi", },
{ .compatible = "rockchip,rk3066-spi", },
{ .compatible = "rockchip,rk3188-spi", },
{ .compatible = "rockchip,rk3228-spi", },
{ .compatible = "rockchip,rk3288-spi", },
{ .compatible = "rockchip,rk3368-spi", },
{ .compatible = "rockchip,rk3399-spi", },
{ },
};
MODULE_DEVICE_TABLE(of, rockchip_spi_dt_match);
static struct platform_driver rockchip_spi_driver = {
.driver = {
.name = DRIVER_NAME,
.pm = &rockchip_spi_pm,
.of_match_table = of_match_ptr(rockchip_spi_dt_match),
},
.probe = rockchip_spi_probe,
.remove = rockchip_spi_remove,
};
可以看到rockchip_spi_dt_match匹配表中有很多芯片的compatible属性,只要设备树中有一个与之匹配,那么rockchip_spi_driver结构体的rockchip_spi_probe函数就会执行。
RK3288开发板的spi的通信函数为rockchip_spi_transfer_one。
static int rockchip_spi_transfer_one(
struct spi_master *master,
struct spi_device *spi,
struct spi_transfer *xfer)
rockchip_spi_transfer_one函数最终会调用下面两个函数来完成与硬件的数据交互。
static int rockchip_spi_pio_transfer(struct rockchip_spi *rs)
static void rockchip_spi_pio_reader(struct rockchip_spi *rs)
3.spi设备和驱动
spi设备通过spi_device结构体描述。
struct spi_device {
struct device dev;
struct spi_master *master;
u32 max_speed_hz;
u8 chip_select;
u8 bits_per_word;
u16 mode;
#define SPI_CPHA 0x01 /* clock phase */
#define SPI_CPOL 0x02 /* clock polarity */
#define SPI_MODE_0 (0|0) /* (original MicroWire) */
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04 /* chipselect active high? */
#define SPI_LSB_FIRST 0x08 /* per-word bits-on-wire */
#define SPI_3WIRE 0x10 /* SI/SO signals shared */
#define SPI_LOOP 0x20 /* loopback mode */
#define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */
#define SPI_READY 0x80 /* slave pulls low to pause */
#define SPI_TX_DUAL 0x100 /* transmit with 2 wires */
#define SPI_TX_QUAD 0x200 /* transmit with 4 wires */
#define SPI_RX_DUAL 0x400 /* receive with 2 wires */
#define SPI_RX_QUAD 0x800 /* receive with 4 wires */
int irq;
void *controller_state;
void *controller_data;
char modalias[SPI_NAME_SIZE];
int cs_gpio; /* chip select gpio */
/* the statistics */
struct spi_statistics statistics;
};
spi设备驱动通过spi_driver结构体描述。
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
struct device_driver driver;
};
当spi设备和驱动匹配后,spi_driver结构体的probe函数就会执行。
spi_driver的常用的API函数如下:
/*spi_driver注册函数*/
#define spi_register_driver(driver)
/*spi_driver注销函数*/
static inline void spi_unregister_driver(struct spi_driver *sdrv)
4.spi设备和驱动匹配过程
spi设备和驱动的匹配是由spi总线完成的,spi总线定义如下:
struct bus_type spi_bus_type = {
.name = "spi",
.dev_groups = spi_dev_groups,
.match = spi_match_device,
.uevent = spi_uevent,
};
EXPORT_SYMBOL_GPL(spi_bus_type);
spi_match_device就是匹配函数,分析如下:
static int spi_match_device(struct device *dev, struct device_driver *drv)
{
const struct spi_device *spi = to_spi_device(dev);
const struct spi_driver *sdrv = to_spi_driver(drv);
/* 设备树匹配方式 */
if (of_driver_match_device(dev, drv))
return 1;
/* ACPI匹配方式 */
if (acpi_driver_match_device(dev, drv))
return 1;
/*传统id_table匹配方式*/
if (sdrv->id_table)
return !!spi_match_id(sdrv->id_table, spi);
/*spi_board_info 中 modalias 成员变量和 device_driver 中的 name 成员变量*/
return strcmp(spi->modalias, drv->name) == 0;
}
二、Linux SPI 设备驱动编写(w25q32)
w25q32是一款32M bit的nor flash芯片。标准spi模式下最大频率可达80MHz。
1.设备树修改
spi外设需要一个片选引脚,这个引脚可以用spi主机控制器提供的,也就是让硬件自动控制片选,但是也可以使用普通GPIO,让软件控制片选。这里我们使用软件控制片选。
&pinctrl {
w25q32{
w25q32_cs:w25q32_cs{
rockchip,pins = <7 3 RK_FUNC_GPIO &pcfg_pull_up>;
};
};
}
&spi0
{
status = "okay";
w25q32@0{
compatible = "winbond,w25q32";
spi-max-frequency = <1000000>;
reg = <0>;
cs-gpios = <&gpio7 3 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&w25q32_cs>;
};
};
w25q32是引脚配置节点,配置w25q32片选脚。如果使用的是硬件控制片选,那么就不需要此节点。
w25q32@0,w25q32是设备名字,可以随意。0表示是挂在spi0片选0下面的,这个需要根据实际情况修改。
spi-max-frequency是spi总线最大通信频率,可以在设备树中指定,也可以在驱动代码中修改spi_device结构体的max_speed_hz成员,从而进行动态调整频率。
2.驱动程序编写
这里使用的是字符设备驱动框架,实际上这种nor flash也可以使用块设备框架。当设备和驱动匹配后,驱动程序的probe函数就会执行,将会打印读到的设备ID信息。
/**
* @file spi_driver.c
* @brief
* @author xzx
* @date 2020-9-2
* @version A001
* @copyright
* W25Q32 一共64个block 每个block 64KB
* 每个block 分为16个sector 每个sector 4KB
* 每个sector分为16个page 每个page 256字节
* 擦除的最小单位是扇区 也就是4KB
* 页写指令最多可写入256字节 超过256字节将会覆盖前面的
*/
#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/gpio.h>/*gpio接口函数*/
#include <asm/uaccess.h>/*__copy_from_user 接口函数*/
#define DEVICE_NAME "rk3288_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;//w25q32指令
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;//创建一个w25q32类
struct device *device;//创建一个w25q32设备 该设备是需要挂在w25q32类下面的
int major;//主设备号
dev_t dev_id;
struct spi_device *spi; /*spi设备*/
int cspin; /*片选脚*/
struct mutex lock;
w25qxx_data_def data;
}w25qxx_typdef;
static w25qxx_typdef w25qxx_dev;//定义一个w25q32设备
/*函数声明*/
static int w25qxx_read_bytes(w25qxx_typdef *w25q32,unsigned int address,unsigned char* buf,int count);
static int w25q32_spi_read_write(w25qxx_typdef *w25q32)
{
struct spi_device *spi = w25q32->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 = w25q32->data.tx_buf;
xfer[0].len = w25q32->data.tx_len;
buf = (unsigned char *)(w25q32->data.tx_buf);
//printk("write:len = %d\n",xfer[0].len);
xfer[1].rx_buf = w25q32->data.rx_buf;
xfer[1].len = w25q32->data.rx_len;
spi_message_init(&msg);
spi_message_add_tail(&xfer[0], &msg);
if(w25q32->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 *)(w25q32->data.rx_buf);
//printk("read:len = %d\n",xfer[1].len);
return ret;
}
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 = w25q32_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 = w25q32_spi_read_write(&w25qxx_dev);
spi_cs_disable();
}
/**
* 获取w25qxx状态寄存器
* @param[in] w25q32 w25qxx设备结构体
* @return 大于0:状态寄存器的值 负值:失败
* @note
* //BIT7 6 5 4 3 2 1 0
* //SPR RV TB BP2 BP1 BP0 WEL BUSY
*/
static int w25qxx_get_sr(w25qxx_typdef *w25q32)
{
int ret = -EINVAL;
unsigned char tx_buf[1];//
unsigned char rx_buf[1];//
spi_cs_enable();
tx_buf[0] = W25X_ReadStatusReg;
w25q32->data.tx_buf= tx_buf;
w25q32->data.tx_len = 1;
w25q32->data.rx_buf = rx_buf;
w25q32->data.rx_len = 1;
ret = w25q32_spi_read_write(w25q32);
spi_cs_disable();
if(ret < 0)
{
printk("w25qxx_get_sr failed \n");
return ret;
}
return rx_buf[0];
}
/**
* 设置w25qxx状态寄存器
* @param[in] w25q32 w25qxx设备结构体
* @param[in] value 需要设置的新值
* @return 0:执行成功 负值:失败
* //BIT7 6 5 4 3 2 1 0
* //SPR RV TB BP2 BP1 BP0 WEL BUSY
* 只有bit7 bit5 bit4 bit3 bit2这几位可写
*/
static int w25qxx_set_sr(w25qxx_typdef *w25q32,unsigned char value)
{
int ret = -EINVAL;
unsigned char tx_buf[2];//
spi_cs_enable();
tx_buf[0] = W25X_ReadStatusReg;
tx_buf[1] = value;
w25q32->data.tx_buf= tx_buf;
w25q32->data.tx_len = 2;
w25q32->data.rx_len = 0;/*rx_len设置为0 表示不需要接收数据*/
ret = w25q32_spi_read_write(w25q32);
spi_cs_disable();
if(ret != 0)
{
printk("w25qxx_set_sr failed\n");
return ret;
}
return 0;
}
/**
* 设置w25qxx的制造商ID
* @param[in] w25q32 w25qxx设备结构体
* @param[in] value 需要设置的新值
* @return 0:执行成功 负值:失败
* @note w25qxx芯片的制造商ID为EFh 后一个字节代表容量
* W25Q80--13h W25Q16--14h W25Q32--15h W25Q128--17h
*/
static int w25qxx_get_id(w25qxx_typdef *w25q32)
{
int ret = -EINVAL;
unsigned char tx_buf[4];//
unsigned char rx_buf[2];//
spi_cs_enable();
tx_buf[0] = W25X_ManufactDeviceID;/*读取ID指令*/
tx_buf[1] = 0x0;
tx_buf[2] = 0x0;
tx_buf[3] = 0x0;
w25q32->data.tx_buf= tx_buf;
w25q32->data.tx_len = 4;
w25q32->data.rx_buf = rx_buf;
w25q32->data.rx_len = 2;
ret = w25q32_spi_read_write(w25q32);
spi_cs_disable();
if(ret != 0)
{
printk("w25qxx_get_id failed %d\n",ret);
return ret;
}
return (rx_buf[0] << 8 | rx_buf[1]);
}
/**
* 判断W25Qxx是否空闲?
* @return 0:空闲 1:忙
* @note w25qxx状态寄存器的bit0
*/
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;/*w25q32空闲*/
}
}
/* REVISIT: at HZ=100, this is sloooow */
msleep(10);
} while(1);
return 1;
}
/**
* 擦除w25qxx的一个扇区
* @param[in] w25q32 w25qxx设备结构体
* @param[in] address 扇区地址
* @return 0:执行成功 负值:失败
* @note 需要关闭写保护,并等待flash操作完成
*/
static int w25qxx_erase_sector(w25qxx_typdef *w25q32,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);
w25q32->data.tx_buf= tx_buf;
w25q32->data.tx_len = 4;
w25q32->data.rx_len = 0;
ret = w25q32_spi_read_write(w25q32);
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;
}
/**
* 判断扇区是否需要擦除
* @param[in] old FLASH内旧数据首地址
* @param[in] new 新写入数据首地址
* @param[in] count 数据长度
* @return 1:需要擦除 0:不需要
* @note
*/
static int w25qxx_need_erase(unsigned char*old,unsigned char*new,int count)
{
int ret = 1;
int i;
unsigned char p;
/*
算法第1步:old 求反, new 不变
old new
1101 0101
~
= 0010 0101
算法第2步: old 求反的结果与 new 位与
old new
& 0010 0101
=0000
算法第3步: 结果为0,则表示无需擦除. 否则表示需要擦除
原理:
问题可以表述为:判断两个数据的对应的每一位是否存在0->1的变化
将原数据按位取反,所有位的0都变成了1,再与新数据 & 操作
结果不为0,说明新数据有些位是1 而原来是0 故需要擦除
*/
for ( i = 0; i < count; i++)
{
p = *old++;
p = ~p;
if((p &(*new++))!=0)
{
return 1;
}
}
return 0;
/*for(i=0;i< count;i++)
{
if(*old++ != 0xFF )return 1;
}
return 0;*/
}
/**
* 读取若干字节数据
* @param[in] w25q32 w25qxx设备结构体
* @param[in] address 需要读取的首地址
* @param[out] buf 缓存区
* @param[in] count 需要读取的数据长度
* @return 0:执行成功 负值:失败
* @note 可以读取任意字节 但不要超过FALSH容量
*/
static int w25qxx_read_bytes(w25qxx_typdef *w25q32,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);
w25q32->data.tx_buf= tx_buf;
w25q32->data.tx_len = 4;
w25q32->data.rx_buf = buf;
w25q32->data.rx_len = count;
ret = w25q32_spi_read_write(w25q32);
spi_cs_disable();
if(ret != 0)
{
printk("read@%d ,%d bytes failed %d\n",address,count,ret);
return ret;
}
return ret;
}
/**
* 写入1页数据
* @param[in] w25q32 w25qxx设备结构体
* @param[in] address 需要写入的首地址
* @param[in] buf 缓存区
* @param[in] count 需要写入的数据长度
* @return 0:执行成功 负值:失败
* @note 最多写入256字节数据
*/
static int w25qxx_write_page(w25qxx_typdef *w25q32,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);
w25q32->data.tx_buf= tx_buf;
w25q32->data.tx_len = count+4;
w25q32->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],w25q32->data.tx_len);
ret = w25q32_spi_read_write(w25q32);
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;
}
/**
* 写入多页数据
* @param[in] w25q32 w25qxx设备结构体
* @param[in] address 需要写入的首地址
* @param[in] buf 缓存区
* @param[in] count 需要写入的数据长度
* @return 0:执行成功 负值:失败
* @note 最多写入4096字节数据,,本函数带擦除功能
*/
static int w25qxx_write_pages(w25qxx_typdef *w25q32,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(w25q32,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(w25q32,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(w25q32,address,buf,count);
return ret;
}
else
{
do
{
printk("address=%d\n,need_to_write=%d\n",address,need_to_write);
ret = w25qxx_write_page(w25q32,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;
}
/**
* 写入大量字节数据
* @param[in] w25q32 w25qxx设备结构体
* @param[in] address 需要写入的首地址
* @param[in] buf 缓存区
* @param[in] count 需要写入的数据长度
* @return 0:执行成功 负值:失败
* @note 写入数据不要超过FALSH最大容量,本函数带擦除功能
*/
static int w25qxx_write_more_bytes(w25qxx_typdef *w25q32,unsigned int address,unsigned char* buf,int count)
{
int ret = -EINVAL;
unsigned int num_of_sector,remain_of_sector,sector_offset;
unsigned int sector_first_address,need_to_write;
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(w25q32,address,buf,count);
return ret;
}
else
{
do
{
ret = w25qxx_write_pages(w25q32,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;
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);
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("w25q32 probe!\n");
/*获取设备节点*/
w25qxx_dev.node = of_find_node_by_path("/spi@ff110000/w25q32@0");
if(w25qxx_dev.node == NULL)
{
printk("device-tree:not found w25q32!\r\n");
return -1;
}
/*读取W25Q32设备节点的compatible属性值*/
ret = of_property_read_string(w25qxx_dev.node,"compatible",&string);
if(ret == 0)
{
printk("%s\n",string);
}
/*申请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);
//gpio_free(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);
/*申请设备号*/
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);
/*创建一个eeprom_class类*/
w25qxx_dev.class = class_create(THIS_MODULE, "eeprom_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_0; /*spi flash对应的模式*/
//w25qxx_dev.spi->bits_per_word = 8;
spi_setup(w25qxx_dev.spi);
mutex_init(&dev->lock);
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");
/*删除at24c02类*/
cdev_del(&w25qxx_dev.cdev);
/*释放at24c02设备号*/
unregister_chrdev_region(w25qxx_dev.dev_id, 1);
/*注销at24c02设备*/
device_destroy(w25qxx_dev.class, w25qxx_dev.dev_id);
/*注销at24c02类*/
class_destroy(w25qxx_dev.class);
gpio_free(w25qxx_dev.cspin);
return 0;
}
static const struct of_device_id w25qxx_of_match[] = {
{.compatible = "winbond,w25q32"},
{},
};
static const struct spi_device_id w25q32_id[] = {
{ "xxxx", 0 },
{},
};
static struct spi_driver w25qxx_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "w25q32",
.of_match_table = w25qxx_of_match,
},
.probe = w25qxx_probe,
.remove = w25qxx_remove,
.id_table = w25q32_id,
};
static int __init w25qxx_init(void)
{
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("W25Q32 driver");
MODULE_AUTHOR("xzx2020");
3.测试APP
测试app首先向FLASH指定地址写入1000字节数据,然后再读出数据。
#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 buf_size 1000
void print_array(const char *title,char *buf,int count)
{
int i = 0;
printf(title);
for(i=0;i<count;i++)
{
printf(" %d",buf[i]);
}
printf("\n");
}
int main(int argc, char *argv[])
{
int fd;
int i;
int ret;
int count = buf_size;
char buf[buf_size]={0};
int offset = 0;
/*判断传入的参数是否合法*/
if(argc != 2)
{
printf("Usage:error\n");
return -1;
}
/*解析传入的参数*/
offset =atoi(argv[1]);
printf("offset = %d\n",offset);
/*打开设备文件*/
fd = open("/dev/rk3288_spi",O_RDWR);
if(fd < 0)
{
printf("open dev fail fd=%d\n",fd);
close(fd);
return fd;
}
/*缓存数组赋值*/
for(i=0;i<buf_size;i++)
{
buf[i] = i;
}
/*写入数据*/
lseek(fd,offset,SEEK_SET);
ret = write(fd,buf,buf_size);
if(ret < 0)
{
printf("write to w25q32 error\n");
close(fd);
return ret;
}
/*打印数据*/
print_array("write to w25q32:",buf,count);
/*清空缓冲区*/
memset(buf,0,buf_size);
/*读取数据*/
ret = lseek(fd,offset,SEEK_SET);
printf("lseek = %d\n",ret);
ret = read(fd,buf,count);
if(ret < 0)
{
printf("read from w25q32 error\n");
close(fd);
return ret;
}
/*打印数据*/
print_array("read from w25q32:",buf,count);
close(fd);
return 0;
}