您的位置:首页 > 其它

i2c子系统学习总结

2017-12-20 11:25 435 查看
这几天在学习一下tlv320aix3106音频芯片的移植,所以接触了一下i2c子系统,做一个简单的学习总结。先来一张大图



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

如果有荣幸被转载还希望能标明出处,谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  i2c子系统