测试几天发现一个bug,就是无法一次读取32个字节的数据,1-31,33,128,512都试过了,唯独无法读取32个字节,驱动未报错,但是读取的都是0,找不到原因,估计应该是全志iic驱动的问题,暂时没有折腾,尽量避开32字节读取吧,32字节写入是没问题的。
使用的字符驱动,可以读写任意字节(32字节读取除外),可以使用lseek设置读写地址,首先设置内核设备数,在对应的iic节点下添加fram支持。
fram: fram@50 {
compatible = "general,iic_fram";
reg = <0x50>;
};
驱动代码如下
// SPDX-License-Identifier: GPL-2.0+
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/leds.h>
#include <linux/mutex.h>
#include <linux/gpio/consumer.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/uaccess.h>
#include <linux/device.h> //自动创建/dev设备节点需要
#include <linux/kdev_t.h> //设备号用到的头文件和宏函数
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
typedef unsigned int u32;
typedef unsigned short u16;
typedef unsigned char u8;
typedef volatile unsigned int vu32;
typedef volatile unsigned short vu16;
typedef volatile unsigned char vu8;
typedef unsigned int const uc32; /* Read Only */
typedef unsigned short const uc16; /* Read Only */
typedef unsigned char const uc8; /* Read Only */
#define DEVICE_NAME "iic_fram" // 设备名字
#define DTS_COMPATIBLE "general,iic_fram" //设备树中对应的COMPATIBLE信息名称
//注意:fram使用的是 MB85RC16PNF-G-JNERE1 进行测试2KB容量,FRAM的通讯有点不一样,芯片地址为4bit,然后3bit寄存器高地址,1bit读写标识,最终依旧使用的是8bit寄存器模式,但是地址范围0-7FF
//设备驱动私有结构体数据定义
struct fram_type {
int fram_init_finished;
struct i2c_client* fram_client;
struct mutex mutex_lock;//定义互斥锁
struct class* device_class; //注册后的设备节点class
struct device* device; //注册的设备
dev_t devno; //设备号
struct cdev cd;
int size; //容量信息
};
static struct fram_type* sg_fram = NULL; //再iic设备注册后进行初始化,非NULL意味着硬件初始化正常,在probe中进行初始化
//注册驱动时传入参数,参数为farm容量信息,默认为2KB,单位字节
static int SIZE = -1; //通过注册驱动的时候传入参数,如 insmode iic_fram SIZE=4096 实现容量设置
module_param(SIZE, int, S_IRUSR); //S_IRUSR在include/linux/stat.h
/*************************************************************************************************************************
*函数 : int fram_read_data(struct i2c_client* client, u16 addr, u16 ByteCount, u8* pData, bool isKernel)
*功能 : iic驱动读取寄存器数据
*参数 : client:句柄;addr:寄存器地址;ByteCount:要读取的数据数量;pData:数据缓冲区;isKernel:是否是内核读取,如果是内核读取将不需要进行内核数据与用户空间数据转换
*返回 : <0 错误,其它:读取的字节数
*依赖 : 底层宏定义
*作者 : cp1300@139.com
*时间 : 2024-03-04
*最后修改时间 : 2024-03-04
*说明 :
*************************************************************************************************************************/
int fram_read_data(struct i2c_client* client, u16 addr, u16 ByteCount, u8 __user* pData, bool isKernel)
{
int ret;
u8 addr_buff[1];
u8* pkbuff;
struct i2c_msg msgs[] = {
//写命令
{
.addr = client->addr | ((addr >> 8) & 0x07),
.flags = 0,
.len = 1, //数据长度为1字节的地址
.buf = addr_buff, //寄存器地址
},
//读取数据
{
.addr = client->addr | ((addr >> 8) & 0x07),
.flags = I2C_M_RD,
.len = ByteCount, //数据长度为n字节的数据
.buf = NULL, //buf-等会设置
}
};
//dev_info(&client->dev, "i2c_smbus_read_16bit_i2c_block_data addr=%d ByteCount=%d client->addr=%d\r\n", addr, ByteCount, client->addr);
//申请内核内存,准备读取数据
pkbuff = (u8*)kmalloc(ByteCount, GFP_KERNEL | GFP_DMA | __GFP_ZERO); //申请内存-iic会用到DMA,内存要连续
if (pkbuff == NULL)
{
printk("fram read out of memory! kmalloc(%dB)\r\n", ByteCount);
return -1;
}
msgs[1].buf = pkbuff; //读取的数据存放到缓冲区
//准备寄存器地址数据
addr_buff[0] = addr & 0xFF; //地址低位
ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); //写入命令,读取数据
if (ret <= 0)
{
dev_err(&client->dev, "i2c_transfer read addr=%d ByteCount=%d failed!\r\n", addr, ByteCount);
ret = -1;
}
else
{
if (isKernel == true) //当前处于内核空间
{
memcpy(pData, pkbuff, ByteCount);
ret = ByteCount; //返回数据长度
}
else
{
ret = copy_to_user(pData, pkbuff, ByteCount); //读取成功,将数据拷贝到用户空间
if (ret != 0)
{
printk("fram copy_to_user error(%dB)\r\n", ret);
ret = -1;
}
else
{
ret = ByteCount; //返回数据长度
}
}
}
kfree(pkbuff); //释放申请的内存
return ret;
}
/*************************************************************************************************************************
*函数 : int fram_write_data(struct i2c_client* client, u16 addr, u16 ByteCount, u8* pData)
*功能 : iic驱动写入寄存器数据
*参数 : client:句柄;addr:寄存器地址;ByteCount:要读取的数据数量;pData:数据缓冲区
*返回 : <0 错误,其它:读取的字节数
*依赖 : 底层宏定义
*作者 : cp1300@139.com
*时间 : 2024-03-04
*最后修改时间 : 2024-03-04
*说明 :
*************************************************************************************************************************/
int fram_write_data(struct i2c_client* client, u16 addr, u16 ByteCount, const u8 __user* pData)
{
int ret;
u8* buf;
struct i2c_msg msgs[] = {
//写数据命令
{
.addr = client->addr | ((addr >> 8) & 0x07),
.flags = 0,
.len = ByteCount + 1, //数据长度为2字节的地址,n字节的数据
//.buf = ®, //buf等会赋值,需要申请内存
}
};
//dev_info(&client->dev, "i2c_smbus_write_16bit_i2c_block_data write addr=%d ByteCount=%d\r\n", addr, ByteCount);
buf = (u8*)kmalloc(ByteCount + 1, GFP_KERNEL | GFP_DMA | __GFP_ZERO); //申请内存-iic会用到DMA,内存要连续
if (buf == NULL)
{
dev_err(&client->dev, "i2c_smbus_write_16bit_i2c_block_data out of memory! kmalloc(%dB)\r\n", ByteCount + 1);
return -1;
}
msgs[0].buf = buf; //记录申请的内存
//准备数据
buf[0] = addr & 0xFF; //地址低位
ret = copy_from_user(&buf[1], pData, ByteCount); //用户空间数据拷贝到内核空间
if (ret != 0) {
printk("fram copy_from_user error(%dB)\r\n", ret);
ret = -EFAULT;
}
else
{
ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); //写入数据
if (ret == ARRAY_SIZE(msgs))
{
ret = ByteCount; //返回写入的数据长度
}
else
{
dev_err(&client->dev, "i2c_transfer write addr=%d ByteCount=%d failed!\r\n", addr, ByteCount);
ret = -1;
}
}
kfree(buf); //释放申请的内存
return ret;
}
/*************************************************************************************************************************
*函数 : int fram_Init(struct i2c_client* client)
*功能 : fram初始化
*参数 : client:iic句柄
*返回 : 0:初始化成功;其它:初始化失败
*依赖 : 底层宏定义
*作者 : cp1300@139.com
*时间 : 2024-03-03
*最后修改时间 : 2024-03-03
*说明 :
*************************************************************************************************************************/
int fram_Init(struct i2c_client* client)
{
u8 i;
struct fram_type* fram = i2c_get_clientdata(client); //获取私有数据
int ret;
u8 temp;
if (fram == NULL)
{
dev_err(&client->dev, "i2c_get_clientdata(client) null\r\n");
return -1;
}
//读取地址0,只要能读取到就认为初始化成功
for (i = 0; i < 3; i++)
{
ret = fram_read_data(client, 0, 1, &temp, true);
if (ret < 0)
{
dev_err(&client->dev, "Failed to read 0x00\r\n");
msleep(5);
}
else
{
dev_info(&client->dev, "addr 0x00:0x%X\r\n", temp);
break;
}
}
if (ret < 0) return -1;
return 0;
}
//===========================================================================================
//标准文件接口相关
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int device_open(struct inode* inode, struct file* filp)
{
if (sg_fram == NULL)
{
printk("fram not initialized\r\n");
return -1;
}
//dev_info(&sg_fram->fram_client->dev, "device_open\n");
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t device_read(struct file* filp, char __user* buf, size_t cnt, loff_t* offt)
{
u32 offset = (u32)*offt;
int ret;
if (sg_fram == NULL)
{
printk("fram not initialized\r\n");
return -1;
}
if (buf == NULL)
{
printk("buf is NULL!\r\n");
return -EFAULT;
}
if (cnt == 0 || cnt > sg_fram->size || (cnt + offset) > sg_fram->size)
{
printk("fram Read out of range cnt=%d offt=%d(max size:%dB)\r\n", cnt, offset, sg_fram->size);
return 0;
}
//读取数据
mutex_lock(&sg_fram->mutex_lock); //阻塞式上互斥锁,抢不到就一直阻塞
ret = fram_read_data(sg_fram->fram_client, (u16)offset, (u16)cnt, buf, false);
mutex_unlock(&sg_fram->mutex_lock); //解锁
return ret;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t device_write(struct file* filp, const char __user* buf, size_t cnt, loff_t* offt)
{
u32 offset = (u32)*offt;
int ret;
if (sg_fram == NULL)
{
printk("fram not initialized\r\n");
return -1;
}
if (buf == NULL)
{
printk("buf is NULL!\r\n");
return -EFAULT;
}
if (cnt == 0 || cnt > sg_fram->size || (cnt + offset) > sg_fram->size)
{
printk("fram write out of range cnt=%d offt=%d(max size:%dB)\r\n", cnt, offset, sg_fram->size);
return 0;
}
mutex_lock(&sg_fram->mutex_lock); //阻塞式上互斥锁,抢不到就一直阻塞
ret = fram_write_data(sg_fram->fram_client, (u16)offset, (u16)cnt, buf);
mutex_unlock(&sg_fram->mutex_lock); //解锁
return ret;
}
/*
* @description : 设置文件读写偏移
* @param - filp : 设备文件,表示打开的文件描述符
* @param - off : 读写偏移
* @param - whence : 光标参考位置
* @return : 当前文件的纸质位置,如果为负值,表示写入失败
*/
static loff_t device_llseek(struct file* filp, loff_t off, int whence)
{
//struct scull_dev* dev = filp->private_data;
loff_t newpos;
//printk("device_llseek off=%u whence=%d\r\n", (u32)off, whence);
switch (whence)
{
case 0: //SEEK_SET 从开始的偏移
{
newpos = off;
if (newpos >= sg_fram->size) newpos = sg_fram->size - 1; //防止超出范围
}break;
case 1: //SEEK_CUR 在当前位置加上偏移
{
newpos = filp->f_pos + off;
if (newpos >= sg_fram->size) newpos = sg_fram->size - 1; //防止超出范围
}break;
case 2: //SEEK_END 偏移位置文件结尾 之外
{
newpos = sg_fram->size - 1; //不允许超出文件
}break;
default: return -EINVAL;
}
if (newpos < 0) return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int device_release(struct inode* inode, struct file* filp)
{
//printk("fram_release\r\n");
return 0;
}
//申请i2c资源,顺便进行初始化
static int device_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int ret;
struct fram_type* fram;
dev_info(&client->dev, "fram_probe\n");
//判断iic适配器是否正常
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
{
return -ENODEV;
}
//申请内核内存资源
fram = devm_kzalloc(&client->dev, sizeof(struct fram_type), GFP_KERNEL);
if (!fram)
{
return -ENOMEM;
}
fram->fram_init_finished = 0; //初始化未完成
fram->fram_client = client; //记录i2c接口指针
fram->device_class = NULL; //设备节点无效
fram->device = NULL; //设备节点无效
//需要提前设置,否则在 fram_Init 中需要调用 i2c_get_clientdata () 将返回空
i2c_set_clientdata(client, fram); // 将fram作为i2c次设备的私有数据区中的设备驱动私有数据
if (SIZE > 0)
{
dev_info(&client->dev, "SIZE=%dB\n", SIZE &0xFFFF);
}
else
{
SIZE = 2048; //默认为2KB
}
if (SIZE < 128) SIZE = 128;
if (SIZE > 0x7FF) SIZE = 0x7FF; //最大大小限制为0x7FF
fram->size = SIZE; //初始化FRAM大小
dev_info(&client->dev, "FRAM SIZE:%dB\n", fram->size);
//开始芯片硬件探测与初始化
ret = fram_Init(client); //初始化并设置初值,也可以通过注册驱动的时候传入参数实现初始化
if (ret < 0)
{
dev_err(&client->dev, "invalid init fram\n");
return -1;
}
mutex_init(&fram->mutex_lock);//初始化互斥锁
sg_fram = fram; //记录全局设备数据,设备硬件初始化完成了
fram->fram_init_finished = 1; //初始化完成
dev_info(&client->dev, "fram probe succeeded\n");
return 0;
}
//移除iic驱动
static int device_remove(struct i2c_client* client)
{
struct fram_type* fram = i2c_get_clientdata(client); //获取私有数据
dev_info(&client->dev, "fram_remove\n");
//释放互斥锁信号
if (fram != NULL && fram->fram_init_finished)
{
mutex_destroy(&fram->mutex_lock);
}
return 0;
}
//===========================================================================================
//iic接口相关
static const struct of_device_id fram_match_table[] = {
{
.compatible = DTS_COMPATIBLE,
},
{},
};
MODULE_DEVICE_TABLE(of, fram_match_table);
static const struct i2c_device_id fram_id[] = {
{ DTS_COMPATIBLE, 0 },
{},
};
MODULE_DEVICE_TABLE(i2c, fram_id);
static struct i2c_driver fram_driver = {
.driver = {
.name = DEVICE_NAME,
.owner = THIS_MODULE,
.of_match_table = fram_match_table,
},
.probe = device_probe, //注册IIC
.remove = device_remove,
.id_table = fram_id,
};
//module_i2c_driver(fram_driver); //需要使用 i2c_add_driver 在 init中进行注册iic适配器,不能直接使用宏
//设备驱动操作相关接口结构体
static struct file_operations sg_device_opera_fops = {
.owner = THIS_MODULE,
.open = device_open, //打开驱动文件接口
.read = device_read, //读取接口
.write = device_write, //写文件接口
.release = device_release, //释放文件接口
.llseek = device_llseek, //设置文件偏移
//.ioctl = device_ioctl, //参数设置接口
};
//驱动接口-驱动入口函数-初始化与申请资源
static int __init device_init(void)
{
int retvalue;
//printk("device_module_init\n");
retvalue = i2c_add_driver(&fram_driver); //添加iic驱动
if (retvalue) {
printk("%s i2c_add_driver failed! %d\n", __func__, retvalue);
return -ENODEV;
}
if (sg_fram == NULL) //设备硬件初始化失败
{
printk("initialization failed!\r\n");
return -EIO;
}
//注册字符设备驱动
retvalue = alloc_chrdev_region(&sg_fram->devno, 0, 1, DEVICE_NAME); //自动申请设备号,从0开始,申请1个
if (retvalue < 0) {
pr_err("alloc_chrdev_region failed!(%d)", retvalue);
i2c_del_driver(&fram_driver); //移除iic设备
return retvalue;
}
printk("MAJOR is %d\n", MAJOR(sg_fram->devno));
printk("MINOR is %d\n", MINOR(sg_fram->devno));
cdev_init(&sg_fram->cd, &sg_device_opera_fops); //字符驱动结构体初始化
retvalue = cdev_add(&sg_fram->cd, sg_fram->devno, 1); //注册字符设备驱动,数量1
if (retvalue < 0) {
pr_err("cdev_add failed!(%d)", retvalue);
i2c_del_driver(&fram_driver); //移除iic设备
unregister_chrdev_region(sg_fram->devno, 1); //注销一个范围的设备号
return retvalue;
}
//自动在/dev目录下创建设备节点
sg_fram->device_class = class_create(THIS_MODULE, DEVICE_NAME); 创建类
if (NULL == sg_fram->device_class)
{
printk(KERN_INFO "create calss failed\n");
cdev_del(&sg_fram->cd);
unregister_chrdev_region(sg_fram->devno, 1); //注销一个范围的设备号
i2c_del_driver(&fram_driver); //移除iic设备
return -1;
}
else
{
sg_fram->device = device_create(sg_fram->device_class, NULL, sg_fram->devno, NULL, DEVICE_NAME); //创建设备
if (NULL == sg_fram->device)
{
printk(KERN_INFO "create device failed\n");
cdev_del(&sg_fram->cd);
unregister_chrdev_region(sg_fram->devno, 1);
class_destroy(sg_fram->device_class);
i2c_del_driver(&fram_driver); //移除iic设备
return -1;
}
}
printk("%s succeeded\n", DEVICE_NAME);
return 0;
}
//驱动接口-驱动出口函数-注销资源
static void __exit device_exit(void)
{
printk("device_module_exit\n");
i2c_del_driver(&fram_driver); //移除iic设备
cdev_del(&sg_fram->cd); //注销字符设备驱动
unregister_chrdev_region(sg_fram->devno, 1); //注销设备号
device_del(sg_fram->device);
printk(KERN_INFO "delete device /dev/my_char_dev \n");
class_destroy(sg_fram->device_class);
printk(KERN_INFO "delete device /sys/class/my_char_dev \n");
}
module_init(device_init);
module_exit(device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("cp1300@139.com");
测试代码
void fram_test(void)
{
int fd;
u8 buff[512];
int len;
int i;
fd = open("/dev/iic_fram", O_RDWR);
if(fd < 0)
{
printf("open iic_frame error:%d\r\n", fd);
return ;
}
lseek(fd, 0, SEEK_SET);
len = read(fd, buff, 32);
if(len <= 0)
{
printf("read iic_frame error:%d\r\n", fd);
return ;
}
for(i = 0;i < len;i ++)
{
printf("0x%02X \t", buff[i]);
}
printf("\r\n");
//lseek(fd, 0, SEEK_SET);
/*for(i = 0;i < len;i ++)
{
buff[i] = i+0xF0;
}
len = write(fd, buff, len);
if(len <= 0)
{
printf("write iic_frame error:%d\r\n", fd);
return ;
}*/
close(fd);
}
读取32字节全部是0
试试33字节就正常了
仔细看底层驱动的打印信息
sunxi-i2c sunxi-i2c2: drv-mode: dma read data end
读取32字节的时候,这个打印都结束了才提示DMA读取完成,很有可能就是因为数据都没读取完成,但是底层已经返回了,原因未知,珍爱生命,远离linux驱动,凑合着用吧(⊙o⊙)