【linux驱动开发】i2c驱动框架之温湿度传感器htu21d

本文详细介绍了Linux内核I2C驱动框架,包括i2c适配器、设备驱动及匹配过程,并以温湿度传感器HTU21D为例,阐述了如何编写设备树、驱动程序、测试应用和Makefile。驱动匹配主要通过设备树中compatible属性与驱动的of_device_id比较完成。
摘要由CSDN通过智能技术生成

一、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 = &reg;					/* 读取的首地址 */
	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

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值