目录
总线驱动注册与加载
添加设备到系统中
总线驱动侧处理
I2C总线通信方式
以S3C-2410为例:
由于总线控制器通常是在内存上的,所以它本身也连接在platform总线上,要通过platform_driver和platform_device的匹配来执行。S3C-2410的i2c_adapter框架是基于 platform_bus_type 的,当设备或驱动注册到 platform_bus_type 时,首先会查找驱动是否有id_table,如果有根据id_table进行匹配(就是匹配设备的名字),否则匹配设备名字和驱动名字。匹配成功则调用驱动里的probe函数。
总线驱动、也叫适配器驱动、也叫主机控制器驱动,主要的工作是产生总线上的波形
根据设备总线驱动模型的分层思想,将一个驱动程序分为 device 和 driver 两层,那么 device 里提供底层的硬件资源,在 driver 中取出这些资源进行使用。一般会提供以下资源
1、寄存器地址
2、中断号
下面看下具体信息为何:
static struct platform_device *smdk2410_devices[] __initdata = {
&s3c_device_i2c0,
};
struct platform_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,
};
static struct resource 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,
},
};
上面是提供的寄存器信息,我们还需要调用函数platform_add_devices将设备添加到系统中
static void __init smdk2410_init(void)
{
s3c_i2c0_set_platdata(NULL);
platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices));
smdk_machine_init();
}
static struct s3c2410_platform_i2c default_i2c_data0 __initdata = {
.flags = 0,
.slave_addr = 0x10,
.frequency = 100*1000,
.sda_delay = 100,
};
void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)
{
struct s3c2410_platform_i2c *npd;
if (!pd)
pd = &default_i2c_data;
npd = s3c_set_platdata(pd, sizeof(struct s3c2410_platform_i2c),
&s3c_device_i2c0);
//s3c_device_i2c0->dev.platform_data = &default_i2c_data;
if (!npd->cfg_gpio)
npd->cfg_gpio = s3c_i2c0_cfg_gpio;//配置sda和scl为I2C
}
void __init *s3c_set_platdata(void *pd, size_t pdsize,
struct platform_device *pdev)
{
void *npd;
if (!pd) {
/* too early to use dev_name(), may not be registered */
printk(KERN_ERR "%s: no platform data supplied\n", pdev->name);
return NULL;
}
npd = kmemdup(pd, pdsize, GFP_KERNEL);
if (!npd) {
printk(KERN_ERR "%s: cannot clone platform data\n", pdev->name);
return NULL;
}
pdev->dev.platform_data = npd;
return npd;
}
void s3c_i2c0_cfg_gpio(struct platform_device *dev)
{
s3c_gpio_cfgpin(S3C2410_GPE(15), S3C2410_GPE15_IICSDA);
s3c_gpio_cfgpin(S3C2410_GPE(14), S3C2410_GPE14_IICSCL);
}
void __init s3c244x_map_io(void)
{
/* register our io-tables */
iotable_init(s3c244x_iodesc, ARRAY_SIZE(s3c244x_iodesc));
/* rename any peripherals used differing from the s3c2410 */
s3c_device_i2c0.name = "s3c2440-i2c";
}
因为总线类型是platform总线,所以驱动首先会调用platform_bus_type->match来匹配 driver->id_table 和device的,前面讲了s3c_device_i2c0.name = "s3c2440-i2c",因此,匹配成功会调用 s3c24xx_i2c_driver->probe 函数,也就是s3c24xx_i2c_probe ,它是个重点。
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c;
struct s3c2410_platform_i2c *pdata;
struct resource *res;
int ret;
pdata = pdev->dev.platform_data;
if (!pdata) {
dev_err(&pdev->dev, "no platform data\n");
return -EINVAL;
}
i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);
if (!i2c) {
dev_err(&pdev->dev, "no memory for state\n");
return -ENOMEM;
}
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");
if (IS_ERR(i2c->clk)) {
dev_err(&pdev->dev, "cannot get clock\n");
ret = -ENOENT;
goto err_noclk;
}
dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
clk_enable(i2c->clk);
/* map the registers */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev, "cannot find IO resource\n");
ret = -ENOENT;
goto err_clk;
}
i2c->ioarea = request_mem_region(res->start, resource_size(res),
pdev->name);
if (i2c->ioarea == NULL) {
dev_err(&pdev->dev, "cannot request IO\n");
ret = -ENXIO;
goto err_clk;
}
i2c->regs = ioremap(res->start, resource_size(res));
if (i2c->regs == NULL) {
dev_err(&pdev->dev, "cannot map IO\n");
ret = -ENXIO;
goto err_ioarea;
}
dev_dbg(&pdev->dev, "registers %p (%p, %p)\n",
i2c->regs, i2c->ioarea, res);
/* setup info block for the i2c core */
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
/* initialise the i2c controller */
ret = s3c24xx_i2c_init(i2c);
if (ret != 0)
goto err_iomap;
/* find the IRQ for this unit (note, this relies on the init call to
* ensure no current IRQs pending
*/
i2c->irq = ret = platform_get_irq(pdev, 0);
if (ret <= 0) {
dev_err(&pdev->dev, "cannot find IRQ\n");
goto err_iomap;
}
ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,
dev_name(&pdev->dev), i2c);
if (ret != 0) {
dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq);
goto err_iomap;
}
ret = s3c24xx_i2c_register_cpufreq(i2c);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register cpufreq notifier\n");
goto err_irq;
}
/* Note, previous versions of the driver used i2c_add_adapter()
* to add the bus at any number. We now pass the bus number via
* the platform data, so if unset it will now default to always
* being bus 0.
*/
i2c->adap.nr = pdata->bus_num;
ret = i2c_add_numbered_adapter(&i2c->adap);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add bus to i2c core\n");
goto err_cpufreq;
}
platform_set_drvdata(pdev, i2c);
dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));
clk_disable(i2c->clk);
return 0;
err_cpufreq:
s3c24xx_i2c_deregister_cpufreq(i2c);
err_irq:
free_irq(i2c->irq, i2c);
err_iomap:
iounmap(i2c->regs);
err_ioarea:
release_resource(i2c->ioarea);
kfree(i2c->ioarea);
err_clk:
clk_disable(i2c->clk);
clk_put(i2c->clk);
err_noclk:
kfree(i2c);
return ret;
}
static int s3c24xx_i2c_remove(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c = platform_get_drvdata(pdev);
s3c24xx_i2c_deregister_cpufreq(i2c);
i2c_del_adapter(&i2c->adap);
free_irq(i2c->irq, i2c);
clk_disable(i2c->clk);
clk_put(i2c->clk);
iounmap(i2c->regs);
release_resource(i2c->ioarea);
kfree(i2c->ioarea);
kfree(i2c);
return 0;
}
static struct platform_device_id s3c24xx_driver_ids[] = {
{
.name = "s3c2410-i2c",
.driver_data = TYPE_S3C2410,
}, {
.name = "s3c2440-i2c",
.driver_data = TYPE_S3C2440,
}, {
.name = "s3c2440-hdmiphy-i2c",
.driver_data = TYPE_S3C2440_HDMIPHY,
}, { },
};
MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids);
static struct platform_driver s3c24xx_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,
},
};
static int __init i2c_adap_s3c_init(void)
{
return platform_driver_register(&s3c24xx_i2c_driver);
}
subsys_initcall(i2c_adap_s3c_init);
s3c24xx_i2c_probe函数(总线驱动模块的加载函数)中完成两个工作。
I2C 总线驱动模块的卸载函数要完成的工作与加载函数相反。
我们需要为特定的I2C 适配器实现其通信方法,主要实现i2c_algorithm 的master_xfer()函数和functionality()函数。
functionality() 函数非常简单, 用于返回algorithm 所支持的通信协议, 如I2C_FUNC_I2C 、I2C_FUNC_10BIT_ADDR、I2C_FUNC_SMBUS_READ_BYTE、I2C_FUNC_SMBUS_WRITE_BYTE 等。
master_xfer()函数在I2C 适配器上完成传递给它的i2c_msg 数组中的每个I2C 消息,如下所示为 xxx 设备的master_xfer()函数模板。
static int i2c_adapter_xxx_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,int num)
{
...
for (i = 0; i < num; i++)
{
i2c_adapter_xxx_start(); /*产生开始位*/
/*是读消息*/
if (msgs[i]->flags &I2C_M_RD)
{
i2c_adapter_xxx_setaddr((msg->addr << 1) | 1); /*发送从设备读地址*/
i2c_adapter_xxx_wait_ack(); /*获得从设备的ack*/
i2c_adapter_xxx_readbytes(msgs[i]->buf, msgs[i]->len);
/*读取msgs[i] ->len长的数据到msgs[i]->buf*/
}
else
/*是写消息*/
{
i2c_adapter_xxx_setaddr(msg->addr << 1); /*发送从设备写地址*/
i2c_adapter_xxx_wait_ack(); /*获得从设备的ack*/
i2c_adapter_xxx_writebytes(msgs[i]->buf, msgs[i]->len);
/*读取msgs[i] ->len长的数据到msgs[i]->buf*/
}
}
i2c_adapter_xxx_stop(); /*产生停止位*/
}
上述代码实际上给出了一个master_xfer()函数处理I2C 消息数组的流程,对于数组中的每个消息,判断消息类型,若为读消息,则赋从设备地址为(msg->addr << 1)|1,否则为msg->addr << 1。对每个消息产生一个开始位,紧接着传送从设备地址,然后开始数据的发送或接收,对最后的消息还需产生一个停止位。如下图所示为整个master_xfer()完成的时序。
master_xfer() 函数模板中的i2c_adapter_xxx_start() 、i2c_adapter_xxx_setaddr() 、i2c_adapter_xxx_wait_ack()、i2c_adapter_xxx_readbytes()、i2c_adapter_xxx_writebytes()和i2c_adapter_xxx_stop()函数用于完成适配器的底层硬件操作,与I2C 适配器和CPU 的具体硬件直接相关,需要由工程师根据芯片的数据手册来实现。
i2c_adapter_xxx_readbytes()用于从从设备上接收一串数据,i2c_adapter_xxx_writebytes()用于向从设备写入一串数据,这两个函数的内部也会涉及I2C 总线协议中的ACK 应答。
master_xfer()函数的实现在形式上会很多样,即便是Linux 内核源代码中已经给出的一些I2C 总线驱动的master_xfer()函数,由于由不同的组织或个人完成,风格上的差别也非常大,不一定能与模板完全对应,如master_xfer()函数模板给出的消息处理是顺序进行的,而有的驱动以中断方式来完成这个流程。不管具体怎么实施,流程的本质都是不变的。因为这个流程不以驱动工程师的意志为转移,最终由I2C 总线硬件上的通信协议决定。
多数 I2C 总线驱动会定义一个xxx_i2c 结构体,作为i2c_adapter 的algo_data(类似“私有数据”),其中包含I2C 消息数组指针、数组索引及I2C 适配器algorithm 访问控制用的自旋锁、等待队列等,而master_xfer()函数完成消息数组中消息的处理也可通过对xxx_i2c 结构体相关成员的访问来控制。