[概述]
I2C总线驱动是对I2C硬件体系结构中适配器的实现,主要提供了数据传输的算法和各种信号(起始,停止,ACK等)实现的函数,总线驱动由i2c_adapter和i2c_algorithm来描述。
[s3c2440I2C总线驱动分析]
S3c2440I2C控制器的硬件描述
S3c2440处理器内部集成了一个I2C控制器,通过四个寄存器来进行控制:
IICCON I2C控制寄存器
IICSTAT I2C状态寄存器
IICDS I2C收发数据移位寄存器
IICADD I2C地址寄存器
通过IICCON,IICDS,IICADD寄存器操作,www.linuxidc.com 可在I2C总线上产生开始位、停止位、数据和地址,而传输的状态则通过IICSTAT寄存器来获取。
[i2c-s3c2410代码分析]
I2C总线驱动代码在drivers/i2c/busses/i2c-s3c2410.c,这个代码同样支持s3c2410,s3c6410,s5pc110等Samsung 系列的芯片。
初始化模块和卸载模块
staticint__init i2c_adap_s3c_init(void)
{
returnplatform_driver_register(&s3c24xx_i2c_driver);
}
staticvoid__exit i2c_adap_s3c_exit(void)
{
platform_driver_unregister(&s3c24xx_i2c_driver);
}
总线驱动是基于platform来实现的,很符合设备驱动模型的思想。
staticstructplatform_drivers3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.id_table = s3c24xx_driver_ids,
.driver = {
.owner = THIS_MODULE,
.name ="s3c-i2c",
.pm = S3C24XX_DEV_PM_OPS,
.of_match_table= s3c24xx_i2c_match,
},
};s3c24xx_i2c_probe函数
当调用platform_driver_register函数注册platform_driver结构体时,如果platformdevice 和 platform driver匹配成功后,会调用probe函数,来初始化适配器硬件。
staticints3c24xx_i2c_probe(structplatform_device *pdev)
{
……
/*初始化适配器信息 */
strlcpy(i2c->adap.name,"s3c2410-i2c",sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm;
i2c->adap.retries= 2;
i2c->adap.class= I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->tx_setup = 50;
/*初始化自旋锁和等待队列头 */
spin_lock_init(&i2c->lock);
init_waitqueue_head(&i2c->wait);
/*find the clock and enable it */
i2c->dev= &pdev->dev;
i2c->clk= clk_get(&pdev->dev,"i2c");
clk_enable(i2c->clk);
/*映射寄存器 */
res= platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2c->ioarea= request_mem_region(res->start, resource_size(res),
pdev->name);
i2c->regs= ioremap(res->start, resource_size(res));
/*设置I2C核心需要的信息 */
i2c->adap.algo_data= i2c;
i2c->adap.dev.parent= &pdev->dev;
/*初始化I2C控制器 */
ret= s3c24xx_i2c_init(i2c);
/*申请中断 */
i2c->irq= ret = platform_get_irq(pdev, 0);
ret= request_irq(i2c->irq, s3c24xx_i2c_irq, 0,
dev_name(&pdev->dev), i2c);
ret= s3c24xx_i2c_register_cpufreq(i2c);
i2c->adap.nr= i2c->pdata->bus_num;
i2c->adap.dev.of_node= pdev->dev.of_node;
ret= i2c_add_numbered_adapter(&i2c->adap);
/*注册I2C适配器 */
of_i2c_register_devices(&i2c->adap);
platform_set_drvdata(pdev,i2c);
dev_info(&pdev->dev,"%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));
clk_disable(i2c->clk);
return0;
……
}Probe主要工作是时能硬件并申请I2C适配器使用的IO地址,中断号等,然后向I2C核心添加这个适配器。当处理器包含多个I2C控制器时,我们通过板文件定义的platform数据中的bus_num来区分。
I2C总线通信方法
staticconststructi2c_algorithms3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};s3c24xx_i2c_xfer函数是总线通信方式的具体实现;
s3c24xx_i2c_func函数返回适配器所支持的通信功能。
完整的通信过程还需要借助中断s3c24xx_i2c_irq,具体分析可以参考《Linux设备驱动开发详解》
[适配器的设备资源]
S3c2440的I2C总线驱动是基于platform来实现,前面我们分析了platformdriver部分,再来看下platform device部分。
在arch/arm/plat-samsung/dev-i2c0.c文件中定义了platform_device结构体以及I2C控制器的资源信息:
staticstructresource s3c_i2c_resource[] ={
[0]= {
.start= S3C_PA_IIC,
.end = S3C_PA_IIC + SZ_4K - 1,
.flags= IORESOURCE_MEM,
},
[1]= {
.start= IRQ_IIC,
.end = IRQ_IIC,
.flags= IORESOURCE_IRQ,
},
};
structplatform_device s3c_device_i2c0 = {
.name ="s3c2410-i2c",/* 设备名 */
#ifdef CONFIG_S3C_DEV_I2C1
.id = 0,
#else
.id = -1,
#endif
.num_resources =ARRAY_SIZE(s3c_i2c_resource),
.resource =s3c_i2c_resource,
};
structs3c2410_platform_i2cdefault_i2c_data __initdata = {
.flags = 0,
.slave_addr = 0x10,/* I2C适配器的地址 */
.frequency = 100*1000,/* 总线频率 */
.sda_delay = 100,/* SDA边沿延迟时间ns */
};
void__init s3c_i2c0_set_platdata(structs3c2410_platform_i2c *pd)
{
structs3c2410_platform_i2c *npd;
if(!pd)
pd= &default_i2c_data;
npd= s3c_set_platdata(pd,sizeof(structs3c2410_platform_i2c),
&s3c_device_i2c0);
if(!npd->cfg_gpio)
npd->cfg_gpio= s3c_i2c0_cfg_gpio;
}
在板文件中把platform_device注册进内核:
staticstructplatform_device*mini2440_devices[] __initdata = {
……
&s3c_device_i2c0,
……
};
调用s3c_i2c0_set_platdata 函数把适配器具体的数据赋值给dev.platform_data:
staticvoid__init mini2440_init(void)
{
……
s3c_i2c0_set_platdata(NULL);
}I2C总线驱动就分析到这里。