• Linux I2C设备驱动编写(一)


    在Linux驱动中I2C系统中主要包含以下几个成员:

    I2C adapter 即I2C适配器
    I2C driver 某个I2C设备的设备驱动,可以以driver理解。
    I2C client  某个I2C设备的设备声明,可以以device理解。

    I2C adapter

    是CPU集成或外接的I2C适配器,用来控制各种I2C从设备,其驱动需要完成对适配器的完整描述,最主要的工作是需要完成i2c_algorithm结构体。这个结构体包含了此I2C控制器的数据传输具体实现,以及对外上报此设备所支持的功能类型。i2c_algorithm结构体如下:

    struct i2c_algorithm {
        int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
                   int num);
        int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
                   unsigned short flags, char read_write,
                   u8 command, int size, union i2c_smbus_data *data);
    
        u32 (*functionality) (struct i2c_adapter *);
    };

    如果一个I2C适配器不支持I2C通道,那么就将master_xfer成员设为NULL。如果适配器支持SMBUS协议,那么需要去实现smbus_xfer,如果smbus_xfer指针被设为NULL,那么当使用SMBUS协议的时候将会通过I2C通道进行仿真。master_xfer指向的函数的返回值应该是已经成功处理的消息数,或者返回负数表示出错了。functionality指针很简单,告诉询问着这个I2C主控器都支持什么功能。

    在内核的drivers/i2c/i2c-stub.c中实现了一个i2c adapter的例子,其中实现的是更为复杂的SMBUS。

    SMBus 与 I2C的区别

    通常情况下,I2C和SMBus是兼容的,但是还是有些微妙的区别的。

    时钟速度对比:

     I2CSMBus
    最小 10kHz
    最大 100kHZ(标准)400kHz(快速模式)2MHz(高速模式) 100kHz
    超时 35ms

    在电气特性上他们也有所不同,SMBus要求的电压范围更低。

    I2C driver

    具体的I2C设备驱动,如相机、传感器、触摸屏、背光控制器常见硬件设备大多都有或都是通过I2C协议与主机进行数据传输、控制。结构体如下:

    struct i2c_driver {
        unsigned int class;
    
        /* Notifies the driver that a new bus has appeared or is about to be
         * removed. You should avoid using this, it will be removed in a
         * near future.
         */
        int (*attach_adapter)(struct i2c_adapter *) __deprecated;  //旧的与设备进行绑定的接口函数
        int (*detach_adapter)(struct i2c_adapter *) __deprecated;  //旧的与设备进行解绑的接口函数
    
        /* Standard driver model interfaces */
        int (*probe)(struct i2c_client *, const struct i2c_device_id *); //现行通用的与对应设备进行绑定的接口函数
        int (*remove)(struct i2c_client *);  //现行通用与对应设备进行解绑的接口函数
    
        /* driver model interfaces that don't relate to enumeration  */
        void (*shutdown)(struct i2c_client *);  //关闭设备
        int (*suspend)(struct i2c_client *, pm_message_t mesg); //挂起设备,与电源管理有关,为省电
        int (*resume)(struct i2c_client *); //从挂起状态恢复
    
        /* Alert callback, for example for the SMBus alert protocol.
         * The format and meaning of the data value depends on the protocol.
         * For the SMBus alert protocol, there is a single bit of data passed
         * as the alert response's low bit ("event flag").
         */
        void (*alert)(struct i2c_client *, unsigned int data);
    
        /* a ioctl like command that can be used to perform specific functions
         * with the device.
         */
        int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
    
        struct device_driver driver;  //I2C设备的驱动模型
        const struct i2c_device_id *id_table;  //匹配设备列表
    
        /* Device detection callback for automatic device creation */
        int (*detect)(struct i2c_client *, struct i2c_board_info *);
        const unsigned short *address_list;
        struct list_head clients;
    };
    #define to_i2c_driver(d) container_of(d, struct i2c_driver, driver)  //一般编写驱动过程中对象常是driver类型,可以通过to_i2c_driver找到其父类型i2c_driver

    如同普通设备的驱动能够驱动多个设备一样,一个I2C driver也可以对应多个I2C client。

    以重力传感器AXLL34X为例,其实现的I2C驱动为:

    static const struct i2c_device_id adxl34x_id[] = { 
         { "adxl34x", 0 },  //匹配i2c client名为adxl34x的设备
         { } 
     };
    
     MODULE_DEVICE_TABLE(i2c, adxl34x_id);
    
     static struct i2c_driver adxl34x_driver = { 
         .driver = { 
             .name = "adxl34x",
             .owner = THIS_MODULE,
             .pm = &adxl34x_i2c_pm,  //指定设备驱动的电源管理接口,包含suspend、resume
         },  
         .probe    = adxl34x_i2c_probe,  //组装设备匹配时候的匹配动作
         .remove   = adxl34x_i2c_remove,  //组装设备移除接口
         .id_table = adxl34x_id,  //制定匹配设备列表
     };
    
     module_i2c_driver(adxl34x_driver);

    这里要说明一下module_i2c_driver宏定义(i2c.h):

    #define module_i2c_driver(__i2c_driver) 
        module_driver(__i2c_driver, i2c_add_driver, 
                         i2c_del_driver)
    
    #define i2c_add_driver(driver) 
            i2c_register_driver(THIS_MODULE, driver)

    module_driver():

    #define module_driver(__driver, __register, __unregister, ...) 
    static int __init __driver##_init(void) 
    { 
            return __register(&(__driver) , ##__VA_ARGS__); 
    } 
    module_init(__driver##_init); 
    static void __exit __driver##_exit(void) 
    { 
            __unregister(&(__driver) , ##__VA_ARGS__); 
    } 
    module_exit(__driver##_exit);

    理解上述宏定义后,将module_i2c_driver(adxl34x_driver)展开就可以得到:

    static int __int adxl34x_driver_init(void)
    {
        return i2c_register_driver(&adxl34x_driver);
    }
    module_init(adxl34x_driver_init);
    static void __exit adxl34x_driver_exit(void)
    {
        return i2c_del_driver(&adxl34x_driver);
    }
    module_exit(adxl34x_driver_exit);

    这一句宏就解决了模块module安装卸载的复杂代码。这样驱动开发者在实现I2C驱动时只要将i2c_driver结构体填充进来就可以了,无需关心设备的注册与反注册过程。

    I2C client

    即I2C设备。I2C设备的注册一般在板级代码中,在解析实例前还是先熟悉几个定义:

    struct i2c_client {
        unsigned short flags;        //I2C_CLIENT_TEN表示设备使用10bit从地址,I2C_CLIENT_PEC表示设备使用SMBus检错
        unsigned short addr;        //设备从地址,7bit。这里说一下为什么是7位,因为最后以为0表示写,1表示读,通过对这个7bit地址移位处理即可。addr<<1 & 0x0即写,addr<<1 | 0x01即读。
        char name[I2C_NAME_SIZE];  //从设备名称
        struct i2c_adapter *adapter;    //此从设备依附于哪个adapter上
        struct i2c_driver *driver;    // 此设备对应的I2C驱动指针
        struct device dev;        // 设备模型
        int irq;            // 设备使用的中断号
        struct list_head detected;  //用于链表操作
    };
    #define to_i2c_client(d) container_of(d, struct i2c_client, dev)  //通常使用device设备模型进行操作,可以通过to_i2c_client找到对应client指针
    
    struct i2c_board_info {
        char        type[I2C_NAME_SIZE];  //设备名,最长20个字符,最终安装到client的name上
        unsigned short    flags;  //最终安装到client.flags
        unsigned short    addr;  //设备从地址slave address,最终安装到client.addr上
        void        *platform_data;  //设备数据,最终存储到i2c_client.dev.platform_data上
        struct dev_archdata    *archdata;
        struct device_node *of_node;  //OpenFirmware设备节点指针
        struct acpi_dev_node acpi_node;
        int        irq;  //设备采用的中断号,最终存储到i2c_client.irq上
    };
    //可以看到,i2c_board_info基本是与i2c_client对应的。
    #define I2C_BOARD_INFO(dev_type, dev_addr) 
        .type = dev_type, .addr = (dev_addr)
    //通过这个宏定义可以方便的定义I2C设备的名称和从地址(别忘了是7bit的)

    下面还是以adxl34x为例:

    static struct i2c_board_info i2c0_devices[] = { 
        {   
            I2C_BOARD_INFO("ak4648", 0x12),
        },  
        {   
            I2C_BOARD_INFO("r2025sd", 0x32),
        },  
        {   
            I2C_BOARD_INFO("ak8975", 0x0c),
            .irq = intcs_evt2irq(0x3380), /* IRQ28 */
        },  
        {   
            I2C_BOARD_INFO("adxl34x", 0x1d),
            .irq = intcs_evt2irq(0x3340), /* IRQ26 */
        },  
    };
    ...
    i2c_register_board_info(0, i2c0_devices, ARRAY_SIZE(i2c0_devices));

    这样ADXL34X的i2c设备就被注册到了系统中,当名字与i2c_driver中的id_table中的成员匹配时就能够出发probe匹配函数了。

  • 相关阅读:
    namespace std 定义的位置
    [Struts]学习日记3 在页面中显示条目列表
    [Hibernate]关于ID的一个容易混淆的地方
    [Struts]"Cannot find bean in any scope"之一解
    [Struts]HibernatePlugIn for Struts(转贴)
    日志搬家了!
    [Struts]学习日记2 增加一些验证
    实验室的项目 讨论
    Struts常见异常信息和解决方法
    参加婚礼
  • 原文地址:https://www.cnblogs.com/AlwaysOnLines/p/3831930.html
Copyright © 2020-2023  润新知