您的位置:首页 > 运维架构 > Linux

Linux驱动子系统之I2C(二)

2013-06-14 11:28 369 查看
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 __init
omap_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 __devinit
omap_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?\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 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\n", dev->irq);
goto err_unuse_clocks;
}

dev_info(dev->dev, "bus %d rev%d.%d at %d kHz\n",
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\n");
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 int
omap_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 u32
omap_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\n", 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\n",
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\n");
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客户驱动。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: