需要了解Linux驱动子系统之一的I2C

嵌入式技术

1372人已加入

描述

4 总线驱动

4.1 概述
I2C总线驱动是I2C适配器的软件实现,提供I2C适配器与从设备间完成数据通信的能力,比如起始,停止,应答信号和master_xfer的实现函数。
I2C总线驱动由i2c_adapter和i2c_algorithm来描述
4.2 DM8168 I2C控制器的硬件描述
DM8168处理器内部集成了二个I2C控制器,通过一下寄存器来进行控制:

const static u8 omap4_reg_map[] = {    [OMAP_I2C_REV_REG] = 0x04, [OMAP_I2C_IE_REG] = 0x2c, [OMAP_I2C_STAT_REG] = 0x28, [OMAP_I2C_IV_REG] = 0x34, [OMAP_I2C_WE_REG] = 0x34, [OMAP_I2C_SYSS_REG] = 0x90, [OMAP_I2C_BUF_REG] = 0x94, [OMAP_I2C_CNT_REG] = 0x98, [OMAP_I2C_DATA_REG] = 0x9c, [OMAP_I2C_SYSC_REG] = 0x20, [OMAP_I2C_CON_REG] = 0xa4, [OMAP_I2C_OA_REG] = 0xa8, [OMAP_I2C_SA_REG] = 0xac, [OMAP_I2C_PSC_REG] = 0xb0, [OMAP_I2C_SCLL_REG] = 0xb4, [OMAP_I2C_SCLH_REG] = 0xb8, [OMAP_I2C_SYSTEST_REG] = 0xbC, [OMAP_I2C_BUFSTAT_REG] = 0xc0, [OMAP_I2C_REVNB_LO] = 0x00, [OMAP_I2C_REVNB_HI] = 0x04, [OMAP_I2C_IRQSTATUS_RAW] = 0x24, [OMAP_I2C_IRQENABLE_SET] = 0x2c, [OMAP_I2C_IRQENABLE_CLR] = 0x30,};

4.3 i2c-oamp总线驱动分析(platform_driver)
I2C总线驱动代码在drivers/i2c/busses/i2c-oamp.c,这个代码同样支持其他TI 芯片。
初始化模块和卸载模块

/* I2C may be needed to bring up other drivers */static int __initomap_i2c_init_driver(void){ return platform_driver_register(&omap_i2c_driver);}subsys_initcall(omap_i2c_init_driver);static void __exit omap_i2c_exit_driver(void){ platform_driver_unregister(&omap_i2c_driver);}module_exit(omap_i2c_exit_driver);

总线驱动是基于platform来实现的,很符合设备驱动模型的思想。

static struct platform_driver omap_i2c_driver = { .probe = omap_i2c_probe, .remove = omap_i2c_remove, .driver = { .name = "omap_i2c", .owner = THIS_MODULE, },};

oamp_i2c_probe函数
当调用platform_driver_register函数注册platform_driver结构体时,如果platformdevice 和 platform driver匹配成功后,会调用probe函数,来初始化适配器硬件。

static int __devinitomap_i2c_probe(struct platform_device *pdev){ struct omap_i2c_dev *dev; struct i2c_adapter *adap; struct resource *mem, *irq, *ioarea; struct omap_i2c_bus_platform_data *pdata = pdev->dev.platform_data; irq_handler_t isr; int r; u32 speed = 0; /* NOTE: driver uses the static register mapping */ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!mem) { dev_err(&pdev->dev, "no mem resource? "); return -ENODEV; } irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!irq) { dev_err(&pdev->dev, "no irq resource? "); return -ENODEV; } ioarea = request_mem_region(mem->start, resource_size(mem), pdev->name); if (!ioarea) { dev_err(&pdev->dev, "I2C region already claimed "); return -EBUSY; } dev = kzalloc(sizeof(struct omap_i2c_dev), GFP_KERNEL); if (!dev) { r = -ENOMEM; goto err_release_region; } if (pdata != NULL) { speed = pdata->clkrate; dev->set_mpu_wkup_lat = pdata->set_mpu_wkup_lat; } else { speed = 100; /* Default speed */ dev->set_mpu_wkup_lat = NULL; } dev->speed = speed; dev->idle = 1; dev->dev = &pdev->dev; dev->irq = irq->start; dev->base = ioremap(mem->start, resource_size(mem)); if (!dev->base) { r = -ENOMEM; goto err_free_mem; } platform_set_drvdata(pdev, dev); if (cpu_is_omap7xx()) dev->reg_shift = 1; else if (cpu_is_omap44xx() || cpu_is_ti81xx()) dev->reg_shift = 0; else dev->reg_shift = 2; if (cpu_is_omap44xx() || cpu_is_ti81xx()) dev->regs = (u8 *) omap4_reg_map; else dev->regs = (u8 *) reg_map; pm_runtime_enable(&pdev->dev); omap_i2c_unidle(dev); dev->rev = omap_i2c_read_reg(dev, OMAP_I2C_REV_REG) & 0xff; if (dev->rev <= OMAP_I2C_REV_ON_3430) dev->errata |= I2C_OMAP3_1P153; if (!(cpu_class_is_omap1() || cpu_is_omap2420())) { u16 s; /* Set up the fifo size - Get total size */ s = (omap_i2c_read_reg(dev, OMAP_I2C_BUFSTAT_REG) >> 14) & 0x3; dev->fifo_size = 0x8 << s; /* * Set up notification threshold as half the total available * size. This is to ensure that we can handle the status on int * call back latencies. */ if (dev->rev >= OMAP_I2C_REV_ON_4430) { dev->fifo_size = 0; dev->b_hw = 0; /* Disable hardware fixes */ } else { dev->fifo_size = (dev->fifo_size / 2); dev->b_hw = 1; /* Enable hardware fixes */ } /* calculate wakeup latency constraint for MPU */ if (dev->set_mpu_wkup_lat != NULL) dev->latency = (1000000 * dev->fifo_size) / (1000 * speed / 8); } /* reset ASAP, clearing any IRQs */ omap_i2c_init(dev); isr = (dev->rev < OMAP_I2C_REV_2) ? omap_i2c_rev1_isr : omap_i2c_isr; r = request_irq(dev->irq, isr, 0, pdev->name, dev); if (r) { dev_err(dev->dev, "failure requesting irq %i ", dev->irq); goto err_unuse_clocks; } dev_info(dev->dev, "bus %d rev%d.%d at %d kHz ", pdev->id, dev->rev >> 4, dev->rev & 0xf, dev->speed); omap_i2c_idle(dev); adap = &dev->adapter; i2c_set_adapdata(adap, dev); adap->owner = THIS_MODULE; adap->class = I2C_CLASS_HWMON; strlcpy(adap->name, "OMAP I2C adapter", sizeof(adap->name)); adap->algo = &omap_i2c_algo; adap->dev.parent = &pdev->dev; /* i2c device drivers may be active on return from add_adapter() */ adap->nr = pdev->id; r = i2c_add_numbered_adapter(adap); if (r) { dev_err(dev->dev, "failure adding adapter "); goto err_free_irq; } return 0;err_free_irq: free_irq(dev->irq, dev);err_unuse_clocks: omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, 0); omap_i2c_idle(dev); iounmap(dev->base);err_free_mem: platform_set_drvdata(pdev, NULL); kfree(dev);err_release_region: release_mem_region(mem->start, resource_size(mem)); return r;}

Probe主要工作是时能硬件并申请I2C适配器使用的IO地址,中断号等,然后向I2C核心添加这个适配器。I2c_adapter注册过程i2c_add_numbered_adapter->i2c_register_adapter
I2C总线通信方法

static const struct i2c_algorithm omap_i2c_algo = { .master_xfer = omap_i2c_xfer, .functionality = omap_i2c_func,};

oamp_i2c_xfer函数是总线通信方式的具体实现,依赖于omap_i2c_wait_for_bb和omap_i2c_xfer_msg两个函数;

/* * Prepare controller for a transaction and call omap_i2c_xfer_msg * to do the work during IRQ processing. */static intomap_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num){ struct omap_i2c_dev *dev = i2c_get_adapdata(adap); int i; int r; omap_i2c_unidle(dev); r = omap_i2c_wait_for_bb(dev); if (r < 0) goto out; for (i = 0; i < num; i++) { r = omap_i2c_xfer_msg(adap, &msgs[i], (i == (num - 1))); if (r != 0) break; } if (r == 0) r = num; omap_i2c_wait_for_bb(dev);out: omap_i2c_idle(dev); return r;}

首先设置s3c I2C控制器是否忙,不忙然后调用omap_i2c_xfer_msg函数启动I2C消息传输。
omap_i2c_func函数返回适配器所支持的通信功能。

static u32omap_i2c_func(struct i2c_adapter *adap){ return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);}

4.4 适配器的设备资源(platform_device)
DM8168的I2C总线驱动是基于platform来实现,前面我们分析了platform driver部分,再来看下platform device部分。
在arch/arm/mach-oamp2/omap_hwmod_81xx_data.c文件中定义了platform_device结构体以及I2C控制器的资源信息(注意由于DM8168属于不对称多核架构,分为l3(内部核之间)、l4(外围设备与核直接)两个总线,通过ti81xx_hwmod_init->omap_hwmod_init->_register将所有的设备资源(omap_hwmod结构体)加入到omap_hwmod_list链表中):

static struct omap_hwmod ti81xx_i2c1_hwmod = { .name = "i2c1", .mpu_irqs = i2c1_mpu_irqs, .mpu_irqs_cnt = ARRAY_SIZE(i2c1_mpu_irqs), .sdma_reqs = i2c1_edma_reqs, .sdma_reqs_cnt = ARRAY_SIZE(i2c1_edma_reqs), .main_clk = "i2c1_fck", .prcm = { .omap4 = { .clkctrl_reg = TI816X_CM_ALWON_I2C_0_CLKCTRL, }, }, .slaves = ti816x_i2c1_slaves, .slaves_cnt = ARRAY_SIZE(ti816x_i2c1_slaves), .class = &i2c_class, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_TI81XX),};///////////////////////////static struct omap_hwmod ti816x_i2c2_hwmod = { .name = "i2c2", .mpu_irqs = i2c2_mpu_irqs, .mpu_irqs_cnt = ARRAY_SIZE(i2c2_mpu_irqs), .sdma_reqs = i2c2_edma_reqs, .sdma_reqs_cnt = ARRAY_SIZE(i2c2_edma_reqs), .main_clk = "i2c2_fck", .prcm = { .omap4 = { .clkctrl_reg = TI816X_CM_ALWON_I2C_1_CLKCTRL, }, }, .slaves = ti816x_i2c2_slaves, .slaves_cnt = ARRAY_SIZE(ti816x_i2c2_slaves), .class = &i2c_class, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_TI816X),};

查找平台设备硬件资源(通过omap2_i2c_add_bus->omap_hwmod_lookup从omap_hwmod_list链表中查到相应的硬件资源(omap_hwmod)):

/** * omap_hwmod_lookup - look up a registered omap_hwmod by name * @name: name of the omap_hwmod to look up * * Given a @name of an omap_hwmod, return a pointer to the registered * struct omap_hwmod *, or NULL upon error. */struct omap_hwmod *omap_hwmod_lookup(const char *name){ struct omap_hwmod *oh; if (!name) return NULL; oh = _lookup(name); return oh;}

将查找到的硬件资源填充平台设备的resource结构体(omap2_i2c_add_bus->omap_device_build->omap_device_build_ss->omap_device_fill_resources->omap_hwmod_fill_resources):

/** * omap_hwmod_fill_resources - fill struct resource array with hwmod data * @oh: struct omap_hwmod * * @res: pointer to the first element of an array of struct resource to fill * * Fill the struct resource array @res with resource data from the * omap_hwmod @oh. Intended to be called by code that registers * omap_devices. See also omap_hwmod_count_resources(). Returns the * number of array elements filled. */int omap_hwmod_fill_resources(struct omap_hwmod *oh, struct resource *res){ int i, j; int r = 0; /* For each IRQ, DMA, memory area, fill in array.*/ for (i = 0; i < oh->mpu_irqs_cnt; i++) { (res + r)->name = (oh->mpu_irqs + i)->name; (res + r)->start = (oh->mpu_irqs + i)->irq; (res + r)->end = (oh->mpu_irqs + i)->irq; (res + r)->flags = IORESOURCE_IRQ; r++; } for (i = 0; i < oh->sdma_reqs_cnt; i++) { (res + r)->name = (oh->sdma_reqs + i)->name; (res + r)->start = (oh->sdma_reqs + i)->dma_req; (res + r)->end = (oh->sdma_reqs + i)->dma_req; (res + r)->flags = IORESOURCE_DMA; r++; } for (i = 0; i < oh->slaves_cnt; i++) { struct omap_hwmod_ocp_if *os; os = oh->slaves[i]; for (j = 0; j < os->addr_cnt; j++) { (res + r)->name = (os->addr + j)->name; (res + r)->start = (os->addr + j)->pa_start; (res + r)->end = (os->addr + j)->pa_end; (res + r)->flags = IORESOURCE_MEM; r++; } } return r;}

在板文件中把platform_device注册进内核(omap2_i2c_add_bus->omap_device_build->omap_device_build_ss->omap_device_register->platform_device_register):

/** * omap_device_register - register an omap_device with one omap_hwmod * @od: struct omap_device * to register * * Register the omap_device structure. This currently just calls * platform_device_register() on the underlying platform_device. * Returns the return value of platform_device_register(). */int omap_device_register(struct omap_device *od){ pr_debug("omap_device: %s: registering ", od->pdev.name); od->pdev.dev.parent = &omap_device_parent; return platform_device_register(&od->pdev);}

调用platform_device_add_data函数把适配器具体的数据赋值给dev.platform_data(omap2_i2c_add_bus->omap_device_build->omap_device_build_ss->platform_device_add_data):

/** * platform_device_add_data - add platform-specific data to a platform device * @pdev: platform device allocated by platform_device_alloc to add resources to * @data: platform specific data for this platform device * @size: size of platform specific data * * Add a copy of platform specific data to the platform device's * platform_data pointer. The memory associated with the platform data * will be freed when the platform device is released. */int platform_device_add_data(struct platform_device *pdev, const void *data, size_t size){ void *d; if (!data) return 0; d = kmemdup(data, size, GFP_KERNEL); if (d) { pdev->dev.platform_data = d; return 0; } return -ENOMEM;}EXPORT_SYMBOL_GPL(platform_device_add_data);

I2C总线驱动就分析到这里。

5 客户驱动
5.1 概述
I2C客户驱动是对I2C从设备的实现,一个具体的I2C客户驱动包括两个部分:一部分是i2c_driver,用于将设备挂接于i2c总线;另一部分是设备本身的驱动。
I2C客户驱动程序主要由i2c_driver和i2c_client来描述。
5.2 实例源码分析
好了,我们来深入了解客户驱动代码的实现,sound/soc/codesc/tlv320aic3x.c文件。
I2c_driver实现

/* machine i2c codec control layer */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,};

初始化和卸载

static int __init aic3x_modinit(void){ int ret = 0;#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) ret = i2c_add_driver(&aic3x_i2c_driver); if (ret != 0) { printk(KERN_ERR "Failed to register TLV320AIC3x I2C driver: %d ", ret); }#endif return ret;}module_init(aic3x_modinit);static void __exit aic3x_exit(void){#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) i2c_del_driver(&aic3x_i2c_driver);#endif}module_exit(aic3x_exit);

aic3x_i2c_Probe函数

/* * If the i2c layer weren't so broken, we could pass this kind of data * around */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; const struct i2c_device_id *tbl; aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL); if (aic3x == NULL) { dev_err(&i2c->dev, "failed to create private data "); return -ENOMEM; } aic3x->control_data = i2c; 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; } for (tbl = aic3x_i2c_id; tbl->name[0]; tbl++) { if (!strcmp(tbl->name, id->name)) break; } aic3x->model = tbl - aic3x_i2c_id; ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_aic3x, &aic3x_dai, 1); if (ret < 0) kfree(aic3x); return ret;}

Probe函数主要的工作是初始化芯片的控制类型,控制数据(i2c_client),并且注册解码器到声卡。
5.3  I2c_client实现
tlv320aic3x不依赖于具体的CPU和I2C控制器硬件特性,因此如果威廉希尔官方网站 板包含该外设,只需要添加对应的i2c_board_info,下面是tlv320aic3x i2c_client在板文件中的实现:

static struct i2c_board_info __initdata ti816x_i2c_boardinfo0[] = { { I2C_BOARD_INFO("tlv320aic3x", 0x18), }, { I2C_BOARD_INFO("ds1337", 0x68), }, #ifdef CONFIG_REGULATOR_TPS40400 { I2C_BOARD_INFO("pmbus", 0x38), .platform_data = &pmbus_pmic_init_data, }, #endif { I2C_BOARD_INFO("24c256",0x00), // },};

I2c_register_board_info函数会把I2C从设备硬件特性信息注册到全局链表__i2c_board_list,在调用i2c_add_adapter函数时,会遍历__i2c_board_list获得从设备信息来构造i2c_client(omap_register_i2c_bus->omap_register_i2c_bus)。

/** * omap_register_i2c_bus - register I2C bus with device descriptors * @bus_id: bus id counting from number 1 * @clkrate: clock rate of the bus in kHz * @info: pointer into I2C device descriptor table or NULL * @len: number of descriptors in the table * * Returns 0 on success or an error code. */int __init omap_register_i2c_bus(int bus_id, u32 clkrate, struct i2c_board_info const *info, unsigned len){ int err; BUG_ON(bus_id < 1 || bus_id > omap_i2c_nr_ports()); if (info) { err = i2c_register_board_info(bus_id, info, len); if (err) return err; } if (!i2c_pdata[bus_id - 1].clkrate) i2c_pdata[bus_id - 1].clkrate = clkrate; i2c_pdata[bus_id - 1].clkrate &= ~OMAP_I2C_CMDLINE_SETUP; return omap_i2c_add_bus(bus_id);}

I2c_client的构建
我们调用I2c_register_board_info函数会把I2C从设备硬件特性信息注册到全局链表__i2c_board_list,但是还没有构建出一个i2c_client结构体,也没有注册进I2C总线。我们来分析一下构造的过程,调用i2c_add_adapter函数时,会遍历__i2c_board_list获得从设备信息来构造i2c_client:i2c_register_adapter()->i2c_scan_static_board_info()->i2c_new_device()->device_register()。
5.4  I2c_driver和i2c_client的match
在调用i2c_add_driver注册i2c_driver和构建i2c_client时,都会调用i2c bus中注册的i2c_device_match()->i2c_match_id()函数通过i2c_driver->id_table->name和client->name来匹配(i2c_add_driver->i2c_register_driver->driver_register->bus_add_driver->driver_attach->__driver_attach->driver_match_device->i2c_device_match->i2c_match_id)

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) return id; id++; } return NULL;}

5.5 测试
i2c-tools测试工具可以从http://www.lm-sensors.org/wiki/I2CTools下载,按照http://3sec.kilab.tw/?p=260来进行试验,这个工具可以测试I2C子系统。
i2c-tools中含有四個執行檔
i2cdetect – 用來列舉I2C bus和上面所有的裝置
i2cdump – 顯示裝置上所有register的值
i2cget – 讀取裝置上某個register的值
i2cset – 寫入裝置上某個register
./i2cdetect -l 查看有多少I2C总线组
./i2cdetect -y -r 1 查看看bus上有那些裝置

总结
I2c_driver、i2c_client与i2c_adapter
I2c_driver与i2c_client是一对多的关系,一个i2c_driver上可以支持多个同等类型的i2c_client。调用i2c_add_driver函数将I2c_driver注册到I2C总线上,调用i2c_register_board_info函数将i2c_client注册到全局链表__i2c_board_list。当调用i2c_add_adapter注册适配器时,遍历__i2c_board_list链表,i2c_register_adapter()->i2c_scan_static_board_info()->i2c_new_device()会构建i2c_client结构。当调用i2c_add_driver时,会先注册i2c_driver到I2C总线上,然后调用I2C BUS注册的match函数进行匹配,如果匹配成功,则先调用I2C BUS中注册的probe函数,在调用i2c_driver中实现的probe函数,完成相应的工作(i2c_add_numbered_adapter->i2c_register_adapter->i2c_scan_static_board_info->i2c_new_device->device_register->device_attach->__device_attach->driver_probe_device->really_probe->dev->bus->probe、drv->probe)。
i2c控制器驱动开发步骤:
1.通过platform_device和platform_device_register创建平台设备和资源i2c控制器驱动。
2.通过platform_driver和platform_driver_register、i2c_add_numbered_adapter实现。
i2c设备驱动开发步骤:
1.判断是写用户驱动还是客服驱动,客服驱动需要下面的两步。
2.通过I2C_BOARD_INFO和i2c_register_board_info来创建i2c_client,并且绑定i2c适配器(也就是控制器)。
3.通过i2c_driver和i2c_add_driver实现i2c客户驱动。



 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分