欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

聊聊i2c框架

程序员文章站 2024-02-26 23:54:04
...
1,聊聊i2c
2,那就开始吧
3,其实我不想贴代码
    3.1板级支持包
    3.2 内核注册i2c设备
    3.3 去找driver吧


1,聊聊i2c

    以前一直觉得linux i2c好复杂,
    做android驱动开发的时候,用的最多的就是i2c了,各种sensor都是i2c接口。在mtk平台上调试一个sensor,发现i2c最多发送7个字节,超过7个字节就出错。项目紧加上没看过i2c平台的源码,解不出来。于是找mtk帮助,然后mtk的开发人员,自己封装了一个使用dma的i2c api接口给我,然后就通信成功。当时感叹很多,,,
    于是下决心把i2c的源码框架看一遍,但是都中途放弃了,主要原因是看不懂,,,终于在看了6、7次后的一天半夜,把代码马马虎虎的看了一遍,然后过了几天去一个公司面试的时候,面试官说聊聊设备平台总线吧,于是我就聊起了i2c,然后面过了,哈哈

    总的意思就是i2c使用的这么频繁,既然是搞研发,怎么说也得把i2c框架了解一下吧。

2,那就开始吧
    i2c这个框架中有几个名词 adaptr, algorithm, client
    1. adapter : 这个对应一个i2c适配器,就是你的三星、mtk、高通啦这些soc里的i2c硬件模块
    2. algorithm: 算法的意思,有点高大上的感觉。接地气的说法就是对应控制i2c发送start end等信号的东西,就当是i2c硬件模块中的控制模块
    3. client:对应外设i2c器件
以上的说法比较俗,也不是非常的完善,暂时先有个大体的框架

我们再来说说这几个名词的对应关系吧
    adapter这个肯定是独一无二的了,那么algorithm也是独一无二的
adapter <---> algorithm
    这个client,外设吗!外设肯定不止一个了,一个i2c线上可以挂好多设备啊
adapter <---> 多个client

algorithm <---> 多个client

聊聊i2c框架

3,其实我不想贴代码
    说来说去,还是贴代码来的的实在,一是说服力强,二是占空间!!!
3.1板级支持包

    板级支持包里,一般都会有这样的一段代码(请原谅我还没有使用dts...) 

static struct i2c_board_info i2c0_boardinfo[] = {
	{
		//I2C_BOARD_INFO("tlv320aic3x", 0x18),
		I2C_BOARD_INFO("wm8523", 0x1a),
	}
}
对就是这个i2c_board_info
type = "wm8523" //i2c外设的名字
addr = 0x1a     //i2c外设的地址(7位地址)

而对于这个结构体,我们使用下面的函数来注册
i2c_registr_board_info(0, i2c_boardinfo, 1);

这个意思是我要把这个"wm8523"i2c设备挂在i2c0上

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

	down_write(&__i2c_board_lock);

	/* dynamic bus numbers will be assigned after the last static one */
	if (busnum >= __i2c_first_dynamic_bus_num)
		__i2c_first_dynamic_bus_num = busnum + 1;

	for (status = 0; len; len--, info++) {
		struct i2c_devinfo	*devinfo;

		devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
		if (!devinfo) {
			pr_debug("i2c-core: can't register boardinfo!\n");
			status = -ENOMEM;
			break;
		}

		devinfo->busnum = busnum;
		devinfo->board_info = *info;
		list_add_tail(&devinfo->list, &__i2c_board_list);
	}

	up_write(&__i2c_board_lock);

	return status;
}
这个函数做了啥?就是把外设的信息,外设要挂载的信息通过一个链表挂在了
__i2c_board_list这个链表上,然后就完了,对完了。。。

和i2c还没产生啥关系啊。。。

3.2 内核注册i2c设备
既然要使用i2c,那么就去创建i2c的框架吧

删删删 终于把代码删减到现在这么多了

static int __devinit
omap_i2c_probe(struct platform_device *pdev)
{
    /* dev的类似和平台厂商有关,不用过多纠结 */
    struct omap_i2c_dev    *dev;
    /* 这个就是adapter了,实例化的i2c硬件模块 */
    struct i2c_adapter *adap;
    ...
    dev = kzalloc(sizeof(struct omap_i2c_dev), GFP_KERNEL);
    ...
    /* 初始化i2c的设备 不关心 */
    omap_i2c_init(dev);
    ...
    /* 申请i2c的中断 不关心 */
    r = request_irq(dev->irq, isr, IRQF_NO_SUSPEND, pdev->name, dev);
    ...
    adap = &dev->adapter;
    ...
 /* 这个就是algorithm了,发送、结束、应答全靠它! */
    adap->algo = &omap_i2c_algo;
    ...
    /* 增加一个有编号的i2c适配器,
     * 可以理解 soc还有好几个i2c呢
     * i2c0,i2c1,i2c2.... i2cX
     */
    adap->nr = pdev->id;
    r = i2c_add_numbered_adapter(adap);
...
}

看看i2c_add_numbered_adapter,再删删

int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
        status = i2c_register_adapter(adap);
}
static int i2c_register_adapter(struct i2c_adapter *adap)
{
 ...
    dev_set_name(&adap->dev, "i2c-%d", adap->nr);
 
    /* 这里成功注册一个 /sys/bus/platform/devices/xxxxx/i2c-1 */
    res = device_register(&adap->dev);
    ...
    /* create pre-declared device nodes */
    /* 上面板级支持包里挂在在__i2c_board_list上信息要起作用了 */
    if (adap->nr < __i2c_first_dynamic_bus_num)
        i2c_scan_static_board_info(adap);
    ...	
    /* Notify drivers */
    /* 这里主要是找driver */
    bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
    ...
}

ok 我们看i2c_scan_static_board_info

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

这里当注册i2c0的适配器adapter的时候,会到__i2c_board_list链表找到要挂在i2c0的外设"wm8523" 。

哎呀,好巧,那么就去i2c_new_device吧

这个i2c_new_device就会产生我们经常说的device和driver匹配的时候会调用probe函数,对就是那个device.

struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
    struct i2c_client  *client;
    int         status;
    /* 其实这个就是我们在驱动里面操作的client, 这里早早的就出现了 */ 
    client = kzalloc(sizeof *client, GFP_KERNEL);
    if (!client)
        return NULL;
    /* 找到adapter 发信息离不开它 */
    client->adapter = adap;
    ...
    client->flags = info->flags;
    /* 外设自己的地址自己记住,只有自己知道 */
    client->addr = info->addr;
    client->irq = info->irq;
    /* client->name = "wm8523" */
    strlcpy(client->name, info->type, sizeof(client->name));
 
    /* Check for address validity */
    /* 检测地址是否有效 */
    status = i2c_check_client_addr_validity(client);
    /* Check for address business */
    /* 检测地址是否和这个i2c上的其他器件冲突 */
    status = i2c_check_addr_busy(adap, client->addr);
    /* 做人不能忘本,一定要记住自己挂在哪条总线
     * 因为device找到driver后的probe还要靠总线呢
     */
     client->dev.bus = &i2c_bus_type;
     client->dev.type = &i2c_client_type;

    /* For 10-bit clients, add an arbitrary offset to avoid collisions */
    /* 这里把dev的name命名成了 "0-001a",为什么不是"wm8523" 
     * 这样driver岂不是通过名字找不到device了???
     * 注意看上面红色的行
     * client->name = "wm8523"
     */
    dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
             client->addr | ((client->flags & I2C_CLIENT_TEN)
                     ? 0xa000 : 0));
    /* dev带着"0-001a"而不是"wm8523"的名字遗憾的去注册了 */
    status = device_register(&client->dev);
    if (status)
        goto out_err;
    /* return也没人用啊
     * client在这里了等待
     * 等到有设备找到带着"0-001a"名字注册的dev的时候,
     * 就是client再现江湖的时候
     */
    return client;
 
out_err:
    kfree(client);
    return NULL;
}
这里整理一下client有什么?
1. adapter有了,简介的algorithm简介也有了,数据传输不愁了
2. addr地址有了,发送数据到哪里确定了
3. dev有了,client再现江湖不远了
还差啥啊,driver

没有driver谁去操作client啊,所以去找driver吧

3.3 去找driver吧

static const struct i2c_device_id wm8523_i2c_id[] = {
	{ "wm8523", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, wm8523_i2c_id);

static struct i2c_driver wm8523_i2c_driver = {
	.driver = {
		.name = "wm8523-codec",
		.owner = THIS_MODULE,
		.of_match_table = wm8523_of_match,
	},
	.probe =    wm8523_i2c_probe,
	.remove =   __devexit_p(wm8523_i2c_remove),
	.id_table = wm8523_i2c_id,
};
#endif

static int __init wm8523_modinit(void)
{
	int ret;
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
	ret = i2c_add_driver(&wm8523_i2c_driver);
	if (ret != 0) {
		printk(KERN_ERR "Failed to register WM8523 I2C driver: %d\n",
		       ret);
	}
#endif
	return 0;
}
代码是贴了但是probe怎么开始执行的?
#define i2c_add_driver(driver) \  
	i2c_register_driver(THIS_MODULE, driver)  

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
	int res;

	...

	/* add the driver to the list of i2c drivers in the driver core */
	driver->driver.owner = owner;
	driver->driver.bus = &i2c_bus_type;

	/* When registration returns, the driver core
	 * will have called probe() for all matching-but-unbound devices.
	 */
	res = driver_register(&driver->driver);
	if (res)
		return res;

	...

	pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);

	INIT_LIST_HEAD(&driver->clients);
	/* Walk the adapters that are already present */
	i2c_for_each_dev(driver, __process_new_driver);

	return 0;
}
记住不能忘本,不能忘记自己挂在i2c_bus_type上
简述一个driver找device的步骤
driver_register
    -> bus_add_driver
        -> driver_attach
            -> __driver_attach
                -> driver_match_device

                    -> i2c_bus_type.match <--> i2c_device_match

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;

	/* Attempt an OF style match */
	if (of_driver_match_device(dev, drv))
		return 1;

	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;
}
看到没那个带着"0-001a"名字注册的dev找到了
i2c_verify_client(dev) 让client重出江湖,这样就client->name "vm8523"和driver->id_table中的名字匹配上了
接上面
__driver_attach
    -> driver_probe_device
        -> really_probe
            -> dev->bus->probe <--> i2c_bus_type.probe <--> i2c_device_probe
                -> driver->probe <--> wm8523_i2c_probe

在往后就算正常的设备操作了
就聊到这里吧!

相关标签: linux i2c