完成platform device注册之后,接下来需要初始化的是platform driver。
在driver/i2c/busees 目录下的i2c_s3c2410c 中有函数:
driver/i2c/busees/i2c_s3c2410.c
static int __init i2c_adap_s3c_init(void)
{
return platform_driver_register(&s3c24xx_i2c_driver); (1.0)
}
subsys_initcall(i2c_adap_s3c_init);
(1.0)完成platform driver的注册操作。
s3c24xx_i2c_driver 被定义为:
static struct platform_driver s3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.id_table = s3c24xx_driver_ids, (2.0)
.driver = {
.owner = THIS_MODULE,
.name = "s3c-i2c",
.pm = S3C24XX_DEV_PM_OPS,
},
};
(2.0)设备表赋值,其中设备表被定义为:
static struct platform_device_id s3c24xx_driver_ids[] = {
{
.name = "s3c2410-i2c",
.driver_data = TYPE_S3C2410,
}, {
.name = "s3c2440-i2c",
.driver_data = TYPE_S3C2440,
}, { },
};
MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids); (3.0)
(3.0)使用MODULE_DEVICE_TABLE 宏声明,s3c24xx_driver_ids 是platform类型的一个设备表。
platform driver 在被注册到platform bus 上的时候,会与已经注册到platform bus 上的所有platform device 进行名字配对。但是这里platform driver 有多个名字,所以就使用id_table 来实现配对。与I2C platform device相匹配的名字是:s3c2410-i2c
回到(1.0)处,我们看platform_driver_register函数,跟踪代码发现:
platform_driver_register
driver_register
bus_add_driver
/drivers/base/Bus.c
int bus_add_driver(struct device_driver *drv)
{
......
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv);<span style="white-space:pre"> </span>(1.1)
if (error)
goto out_unregister;
}
<span style="white-space:pre"> </span>......
if (!drv->suppress_bind_attrs) {
error = add_bind_files(drv);<span style="white-space:pre"> </span>(1.2)
if (error) {
/* Ditto */
printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
__func__, drv->name);
}
}
<span style="white-space:pre"> </span>......
}
(1.1)进行platform device与platform driver 的配对
(1.2)进行platform device 与platform driver 的绑定
在绑定函数中:
static int __must_check add_bind_files(struct device_driver *drv)
{
int ret;
ret = driver_create_file(drv, &driver_attr_unbind);
if (ret == 0) {
ret = driver_create_file(drv, &driver_attr_bind); (1.3)
if (ret)
driver_remove_file(drv, &driver_attr_unbind);
}
return ret;
}
(1.3) 这里会调用dirver_bind函数:
static ssize_t driver_bind(struct device_driver *drv,
const char *buf, size_t count)
{
struct bus_type *bus = bus_get(drv->bus);
struct device *dev;
int err = -ENODEV;
dev = bus_find_device_by_name(bus, NULL, buf);
if (dev && dev->driver == NULL && driver_match_device(drv, dev)) {
if (dev->parent) /* Needed for USB */
down(&dev->parent->sem);
down(&dev->sem);
err = driver_probe_device(drv, dev); (1.5)
up(&dev->sem);
if (dev->parent)
up(&dev->parent->sem);
if (err > 0) {
/* success */
err = count;
} else if (err == 0) {
/* driver didn't accept device */
err = -ENODEV;
}
}
put_device(dev);
bus_put(bus);
return err;
}
static DRIVER_ATTR(bind, S_IWUSR, NULL, driver_bind); (1.4)
(1.4)DRIVER_ATTR 的定义为下面的这个宏,这也是为什么(1.3)处会调用到driver_bind 函数。
#define DRIVER_ATTR(_name, _mode, _show, _store) \
struct driver_attribute driver_attr_##_name = \
__ATTR(_name, _mode, _show, _store)
(1.5)进一步分析probe函数:
driver_probe_device
really_probe
static int really_probe(struct device *dev, struct device_driver *drv)
{
int ret = 0;
atomic_inc(&probe_count);
pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
drv->bus->name, __func__, drv->name, dev_name(dev));
WARN_ON(!list_empty(&dev->devres_head));
dev->driver = drv;
if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
}
if (dev->bus->probe) {<span style="white-space:pre"> </span>(1.6)
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);<span style="white-space:pre"> </span>(1.7)
if (ret)
goto probe_failed;
}
driver_bound(dev);
<span style="white-space:pre"> </span>......
}
在(1.6)我们的bus->probe 是没有提供probe 方法的,因此调用的是下面(1.7)drv->probe(dev),也就是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; (5.0)
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; (6.0)
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); (7.0)
/* 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); (8.0)
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, (9.0)
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); (1.0)
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));
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;
}
(5.0)获取s3c24xx_i2c.platform_data 里面i2c的一些设备参数,也就是在platform device 注册到内核的那些设备信息。
(6.0)初始化algo 算法。
(7.0)初始化一个等待队列
(8.0)初始化i2c 控制器,主要是针对s3c24xx_i2c 寄存器的一些操作。
(9.0)申请中断,内核中i2c的读写通过中断来实现。
(10)向系统注册一个i2c adapter
初始化到这里,IIC总线设备已经初始化完成。也就是I2C硬件体系结构中适配器端的驱动程序已经完成。
如果需要调用I2C子系统去访问客户端设备,这里还需要添加I2C设备驱动(客户驱动)
说明:
1.分析的内核版本是linux2.6.32.2
2.开发板为友善之臂的mini2440, 用的是ARM9(S3C2440A)处理器
3.链接的IIC设备是EEPROM(AT24C02)
4.按照内核I2C子系统的注册顺序分析。
---------------------------------------2022.08.20:21:20更新----------------------------------------
由于各种原因,后续文章内容将更新到公众号,本平台将不再做更新。
CSDN上相关文章的测试工程代码,也统一放到了公众号上,可以免费免积分下载
可以通过主页上的二维码,也可以通过搜索微信公众号 liwen01 进入公众号
liwen01 2022.08.20
---------------------------------------2022.08.20:21:20更新----------------------------------------