linux IIC设备驱动开发

  • 操作系统
  • linux

一,SPI设备驱动框架

在 platform驱动框架和pwm驱动框架中,都提到过驱动的分离,也就是控制器或总线和设备的分离。I2C 的驱动结构,分为 I2C 总线和 I2C设备。总线是芯片本身的 I2C 资源,而设备则是 I2C 外接的用户设备如 RTC、EEPROM 等。

1,I2C 控制器驱动:内核中使用结构体 i2c_adapter 来表示 I2C 控制器,i2c_adapter 结构体定义在文件 include/linux/i2c.h 中。

struct i2c_adapter 
{
     struct module *owner;
     unsigned int class; /* classes to allow probing for */
     const struct i2c_algorithm *algo; /* the algorithm to access the bus */
     void *algo_data;
 
    /* data fields that are valid for all devices */
     const struct i2c_lock_operations *lock_ops;
     struct rt_mutex bus_lock;
     struct rt_mutex mux_lock;
  
     int timeout; /* in jiffies */
     int retries;
     struct device dev; /* the adapter device */
  
     int nr;
     char name[48];
     struct completion dev_released;
 
     struct mutex userspace_clients_lock;
     struct list_head userspace_clients;
 
     struct i2c_bus_recovery_info *bus_recovery_info;
     const struct i2c_adapter_quirks *quirks;
};

(1) const struct i2c_algorithm 型指针成员发量 algo,是 I2C 设备访问总线的接口函数的
合集,是 I2C 设备和 I2C 控制器通讯的方法。

struct i2c_algorithm 
{
/*If an adapter algorithm can't do I2C-level access,set master_xfer to NULL.If an adapter algorithm can do SMBus access,set smbus_xfer.If set to NULL,the SMBus protocol is simulated using common I2C messages */
 /* master_xfer should return the number of messages successfully processed, or a negative value on error */


        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);
/* To determine what the adapter supports */
        u32 (*functionality) (struct i2c_adapter *);
 
          #if   IS_ENABLED(CONFIG_I2C_SLAVE)
        int (*reg_slave)(struct i2c_client *client);
        int (*unreg_slave)(struct i2c_client *client);
         #endif

(2)master_xfer函数就是用于与I2C设备通讯的函数; smbus_xfer函数是smbus的传输函数。

(3)iic_adapter结构体

linux IIC设备驱动开发_第1张图片

2,I2C 设备驱动:分层为两个部分,设备 i2c_client 和驱动  i2c_driver。

(1)i2c_client 是描述设备信息的,定义如下:

 struct i2c_client 
{
     unsigned short flags; /* div., see below */
     unsigned short addr; //chip address - NOTE: 7bit表示芯片地址,储存在低 7 位
     //addresses are stored in the_LOWER_ 7 bits  
     char name[I2C_NAME_SIZE];//表示设备名称
     struct i2c_adapter *adapter; //the adapter we sit on 对应 I2C 控制器
     struct device dev; /* the device structure */
     int irq; /* irq issued by device */
     struct list_head detected;
     #if IS_ENABLED(CONFIG_I2C_SLAVE)
     i2c_slave_cb_t slave_cb; /* callback for slave mode */
     #endif
}

(2)i2c_driver是linux  SPI驱动框架中重点,结构体定义如下

struct i2c_driver 
{
    unsigned int class;
    /* Notifies the driver that a new bus has appeared. You should avoid
         using this, it will be removed in a near future. */
    int (*attach_adapter)(struct i2c_adapter *) __deprecated;
    /* Standard driver model interfaces */
    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 *);
    void(*alert)(struct i2c_client*,enum i2c_alert_protocol protocol,unsigned int data);

  //a ioctl like command that can be used to perform specific functions with the device.
    int  (*command)(struct i2c_client *client,  unsigned int cmd, void *arg);
    struct device_driver driver;
    const struct i2c_device_id *id_table;

   /* Device detection callback for automatic device creation */
    int (*detect)(struct i2c_client *, struct i2c_board_info *);
    const unsigned short *address_list;
    struct list_head clients;
};

(1)probe 函数和platform 框架中的类似,I2C设备和驱动匹配成功后就会执行。

(2)device_driver结构体发量 driver 就是用于和设备匹配,使用设备树的话,需要设置 driver.of_match_table中的compatible 属性。

(3)定义并始化完成 i2c_driver 之后使用下面的函数来向内核注册:

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

owner 一般位 THIS_MODULE。driver 就是需要注册的 i2c_driver。返回0成功,负值失败。

(4)注销函数为:void i2c_del_driver(struct i2c_driver *driver)

   二,综上,IIC驱动填入框架

static int ax_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
 return 0;
}
 
 static int ax_remove(struct i2c_client *client)
{
  return 0;
}

static const struct of_device_id ax_of_match[] =
{
    { .compatible = "alinx-xxx"},
    {/* sentinel */}
};
 
static struct i2c_driver ax_driver = 
{
    .driver = 
    {
    .owner = THIS_MODULE,
    .name = "alinx-xxx",
    .of_match_table = ax_of_match,
    },
    .probe = ax_probe,
    .remove = ax_remove,
};
 
static int ax_init(void)
{
    i2c_add_driver(&ax_driver);
    return 0;
}

static void ax_exit(void)
{
     i2c_del_driver(&ax_driver);
}

    module_init(ax_init);
    module_exit(ax_exit);

三,I2C 设备驱动的实现流程

1,首先要在对应的 I2C 节点中添加设备节点

i2c0: i2c@e0004000                      //i2c0是控制器的节点。
{
    compatible = "cdns,i2c-r1p10";
    clocks = <&clkc 38>;
    interrupt-parent = <&intc>;
    interrupts = <0 25 4>;
    reg = <0xe0004000 0x1000>;
    #address-cells = <1>;
    #size-cells = <0>;
};
 
   &i2c0                          //引用控制器节点&i2c,并在里面添加讴备节点axrtc
   {
     axrtc@68          //设备备节点名称后面@接的是设备的地址 68。
     {
       compatible = "subomb-rtc";//compatible 兼容性用于和设备驱动相匹配。
       status = "okay";
       reg = <0x68>;  //reg 和 节点名@后面的值相同,都是设备地址
     };
  };

2,数据收发:I2C 收发通过内核中的 i2c_transfer函数实现,返回函数最终会调用 i2c 控制器驱动中的master_xfer函数。i2c_transfer函数定义在 include/linux/i2c.h 中。

函数原型:int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

参数 adap 可以在 probe函数中获取,probe 函数被调用时,第一个输入参数为设备树中对应节点的struct i2c_client *client,client 中的 adap 也就是对应的控制器。参数 msgs 是需要发送的数据。参数 num 为需要发送msgs数量。返回负值失败,返回非负值为 msgs 发送的数量。
msgs的为struct i2c_msg类型的指针,struct i2c_msg定义在include/uapi/linux/i2c.h中:

struct i2c_msg 
{
    __u16 addr; /* slave address */
    __u16 flags;
    #define I2C_M_RD 0x0001 /* read data, from slave to master */
    /* I2C_M_RD is guaranteed to be 0x0001! */
    #define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
    #define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
    #define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
    #define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
    #define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
    #define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
    #define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
    __u16 len; /* msg length */
    __u8 *buf; /* pointer to msg data */
};

3,根据 flags 的值,i2c_transfer函数执行不同的工作,包括读写。调用 i2c_transfer 函数之前,需要先构建 struct i2c_msg 发量

static int ax_read_regs(struct axrtc_dev *dev, u8 reg, void *val, int len)
{
    int ret;
    struct i2c_msg msg[2];
    struct i2c_client *client = (struct i2c_client *)dev->private_data;
 //这里的client获取了 dev->private_data 中的值,dev->private_data 是 probe 中设置的私有发量,具体到后面的实验中再去分析,先看msg构造。


//构建msg[0],addr为设备地址值,使用 client 中的 addr 即可。flags 赋为 0 时为写。
flags 为写时buf的值为写入的数据的首地址。len 为写入数据的长度。
    msg[0].addr = client->addr;
    msg[0].flags = 0;
    msg[0].buf = ®
    msg[0].len = 1;

//构建msg[1],flag 等于I2C_M_RD 为读数据,此时buf为储存读出数据buffer的首
地址。len 为读出数据的长度。
    msg[1].addr = client->addr;
    msg[1].flags = I2C_M_RD;
    msg[1].buf = val;
    msg[1].len = len;
    ret = i2c_transfer(client->adapter, msg, 2);
    if(2 == ret)
    {
       ret = 0;
    }
    else
    {
    printk("i2c read failed %d", ret);
    ret = -EREMOTEIO;
    }
 
    return ret;
}

四,项目实战

1,硬件连接

找到 EEPROM 连接的 IIC,传感器共用一路。通过一个转换芯片,最终到 PS_IIC1_SDA

linux IIC设备驱动开发_第2张图片

通过 PS_IIC1_SDA 找到连接到芯片的引脚,把 I2C1 通道配置到 ps 端的 PS_MIO24,PS_MIO25, PS_MIO24两个引脚上。

linux IIC设备驱动开发_第3张图片

2, 打开 system-user.dtsi 文件,在根目录外添加下面的节点

&i2c1 //因为vivado中配置引脚时,把eeprom的引脚约束到 i2c1上了,设备树需要对应的添加在i2c_1节点中。
{
    clock-frequency = <100000>;//讴置 i2c_1 的时钟为 100hz
 
    //添加 eeprom 的节点 ax-e2p1@50,设备地址为 50。添加 compatible 属性等于”x-e2p
1” ,用于驱动匹配。添加 reg 属性等亍讴备地址 0x50
    ax-e2p1@50  
    {
      compatible = "ax-e2p1";
      reg = <0x50>;
    };
};

 3,驱动程序

petalinux 新建名为”ax-i2c”驱动程序,并执行 petalinux-config -c rootfs 命令选上新增的驱动程序。

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

/* 驱动个数 */  
#define AX_I2C_CNT  1
/* 设备节点名称 */ 
#define AX_I2C_NAME "ax_i2c_e2p"

struct ax_i2c_dev {
    dev_t devid;              //设备号
    struct cdev cdev;         //字符设备
    struct class *class;      //类
    struct device *device;    //设备
    int major;                //主设备号
    void *private_data;       //用于在probe函数中获取client
};
/* 声明设备结构体变量 */
struct ax_i2c_dev axi2cdev;

/* i2c数据读取
 * struct ax_i2c_dev *dev : 设备结构体
 * u8 reg                 : 数据在目标设备中的地址
 * void *val              : 数据buffer首地址
 * int len                :数据长度
 */
static int ax_i2c_read_regs(struct ax_i2c_dev *dev, u8 reg, void *val, int len)
{
    int ret;
    /* 构建msg, 读取时一般使用一个至少两个元素的msg数组
       第一个元素用于发送目标数据地址(写), 第二个元素发送buffer地址(读) */
    struct i2c_msg msg[2];
    /* 从设备结构体变量中获取client数据 */
    struct i2c_client *client = (struct i2c_client *)dev->private_data;

    /* 构造msg */
    msg[0].addr = client->addr;    //设置设备地址
    msg[0].flags = 0;              //标记为写, 先给eeprom发送读取数据的所在地址
    msg[0].buf = ®             //读取数据的所在地址
    msg[0].len = 1;                //地址数据长度, 只发送首地址的话长度就为1
	
    msg[1].addr = client->addr;    //设置设备地址
    msg[1].flags = I2C_M_RD;       //标记为读
    msg[1].buf = val;              //数据读出的buffer地址
    msg[1].len = len;              //读取数据长度

    /* 调用i2c_transfer发送msg */
    ret = i2c_transfer(client->adapter, msg, 2);
    if(2 == ret)
    {
        ret = 0;
    }
    else
    {
        printk("i2c read failed %d\r\n", ret);
        ret = -EREMOTEIO;
    }

    return ret;
}

/* i2c数据写入
 * struct ax_i2c_dev *dev : 设备结构体
 * u8 reg                 : 数据在目标设备中的地址
 * void *val              : 数据buffer首地址
 * int len                :数据长度
 */
static s32 ax_i2c_write_regs(struct ax_i2c_dev *dev, u8 reg, u8 *buf, int len)
{
    int ret;
    /* 数据buffer */
    u8 b[100] = {0};
    /* 构建msg */
    struct i2c_msg msg;
    /* 从设备结构体变量中获取client数据 */
    struct i2c_client *client = (struct i2c_client *)dev->private_data;

    /* 把写入目标地址放在buffer的第一个元素中首先发送 */
    b[0] = reg;
    /* 把需要发送的数据拷贝到随后的地址中 */
    memcpy(&b[1], buf, 100 > len ? len : 100);

    /* 构建msg */
    msg.addr = client->addr;    //设置设备地址
    msg.flags = 0;              //标记为写
    msg.buf = b;                //数据写入的buffer地址
    msg.len = len + 1;          //写入的数据长度, 因为除了用户数据外, 
                                //还需要发送数据地址所以要+1
   
    /* 调用i2c_transfer发送msg */
    ret = i2c_transfer(client->adapter, &msg, 1);

    if(1 == ret)
    {
        ret = 0;
    }
    else
    {
        printk("i2c write failed %d\r\n", ret);
        ret = -EREMOTEIO;
    }
    return ret;
}

/* open函数实现, 对应到Linux系统调用函数的open函数 */
static int ax_i2c_open(struct inode *inode, struct file *filp)
{
    /* 设置私有数据 */
    filp->private_data = &axi2cdev;
    return 0;
}

/* read函数实现, 对应到Linux系统调用函数的read函数 */ 
static ssize_t ax_i2c_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
    /* 获取私有数据 */
    struct ax_i2c_dev *dev = (struct ax_i2c_dev *)file->private_data;
    /* 读取数据buffer */
    char b[100] = {0};
    int ret = 0;
    /* 从0地址开始读, 这里只是为了实验方便使用了read并且把地址写死了, 
       实际的应用中不应该在驱动中把地址写死, 可以尝试使用iotcl去实现灵活的方法 */
    ax_i2c_read_regs(dev, 0x00, b, 100 > size ? size : 100);

    /* 把读取到的数据拷贝到用户读取的地址 */
    ret = copy_to_user(buf, b, 100 > size ? size : 100);
    return 0;
}

/* write函数实现, 对应到Linux系统调用函数的write函数 */
static ssize_t ax_i2c_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    /* 获取私有数据 */
    struct ax_i2c_dev *dev = (struct ax_i2c_dev *)file->private_data;
    /* 写入数据的buffer */
    static char user_data[100] = {0};
    int ret = 0;
    /* 获取用户需要发送的数据 */
    ret = copy_from_user(user_data, buf, 100 > size ? size : 100);
    if(ret < 0)
    {
        printk("copy user data failed\r\n");
        return ret;
    } 
    /* 和读对应的从0开始写 */
    ax_i2c_write_regs(dev, 0x00, user_data, size);
    
    return 0;
}

/* release函数实现, 对应到Linux系统调用函数的close函数 */
static int ax_i2c_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* file_operations结构体声明 */
static const struct file_operations ax_i2c_ops = {
    .owner = THIS_MODULE,
    .open  = ax_i2c_open,
    .read  = ax_i2c_read,
    .write = ax_i2c_write,
    .release = ax_i2c_release,
};

/* probe函数实现, 驱动和设备匹配时会被调用 */
static int axi2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    printk("eeprom probe\r\n");
    /* 构建设备号 */
    alloc_chrdev_region(&axi2cdev.devid, 0, AX_I2C_CNT, AX_I2C_NAME);

    /* 注册设备 */
    cdev_init(&axi2cdev.cdev, &ax_i2c_ops);
    cdev_add(&axi2cdev.cdev, axi2cdev.devid, AX_I2C_CNT);

    /* 创建类 */
    axi2cdev.class = class_create(THIS_MODULE, AX_I2C_NAME);
    if(IS_ERR(axi2cdev.class))
    {
        return PTR_ERR(axi2cdev.class);
    }

    /* 创建设备 */
    axi2cdev.device = device_create(axi2cdev.class, NULL, axi2cdev.devid, NULL, AX_I2C_NAME);
    if(IS_ERR(axi2cdev.device))
    {
        return PTR_ERR(axi2cdev.device);
    }

    axi2cdev.private_data = client;

    return 0;
}

/* remove函数实现, 驱动卸载时会被调用 */
static int axi2c_remove(struct i2c_client *client)
{
    /* 删除设备 */
    cdev_del(&axi2cdev.cdev);
    unregister_chrdev_region(axi2cdev.major, AX_I2C_CNT);
    /* 注销类 */
    device_destroy(axi2cdev.class, axi2cdev.devid);
    class_destroy(axi2cdev.class);
    return 0;
}

/* of匹配表, 设备树下的匹配方式 */
static const struct of_device_id axi2c_of_match[] = 
{
    { .compatible = "ax-e2p1"},
    {/* sentinel */}
};

/* 传统的id_table匹配方式 */
static const struct i2c_device_id axi2c_id[] = {
    {"ax-e2p1"},
    {}
};

/* 声明并初始化i2c驱动 */
static struct i2c_driver axi2c_driver = {
    .driver = {
        .owner = THIS_MODULE,
        .name    = "ax-e2p1",
        /* 用of_match_table匹配 */
        .of_match_table = axi2c_of_match,
    },
    /* 使用传统的方式匹配 */
    .id_table = axi2c_id,
    .probe = axi2c_probe,
    .remove = axi2c_remove,
};

/* 驱动入口函数 */
static int __init ax_i2c_init(void)
{
    /* 在入口函数中调用i2c_add_driver, 注册驱动 */
    return i2c_add_driver(&axi2c_driver);
}

/* 驱动出口函数 */
static void __exit ax_i2c_exit(void)
{
    /* 在出口函数中调用i2c_add_driver, 卸载驱动 */
    i2c_del_driver(&axi2c_driver);
}

/* 标记加载、卸载函数 */ 
module_init(ax_i2c_init);
module_exit(ax_i2c_exit);

/* 驱动描述信息 */  
MODULE_AUTHOR("subomb");  
MODULE_ALIAS("pwm_led");  
MODULE_DESCRIPTION("I2C EEPROM driver");  
MODULE_VERSION("v1.0");  
MODULE_LICENSE("GPL"); 

4,IIC测试代码

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "assert.h"

int main(int argc, char *argv[])
{
    int fd, ret;
    char *filename;
    char buffer[3] = {0};

    if(argc != 2)
    {
        printf("Error Usage\r\n");
        return -1;
    }

    filename = argv[1];
    fd = open(filename, O_RDWR);
    if(fd < 0)
    {
        printf("file %s open failed\r\n", argv[1]);
        return -1;
    }

    /* 随便写入一些数据 */
    buffer[0] = 0x5A;
    buffer[1] = 0x55;
    buffer[2] = 0xAA;
    ret = write(fd, buffer, sizeof(buffer));
    if(ret < 0)
    {
        printf("write failed\r\n");
    }
    /* 在控制台打印写入的数据 */
    printf("write data %X, %X, %X\r\n", buffer[0], buffer[1], buffer[2]);

    /* 初始化buffer, 再用来读取数据 */
    memset(buffer, 0, sizeof(buffer));
    /* 稍作延时,否则可能会写失败 */
    usleep(4000);
    /* 读出数据 */
    ret = read(fd, buffer, sizeof(buffer));
    if(ret < 0)
    {
        printf("read failed\r\n");
    }
    /* 在控制台打印读出的数据 */
    printf("read data %X, %X, %X\r\n", buffer[0], buffer[1], buffer[2]);
    close(fd);
    return 0;
}

你可能感兴趣的:(IIC,驱动开发,linux)