Linux I2C总线设备驱动

内核版本:linux-3.10.61

1、I2C总线硬件介绍

        I2C总线在硬件上很简单,由两条线构成,分别是数据线SDA,时钟线SCL。在设备结构上它属于主从结构,一条总线上可以挂接多个设备,每个设备都有成为主机的潜力,但是在同一时刻只能有一个主机。对设备的读写操作都是由主设备发起的。在对从设备进行读写时,主设备会先发出从设备设备地址确定是对哪一个设备进行读写。

        来分析一下I2C设备的时序图。第一步,发出Start信号,在高电平时将SDA拉低表示主设备将要进行读写操作。然后发出第一个字节的数据,在一个时钟周期内SCL为低电平时SDA根据数据变换电平高低,在SCL高电平时确定数据。第一个字节的前7位是设备地址,设备地址由设备手册和接线共同决定,最后一位是读写位,0表示写,1表示读。每发出8位数据后第9位就是一个ACK信号。在写操作时ACK由从设备发出,读设备时ACK由主设备发出。发出地址信号后接下来的数据就是需要读取或者写入的数据了。当数据传输结束后在SCL高电平时将SDA拉高,表示发出一个停止信号。这样一次I2C的数据传输就结束了。

2、I2C驱动框架分析

        通常我们把I2C驱动框架分为3层,驱动层,核心层和适配器层。每个层级都有对应的结构体对象来表示。

2-1、I2C驱动框架分层

驱动层:具体设备的I2C驱动程序,主要实现数据的处理

核心层:提供各种操作函数并且实现I2C总线 i2c_bus_type

适配器层:实现读写设备函数。下面以三星的芯片s3c2410为例

 2-2、I2C总线

        看过平台总线驱动架构你就会发现,I2C总线也是用一个bus_type结构体来描述,实际上这是一个内核中很常用的总线驱动模型,后面专门出一章来细说。和platform总线一样,match函数会在设备和驱动匹配时被调用。probe函数会在匹配成功后被调用。

struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match,    /* 用于匹配device和driver */ 
	.probe		= i2c_device_probe,    /* device和driver匹配成功后调用 */
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
};

 2-3、I2C适配器

        struct i2c_adapter的核心成员是struct i2c_algorithm *algo;这个结构体实现了I2C的发送和接收算法。

struct i2c_adapter {
	struct module *owner;
	unsigned int class;		  /* classes to allow probing for */
	const struct i2c_algorithm *algo; /* the algorithm to access the bus */
	void *algo_data;

	/* data fields that are valid for all devices	*/
	struct rt_mutex bus_lock;

	int timeout;			/* in jiffies */
	int retries;
	struct device dev;		/* the adapter device */

	int nr;
	char name[48];
	struct completion dev_released;

	struct mutex userspace_clients_lock;
	struct list_head userspace_clients;
};
struct i2c_algorithm {
    /* 用于和I2C通信 */
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);

    /* 一种特殊的I2C通讯方式,也可以用master_xfer来实现 */
	int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
			   unsigned short flags, char read_write,
			   u8 command, int size, union i2c_smbus_data *data);

	/* To determine what the adapter supports */
	u32 (*functionality) (struct i2c_adapter *);
};

        struct i2c_adapter使用函数 i2c_add_adapter()注册进系统。通过device_register()将i2c_adapter挂接到i2c_bus_type.p->klist_devices,i2c_client 也会挂接到i2c_bus_type.p->klist_devices,使用adap->dev.type可以区分i2c_client 和i2c_adapter。

int i2c_add_adapter(struct i2c_adapter *adapter)
{
    return i2c_register_adapter(adapter);
}

static int i2c_register_adapter(struct i2c_adapter *adap)
{
	adap->dev.bus = &i2c_bus_type;
	adap->dev.type = &i2c_adapter_type;
	res = device_register(&adap->dev);
}

2-4、驱动层i2c_client 

         struct i2c_client用来描述一个I2C设备。dev_type是一个字符串用于和driver的id_table进行匹配。dev_addr是i2c设备的硬件地址。i2c_client最终会被挂接到i2c_bus_type.p->klist_devices用于driver匹配。

struct i2c_client {
	unsigned short flags;		/* div., see below		*/
	unsigned short addr;		/* chip address - NOTE: 7bit	*/
					/* addresses are stored in the	*/
					/* _LOWER_ 7 bits		*/
	char name[I2C_NAME_SIZE];
	struct i2c_adapter *adapter;	/* I2C adapter	*/
	struct i2c_driver *driver;	/* and our access routines	*/
	struct device dev;		/* the device structure		*/
	int irq;			/* irq issued by device		*/
	struct list_head detected;
};

添加i2c_client 的方式总共有四种详情。不同的内核版本可能会有一些偏差,可以参考内核文档 "\Documentation\i2c\instantiating-devices"

第一种:静态声明

        静态注册通过i2c_register_board_info()将struct i2c_board_info的信息添加到__i2c_board_list ,struct i2c_board_info一般使用宏I2C_BOARD_INFO(dev_type, dev_addr)来进行初始化。dev_type为设备类型,dev_addr是7位设备地址。

static struct i2c_board_info __initdata h4_i2c_board_info[] = {
	{
		I2C_BOARD_INFO("isp1301_omap", 0x2d),
		.irq		= OMAP_GPIO_IRQ(125),
	},
	{	/* EEPROM on mainboard */
		I2C_BOARD_INFO("24c01", 0x52),
		.platform_data	= &m24c01,
	},
	{	/* EEPROM on cpu card */
		I2C_BOARD_INFO("24c01", 0x57),
		.platform_data	= &m24c01,
	},
};

static void __init omap_h4_init(void)
{
	(...)
	i2c_register_board_info(1, h4_i2c_board_info,
			ARRAY_SIZE(h4_i2c_board_info));    /* 把数据添加进全局链表 __i2c_board_list */
	(...)
}

在注册i2c_adapter时,i2c_register_adapter()会调用i2c_scan_static_board_info()将__i2c_board_list 链表中的信息提取出来通过i2c_new_device()构建一个i2c_client。

static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
	struct i2c_devinfo	*devinfo;

	down_read(&__i2c_board_lock);
	list_for_each_entry(devinfo, &__i2c_board_list, list) {
		if (devinfo->busnum == adapter->nr
				&& !i2c_new_device(adapter,
						&devinfo->board_info))
			dev_err(&adapter->dev,
				"Can't create device at 0x%02x\n",
				devinfo->board_info.addr);
	}
	up_read(&__i2c_board_lock);
}

第二种:显式声明

        将struct i2c_board_info作为参数直接传递给i2c_new_device(),得到一个i2c_client,和第一种方法不同的是直接调用函数i2c_new_device()。

下面是内核文档中提供的实例代码

static struct i2c_board_info sfe4001_hwmon_info = {
	I2C_BOARD_INFO("max6647", 0x4e),
};

int sfe4001_init(struct efx_nic *efx)
{
	(...)
	efx->board_info.hwmon_client =
		i2c_new_device(&efx->i2c_adap, &sfe4001_hwmon_info);

	(...)
}

        知道怎么用i2c_new_device()了就来分析一下实现该函数实现原理吧。分配一段空间给client,然后初始化它,最后调用device_register()将client挂接到i2c_bus_type.p->klist_devices,通过dev.type来区别这个device是一个client还是adapter。

struct i2c_client * i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)	{
    struct i2c_client	*client;
	client = kzalloc(sizeof *client, GFP_KERNEL);
    /* ... */
	client->dev.parent = &client->adapter->dev;
	client->dev.bus = &i2c_bus_type;
	client->dev.type = &i2c_client_type; /* 把device类型设置为client */
	client->dev.of_node = info->of_node;
     /* ... */
    status = device_register(&client->dev);
    /* ... */
}

对于显式声明还有第二种方式,得到一个i2c_client,实现方式如下所示。

static const unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END };

static int __devinit usb_hcd_nxp_probe(struct platform_device *pdev)
{
	(...)
	struct i2c_adapter *i2c_adap;
	struct i2c_board_info i2c_info;

	(...)
	i2c_adap = i2c_get_adapter(2);
	memset(&i2c_info, 0, sizeof(struct i2c_board_info));
	strlcpy(i2c_info.type, "isp1301_nxp", I2C_NAME_SIZE);
	isp1301_i2c_client = i2c_new_probed_device(i2c_adap, &i2c_info,
						   normal_i2c, NULL);
	i2c_put_adapter(i2c_adap);
	(...)
}

 同样来分析一下i2c_new_probed_device()的实现过程做了什么特殊处理,依次调用probe函数访问传入的设备地址,若设备有响应就跳出循环调用i2c_new_device()生成一个client。

struct i2c_client *
i2c_new_probed_device(struct i2c_adapter *adap,
		      struct i2c_board_info *info,
		      unsigned short const *addr_list,
		      int (*probe)(struct i2c_adapter *, unsigned short addr))
{
	if (!probe)
		probe = i2c_default_probe;
    for (i = 0; addr_list[i] != I2C_CLIENT_END; i++){

		/* Test address responsiveness */
		if (probe(adap, addr_list[i]))
			break;
    }
	if (addr_list[i] == I2C_CLIENT_END) {
		dev_dbg(&adap->dev, "Probing failed, no device found\n");
		return NULL;
	}

	info->addr = addr_list[i];
	return i2c_new_device(adap, info);
}

第三种:用户空间创建

      通过执行命令 "echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-*/new_device",第一个参数是名称,第二个参数是设备地址,其中"i2c-*" *是通配符根据你自己设备实际情况取值。可以生成一个client 。这种方法往往在测试时使用。

第四种方法在注册i2c_driver时介绍。

2-5、驱动层i2c_driver

        i2c_driver用来描述IIC硬件驱动程序,实现对IIC数据的处理。id_table用于匹配设备。在设备匹配成功后会调用probe函数。

struct i2c_driver {
	unsigned int class;

    /* 比较老的内核使用的方法探测设备 */
	int (*attach_adapter)(struct i2c_adapter *) __deprecated; 
	int (*detach_adapter)(struct i2c_adapter *) __deprecated;

	int (*probe)(struct i2c_client *, const struct i2c_device_id *); /* 匹配到device后调用 */
	int (*remove)(struct i2c_client *);    /* 设备移除时调用 */


	struct device_driver driver;    /* 实现一个driver */
	const struct i2c_device_id *id_table; /* 用于匹配device */

	int (*detect)(struct i2c_client *, struct i2c_board_info *); /* 设置了这个方法可以生成一个i2c_client */
	const unsigned short *address_list;
	struct list_head clients;

};

 i2c_driver通过i2c_register_driver()函数注册进内核。通过driver_register() i2c_driver会被挂接到i2c_bus_type.p->klist_drivers用于device匹配。

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
    driver->driver.bus = &i2c_bus_type;
    res = driver_register(&driver->driver);

    /* 
     * 取出i2c_bus上所有的adapter类型的device和driver进行匹配
     * __process_new_driver()可以生成一个i2c_client
     */
    i2c_for_each_dev(driver, __process_new_driver);    
}

         client的第四种生成方式就是在__process_new_driver()实现的,跟着这个入口看看来实现过程。

static int __process_new_driver(struct device *dev, void *data)
{
	if (dev->type != &i2c_adapter_type)    /* 判断是一个adapter类型的device */
		return 0;
	return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}

static int i2c_do_add_adapter(struct i2c_driver *driver,
			      struct i2c_adapter *adap)
{
    i2c_detect(adap, driver);    /* 重要过程在这里,往下看 */
    if (driver->attach_adapter) {
        driver->attach_adapter(adap);
    }
}

static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
    
	address_list = driver->address_list;
	if (!driver->detect || !address_list)    /* 如果的i2c_driver.detect未实现或者地址列表为空则返回*/
		return 0;

    /* 为每一个地址调用 i2c_detect_address()*/
    for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
        temp_client->addr = address_list[i];
        err = i2c_detect_address(temp_client, driver);
    }

}

static int i2c_detect_address(struct i2c_client *temp_client,
			      struct i2c_driver *driver)
{
    if (!i2c_default_probe(adapter, addr))    /* 检测总线上是否存在该设备地址 */
        return 0;
    err = driver->detect(temp_client, &info);    /* 调用 driver->detect()设置info.type */
    if (info.type[0] == '\0') {
        ;
    } else {
        client = i2c_new_device(adapter, &info);    /* 得到一个i2c_client */
        list_add_tail(&client->detected, &driver->clients);   /* i2c_client 挂接到i2c_driver */
    }
}

2-6、消息结构体

         struct i2c_msg用来描述一个I2C消息,当有多条消息时一般定义为一个结构数组。i2c_transfer()用来接收/发送消息。这个发送函数也只是通过调用adap->algo->master_xfer()实现接收/发送功能。

struct i2c_msg {
	__u16 addr;	    /* 7位设备地址 */
	__u16 flags;    /* 读写标志0表示写,1表示读 */
	__u16 len;		/* 消息长度 */
	__u8 *buf;		/* 存储数据的buffer */
};

 3、i2c_client和i2c_driver的匹配

        通过前面的分析我们得知注册i2c_client会调用register_device(),注册i2c_driver会调用到register_driver()。这两个函数最终都会调用i2c_bus的match函数来匹配driver,而后调用bus的probe函数。如果不清楚register_device()/register_driver()是如何调用到bus的match函数和probe函数请看我之前的文章Linux Platform 平台设备驱动模型简介

        分析i2c_device_match()我们可以得出结论:通过比较drv->id_table和i2c_client的name来匹配i2c_driver和i2c_client。

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;

	/* 设备树实现的匹配方式这边做展开 */
	if (of_driver_match_device(dev, drv))
		return 1;

	driver = to_i2c_driver(drv);
	/* 通过id_table进行匹配 */
	if (driver->id_table)
		return i2c_match_id(driver->id_table, client) != NULL;
	return 0;
}

static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
						const struct i2c_client *client)
{
	while (id->name[0]) {
		if (strcmp(client->name, id->name) == 0)    /* 比较id_table和client的name */
			return id;
		id++;
	}
	return NULL;
}

       其实i2c_bus的probe函数就会调用到i2c_driver中实现的probe函数

static int i2c_device_probe(struct device *dev){
    struct i2c_driver	*driver;
    driver = to_i2c_driver(dev->driver);
    status = driver->probe(client, i2c_match_id(driver->id_table, client));
}

4、总结

        从总线上看实现上I2C设备是简单的,虽然在驱动框架上已经有大佬帮我们实现了,但是不同的设备也会有不同的操作方式需要在工作中去学习了解。感谢你的阅读,祝你早日找到自己的路,成为你想成为的人!

恭喜获得知识点+1

若有讲解不妥之处烦请在评论区指正!

如果有收获那就一键三连鼓励一下吧!

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值