i2c子系统学习总结
2017-12-20 11:25
435 查看
这几天在学习一下tlv320aix3106音频芯片的移植,所以接触了一下i2c子系统,做一个简单的学习总结。先来一张大图
2.i2c总线驱动:,主要包含i2c适配器i2c_adapter,algorithm数据结构,以及产生i2c通信的函数(主要跟cpu的i2c有关)。
3.i2c设备驱动:主要包含i2c_driver跟i2c_client,具体的设备实现(主要跟具体的外设有关,比如在/sound/soc/corecs/tlv320aic3x.c 内就有i2c_driver aic3x_i2c_driver设备)。
以下用i2c-davinci.c作为分析。
一个很常规的platform_driver驱动代码,我们在板级文件只有注册一个名为“i2c_davinci”的设备就会调用.probe = davinci_i2c_probe,这个函数,接下来看一下davinci_i2c_probe函数的实现。
前面的代码跟主要跟cpu的i2c初始化有关,包括获取内存,中断号以及进行地址映射以及相对于硬件初始化i2c_davinci_init(dev);,这些我们不做太详细关注。
主要在这个函数做了i2c_adapter数据结构的初始化跟增加。
接下来我们进入i2c_adapter增加函数i2c_add_numbered_adapter(adap);
进入i2c_adapter的注册函数i2c_register_adapter(adap)
进入函数i2c_scan_static_board_info(adap);
这个函数会对我们的板级文件中I2C_BOARD_INFO匹配,匹配到生成一个i2c_client数据结构以及注册一个新的设备在节点下。
进行检查,然后在i2c_new_device函数里边实现i2c_client数据结构,再调用device_register函数在节点生成一个设备。至此i2c总线驱动分析结束。
i2c_new_device函数里边有这样的一条函数,值得注意一下
client->dev.platform_data = info->platform_data;
i2c_driver跟i2c_client匹配是通过i2c_driver->id_table 跟client->dev.platform_data进行名字匹配,而client->dev.platform_data来至于info->platform_data,所以我们可以知道id_table 跟 I2C_BOARD_INFO是有名字上的联系的,这个是我们接下来i2c_driver的id_table的设置的依据。
下面大概说一下i2c_adapter怎么跟i2c的通信方法algorithm关联上的,回到 davinci_i2c_probe函数,我们可以看到adap->algo = &i2c_davinci_algo;这段程序就给i2c_adapter数据结构成员algo指向一个函数,就是此i2c适配器的i2c通讯方法了。
主要看.master_xfer = i2c_davinci_xfer,
i2c_davinci_xfer_msg(adap, &msgs[i], (i == (num - 1)));这个函数就是i2c实现数据发送接收的函数了,里边主要是cpu硬件相关的操作,这里就不详细了解了。
首先看i2c_driver这个数据结构
首先承接i2c总线驱动最后的内容进行总结,我们进入.id_table = aic3x_i2c_id这个元素看一下。
可以看到aic3x_i2c_id的{ “tlv320aic3x”, AIC3X_MODEL_3X }名字是跟我们板级文件里边,i2c_board_info的I2C_BOARD_INFO(“tlv320aic3x”, 0x18)名字是一样,这样在,i2c总线驱动i2c_bus_type的match()函数i2c_device_match(),会调用i2c_match_id(),匹配板文件中定义的ID和i2c_driver所支持的ID表。
所以我们进入/sys/devices/platform/i2c_davinci.1/i2c-1/1-0018查看
可以看到节点下确实挂在了一个名为tlv320aic3x.-codec的设备,这个设备就是在上面i2c_new_device函数里边生成的。
接下来看一下.probe = aic3x_i2c_probe,
这里就是一些具体的挂载在i2c总线上设备的操作了,我们也不具体了解,主要看一条程序i2c_set_clientdata(i2c, aic3x);这里将i2c_driver的id匹配到的client传递给相关数据结构,给现对于的具体设备进行i2c通信的控制。到此整个的总结结束。
互相学习,有错误的地方欢迎留言纠正,有所交流也欢迎留言,或者发邮件
Berlin_7123@163.com
如果有荣幸被转载还希望能标明出处,谢谢!
i2c子系统3个组成部分
1.i2c核心:主要提供i2c总线驱动(i2c_adapter)和设备驱动(i2c_driver)的注册注销方法,i2c的通信方法(algorithm)。2.i2c总线驱动:,主要包含i2c适配器i2c_adapter,algorithm数据结构,以及产生i2c通信的函数(主要跟cpu的i2c有关)。
3.i2c设备驱动:主要包含i2c_driver跟i2c_client,具体的设备实现(主要跟具体的外设有关,比如在/sound/soc/corecs/tlv320aic3x.c 内就有i2c_driver aic3x_i2c_driver设备)。
i2c总线的实现
i2c总线主要涉及到i2c_adapter、algorithm结构以及对应的i2c通信方法的实现。一般这部分代码是存在的了,在/driver/i2c/busses对应的芯片的i2c驱动下。以下用i2c-davinci.c作为分析。
static struct platform_driver davinci_i2c_driver = { .probe = davinci_i2c_probe, .remove = davinci_i2c_remove, .driver = { .name = "i2c_davinci", .owner = THIS_MODULE, .pm = davinci_i2c_pm_ops, }, }; /* I2C may be needed to bring up other drivers */ static int __init davinci_i2c_init_driver(void) { return platform_driver_register(&davinci_i2c_driver); } subsys_initcall(davinci_i2c_init_driver); static void __exit davinci_i2c_exit_driver(void) { platform_driver_unregister(&davinci_i2c_driver); } module_exit(davinci_i2c_exit_driver); MODULE_AUTHOR("Texas Instruments India"); MODULE_DESCRIPTION("TI DaVinci I2C bus adapter"); MODULE_LICENSE("GPL");
一个很常规的platform_driver驱动代码,我们在板级文件只有注册一个名为“i2c_davinci”的设备就会调用.probe = davinci_i2c_probe,这个函数,接下来看一下davinci_i2c_probe函数的实现。
static int davinci_i2c_probe(struct platform_device *pdev) { struct davinci_i2c_dev *dev; struct i2c_adapter *adap; struct resource *mem, *irq, *ioarea; int r; /* NOTE: driver uses the static register mapping */ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!mem) { dev_err(&pdev->dev, "no mem resource?\n"); return -ENODEV; } irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!irq) { dev_err(&pdev->dev, "no irq resource?\n"); return -ENODEV; } ioarea = request_mem_region(mem->start, resource_size(mem), pdev->name); if (!ioarea) { dev_err(&pdev->dev, "I2C region already claimed\n"); return -EBUSY; } dev = kzalloc(sizeof(struct davinci_i2c_dev), GFP_KERNEL); if (!dev) { r = -ENOMEM; goto err_release_region; } init_completion(&dev->cmd_complete); #ifdef CONFIG_CPU_FREQ init_completion(&dev->xfr_complete); #endif dev->dev = get_device(&pdev->dev); dev->irq = irq->start; platform_set_drvdata(pdev, dev); dev->clk = clk_get(&pdev->dev, NULL); if (IS_ERR(dev->clk)) { r = -ENODEV; goto err_free_mem; } clk_enable(dev->clk); dev->base = ioremap(mem->start, resource_size(mem)); if (!dev->base) { r = -EBUSY; goto err_mem_ioremap; } i2c_davinci_init(dev); r = request_irq(dev->irq, i2c_davinci_isr, 0, pdev->name, dev); if (r) { dev_err(&pdev->dev, "failure requesting irq %i\n", dev->irq); goto err_unuse_clocks; } r = i2c_davinci_cpufreq_register(dev); if (r) { dev_err(&pdev->dev, "failed to register cpufreq\n"); goto err_free_irq; } adap = &dev->adapter; i2c_set_adapdata(adap, dev); adap->owner = THIS_MODULE; adap->class = I2C_CLASS_HWMON; strlcpy(adap->name, "DaVinci I2C adapter", sizeof(adap->name)); adap->algo = &i2c_davinci_algo; adap->dev.parent = &pdev->dev; adap->timeout = DAVINCI_I2C_TIMEOUT; adap->nr = pdev->id; r = i2c_add_numbered_adapter(adap); if (r) { dev_err(&pdev->dev, "failure adding adapter\n"); goto err_free_irq; } return 0; err_free_irq: free_irq(dev->irq, dev); err_unuse_clocks: iounmap(dev->base); err_mem_ioremap: clk_disable(dev->clk); clk_put(dev->clk); dev->clk = NULL; err_free_mem: platform_set_drvdata(pdev, NULL); put_device(&pdev->dev); kfree(dev); err_release_region: release_mem_region(mem->start, resource_size(mem)); return r; }
前面的代码跟主要跟cpu的i2c初始化有关,包括获取内存,中断号以及进行地址映射以及相对于硬件初始化i2c_davinci_init(dev);,这些我们不做太详细关注。
主要在这个函数做了i2c_adapter数据结构的初始化跟增加。
接下来我们进入i2c_adapter增加函数i2c_add_numbered_adapter(adap);
int i2c_add_numbered_adapter(struct i2c_adapter *adap) { int id; int status; if (adap->nr == -1) /* -1 means dynamically assign bus id */ return i2c_add_adapter(adap); if (adap->nr & ~MAX_ID_MASK) return -EINVAL; retry: if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0) return -ENOMEM; mutex_lock(&core_lock); /* "above" here means "above or equal to", sigh; * we need the "equal to" result to force the result */ status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id); if (status == 0 && id != adap->nr) { status = -EBUSY; idr_remove(&i2c_adapter_idr, id); } mutex_unlock(&core_lock); if (status == -EAGAIN) goto retry; if (status == 0) status = i2c_register_adapter(adap); return status; }
进入i2c_adapter的注册函数i2c_register_adapter(adap)
static int i2c_register_adapter(struct i2c_adapter *adap) { int res = 0; /* Can't register until after driver model init */ if (unlikely(WARN_ON(!i2c_bus_type.p))) { res = -EAGAIN; goto out_list; } /* Sanity checks */ if (unlikely(adap->name[0] == '\0')) { pr_err("i2c-core: Attempt to register an adapter with " "no name!\n"); return -EINVAL; } if (unlikely(!adap->algo)) { pr_err("i2c-core: Attempt to register adapter '%s' with " "no algo!\n", adap->name); return -EINVAL; } rt_mutex_init(&adap->bus_lock); mutex_init(&adap->userspace_clients_lock); INIT_LIST_HEAD(&adap->userspace_clients); /* Set default timeout to 1 second if not already set */ if (adap->timeout == 0) adap->timeout = HZ; dev_set_name(&adap->dev, "i2c-%d", adap->nr); adap->dev.bus = &i2c_bus_type; adap->dev.type = &i2c_adapter_type; res = device_register(&adap->dev); // adapter也作为一个设备注册,这里得到的sys路径为:/sys/devices/platform/i2c-davinci.1/i2c-2 if (res) goto out_list; dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name); #ifdef CONFIG_I2C_COMPAT res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev, adap->dev.parent); if (res) dev_warn(&adap->dev, "Failed to create compatibility class link\n"); #endif /* create pre-declared device nodes */ if (adap->nr < __i2c_first_dynamic_bus_num) i2c_scan_static_board_info(adap); /* Notify drivers */ mutex_lock(&core_lock); bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter); mutex_unlock(&core_lock); return 0; out_list: mutex_lock(&core_lock); idr_remove(&i2c_adapter_idr, adap->nr); mutex_unlock(&core_lock); return res; }
进入函数i2c_scan_static_board_info(adap);
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))// 将i2c_boardinfo传入,构造一个i2c_client, 将此i2c设备与所有此i2c线上的驱动匹配 dev_err(&adapter->dev, "Can't create device at 0x%02x\n", devinfo->board_info.addr); } up_read(&__i2c_board_lock); }
这个函数会对我们的板级文件中I2C_BOARD_INFO匹配,匹配到生成一个i2c_client数据结构以及注册一个新的设备在节点下。
static struct i2c_board_info __initdata da850_evm_i2c_devices[] = { { I2C_BOARD_INFO("tlv320aic3x", 0x18), }, { I2C_BOARD_INFO(EEPROM_DEVICE, 0x50), .platform_data = &da850_evm_i2c_eeprom_info, }, #if 0 { I2C_BOARD_INFO("tca6416", 0x20), .platform_data = &da850_evm_ui_expander_info, }, { I2C_BOARD_INFO("tca6416", 0x21), .platform_data = &da850_evm_bb_expander_info, }, { I2C_BOARD_INFO("cdce913", 0x65), }, { I2C_BOARD_INFO("PCA9543A", 0x73), }, #endif };
进行检查,然后在i2c_new_device函数里边实现i2c_client数据结构,再调用device_register函数在节点生成一个设备。至此i2c总线驱动分析结束。
i2c_new_device函数里边有这样的一条函数,值得注意一下
client->dev.platform_data = info->platform_data;
i2c_driver跟i2c_client匹配是通过i2c_driver->id_table 跟client->dev.platform_data进行名字匹配,而client->dev.platform_data来至于info->platform_data,所以我们可以知道id_table 跟 I2C_BOARD_INFO是有名字上的联系的,这个是我们接下来i2c_driver的id_table的设置的依据。
下面大概说一下i2c_adapter怎么跟i2c的通信方法algorithm关联上的,回到 davinci_i2c_probe函数,我们可以看到adap->algo = &i2c_davinci_algo;这段程序就给i2c_adapter数据结构成员algo指向一个函数,就是此i2c适配器的i2c通讯方法了。
static struct i2c_algorithm i2c_davinci_algo = { .master_xfer = i2c_davinci_xfer, .functionality = i2c_davinci_func, };
主要看.master_xfer = i2c_davinci_xfer,
static int i2c_davinci_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) { struct davinci_i2c_dev *dev = i2c_get_adapdata(adap); int i; int ret; dev_dbg(dev->dev, "%s: msgs: %d\n", __func__, num); ret = i2c_davinci_wait_bus_not_busy(dev, 1); if (ret < 0) { dev_warn(dev->dev, "timeout waiting for bus ready\n"); return ret; } for (i = 0; i < num; i++) { ret = i2c_davinci_xfer_msg(adap, &msgs[i], (i == (num - 1))); dev_dbg(dev->dev, "%s [%d/%d] ret: %d\n", __func__, i + 1, num, ret); if (ret < 0) return ret; } #ifdef CONFIG_CPU_FREQ complete(&dev->xfr_complete); #endif return num; }
i2c_davinci_xfer_msg(adap, &msgs[i], (i == (num - 1)));这个函数就是i2c实现数据发送接收的函数了,里边主要是cpu硬件相关的操作,这里就不详细了解了。
设备驱动实现
以/sound/soc/corece/tlv320aix3x.c为例子首先看i2c_driver这个数据结构
static struct i2c_driver aic3x_i2c_driver = { .driver = { .name = "tlv320aic3x-codec", .owner = THIS_MODULE, }, .probe = aic3x_i2c_probe, .remove = aic3x_i2c_remove, .id_table = aic3x_i2c_id, };
首先承接i2c总线驱动最后的内容进行总结,我们进入.id_table = aic3x_i2c_id这个元素看一下。
static const struct i2c_device_id aic3x_i2c_id[] = { { "tlv320aic3x", AIC3X_MODEL_3X }, { "tlv320aic33", AIC3X_MODEL_33 }, { "tlv320aic3007", AIC3X_MODEL_3007 }, { } };
可以看到aic3x_i2c_id的{ “tlv320aic3x”, AIC3X_MODEL_3X }名字是跟我们板级文件里边,i2c_board_info的I2C_BOARD_INFO(“tlv320aic3x”, 0x18)名字是一样,这样在,i2c总线驱动i2c_bus_type的match()函数i2c_device_match(),会调用i2c_match_id(),匹配板文件中定义的ID和i2c_driver所支持的ID表。
所以我们进入/sys/devices/platform/i2c_davinci.1/i2c-1/1-0018查看
可以看到节点下确实挂在了一个名为tlv320aic3x.-codec的设备,这个设备就是在上面i2c_new_device函数里边生成的。
接下来看一下.probe = aic3x_i2c_probe,
static int aic3x_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct aic3x_pdata *pdata = i2c->dev.platform_data; struct aic3x_priv *aic3x; int ret; aic3x = devm_kzalloc(&i2c->dev, sizeof(struct aic3x_priv), GFP_KERNEL); if (aic3x == NULL) { dev_err(&i2c->dev, "failed to create private data\n"); return -ENOMEM; } aic3x->control_type = SND_SOC_I2C; i2c_set_clientdata(i2c, aic3x); if (pdata) { aic3x->gpio_reset = pdata->gpio_reset; aic3x->setup = pdata->setup; } else { aic3x->gpio_reset = -1; } aic3x->model = id->driver_data; ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_aic3x, &aic3x_dai, 1); return ret; }
这里就是一些具体的挂载在i2c总线上设备的操作了,我们也不具体了解,主要看一条程序i2c_set_clientdata(i2c, aic3x);这里将i2c_driver的id匹配到的client传递给相关数据结构,给现对于的具体设备进行i2c通信的控制。到此整个的总结结束。
互相学习,有错误的地方欢迎留言纠正,有所交流也欢迎留言,或者发邮件
Berlin_7123@163.com
如果有荣幸被转载还希望能标明出处,谢谢!
相关文章推荐
- STM32 I2C总线 自我总结学习 不断更新中
- linux_I2C学习三(I2C子系统)
- 【北大天网搜索引擎TSE学习笔记】第10节——查询服务子系统/总结
- I2C和SPI学习总结
- linux内核I2C子系统学习(一)
- UART、I2C以及SPI协议学习总结
- 综合布线的6大子系统定义(个人总结学习)
- I2C总线-学习总结
- 协议[I2C]_I2C子系统之总结
- linux内核I2C子系统学习(三)
- I2C子系统之总结
- 关于高通8053平台i2c和spi配置的学习总结
- linux内核I2C子系统学习(二)
- I2C学习总结
- linux内核I2C子系统学习(一)
- I2C原理学习总结
- linux内核I2C子系统学习(三)
- linux内核I2C子系统学习(二)
- 关于高通8053平台i2c和spi配置的学习总结
- linux内核I2C子系统学习(三)