讨好面试官之 IIC子系统驱动

大家好我是石斑鱼,

本文适合正在找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	= &reg;

	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设备驱动开发详解:宋宝华》




该文章对兄弟姐妹们有用的话,求关注我技术公众号,就靠这点东西赚钱买零食了,感谢!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值