轉自:http://www.linuxidc.com/Linux/2011-08/39948p1.htm
Linux中I2C體系結構如下圖所示(圖片來源於網絡)。圖中用分割線分成了三個層次:用戶空間(也就是應用程序),內核(也就是驅動部分)和硬件(也就是實際物理設備,這裏就是6410中的i2c控制器和at24xx)。這個夠清晰了吧?我們主要研究的是中間那一層。
中間一層又分爲i2c設備驅動、i2c-core層、i2c控制器驅動三部分。其中i2c-core提供了i2c設備驅動和控制器驅動的註冊、註銷方法。其上三部分Driver、Client、i2c-dev用來描述i2c設備(如at24xx)及其驅動,下面Algorithm、Adapter、及Adapter specific code 用來描述i2c控制器驅動。
以s3c6410 linux 2.6.26下iic器件 at24xx驅動爲例進行分析,主要包含以下文件。
1、i2c-core.c 實現了I2C的核心功能。
2、i2c-dev.c 實現了I2C控制器設備文件的功能。
3、At24.c 實現了at24xx系列IIC接口設備驅動。
4、i2c-s3c2410.c 實現了6410 IIC控制器驅動。
5、Algos 實現了一些IIC控制器的algorithm。
6、mach-mini6410.c 定義並註冊了I2C平臺設備。
我們根據系統加載有關I2C模塊的順序進行分析。
1、運行 MACHINE_START .init_machine 在 arch/arm/kernel/setup.c 中被 customize_machine 調用,放在 arch_initcall() 段裏面,會自動按順序被調用。
MACHINE_START(MINI6410, "MINI6410")
/* Maintainer: Ben Dooks <[email protected]> */
.boot_params = S3C64XX_PA_SDRAM + 0x100,
.init_irq = s3c6410_init_irq,
.map_io = mini6410_map_io,
.init_machine = mini6410_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END
再看看 mini6410_machine_init(void) 中和 i2c 有關的部分。
static void __init mini6410_machine_init(void)
{
u32 cs1;
s3c_i2c0_set_platdata(NULL);
#ifdef CONFIG_S3C_DEV_I2C1
s3c_i2c1_set_platdata(NULL);
#endif
...
...
if (ARRAY_SIZE(i2c_devs0)) {
i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
}
if (ARRAY_SIZE(i2c_devs1)) {
i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));
}
...
...
platform_add_devices(mini6410_devices, ARRAY_SIZE(mini6410_devices));
#ifdef CONFIG_VIDEO_SAMSUNG
create_proc_read_entry("videomem", 0, NULL, s3c_media_read_proc, NULL);
#endif
}
platform_add_devices 添加了平臺設備 (mini210的設備,與6410基本相同)
static struct platform_device *mini210_devices[] __initdata = {
#ifdef CONFIG_FIQ_DEBUGGER
&s5pv210_device_fiqdbg_uart2,
#endif
#ifdef CONFIG_MTD_ONENAND
&s5pc110_device_onenand,
#endif
#ifdef CONFIG_MTD_NAND
&s3c_device_nand,
#endif
&s5p_device_rtc,
#if defined(CONFIG_SND_S3C64XX_SOC_I2S_V4) || \
defined(CONFIG_SND_S5PC1XX_SOC_I2S)
&s5pv210_device_iis0,
#endif
#ifdef CONFIG_SND_S3C_SOC_AC97
&s5pv210_device_ac97,
#endif
#ifdef CONFIG_SND_S3C_SOC_PCM
&s5pv210_device_pcm0,
#endif
#ifdef CONFIG_SND_SOC_SPDIF
&s5pv210_device_spdif,
#endif
&s3c_device_wdt,
#ifdef CONFIG_FB_S3C
&s3c_device_fb,
#endif
#ifdef CONFIG_VIDEO_MFC50
&s3c_device_mfc,
#endif
#ifdef CONFIG_TOUCHSCREEN_S3C
&s3c_device_ts,
#endif
&s3c_device_1wire,
#ifdef CONFIG_KEYBOARD_GPIO
&s3c_device_gpio_btns,
#endif
#if defined(CONFIG_KEYPAD_S3C) || defined(CONFIG_KEYPAD_S3C_MODULE)
&s3c_device_keypad,
#endif
#ifdef CONFIG_S5P_ADC
&s3c_device_adc,
#endif
#ifdef CONFIG_VIDEO_FIMC
&s3c_device_fimc0,
&s3c_device_fimc1,
&s3c_device_fimc2,
#endif
#ifdef CONFIG_VIDEO_FIMC_MIPI
&s3c_device_csis,
#endif
#ifdef CONFIG_VIDEO_JPEG_V2
&s3c_device_jpeg,
#endif
#ifdef CONFIG_VIDEO_G2D
&s3c_device_g2d,
#endif
#ifdef CONFIG_VIDEO_TV20
&s5p_device_tvout,
&s5p_device_cec,
&s5p_device_hpd,
#endif
&s3c_device_g3d,
&s3c_device_lcd,
&s3c_device_i2c0,
#ifdef CONFIG_S3C_DEV_I2C1
&s3c_device_i2c1,
#endif
#ifdef CONFIG_S3C_DEV_I2C2
&s3c_device_i2c2,
#endif
#ifdef CONFIG_USB_OHCI_HCD
&s3c_device_usb_ohci,
#endif
#ifdef CONFIG_USB_EHCI_HCD
&s3c_device_usb_ehci,
#endif
#ifdef CONFIG_USB_GADGET
&s3c_device_usbgadget,
#endif
#ifdef CONFIG_USB_ANDROID
&s3c_device_android_usb,
#ifdef CONFIG_USB_ANDROID_MASS_STORAGE
&s3c_device_usb_mass_storage,
#endif
#ifdef CONFIG_USB_ANDROID_RNDIS
&s3c_device_rndis,
#endif
#endif
#ifdef CONFIG_BATTERY_S3C
&sec_device_battery,
#endif
#ifdef CONFIG_S3C_DEV_HSMMC
&s3c_device_hsmmc0,
#endif
#ifdef CONFIG_S3C_DEV_HSMMC1
&s3c_device_hsmmc1,
#endif
#ifdef CONFIG_S3C_DEV_HSMMC2
&s3c_device_hsmmc2,
#endif
#ifdef CONFIG_S3C_DEV_HSMMC3
&s3c_device_hsmmc3,
#endif
#ifdef CONFIG_S3C64XX_DEV_SPI
&s5pv210_device_spi0,
&s5pv210_device_spi1,
#endif
#ifdef CONFIG_S5PV210_POWER_DOMAIN
&s5pv210_pd_audio,
&s5pv210_pd_cam,
&s5pv210_pd_tv,
&s5pv210_pd_lcd,
&s5pv210_pd_g3d,
&s5pv210_pd_mfc,
#endif
#ifdef CONFIG_HAVE_PWM
&s3c_device_timer[0],
&s3c_device_timer[1],
&s3c_device_timer[2],
&s3c_device_timer[3],
#endif
#ifdef CONFIG_DM9000
&mini210_device_dm9000,
#endif
};
其中s3c_device_i2c0如下:
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,
},
};
struct platform_device s3c_device_i2c0 = {
.name = "s3c2410-i2c",
.id = 0,
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
看到系統註冊了platform_device 我們會想到 platform_driver 在何處註冊。
在 i2c-s3c2410.c 中我們看到了 i2c 平臺驅動註冊。
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", //注意這裏驅動名稱 s3c-i2c 和 上面設備名稱 s3c2410-i2c 不一致 why?
.pm = S3C24XX_DEV_PM_OPS,
},
};
static int __init i2c_adap_s3c_init(void)
{
return platform_driver_register(&s3c24xx_i2c_driver);
}
我們來分析爲什麼 s3c_device_i2c0 中name 和 s3c24xx_i2c_driver 中的 .driver.name不一致,一直認爲只有此兩項一致才能保證設備和驅動能夠匹配。
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 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,
},
};
我們看到 s3c24xx_i2c_driver 中有一個成員 .id_table = s3c24xx_driver_ids 定義如下
static struct platform_device_id s3c24xx_driver_ids[] = {
{
.name = "s3c2410-i2c",
.driver_data = TYPE_S3C2410, //
}, {
.name = "s3c2440-i2c",
.driver_data = TYPE_S3C2440,
}, { },
};
而這裏的 .name 和 s3c_device_i2c0 中的成員 .name 一致。
我們來具體追蹤 平臺驅動註冊時是如何查找總線上的設備並與其進行匹配並綁定的。
platform_driver_register(&s3c24xx_i2c_driver) --> driver_register(&drv->driver) --> bus_add_driver(drv) --> driver_attach(drv)
--> bus_for_each_dev(drv->bus, NULL, drv, __driver_attach) 這裏對總線下的每一個設備與添加的驅動進行匹配 --> driver_match_device(drv, dev)
static inline int driver_match_device(struct device_driver *drv,
struct device *dev)
{
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
通過代碼我們看到 調用了 drv->bus->match 通過以下兩段代碼我們知道 drv->bus->match 及調用了platform_bus_type中的 platform_match。
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
我們看platform_bus_type中的 platform_match代碼
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
看到了代碼
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
static const struct platform_device_id *platform_match_id(
const struct platform_device_id *id,
struct platform_device *pdev)
{
while (id->name[0]) {
if (strcmp(pdev->name, id->name) == 0) {
pdev->id_entry = id;
return id;
}
id++;
}
return NULL;
}
這裏的strcmp(pdev->name, id->name) 就完成了 pdev->name和id->name 的匹配。
關於設備和驅動匹配後如何綁定的我們來繼續分析代碼
static int __driver_attach(struct device *dev, void *data)
{
struct device_driver *drv = data;
/*
* Lock device and try to bind to it. We drop the error
* here and always return 0, because we need to keep trying
* to bind to devices and some drivers will return an error
* simply if it didn't support the device.
*
* driver_probe_device() will spit a warning if there
* is an error.
*/
if (!driver_match_device(drv, dev))
return 0;
if (dev->parent) /* Needed for USB */
device_lock(dev->parent);
device_lock(dev);
if (!dev->driver)
driver_probe_device(drv, dev);
device_unlock(dev);
if (dev->parent)
device_unlock(dev->parent);
return 0;
}
上節分析到 在
- driver_match_device(drv, dev)
/**
* driver_probe_device - attempt to bind device & driver together
* @drv: driver to bind a device to
* @dev: device to try to bind to the driver
*
* This function returns -ENODEV if the device is not registered,
* 1 if the device is bound successfully and 0 otherwise.
*
* This function must be called with @dev lock held. When called for a
* USB interface, @dev->parent lock must be held as well.
*/
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
int ret = 0;
if (!device_is_registered(dev))
return -ENODEV;
pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
pm_runtime_get_noresume(dev);
pm_runtime_barrier(dev);
ret = really_probe(dev, drv);
pm_runtime_put_sync(dev);
return ret;
}
繼續看裏面有個 real_probe(dev, drv) 函數
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) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
driver_bound(dev);
ret = 1;
pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
goto done;
probe_failed:
devres_release_all(dev);
driver_sysfs_remove(dev);
dev->driver = NULL;
if (ret != -ENODEV && ret != -ENXIO) {
/* driver matched but the probe failed */
printk(KERN_WARNING
"%s: probe of %s failed with error %d\n",
drv->name, dev_name(dev), ret);
}
/*
* Ignore errors returned by ->probe so that the next driver can try
* its luck.
*/
ret = 0;
done:
atomic_dec(&probe_count);
wake_up(&probe_waitqueue);
return ret;
}
看到一行 dev->driver = drv, 這樣應該就是把設備結構裏的驅動指向了 我們註冊的 driver。那下面爲什麼還有一行driver_bound(dev),看樣還要繼續分析。
static void driver_bound(struct device *dev)
{
if (klist_node_attached(&dev->p->knode_driver)) {
printk(KERN_WARNING "%s: device %s already bound\n",
__func__, kobject_name(&dev->kobj));
return;
}
pr_debug("driver: '%s': %s: bound to device '%s'\n", dev_name(dev),
__func__, dev->driver->name);
klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_BOUND_DRIVER, dev);
}
把設備加入了驅動的的設備鏈表。
中間還落下了一部分 就是 real_probe(dev, drv) 函數 的
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
此處就是調用了
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,
},
};
的 s3c_i2c_probe 函數。 此處傳給s3c_i2c_probe 的參數就是 我們註冊的i2c 平臺設備 s3c_device_i2c0
/* 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;
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;
}