Linux I2C驅動分析 S3C6410

轉自: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;  
    }  

上節分析到 在
  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;  
    }  


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章