Linux下的I2C驱动

发布时间 2023-07-25 11:30:55作者: 阿风小子

1. Linux中I2C需要编写的驱动?

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

  • I2C主机驱动:I2C适配器的驱动程序
  • I2C设备驱动:I2C设备的驱动程序
  • I2C总线驱动:用户不用做这一部分

I2C协议和SMBus协议:

	I2C (pronounce: I squared C) is a protocol developed by Philips. It is a slow two-wire protocol(variable speed, up to 400 kHz), with a high speed extension (3.4 MHz). It provides an inexpensive bus for connecting many types of devices with infrequent or low bandwidth communications needs. I2C is widely used with embedded systems. Some systems use variants that don't meet branding requirements, and so are not advertised as being I2C.
	SMBus (System Management Bus) is based on the I2C protocol, and is mostly a subset of I2C protocols and signaling. Many I2C devices will work on a SMBus, but some SMBus protocols add semantics beyond what is required to achieve I2C branding. Modern PC mainboards rely on SMBus. The most common devices connected through SMBus are RAM modules configured using I2C EEPROMs, and hardware monitoring chips. Because the SMBus is mostly a subset of the generalized I2C bus, you can use its protocols on many I2C systems. However, there are systems that don't meet both SMBus and I2C electrical constraints; and others which can't implement all the common SMBus protocol semantics or messages.
	If you write a driver for an I2C device, please try to use the SMBus commands if at all possible (if the device uses only that subset of the I2C protocol). This makes it possible to use the device driver on both SMBus adapters and I2C adapters (the SMBus command set is automatically translated to I2C on I2C adapters, but plain I2C commands can not be handled at all on most pure SMBus adapters).

总结为一句话:

  • SMBus是I2C协议的子集。

  • 如果要实现I2C的驱动程序,最好使用SMBus协议的驱动(与I2C兼容)。

**2. Linux驱动中的三个数据传输函数? **

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适配器层的驱动中实现。

更多的函数资料可以参考书籍或者官方给出的手册,我只是给出最常用和最基础的函数

**3. I2C驱动注册和驱动匹配 ? **

  • I2C驱动注册方式
static struct i2c_driver ioaccel_driver = {
    .driver = {
        .name = "mma8451",
        .owner = THIS_MODULE,
        .of_match_table = ioaccel_dt_ids,
    },
    .probe = ioaccel_probe,
    .remove = ioaccel_remove,
    .id_table = i2c_ids,
};


/**********************How to register I2C device ***********************************
      The i2c_add_driver() and i2c_del_driver() functions are used to register/unregister the driver. 
They are included in the init()/exit() kernel module functions. If the driver doesn´t do anything 
else in these functions use the module_i2c_driver() macro instead.
************************************************************************************/


static int __init i2c_init(void)
{
	return i2c_add_driver(&ioaccel_driver);
}
module_init(i2c_init);
static void __exit i2c_cleanup(void)
{
	i2c_del_driver(&ioaccel_driver);
}
module_exit(i2c_cleanup);

/* The module_i2c_driver() macro can be used to simplify the code above: */
module_i2c_driver(ioaccel_driver);
  • I2C驱动匹配
How to match I2C device , In your device driver create an array of struct of_device_id structures where 
you specify .compatible strings that should store the same value of the DT device node´s compatible property. 
The struct of_device_id is defined ininclude/linux/mod_devicetable.h as:
struct of_device_id {
    char name[32];
    char type[32];
    char compatible[128];
};
The of_match_table field (included in the driver field) of the struct i2c_driver is a pointer to the array of struct of_device_id structures that hold the compatible strings supported by the driver:
static const struct of_device_id ioaccel_dt_ids[] = {
    { .compatible = "fsl,mma8451", },
    { }
};
MODULE_DEVICE_TABLE(of, ioaccel_dt_ids);
The driver´s probe() function is called when the compatible field in one of the of_device_id entries matches with the compatible property of a DT device node. The probe() function is responsible of initializing the device with the configuration values obtained from the matching DT device node and also to register the device to the appropriate kernel framework. In your I2C device driver, you have also to define an array of struct i2c_device_id structures:
static const struct i2c_device_id mma8451_id[] = {
    { "mma8450", 0 },
    { "mma8451", 1 },
    { }
};
MODULE_DEVICE_TABLE(i2c, mma8451_id);
	The probe function ,The second argument of the probe() function is an element of this array related to your attached device:
static ioaccel_probe(struct i2c_client *client, const struct i2c_device_id *id)
You can use id->driver_data (which is unique to each device), for specific device data. For example, for the "mma8451" device, the driver_data will be 1.

​ The binding will happen based on the i2c_device_id table or device tree compatible string. The I2C core first tries to match the device by compatible string (OF style, which is device tree), and if it fails, it then tries to match device by id table.

关于miscdevice驱动的一些详细内容:

​ You need to create a private structure that will store the I2C I/O device specific information. In this driver, the first field of the private structure is a struct i2c_client structure used to handle the I2C device. The second field of the private structure is a struct miscdevice structure. The misc subsystem will automatically handle the open() function for you. Inside the automatically created open() function, it will tie your created struct miscdevice to the private struct ioexp_dev(filp) for the file that’s being opened. In this way in your write/read kernel callback functions you can recover the struct miscdevice structure, which will allow you to get access to the struct i2c_client that is included in the private struct ioexp_dev structure. Once you get the struct i2_client structure you can read/write each I2C specific device using the SMBus functions. The last field of the private structure is a char array that will hold the name of the I2C I/O device.

struct ioexp_dev {
struct i2c_client * client;
struct miscdevice ioexp_miscdevice;
char name[8]; /* ioexpXX */
};
/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:	the pointer to the member.
 * @type:	the type of the container struct this is embedded in.
 * @member:	the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({			\
	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
	(type *)( (char *)__mptr - offsetof(type,member) );})

**5. 关于I2C地址的坑 ? **

​ 使用I2C时,地址配置是有一定范围的,不是在一个字节[0x00-0xff]内随便配置的。我个人建议不要使用十六进制中的a-f这几个符号表示地址,完全使用数字格式表示地址。在我创建的i2c设备中,没有包含英文字母的设备被创建成功过。可以参考我给出的PDF资料。

​ 关于AT24C256的地址,其实这个地址也是一个坑,在AT24C256的芯片手册中可以看到如下的地址描述:

第七位 第六位 第五位 第四位 第三位 第二位 第一位 第零位
1 0 1 0 A2 A1 A0 R/W

​ 虽然芯片手册上说的是R/W = 1时代表读操作,R/W=0时代表写操作。但是其实这是没有用的,我最初严格按照这个标准来做,但是发现根本不能读取/写入数据。后来发现这个地址中其实R/W位不能够算入,我们需要把R/W位去掉,把第八位设置为0,从第八位到第一位的便是AT24C256的地址。

6. 驱动实例?

  • 旧版本的I2C驱动程序(编译可能有问题,但结构是这样)
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>

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");
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/slab.h>


#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("<kernel>: Write data = %c to addr = %c !\n");
	
	if(!i2c_smbus_write_byte_data(at24cxx_client, addr, data)){
		printk("<kernel>: Success write data to device! \n");
		return 2;
	}else{
		printk("<kernel>: 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");

第一段代码描述的是一个设备,再第二段代码中描述的就是与设备匹配的具体驱动程序。

  • 新版本的I2C驱动程序
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>

#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("<kernel>: Failed to Write data to i2c device !\n");
	else
		printk("<kernel>: 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("<kernel>: Failed to Read data from i2c device !\n");
	else{
		ret = copy_to_user(buf, &dat, 1);
		if(ret < 0)
			printk("<kernel>: Failed to copy data to user ! \n");
		printk("<kernel>: 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("<kernel>: Unknow whence !\n");
		return - EINVAL;
	}

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

	filp->f_pos = new_pos;
	//printk("<kernel>: 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("<kernel>: 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("<kernel>: 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<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<termios.h>
#include<string.h>

#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("<user>: 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;
}