大家好我是石斑鱼,
本文适合正在找Linux驱动相关工作的新手开发者,大佬们可以跳过了。
阅读本文之前,建议先大概了解一下IIC的物理电路时序,这个在面试中可能也会被问到,由于我目前主要关注驱动,所以先略过,后面有机会再补上。
Linux面试中,面试官会常问 IIC设备驱动常规写法,因为IIC子系统算经常修改调试的驱动,之前也只是会怎么抄。这里针对口语化理解总结,细节部分没有写出来,细节可以再搜一下其他大佬的文章。
本文大概写下主干框架,方便自己回忆结构,也方便兄弟姐妹们面试口语回答这个问题时候参考,如果哪里写错了,请尽情骂我吧,骂完我马上改。(我个人觉得,要在一大片知识里面总结关键架构,讨好面试官并口语化回答到要点还是挺难的)
如果还有其他IIC相关的面试问题,也欢迎评论,后面增加上来。
面试官:说说IIC驱动怎么写?
口语简答版:
嵌入式Linux驱动中IIC驱动开发大致有三个步骤:
- 1、在设备树中描述好IIC设备信息
- 2、实现
i2c_driver
结构体并通过i2c_add_driver()
添加到IIC总线 - 3、实现
probe()
等函数- 使用
register_chrdev()
申请设备号并注册一个字符设备,实现file_operations
- 使用
class_create()
、device_create()
创建设备节点文件 - 通过IIC的相关接口如
i2c_transfer()
去初始化IIC从设备
- 使用
虽然上面的口语回答覆盖了面试官的问题答案,但也要了解相关代码:
1. 在设备树中描述好IIC设备信息,挂在哪个IIC总线下,设备寄存器地址。
i2c@138B0000{
/*i2c adapter信息*/
#address-cells = <0>;
#size-cells = <0>;
samsung,i2c-sda-delay = <100>;
samsung,i2c-max-bus-freq = <20000>;
pinctrl-0 = <&i2c5_bus>;
pinctrl-names = "default";
status = "okay";
mpu6050@68{/*i2c client信息*/
compatible = "invensense,mpu6050";
reg = <0x68>
};
};
2. 实现 i2c_driver
结构体并通过 i2c_add_driver()
添加到IIC总线
struct i2c_driver mpu6050_drv = {
.probe = mpu6050_drv_probe,
.remove = mpu6050_drv_remove,
.driver = {
.name = "mpu6050_drv"; /*将出现在 /sys/bus/i2c/driver 目录下。*/
.of_match_table = of_match_ptr( of_mpu6050_id );/* 用于跟i2c_client中的name匹配 */
},
.id_table = {}, /* 非设备树环境下的匹配,现在已经用不上这个成员了。 */
};
3. 实现 probe() 等函数
int mpu6050_drv_probe(struct i2c_client *client, const struct i2c_device_id *devid)
{
mpu_dev = kzalloc(sizeof(struct mpu_sensor), GFP_KERNEL);
mpu_dev->client = client;
//申请设备号。
mpu_dev->dev_major = register_chrdev(0, "mpu_drv", &mpu6050_fops);
//创建设备节点。
mpu_dev->cls = class_create(THIS_MODULE, "mpu_cls");
mpu_dev->dev = device_create(mpu_dev->cls, NULL, MKDEV(mpu_dev->dev_major, 0), NULL, "mpu_sensor");
//通过接口初始化I2C设备。
// i2c_master_send(client, ); //可以直接通过这个函数来给从设备发数据。
char buf[2] = {0x43/*从设备内部地址*/, 0x00/*要写进从设备的值*/};
mpu6050_write_bytes(mpu_dev->client, buf, 2);//用自己实现的函数来给从设备写数据。
return 0;
}
一个完整的简单驱动:
mpu6050 是一个IIC 接口的三轴加速度传感器
#include <linux/init.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/i2c.h>
#include <asm/io.h>
#include <asm/uaccess.h>
union mpu6050_data {
struct {
short x;
} accel;
};
#define IOC_GET_ACCEL _IOR( 'M', 0x34, union mpu6050_data )
struct device_driver driver;
const struct i2c_device_id *id_table;
const struct of_device_id of_mpu6050_id[] = {
{
.compatible = "invensense,mpu6050",/* 这个名字要跟设备树中的一致。 */
},
{ },/* 在这末尾最好加上一个空对象表示结束。 */
};
const struct i2c_device_id mpu_id_table[] = {
{},/* do nothing. */
};
struct mpu_sensor {
int dev_major;
struct device *dev;
struct class *cls;
struct i2c_client *client;
};
struct mpu_sensor *mpu_dev;
int mpu6050_drv_open( struct inode *inode, struct file *fp )
{
return(0);
}
int mpu6050_drv_close( struct inode *inode, struct file *fp )
{
return(0);
}
long mpu6050_drv_ioctl( struct file *fp, unsigned int cmd, unsigned long args )
{
union mpu6050_data data;
switch ( cmd )
{
case IOC_GET_ACCEL: {
/* 读数据。 */
data.accel.x = mpu6050_read_reg_byte( mpu_dev_client, 0x43 /*从设备内部地址*/ );
} break;
}
/* 读完数据复制到用户空间。 */
copy_to_user( (void __user *) args /*long型完全可以放得下指针地址*/, &data, sizeof(data) );
return(0);
}
const struct file_operations mpu6050_fops = {
.open = mpu6050_drv_open,
.release = mpu6050_drv_close,
.unlocked_ioctl = mpu6050_drv_ioctl,
};
/*
* 自己实现I2C的读写功能。
*/
int mpu6050_write_bytes( struct i2c_client *client, char *buf, int count )
{
struct i2c_adapter *adapter = client->adapter;
struct i2c_msg msg;
msg.addr = client->addr;
msg.flags = 0;
msg.len = count;
msg.buf = buf;
int ret = i2c_transfer( adapter, &msg, 1 /*指消息msg的个数*/ );
return(ret == 1 ? count : ret);
}
int mpu6050_read_bytes( struct i2c_client *client, char *buf, int count )
{
struct i2c_adapter *adapter = client->adapter;
struct i2c_msg msg;
msg.addr = client->addr;
msg.flags = 1;
msg.len = count;
msg.buf = buf;
int ret = i2c_transfer( adapter, &msg, 1 /*指消息msg的个数*/ );
return(ret == 1 ? count : ret);
}
/* 读取指定寄存器的地址,然后返回值。 */
int mpu6050_read_reg_byte( struct i2c_client *client, char reg )
{
struct i2c_adapter *adapter = client->adapter;
struct i2c_msg msg[2];
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].len = 1;
msg[0].buf = ®
char rxbuf[1];
msg[1].addr = client->addr;
msg[1].flags = 1;
msg[1].len = 1;
msg[1].buf = rxbuf;
int ret = i2c_transfer( adapter, msg, 2 /*指消息msg的个数*/ );
if ( ret < 0 )
{
return(ret);
}
return(rxbuf[0]);
}
int mpu6050_drv_probe( struct i2c_client *client, const struct i2c_device_id *devid )
{
mpu_dev = kzalloc( sizeof(struct mpu_sensor), GFP_KERNEL );
mpu_dev->client = client;
/* 申请设备号。 */
mpu_dev->dev_major = register_chrdev( 0, "mpu_drv", &mpu6050_fops );
/* 创建设备节点。 */
mpu_dev->cls = class_create( THIS_MODULE, "mpu_cls" );
mpu_dev->dev = device_create( mpu_dev->cls, NULL, MKDEV( mpu_dev->dev_major, 0 ), NULL, "mpu_sensor" );
/*
* 通过接口初始化I2C设备。
* i2c_master_send(client, ); //可以直接通过这个函数来给从设备发数据。
*/
char buf[2] = { 0x43 /*从设备内部地址*/, 0x00 /*要写进从设备的值*/ };
mpu6050_write_bytes( mpu_dev->client, buf, 2 ); /* 用自己实现的函数来给从设备写数据。 */
return(0);
}
int mpu6050_drv_remove( struct i2c_client *client )
{
device_destroy( mpu_dev->cls, MKDEV( mpu_dev->dev_major, 0 ) );
class_destroy( mpu_dev->cls );
unregister_chrdev( mpu_dev->dev_major, "mpu_drv" );
kfree( mpu_dev );
}
struct i2c_driver mpu6050_drv = {
.probe = mpu6050_drv_probe,
.remove = mpu6050_drv_remove,
.driver = {
.name = "mpu6050_drv"; /* 将出现在 /sys/bus/i2c/driver 目录下。 */
.of_match_table = of_match_ptr( of_mpu6050_id ); /* 用于跟i2c_client中的name匹配用的。 */
},
.id_table = {}, /* 非设备树环境下的匹配,现在已经用不上这个成员了。 */
};
static int __init mpu6050_drv_init()
{
return(i2c_add_driver( &mpu6050_drv ) );
}
static void __exit mpu6050_drv_exit()
{
i2c_del_driver( &mpu6050_drv );
}
module_init( mpu6050_drv_init );
module_exit( mpu6050_drv_exit );
MODULE_LICENSE( "GPL" );
粘在巨人的大腿上:
参考:https://www.cnblogs.com/chorm590/p/12323850.html
参考:《Linux设备驱动开发详解:宋宝华》
该文章对兄弟姐妹们有用的话,求关注我技术公众号,就靠这点东西赚钱买零食了,感谢!