firefly-rk3288j开发板–linux I2C实验之eeprom驱动
1 准备工作
开发板: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
2 硬件原理图
2.1 开发板I2C接口
2.2 EEPROM模块原理图
3 修改设备树文件
3.1 驱动参数配置
I2C 的参数配置最主要就是 I2C 频率的配置,可配 I2C frequency 除了与芯片有关外,主要是由 I2C SCL rise time 决定的,因为 I2C 协议标准里面对上升沿和下降沿时间有规定要求特别是上升沿时间,如果超 过了协议规定的最大值,则 I2C 通讯可能失败,下面是协议里面规定的最大最小值范围,下图表示了二 者之间的关系:
上升沿 Tr 和下降沿 Tf,需要用示波器测量,参考下面示图:
3.2 配置DTS
设备树文件位于内核kernel/arch/arm/boot/dts目录下,我们需要打开rk3288.dtsi、rk3288-linux.dtsi、rk3288-firefly-port.dtsi、rk3288-firefly-aio.dtsi.对于i2c设备只需要打开rk3288-firefly-aio.dtsi文件,添加i2c4设备节点:
&i2c4 {
status = "okay";
//i2c-scl-rising-time-ns = <400>;
//i2c-scl-falling-time-ns = <20>;
clock-frequency = <100000>;
at24c64: at24c64@50 {
compatible = "firefly,24c64";
reg = <0x50>;
};
};
clock-frequency: 默认 frequency 为 100k 可不配置,其它 I2C 频率需要配置,最大可配置频率由 i2c- scl-rising-time-ns 决定;例如配置 400k,clock-frequency=<400000>。
i2c-scl-rising-time-ns:SCL 上升沿时间由硬件决定,改变上拉电阻可调节该时间,需通过示波器量 测, 参考上图;例如测得 SCL 上升沿 400ns,i2c-scl-rising-time-ns=<400>。(默认可以不配置,但必须保证当前的上升沿时间不能超过所配置频率下的 I2C 标准所定义的最大上升沿时间)
i2c-scl-falling-time-ns: SCL 下降沿时间, 一般不变, 等同于 i2c-sda-falling-time-ns。(默认也可以不配置)
编译内核,输入如下命令
./build.sh kernel
./build.sh updateimg
4 API函数
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
5 AT24C64驱动编写
#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/i2c.h> /*i2c相关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 "eeprom"
#define DEVICE_SIZE 256
typedef struct
{
struct device_node *node;//设备树节点
struct cdev cdev;//定义一个cdev结构体
struct class *class;//创建一个at24cxx类
struct device *device;//创建一个at24cxx设备 该设备是需要挂在at24cxx类下面的
int major;//主设备号
dev_t dev_id;
struct i2c_client *client; /*适配器 probe函数中会填充此变量*/
/*使用SMBUS协议方式读写*/
int use_smbus;
int use_smbus_write;
struct mutex lock;
}at24xx_typdef;
static at24xx_typdef at24cxx_dev;//定义一个AT24Cxx设备
static unsigned char io_limit = 128; /*一次最多读取128字节*/
static unsigned char write_timeout = 25;/*i2c通信超时时间*/
static unsigned char at24cxx_page_size = 8;/*at24cxx 每页8字节*/
static ssize_t at24_eeprom_read(at24xx_typdef *at24, char *buf,unsigned offset, size_t count)
{
struct i2c_msg msg[2];
u8 msgbuf[2];
struct i2c_client *client;
unsigned long timeout, read_time;
int status, i;
memset(msg, 0, sizeof(msg));
/*获取设备信息*/
client = at24->client;
if (count > io_limit)
count = io_limit;
if (at24->use_smbus)
{
/* Smaller eeproms can work given some SMBus extension calls */
if (count > I2C_SMBUS_BLOCK_MAX)
count = I2C_SMBUS_BLOCK_MAX;
}
else
{
i = 0;
msgbuf[i++] = offset;/*读取首地址*/
/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr;
msg[0].buf = msgbuf;
msg[0].len = i;
/* msg[1]读取数据 */
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].buf = buf; /* 读取数据缓冲区*/
msg[1].len = count; /* 读取数据长度 */
}
/*超时时间设置为write_timeout毫秒*/
timeout = jiffies + msecs_to_jiffies(write_timeout);
do {
read_time = jiffies;
if (at24->use_smbus) /*使用SMBUS协议*/
{
status = i2c_smbus_read_i2c_block_data_or_emulated(client, offset,count, buf);
}
else /*普通I2C协议*/
{
status = i2c_transfer(client->adapter, msg, 2);
if (status == 2)
status = count;
}
if (status == count)
return count;
/* REVISIT: at HZ=100, this is sloooow */
msleep(1);
} while (time_before(read_time, timeout));
return -ETIMEDOUT;
}
static ssize_t at24_eeprom_write(at24xx_typdef *at24, char *buf,unsigned offset, size_t count)
{
struct i2c_client *client;
struct i2c_msg msg;
ssize_t status = 0;
unsigned long timeout, write_time;
int i = 0;
/*获取设备信息*/
client = at24->client;
/* 最大写入数据是1页 */
if (count > at24cxx_page_size)
count = at24cxx_page_size;
/*msg.buf申请内存*/
msg.buf = kmalloc(count+2,GFP_KERNEL);
if(!msg.buf)
return -ENOMEM;
/* 不使用SMBUS协议 需要填充msg */
if (!at24->use_smbus)
{
msg.addr = client->addr;
msg.flags = 0; /*标记为写数据*/
msg.buf[i++] = offset; /*写的起始地址*/
memcpy(&msg.buf[i], buf, count);
msg.len = i + count; /*写数据的长度*/
}
timeout = jiffies + msecs_to_jiffies(write_timeout);
do {
write_time = jiffies;
if (at24->use_smbus_write)
{
switch (at24->use_smbus_write)
{
case I2C_SMBUS_I2C_BLOCK_DATA:
status = i2c_smbus_write_i2c_block_data(client,offset, count, buf);break;
case I2C_SMBUS_BYTE_DATA:
status = i2c_smbus_write_byte_data(client,offset, buf[0]);break;
}
if (status == 0)
status = count;
}
else
{
status = i2c_transfer(client->adapter, &msg, 1);
if (status == 1)
status = count;
}
if (status == count)
{
kfree(msg.buf);
return count;
}
/* REVISIT: at HZ=100, this is sloooow */
msleep(1);
} while (time_before(write_time, timeout));
kfree(msg.buf);
return -ETIMEDOUT;
}
static int at24cxx_open(struct inode *inode, struct file *filp)
{
/*使用普通I2C模式读写*/
at24cxx_dev.use_smbus = 0;
at24cxx_dev.use_smbus_write = 0;
filp->private_data = &at24cxx_dev;
printk("open at24cxx success\n");
return 0;
}
static int at24cxx_release(struct inode* inode ,struct file *filp)
{
return 0;
}
static int at24cxx_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{
int ret = -EINVAL;
char *buffer;/*缓冲区*/
unsigned char pages;/*页数*/
unsigned char num;/*不足一页剩下的字节数*/
unsigned char pos = filp->f_pos;/*写入的地址*/
int i = 0;
at24xx_typdef *dev = (at24xx_typdef *)filp->private_data;
printk("w_count = %d w_pos = %d\n",count,pos);
buffer =(char *)kmalloc(count,GFP_KERNEL);
if (!buffer)
return -ENOMEM;
pages = count / at24cxx_page_size;
num = count % at24cxx_page_size;
/*将需要写入的数据拷贝到内核空间的缓冲区*/
ret = copy_from_user((void *)buffer,buf,count);
mutex_lock(&dev->lock);
for(i = 0; i < pages; i++)
{
ret = at24_eeprom_write(dev,&buffer[i*at24cxx_page_size],pos,at24cxx_page_size);
if(ret < 0)
{
printk("at24cxx write error\n");
kfree(buffer);
return ret;
}
pos += 8;
}
if(num)
{
ret = at24_eeprom_write(dev,&buffer[i*at24cxx_page_size],pos,num);
if(ret < 0)
{
printk("at24cxx write error\n");
kfree(buffer);
return ret;
}
}
mutex_unlock(&dev->lock);
/*释放缓冲区内存*/
kfree(buffer);
return 0;
}
static ssize_t at24cxx_read(struct file *filp,char __user *buf, size_t count,loff_t *f_pos)
{
int ret = -EINVAL;
char *buffer;/*数据缓存区*/
unsigned char pos = filp->f_pos; /*读取位置*/
at24xx_typdef *dev = (at24xx_typdef *)filp->private_data;
printk("r_count = %d r_pos = %d\n",count,pos);
buffer =(char *)kmalloc(count,GFP_KERNEL);
if(!buffer)
return -ENOMEM;
mutex_lock(&dev->lock);
ret = at24_eeprom_read(dev,buffer,pos,count);
if(ret < 0 )
{
printk("at24cxx read error\n");
kfree(buffer);
return ret;
}
/*将读取到的数据返回用户层*/
ret = copy_to_user(buf,(void *)buffer,ret);
mutex_unlock(&dev->lock);
/*释放缓冲区内存*/
kfree(buffer);
return 0;
}
loff_t at24cxx_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 = DEVICE_SIZE - offset;
break;
default:
printk("cmd not supported\n");
break;
}
if(pos < 0 || pos > DEVICE_SIZE)
{
printk("error: pos > DEVICE_SIZE !\n");
ret = -EINVAL;
return ret;
}
file->f_pos = pos;
ret = offset;
return ret;
}
static struct file_operations at24cxx_fops={
.owner = THIS_MODULE,
.open = at24cxx_open,
.write = at24cxx_write,
.read = at24cxx_read,
.release = at24cxx_release,
.llseek = at24cxx_llseek,
};
static int at24cxx_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
int ret = -1;
const char *string = NULL;
at24xx_typdef *dev = &at24cxx_dev;
printk("at24cxx probe!\n");
/*获取设备节点*/
at24cxx_dev.node = of_find_node_by_path("/i2c@ff160000/at24c64@50");
if(at24cxx_dev.node == NULL)
{
printk("find node by path fialed!\r\n");
return -1;
}
/*读取at24cxx设备节点的compatible属性值*/
ret = of_property_read_string(at24cxx_dev.node,"compatible",&string);
if(ret == 0)
{
printk("%s\n",string);
}
/*申请设备号*/
alloc_chrdev_region(&at24cxx_dev.dev_id,0,1,DEVICE_NAME);
/*初始化一个cdev*/
cdev_init(&at24cxx_dev.cdev,&at24cxx_fops);
/*向cdev中添加一个设备*/
cdev_add(&at24cxx_dev.cdev,at24cxx_dev.dev_id,1);
/*创建一个eeprom_class类*/
at24cxx_dev.class = class_create(THIS_MODULE, "eeprom_class");
if(at24cxx_dev.class == NULL)
{
printk("class_create failed\r\n");
return -1;
}
/*在eeprom_class类下创建一个eeprom_class设备*/
at24cxx_dev.device = device_create(at24cxx_dev.class, NULL, at24cxx_dev.dev_id, NULL, DEVICE_NAME);
/*每个设备都会分配一个client*/
at24cxx_dev.client = client;
printk("slave address is %x\n",client->addr);
mutex_init(&dev->lock);
return 0;
}
static int at24cxx_remove(struct i2c_client *client)
{
printk("at24cxx remove!\n");
/*删除at24cxx类*/
cdev_del(&at24cxx_dev.cdev);
/*释放at24cxx设备号*/
unregister_chrdev_region(at24cxx_dev.dev_id, 1);
/*注销at24cxx设备*/
device_destroy(at24cxx_dev.class, at24cxx_dev.dev_id);
/*注销at24cxx类*/
class_destroy(at24cxx_dev.class);
return 0;
}
static const struct of_device_id at24cxx_of_match[] = {
{.compatible = "firefly,24c64"},
{},
};
static const struct i2c_device_id at24cxx_id[] = {
{ "xxxx", 0 },
{},
};
static struct i2c_driver at24cxx_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "firefly,24c64",
.of_match_table = at24cxx_of_match,
},
.probe = at24cxx_probe,
.remove = at24cxx_remove,
.id_table = at24cxx_id,
};
static int __init at24cxx_init(void)
{
return i2c_add_driver(&at24cxx_driver);
}
static void at24cxx_exit(void)
{
i2c_del_driver(&at24cxx_driver);
printk("module exit ok\n");
}
module_init(at24cxx_init);
module_exit(at24cxx_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("at24cxx driver");
MODULE_AUTHOR("lsjml2022");
6 编写测试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>
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,i,ret;
int count = 128;
char offset = 0;
char writebuf[128],readbuf[128];
/*判断传入的参数是否合法*/
if(argc != 2)
{
printf("Usage:error!\n");
return -1;
}
/*解析传入的参数*/
offset =atoi(argv[1]);
printf("offset = %d\n",offset);
/*打开设备文件*/
fd = open("/dev/eeprom",O_RDWR);
if(fd < 0)
{
printf("open eeprom fail fd = %d\n",fd);
close(fd);
return fd;
}
/*缓存数组赋值*/
memset(writebuf, 0xaa, sizeof(writebuf));
/*写入数据*/
lseek(fd,offset, SEEK_SET);
ret = write(fd, writebuf, sizeof(writebuf));
if(ret < 0)
{
printf("write to at24c64 error\n");
close(fd);
return ret;
}
/*打印数据*/
print_data("write to at24c64:", writebuf, count);
/*读取数据*/
ret = lseek(fd,offset,SEEK_SET);
printf("lseek = %d\n",ret);
memset(readbuf, 0, sizeof(readbuf));
ret = read(fd, readbuf, count);
if(ret < 0)
{
printf("read from at24c64 error\n");
close(fd);
return ret;
}
/*打印数据*/
print_data("read from at24c64:",readbuf,count);
/*比较写入数据与读出数据是否一致*/
ret = memcmp(readbuf, writebuf, 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;
}
7 编译驱动程序和测试APP
7.1 编译驱动程序
KDIR:=/rk3288_linux/rk3288_linux_release_20220607/kernel
obj-m:=at24c64.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文件拷贝到开发板上并加载
7.2 编译测试App
输入如下命令编译测试 eepromApp.c 这个测试程序:
arm-linux-gnueabihf-gcc eepromApp.c -o eepromApp
编译成功以后就会生成 eepromApp 这个应用程序
7.3 运行测试
编译出来的.ko 和 eepromApp 这两个文件拷贝到/lib/modules/4.4.194目录中,重启开发板,进入目录/lib/modules/4.4.194中输入加载.ko驱动模块:
insmod at24c64.ko
驱动加载成功以后就可以使用eepromApp软件来测试驱动是否正常,输入如下命令:
./eepromApp /dev/eeprom
rmmod at24c64.ko //卸载驱动模块