Linux驱动开发:I2C子系统
目录
1、I2C简介
1.1 两根线
1.2 信号
1.3 写时序
1.4 读时序
1.5 I2C速率
1.6 I2C驱动框架简介
2、I2C设备驱动
2.1 I2C相关API
2.1.1 i2c_driver
2.1.2 注册:i2c_add_driver
2.1.3 注销:i2c_del_driver
2.1.4 module_i2c_driver:一键注册,不需要以上注册注销的过程
2.1.5 i2c_client
2.1.6 i2c_msg
2.1.7 i2c_transfer
3、驱动程序
3.1 写消息封装
3.2 读消息封装
3.3 驱动程序
3.3.1 修改设备树
3.3.2 驱动程序编写
3.4 应用程序
1、I2C简介
1.1 两根线
I2C总线只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL。
1.2 信号
空闲状态:I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。
起始信号:当SCL为高电平期间,SDA由高到低的跳变;
停止信号:当SCL为高电平期间,SDA由低到高的跳变;
应答信号:在第九个时钟周期的时候,sda上低电平就代表应答
非应答信号:在第九个时候周期的时候,sda维持高电平
1.3 写时序
起始信号+七位从机地址+一位写(写是0 读是1)+ ack + 寄存器地址+数据+ ack +停止位
1.4 读时序
起始信号+七位从机地址+一位写(写是0 读是1)+ ack + 寄存器地址+ ack +
起始信号+七位从机地址+一位读(写实0 读是1)+ ack + 数据+NO ack + 停止位
1.5 I2C速率
100K 低速 400K 全速 3.4M 高速
1.6 I2C驱动框架简介
在Linux 内核中 I2C 的体系结构分为3 个部分:
1、I2C 核心:I2C 核心提供了I2C 总线驱动和设备驱动的注册、注销方法等。
2、I2C 总线驱动:I2C 总线驱动是对I2C 硬件体系结构中适配器端的实现,适配器可由 CPU 控制,甚至可以直接集成在CPU 内部。一般SOC 的 I2C 总线驱动都是由半导体厂商编写的,不需要用户去编写。因此我们不用关心 I2C 总线驱动具体是如何实现的,我们只要专注于 I2C 设备驱动即可。
3、I2C 设备驱动:I2C 设备驱动是对I2C 硬件体系结构中设备端的实现,设备一般挂接在受CPU 控制的I2C 适配器上,通过I2C 适配器与CPU 交换数据。
2、I2C设备驱动
2.1 I2C相关API
2.1.1 i2c_driver
struct i2c_driver { int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);int (*remove)(struct i2c_client *client);struct device_driver driver;
};struct device_driver {const char *name;const struct of_device_id *of_match_table;
};
2.1.2 注册:i2c_add_driver
#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
2.1.3 注销:i2c_del_driver
void i2c_del_driver(struct i2c_driver *driver)
2.1.4 module_i2c_driver:一键注册,不需要以上注册注销的过程
定义在 linux/i2c.h 中
#define module_i2c_driver(__i2c_driver) module_driver(__i2c_driver, i2c_add_driver, i2c_del_driver)#define module_driver(__driver, __register, __unregister, ...)
static int __init __driver##_init(void)
{ return __register(&(__driver) , ##__VA_ARGS__);
}
module_init(__driver##_init);
static void __exit __driver##_exit(void)
{ __unregister(&(__driver) , ##__VA_ARGS__);
}
module_exit(__driver##_exit);
module_i2c_driver(myi2c);
->
#define module_i2c_driver(myi2c) module_driver(myi2c, i2c_add_driver, i2c_del_driver)#define module_driver(myi2c, i2c_add_driver, i2c_del_driver)
static int __init myi2c_init(void)
{ return i2c_add_driver(&myi2c);
} static void __exit __driver##_exit(void)
{ i2c_del_driver(&myi2c);
}
module_init(myi2c_init);
module_exit(myi2c_exit);
2.1.5 i2c_client
struct i2c_client { unsigned short flags; // 0写 1读unsigned short addr; //从机地址 char name[I2C_NAME_SIZE]; //驱动的名字struct i2c_adapter *adapter;//控制器驱动的对象struct device dev; //这是设备的对象
};
2.1.6 i2c_msg
有多少起始信号就有多少消息,消息的长度用字节表示
struct i2c_msg {__u16 addr; //从机地址__u16 flags; //读写标志位 0写 1度__u16 len; //消息长度__u8 *buf; //消息首地址
};
2.1.7 i2c_transfer
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
/*
功能:消息的发送函数
参数:@adap:控制器的结构体对象@msgs:消息结构体的首地址@num:消息结构体的个数
返回值:成功返回num,否则就是失败
*/
3、驱动程序
3.1 写消息封装
起始信号+七位从机地址+一位写(写是0 读是1)+ ack + 寄存器地址+数据+ ack +停止位
char w_buf[] = {地址,数据};
struct i2c_msg w_msg = {.addr = client->addr,.flags = 0,.len = 2,.buf = w_buf,
};
3.2 读消息封装
起始信号+七位从机地址+一位写(写是0 读是1)+ ack + 寄存器地址+ ack +
起始信号+七位从机地址+一位读(写实0 读是1)+ ack + 数据+NO ack + 停止位
char r_buf[] = {地址};
char val;
struct i2c_msg r_msg[] = {[0] = {.addr = client->addr,.flags = 0,.len = 1,.buf = r_buf,},[1] = {.addr = client->addr,.flags = 1,.len = 1,.buf = &val;},
};
3.3 驱动程序
3.3.1 修改设备树
&i2c1{pinctrl-names = "default", "sleep";pinctrl-0 = <&i2c1_pins_b>;pinctrl-1 = <&i2c1_sleep_pins_b>;i2c-scl-rising-time-ns = <100>; i2c-scl-falling-time-ns = <7>;status = "okay";/delete-property/dmas;/delete-property/dma-names;si7006@40{compatible = "aaa,si7006";reg = <0x40>;};
};
3.3.2 驱动程序编写
由于 client 在各个函数中都需要用到,所以有两种方法,第一种是直接定义一个全局变量,在probe函数中获取到这个 client ,第二种实际上和第一种几乎一致,就是用一个 private_data 的指针去接住这个client。
#ifndef __SI7006_H__
#define __SI7006_H__#define GET_HUM _IOR('l',0,int)
#define GET_TMP _IOR('l',1,int)#define HUM_ADDR 0xe5
#define TMP_ADDR 0xe3
#endif
#define I2CNAME "si7006"
struct i2c_client* gclient;
int major = 0;
struct class* cls;
struct device* dev;int i2c_read(unsigned char reg)
{// 1.封装消息int ret;unsigned char r_buf[] = { reg };unsigned short val;struct i2c_msg r_msg[] = {[0] = {.addr = gclient->addr,.flags = 0,.len = 1,.buf = r_buf,},[1] = {.addr = gclient->addr,.flags = 1,.len = 2,.buf = (char *)&val,},};// 2.发送消息ret = i2c_transfer(gclient->adapter, r_msg, ARRAY_SIZE(r_msg));if (ret != ARRAY_SIZE(r_msg)) {printk("i2c read hum or temp error\n");return -EAGAIN;}return val >> 8 | val << 8;
}
int si7006_open(struct inode* inode, struct file* file)
{printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);return 0;
}
long si7006_ioctl(struct file* file,unsigned int cmd, unsigned long arg)
{int ret, data;switch (cmd) { case GET_HUM:data = i2c_read(HUM_ADDR);ret = copy_to_user((void*)arg, &data, 4);break;case GET_TMP:data = i2c_read(TMP_ADDR);ret = copy_to_user((void*)arg, &data, 4);break;}return 0;
}
int si7006_close(struct inode* inode, struct file* file)
{printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);return 0;
}
struct file_operations fops = {.open = si7006_open,.unlocked_ioctl = si7006_ioctl,.release = si7006_close,
};
int si7006_probe(struct i2c_client* client, const struct i2c_device_id* id)
{gclient = client;// 1.注册字符设备驱动major = register_chrdev(0, I2CNAME, &fops);// 2.自动创建设备节点cls = class_create(THIS_MODULE, I2CNAME);dev = device_create(cls, &client->dev, MKDEV(major, 0), NULL, I2CNAME);return 0;
}
int si7006_remove(struct i2c_client* client)
{device_destroy(cls, MKDEV(major, 0));class_destroy(cls);unregister_chrdev(major, I2CNAME);return 0;
}struct of_device_id oftable[] = {{.compatible = "aaa,si7006",},{}
};struct i2c_driver si7006 = {.probe = si7006_probe,.remove = si7006_remove,.driver = {.name = "bbb",.of_match_table = oftable,}
};module_i2c_driver(si7006);
3.4 应用程序
#include "si7006.h"int main(int argc, const char* argv[])
{int fd;int data,hum,tmp;if ((fd = open("/dev/si7006", O_RDWR)) == -1)PRINT_ERR("open error");while (1) {ioctl(fd, GET_HUM, &hum);ioctl(fd, GET_TMP, &tmp);usleep(2000);}close(fd);return 0;
}