文章目录
一、linux内核i2c驱动框架
1.1 i2c适配器
linux内核将i2c适配器抽象成i2c_adapter,这是linux驱动框架里的最底层,即i.mx6ull这颗soc的i2c外设的驱动,在适配器中,最重要的一个是i2c通信算法,其被linux内核抽象成i2c_algorithm。i2c_adapter和i2c_algorithm见linux内核源码如下:
/*
* i2c_adapter is the structure used to identify a physical i2c bus along
* with the access algorithms necessary to access it.
*/
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 */
struct rt_mutex bus_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;
};
/**
* struct i2c_algorithm - represent I2C transfer method
* @master_xfer: Issue a set of i2c transactions to the given I2C adapter
* defined by the msgs array, with num messages available to transfer via
* the adapter specified by adap.
* @smbus_xfer: Issue smbus transactions to the given I2C adapter. If this
* is not present, then the bus layer will try and convert the SMBus calls
* into I2C transfers instead.
* @functionality: Return the flags that this algorithm/adapter pair supports
* from the I2C_FUNC_* flags.
* @reg_slave: Register given client to I2C slave mode of this adapter
* @unreg_slave: Unregister given client from I2C slave mode of this adapter
*
* The following structs are for those who like to implement new bus drivers:
* i2c_algorithm is the interface to a class of hardware solutions which can
* be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584
* to name two of the most common.
*
* The return codes from the @master_xfer field should indicate the type of
* error code that occurred during the transfer, as documented in the kernel
* Documentation file Documentation/i2c/fault-codes.
*/
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
};
i2c适配器驱动就是初始化结构体i2c_adapter,设置i2c通信算法i2c_algorithm的master_xfer函数,最后调用i2c_add_numbered_adapter或 i2c_add_adapter向系统注册i2c_adapter。
1.2 i2c设备驱动
i2c_driver是编写驱动重点处理的内容,
/**
* struct i2c_driver - represent an I2C device driver
* @class: What kind of i2c device we instantiate (for detect)
* @attach_adapter: Callback for bus addition (deprecated)
* @probe: Callback for device binding
* @remove: Callback for device unbinding
* @shutdown: Callback for device shutdown
* @alert: Alert callback, for example for the SMBus alert protocol
* @command: Callback for bus-wide signaling (optional)
* @driver: Device driver model driver
* @id_table: List of I2C devices supported by this driver
* @detect: Callback for device detection
* @address_list: The I2C addresses to probe (for detect)
* @clients: List of detected clients we created (for i2c-core use only)
*
* The driver.owner field should be set to the module owner of this driver.
* The driver.name field should be set to the name of this driver.
*
* For automatic device detection, both @detect and @address_list must
* be defined. @class should also be set, otherwise only devices forced
* with module parameters will be created. The detect function must
* fill at least the name field of the i2c_board_info structure it is
* handed upon successful detection, and possibly also the flags field.
*
* If @detect is missing, the driver will still work fine for enumerated
* devices. Detected devices simply won't be supported. This is expected
* for the many I2C/SMBus devices which can't be detected reliably, and
* the ones which can always be enumerated in practice.
*
* The i2c_client structure which is handed to the @detect callback is
* not a real i2c_client. It is initialized just enough so that you can
* call i2c_smbus_read_byte_data and friends on it. Don't do anything
* else with it. In particular, calling dev_dbg and friends on it is
* not allowed.
*/
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;
};
1.3 i2c设备驱动匹配过程
匹配方法:比较设备树I2C设备节点的 compatible 属性和适配器驱动的of_device_id 中的 compatible 属性是否相等。
设备树如下:
i2c1: i2c@021a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_I2C1>;
status = "disabled";
};
驱动的of_device_id如下:
static const struct of_device_id i2c_imx_dt_ids[] = {
{ .compatible = "fsl,imx1-i2c", .data = &imx1_i2c_hwdata, },
{ .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, },
{ .compatible = "fsl,vf610-i2c", .data = &vf610_i2c_hwdata, },
{ /* sentinel */ }
};
二、温湿度传感器htu21d
略,请自行看手册了解,本文重点为内核i2c驱动框架分析。后面有时间会补充。
三、htu21d设备驱动编写
3.1 修改设备树,增加htu21d节点
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
htu32d@40 {
compatible = "myiic,htu21d";
reg = <0x40>;
};
};
3.2 驱动编写
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/***************************************************************
文件名 : htu21d.c
作者 : 秦韦忠
版本 : V1.0
描述 : htu21d驱动程序
其他 : 无
***************************************************************/
#define htu21d_CNT 2
#define htu21d_NAME "htu21d"
#define SISEN_TEMP 0xe3
#define SISEN_HUMI 0Xe5
#define SISEN_SOFTWARE_RESET 0xfe
struct htu21d_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int major; /* 主设备号 */
void *private_data; /* 私有数据 */
unsigned short temprature; /* 温度数据 */
};
static struct htu21d_dev htu21ddev;
/*
* @description : 从htu21d读取多个寄存器数据
* @param - dev: htu21d设备
* @param - reg: 要读取的寄存器首地址
* @param - val: 读取到的数据
* @param - len: 要读取的数据长度
* @return : 操作结果
*/
static int htu21d_read_regs(struct htu21d_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;
/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr; /* htu21d地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg长度*/
/* msg[1]读取数据 */
msg[1].addr = client->addr; /* htu21d地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据*/
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度*/
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
/*
* @description : 向htu21d多个寄存器写入数据
* @param - dev: htu21d设备
* @param - reg: 要写入的寄存器首地址
* @param - val: 要写入的数据缓冲区
* @param - len: 要写入的数据长度
* @return : 操作结果
*/
static s32 htu21d_write_regs(struct htu21d_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->private_data;
b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */
msg.addr = client->addr; /* htu21d地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 1; /* 要写入的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
/*
* @description : 读取htu21d指定寄存器值,读取一个寄存器
* @param - dev: htu21d设备
* @param - reg: 要读取的寄存器
* @return : 读取到的寄存器值
*/
static unsigned char htu21d_read_reg(struct htu21d_dev *dev, u8 reg)
{
u8 data = 0;
htu21d_read_regs(dev, reg, &data, 1);
return data;
#if 0
struct i2c_client *client = (struct i2c_client *)dev->private_data;
return i2c_smbus_read_byte_data(client, reg);
#endif
}
/*
* @description : 向htu21d指定寄存器写入指定的值,写一个寄存器
* @param - dev: htu21d设备
* @param - reg: 要写的寄存器
* @param - data: 要写入的值
* @return : 无
*/
static void htu21d_write_reg(struct htu21d_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
htu21d_write_regs(dev, reg, &buf, 1);
}
/*
* @description : 读取htu21d的数据,读取原始数据,包括ALS,PS和IR, 注意!
* : 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
* @param - ir : ir数据
* @param - ps : ps数据
* @param - ps : als数据
* @return : 无。
*/
void htu21d_readdata(struct htu21d_dev *dev)
{
unsigned char i =0;
unsigned char temprature_buf[2];
//unsigned short temprature= 0;
unsigned short f_RetVal;
u8 ucLsb = 0;
u8 ucMsb = 0;
/* 读取温度数据 */
htu21d_read_regs(dev, SISEN_TEMP, temprature_buf, 2);
ucMsb = temprature_buf[0];
ucLsb = temprature_buf[1];
ucLsb &= 0xfc; //设置分辨率,最低两位为0,温度:14位;湿度:12位
f_RetVal = ucMsb * 256 + ucLsb;/*MSB=(MSB<<=8)+LSB;即将MSB移位到高8位*/
//dev->temprature = (175.72)*f_RetVal/65536-46.85;
dev->temprature = 175*f_RetVal/65536-46;
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int htu21d_open(struct inode *inode, struct file *filp)
{
filp->private_data = &htu21ddev;
/* 初始化htu21d */
htu21d_write_reg(&htu21ddev, SISEN_SOFTWARE_RESET, 0X03);
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t htu21d_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
short data[1];
long err = 0;
struct htu21d_dev *dev = (struct htu21d_dev *)filp->private_data;
htu21d_readdata(dev);
data[0] = dev->temprature;
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int htu21d_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* htu21d操作函数 */
static const struct file_operations htu21d_ops = {
.owner = THIS_MODULE,
.open = htu21d_open,
.read = htu21d_read,
.release = htu21d_release,
};
/*
* @description : i2c驱动的probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - client : i2c设备
* @param - id : i2c设备ID
* @return : 0,成功;其他负值,失败
*/
static int htu21d_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
/* 1、构建设备号 */
if (htu21ddev.major) {
htu21ddev.devid = MKDEV(htu21ddev.major, 0);
register_chrdev_region(htu21ddev.devid, htu21d_CNT, htu21d_NAME);
} else {
alloc_chrdev_region(&htu21ddev.devid, 0, htu21d_CNT, htu21d_NAME);
htu21ddev.major = MAJOR(htu21ddev.devid);
}
/* 2、注册设备 */
cdev_init(&htu21ddev.cdev, &htu21d_ops);
cdev_add(&htu21ddev.cdev, htu21ddev.devid, htu21d_CNT);
/* 3、创建类 */
htu21ddev.class = class_create(THIS_MODULE, htu21d_NAME);
if (IS_ERR(htu21ddev.class)) {
return PTR_ERR(htu21ddev.class);
}
/* 4、创建设备 */
htu21ddev.device = device_create(htu21ddev.class, NULL, htu21ddev.devid, NULL, htu21d_NAME);
if (IS_ERR(htu21ddev.device)) {
return PTR_ERR(htu21ddev.device);
}
htu21ddev.private_data = client;
return 0;
}
/*
* @description : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
* @param - client : i2c设备
* @return : 0,成功;其他负值,失败
*/
static int htu21d_remove(struct i2c_client *client)
{
/* 删除设备 */
cdev_del(&htu21ddev.cdev);
unregister_chrdev_region(htu21ddev.devid, htu21d_CNT);
/* 注销掉类和设备 */
device_destroy(htu21ddev.class, htu21ddev.devid);
class_destroy(htu21ddev.class);
return 0;
}
/* 传统匹配方式ID列表 */
static const struct i2c_device_id htu21d_id[] = {
{"myiic,htu21d", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id htu21d_of_match[] = {
{ .compatible = "myiic,htu21d" },
{ /* Sentinel */ }
};
/* i2c驱动结构体 */
static struct i2c_driver htu21d_driver = {
.probe = htu21d_probe,
.remove = htu21d_remove,
.driver = {
.owner = THIS_MODULE,
.name = "htu21d",
.of_match_table = htu21d_of_match,
},
.id_table = htu21d_id,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init htu21d_init(void)
{
int ret = 0;
ret = i2c_add_driver(&htu21d_driver);
return ret;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit htu21d_exit(void)
{
i2c_del_driver(&htu21d_driver);
}
/* module_i2c_driver(htu21d_driver) */
module_init(htu21d_init);
module_exit(htu21d_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("qwz");
3.3 编写测试应用
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd;
char *filename;
unsigned short temprature;
int ret = 0;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0) {
printf("can't open file %s\r\n", filename);
return -1;
}
while (1) {
ret = read(fd, &temprature, 1);
if(ret == 0) { /* 数据读取成功 */
//ir = databuf[0]; /* ir传感器数据 */
//als = databuf[1]; /* als传感器数据 */
//ps = databuf[2]; /* ps传感器数据 */
printf("temprature = %d\r\n", temprature);
}
usleep(200000); /*100ms */
}
close(fd); /* 关闭文件 */
return 0;
}
3.4 编写makefile
KERNELDIR :=/home/qinweizhong/linux/linux_alientek/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)
obj-m := htu21d.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
3.5 编译
前面几节已经写好htu21d设备驱动,测试app、makefile。
- htu21d设备驱动:该驱动文件调用更底层的适配器驱动,实现对htu21d的寄存器的读写,适配器驱动由nxp提供。编译出来的是一个.ko文件,即模块文件,后面通过insmod指令来挂载这个模块。
- 测试app:属于linux应用开发。
- makefile:指定linux源码路径,注意此linux源码的版本应与开发板的内核版本一致,该makefile用于编译htu21d设备驱动为linux的模块。
四、测试
执行insmod挂载后,将可以在/dev下看到htu21d设备
执行如下shell指令,将实时打印htu21d的温度数据,目前存在一个问题是linux内核的浮点数计算问题,所以没能显示温度值的小数部分。
./htu21dApp /dev/htu21d