Linux I2C驱动分析 S3C6410

本文深入剖析Linux中I2C驱动的加载流程与设备驱动匹配机制,详细介绍了平台设备s3c_device_i2c0与平台驱动s3c24xx_i2c_driver之间的绑定过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自: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 <ben-linux@fluff.org> */  
    .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;  
    }  

上节分析到 在
  1. driver_match_device(drv, dev)  
中完成了驱动和设备的匹配,  向下看 driver_probe_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;  
    }  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值