一,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结构体
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
通过 PS_IIC1_SDA 找到连接到芯片的引脚,把 I2C1 通道配置到 ps 端的 PS_MIO24,PS_MIO25, PS_MIO24两个引脚上。
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;
}