Linux I2C设备驱动基本规范
I2C的驱动程序分为两个部分:
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)
};
struct device_driver {
const char *name; <-----------(1)
const struct of_device_id *of_match_table; <-----------(2)
};
struct i2c_client代表一个具体的I2C设备实体。其主要数据成员如下:
struct i2c_client {
unsigned short addr; <-----------(1)
char name[I2C_NAME_SIZE]; <-----------(2)
struct i2c_adapter *adapter; <-----------(3)
};
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)
};
struct i2c_adapter代表具体的I2C控制器,其完成I2C的物理信号通信。对于一个设备驱动,一般不会涉及到该数据结构,待到学习I2C架构时,再进行详细的分析。
I2C设备驱动与设备进行通信时,有两种方式可供选择:
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 = ®1;
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 = ®1;
i2c_transfer(client->adapeter, msg, 1);
SMbus是Intel基于I2C推出的一种通用的通信协议(System Management Bus),其可以认为是I2C的通信子集,其定义了一套I2C主-从设备之间通信的时序。SMbus与I2C的关系,可以类比与网络通信中的HTTP和TCP的关系,I2C提供的基本的通信规则,其上可以跑的是裸数据,而SMbus规定了数据的格式。Linux系统的I2C通信架构提供了关于SMbus的支持,在支持SMbus的适配器和I2C设备之间可以使用SMbus协议进行通信。具体的SMbus协议可以参考Linux内核文档。
SMbus提供丰富的通信接口,用于传输单字节、双字节、字节数据数组等数据单元。
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);
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);
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);
几点注意事项:
command代表具体的设备寄存器。
Linux推荐尽可能的使用SMbus协议与设备进行I2C通信。
在使用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_DATA
和I2C_FUNC_SMBUS_I2C_BLOCK
这两项操作,如果不支持,返回EIO
错误。
参考:https://www.cnblogs.com/weirdo-xo/p/13210261.html
编译不一定能通过,但是结构是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");
代码不一定能编译过去,结构是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;
}