IIC控制器驱动流程
IIC控制器也属于片上设备, 因此它的流程也如其他的片上设备类似。首先是静态的初始化好这个设备的相关信息, 在arch/arm/mach-s3c2410.c下
/* I2C */
static struct resource s3c_i2c_resource[] = {
[0] = { /*寄存器地址*/
.start = S3C24XX_PA_IIC,
.end = S3C24XX_PA_IIC + S3C24XX_SZ_IIC - 1,
.flags = IORESOURCE_MEM,
},
[1] = { /*中断号*/
.start = IRQ_IIC,
.end = IRQ_IIC,
.flags = IORESOURCE_IRQ,
}
};
struct platform_device s3c_device_i2c = { /*IIC设备*/
.name = "s3c2410-i2c", /*名字和驱动里定义的名字要一样,用于匹配*/
.id = -1,
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
EXPORT_SYMBOL(s3c_device_i2c);
在系统初始化的时候这个设备会被添加到系统中去。等IIC驱动注册到系统后会probe到这个设备,并初始化这里定义的资源信息。
接下来就是IIC控制器的驱动了 在如下目录: drivers/i2c/busses/i2c-s3c2410.c
模块编程都有一个init和exit的函数,init在模块被插入系统时调用,exit在模块被卸载时调用。我们先来看这个驱动的init函数。
static int __init i2c_adap_s3c_init(void)
{
int ret;
ret = platform_driver_register(&s3c2410_i2c_driver); /*主测IIC驱动*/
if (ret == 0) {
ret = platform_driver_register(&s3c2440_i2c_driver);
if (ret)
platform_driver_unregister(&s3c2410_i2c_driver);
}
return ret;
}
很简单就是注册一个代表IIC驱动的platform_driver对象。
在看exit函数
static void __exit i2c_adap_s3c_exit(void)
{
platform_driver_unregister(&s3c2410_i2c_driver); /*卸载IIC驱动*/
platform_driver_unregister(&s3c2440_i2c_driver);
}
做和init函数相反的工作。
我们看到在init中还注册了s3c2440_i2c_driver对象, 由于它和定义的设备兑现不匹配所以这里就不列出来了。 我们看s3c2410_i2c_driver
static struct platform_driver s3c2410_i2c_driver = {
.probe = s3c24xx_i2c_probe, /*探测函数*/
.remove = s3c24xx_i2c_remove, /*卸载函数*/
.resume = s3c24xx_i2c_resume,
.driver = {
.owner = THIS_MODULE,
.name = "s3c2410-i2c", /*这个要和设备定义的名字相同以能够探测到设备*/
},
};
当驱动被注册进系统后, 系统会探测到上面提到的已被添加到系统的IIC控制器,所以就会调用probe函数
/* s3c24xx_i2c_probe
*
* called by the bus driver when a suitable device is found
*/
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c = &s3c24xx_i2c; /*自己定义的用于保存IIC设备信息的对象*/
struct resource *res;
int ret;
/* find the clock and enable it */
i2c->dev = &pdev->dev;
i2c->clk = clk_get(&pdev->dev, "i2c"); /*获取IIC的时钟源*/
if (IS_ERR(i2c->clk)) {
dev_err(&pdev->dev, "cannot get clock/n");
ret = -ENOENT;
goto out;
}
dev_dbg(&pdev->dev, "clock source %p/n", i2c->clk);
clk_enable(i2c->clk); /*使能IIC时钟*/
/* 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 out;
}
i2c->ioarea = request_mem_region(res->start, (res->end-res->start)+1,
pdev->name); /*查看地址是否空闲*/
if (i2c->ioarea == NULL) {
dev_err(&pdev->dev, "cannot request IO/n");
ret = -ENXIO;
goto out;
}
/*
* 内存映射, 即把寄存器的物理地址映射为虚拟地址, 以便以后在代码中使用。
*/
i2c->regs = ioremap(res->start, (res->end-res->start)+1);
if (i2c->regs == NULL) {
dev_err(&pdev->dev, "cannot map IO/n");
ret = -ENXIO;
goto out;
}
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); /*初始化IIC硬件控制器*/
if (ret != 0)
goto out;
/* find the IRQ for this unit (note, this relies on the init call to
* ensure no current IRQs pending
*/
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); /*获取中断号*/
if (res == NULL) {
dev_err(&pdev->dev, "cannot find IRQ/n");
ret = -ENOENT;
goto out;
}
ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED,
pdev->name, i2c); /*注册中断例程*/
if (ret != 0) {
dev_err(&pdev->dev, "cannot claim IRQ/n");
goto out;
}
i2c->irq = res;
dev_dbg(&pdev->dev, "irq resource %p (%ld)/n", res, res->start);
ret = i2c_add_adapter(&i2c->adap); /*把适配器添加到系统中去*/
if (ret < 0) {
dev_err(&pdev->dev, "failed to add bus to i2c core/n");
goto out;
}
platform_set_drvdata(pdev, i2c); /*保存信息,便于以后使用*/
dev_info(&pdev->dev, "%s: S3C I2C adapter/n", i2c->adap.dev.bus_id);
out:
if (ret < 0)
s3c24xx_i2c_free(i2c);
return ret;
}
1 IIC有两个信号线:时钟线和数据线, 任何数据都要在每个时钟点上才能收发, 所以要有个时钟来驱动IIC,
2 Probe函数中获取到的资源都是在上面我们讲过的根据硬件spec静态设置好的,我们这里就是把这些资源初始化好。
3 每个IIC控制器都由一个i2c_adapter(适配器)来表示,这里我们把它嵌在了我们自己的结构中s3c24xx_i2c,即代码中的i2c->adap, 每个适配器都要有一套接受,发送的通信规则,这在s3c24xx_i2c中定义, 以后会讲到,
4 最后我们要把控制器添加到系统中去: i2c_add_adapter(&i2c->adap), 这样用于就能通过设备文件访问到这个设备。
s3c24xx_i2c_remove 主要做一些与probe相反的工作,这里就不多讲了。
接下来看一下s3c24xx_i2c
/* i2c bus registration info */
static struct i2c_algorithm s3c24xx_i2c_algorithm = { /*iic通信函数*/
.master_xfer = s3c24xx_i2c_xfer, /*收发*/
.functionality = s3c24xx_i2c_func, /*返回IIC支持的功能*/
};
static struct s3c24xx_i2c s3c24xx_i2c = {
.lock = SPIN_LOCK_UNLOCKED,
.wait = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
.adap = {
.name = "s3c2410-i2c",
.owner = THIS_MODULE,
.algo = &s3c24xx_i2c_algorithm, /*通信算法*/
.retries = 2,
.class = I2C_CLASS_HWMON,
},
};
实际上最主要的就是s3c24xx_i2c_xfer了, 它就是实际上用来实现IIC算法的函数。当用于要读写IIC控制器的设备文件时,系统最终会把执行流程走到这个函数。
/* s3c24xx_i2c_xfer
*
* first port of call from the i2c bus code when an message needs
* transferring across the i2c bus.
*/
static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
/*我们probe中保存的信息*/
struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;
int retry;
int ret;
for (retry = 0; retry < adap->retries; retry++) { /*用于重试次数*/
ret = s3c24xx_i2c_doxfer(i2c, msgs, num); /*IIC通信*/
if (ret != -EAGAIN)
return ret;
dev_dbg(i2c->dev, "Retrying transmission (%d)/n", retry);
udelay(100);
}
return -EREMOTEIO;
}
可以看出它是调用s3c24xx_i2c_doxfer来实现IIC通信的
/* s3c24xx_i2c_doxfer
*
* this starts an i2c transfer
*/
static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c, struct i2c_msg *msgs, int num)
{
unsigned long timeout;
int ret;
ret = s3c24xx_i2c_set_master(i2c); /*设适配器为主设备*/
if (ret != 0) {
dev_err(i2c->dev, "cannot get bus (error %d)/n", ret);
ret = -EAGAIN;
goto out;
}
spin_lock_irq(&i2c->lock);
i2c->msg = msgs;
i2c->msg_num = num;
i2c->msg_ptr = 0;
i2c->msg_idx = 0;
i2c->state = STATE_START;
s3c24xx_i2c_enable_irq(i2c); /*允许中断*/
s3c24xx_i2c_message_start(i2c, msgs); /*通信开始*/
spin_unlock_irq(&i2c->lock);
timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
ret = i2c->msg_idx;
/* having these next two as dev_err() makes life very
* noisy when doing an i2cdetect */
if (timeout == 0)
dev_dbg(i2c->dev, "timeout/n");
else if (ret != num)
dev_dbg(i2c->dev, "incomplete xfer (%d)/n", ret);
/* ensure the stop has been through the bus */
msleep(1);
out:
return ret;
}
具体的算法及IIC通信规则就不多讲了, 可以参考代码和IIC的规范, 这里只是简要讲述了IIC适配器在linux下的驱动的编写流程。