Linux I2C 子系统分析

     最近看了看Linux I2C子系统的代码,参考了网上的一些资料,对I2C子系统有些理解,记录下来以后备用。

     在分析之前,先明确两个概念:

    (1) I2C控制器:用于实现I2C通信,在CPU外部扩展I2C设备的接口

    (2) I2C设备:使用I2C接口的设备。

     这两个东西的关系类似与主板上的USB接口和外接的USB设备(如U盘)的关系。

     首先分析以下I2C子系统中几个比较重要的结构体:

    (1) i2c_board_info:I2C设备板级信息

 这个结构体定义板子上的I2C设备信息,对于每个I2C设备,有一个相应的结构体。例如arch/arm/mach-s3c2440/mach-mini2440.c中定义了板子上的一片at24c08芯片:

 static struct at24_platform_data at24c08 = {

.byte_len = SZ_8K / 8,

.page_size = 16,

};

static struct i2c_board_info mini2440_i2c_devs[] __initdata = {   //定义i2c_board_info结构体数组

{

I2C_BOARD_INFO("24c08", 0x50),   //定义I2C设备的设备名和设备地址(设备名用于与I2C设备驱动进行匹配,设备地址根据板子的硬件设计得来)

.platform_data = &at24c08,   //使用上面定义的at24c08结构体

},

};

在mini2440_init函数中,对上面定义的板级信息进行了注册:

i2c_register_board_info(0, mini2440_i2c_devs, ARRAY_SIZE(mini2440_i2c_devs));

i2c_register_board_info的声明如下:

int __init i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len);

这里重点说明的参数是第一个参数busnum,这个表示info数组定义的I2C设备所连接的总线号,也是根据板子的硬件连接来定义的。那么这个总线号是根据什么来设置的呢?比如说S3C2440只有一个I2C控制器,但是S5PV210有三个I2C控制器,这个总线号就是标识I2C设备连接到了那个I2C控制器上。

    (2) i2c_driver:I2C设备驱动

这个结构体是定义I2C设备驱动。同一个I2C设备驱动可以对应多个I2C设备。例如drivers/misc/eeprom/at24.c文件中定义了如下i2c_driver结构体:

static struct i2c_driver at24_driver = {
	.driver = {
		.name = "at24",
		.owner = THIS_MODULE,
	},
	.probe = at24_probe,
	.remove = __devexit_p(at24_remove),
	.id_table = at24_ids,
};

at24_ids定义了该驱动支持的I2C设备名称列表(前面的字符串与i2c_board_info中的定义相对应):

static const struct i2c_device_id at24_ids[] = {
	/* needs 8 addresses as A0-A2 are ignored */
	{ "24c00", AT24_DEVICE_MAGIC(128 / 8, AT24_FLAG_TAKE8ADDR) },
	/* old variants can't be handled with this generic entry! */
	{ "24c01", AT24_DEVICE_MAGIC(1024 / 8, 0) },
	{ "24c02", AT24_DEVICE_MAGIC(2048 / 8, 0) },
	/* spd is a 24c02 in memory DIMMs */
	{ "spd", AT24_DEVICE_MAGIC(2048 / 8,
		AT24_FLAG_READONLY | AT24_FLAG_IRUGO) },
	{ "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) },
	/* 24rf08 quirk is handled at i2c-core */
	{ "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) },
	{ "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) },
	{ "24c32", AT24_DEVICE_MAGIC(32768 / 8, AT24_FLAG_ADDR16) },
	{ "24c64", AT24_DEVICE_MAGIC(65536 / 8, AT24_FLAG_ADDR16) },
	{ "24c128", AT24_DEVICE_MAGIC(131072 / 8, AT24_FLAG_ADDR16) },
	{ "24c256", AT24_DEVICE_MAGIC(262144 / 8, AT24_FLAG_ADDR16) },
	{ "24c512", AT24_DEVICE_MAGIC(524288 / 8, AT24_FLAG_ADDR16) },
	{ "24c1024", AT24_DEVICE_MAGIC(1048576 / 8, AT24_FLAG_ADDR16) },
	{ "at24", 0 },
	{ /* END OF LIST */ }
};

    (3) i2c_adapter:I2C控制器驱动

一个i2c_adapter结构体对应一个与CPU相连的I2C控制器。

struct i2c_adapter {
	struct module *owner;
	unsigned int id;
	unsigned int class;		  /* classes to allow probing for */
	const struct i2c_algorithm *algo; //I2C控制器操作算法
	/* 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;                 //I2C控制器编号
	char name[48];
	struct completion dev_released;

	struct list_head userspace_clients;
};


const struct i2c_algorithm *algo是对I2C控制器进行操作的方法。nr是I2C控制器的编号,这个编号是跟注册i2c_board_info结构体时使用的busnum参数相关的。

    (4) i2c_algorithm:I2C控制器操作算法

struct i2c_algorithm {
	/* If an adapter algorithm can't do I2C-level access, set master_xfer
	   to NULL. If an adapter algorithm can do SMBus access, set
	   smbus_xfer. If set to NULL, the SMBus protocol is simulated
	   using common I2C messages */
	/* master_xfer should return the number of messages successfully
	   processed, or a negative value on error */
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);
	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 *);
};

在编写I2C控制器驱动程序时,重点就是要实现这几个函数。

    (5) i2c_client:I2C用户

每一个I2C设备对应一个i2c_client结构体。i2c_client结构体是操作i2c设备的直接接口,因为在i2c_client结构体中包含指向相应i2c_adapter的指针。

明确了以上几个结构体之后,下面重点分析I2C设备的探测过程:

上面已经分析了在板级初始化时注册i2c_board_info结构体,对于每个I2C设备,注册了I2C设备名、I2C地址和连接的总线编号。i2c_register_board_info函数将所有的这些结构体添加到__i2c_board_list链表中。

I2C控制器驱动程序(如i2c-s3c2410.c)通常定义为平台设备,这个跟一般的Linux设备驱动程序是一致的。在该驱动程序probe时,会首先分配一个i2c_adapter结构体,然后调用i2c_add_numbered_adapter(struct i2c_adapter *adap)函数。这个函数使用静态的i2c busnum。这个函数又会调用int i2c_register_adapter(struct i2c_adapter *adap)函数。

int i2c_register_adapter(struct i2c_adapter *adap)函数首先调用device_register对该i2c_adapter设备进行注册,然后调用static void i2c_scan_static_board_info(struct i2c_adapter *adapter)函数。

这个函数比较重要:

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);
}

devinfo指针相当于一个游标。list_for_each_entry对__i2c_board_list中的每个元素进行遍历。首先判断busnum是否与该adapter的编号一致,如果不一致,就直接返回。如果一致,就进一步调用i2c_new_device函数对于匹配成功的i2c_board_info结构体创建一个device。如果创建失败,打印失败信息。

struct i2c_client * i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info):通过这个函数的参数可以明白这个函数的功能了。就是通过匹配成功的i2c_adapter和i2c_board_info结构体,创建一个i2c_client结构体,并返回指向该结构体的指针。其实该函数更重要的功能是创建了一个device,并将该device添加到I2C总线上去。

	client->dev.parent = &client->adapter->dev;
	client->dev.bus = &i2c_bus_type; /*添加到i2c总线*/
	client->dev.type = &i2c_client_type;
#ifdef CONFIG_OF
	client->dev.of_node = info->of_node;
#endif

	dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
		     client->addr);
	status = device_register(&client->dev);/*注册i2c总线设备*/
	if (status)
		goto out_err;

既然将该设备添加到了总线上,就看看i2c总线的匹配函数:

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 (!client)
		return 0;

	driver = to_i2c_driver(drv);
	/* match on an id table if there is one */
	if (driver->id_table)
		return i2c_match_id(driver->id_table, client)!= NULL;

	return 0;
}


另外注意的是,在I2C设备驱动程序中,调用i2c_add_driver函数将i2c_driver结构体也添加到了i2c总线上。由Linux设备驱动模型知,在添加driver或者device时,都会调用这个i2c bus的匹配函数。对上面这个函数就不具体分析了,主要就是根据i2c_diver的id table匹配根据i2c_board_info生成的client结构体的name。client->name在i2c_new_device函数中填充:

	strlcpy(client->name, info->type, sizeof(client->name));


匹配成功后,就是调用I2C总线的probe函数:

static int i2c_device_probe(struct device *dev)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;
	int status;

	if (!client)
		return 0;

	driver = to_i2c_driver(dev->driver);
	if (!driver->probe || !driver->id_table)
		return -ENODEV;
	client->driver = driver;
	if (!device_can_wakeup(&client->dev))
		device_init_wakeup(&client->dev,
					client->flags & I2C_CLIENT_WAKE);
	dev_dbg(dev, "probe\n");

	status = driver->probe(client, i2c_match_id(driver->id_table, client)); /*调用i2c_driver的probe函数*/
	if (status) {
		client->driver = NULL;
		i2c_set_clientdata(client, NULL);
	}
	return status;
}

i2c_driver的probe函数就可以使用client结构体完成对设备的操作。

最后给出dev-i2c0.c(linux/arch/arm/plat-s3c/dev-i2c0.c,dev-i2c1.c,dev-i2c2.c对应另外两个i2c控制器)对I2C控制器平台设备的定义:

static struct resource s3c_i2c_resource[] = {
	[0] = {
		.start = S3C_PA_IIC,
		.end   = S3C_PA_IIC + SZ_4K - 1,
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = IRQ_IIC,
		.end   = IRQ_IIC,
		.flags = IORESOURCE_IRQ,
	},
};

struct platform_device s3c_device_i2c0 = {
	.name		  = "s3c2410-i2c",
	id		  = 0,  //I2C控制器驱动程序根据这个生成i2c_adapter的busnum
        .num_resources	  = ARRAY_SIZE(s3c_i2c_resource),
	.resource	  = s3c_i2c_resource,
};

static struct s3c2410_platform_i2c default_i2c_data0 __initdata = {
	.flags		= 0,
	.slave_addr	= 0x10,
	.frequency	= 400*1000,
	.sda_delay	= S3C2410_IICLC_SDA_DELAY15 | S3C2410_IICLC_FILTER_ON,
};


总而言之,最重要的还是Linux设备驱动模型,整个架构是建立在Linux设备驱动模型之上的。i2c_client这个结构体使得i2c_driver的实现与i2c_adapter的实现分离出来,而i2c_board_info使板级设计与i2c_driver分离出来。最终进行探测匹配的还是i2c_driver和i2c_device,只不过中间多出来个生成i2c_client的过程。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值