57 linux内核的i2c设备驱动模型

为了实现设备驱动的可移植性及可重用性,在linux内核的驱动模型里, device_driver设备驱动只需实现驱动方法和使用device设备提供的硬件资源(如用到的io口,中断号等). bus总线用于装载device设备与device_driver设备驱动,并管理设备与设备驱动的匹配.

为了方便我们,内核里基于设备驱动模型又扩展出平台设备驱动模型. 在嵌入式里,平台设备驱动模型是使用率最高的驱动模型, 所有的控制器驱动都是由SOC芯片厂家实现的平台设备驱动. 在控制器驱动里,平台设备一般用于提供控制器的配置寄存器基地址、IO口、中断号等硬件相关的资源; 平台驱动都是可重用的控制器驱动方法。

在全志H3平台里, i2c控制器的驱动就是使用了平台设备驱动的模型。
SOC共提供了4个控制器, 每个控制器的驱动方法都是一样,所以只需写一份可重用的控制器驱动代码即可。但每个控制器在硬件上都是有区别的,如使用的IO口,配置寄存器的基地址,中断号等等。 在驱动模型中,控制器的驱动方法可实现为平台驱动, 每个控制器的硬件资源由一个平台设备对象来提供。

//
当控制器驱动好后,即控制器的平台设备对象与平台驱动对象匹配后,在内核里由一个struct i2c_adapter对象来表示一个具体的控制器。而且只需通过此i2c_adapter对象就可以实现调用i2c控制器,从而让控制器按需进行收发数据.

为了让i2c设备驱动实现可移植性和可重用性,i2c设备驱动也就必须脱离使用固定的硬件资源实现设备的驱动方法。所以i2c的设备驱动模型也是基于linux驱动模型上扩展实现的.

用于提供硬件资源的i2c设备类型:
//在内核里先用i2c_board_info对象来描述设备,在当i2c_adapter对象注册时,再根据i2c_board_info信息创建出真正的i2c_client设备对象.

struct i2c_client {
    unsigned short flags;  //没用. 如设备地址为10位,则设I2C_CLIENT_TEN
    unsigned short addr;   //i2c设备地址
    char name[I2C_NAME_SIZE]; //设备的名字
    struct i2c_adapter *adapter;   //此指针指向此设备在具体i2c控制器的i2c_adapter对象
    struct i2c_driver *driver;  //指向匹配上的i2c设备驱动
    struct device dev;      //基于device结构体扩展而来, 驱动模型. 也可以用dev.platform_data来给设备驱动提供硬件资源
    int irq;            //中断号
    struct list_head detected;
};

i2c设备驱动类型:

struct i2c_driver {
    int (*attach_adapter)(struct i2c_adapter *) __deprecated;
    int (*detach_adapter)(struct i2c_adapter *) __deprecated;
     //上面两个函数已淘汰, 不要再使用. 现由probe, remove代替.

    int (*probe)(struct i2c_client *, const struct i2c_device_id *);  //与设备匹配时触发
    int (*remove)(struct i2c_client *); //匹配上的设备移除时触发


    void (*shutdown)(struct i2c_client *); 
    int (*suspend)(struct i2c_client *, pm_message_t mesg);
    int (*resume)(struct i2c_client *); 
    //上面三个函数由电源管理事件触发.
    ...
    struct device_driver driver; //基于device_driver扩展而来. 需设置driver.name
    const struct i2c_device_id *id_table; //用于指定只与哪些设备名匹配 
    ...
};

extern int i2c_register_driver(struct module *, struct i2c_driver *); //注册i2c设备驱动对象
  //也可以用宏: #define i2c_add_driver(driver)     i2c_register_driver(THIS_MODULE, driver)

extern void i2c_del_driver(struct i2c_driver *); //移除i2c设备驱动对象

i2c的设备与设备驱动对象都是挂载到i2c总线上的。它们与i2c控制器驱动的关系如下图:
这里写图片描述

/
i2c总线的源码分析:

"drivers/i2c/i2c-core.c"
 316 struct bus_type i2c_bus_type = {
 317     .name       = "i2c",
 318     .match      = i2c_device_match, //匹配i2c设备与设备驱动
 319     .probe      = i2c_device_probe,  //注意: 总线实现probe函数,则设备与设备驱动匹配上后,触发的就是总线的probe函数
 320     .remove     = i2c_device_remove, //当设备或设备驱动移除时,触发的是总线的remove函数
 321     .shutdown   = i2c_device_shutdown,
 322     .pm     = &i2c_device_pm_ops,
 323 };
 324 EXPORT_SYMBOL_GPL(i2c_bus_type);

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
    struct i2c_client   *client = i2c_verify_client(dev); //i2c_client基于device扩展而来,这里是根据i2c_client里的dev成员的地址获取出结构体对象的首地址
    struct i2c_driver   *driver;
    ...
    driver = to_i2c_driver(drv);//i2c_driver是基于device_driver扩展而来,这里根据driver成员的地址获取出对象的首地址
    if (driver->id_table) //按设备驱动的id_table里指定的名字与设备名进行匹配
        return i2c_match_id(driver->id_table, client) != NULL;

    return 0;
}

/
i2c总线实现了probe函数,所以当i2c设备与设备驱动匹配后,触发总线的probe函数,即i2c_device_probe。i2c_driver对象的probe函数是在总线的probe函数里被调用的.

static int i2c_device_probe(struct device *dev)
{
    struct i2c_client   *client = i2c_verify_client(dev);
    struct i2c_driver   *driver;
    ...
    driver = to_i2c_driver(dev->driver);
    if (!driver->probe || !driver->id_table)
        return -ENODEV;

    client->driver = driver;
    ... //这里调用了i2c_driver对象的probe函数
    status = driver->probe(client, i2c_match_id(driver->id_table, client));
    ...
    return status;
}

i2c总线实现了remove函数,所以当i2c设备或设备驱动移除时,触发总线的remove函数,即i2c_device_remove。i2c_driver对象的remove函数是在总线的remove函数里被调用的.

static int i2c_device_remove(struct device *dev)
{
    struct i2c_client   *client = i2c_verify_client(dev);
    struct i2c_driver   *driver;
    int         status;
    ...
    driver = to_i2c_driver(dev->driver);
    if (driver->remove) {
        dev_dbg(dev, "remove\n");
        status = driver->remove(client); //这里调用了i2c_driver对象的remove函数
    } else {
        dev->driver = NULL;
        status = 0;
    }
    ...
    return status;
}


///
内核里已提供在设备驱动中调用i2c控制器对象的接口函数,基中最常用的函数是i2c_transfer

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); 
// msgs表示多个i2c_msg的消息数组(每个i2c_msg可示读/写的操作), num表示msgs数组的元素个数
//注意这个函数,每个i2c_msg消息都会发出开始信号,但这个函数只有在最后一条消息执行结束时才会发出一个停止信号,不管函数调用时共发出多少条消息
如:
    struct i2c_msg msgs[3];
    i2c_transfer(adap, msgs, 3); //发出3条消息,每条消息都带有一个开始信号,但3条消息发完后才会有一个信止信号
注意: 连续的i2c_transfer操作时有可能需要每次调用后加入延时.

struct i2c_msg {
    __u16 addr; //设备地址,注意不包含读写位
    __u16 flags; // 0表示写, I2C_M_RD表示读
    __u16 len;     //发出设备地址及读写位后,传输的数据字节数
    __u8 *buf;     //数据缓冲区首地址
};
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值