linux设备驱动之 i2c设备驱动 at24c08驱动程序分析
linux-2.6.22源码分析\linux-2.6.22\drivers\i2c\chips\eeprom.c为例,分析i2c设备驱动程序的原理
1.从驱动的入口函数开始分析
eeprom_init
>i2c_add_driver(&eeprom_driver)
>i2c_register_driver(THIS_MODULE, driver)
>is_newstyle_driver(driver) //这是一处宏定义:#define is_newstyle_driver(d) ((d)->probe || (d)->remove)
/*用于判断是否是新风格的驱动,新风格的驱动i2c_driver成员的probe与remove函数指针都为空*/
接下来填充i2c_driver的owner与bus成员
>driver_register(&driver->driver)
>bus_add_driver(&driver->driver)也就是(struct device_driver * drv)//添加成功返回0
>list_add_tail(&driver->list,&drivers)//把该i2c_driver添加到链表drivers全局变量中
>list_for_each_entry(adapter, &adapters, list) //遍历adapters适配器链表的所有成员
{driver->attach_adapter(adapter);} //调用i2c_driver->attach_adapter
在eeprom.c中定义了一个全局变量:
static struct i2c_driver eeprom_driver = {
.driver = {
.name = "eeprom",
},
.id = I2C_DRIVERID_EEPROM,
.attach_adapter = eeprom_attach_adapter,
.detach_client = eeprom_detach_client,
};
2.i2c设备的探测过程分析
在1中遍历调用了适配器链表中的每一个成员的attach_adapter成员函数,来完成i2c设备的探测
driver->attach_adapter(adapter)
等效于
eeprom_attach_adapter
>i2c_probe(adapter, &addr_data, eeprom_detect)
//当探测到i2c设备后会调用第三个参数所指的函数:也就是函数eeprom_detect。
/*在i2c.h头文件中定义了addr_data全局变量
*static struct i2c_client_address_data addr_data = { \
* .normal_i2c = normal_i2c, \
* .probe = probe, \
* .ignore = ignore, \
* .forces = forces, \
*}
*/
函数原型为:
i2c_probe(struct i2c_adapter *adapter,
struct i2c_client_address_data *address_data,
int (*found_proc) (struct i2c_adapter *, int, int))
>判断是否强制识别 if(address_data->forces),如果设置了i2c_client_address_data的
成员.forces则强制识别,采取相应的措施。
>i2c_check_functionality(adapter, I2C_FUNC_SMBUS_QUICK)//检查是否支持SMBUS_QUICK方式
>for (i = 0; address_data->probe[i] != I2C_CLIENT_END; i += 2){
判断if(address_data->probe[i] || address_data->probe[i] == ANY_I2C_BUS)成立则:
>i2c_probe_address(adapter,address_data->probe[i + 1],-1, found_proc);
>i2c_check_addr(adapter, addr)
>__i2c_check_addr(adapter, addr)//检查地址是否可用
>i2c_smbus_xfer(adapter, addr, 0, 0, 0,I2C_SMBUS_QUICK, NULL)
>如果定义了adapter->algo->smbus_xfer则调用:
adapter->algo->smbus_xfer(adapter,addr,flags,read_write,command,size,data)
>found_proc(adapter, addr, kind)//也就是调用i2c_probe函数的第三个参数
(该参数为一个函数)
>否则调用:
i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,command,size,data);
}
由以上的程序分析可知:
在编写i2c设备驱动时需要手动 设置i2c_client_address_data结构体的成员变量。使驱动能探测到设备
3.i2c_probe的第三个参数(函数)分析
在事例代码中是eeprom_detect,下面分析这个函数都是做了什么工作。
如果通过第2项的程序调用后,说明i2c设备(i2c_client)已经找到能配合工作的适配器(adapter),接下来
就会调用用户自定义的一个函数,该函数为 eeprom_driver结构体成员函数指针:attach_adapter中调用
的i2c_probe(adapter, &addr_data, xxxx)的第三个参数xxxx
xxxx <==>eeprom_detect(struct i2c_adapter *adapter, int address, int kind)
>struct i2c_client *new_client //在这里填充一个和欲驱动的设备相应的i2c_client结构体
或者在该函数中分配一个与设备相关的结构体
data = kzalloc(sizeof(struct eeprom_data), GFP_KERNEL)
>i2c_set_clientdata(new_client, data);//将指针new_client->dev->driver_data(=data)指向data
>填充i2c_client结构体成员:
new_client->addr = address;
new_client->adapter = adapter;
new_client->driver = &eeprom_driver;
new_client->flags = 0;
/* Fill in the remaining client fields */
strlcpy(new_client->name, "eeprom", I2C_NAME_SIZE);
>i2c_attach_client(new_client) //告诉i2c协议层已经生成了一个i2c_client
>struct i2c_adapter *adapter = client->adapter;//取出client中的适配器adapter
>list_add_tail(&client->list,&adapter->clients);//把client加入到适配器adapter的链表尾
>现对client的成员进行进一步地相应设置
>device_register(&client->dev)
>device_initialize(dev);
>device_add(dev)
>sysfs_create_bin_file(&new_client->dev.kobj, &eeprom_attr)//创建sysfs
至此i2c设备驱动程序分析完毕,那么,如何写一个i2c设备驱动呢?比如at24c08
a.在入口函数中调用i2c_add_driver(struct i2c_driver *driver)
所以需要定义一个全局变量 i2c_driver ,并对一些必要的成员进行初始化
b.编写 i2c_driver成员函数: attach_adapter 与 detach_client
int (*attach_adapter)(struct i2c_adapter *);
>i2c_probe
所以需要提供 i2c_client_address_data 结构体,并提供一个探测函数:gq_at24cxx_detect
int (*detach_adapter)(struct i2c_adapter *);
c.定义struct i2c_client *new_client,并填充
i2c_attach_client(at24cxx_client);
d.至此,其实该设备的i2c驱动框架已经完成了,下来要做的就是提供一个字符设备或块设备驱动的接口
e.读写i2c设备时的接口函数设计:
读过程:
copy_from_user(&dev_addr,buf,1);//拷贝来数据地址
需要构造两个msg消息结构,第一个用来写欲读取的数据地址,第二个才是用来读取数据值
/* 数据传输:源地址,目的地址,数据长度 */
/* 先把要读的数据地址写入 */
msg[0].addr = at24cxx_client->addr;//distination
msg[0].len = 1; //length 地址长度=1byte
msg[0].buf = &dev_addr; //source
msg[0].flags = 0; //write flag
/* 再把数据读出 */
msg[1].addr = at24cxx_client->addr;//source
msg[1].len = 1; //length 数据长度=1byte
msg[1].buf = &read_data; //distination
msg[1].flags = I2C_M_RD; //read flag
构造完成后,调用下面的这个函数,它会调用i2c-core等提供的适配器算法的相关函数来 发送/接收 数据
i2c_transfer(at24cxx_client->adapter, msg, 2);
copy_to_user(buf,&read_data,1);
把读到的数据拷贝给应用程序
写过程:
copy_from_user(dev_addr,buf,2);//把写的地址和数据拷贝过来
构造一个msg消息,用于发送地址和数据
/* 数据传输:源地址,目的地址,数据长度 */
msg[0].addr = at24cxx_client->addr;//distination
msg[0].len = 2; //length 地址+数据 长度=2byte
msg[0].buf = dev_addr; //source
msg[0].flags = 0; //write flag
ret = i2c_transfer(at24cxx_client->adapter, msg, 1);//同读过程
f.卸载
在gq_at24cxx_detach_client 中完成卸载所需要的操作,释放内存,反注册等。
源码如下:
/*
* 在字符驱动中的读写操作:
* 全部页的读写操作
* 读: copy_from_user(write_buf,buf,2) write_buf数组的0元素为该页中的地址,1元素为页数
* 写: copy_from_user(dev_addr,buf,3) dev_addr数组的0元素为页数,1元素为该页中的地址,2元素为写入的数据
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/types.h>
static int gq_at24cxx_detect(struct i2c_adapter *adapter, int addrss, int kind);
static unsigned short normal_gq[] = {I2C_CLIENT_END};
static unsigned short ignore_gq[] = {ANY_I2C_BUS,0x50,I2C_CLIENT_END};
static struct i2c_client_address_data gq_address_data = {
.normal_i2c = normal_gq,
.probe = ignore_gq,
.ignore = ignore_gq,
//.forces = ,
};
static struct i2c_client *at24cxx_client;
static struct class *cls;
static struct class_device *cls_dev;
static int major;
static ssize_t at24cxx_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
#if 0
/*现在驱动程序只能读一页的数据 256 byte*/
struct i2c_msg msg[2];
unsigned char dev_addr;
unsigned char read_data;
int ret;
copy_from_user(&dev_addr,buf,1);
/* 数据传输:源地址,目的地址,数据长度 */
/* 先把要读的数据地址写入 */
msg[0].addr = at24cxx_client->addr;//distination
msg[0].len = 1; //length 地址长度=1byte
msg[0].buf = &dev_addr; //source
msg[0].flags = 0; //write flag
/* 再把数据读出 */
msg[1].addr = at24cxx_client->addr;//source
msg[1].len = 1; //length 数据长度=1byte
msg[1].buf = &read_data; //distination
msg[1].flags = I2C_M_RD; //read flag
ret = i2c_transfer(at24cxx_client->adapter, msg, 2);
if(ret==2){
copy_to_user(buf,&read_data,1);
return 1;
}
else
return -1;
#endif
#if 1
/*使驱动程序能读e2prom全部的页*/
struct i2c_msg msg[2];
unsigned char write_buf[2];/*数组的0元素为该页中的地址,1元素为页数*/
unsigned char read_buf;
int ret;
if(size!=2){
printk("error param:write size is 2\n");
return -1;
}
copy_from_user(write_buf,buf,2);
/* 数据传输:源地址,目的地址,数据长度 */
/* 先把要读的数据地址写入 */
msg[0].addr = at24cxx_client->addr + write_buf[1]%4;//distination
msg[0].len = 1; //length 地址长度=1byte
msg[0].buf = &write_buf[0]; //source
msg[0].flags = 0; //write flag
/* 再把数据读出 */
msg[1].addr = at24cxx_client->addr + write_buf[1]%4;//source
msg[1].len = 1; //length 数据长度=1byte
msg[1].buf = &read_buf; //distination
msg[1].flags = I2C_M_RD; //read flag
ret = i2c_transfer(at24cxx_client->adapter, msg, 2);
if(ret==2){
copy_to_user(buf,&read_buf,1);
return 1;
}
else
return -1;
#endif
}
static ssize_t at24cxx_write(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
#if 0
/*现在驱动程序只能写一页的数据 256 byte*/
struct i2c_msg msg[1];
unsigned char dev_addr[2];/*第一个元素是数据地址,第二个元素是数据值*/
int ret;
copy_from_user(dev_addr,buf,2);
/* 数据传输:源地址,目的地址,数据长度 */
msg[0].addr = at24cxx_client->addr;//distination
msg[0].len = 2; //length 地址+数据 长度=2byte
msg[0].buf = dev_addr; //source
msg[0].flags = 0; //write flag
// printk("i2c dev write\n");
ret = i2c_transfer(at24cxx_client->adapter, msg, 1);
if(ret==1){
return 2;
}
else
return -1;
#endif
#if 1
/*使驱动程序能写e2prom全部的页*/
struct i2c_msg msg[1];
unsigned char dev_addr[3];/*第一二个元素是数据地址并认为数组的0为高位1为低位,第三个元素是数据值*/
/*dev_addr数组的0元素为页数,1元素为该页中的地址,2元素为写入的数据*/
int ret;
if(size!=3){
printk("error param:write size is 3\n");
return -1;
}
copy_from_user(dev_addr,buf,3);
/* 数据传输:源地址,目的地址,数据长度 */
msg[0].addr = at24cxx_client->addr+dev_addr[0]%4;//distination
msg[0].len = 2; //length 地址+数据 长度=2byte
msg[0].buf = &dev_addr[1]; /*source第一个元素是数据地址,第二个元素是数据值*/
msg[0].flags = 0; //write flag
ret = i2c_transfer(at24cxx_client->adapter, msg, 1);
if(ret==1){
return 2;
}
else{
printk("error:i2c_transfer failed!\n");
return -1;
}
#endif
}
static struct file_operations at24cxx_fops = {
.read = at24cxx_read,
.write = at24cxx_write,
.owner = THIS_MODULE,
};
static int gq_at24cxx_attach_adapter(struct i2c_adapter *adapter)
{
printk("attach_adapter\n");
return i2c_probe(adapter, &gq_address_data, gq_at24cxx_detect);
}
static int gq_at24cxx_detach_client(struct i2c_adapter *adapt)
{
printk("detach_client\n");
class_device_destroy(cls,MKDEV(major,0));
class_destroy(cls);
unregister_chrdev(major,"at24cxx");
/* detach the client */
i2c_detach_client(at24cxx_client);
kfree(at24cxx_client);
return 0;
}
static struct i2c_driver gq_at24cxx_driver = {
.driver ={
.name = "at24cxx",
},
.id = -1,
.attach_adapter = gq_at24cxx_attach_adapter,
.detach_client = gq_at24cxx_detach_client,
};
static int gq_at24cxx_detect(struct i2c_adapter *adapter, int addrss, int kind)
{
printk("detect the at24c08 device 0x%2x\n",addrss);
at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
if(at24cxx_client==NULL)
{
printk("alloc client failed\n");
return -1;
}
/* 设置i2c_client */
at24cxx_client->adapter = adapter;
at24cxx_client->addr = addrss;
at24cxx_client->driver = &gq_at24cxx_driver;
at24cxx_client->flags = 0;
strcpy(at24cxx_client->name, "at24cxx");
/* 通知协议层已经生成一个client,并attach */
i2c_attach_client(at24cxx_client);
/* 下来就提供字符设备接口 */
major = register_chrdev(0, "at24cxx", &at24cxx_fops);
cls = class_create(THIS_MODULE, "gq_at24c08");
cls_dev = class_device_create(cls, NULL, MKDEV(major,0), NULL, "at24cxx");
return 0;
}
static int gq_at24cxx_init(void)
{
i2c_add_driver(&gq_at24cxx_driver);
return 0;
}
static void gq_at24cxx_exit(void)
{
i2c_del_driver(&gq_at24cxx_driver);
}
module_init(gq_at24cxx_init);
module_exit(gq_at24cxx_exit);
MODULE_LICENSE("GPL");
测试:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char **argv)
{
#if 0
/*测试读写at24c08第0页*/
int fd;
unsigned char buf[2];
unsigned char read_addr;
int i;
fd = open("/dev/at24cxx",O_RDWR);
if(fd<0)
{
printf("open error\n");
return -1;
}
for(i=0;i<256;i++){
buf[0] = i;
buf[1] = 255-i;
if(2==write(fd,buf,2))
{
printf("write %d : data:0x%2x \n",i,buf[1]);
}
}
for(i=0;i<256;i++){
read_addr = i;
if(1==read(fd,&read_addr,1))
{
printf("read %d : data:0x%2x \n",i,read_addr);
}
}
#endif
#if 1
/*测试读写at24c08的全部页*/
int fd;
unsigned char buf[3]={3,0xfe,0x01};
unsigned short read_addr=1000;
int i;
fd = open("/dev/at24cxx",O_RDWR);
if(fd<0)
{
printf("open error\n");
return -1;
}
for(i=0;i<1024;i++){
if(i%4==0)
printf("\n");
buf[0]=i/256;
buf[1]=i%256;
buf[2]=i%256;
if(2==write(fd,buf,3))
{
printf("write %d : data 0x%2x__",i,buf[2]);
}
}
for(i=0;i<1024;i++){
read_addr = i;
if(1==read(fd,&read_addr,2))
{
if(i%4==0)
printf("\n");
printf("read %d : data:0x%2x__",i,read_addr&0xff);
}
}
close(fd);
#endif
return 0;
}
linux设备驱动之 i2c设备驱动 at24c08驱动程序分析相关推荐
- Linux设备驱动篇——[I2C设备驱动-1]
Linux 设备驱动篇之I2c设备驱动 fulinux 一.I2C驱动体系 虽然I2C硬件体系结构和协议都很容易理解,但是Linux I2C驱动体系结构却有相当的复杂度,它主要由3部分组成,即I2C设 ...
- linux IIC子系统分析(九)——实例分析通过设备节点访问I2c设备
在< linux IIC子系统分析(四)--I2c bus初始化> 中我们创建了I2C 总线驱动,I2C adapter device 和adapter drivers也在这时创建 在&l ...
- linux kernel 2.6 i2c设备驱动程序框架介绍,linux设备驱动程序-i2c(2)-adapter和设备树的解析...
linux设备驱动程序-i2c(2)-adapter和设备树的解析 (注: 基于beagle bone green开发板,linux4.14内核版本) 而在linux设备驱动程序--串行通信驱动框架分 ...
- 基于linux4.4程序,linux驱动之i2c总线驱动调用分析【基于linux4.4】
平台:RK3399 使用设备树描述板级资源: 框架: linux i2c框架同样采用分层.分离的模式设计:从上到下分为 app调用层.i2c core层.驱动层:驱动层又分为 cpu平台 i2c控制 ...
- 【linux iic子系统】i2c设备的添加方法(四)
文章目录 前言 一.静态注册 二.动态注册 三.用户空间注册 四.i2c驱动扫描注册 前言 I2C设备的4种添加方法: 1)静态注册 2)动态注册 3)用户空间注册 4)i2c驱动扫描注册 一.静态注 ...
- tiny4412 设备树之i2c设备(二)
开发板:tiny4412(1611) 内核:linux4.4 编译器: arm-none-linux-gnueabi-gcc (gcc version 4.8.3 20140320) mma7660连 ...
- 设备树解析 i2c设备模型
目录 1.基础概念 1.总线 2.手机启动流程 1.MTK启动流程 2.高通启动流程的差别 3.设备树解析 1.设备树相关 2.设备树解析 4. i2c 设备初始化流程 1.基础概念 1.总线 总线是 ...
- v4l2驱动框架_【干货分享】Xilinx Linux V4L2视频管道(Video Pipeline)驱动程序分析...
作者:付汉杰,hankf@xilinx.com, 文章转载自:赛灵思中文社区论坛 概述 Xilinx提供了完整的V4L2的驱动程序,Xilinx V4L2 driver.处于最顶层的驱动程序是V4L2 ...
- 驱动开发(二)——最简单的驱动程序分析
文章目录 系统调用与驱动程序的流程 最简单的驱动程序 源码 Makefile 驱动测试 应用程序 测试 系统调用与驱动程序的流程 如下图 通过分析源码,我们可以得出,从我们的测试程序到调用我们的驱动程 ...
- asoc linux设备驱动_Linux驱动分析之I2C设备
内核:4.20 芯片:HYM8563 RTC 下面的代码分析主要都在注释中,会按照驱动中函数的执行顺序分析. (1) 加载和卸载函数 static const struct i2c_device_id ...
最新文章
- C/C++ 全局变量和局部变量在内存里的区别?堆和栈
- 华为荣耀手机指令代码大全_双十二,华为/荣耀手机推荐选购指南,全系列横评推荐,那一款华为/荣耀手机最值得够买...
- OCS2007R2部署之二准备AD及扩展
- 怎么将oracle的sql文件转换成mysql的sql文件
- lambda python_Python | Lambda和filter()与示例
- tcp协议seq和ack
- $router和$route的区别
- 常见SQL Server 2000漏洞及其相关利用2
- Web前端是什么?大牛推荐的高效学习路线,减少2倍时间
- 如果一年只能两次旅游,你会选择什么季节去什么地方?
- JQuery杂项方法
- 使用pycharm编写python爬虫时出现乱码的解决方案UnicodeEncodeError: ‘gbk‘ codec can‘t encode character ‘\U0001f601‘ in
- Qt 5.9 mysql 驱动加载失败解决办法
- 数据结构之队列和栈的应用
- C++20 span
- php项目怎么分工,路德维希·冯·贝塔郎菲 Ludwig von Bertalanffy
- 下载腾讯视频为mp4格式
- Git使用 从入门到入土 收藏吃灰系列 (九) git reset 移除暂存区与版本回退
- [零基础学JAVA]Java SE实战开发-37.MIS信息管理系统实战开发[文件保存](1)
- 学习笔记 c++ (简单的消息队列)
热门文章
- 一篇文章让小白了解什么是软件测试
- Win10安装程序修复计算机,directx修复工具win10最新版
- banner设圆角_Banner设计技巧!
- 在plc中用c语言实现电梯控制程序,基于三菱FX2N PLC的两部电梯控制系统设计(附梯形图程序)...
- 计算机控制电梯报告总结,电梯控制实验报告电梯控制实验报告.doc
- 51单片机通过WIFI模块ESP8266控制四路继电器
- 计算机毕业设计jspm平面设计类众包威客网网站mjmBBmysql程序
- 三宝小精灵机器人_三宝小精灵机器人
- 建造者2全部岛屿_勇者斗恶龙建造者2全流程攻略介绍 通关流程攻略分享 蒙佐拉岛-游侠网...
- 三、FreeNas实现SMB共享、FTP搭建实验报告