Linux中IIC设备驱动再细读

S3C2440内核IIC设备驱动建立流程:

由内核打印信息,追索驱动流程:

内核注册流程:
由打印信息:
1. 先注册 i2c_driver: i2cdev_driver — \drivers\i2c\i2c-dev.c
前面分析过,内核启动初始化会执行函数:i2c_dev_init
i2c_dev_init(void)
register_chrdev(I2C_MAJOR, “i2c”, &i2cdev_fops); // 注册字符设备,主设备号为:I2C_MAJOR,应用层统一的操作接口为 i2cdev_fops
i2c_dev_class = class_create(THIS_MODULE, “i2c-dev”); // 构建一个class:i2c_dev_class

i2c_add_driver(&i2cdev_driver);
    i2c_register_driver(THIS_MODULE, driver);
        driver_register(&driver->driver);
        list_add_tail(&driver->list,&drivers);   // 这里将 i2cdev_driver 挂接到 drivers 链表上

        // 遍历adapters链表中的adapter,调用函数 i2cdev_attach_adapter(adapter)
        if (driver->attach_adapter) {   // 这里 driver->attach_adapter = i2cdev_attach_adapter
            struct i2c_adapter *adapter;

            // 在系统初始化的时候,adapters链表上,还没有初始化adapter元素,所以找不到adapter
            list_for_each_entry(adapter, &adapters, list) {
                driver->attach_adapter(adapter);  // 初始化的时候,找不到adapter,所以该句不会执行。
            }
        }


static struct i2c_driver i2cdev_driver = {
    .driver = {
        .name   = "dev_driver",
    },
    .id     = I2C_DRIVERID_I2CDEV,
    .attach_adapter = i2cdev_attach_adapter,
    .detach_adapter = i2cdev_detach_adapter,
    .detach_client  = i2cdev_detach_client,
};

注:
    这个代码是linux内核做好的框架,内核启动,就会注册一个i2c字符设备以及类,i2c-driver链表。
    并且为这个i2c字符设备提供了统一的应用层的操作接口函数。

打印信息:
i2c /dev entries driver

i2c_dev_init, i2c_add_driver: i2cdev_driver

  1. 然后注册i2c_adapter: s3c24xx_i2c.adap — \drivers\i2c\busses\i2c-s3c2410.c

注:这里就是针对不同的Soc注册i2c adapter,adapter是针对硬件i2c设备来说的。

在S3C2440中,使用平台总线驱动来注册I2C设备的。

2.1 初始化注册i2c平台设备资源:

linux-2.6.22.6\arch\arm\plat-s3c24xx\devs.c
在这个文件中定义了i2c设备资源:
    struct platform_device s3c_device_i2c = {
        .name         = "s3c2410-i2c",
        .id       = -1,
        .num_resources    = ARRAY_SIZE(s3c_i2c_resource),
        .resource     = s3c_i2c_resource,
    };

linux-2.6.22.6\arch\arm\mach-s3c2440\mach-smdk2440.c
在这个文件中注册i2c平台设备:
    static struct platform_device *smdk2440_devices[] __initdata = {
        &s3c_device_usb,
        &s3c_device_lcd,
        &s3c_device_wdt,
        &s3c_device_i2c,  
        &s3c_device_iis,
        &s3c2440_device_sdi,
    };

    // 初始化函数
    static void __init smdk2440_machine_init(void)
        platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
            platform_device_register(devs[i]);  // 这里真正注册 s3c_device_i2c 设备

2.2 初始化注册i2c平台驱动:

linux-2.6.22.6\drivers\i2c\busses\i2c-s3c2410.c

// 注册i2c平台驱动:
static int __init i2c_adap_s3c_init(void)
    platform_driver_register(&s3c2440_i2c_driver);

// s3c2440的i2c平台设备驱动结构体:
static struct platform_driver s3c2440_i2c_driver = {
    .probe      = s3c24xx_i2c_probe,
    .remove     = s3c24xx_i2c_remove,
    .resume     = s3c24xx_i2c_resume,
    .driver     = {
        .owner  = THIS_MODULE,
        .name   = "s3c2440-i2c",
    },
};

而这里要问:那么 i2c_adapter 在哪里设置的呢?
回答:
    当初始化i2c平台驱动程序时,就会去找与平台驱动同名的平台设备,如果找到匹配的设备,则会调用
    平台驱动中定义的probe函数。
    在这个probe函数中,将会获取该SoC的i2c设备的相关资源(也就是前面定义的i2c平台设备资源),并且构造一个 
    i2c_adapter 结构体,设置填充,最后调用函数i2c_add_adapter(该i2c_adapter结构体),向系统注册。

    // 平台设备驱动:注册driver的时候,当找到匹配的设备时,调用driver里面的probe函数
    s3c24xx_i2c_probe(struct platform_device *pdev)
        struct s3c24xx_i2c *i2c = &s3c24xx_i2c;
        i2c->adap.algo_data = i2c;
        i2c->adap.dev.parent = &pdev->dev;
        s3c24xx_i2c_init(i2c);                            // 初始化IIC控制器
        i2c_add_adapter(&i2c->adap);
            i2c_register_adapter(adapter);
                list_add_tail(&adap->list, &adapters);   // 将该adapter挂接到adapters链表上

                // 遍历drivers链表中的i2c_driver,调用函数:driver->attach_adapter
                list_for_each(item,&drivers) {
                    driver = list_entry(item, struct i2c_driver, list);
                    if (driver->attach_adapter)
                        /* We ignore the return code; if it fails, too bad */
                        driver->attach_adapter(adap);  // 这里由于前面内核启动的时候,初始化了一个 i2cdev_driver 结构体
                                                       // 所以这里将执行: i2cdev_attach_adapter(adap)

                            i2cdev_attach_adapter(注册的adapter结构体)
                                // 在类 i2c_dev_class 下面注册一个device: i2c_dev->dev,
                                // 在应用层的文件为: /dev/i2c-0,/dev/i2c-1,... /dev/i2c-n
                                // 这个类: i2c_dev_class 也是在内核启动的时候注册的
                                i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
                                                 MKDEV(I2C_MAJOR, adap->nr),
                                                 "i2c-%d", adap->nr);
                }

    // 结构体:i2c_adapter,这里用结构体 struct s3c24xx_i2c 做了一层封装:
    static struct s3c24xx_i2c s3c24xx_i2c = {
        .lock       = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
        .wait       = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
        .tx_setup   = 50,

        // 这里才是真正的 i2c_adapter
        .adap       = {
            .name           = "s3c2410-i2c",
            .owner          = THIS_MODULE,
            .algo           = &s3c24xx_i2c_algorithm,   // 这里定义了i2c设备通过I2C接口如何发送,接收数据,
                                                        // 至于数据啥意思,它不管。
            .retries        = 2,
            .class          = I2C_CLASS_HWMON,
        },
    };

打印信息:

cnt: 1, i2c_adapter name: s3c2410-i2c

  1. 进一步解释:
    当注册 i2c_adapter: s3c24xx_i2c.adap 的时候,就会遍历drivers链表,把drivers链表中的 i2c_driver
    一个一个取出来,然后调用 i2c_driver.attach_adapter(adapt)函数。

    因为前面只注册了一个 i2c_driver结构体: i2cdev_driver,所以就将调用函数:
    i2cdev_driver.attach_adapter(adapter) = i2cdev_attach_adapter(s3c24xx_i2c.adap)

    i2cdev_attach_adapter(s3c24xx_i2c.adap)
    device_create(i2c_dev_class, &adap->dev, MKDEV(I2C_MAJOR, adap->nr), “i2c-%d”, adap->nr);
    这个函数前面已经分析了,将会创建设备文件:”/dev/i2c-0”
    所以当内核启动后,可以查看是否建立的设备文件:

    ls /dev/i2c* -l

    crw-rw—- 1 0 0 89, 0 Jan 1 00:00 /dev/i2c-0
    可以看到,主设备号为89,次设备号为0,建立了设备文件: “/dev/i2c-0”

    内核打印信息:
    cnt: 1, i2cdev_attach_adapter,adap.name:s3c2410-i2c
    cnt: 1, i2c_register_driver, i2c_driver->name: dev_driver

  2. 问题:上面只是生成了一个/dev/i2c-0设备文件,那真正的IIC从设备如何注册驱动呢?
    回答:
    可以参考例子程序:linux-2.6.22.6\drivers\i2c\chips\ds1374.c
    static int __init ds1374_init(void)
    i2c_add_driver(&ds1374_driver)
    i2c_register_driver(THIS_MODULE, driver);
    driver->driver.owner = owner;
    driver->driver.bus = &i2c_bus_type;
    driver_register(&driver->driver);

                // 将注册的 i2c_driver 挂接到drivers链表上
                list_add_tail(&driver->list,&drivers);
    
                // 遍历adapters链表下的 adapter 结构体,调用新注册的 i2c_driver 下面的attach_adapter函数
                if (driver->attach_adapter) {
                    struct i2c_adapter *adapter;
    
                    list_for_each_entry(adapter, &adapters, list) {
                        driver->attach_adapter(adapter);  // 这里函数 attach_adapter = ds1374_attach
                                                          // adapter 就是之前初始化注册的 s3c24xx_i2c.adap
                        // 相当于执行函数:
                        ds1374_attach(s3c24xx_i2c.adap)
                            i2c_probe(s3c24xx_i2c.adap, &addr_data, ds1374_probe);
                            // 在这里才真正的和底层设备扯上关系了
                                i2c_probe_address(s3c24xx_i2c.adap, 从设备的IIC地址, -1, 传递进来的函数 ds1374_probe );
                                    i2c_smbus_xfer(adapter, addr, 0, 0, 0, I2C_SMBUS_QUICK, NULL)
                                        // 1. 探测是否真正存在该地址的IIC从设备
                                        i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,command,size,data);
    
                                        // 2. 如果存在该IIC从设备,则调用真正的IIC从设备probe函数,
                                        //    这个函数是传递进来的: ds1374_probe
                                        found_proc(adapter, addr, kind);    
                                        ds1374_probe(struct i2c_adapter *adap, int addr, int kind)  
                                            // 2.1 建立一个i2c_client结构体,这个就表示一个外部的IIC从设备
                                            //  可以看到这个client结构体才是真正的操作源头。
                                            struct i2c_client *client;
                                            client->addr = addr;
                                            client->adapter = adap;
                                            client->driver = &ds1374_driver;    
                                            i2c_attach_client(client);
                                                // 2.2 将这个i2c_client结构体,挂接到该 i2c_adapter 的clients链表下面
                                                list_add_tail(&client->list,&adapter->clients);
                                                client->dev.bus = &i2c_bus_type;
                                                device_register(&client->dev);
                    }
                }
    
    static struct i2c_driver ds1374_driver = {
        .driver = {
            .name   = DS1374_DRV_NAME,
        },
        .id = I2C_DRIVERID_DS1374,
        .attach_adapter = ds1374_attach,
        .detach_client = ds1374_detach,
    };
    

    总结:
    当注册 i2c_driver 到内核时,
    (1)会从adapters链表下取出一个个adapter,
    (2)然后调用新注册i2c_driver结构体总定义的 attach_adapter 函数,
    (3)在这个 attach_adapter 函数中,会调用内核定义的统一的接口函数: i2c_probe
    i2c_probe函数原型:
    int i2c_probe(struct i2c_adapter *adapter, struct i2c_client_address_data *address_data,
    int (found_proc) (struct i2c_adapter , int, int))

    (4)在i2c_probe函数中,做两件事:
        a. 探测该IIC从设备是否真的存在,这是有内核定义的框架来自动probe的;
        b. 传递给 i2c_probe 函数的参数中带有一个真正的 IIC从设备的probe函数,在这一步得到调用
            在这个真正的IIC从设备probe函数中,将建立一个I2c_client结构体,这个结构体就是IIC从设备
            和外界进行IIC通信的载体(各种不同的操作:比如读写IIC从设备)。
    
  3. 总结如何编写i2c驱动
    5.1 构造一个i2c_driver结构体
    该结构体包括函数:
    attach_adapter
    detach_client
    5.2 注册i2c_driver结构体
    当向内核注册i2c_driver,将会调用该结构体下面的attach_adapter函数

    5.3 所以当内核注册i2c_driver,会先探测IIC设备时候存在,如果存在,则调用attach_adapter函数来probe设备,
    来建立某种联系:
    5.3.1 在attach_adapter函数中,调用统一的函数接口:i2c_probe(adapter, 设备地址, “设备真正的probe函数”);
    5.3.2 在”设备真正的probe函数”中,构造一个:struct i2c_client结构体:
    该结构体一端连着”adapter”,一端连着”i2c_driver”,并保存IIC设备地址;最后调用函数 i2c_attach_client
    将该client注册到内核的 i2c_bus_type 上。
    5.3.3 运行到这里,表示已经找到IIC设备,并且已经利用client建立了某种联系,就可以创建与IIC设备对应的字符
    设备文件,这个创建过程和前面讲的建立字符设备驱动一样。以后IIC设备文件的读写等操作,都是借助于client
    这个桥梁。
    注:
    5.3.3 这一步在有的driver里面并没有做。注意在源代码中查看。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值