《Linux设备驱动开发详解》——I2C核心、总线与设备驱动

来源:互联网 发布:服务器机柜和网络机柜 编辑:程序博客网 时间:2024/05/24 05:39

15.1 LInux 的 I2C体系结构

      (1)I2C核心

        I2C核心提供:①I2C总线驱动和设备驱动的注册、注销方法 

                               ②I2C通信方法(“algorithm”)

                               ③上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码

                                

        (2)I2C总线驱动

           I2C总线驱动在适配器端实现,适配器由CPU控制,甚至可以集成在CPU内部。

           I2C总线驱动主要包含了:①I2C适配器数据结构 i2c_adapter

                                                   ②I2C适配器的 algorithm 数据结构 i2c_algorithm

                                                   ③控制适配器产生通信信号的函数

           I2C总线驱动控制I2C适配器产生以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。

           (3)I2C设备驱动

             I2C设备驱动在设备端实现,设备一般挂接在I2C适配器上,通过I2C适配器与CPU交换数据。

             主要数据结构:i2c_driver   i2c_client

             Linux中 I2C驱动的主要文件:

            (1)i2c-core.c——实现I2C核心功能以及 /proc/bus/i2c* 接口

             (2)i2c-dev.c——I2C设配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访问设备时的主设备号都是89,次设备号为0-255.应用程序通过“i2c-%d”(i2c-0,i2c-1...)文件名使用文件操作接口 open() write() read()等来访问设备。

                     i2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read(),write(),ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的I2C设备或寄存器,并控制I2C设备的工作方式。

             (3)chips文件夹——包含特定的I2C设备驱动

             (4)busses文件夹——包含一些I2C总线驱动

             (5)algos文件夹——实现一些I2C总线适配器的algorithm。

/* i2c_adapter 结构体,对应一个i2c适配器,一个i2c适配器必须对应一套通信方法 */struct i2c_adapter {    struct module *owner;    unsigned int id;                      /* algorithm 的类型,定义于 i2c-id.h ,以 I2C_ALGO_开始 */    unsigned int class;                   /* classes to allow probing for */    const struct i2c_algorithm *algo;     /* 总线通信方法结构体指针 */    void *algo_data;                      /* algorithm 数据*/    u8 level;                             /* nesting level for lockdep */    struct mutex bus_lock;    int timeout;            /* in jiffies */    int retries;    struct device dev;        /* the adapter device适配器设备 */    int nr;    char name[48];    struct completion dev_released;};

/*i2c_algorithm结构体,产生i2c访问周期需要的信号函数,以msg为单位*/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 *);};
          smbus_xfer对应SMBus传输函数指针,SMBus大部分基于I2C总线规范,SMBus不许增加额外的引脚。与I2C总线相比,SMBus增加了一些新的功能特性,在访问时序也有一定的差异。

/* i2c_driver 结构体 */struct i2c_driver {    unsigned int class;    int (*attach_adapter)(struct i2c_adapter *);   /* 依附i2c_adapter函数指针 */    int (*detach_adapter)(struct i2c_adapter *);   /* 脱离i2c_adapter函数指针 */    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 *);    int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);    struct device_driver driver;    const struct i2c_device_id *id_table;       /* 驱动所支持的ID表 */    /* Device detection callback for automatic device creation */    int (*detect)(struct i2c_client *, int kind, struct i2c_board_info *);    const struct i2c_client_address_data *address_data;    struct list_head clients;};

/*i2c_client结构体,对应真是的物理设备*/struct i2c_client {    unsigned short flags;           /* 标志        */    unsigned short addr;            /* 低7位芯片地址    */    char name[I2C_NAME_SIZE];       /* 设备名称 */    struct i2c_adapter *adapter;    /* 依附的i2c_adapter    */    struct i2c_driver *driver;      /* 依附的i2c_driver */    struct device dev;              /* 设备结构体   */    int irq;                        /* 设备使用的中段号  */    struct list_head detected;      /* 链表头 */};

      i2c_driver与i2c_client的关系式有一对多,一个i2c_driver可以支持多个同等类型的i2c_client.

      i2c_client信息通常在BSP的板文件中通过i2c_board_info填充。

/* i2c_msg ,i2c访问周期的信号 */struct i2c_msg {    __u16 addr;                         /* 设备地址  */    __u16 flags;#define I2C_M_TEN        0x0010         /* this is a ten bit chip address */#define I2C_M_RD        0x0001          /* read data, from slave to master */#define I2C_M_NOSTART        0x4000     /* if I2C_FUNC_PROTOCOL_MANGLING */#define I2C_M_REV_DIR_ADDR    0x2000    /* if I2C_FUNC_PROTOCOL_MANGLING */#define I2C_M_IGNORE_NAK    0x1000      /* if I2C_FUNC_PROTOCOL_MANGLING */#define I2C_M_NO_RD_ACK        0x0800   /* if I2C_FUNC_PROTOCOL_MANGLING */#define I2C_M_RECV_LEN        0x0400    /* length will be first received byte */    __u16 len;                          /* msg length                */    __u8 *buf;                          /* pointer to msg data            */};

i2c驱动实现步骤:

15.2 Linux核心

        对应 i2c-core.c 文件。



15.3 Linux I2C总线驱动

              分析 /drivers/i2c/busses/i2c_s3c2410.c文件

15.4 Linux I2C设备驱动

             分析 /drivers/i2c/chips目录下文件

15.4.2 Linux I2C设备驱动的数据传输

/* i2c设备驱动数据传输范例 */struct i2c_msg msg[2];/* 第一条消息是写消息 */msg[0].addr = client->addr;msg[0].flags = 0;msg[0].len = 1;msg[0].buf = &ffs;/* 第二条消息是读消息 */msg[1].addr = client->addr;msg[1].flags = I2C_M_RD;msg[1].len = sizeof(buf);msg[1].buf = &buf[0];i2c_transfer(client->adapter, msg, 2);

15.4.3 Linux的i2c-dev.c文件分析

           i2c-dev.c实现了一个虚拟的、临时的i2c_client,随着设备文件的打开而产生,并随着设备文件的关闭而销毁,并没有添加到i2c_adapter的clien链表中。i2c-dev.c针对每个i2c适配器生成一个主设备号为89的设备文件,实现了i2c_driver的成员函数以及文件操作的接口,所以i2c-dev.c的主体是“i2c_driver成员函数+字符设备驱动”。

          i2c-dev.c中提供 i2cdev_read()、i2cdev_write()函数来对应用户空间要使用的 read() 和 write() 文件操作接口,这两个函数分别调用i2c核心的 i2c_master_recv()和i2c_master_send()函数来构造一条i2c消息并引发适配器 algorithm 通信函数的调用,完成数据的传输。

但是对于大多数的i2c设备读写流程并不对应于一条消息,往往需要两条甚至更多的消息来进行一次读写周期,这种情况,在应用层调用 i2cread() 、i2cwrite() 文件API进行i2c设备读写,将不能正确的读写。

          鉴于上述原因,i2c-dev.c中的i2cdev_read()和i2cdev_write()函数不具备太强的通用性,没有太大的实用价值,只能适用于非RepStart模式情况。对于两条以上消息组成的读写,在用户控件需要组织i2c_msg消息数组并调用I2C_RDWR_IOCTL命令。

<span style="color:#000000;">/* i2c-dev.c 中的 i2cdev_ioctl 函数 */static int i2cdev_ioctl(struct inode* inode, struct file* file, unsigned int cmd, unsigned long arg){    struct i2c_client* client = (struct i2c_client *)file->private_data;    ...    switch( cmd ){        case I2C_SLAVE:        case I2C_SLAVE_FORCE:            .../* 设置从设备地址 */        case I2C_TENBIT:            ...        case I2C_PEC:            ...        case I2C_FUNCS:            ...        case I2C_RDWR:            return i2cdev_ioctl_rdrw(client, arg);        case I2C_SMBUS:            ...        case I2C_RETRIES:            ...        case I2C_TIMEOUT:            ...        default:            return i2c_control(client, cmd, arg);    }    return 0;}</span>
     常用的IOCTL包括I2C_SLAVE(设置从设备地址)、I2C_RETRIES(没有收到设备ACK情况下的重试次数,默认为1)、I2C_TIMEOU(超时)以及I2C_RDWR。

/* 直接通过read()/write()读写i2c设备 */#include <stdio.h>#include <linux/types.h>#include <fcntl.h>#include <unistd.h>#include <sys/types.h>#include <sys/ioctl.h>#include <linux/i2c.h>#include <linux/i2c-dev.h>int main(int argc, char** argv){    unsigned int fd;    unsigned short mem_addr;    unsigned short size;    unsigned short idx;#define BUFF_SIZE 32    char buf[BUFF_SIZE];    char cswap;    union{        unsinged short addr;          char bytes[2];    }tmp;    if( argc < 3 ){        printf("Use:\n%s /dev/i2c-x mem_addr size\n", argv[0]);        return 0;    }    sscanf(argv[2], "%d", &mem_addr);    sscanf(argv[3], "%d", &size);    if( size > BUFF_SIZE )        size = BUFF_SIZE;    fd = open(argv[1], O_RDWR);    if( !fd ){        printf("Error on opening the device file\n");        return 0;    }    ioctl(fd, I2C_SLAVE, 0x50);   /* 设置EEPROM地址 */    ioctl(fd, I2C_TIMEOUT, 1);     /* 设置超时 */    ioctl(fd, I2C_RETRIES, 1);     /* 设置重试次数 */    for( idx = 0; idx < size; ++idx, ++mem_addr ){        tmp.addr = mem_addr;        cswap = tmp.bytes[0];        tmp.bytes[0] = tmp.bytes[1];        tmp.bytes[1] = cswap;        write(fd, &tmp.addr, 2);        read()fd, &buf[idx], 1);    }    buf[size] = 0;    close(fd);    printf("Read %d char : %s\n", size, buf);    return 0;}
/* 通过O_RDWR IOCATL读写i2c设备 */#include <stdio.h>#include <linux/types.h>#include <fcntl.h>#include <unistd.h>#include <stdlib.h>#include <sys/types.h>#include <sys/ioctl.h>#include <errno.h>#include <assert.h>#include <string.h>#include <linux/i2c.h>#include <linux/i2c-dev.h>int main(int argc, char** argv){    struct i2c_rdwr_ioctl_data work_queue;    unsigned int idx;    unsigned int fd;    unsigned int slave_address, reg_address;    unsigned char val;    int i;    int ret;    if( argc < 4 ){        printf("Usage:\n%s /dev/i2c-x start_addr reg_addr\n", argv[0]);        return 0;    }    fd = open(argv[1], O_RDWR);    if( !fd ){        printf("Error on opening the device file\n");        return 0;    }    sscanf(argv[2], "%x", &slave_address);    sscanf(argv[3], "%x", &reg_address);    work_queue.nmsgs = 2;   /* 消息数量 */    work_queue.msgs = (struct i2c_msg*)malloc(work_queue.nmsgs * sizeof(struct i2c_msg));    if( !work_queue.msgs ){        printf("Memory alloc error\n");        close(fd);        return 0;    }    ioctl(fd, I2C_TIMEOUT, 2);    /* 设置超时 */    ioctl(fd, I2C_RETRIES, 1); /* 设置重试次数 */    for( i = reg_address; i < reg_address + 16; i++ ){        val = i;        (work_queue.msgs[0]).len = 1;        (work_queue.msgs[0]).addr = slave_address;        (work_queue.msgs[0]).buf = &val;        (work_queue.msgs[1]).len = 1;        (work_queue.msgs[1]).flags = I2C_M_RD;        (work_queue.msgs[1]).addr = slave_address;        (work_queue.msgs[1]).buf = &val;        ret = ioctl(fd, I2C_RDWR, (unsigned long)&work_queue);        if( ret < 0 )            printf("Error during I2C_RDWR ioctl with error code:%d\n", ret);        else            printf("reg:%02x val:%02x\n", i, val);    }    close(fd);    return;}

0 0
原创粉丝点击