标签:smbus number inode 分配 clu 设备节点 处理 goto 内容
1.iic设备(client)注册
1.1 老内核版本下没有设备树的情况
在老内核版本下,可以使用如下三种方法注册client:
(1)i2c_register_board_info函数。以i2c_devs0为例,i2c_devs0是一个数组,里面是i2c0上所有的设备,i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
static struct i2c_board_info i2c_devs0[] __initdata = {
{I2C_BOARD_INFO("wm8580", 0x1b),
},
};
查看i2c_devs0的定义,我们发现该数组内部都是i2c_board_info结构体,如果要添加设备到i2c0,只需在该数组中使用I2C_BOARD_INFO这个宏即可,第一个参数是名字,第二个参数是设备在i2c上的地址,此宏的本质就是填充一个struct i2c_board_info,这一步作用 是把wm8580以i2c设备的身份被注册,并且绑定i2c0这个适配器。
(2)static struct i2c_client *xx_client;
static struct i2c_board_info xx_info = { //所支持的i2c设备的列表
I2C_BOARD_INFO("at24c08", 0x50), //一项代表一个支持的设备,它的名字叫做“at24c08”,器件地址是0x50
};
struct i2c_adapter *i2c_adap; //分配一个适配器的指针
i2c_adap = i2c_get_adapter(0); //调用core层的函数,获得一个i2c总线。这里我们已经知道新增的器件挂接在编号为0的i2c总线上
xx_client = i2c_new_device(i2c_adap, &xx_info); // 把i2c适配器和新增的I2C器件关联起来,这个用了i2c总线0,地址是0x50。这就组成了一个客户端xx_client。
i2c_put_adapter(i2c_adap);//释放adapter
(3)该方法与第二种方法类似,只是在iic总线未知的情况下使用,即将i2c_new_device改为i2c_new_probe_device
1.2新内核版本下使用设备树
&i2c1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hummingboard_i2c1>;
status = "okay";
rtc: pcf8523@68 {
compatible = "nxp,pcf8523";
reg = <0x68>;
};
};
2.IIC驱动端程序
首先要明白一点,对于驱动工程师,如果手中是移植过的内核,则i2c总线核心和i2c适配器驱动是不需要动的,我们主要关注点在:提供i2c设备(client)、编写i2c设备驱动
- i2c子系统的本质是:工程师任意选用input子系统、misc框架、普通字符驱动等方式实现i2c驱动,i2c子系统的意义仅仅是为硬件操作提供接口(库)
- 此外,对于新内核和老内核,设备树会导致驱动会有一些细微的不同,主要体现在驱动和设备match的部分
老内核下的i2c驱动
-
-
#include <linux/module.h>
-
#include <linux/string.h>
-
-
#include <linux/device.h>
-
-
-
-
-
-
#define MPU6050_RA_PWR_MGMT_1 0x6B
-
#define MPU6050_RA_ACCEL_XOUT_H 0x3B
-
-
-
#define MPU6050_NAME "mpu6050"
-
-
struct i2c_client *mpu6050_client;
-
-
-
-
-
static int mpu6050_write_reg(unsigned char addr, unsigned char dat)
-
-
-
-
-
msgs[0].addr = mpu6050_client -> addr;
-
-
-
-
-
msgs[1].addr = mpu6050_client -> addr;
-
-
-
-
-
-
ret = i2c_transfer(mpu6050_client ->adapter, msgs, 2);
-
-
printk(KERN_INFO "i2c_transfer(mpu6050 write) error \n");
-
-
-
-
-
-
-
-
-
static int mpu6050_read_reg(unsigned char addr, unsigned char buf)
-
-
-
-
-
msgs[0].addr = mpu6050_client -> addr;
-
-
-
-
-
msgs[1].addr = mpu6050_client -> addr;
-
-
-
msgs[1].flags = I2C_M_RD;
-
-
-
ret = i2c_transfer(mpu6050_client ->adapter, msgs, 2);
-
-
printk(KERN_INFO "i2c_transfer(mpu6050 read) error \n");
-
-
-
-
-
-
-
static int mpu6050_open(struct inode *inode, struct file *file)
-
-
printk(KERN_INFO "open mpu6050\n");
-
-
mpu6050_write_reg(MPU6050_RA_PWR_MGMT_1, 0X80);
-
-
-
-
-
-
ssize_t mpu6050_read(struct file *file, char __user *ubuf,
-
size_t size, loff_t *opp)
-
-
unsigned char buf [6] = {0};
-
-
mpu6050_read_reg(MPU6050_RA_GYRO_XOUT_H, buf[0]);
-
-
-
-
ret = copy_to_user(ubuf, buf , size);
-
-
printk(KERN_INFO "copy_to_user fail\n");
-
-
-
-
-
-
-
static int mpu6050_release(struct inode *inode, struct file *file)
-
-
-
-
-
static const struct file_operations mpu6050_fops = {
-
-
-
-
.release = mpu6050_release,
-
-
-
-
static struct cdev *mpu6050_pcdev;
-
static struct class *mpu6050_pclass;
-
-
dev_t mpu6050dev_num = 0;
-
unsigned int mpu6050dev_major = 0;
-
-
int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
-
-
-
-
-
-
ret = alloc_chrdev_region(&mpu6050dev_num, 0, MPU6050_CNT, MPU6050_NAME);
-
mpu6050dev_major = MAJOR(mpu6050dev_num);
-
-
printk(KERN_INFO "alloc_chrdev_region fail\n");
-
-
-
printk(KERN_INFO "MAJOR %d\n", mpu6050dev_major);
-
-
-
mpu6050_pcdev = cdev_alloc();
-
-
cdev_init(mpu6050_pcdev, &mpu6050_fops);
-
-
-
ret = cdev_add(mpu6050_pcdev, mpu6050dev_num, MPU6050_CNT);
-
-
printk(KERN_INFO "cdev_add fail\n");
-
-
-
-
-
mpu6050_pclass = class_create(THIS_MODULE, "mpu6050");
-
if (IS_ERR(mpu6050_pclass)) {
-
printk(KERN_ERR "can‘t register class\n");
-
-
-
device_create(mpu6050_pclass, NULL, mpu6050dev_num, NULL, "mpu6050");
-
-
-
-
-
-
class_destroy(mpu6050_pclass);
-
-
-
-
-
-
unregister_chrdev_region(mpu6050dev_num, MPU6050_CNT);
-
-
-
-
-
-
int mpu6050_remove(struct i2c_client *client)
-
-
-
device_destroy(mpu6050_pclass, mpu6050dev_num);
-
class_destroy(mpu6050_pclass);
-
-
unregister_chrdev_region(mpu6050dev_num, MPU6050_CNT);
-
-
-
-
-
-
-
-
static struct i2c_device_id mpu6050_id[] = {
-
-
-
-
-
-
-
static struct i2c_driver mpu6050_driver = {
-
-
-
-
-
-
.remove = mpu6050_remove,
-
-
-
-
-
-
-
static int __init mpu6050_init(void)
-
-
return i2c_add_driver(&mpu6050_driver);
-
-
-
static void __exit mpu6050_exit(void)
-
-
i2c_del_driver(&mpu6050_driver);
-
-
-
module_init(mpu6050_init);
-
module_exit(mpu6050_exit);
-
-
整个程序很简单,关键点主要是driver结构体里要有一个id_table,如果mach-xxx中定义的设备名字和id_table相同,那么会probe函数就被触发
- 触发probe后,
i2c_client *client
将作为参数传入probe,这个i2c_client里面就包含了设备的私有数据(比如设备的i2c地址、绑定的i2c适配器等),类似plat_data,我们在probe中将i2c_client *client
绑定给全局变量mpu6050_client
,这样就能在read、write等函数中用mpu6050_client -> addr
来得到设备的i2c地址了,用mpu6050_client ->adapter
来得到绑定的i2c适配器
- 只要read、write知道了设备的i2c地址、绑定的i2c适配器,那么就能利用kernel提供的接口来进行i2c传输。上面代码中用的接口是
i2c_transfer
,这种方法有点老旧;kernel官方强烈推荐smbus族接口来进行i2c收发,smbus是i2c_transfer的子集,很多soc可能不支持i2c_transfer
这个接口,这时就只能使用smbus族接口。这两个接口在内部逻辑上有很大不同,比如我们要写mpu6050内部的RA_PWR_MGMT_1寄存器,根据上面的代码,我们调用了两次i2c_transfer
,第一次发送RA_PWR_MGMT_1寄存器的地址,第二次发送要写的值。而对于smbus族接口来说,只需调用一次就行了,可以认为smbus族接口进行了更好的封装,不仅写操作如此,读操作也如此,具体接口如下
-
-
i2c_smbus_write_byte_data(mpu6050_client, MPU6050_RA_PWR_MGMT_1, data);
-
-
-
read_val = i2c_smbus_read_byte_data(mpu6050_client, MPU6050_RA_ACCEL_XOUT_H);
- 如果要以16bit为单位读写i2c,那么可以用下面的接口,使用方法都是类似的
-
i2c_smbus_read_word_data();
-
i2c_smbus_write_word_data();
新内核下的i2c驱动
设备树对i2c设备的注册有比较大的影响,详见前面的章节,这里不再赘述;而对于驱动程序,设备树带来的变化极小,主要是驱动和设备之间的匹配方式变了
- 老旧的id_table方式不再使用,取而代之的是类似的一种结构:of_match_table
- 这里以pcf8523驱动为例,只要驱动中的of_match_table 中的compatible 值和设备节点中的compatible 相匹配,那么probe函数就会被触发。不仅i2c是这样,platform、spi等都是这个原理
-
-
static const struct of_device_id pcf8523_of_match[] = {
-
{ .compatible = "nxp,pcf8523" },
-
-
-
-
-
static struct i2c_driver pcf8523_driver = {
-
-
-
-
.of_match_table = of_match_ptr(pcf8523_of_match),
-
-
-
-
}
i2c和spi驱动还支持一种“别名匹配”的机制,就以pcf8523为例,假设某程序员在设备树中的pcf8523设备节点中写了compatible = “pcf8523”;,显然相对于驱动id_table中的”nxp,pcf8523”,他遗漏了nxp字段,但是驱动却仍然可以匹配上,因为别名匹配对compatible中字符串里第二个字段敏感。
以上内容参考如下博文:https://blog.csdn.net/sdkdlwk/article/details/76105112
https://www.cnblogs.com/alan666/p/8311853.html
https://www.linuxidc.com/Linux/2014-05/101649.htm
linux中iic驱动编写—有设备树&没有设备树
标签:smbus number inode 分配 clu 设备节点 处理 goto 内容
原文地址:https://www.cnblogs.com/lonny0406/p/12776487.html