【IIC子系统】IIC驱动框架解析(二)

文章目录

  • I2C相关的数据结构(结构体)
    • struct i2c_driver
    • struct i2c_device
    • struct i2c_device_id
    • struct i2c_adapter
  • 通信API
    • i2c_msg方式
      • 常用的数据传输函数
    • SMbus方式
      • 常用数据传输函数
  • 代码实例
    • i2c_msg版本的EEPROM驱动
      • 设备驱动
      • 主机驱动
    • SMbus版本的EEPROM驱动
    • 测试程序

Linux I2C设备驱动基本规范

I2C的驱动程序分为两个部分:

  • I2C主机驱动:I2C适配器的驱动程序
  • I2C设备驱动:I2C设备的驱动程序
  • I2C总线驱动:用户不用做这一部分(SOC原厂移植内核时适配)

I2C相关的数据结构(结构体)

struct i2c_driver

struct i2c_driver代表一个I2C设备驱动实体。其主要的数据成员如下:

struct i2c_driver {
	... 

	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *client, const struct i2c_device_id *id); <-----------(1)
	int (*remove)(struct i2c_client *client);								 <-----------(2)
	...
	
	struct device_driver driver;											 <-----------(3)
	const struct i2c_device_id *id_table;									 <-----------(4)
};

  • (1) 设备与驱动程序匹配之后,会调用该probe接口完成设备的初始化和注册。
  • (2) 卸载设备驱动时,会调用该接口完成设备注销和相关资源的释放。
  • (3) Linux设备驱动抽象结构。
  • (4) i2c设备id表,驱动程序中根据具体的设备ID来区分不同的设备。依次来达到兼容同种类型,不同型号的设备。
struct device_driver {
		const char	   *name;							<-----------(1)
		const struct of_device_id  *of_match_table;		<-----------(2)
}
  • (1) 设备驱动名称,Linux内核未支持DeviceTree之前,设备和驱动程序需要根据name进行匹配。
  • (2) 设备和驱动匹配类型表,设备驱动程序需要定义其支持的设备类型,并初始化该of_match_table。

struct i2c_device

struct i2c_client代表一个具体的I2C设备实体。其主要数据成员如下:

struct i2c_client {
	unsigned short addr;          <-----------(1)               
	char name[I2C_NAME_SIZE];	  <-----------(2)
	struct i2c_adapter *adapter;  <-----------(3)
};

  • (1) 设备芯片通信地址,默认为7bit,保存在addr的低7bit。
  • (2)设备名称。
  • (3) I2C设备所依赖的I2C适配器,该适配器用于完成具体的I2C物理信号通信。

struct i2c_device_id

struct i2c_device_id代表一种具体的i2c设备类型,设备与驱动匹配之后,会确定具体的设备类型。其数据成员如下:

struct i2c_device_id {
	char name[I2C_NAME_SIZE];                                     <-----------(1)      
	kernel_ulong_t driver_data;	/* Data private to the driver */  <-----------(2)
};

  • (1)设备类型名称。
  • (2)设备私有数据,数据类型为long,可以指向一个具体的整型,也可以指向一个指针。

struct i2c_adapter

struct i2c_adapter代表具体的I2C控制器,其完成I2C的物理信号通信。对于一个设备驱动,一般不会涉及到该数据结构,待到学习I2C架构时,再进行详细的分析。

通信API

I2C设备驱动与设备进行通信时,有两种方式可供选择:

  • (1) 基于i2c_msg方式;
  • (2) 基于SMbus方式。

i2c_msg方式

i2cmsg可以作为I2C传输的一个单元进行使用,通过将通信数据封装到i2cmsg中,之后再通过i2c_transfer完成驱动程序与设备的I2C通信。

struct i2c_msg 的定义如下:

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_DMA_SAFE    0x0200  /* the buffer of this message is DMA safe */
            /* makes only sense in kernelspace */
            /* userspace buffers are copied anyway */
  #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      */
};

常用的数据传输函数

int i2c_master_send(const struct i2c_client *client, const char *buf, int count)

函数参数和返回值含义如下:

  • client:I2C设备对应的 设备对应的 设备对应的 i2c_client。

  • buf:要发送的数据指针。

  • count:要发送的数据字节,小于64KB, i2c_msg的 len成员变量是一个 u16(无符号16位)类型的数据。

  • return: 负值:失败。其他非负数: 发送的字节数。

int i2c_master_recv(const struct i2c_client *client, char *buf, int count)

函数参数和返回值含义如下:

  • client:I2C设备对应的i2c_client。

  • buf:要接收的数据。

  • count:要接收的数据字节,小于 64KB,以为 i2c_msg的 len成员变量是一个u16(无 符号 16位)类型的数据。

  • return: 负值:失败。其他非负数: 发送的字节数。

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

函数参数参和返回值含义如下:

  • adap: 所使用的 I2C适配器, i2c_client会保存其对应的 i2c_adapter。

  • msgs:I2C要发送的一个或多消息。

  • num:消息数量也就是 msgs的数量

  • return: 负值,失败。其他非负:发送的数据数

该函数是适配器的核心驱动函数,需要是I2C适配器层的驱动中实现(SOC原厂完成)。

下面展示了一个读取寄存reg1上数据的示例。

struct i2c_msg msg[2];

msg[0].addr = client->addr;
msg[0].flags = 0;//写
msg[0].len = 1;
msg[0].buf = &reg1;

msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;//读
msg[1].len   = sizeof(buf);
msg[1].buf   = &buf[0];

i2c_transfer(client->adapeter, msg, 2);

上面定义了两个msg,第一个msg定义了将要读取的设备寄存器,第二个msg用于读取该寄存器中的数据。

下面展示了写寄存器的示例。

struct i2c_msg msg[2];

msg[0].addr = client->addr;
msg[0].flags = 0;//写
msg[0].len = 1;
msg[0].buf = &reg1;

i2c_transfer(client->adapeter, msg, 1);

SMbus方式

 SMbus是Intel基于I2C推出的一种通用的通信协议(System Management Bus),其可以认为是I2C的通信子集,其定义了一套I2C主-从设备之间通信的时序。SMbus与I2C的关系,可以类比与网络通信中的HTTP和TCP的关系,I2C提供的基本的通信规则,其上可以跑的是裸数据,而SMbus规定了数据的格式。Linux系统的I2C通信架构提供了关于SMbus的支持,在支持SMbus的适配器和I2C设备之间可以使用SMbus协议进行通信。具体的SMbus协议可以参考Linux内核文档。
SMbus提供丰富的通信接口,用于传输单字节、双字节、字节数据数组等数据单元。

常用数据传输函数

  1. 单字节数据传输:
extern s32 i2c_smbus_read_byte(const struct i2c_client *client);
extern s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value);
extern s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command);
extern s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command, u8 value);
  1. 双字节数据传输:
extern s32 i2c_smbus_read_word_data(const struct i2c_client *client,
            u8 command);
extern s32 i2c_smbus_write_word_data(const struct i2c_client *client,
            u8 command, u16 value);
  1. 字节数组数据传输:
extern s32 i2c_smbus_read_block_data(const struct i2c_client *client,
             u8 command, u8 *values);
extern s32 i2c_smbus_write_block_data(const struct i2c_client *client,
             u8 command, u8 length, const u8 *values);
    /* Returns the number of read bytes */
extern s32 i2c_smbus_read_i2c_block_data(const struct i2c_client *client,
             u8 command, u8 length, u8 *values);
extern s32 i2c_smbus_write_i2c_block_data(const struct i2c_client *client,
             u8 command, u8 length, const u8 *values);

几点注意事项:

  1. command代表具体的设备寄存器。

  2. Linux推荐尽可能的使用SMbus协议与设备进行I2C通信。

  3. 在使用SMbus进行通信之前,需要检查当前的适配器是否支持需要的SMbus操作,比如:

if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA
      | I2C_FUNC_SMBUS_I2C_BLOCK)) {
	dev_err(&adapter->dev, "doesn't support required functionality\n");
	return -EIO;
}

上面这段代码用于检查当前的adapter是否支持:I2C_FUNC_SMBUS_BYTE_DATAI2C_FUNC_SMBUS_I2C_BLOCK 这两项操作,如果不支持,返回EIO错误。

代码实例

参考:https://www.cnblogs.com/weirdo-xo/p/13210261.html

i2c_msg版本的EEPROM驱动

编译不一定能通过,但是结构是OK的。

设备驱动

#include 
#include 
#include 
#include 

static struct i2c_board_info at24cxx_info = {
		I2C_BOARD_INFO("at24c02", 0x50),
};

static struct i2c_client *at24cxx_client;


static int at24cxx_init(void)
{
	struct i2c_adapter *i2c_adapt;
	i2c_adapt = i2c_get_adapter(0);
	at24cxx_client = i2c_new_device(i2c_adapt, &at24cxx_info);
	i2c_put_adapter(i2c_adapt);

	return 0;
}

static void at24cxx_exit(void)
{
	i2c_unregister_device(at24cxx_client);
}


module_init(at24cxx_init);
module_exit(at24cxx_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("weirdo");
MODULE_DESCRIPTION("This file using to add i2c device for smart210");

主机驱动

编译不一定能通过,但是结构是OK的。

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

#define DEVICE_NAME		"at24cxx"
#define CLASS_NAME		"at24cxx"

static dev_t major;
static struct class *i2c_class;
static struct i2c_client *at24cxx_client;

static int at24cxx_read(struct file *filep, const char __user *buf, size_t size, loff_t *ppos)
{
	unsigned char data, addr;
	copy_from_user(&addr, buf, 1);			//get data from buf
	data = i2c_smbus_read_byte_data(at24cxx_client, addr);		//read data from device
	copy_to_user(buf, &data, 1);

	return 1;
}

static int at24cxx_write(struct file *filep, char __user *buf, size_t size, loff_t *ppos)
{
	unsigned char ker_buf[2];
	unsigned char data, addr;

	copy_from_user(ker_buf, buf, 2);
	addr = ker_buf[0];
	data = ker_buf[1];
	printk(": Write data = %c to addr = %c !\n");
	
	if(!i2c_smbus_write_byte_data(at24cxx_client, addr, data)){
		printk(": Success write data to device! \n");
		return 2;
	}else{
		printk(": Fail to write data to device! \n");
		return -EIO;
	}
}


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


static int __init at24cxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	at24cxx_client = client;
	major = register_chrdev(0, DEVICE_NAME, &at24cxx_fops);
	class = class_create(THIS_MODULE, CLASS_NAME);
	device_create(class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);

	return 0;
}

static int __exit at24cxx_remove(struct i2c_client *client)
{
	device_destroy(class, MKDEV(major, 0));
	class_destroy(class);
	unregister_chrdev(major, DEVICE_NAME);

	return 0;
}

static const struct i2c_device_id at24cxx_id_table[] = {
		{"at24c02", 0},
};

static struct i2c_driver at24cxx_driver = {
		.driver = {
				.name = "100ask",
				.owner = THIS_MODULE,
		},
		.probe = at24cxx_probe,
		.remove = __devexit_p(at24cxx_remove),
		.id_table = at24cxx_id_table,
};


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");
MODULE_AUTHOR("weirdo");
MODULE_DESCRIPTION("This module design for smart210 at24c02 i2c device");

SMbus版本的EEPROM驱动

代码不一定能编译过去,结构是OK的。

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

#define DEV_NAME	"at24c256"
#define DEV_ADDRW	0x50
#define DEV_ADDRR	0x50
#define DEV_SIZE	0x1000

struct sunxi_i2c_device
{
	struct i2c_client client;
	struct miscdevice misc;
	char name[8];
	unsigned char addr;
};


static ssize_t at24cxx_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
	int ret;
	char data;
	struct i2c_msg msg;
	unsigned char packet[3];

	struct sunxi_i2c_device *sunxi_dev;
	sunxi_dev = container_of(filp->private_data,
		struct sunxi_i2c_device,
		misc);

	ret = copy_from_user(&data, buf, 1);

	packet[0] = *ppos / 256;
	packet[1] = *ppos % 256;
	packet[2] = data;

	printk("[0]=%x [1]=%x [2]=%c \n", packet[0], packet[1], packet[2]);

	msg.addr = DEV_ADDRW;			//assign address for at24c256
	msg.buf = packet;
	msg.len = 3;
	msg.flags = 0;				//need to point where to write

	ret = i2c_transfer(sunxi_dev->client.adapter, &msg, 1);
	if(ret != 1)
		printk(": Failed to Write data to i2c device !\n");
	else
		printk(": Success Write data from i2c device !\n");

	return ret;
}


static ssize_t at24cxx_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
	int ret;
	char dat;
	unsigned char addr[2];
	struct i2c_msg msg[2];

	struct sunxi_i2c_device *sunxi_dev;
	sunxi_dev = container_of(filp->private_data,
		struct sunxi_i2c_device,
		misc);

	addr[0] = *ppos / 256;			//point the address to write
	addr[1] = *ppos % 256;

	msg[0].addr = DEV_ADDRW;		//assign address for at24c256
	msg[0].buf = addr;
	msg[0].len = 2;
	msg[0].flags = 0;			//need to point where to write

	msg[1].addr = DEV_ADDRR;		//fill the struct of msg
	msg[1].buf = &dat;
	msg[1].len = 1;
	msg[1].flags = I2C_M_RD;

	ret = i2c_transfer(sunxi_dev->client.adapter, msg, 2);
	if(ret != 2)
		printk(": Failed to Read data from i2c device !\n");
	else{
		ret = copy_to_user(buf, &dat, 1);
		if(ret < 0)
			printk(": Failed to copy data to user ! \n");
		printk(": Success Read data from i2c device !\n");
	}

	return ret;
}


loff_t at24cxx_llseek (struct file *filp, loff_t offset, int whence)
 {
	loff_t new_pos; 				//new offset
	loff_t old_pos = filp->f_pos; 	//old offset

	switch(whence){
	case SEEK_SET:
		new_pos = offset;
		break;
	case SEEK_CUR:
		new_pos = old_pos + offset;
		break;
	case SEEK_END:
		new_pos = DEV_SIZE + offset;
		break;
	default:
		printk(": Unknow whence !\n");
		return - EINVAL;
	}

	//check the argument
	if(new_pos < 0 || new_pos > DEV_SIZE){
		printk(": Set offset error !\n");
		return - EINVAL;
	}

	filp->f_pos = new_pos;
	//printk(": The new pos = %ul and offset = %d!\n", new_pos, offset);

	return new_pos; 				//return new offset
 }


static const struct file_operations i2c_fops= {
		.owner = THIS_MODULE,
		.read = at24cxx_read,
		.write = at24cxx_write,
		.llseek = at24cxx_llseek,
};

static const struct i2c_device_id at24_ids[] = {
		{.name = DEV_NAME, },
		{ },
};

static const struct of_device_id at24_dtids[] = {
		{.compatible = DEV_NAME, },
		{ },
};

MODULE_DEVICE_TABLE(i2c, at24_ids);
MODULE_DEVICE_TABLE(of, at24_dtids);


static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	int ret;
	struct sunxi_i2c_device *i2c_dev;

	i2c_dev = devm_kzalloc(&client->dev, sizeof(struct sunxi_i2c_device), GFP_KERNEL);
	i2c_set_clientdata(client, i2c_dev);

	i2c_dev->client = *client;
	i2c_dev->misc.name = DEV_NAME;
	i2c_dev->misc.minor = MISC_DYNAMIC_MINOR;
	i2c_dev->misc.fops = &i2c_fops;

	ret = misc_register(&i2c_dev->misc);
	printk(": Success probe the device of at24cxx !\n");

	return ret;
}


static int at24_remove(struct i2c_client *client)
{
	struct sunxi_i2c_device *dev;

	dev = i2c_get_clientdata(client);
	misc_deregister(&dev->misc);

	printk(": Remove device driver from system !\n");

	return 0;
}

struct i2c_driver at24_driver = {
		.driver = {
			.name = "i2c",
			.owner = THIS_MODULE,
			.of_match_table = at24_dtids,
		},
		.probe = at24_probe,
		.remove = at24_remove,
		.id_table = at24_ids,
};

module_i2c_driver(at24_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("WEIRDO");
MODULE_DESCRIPTION("This driver for h3-i2c !");

测试程序

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

#define DEV_NAME	"/dev/at24c256"

int main(void)
{
	int fd;
	char buf[1] = {'b'};

	fd = open(DEV_NAME, O_RDWR); //open device file
	if(fd == -1)
		return -1;
	printf(": Success open device file !\n");
	
	lseek(fd, 64, SEEK_CUR);
	read(fd, buf, sizeof(buf));
	printf("The data is: %c \n", buf[0]);

	buf[0] = 'a';
	write(fd, buf, sizeof(buf));	//write data to device file
	buf[0] = 'c';
    
    sleep(1);
	read(fd, buf, 1);
	printf("The data is %c \n", buf[0]);

	close(fd);
	
	return 0;
}

你可能感兴趣的:(嵌入式Linux驱动,驱动开发)