Linux I2C驱动源码分析(一)

内核版本: 2.6.31 .6

 

首先在 S3C2440 平台的初始化函数中,主要是将开发平台的设备注册进了系统,也就是将 device 注册到了 platform 虚拟的总线上,并进行了一些初始化的工作,这里我们只关注 I2C 的部分。

 

static void __init smdk2440_machine_init(void)

{

       s3c24xx_fb_set_platdata(&smdk2440_fb_info);

       s3c_i2c0_set_platdata(NULL);

 

       platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));

       smdk_machine_init();

}

 

s3c_i2c0_set_platdata ()函数将 S3C2440 上的 I2C 控制器进行了一些初始化,但是并没有写入硬件寄存器,仅仅是保存在了 s3c2410_platform_i2c 结构体中。

 

void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)

{

       struct s3c2410_platform_i2c *npd;

 

       if (!pd)

              pd = &default_i2c_data0;

 

       npd = kmemdup(pd, sizeof(struct s3c2410_platform_i2c), GFP_KERNEL);

       if (!npd)

              printk(KERN_ERR "%s: no memory for platform data/n", __func__);

       else if (!npd->cfg_gpio)

              npd->cfg_gpio = s3c_i2c0_cfg_gpio;

       /* s3c_i2c0_cfg_gpio 配置 I2C 控制器 GPIO 函数指针 */

       s3c_device_i2c0.dev.platform_data = npd;

    /* 最后将 struct device 中的 platform_data 指针直指向了初始化后的 s3c2410_platform_i2c 结构体   */

}

 

函数 s3c_i2c0_cfg_gpio() 很简单,实际上就是配置 GPIO I2C 的工作模式

 

void s3c_i2c0_cfg_gpio(struct platform_device *dev)

{

       s3c2410_gpio_cfgpin(S3C2410_GPE(15), S3C2410_GPE15_IICSDA);

       s3c2410_gpio_cfgpin(S3C2410_GPE(14), S3C2410_GPE14_IICSCL);

}

s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd) 函数实际上就是把初始化数据段中的 default_i2c_data0 结构体复制过来,然后对 GPIO 进行配置的函数指针进行了初始化。 default_i2c_data0 结构体如下:

static struct s3c2410_platform_i2c default_i2c_data0 __initdata = {

       .flags             = 0,

       .slave_addr     = 0x10,

       .frequency      = 100*1000,

       .sda_delay      = 100,

};

 

s3c2410_platform_i2c 结构体原型如下,根据英文注释即可大致理解其意思

/**

  *    struct s3c2410_platform_i2c - Platform data for s3c I2C.

  *    @bus_num: The bus number to use (if possible).

  *    @flags: Any flags for the I2C bus (E.g. S3C_IICFLK_FILTER).

  *    @slave_addr: The I2C address for the slave device (if enabled).

  *    @frequency: The desired frequency in Hz of the bus.  This is

  *                   guaranteed to not be exceeded.  If the caller does

  *                  not care, use zero and the driver will select a

  *                  useful default.

  *    @sda_delay: The delay (in ns) applied to SDA edges.

  *    @cfg_gpio: A callback to configure the pins for I2C operation.

  */

struct s3c2410_platform_i2c {

       int           bus_num;

       unsigned int    flags;

       unsigned int    slave_addr;

       unsigned long  frequency;

       unsigned int    sda_delay;

 

       void (*cfg_gpio)(struct platform_device *dev);

};

 

在函数 smdk2440_machine_init(void) 中,调用了

platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));

即将 smdk2440_devices 结构体数组中 platform_device 添加到了系统中,也就是添加到了 platform 总线上。 smdk2440_devices 的具体内容如下:

 

static struct platform_device *smdk2440_devices[] __initdata = {

       &s3c_device_usb,

       &s3c_device_lcd,

       &s3c_device_wdt,

       &s3c_device_i2c0,

       &s3c_device_iis,

       &s3c_device_dm9000,

       &s3c_device_rtc,

};

 

其中 s3c_device_i2c0 保存了 S3C2440 中的 I2C 控制器的一些内部资源等信息,具体内容如下:

struct platform_device s3c_device_i2c0 = {

  .name             = "s3c2410-i2c",     

/* 设备名 ,platform 总线的 match 函数中会用设备名和驱动名的比较来绑定设备和驱动程序 */

#ifdef CONFIG_S3C_DEV_I2C1

       .id             = 0,

#else

       .id             = -1,

#endif

       .num_resources       = ARRAY_SIZE(s3c_i2c_resource),

       .resource   = s3c_i2c_resource,

};

 

其中 s3c_i2c_resource 结构体保存了 S3C2440 I2C 控制器寄存器的物理地址和中断号等具体的硬件信息。

 

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,

       },

};

在后面注册具体设备驱动时也会添加到 paltform 总线上, platform 总线会将具体的设备和驱动进行绑定,这样驱动就可以操作具体的设备了。 platform 实际上是一个虚拟的总线,本质上也是一个设备。

 

好了,上面是一些板级的硬件设备资源向系统的注册,没有设计到具体的硬件操作,在加载驱动程序时,驱动程序会根据已经注册到系统的具体设备的硬件资源进行初始化,也就是进行一些硬件操作,控制硬件设备的正常工作,下面来分析驱动程序的加载过程。

 

S3C2440 平台上的 I2C 的驱动程序在 linux/drivers/i2c/busses/i2c-s3c2410.c 文件中,

在驱动的加载程序中,将 platform_driver 类型的 s3c24xx_i2c_driver 注册到了系统中。

static int __init i2c_adap_s3c_init(void)

{

       return platform_driver_register(&s3c24xx_i2c_driver);

}

 

分析 platform_driver_register(&s3c24xx_i2c_driver); 的源代码可知,实际上是将 s3c24xx_i2c_driver 注册到了 platform 总线上。

int platform_driver_register(struct platform_driver *drv)

{

       drv->driver.bus = &platform_bus_type;

/* device_driver 中的 probe remove shutdown 函数指针指向 platform_driver 中的函数,后面进行驱动和设备绑定后会调用 probe 函数 */

       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);

}

 

下图即为 Linux 2.6 中引入的设备驱动模型的结构图(只是个总体框架,并不是指这的 platform 总线,设备和驱动)。

 

 

 

 

 

总线上包括设备和驱动的集合,总线上所有设备组成双向循环链表,包含在 platform_device 的设备集合中,总线上所有驱动组成双向循环链表,包含在 platform_dirver 的驱动集合中。

platform_driver_register(struct platform_driver *drv) 函数实际上是对 driver_register(struct device_driver *drv) 函数的一个简单封装。 driver_register ()函数的调用关系如下

 

driver_register ()

>bus_add_driver(drv);

       —> driver_attach(drv);

         —> bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);

 

bus_for_each_dev(drv->bus, NULL, drv, __driver_attach) 函数会遍历总线上所有的设备,并调用 __driver_attach 函数,判断驱动是否和设备匹配,若匹配则将 struct device 中的 struct device_driver *driver 指向此驱动,也就是进行了驱动和设备的绑定,若不匹配,则继续遍历下一个设备。事实上,在向总线注册设备时,同样会进行类似的操作,遍历总线上所有驱动程序,找到则进行设备与驱动程序的绑定。

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.

         */

/* 调用 platform 总线的 match ()函数,即 platform_match 函数,判断设备和驱动是否匹配 , 若匹配则返真,找到对应的设备,继续执行后面的程序,若没有找到,则返回假,函数执行结束 。这里我们的 I2C 驱动找到了可以驱动的设备,所以会继续执行 */

       if (!driver_match_device(drv, dev))

              return 0;

       if (dev->parent)      /* Needed for USB */

              down(&dev->parent->sem);

       down(&dev->sem);

/* 设备是否已经找到驱动?显然,这里没有找到驱动,因为设备在向系统中 platform 总线注册时还没有驱动注册到 platform 总线上,所以 dev->drive = NULL */

       if (!dev->driver)

              driver_probe_device(drv, dev);

       up(&dev->sem);

       if (dev->parent)            

up(&dev->parent->sem);

 

       return 0;

}

 

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 sucessfully and 0 otherwise.

  *

  * This function must be called with @dev->sem held.  When called for a

  * USB interface, @dev->parent->sem 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);

       ret = really_probe(dev, drv);

 

       return ret;

}

 

really_probe 函数中 进行 device driver 的绑定,并调用用户在 device_driver 中注册的 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));

 

/* device 中的 device_driver 指针指向了这个 driver ,即完成 device driver 的绑定 */

dev->driver = drv; 

f (driver_sysfs_add(dev)) {

              printk(KERN_ERR "%s: driver_sysfs_add(%s) failed/n",

                     __func__, dev_name(dev));

              goto probe_failed;

       }

/* 若总线设置了 probe 函数,则调用总线的 probe 函数,然而 platform 总线并没有设置 */

       if (dev->bus->probe) {

              ret = dev->bus->probe(dev);

              if (ret)

                     goto probe_failed;

       }

/* 否则,调用驱动注册在 device_driver 里的 probe ,这个函数中一般进行获得硬件资源,初始化硬件等操作,这里实际调用了 s3c24xx_i2c_probe 函数 */

else if (drv->probe) {

              ret = drv->probe(dev);

              if (ret)

                     goto probe_failed;

       }

/* 将设备添加到 driver 所支持的设备列表中(因为一个驱动可以支持多个设备),并通知 bus 上的设备,表明 BUS_NOTIFY_BOUND_DRIVER   */

       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;

}

 

到这里, I2C 设备软件层次上的驱动模型已经建立好了,接着会执行 s3c24xx_i2c_probe 函数,获取系统开始注册的一些硬件资源信息,进行硬件上的一些操作,以及真正的涉及到数据传输驱动程序的注册等操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值