• 欢迎访问少将全栈,学会感恩,乐于付出,珍惜缘份,成就彼此、推荐使用最新版火狐浏览器和Chrome浏览器访问本网站。
  • 吐槽,投稿,删稿,交个朋友,商务沟通v:ai_draw
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏少将全栈吧

字符设备驱动之I2C设备驱动(2)

点滴 admin 9年前 (2015-01-21) 1587次浏览 已收录 0个评论 扫描二维码

     前文介绍了利用/dev/i2c-0在应用层完成对i2c设备的操作,但很多时候我们还是习惯为i2c设备在内核层编写驱动程序。目前内核支持两种编写i2c驱动程序的方式。这里分别称这两种方式为“Adapter方式(LEGACY)”和“Probe方式(new style)”。
在介绍i2c设备驱动前首先认识下两个重要的结构体:

struct i2c_driver {
            int id;
            unsigned int class;

            int (*attach_adapter)(struct i2c_adapter *);    /*依附i2c_adapter的函数指针*/
            int (*detach_adapter)(struct i2c_adapter *);  /*脱离i2c_adapter函数指针*/

            int (*detach_client)(struct i2c_client *);           /*脱离i2c_client函数指针*/

            int (*probe)(struct i2c_client *);
            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 *);

            int (*command)(struct i2c_client *client,unsigned int cmd, void *arg);

            struct device_driver driver;
            struct list_head list;
}

struct i2c_client {  
            unsigned short flags;                   //设备的标志,如唤醒标志等等   
   
            /* chip address – NOTE: 7bit    */  
            /* addresses are stored in the  */  
            /* _LOWER_ 7 bits       */  
            unsigned short addr;                    //设备的地址   
            char name[I2C_NAME_SIZE];               //设备的名字   
            struct i2c_adapter *adapter;            //设备所属的适配器   
            struct i2c_driver *driver;              //设备的driver   
            struct device dev;      /* the device structure */  
            int irq;                               //设备所使用的中断号   
            char driver_name[KOBJ_NAME_LEN];
            struct list_head list;                //链表头
            struct completion released;           //用于同步
};

下面利用一个实例介绍“Adapter方式“的实现。
(1)/*  构造i2c_driver */
static struct i2c_driver at24c02_driver = {
 .driver = {
  .name = “at24c02”,
 },
 .attach_adapter = at24c02_attach_adapter,
 .detach_client  = at24c02_detach_client,
};
(2)/*注册i2c_driver结构*/
static int __init at24c02_init(void)
{
    i2c_add_driver(&at24c02_driver);
    return 0;
}
执行i2c_add_driver(&at24c02_driver)后会,如果内核中已经注册了i2c适配器,则顺序调用这些适配器来连接我们的i2c设备。此过程是通过调用i2c_driver中的attach_adapter方法完成的。具体实现形式如下:
(3)/* 识别、探测 I2C设备 */
static int at24c02_attach_adapter(struct i2c_adapter *adapter)
{
    /* i2c_probe首先会确定是否存在地址为0x50的设备
     * 如果存在, 则使用at24c02_detect进一步处理
     */
 return i2c_probe(adapter, &at24c02_addr_data, at24c02_detect);
     /*
      adapter:适配器
      at24c02_addr_data:地址信息at24c02_addr_data.normal_i2c[]
      at24c02_detect:探测到设备后调用的函数
  */
}
地址信息at24c02_addr_data是由下面代码指定的
static struct i2c_client_address_data at24c02_addr_data = {
 .normal_i2c = at24c02_addr,
 .probe      = ignore,
 .ignore     = ignore,
};
static unsigned short at24c02_addr[] = { 0x50, I2C_CLIENT_END };
注意:normal_i2c里的地址必须是你i2c芯片的地址。否则将无法正确探测到设备。
 (4)构建i2c_client,并注册字符设备驱动
 i2c_probe在探测到目标设备后,后调用at24c02_detect,并把当时的探测地址addr作为参数传入。
/*实现检测函数*/
static int at24c02_detect(struct i2c_adapter *adap, int addr, int kind)
{
    int rc;
    u8 value;

    printk(“at24c02_detectn”);

    /* 分配一个i2c_client结构体 */
 at24c02_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
 if (!at24c02_client)
  return -ENOMEM;

 strncpy(at24c02_client->name, name, strlen(name)+1);/*static char name[] = “at24c02”;*/
    
 at24c02_client->addr = addr;    /* 0x50 */
 at24c02_client->adapter = adap;
 at24c02_client->driver = &at24c02_driver;
    
     /* 注册i2c_client */
 if ((rc = i2c_attach_client(at24c02_client)) != 0) {
  kfree(at24c02_client);
  return rc;
 }

     /* 注册字符设备。那么自然要先实现字符设备file_operations结构体*/
     major = register_chrdev(0, “at24c02”, &at24c02_fops);
    
 // 构造供udev/mdev创建设备节点用的信息
 at24c02_class = class_create(THIS_MODULE, “at24c02”);
 if (IS_ERR(at24c02_class))
 {
  unregister_chrdev(major, “buttons”);
  return -1;
 }
 class_device_create(at24c02_class, NULL, MKDEV(major, 0), NULL, “at24c02”); 
    
     return 0;    
}
(5)/*字符设备操作函数的实现,应该在at24c02_detect函数前实现,要么得先声明*/
static int at24c02_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
    int ret;
 struct i2c_msg msg[2];
    char addr = 0;
    
    if (count > 1024)
    {
        return -EINVAL;
    }

    /* 先写地址 */
 msg[0].addr  = at24c02_client->addr;
 msg[0].flags = 0;        /* 0  表示写 */
 msg[0].len   = 1;   /* 1个地址 */
 msg[0].buf   = &addr;

    /* 再读数据 */
 msg[1].addr  = at24c02_client->addr;
 msg[1].flags = 1;       /* 1: 表示读 */
 msg[1].len   = count;              /* 要读的数据个数 */
 msg[1].buf   = at24c02_buf;

 ret = i2c_transfer(at24c02_client->adapter, msg, 2);
    
 if (ret == 2)
 {
        copy_to_user(buff, at24c02_buf, count);
  return count;
 }
    else
        return -EIO;
    
}

static ssize_t at24c02_write(struct file *filp, const char __user *buff, size_t count, loff_t *offp)
{
    int ret;
 struct i2c_msg msg[1];
    
    if (count > 1024)
    {
        return -EINVAL;
    }

    copy_from_user(&at24c02_buf[1], buff, count);

    at24c02_buf[0] = 0; /* 表示写到哪里 */

 msg[0].addr  = at24c02_client->addr;
 msg[0].flags = 0;       /* 0  表示写 */
 msg[0].len   = count + 1; /* 1个地址+count个数据 */
 msg[0].buf   = at24c02_buf;

 ret = i2c_transfer(at24c02_client->adapter, msg, 1);//最后会调用i2c_algorithm中的master_xfer
    
 if (ret == 1)
  return count;
    else
        return -EIO;
}
/*5,字符驱动的file_operations结构体,并在at24c02_detect函数前实现*/
static struct file_operations at24c02_fops = {
    .owner   =   THIS_MODULE,    /* 这是一个宏,指向编译模块时自动创建的__this_module变量 */
    .read    =   at24c02_read,
    .write   =   at24c02_write,
};
字符设备驱动本身没有什么好说的,这里主要想说一下,如何在驱动中调用i2c设配器帮我们完成数据传输。

目前设配器主要支持两种传输方法:smbus_xfer和master_xfer。一般来说,如果设配器支持了master_xfer那么它也可以模拟支持smbus的传输。但如果只实现smbus_xfer,则不支持一些i2c的传输。
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);

master_xfer中的参数设置,和前面的用户空间编程一致。现在只是要在驱动中构建相关的参数然后调用i2c_transfer来完成传输既可。

int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num);
     ret = adap->algo->master_xfer(adap,msgs,num);
  将调用i2c_algorithm中的master_xfer函数,在这里调用i2c-s3c2410.c中的
  static int s3c24xx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)函数。
      里面调用ret = s3c24xx_i2c_doxfer(i2c, msgs, num);函数传送i2c消息,在s3c24xx_i2c_doxfer里面首先ret =s3c24xx_i2c_set_master(i2c);将i2c适配器设置为i2c主设备,其后初始化  s3c2410_i2c结构体,使能i2c中断,并调用s3c24xx_i2c_message_start(i2c, msgs);函数启动i2c消息的传输。
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
 .master_xfer  = s3c24xx_i2c_xfer,     /*真正的实现发送的函数,最底层的了*/
 .functionality  = s3c24xx_i2c_func,
};
(6)/注销i2c_driver*/
static void __exit at24c02_exit(void)
{
    i2c_del_driver(&at24c02_driver);
}
 detach_client动作

顺序调用内核中注册的适配器来断开我们注册过的i2c设备。此过程通过调用i2c_driver中的detach_client 方法完成的。具体实现形式如下:
static int at24c02_detach_client(struct i2c_client *client)
{
    int err;
    
    if ((err = i2c_detach_client(at24c02_client)))
        return err;
    
    kfree(at24c02_client);

    unregister_chrdev(major, “at24c02”);
    class_device_destroy(at24c02_class, MKDEV(major, 0));
    class_destroy(at24c02_class); 
    
    return 0;
}

http://blog.csdn.net/yicao821/article/details/6792651

喜欢 (0)
[🍬谢谢你请我吃糖果🍬🍬~]
分享 (0)
关于作者:
少将,关注Web全栈开发、项目管理,持续不断的学习、努力成为一个更棒的开发,做最好的自己,让世界因你不同。
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址