linux i2c驱动学习

I2C 子系统要处理的问题主要有两个:

  • 控制总线的 I2C 控制器adapter和总线上的从机设备client。
    I2C 子系统一方面要驱动 I2C 控制器,以实现 I2C 总线上的通信;另一方面要使 I2C 总线上的从机器件能很好地工作起来.
    注册设备就是把设备的某个结构体挂接到内核设备链表中。驱动挂接在驱动链表中。注册驱动或者设备时内核都会进行设备和驱动的匹配,匹配成功调用驱动的probe函数。:
    一个具体的I2C设备驱动有两部分组成,一部分是i2c_driver,用于将设备挂接于I2C总线,一部分是设备本身的驱动

I2C设备驱动注册


注册驱动时会检测是否有匹配的设备,有则将相应的设备挂接到正确的adapter

i2c_add_driver(&eeprom_driver); //i2c_driver *driver=&eeprom_driver
    driver_register(&driver->driver)//新式驱动注册方法把设备驱动eeprom_driver挂入链表
    list_for_each_entry(adapter, &adapters, list) //老式驱动注册方法,遍历adapter链表中的所有已经注册的adapter
    driver->attach_adapter(adapter);  //相当于调用eeprom_attach_adapter匹配eeprom和adapter
         eeprom_attach_adapter(eeprom_driver)
             i2c_probe(adapter, &addr_data, eeprom_detect)
                    i2c_probe_address //使用adapter里面的master_xfer发送设备地址,如果能够收到ACK,标识发现一个支持的设备
                        i2c_smbus_xfer                  
                            i2c_smbus_xfer_emulated
                                i2c_transfer
                                    adap->algo->master_xfer /*master_xfer 该函数即s3c24xx_i2c_xfer发信号确定是否有                                                        该I2C设备,如果有调用 eeprom_detect                                                              found_proc=eeprom_detect*/
err = found_proc(adapter, addr, kind);
err = i2c_attach_client(new_client)//将新设备挂接在dapter

1.驱动的三种编写方法

部分示例代码和总结来源于他人博客
LinuxI2C驱动-从两个访问eeprom的例子开始

1. i2c-dev.c提供的devfs通用驱动方法


1. 该驱动实际只是提供了i2c通信接口,实际的驱动在用户层,应用层人员必须清楚与设备交互的时序。 应用层可以使用该方法访问几乎所有i2c设备。该方法一次只能实现一个方向的通信。比如读eeprom数据必须分两次操作,先写地址,再读数据。linux的i2c驱动会针对每个i2c适配器在/dev/目录下生成一个主设备号为89的设备文件,简单的来说,对于本例的eeprom驱动,/dev/i2c/0就是它的设备文件,因此接下来的eeprom的访问就变为了对此设备文件的访问。
我们需要用到两个结构体i2c_msg和i2c_rdwr_ioctl_data。

    struct i2c_rdwr_ioctl_data {  
      struct i2c_msg __user *msgs;    /* 指向一个i2c消息 */  
      __u32 nmsgs;                    /* i2c消息的数量 */  
    }; 

    struct i2c_rdwr_ioctl_data {  
       struct i2c_msg __user *msgs;    /* 指向一个i2c消息 */  
        __u32 nmsgs;                    /* i2c消息的数量 */  
    }; 

对一个eeprom上的特定地址(0x10)写入特定数据(0x58)并在从此地址读出写入数据的示例程序如下所示。

    #include   
    #include   
    #include   
    #include   
    #include   
    #include   
    #include   
    #include   
    #include   
    #include 

    int main()  
    {  
    int fd,ret;  
    struct i2c_rdwr_ioctl_data e2prom_data;  
    fd=open("/dev/i2c/0",O_RDWR);//打开eeprom设备文件结点  
    if(fd<0)  
    {  
        perror("open error");  
    }  
    e2prom_data.nmsgs=2;   
   e2prom_data.msgs(structi2c_msg*)malloc(e2prom_data.nmsgs*sizeof(
    struct i2c_msg));//分配空间  
    if(!e2prom_data.msgs)  
    {  
        perror("malloc error");  
        exit(1);  
    } 
    ioctl(fd,I2C_TIMEOUT,1);/*超时时间*/  
    ioctl(fd,I2C_RETRIES,2);/*重复次数*/  
    /*写eeprom*/  
    e2prom_data.nmsgs=1;//由前面eeprom读写分析可知,写eeprom需要一条消息  
    (e2prom_data.msgs[0]).len=2; //此消息的长度为2个字节,第一个字节是要写入数据的地址,第二个字节是要写入的数据  
    (e2prom_data.msgs[0]).addr=0x50;//e2prom 设备地址  
    (e2prom_data.msgs[0]).flags=0; //写  
    (e2prom_data.msgs[0]).buf=(unsigned char*)malloc(2);  
    (e2prom_data.msgs[0]).buf[0]=0x10;// e2prom 写入目标的地址  
    (e2prom_data.msgs[0]).buf[1]=0x58;//写入的数据  
    ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);//通过ioctl进行实际写入操作,后面会详细分析  
    if(ret<0)  
    {  
        perror("ioctl error1");  
    }  
    sleep(1);   
    /*读eeprom*/  
    e2prom_data.nmsgs=2;//读eeprom需要两条消息  
    (e2prom_data.msgs[0]).len=1; //第一条消息实际是写eeprom,需要告诉eeprom需要读数据的地址,因此长度为1个字节  
    (e2prom_data.msgs[0]).addr=0x50; // e2prom 设备地址  
    (e2prom_data.msgs[0]).flags=0;//先是写  
    (e2prom_data.msgs[0]).buf[0]=0x10;//e2prom上需要读的数据的地址  
    (e2prom_data.msgs[1]).len=1;//第二条消息才是读eeprom,  
    (e2prom_data.msgs[1]).addr=0x50;// e2prom 设备地址   
    (e2prom_data.msgs[1]).flags=I2C_M_RD;//然后是读  
    (e2prom_data.msgs[1]).buf=(unsigned char*)malloc(1);//存放返回值的地址。  
    (e2prom_data.msgs[1]).buf[0]=0;//初始化读缓冲,读到的数据放到此缓冲区  
    ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);//通过ioctl进行实际的读操作  
    if(ret<0)  
    {  
        perror("ioctl error2");  
    }    
    printf("buff[0]=%x\n",(e2prom_data.msgs[1]).buf[0]);  
    /***打印读出的值,没错的话,就应该是前面写的0x58了***/  
    close(fd);  
    return 0;  
}    
2. 通过sysfs文件系统访问I2C设备

eeprom的设备驱动在/sys/bus/i2c/devices/0-0050/目录下把eeprom设备映射为一个二进制节点,文件名为eeprom。对这个eeprom文件的读写就是对eeprom进行读写。对应系统内核中的eeprom.c。

    # echo "test" > eeprom 
    # cat eeprom
    test

当然,因为eeprom已经映射为一个文件了,我们还可以通过文件I/O写应用程序对其进行简单的访问测试。比如以下程序对特定地址(0x40)写入特定数据(Hi,this is an eepromtest!),然后再把写入的数据在此地址上读出来。

#include
#include
#include
#include
#include
#include

int main(void){
    int fd, size, len, i;
    char buf[50]= {0};
    char *bufw="Hi,this is an eepromtest!";//要写入的数据

    len=strlen(bufw);//数据长度
    fd= open("/sys/bus/i2c/devices/0-0050/eeprom",O_RDWR);//打开文件
    if(fd< 0)
    {
        printf("####i2c test device open failed####/n");
        return(-1);
    }
    //写操作
    lseek(fd,0x40,SEEK_SET); //定位地址,地址是0x40
    if((size=write(fd,bufw, len))<0)//写入数据
    {
        printf("write error\n");
        return 1;
    }
    printf("writeok\n");
    //读操作
    lseek(fd,0x40, SEEK_SET);//准备读,首先定位地址,因为前面写入的时候更新了当前文件偏移量,所以这边需要重新定位到0x40.
    if((size=read(fd,buf,len))<0)//读数据
    {
        printf("readerror\n");
        return 1;
    }
    printf("readok\n");
    for(i=0; i< len; i++)
        printf("buff[%d]=%x\n",i, buf[i]);//打印数据
    close(fd);

    return 0;
}
3.通过file_operations.该方法完全自己编写
/********************************************************
驱动代码,代码来源于韦东山教程
*********************************************************/
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

static unsigned short ignore[]      = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /* 地址值是7位 */
                                        /* 改为0x60的话, 由于不存在设备地址为0x60的设备, 所以at24cxx_detect不被调用 */

static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};
static unsigned short * forces[] = {force_addr, NULL};

static struct i2c_client_address_data addr_data = {
    .normal_i2c = normal_addr,  /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
    .probe      = ignore,
    .ignore     = ignore,
    //.forces     = forces, /* 强制认为存在这个设备 */
};

static struct i2c_driver at24cxx_driver;


static int major;
static struct class *cls;
struct i2c_client *at24cxx_client;

static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
    unsigned char address;
    unsigned char data;
    struct i2c_msg msg[2];
    int ret;

    /* address = buf[0] 
     * data    = buf[1]
     */
    if (size != 1)
        return -EINVAL;

    copy_from_user(&address, buf, 1);

    /* 数据传输三要素: 源,目的,长度 */

    /* 读AT24CXX时,要先把要读的存储空间的地址发给它 */
    msg[0].addr  = at24cxx_client->addr;  /* 目的 */
    msg[0].buf   = &address;              /* 源 */
    msg[0].len   = 1;                     /* 地址=1 byte */
    msg[0].flags = 0;                     /* 表示写 */

    /* 然后启动读操作 */
    msg[1].addr  = at24cxx_client->addr;  /* 源 */
    msg[1].buf   = &data;                 /* 目的 */
    msg[1].len   = 1;                     /* 数据=1 byte */
    msg[1].flags = I2C_M_RD;                     /* 表示读 */


    ret = i2c_transfer(at24cxx_client->adapter, msg, 2);
    if (ret == 2)
    {
        copy_to_user(buf, &data, 1);
        return 1;
    }
    else
        return -EIO;
}

static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    unsigned char val[2];
    struct i2c_msg msg[1];
    int ret;

    /* address = buf[0] 
     * data    = buf[1]
     */
    if (size != 2)
        return -EINVAL;

    copy_from_user(val, buf, 2);

    /* 数据传输三要素: 源,目的,长度 */
    msg[0].addr  = at24cxx_client->addr;  /* 目的 */
    msg[0].buf   = val;                   /* 源 */
    msg[0].len   = 2;                     /* 地址+数据=2 byte */
    msg[0].flags = 0;                     /* 表示写 */

    ret = i2c_transfer(at24cxx_client->adapter, msg, 1);
    if (ret == 1)
        return 2;
    else
        return -EIO;
}


static struct file_operations at24cxx_fops = {
    .owner = THIS_MODULE,
    .read  = at24cxx_read,
    .write = at24cxx_write,
};

static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{   
    printk("at24cxx_detect\n");

    /* 构构一个i2c_client结构体: 以后收改数据时会用到它 */
    at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
    at24cxx_client->addr    = address;
    at24cxx_client->adapter = adapter;
    at24cxx_client->driver  = &at24cxx_driver;
    strcpy(at24cxx_client->name, "at24cxx");
    i2c_attach_client(at24cxx_client);

    major = register_chrdev(0, "at24cxx", &at24cxx_fops);

    cls = class_create(THIS_MODULE, "at24cxx");
    class_device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */

    return 0;
}

static int at24cxx_attach(struct i2c_adapter *adapter)
{
    return i2c_probe(adapter, &addr_data, at24cxx_detect);
}

static int at24cxx_detach(struct i2c_client *client)
{
    printk("at24cxx_detach\n");
    class_device_destroy(cls, MKDEV(major, 0));
    class_destroy(cls);
    unregister_chrdev(major, "at24cxx");

    i2c_detach_client(client);
    kfree(i2c_get_clientdata(client));

    return 0;
}


/* 1. 分配一个i2c_driver结构体 */
/* 2. 设置i2c_driver结构体 */
static struct i2c_driver at24cxx_driver = {
    .driver = {
        .name   = "at24cxx",
    },
    .attach_adapter = at24cxx_attach,
    .detach_client  = at24cxx_detach,
};

static int at24cxx_init(void)
{
    i2c_add_driver(&at24cxx_driver);
    return 0;
}

static void at24cxx_exit(void)
{
    i2c_del_driver(&at24cxx_driver);
}

module_init(at24cxx_init);
module_exit(at24cxx_exit);

MODULE_LICENSE("GPL");
/**************************************************************
                应用层测试代码
**************************************************************/
#include 
#include 
#include 
#include 
#include 
#include 


/* i2c_test r addr
 * i2c_test w addr val
 */

void print_usage(char *file)
{
    printf("%s r addr\n", file);
    printf("%s w addr val\n", file);
}

int main(int argc, char **argv)
{
    int fd;
    unsigned char buf[2];

    if ((argc != 3) && (argc != 4))
    {
        print_usage(argv[0]);
        return -1;
    }

    fd = open("/dev/at24cxx", O_RDWR);
    if (fd < 0)
    {
        printf("can't open /dev/at24cxx\n");
        return -1;
    }

    if (strcmp(argv[1], "r") == 0)
    {
        buf[0] = strtoul(argv[2], NULL, 0);
        read(fd, buf, 1);
        printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
    }
    else if (strcmp(argv[1], "w") == 0)
    {
        buf[0] = strtoul(argv[2], NULL, 0);
        buf[1] = strtoul(argv[3], NULL, 0);
        write(fd, buf, 2);
    }
    else
    {
        print_usage(argv[0]);
        return -1;
    }

    return 0;
}

添加一个i2c设备

mach-smdk2440.c添加AT24C02设备的板级信息如下:

添加一个i2c设备
mach-smdk2440.c添加AT24C02设备的板级信息如下:
static struct at24_platform_data at24c02= {
6.      .byte_len   = SZ_2K / 8,
7.      .page_size  = 8,
8.      .flags      = AT24_FLAG_ADDR8,
9. };
10.  
11.  static struct i2c_board_info __initdata smdk2440_i2c_devs[] = {
12.      {
13.          I2C_BOARD_INFO("24c02", 0x50),
14.          .platform_data = &at24c02,
15.      },
16.      /*  more devices can be added using expansion connectors */
17. };
18. static void __init smdk2440_machine_init(void)
{
    s3c24xx_fb_set_platdata(&smdk2440_lcd_cfg);
    platform_add_devices(smdk2440_devices,  ARRAY_SIZE(smdk2440_devices));
    /*添加i2c设备*/
    i2c_register_board_info(0,smdk2440_i2c_devs,ARRAY_SIZE(smdk2440_i2c_devs));
    smdk_machine_init();
    }   

你可能感兴趣的:(linux i2c驱动学习)