Chinaunix首页 | 论坛 | 博客
  • 博客访问: 488609
  • 博文数量: 76
  • 博客积分: 5196
  • 博客等级: 大校
  • 技术积分: 1414
  • 用 户 组: 普通用户
  • 注册时间: 2007-10-10 18:43
个人简介

转了个圈,又回来了

文章分类

全部博文(76)

文章存档

2013年(1)

2011年(8)

2010年(9)

2009年(22)

2008年(36)

我的朋友

分类: 嵌入式

2010-01-25 15:53:22

       这几篇文章来分析下linux2.6I2C驱动的写法,对个人也是一个学习的过程。

       linux系统下,i2c驱动架构分为三个部分:I2C核心、I2C总线驱动、I2C设备驱动,下面首先从设备驱动怎么写开始,主要是分析内核中的代码。本文中分析的内核代码是基于LINUX2.6.19版本。

       首先看一个以前做过的I2C驱动。下面是一个设备驱动的模块加载和卸载函数。

static struct i2c_driver tw_i2c_driver = {

       .driver = {

                 .owner = THIS_MODULE,

                 .name = "TW Client",

                 },

       .attach_adapter = tw_attach,

       .detach_client = tw_detach,

};

static __init int tw_init(void)

{

       u8 err;

       err = i2c_add_driver(&tw_i2c_driver);

       return err;

}

static void __exit tw_clean(void)

{

       i2c_del_driver(&tw_i2c_driver);

}

 

module_init(tw_init);

module_exit(tw_clean);

       从上面模块加载函数中可以看到,调用了I2C核心函数i2c_add_driver,我们看看这个函数做了些什么。

static inline int i2c_add_driver(struct i2c_driver *driver)

{

       return i2c_register_driver(THIS_MODULE, driver);

}

i2c_register_driver的作用是向系统注册该驱动,下面我们看这个函数的具体实现。

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)

{

       struct list_head   *item;

       struct i2c_adapter *adapter;

       int res;

//将该设备插入到设备链表中,这部分属于设备管理模型中的内容。

       /* add the driver to the list of i2c drivers in the driver core */

       driver->driver.owner = owner;

       driver->driver.bus = &i2c_bus_type;

 

       res = driver_register(&driver->driver);

       if (res)

              return res;

      

       mutex_lock(&core_lists);

 

       list_add_tail(&driver->list,&drivers);

       pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);

//下面将要找到该设备所接的适配器,调用i2c_driver结构体的attach_adapter

//函数。

       /* now look for instances of driver on our adapters */

       if (driver->attach_adapter) {

//注意下面是一个循环体,系统总所有的适配器,都放在链表adapters里了,这个

//工作是在I2C总线驱动里做的。

              list_for_each(item,&adapters) {

                     adapter = list_entry(item, struct i2c_adapter, list);

//对于每一个适配器都要调用attach_adapter函数,这个attach_adpter函数是用

//户编写的,需要要在这个函数调用i2c_probe函数来探测该设备是否接在该适配器上面。

                     driver->attach_adapter(adapter);

              }

       }

 

       mutex_unlock(&core_lists);

       return 0;

}

下面我们看看driver->attach_adapter函数的实现:

static unsigned short normal_i2c[] = { TW_I2C_ADDRESS, I2C_CLIENT_END };

static int tw_attach(struct i2c_adapter *adap)

{

//调用了i2c_probe函数,注意参数addr_data已经在i2c.h中给出了模板定义的,这里只需要给出addr_data.normal_i2c即可,就是给出需要探测的设备从地址。

    err = i2c_probe(adap, &addr_data, &tw_detect_client);

    clk_disable(clk);

    clk_put(clk);

    return err;

}

我们在来跟踪i2c_probe函数的实现:

 

int i2c_probe(struct i2c_adapter *adapter,

             struct i2c_client_address_data *address_data,

             int (*found_proc) (struct i2c_adapter *, int, int))

{

       int i, err;

//获得当前适配器的ID

       int adap_id = i2c_adapter_id(adapter);

 

       /* Force entries are done first, and are not affected by ignore

          entries */

//首先扫描forces中的地址,这个是不受ignore影响的

       if (address_data->forces) {

              unsigned short **forces = address_data->forces;

              int kind;

 

              for (kind = 0; forces[kind]; kind++) {

                     for (i = 0; forces[kind][i] != I2C_CLIENT_END;

                          i += 2) {

                            if (forces[kind][i] == adap_id

                             || forces[kind][i] == ANY_I2C_BUS) {

                                   dev_dbg(&adapter->dev, "found force "

                                          "parameter for adapter %d, "

                                          "addr 0x%02x, kind %d\n",

                                          adap_id, forces[kind][i + 1],

                                          kind);

                                   err = i2c_probe_address(adapter,

                                          forces[kind][i + 1],

                                          kind, found_proc);

                                   if (err)

                                          return err;

                            }

                     }

              }

       }

//下面是有关适配器具体功能支持方面的问题,暂且不管他

       /* Stop here if we can't use SMBUS_QUICK */

       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_QUICK)) {

              if (address_data->probe[0] == I2C_CLIENT_END

               && address_data->normal_i2c[0] == I2C_CLIENT_END)

                    return 0;

 

              dev_warn(&adapter->dev, "SMBus Quick command not supported, "

                      "can't probe for chips\n");

              return -1;

       }

 

       /* Probe entries are done second, and are not affected by ignore

          entries either */

//接着会扫描probe

       for (i = 0; address_data->probe[i] != I2C_CLIENT_END; i += 2) {

              if (address_data->probe[i] == adap_id

               || address_data->probe[i] == ANY_I2C_BUS) {

                     dev_dbg(&adapter->dev, "found probe parameter for "

                            "adapter %d, addr 0x%02x\n", adap_id,

                            address_data->probe[i + 1]);

                     err = i2c_probe_address(adapter,

                                          address_data->probe[i + 1],

                                          -1, found_proc);

                     if (err)

                            return err;

              }

       }

//最为关键的地方,扫描normal项,一般我们设备的地址就是放在这里的。

       /* Normal entries are done last, unless shadowed by an ignore entry */

       for (i = 0; address_data->normal_i2c[i] != I2C_CLIENT_END; i += 1) {

              int j, ignore;

 

              ignore = 0;

//下面的地方就是确认扫描的地址不在ignore里面

              for (j = 0; address_data->ignore[j] != I2C_CLIENT_END;

                   j += 2) {

                     if ((address_data->ignore[j] == adap_id ||

                          address_data->ignore[j] == ANY_I2C_BUS)

                      && address_data->ignore[j + 1]

                         == address_data->normal_i2c[i]) {

                            dev_dbg(&adapter->dev, "found ignore "

                                   "parameter for adapter %d, "

                                   "addr 0x%02x\n", adap_id,

                                   address_data->ignore[j + 1]);

                            ignore = 1;

                            break;

                     }

              }

              if (ignore)

                     continue;

//当该地址是有效地址后,就调用i2c_probe_address函数来判断该设备是否接在该适配器上面。

              dev_dbg(&adapter->dev, "found normal entry for adapter %d, "

                     "addr 0x%02x\n", adap_id,

                     address_data->normal_i2c[i]);

              err = i2c_probe_address(adapter, address_data->normal_i2c[i],

                                   -1, found_proc);

              if (err)

                     return err;

       }

 

       return 0;

}

下面我们来追踪下i2c_probe_address函数的实现过程:

 

/* ----------------------------------------------------

 * the i2c address scanning function

 * Will not work for 10-bit addresses!

 * ----------------------------------------------------

 */

static int i2c_probe_address(struct i2c_adapter *adapter, int addr, int kind,

                          int (*found_proc) (struct i2c_adapter *, int, int))

{

       int err;

//确定该地址为有效地址

       /* Make sure the address is valid */

       if (addr < 0x03 || addr > 0x77) {

              dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",

                      addr);

              return -EINVAL;

       }

//如果该地址已经被用,则跳过

       /* Skip if already in use */

       if (i2c_check_addr(adapter, addr))

              return 0;

//判断该总线上面是否真的接有这个设备,对于i2c_smbus_xfer在讲总线驱动的时候再详细介绍。

       /* Make sure there is something at this address, unless forced */

       if (kind < 0) {

              if (i2c_smbus_xfer(adapter, addr, 0, 0, 0,

                               I2C_SMBUS_QUICK, NULL) < 0)

                     return 0;

 

              /* prevent 24RF08 corruption */

              if ((addr & ~0x0f) == 0x50)

                     i2c_smbus_xfer(adapter, addr, 0, 0, 0,

                                   I2C_SMBUS_QUICK, NULL);

       }

//如果有该设备就调用回调函数

       /* Finally call the custom detection function */

       err = found_proc(adapter, addr, kind);

       /* -ENODEV can be returned if there is a chip at the given address

          but it isn't supported by this chip driver. We catch it here as

          this isn't an error. */

       if (err == -ENODEV)

              err = 0;

 

       if (err)

              dev_warn(&adapter->dev, "Client creation failed at 0x%x (%d)\n",

                      addr, err);

       return err;

}

下面我们在看看回调函数做了什么:

 

/*!

 * tw I2C detect_client function

 *

 * @param adapter            struct i2c_adapter *

 * @param address            int

 * @param kind               int

 *

 * @return  Error code indicating success or failure

 */

static int tw_detect_client(struct i2c_adapter *adapter, int address,

                            int kind)

{

//i2c_clientadapter变量指向其所接的适配器

       tw_i2c_client.adapter = adapter;

//调用i2c_attach_client函数将i2c_client依附到适配器,就是将该client接到其对应adapterclient链表上面。

       if (i2c_attach_client(&tw_i2c_client)) {

              tw_i2c_client.adapter = NULL;

              printk(KERN_ERR

                     "tw_detect_client: i2c_attach_client failed\n");

              return -1;

       }

}

 

int i2c_attach_client(struct i2c_client *client)

{

       struct i2c_adapter *adapter = client->adapter;

       int res = 0;

 

       mutex_lock(&adapter->clist_lock);

       if (__i2c_check_addr(client->adapter, client->addr)) {

              res = -EBUSY;

              goto out_unlock;

       }

//最关键的地方:将该client插到适配器的clients链表中

       list_add_tail(&client->list,&adapter->clients);

      

       client->usage_count = 0;

//下面的都是设备管理模型实现,以后再详细讨论

       client->dev.parent = &client->adapter->dev;

       client->dev.driver = &client->driver->driver;

       client->dev.bus = &i2c_bus_type;

       client->dev.release = &i2c_client_release;

      

       snprintf(&client->dev.bus_id[0], sizeof(client->dev.bus_id),

              "%d-%04x", i2c_adapter_id(adapter), client->addr);

       dev_dbg(&adapter->dev, "client [%s] registered with bus id %s\n",

              client->name, client->dev.bus_id);

       res = device_register(&client->dev);

       if (res)

              goto out_list;

       res = device_create_file(&client->dev, &dev_attr_client_name);

       if (res)

              goto out_unregister;

       mutex_unlock(&adapter->clist_lock);

 

       if (adapter->client_register)  {

              if (adapter->client_register(client)) {

                     dev_dbg(&adapter->dev, "client_register "

                            "failed for client [%s] at 0x%02x\n",

                            client->name, client->addr);

              }

       }

 

       return 0;

 

out_unregister:

       init_completion(&client->released); /* Needed? */

       device_unregister(&client->dev);

       wait_for_completion(&client->released);

out_list:

       list_del(&client->list);

       dev_err(&adapter->dev, "Failed to attach i2c client %s at 0x%02x "

              "(%d)\n", client->name, client->addr, res);

out_unlock:

       mutex_unlock(&adapter->clist_lock);

       return res;

}

       现在我们已经找到设备所接的适配器,并且将该设备依附到适配器链表中,接下来我们看看用户如何访问设备。看下面的函数:

 

/*

 * Function definitions

 */

static int tw_i2c_client_xfer(unsigned int addr, u8 page, u8 reg,

                              char *buf, int num, int tran_flag)

{

       struct i2c_msg msg[3];

       int ret;

//构造了3I2C消息,注意这里构造多少条I2C消息,是有芯片决定的。

       msg[0].addr = addr;

       msg[0].len = 1;

       msg[0].buf = &page;

       msg[0].flags = tran_flag;

       msg[0].flags &= ~I2C_M_RD;

 

       msg[1].addr = addr;

       msg[1].len = 1;

       msg[1].buf = ®

       msg[1].flags = tran_flag;

       msg[1].flags &= ~I2C_M_RD;

 

       msg[2].addr = addr;

       msg[2].len = num;

       msg[2].buf = buf;

       msg[2].flags = tran_flag;

 

       if (tran_flag & MXC_I2C_FLAG_READ) {

              msg[2].flags |= I2C_M_RD;

       } else {

              msg[2].flags &= ~I2C_M_RD;

       }

//调用i2c_transfer访问设备

       ret = i2c_transfer(tw_i2c_client.adapter, msg, 3);

       if (ret >= 0)

              return 0;

 

       return ret;

}

我们跟踪i2c_transfer函数:

int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)

{

       int ret;

 

       if (adap->algo->master_xfer) {

 

              mutex_lock(&adap->bus_lock);

//调用了adap->algo->master_xfer函数,这个函数的实现是在i2c总线驱动里做的,在讲总线驱动的时候具体解释。

              ret = adap->algo->master_xfer(adap,msgs,num);

              mutex_unlock(&adap->bus_lock);

 

              return ret;

       } else {

              dev_dbg(&adap->dev, "I2C level transfers not supported\n");

              return -ENOSYS;

       }

}

 

总结:我们来看看整个I2C设备驱动的设计流程。

1. i2c_add_driver:添加一个I2C驱动

2.i2c_register_driver:直接被调用,将该驱动向系统注册(设备管理模型)。对系统的每个适配器,都调用i2c_probe函数,直到找到设备所接的适配器。

3.i2c_probe:将合法的设备从地址传递给i2c_probe_address函数

4.i2c_probe_address:调用总线驱动函数探测该总线上面是否接有该设备,如果有则调用i2c_attach_client函数

5.i2c_attach_client:将该设备的 i2c_client结构体依附到对应的适配器

阅读(2474) | 评论(0) | 转发(4) |
给主人留下些什么吧!~~