迅为嵌入式linux驱动开发笔记(十)—IIC驱动实验

一、IIC简介

IIC介绍参照

二、应用层实现IIC

应用层操作 I2C 是以数据包进行交流的,所有在应用层就要进行封包的操作。 数据包对应的结构体是 i2c_rdwr_ioctl_data,这个结构体定义在 include\uapi\linux\i2c-dev.h 下面:定义如下:

/* This is the structure as used in the I2C_RDWR ioctl call */ 
struct i2c_rdwr_ioctl_data { 
		struct i2c_msg __user *msgs; /* pointers to i2c_msgs */ 
		__u32 nmsgs; /* number of i2c_msgs */
 }

第一个结构体成员是要发送的数据包的指针,第二个结构体成员发送数据包的个数。i2c_msg 结构体的定义,这个结构体是定义在 include\uapi\linux\i2c.h 下 面,定义如下:

 struct i2c_msg { 
		__u16 addr; /* slave address */ 
		__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_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */ 
		#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */ 
		#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 */ 
  }; 

结构体成员 addr 是从机的地址,flags 为读写标志位,如果flags为1,则为读,反之为0,则为写。len为buf的大小,单位是字节。
当flags为1是,buf就是我们要接收的数据,当flags为0时,就是要发送的数据。

应用层代码实现IIC

	#include <stdio.h>
	#include <sys/types.h> 
	#include <sys/stat.h> 
	#include <fcntl.h> 
	#include <unistd.h> 
	#include <linux/i2c.h> 
	#include <linux/i2c-dev.h> 
	#include <sys/ioctl.h> 
	int fd; 

//I2C 读函数 
 
 int i2c_read_date(unsigned int slave_addr,unsigned char reg_addr) {
		unsigned char data;
		struct i2c_rdwr_ioctl_data i2c_read_lcd; 
		int ret; 

	struct i2c_msg msg[2] = {
				[0] = { 
				.addr = slave_addr, 
				.flags = 0, 
				.buf = &reg_addr, 
				.len = sizeof(reg_addr) }, 
				
				[1] = { 
				.addr = slave_addr, 
				.flags = 1, 
				.buf = &data, 
				.len = sizeof(data) 
				},
		}; 
				 i2c_read_lcd.msgs = msg; 
				 i2c_read_lcd.nmsgs = 2; 
 
 ret = ioctl(fd, I2C_RDWR,&i2c_read_lcd); 
 
 if(ret < 0){
				perror("i2c ioctl error:"); 
				return ret; 
 				} 

return data; 
} 


int main(int argc,char *argv[]) {
		 int GEST_ID;
		 
		 fd = open("/dev/i2c-1", O_RDWR); 
		 
		 if(fd < 0){ 
		 
		 perror("open i2c error:"); 
		 
		 return fd; 
 		} 

while(1)
{ 	
		GEST_ID = i2c_read_date(0x38,0x01); 
		
		printf("GEST_ID value is %#x\n",GEST_ID);
		
		sleep(1); 
} 

	close(fd); 
	
	return 0; 
}

IIC驱动开发

一. Linux I2C 驱动框架简介

Linux 中的 I2C 也是按照平台总线模型设计的,既然也是按照平台总线模型设计分为一个 device 和一个 driver,但是I2C这里的 device 不叫 device,而是叫 client。在讲platform的时候就说过,platform是虚拟出来的一条总线,目的是为了实现总线、设备、驱动框架。对于I2C而言,不需要虚拟出一条总线,直接使用 I2C 总线即可。同样,也是先从非设备树开始,先来看一下,在没有设备树之前是怎么实现的 I2C 的 device 部分,也就是 client 部分。然后在学习有了设备树之后,再看client 是怎么编写的。按照 Linux 的发展路径来学习。 在没有使用设备树之前,使用的是i2c_board_info这个结构体来描述一个I2C设备 的,i2c_board_info 这个结构体如下:

struct i2c_board_info { 
		char type[I2C_NAME_SIZE]; /* I2C 设备名字 */ 
		unsigned short flags; /* 标志 */ 
		unsigned short addr; /* I2C 器件地址 */ 
		void *platform_data; 
		struct dev_archdata *archdata; 
		struct device_node *of_node; 
		struct fwnode_handle *fwnode; 
		int irq; 
};

在这个结构体里面, type 和 addr 这两个成员变量是必须要设置的,一个是 I2C 设备的名字,这个名字就是用来进行匹配用的,一个是I2C设备的器件地址。也可以使用宏:

#define I2C_BOARD_INFO(dev_type, dev_addr) \
 . type = dev_type, .addr = (dev_addr)

可以看出,I2C_BOARD_INFO宏其实就是设置 i2c_board_info 的 type 和 addr 这 两个成员变量。

I2C 设备和驱动的匹配过程是由 I2C 核心来完成的,在 Linux 源码的drivers/i2c/i2c core.c 就是I2C的核心部分,I2C核心提供了一些与具体硬件无关的 API 函数,如下:

1、 i2c_get_adapter

函数作用:获得一个I2C适配器原型:

struct i2c_adapter *i2c_get_adapter(int nr);

参数:要获得的哪个 I2C 适配器的编号
返回值:失败返回 NULL

2、i2c_put_adapter 函数

作用:释放 I2C 适配器
原型:

void i2c_put_adapter(struct i2c_adapter *adap);

参数:要释放的 I2C 适配器
返回值:失败返回 NULL

3 、 i2c_new_device 函数

作用:把 I2C 适配器和 I2C 器件关联起来。
原型:

i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info); 

参数:
struct i2c_adapter *adap : I2C 适配器
struct i2c_board_info const *info : i2c_board_info 的指针
返回值:失败返回 NULL;

4、i2c_unregister_device 函数

作用:注销一个 client 。
原型:

void i2c_unregister_device(struct i2c_client *client) 

参数: i2c client 的指针。
返回值:失败返回 NULL

设备树开发

在使用了设备树以后,就不用这么复杂了,使用设备树的时候只要在对应的 I2C 节点下创 建相应设备的节点即可,比如想添加一个触摸芯片 FT5X06 的设备,就可以在对应的 I2C 的节点下这样写,如下图所示:

&i2c2 { 
		clock_frequency = <100000>; 
		pinctrl-names = "default"; 
		pinctrl-0 = <&pinctrl_i2c2>; 
		status = "okay"; 
		edt-ft5x06@38 { 
					compatible = "edt,edt-ft5306", "edt,edt-ft5x06", "edt,edt-ft5406"; 
					pinctrl-names = "default"; 
					pinctrl-0 = <&ts_int_pin &ts_reset_pin>; 
					reg = <0x38>; 
					interrupt-parent = <&gpio1>; 
					interrupts = <9 0>; 
					reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>; 
					irq-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>; 
					status = "okay"; 
		}; 
}

然后再来看 driver 部分。 不管是使用设备树还是非设备树, driver 部分就比较复杂了。 和注册一个杂项设备或者是字符设备的套路一样,也是要先定一i2c_driver 的结构体,然后在对他进行初始化,来看一下这个结构体的定义,如下图所示:

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 *); /* Alert callback, for example for the SMBus alert protocol. * The format and meaning of the data value depends on the protocol. * For the SMBus alert protocol, there is a single bit of data passed * as the alert response's low bit ("event flag"). */ 
		void (*alert)(struct i2c_client *, 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; 
}; 

在驱动注册之前 i 2c_driver 结构体需要被正确地初始化,有 4 个成员要求必须被初始化,其中id_table不管用不用设备树都要被初始化,否则不能匹配成功:

static struct i2c_driver xxx_driver = 
{ 
	.driver = { . name = "xxx", }, 
	.probe = xxx_probe, 
	.remove = xxx_remove, 
	.id_table = xxx_table, 
}; 

初始化完成以后就是把 i2c_driver 注册进内核,注册进内核使用是i2c_add_driver 。来看一下这个函数:

1、i2c_add_driver 函数作用:

注册一个 i2c 驱动 函数原型:

#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver) 

参数: struct i2c_driver 的指针。

返回值:失败返回负值

2、i2c_del_driver

函数作用:

删除一个 i2c 驱动 函数原型:

extern void i2c_del_driver(struct i2c_driver *); 

参数: struct i2c_driver 的指针

返回值:失败返回负值

实验课

IIC_client代码部分编写:
#include<linux/init.h>
#include<linux/module.h>
#include <linux/i2c.h>

//分配一个I2C适配器指针
struct i2c_adapter *i2c_ada;

//分配一个I2C_client指针
struct i2c_client *i2c_client;

//支持的IIC设备列表
struct i2c_board_info ft5x06_info[] = {
    
    //每一项代表一个IIC设备,这个设备的名字是ft5x06,器件地址是0x38
    {I2C_BOARD_INFO("ft5x06_test",0x38)},
    {}
};

static int ft5x06_client_init(void)
{
    
    //调用i2c_get_adapter,获得到iic总线,因为ft5x06挂在到iic 2总线,参数就是1。
    //把这个触摸芯片挂在到i2c上。
    i2c_ada = i2c_get_adapter(1);

    //把iic client 和iic器件关联起来
    i2c_client = i2c_new_device(i2c_ada,ft5x06_info);

    //释放iic控制器
    i2c_put_adapter(i2c_ada);
    
    printk("This is ft5x06_client\n");
    return 0;
}

static void ft5x06_client_exit(void)
{  
    i2c_unregister_device(i2c_client);
    printk("bye bye\n");
}

module_init(ft5x06_client_init);

module_exit(ft5x06_client_exit);

MODULE_LICENSE("GPL");

IIC总线实现driver驱动:
#include<linux/init.h>
#include<linux/module.h>
#include <linux/i2c.h>

static const struct i2c_device_id ft5x06_id_ts[] = {
    {"xxx",0},
    {}
};

static const struct of_device_id ft5x06_id[] = 
{
    {.compatible = "edt,edt-ft5306",0,},
    {.compatible = "edt,edt-ft5x06",0,},
    {.compatible = "edt,edt-ft5406",0,},
    {}

};

int ft5x06_probe(struct i2c_client *i2c_client,const struct i2c_device_id *id)
{
    printk("ft5x06_probe\n");

    //比如可以注册一个杂项设备。
    return 0;
}

int ft5x06_remove(struct i2c_client *i2c_client)
{
    return 0;
}

static struct i2c_driver ft5x06_driver = { 
		
		//必须实现如下四个功能
		//int (*probe)(struct i2c_client *, const struct i2c_device_id *); 

		//int (*remove)(struct i2c_client *); 

        //struct device_driver driver; 

		//const struct i2c_device_id *id_table;

        .driver = {
            .owner = THIS_MODULE,
            .name = "ft5x06_test",
            .of_match_table = ft5x06_id,
        },
        .probe = ft5x06_probe,
        .remove = ft5x06_remove,
        .id_table = ft5x06_id_ts      
}; 

static int ft5x06_driver_init(void)
{
    
    int ret;

    ret = i2c_add_driver(&ft5x06_driver);
    if (ret < 0)
    {
        printk("i2c_add_driver is error\n");
        return ret;
    }
    printk("i2c_add_driver init\n");
    return 0;
}

static void ft5x06_driver_exit(void)
{
    
    i2c_del_driver(&ft5x06_driver);

    printk("i2c_del_driver exit\n");

}

module_init(ft5x06_driver_init);

module_exit(ft5x06_driver_exit);

MODULE_LICENSE("GPL");

驱动程序实现IIC通信:

#include<linux/init.h>
#include<linux/module.h>

#include <linux/i2c.h>

static struct i2c_client *ft5x06_client;

static void ft5x06_write_reg(u8 reg_addr,u8 data,u8 len);

static void ft5x06_write_reg(u8 reg_addr,u8 data,u8 len)
{
    u8 buff[256];

    struct i2c_msg msgs[] = {
            [0] = {
            .addr = ft5x06_client->addr,
            .flags = 0,
            .len = len+1,
            .buf = buff,
            }
    };

    buff[0] = reg_addr;//存放寄存器的地址

    memcpy(&buff[1],&data,len);

    i2c_transfer(ft5x06_client->adapter,msgs,1); 
}

static int ft5x06_read_reg(u8 reg_addr)
{
    u8 data;

    struct i2c_msg msgs[] = {
        [0] = {
            .addr = ft5x06_client->addr,
            .flags = 0,
            .len = sizeof(reg_addr),
            .buf = &reg_addr,
        },

        [1] = {
            .addr = ft5x06_client->addr,
            .flags = 1,
            .len = sizeof(data),
            .buf = &data,
        }
    };

    i2c_transfer(ft5x06_client->adapter,msgs,2);

    return data;

}

static const struct i2c_device_id ft5x06_id_ts[] = {
    {"xxx",0},
    {}
};

static const struct of_device_id ft5x06_id[] = 
{
    {.compatible = "edt,edt-ft5306",0,},
    {.compatible = "edt,edt-ft5x06",0,},
    {.compatible = "edt,edt-ft5406",0,},
    {}

};

int ft5x06_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
    int ret;

    printk("ft5x06_probe\n");

    ft5x06_client = client;//因为我们要在别的地方用到client,所以我们要把他复制出来
    //注册一个杂项设备

    //往地址为0x80的寄存器写入数据0x4b
    ft5x06_write_reg(0x80,0x4b,1);
    //读取寄存器地址为0x80的数据。
    ret = ft5x06_read_reg(0x80);

    printk("ret is %#x",ret);
    return 0;
    
}

int ft5x06_remove(struct i2c_client *i2c_client)
{
    return 0;
}

static struct i2c_driver ft5x06_driver = { 
		//unsigned int class; 
		
		//int (*probe)(struct i2c_client *, const struct i2c_device_id *); 

		//int (*remove)(struct i2c_client *); 

        //struct device_driver driver; 

		//const struct i2c_device_id *id_table;

        .driver = {
            .owner = THIS_MODULE,
            .name = "ft5x06_test",
            .of_match_table = ft5x06_id,
        },

        .probe = ft5x06_probe,
        .remove = ft5x06_remove,
        .id_table = ft5x06_id_ts       
}; 

static int ft5x06_driver_init(void)
{
    
    int ret;

    ret = i2c_add_driver(&ft5x06_driver);
    if (ret < 0)
    {
        printk("i2c_add_driver is error\n");
        return ret;
    }
    printk("i2c_add_driver init\n");
    return 0;
}

static void ft5x06_driver_exit(void)
{
    
    i2c_del_driver(&ft5x06_driver);

    printk("i2c_del_driver exit\n");

}

module_init(ft5x06_driver_init);

module_exit(ft5x06_driver_exit);

MODULE_LICENSE("GPL");

至此,IIC驱动实验已经完成。

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值