Linux字符设备驱动框架之IIC总线驱动框架

1、参考链接:https://www.cnblogs.com/linfeng-learning/p/9523046.html#_label2
2、参考文档:正点原子I.MX6U嵌入式Linux驱动开发指南

1、I2C简介

1)IIC物理总线的构成

IIC总线是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,最高传送速率100kbps。

2)IIC通信的特点

同步、串行、电平信号、低速率、近距离。

3)IIC通信时序

开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据;

结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据;

数据传输信号:在开始条件以后,时钟信号SCL的高电平周期期间,数据线SDA的数据有效,即数据可以被读走,开始进行读操作。在时钟信号SCL的低电平周期期间,数据线SDA的数据才允许改变。

应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。

在这里插入图片描述

2、I2C驱动框架

Linux内核中的IIC总线不同于SoC内部的物理IIC总线 ,内核中的IIC总线是虚拟出来的,目的是管理内核中的IIC从设备及其驱动。

Linux的I2C体系结构分为3个组成部分:

1)IIC核心

IIC 核心提供了IIC总线驱动和设备驱动的注册、注销方法、IIC通信方法(algorithm)上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。

2)IIC总线驱动(这一部分一般由SOC厂商编写好)
I2C 总线驱动就是 SOC 的 I2C 控制器驱动,也叫做 I2C 适配器驱动。

IIC总线驱动是对IIC硬件体系结构中适配器端(SoC内部的IIC总线控制器)的实现。IIC总线驱动主要包含了IIC适配器数据结构i2c_adapter,IIC适配器的通信方法数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数。经由IIC总线驱动的代码,我们可以控制IIC适配器以主控方式产生开始位,停止位,读写周期,以及以从设备方式被读写,产生ACK等。不同的CPU平台对应着不同的I2C总线驱动。

3)IIC设备驱动
I2C 设备驱动, I2C 设备驱动就是针对具体的 I2C 设备而编写的驱动。

IIC设备驱动是对IIC硬件体系结构中设备端的实现,与挂在I2C总线上的具体的设备通讯的驱动。通过I2C总线驱动提供的函数,设备驱动可以忽略不同IIC总线适配器的差异,不考虑其实现细节地与硬件设备通讯。这部分代码一般由驱动工程师完成。

2.1、I2C核心

IIC 核心提供了IIC总线驱动和设备驱动的注册、注销方法、IIC通信方法(algorithm)上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。
IIC核心的实现代码:/kernel/drivers/i2c/i2c-core.c。

2.1.1、I2C bus初始化

Linux内核初始化阶段,调用i2c_init() 函数来初始化IIC总线。

static int __init i2c_init(void)
{
	int retval;
	
	... ...
	
	retval = bus_register(&i2c_bus_type);  	/* 注册i2c总线 */
	if (retval)
		return retval;

	retval = i2c_add_driver(&dummy_driver);  /* 添加一个假的i2c从设备 */
	if (retval)
		goto class_err;
	return 0;
	
	... ...
	
class_err:
	bus_unregister(&i2c_bus_type);
	return retval;
}

i2c_init()函数中调用bus_register()来注册I2C总线,注册的I2C总线定义如下:

struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match,	/*match方法用来进行 device 和driver 	的匹配,
									在向总线注册设备或是驱动的的时候会调用此方法*/
	.probe		= i2c_device_probe, /*probe方法在完成设备和驱动的配对之后调用执行*/
	.remove		= i2c_device_remove, /*当注册i2c总线时会调用执行*/
	.shutdown	= i2c_device_shutdown,
};

IIC总线提供的match方法:match方法用来进行 i2c_driver 和 i2c_client 的匹配,在向总线注册i2c_driver或i2c_client的的时候会调用此方法。匹配的方法是有三种。其函数定义如下:

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;

	if (!client)
		return 0;

	/* 
	   第一种匹配方式,OF 类型的匹配,也就是设备树采用的匹配方式,
	   of_driver_match_device函数定义在文件 include/linux/of_device.h 
	   中。 device_driver 结构体(表示设备驱动)中有个名为of_match_table的成	
	   员变量,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点
	   的 compatible 属性会和 of_match_table 表中的所有成员比较,查看是否有	
	   相同的条目,如果有的话就表示设备和此驱动匹配。 
	   (即比较 I2C 设备节点的 compatible 属性和 of_device_id 中的	
	     compatible 属性是否相等,如果相当的话就表示 I2C
		 设备和驱动匹配。)
	*/
	/* Attempt an OF style match */
	if (of_driver_match_device(dev, drv))
		return 1;
	
	/* 
		第二种匹配方式, ACPI 匹配方式。
	*/
	/* Then ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	driver = to_i2c_driver(drv);

	/*
		第三种匹配方式, id_table 匹配,每个 platform_driver 结构体有一个 
		id_table 成员变量,顾名思义,保存了很多 id 信息。这些 id 信息存放着	
		这个 platformd 驱动所支持的驱动类型。
		用于传统的、无设备树的 I2C 设备和驱动匹配过程。比较 I2C 设备名字和 
		i2c_device_id 的 name 字段是否相等,相等的话就说明 I2C 设备和驱动匹
		配。
	*/
	/* match on an id table if there is one */
	if (driver->id_table)
		return i2c_match_id(driver->id_table, client) != NULL;

	return 0;
}

i2c_driver 和 i2c_client 匹配成功后,IIC 总线提供的 probe 方法将被调用执行,即执行 i2c_device_probe() 函数。实质上,最终调用执行的是IIC设备驱动中的probe函数,即 i2c_driver->probe。

static int i2c_device_probe(struct device *dev)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;
	int status;

	if (!client)
		return 0;

	if (!client->irq && dev->of_node) {
		int irq = of_irq_get(dev->of_node, 0);

		if (irq == -EPROBE_DEFER)
			return irq;
		if (irq < 0)
			irq = 0;

		client->irq = irq;
	}

	driver = to_i2c_driver(dev->driver);
	if (!driver->probe || !driver->id_table)
		return -ENODEV;

	if (!device_can_wakeup(&client->dev))
		device_init_wakeup(&client->dev,
					client->flags & I2C_CLIENT_WAKE);
	dev_dbg(dev, "probe\n");

	status = of_clk_set_defaults(dev->of_node, false);
	if (status < 0)
		return status;

	status = dev_pm_domain_attach(&client->dev, true);
	if (status != -EPROBE_DEFER) {
		status = driver->probe(client, i2c_match_id(driver->id_table,
					client));
		if (status)
			dev_pm_domain_detach(&client->dev, true);
	}

	return status;
}

2.1.2 IIC核心提供的接口函数

(1)增加/删除IIC总线适配器

/*增加一个IIC总线适配器*/
int i2c_add_adapter(struct i2c_adapter *adapter);

/*删除一个IIC总线适配器*/
int i2c_del_adapter(struct i2c_adapter *adap);

(2)增加/删除IIC从设备驱动

/*增加一个IIC从设备驱动*/
int i2c_add_driver(struct i2c_driver *driver);

/*删除一个IIC从设备驱动*/
void i2c_del_driver(struct i2c_driver *driver);

(3)IIC数据传输(详解位于下方)
i2c_transfer 函数

IIC总线上的数据传输是以字节为单位的,有读和写两种通信模式。IIC子系统为了实现这种通信方法,提供了 i2c_msg 结构,对于每一个START信号,都对应一个 i2c_msg 对象,实际操作中我们会将所有的请求封装成一个 struct i2c_msg[] ,一次性将所有的请求通过 i2c_transfer() 发送给匹配到的client的从属的adapter,由adapter根据相应的algo域以及 master_xfer 域通过主机驱动来将这些请求发送给硬件上的设备。

struct i2c_msg 
{
    __u16 addr;    //IIC从设备地址
    __u16 flags;  //操作标志位,I2C_M_RD为读(1),写为0

	#define I2C_M_TEN           0x0010    /* this is a ten bit chip address */
	#define I2C_M_RD            0x0001    /* read data, from slave to master */
	#define I2C_M_NOSTART       0x4000    /* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_REV_DIR_ADDR  0x2000    /* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_IGNORE_NAK    0x1000    /* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_NO_RD_ACK     0x0800    /* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_RECV_LEN      0x0400    /* length will be first received byte */

    __u16 len;      //传输的数据长度,字节为单位
    __u8 *buf;      //存放read或write的数据的buffer
};

2.2 IIC总线驱动

IIC总线驱动是对IIC硬件体系结构中适配器端(SoC内部的IIC总线控制器) 的实现。
IIC总线驱动主要包含了IIC适配器数据结构i2c_adapter,IIC适配器的通信方 法数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数。经由IIC总线驱动的代码,我们可以控制IIC适配器以主控方式产生开始位,停止位,读写周期,以及以从设备方式被读写,产生ACK等。不同的CPU平台对应着不同的I2C总线驱动。

Linux 内核将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter, i2c_adapter 结构体定义在 include/linux/i2c.h 文件中,结构体内容如下:

/*
 * 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;
	
	... ...
};

i2c_algorithm 类型的指针变量 algo,对于一个 I2C 适配器,肯定要对外提供读写 API 函数,设备驱动程序可以使用这些 API 函数来完成读写操作。 i2c_algorithm 就是 I2C 适配器与 IIC 设备进行通信的方法。
i2c_algorithm 结构体定义在 include/linux/i2c.h 文件中,内容如下(删除条件编译):

struct i2c_algorithm {
	/*
		I2C 适配器的传输函数,可以通过此函数来完成与 IIC 设备之间的通信 
	*/
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);

	/* 
		SMBUS 总线的传输函数
	*/
	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 *);
};

综上所述, I2C 总线驱动,或者说 I2C 适配器驱动的主要工作就是初始化 i2c_adapter 结构体变量,然后设置 i2c_algorithm 中的 master_xfer 函数。完成以后通过 i2c_add_numbered_adapteri2c_add_adapter 这两个函数向系统注册设置好的 i2c_adapter,这两个函数的原型如下:

int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)

这两个函数的区别在于 i2c_add_adapter 使用动态的总线号,而i2c_add_numbered_adapter 使用静态总线号。函数参数和返回值含义如下:
adapter 或 adap:要添加到 Linux 内核中的 i2c_adapter,也就是 I2C 适配器。
返回值: 0,成功;负值,失败。

如果要删除 I2C 适配器的话使用 i2c_del_adapter 函数即可,函数原型如下:

void i2c_del_adapter(struct i2c_adapter * adap)

函数参数和返回值含义如下:
adap:要删除的 I2C 适配器。
返回值: 无

2.2.1 I.MX6U 的 I2C 适配器驱动分析

I2C 适配器驱动一般都是 SOC 厂商去编写的,比如 NXP 就编写好了I.MX6U 的I2C 适配器驱动。

  1. imx6ull.dtsi 文件中找到 I.MX6U 的 I2C1 控制器节点,节点内容如下所示:
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";
			};

重点关注 i2c1 节点的 compitable 属性值,因为通过 compatible 属性值可以在 Linux 源码里面找到对应的驱动文件。这里i2c1节点的 compatible 属性值有两个:“fsl,imx6ul-i2c”和“fsl,imx21-i2c”,在 Linux 源码中搜索这两个字符串即可找到对应的驱动文件。 I.MX6U 的 I2C 适配器驱动驱动文件为 drivers/i2c/busses/i2c-imx.c ,在此文件中有如下内容:

static struct platform_device_id imx_i2c_devtype[] = {
	{
		.name = "imx1-i2c",
		.driver_data = (kernel_ulong_t)&imx1_i2c_hwdata,
	}, {
		.name = "imx21-i2c",
		.driver_data = (kernel_ulong_t)&imx21_i2c_hwdata,
	}, {
		/* sentinel */
	}
};
MODULE_DEVICE_TABLE(platform, imx_i2c_devtype);

static const struct of_device_id i2c_imx_dt_ids[] = {
	{ .compatible = "fsl,imx1-i2c", .data = &imx1_i2c_hwdata, },
	/*
		设备树中 i2c1 节点的 compatible 属性值就是与此匹配上的。
		因此 i2c-imx.c 文件就是 I.MX6U 的 I2C 适配器驱动文件。
	*/
	{ .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, },  
	{ .compatible = "fsl,vf610-i2c", .data = &vf610_i2c_hwdata, },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, i2c_imx_dt_ids);

... ...

static struct platform_driver i2c_imx_driver = {
	/*
		当设备和驱动匹配成功以后 i2c_imx_probe 函数就会执行, i2c_imx_probe 	
		函数就会完成 I2C 适配器初始化工作 
	*/
	.probe = i2c_imx_probe,  
	.remove = i2c_imx_remove,
	.driver	= {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
		.of_match_table = i2c_imx_dt_ids,
		.pm = IMX_I2C_PM,
	},
	.id_table	= imx_i2c_devtype,
};

static int __init i2c_adap_imx_init(void)
{
	return platform_driver_register(&i2c_imx_driver);
}
subsys_initcall(i2c_adap_imx_init);

static void __exit i2c_adap_imx_exit(void)
{
	platform_driver_unregister(&i2c_imx_driver);
}
module_exit(i2c_adap_imx_exit);

因此可以看出,I.MX6U 的 I2C 适配器驱动是个标准的 platform 驱动,由此可以看出,虽然 I2C 总线为别的设备提供了一种总线驱动框架,但是 I2C 适配器却是 platform 驱动。

i2c_imx_probe 函数主要的工作就是一下两点:
①、初始化 i2c_adapter,设置 i2c_algorithmi2c_imx_algo,最后向Linux
内核注册 i2c_adapter
②、初始化 I2C1 控制器的相关寄存器。

其中 i2c_imx_algo 包含 I2C1 适配器与 I2C 设备的通信函数 master_xfer ,而 masetr_xfer 函数里面调用了i2c_imx_starti2c_imx_readi2c_imx_writei2c_imx_stop等I2C的具体操作函数。

2.3、IIC设备驱动

2.3.1、I2C设备驱动解析

I2C 设备驱动重点关注两个数据结构: i2c_client 和 i2c_driver,i2c_client 就是描述设备信息的, i2c_driver 描述驱动内容,类似于 platform_driver。

i2c_client 结构体
i2c_client 结构体定义在 include/linux/i2c.h 文件中,内容如下:

struct i2c_client {
	unsigned short flags;		/* 标志		*/
	unsigned short addr;		/* 芯片地址,7位,存在低7位	*/
	char name[I2C_NAME_SIZE];	/* 名字 */
	struct i2c_adapter *adapter;	/* 对应的i2c适配器	*/
	struct device dev;		/* 设备结构体 */
	int irq;			/* 中断 */
	struct list_head detected;
	
	... ...
};

一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个 i2c_client。

i2c_driver 结构体
i2c_driver 类似 platform_driver,是我们编写 I2C 设备驱动重点要处理的内容, i2c_driver 结构体定义在 include/linux/i2c.h 文件中,内容如下:

struct i2c_driver {
	unsigned int class;

	int (*attach_adapter)(struct i2c_adapter *) __deprecated;

	/* Standard driver model interfaces */
	/*
		当i2c设备与驱动匹配成功后 probe 函数就会执行,和 platform 函数一样。
	*/
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);

	... ...
	
	/*
		device_driver 驱动结构体,如果使用设备树的话,需要设置 device_driver 
		的 of_match_table 成员变量,也就是驱动的兼容(compatible)属性。 
	*/
	struct device_driver driver;

	/*
		id_table 是传统的、未使用设备树的设备匹配 ID 表
	*/
	const struct i2c_device_id *id_table;

	... ...
	
	struct list_head clients;
};

** 注册 i2c_driver**

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
参数:
	owner: 一般为 THIS_MODULE。
	driver:要注册的 i2c_driver。
返回值:
	返回值: 0,成功;负值,失败。

注销 i2c_driver

void i2c_del_driver(struct i2c_driver *driver)
参数:
	driver:要注销的 i2c_driver。
返回值:
	返回值: 无。

2.3.2、I2C 设备驱动编写流程

I2C 适配器驱动 SOC 厂商已经替我们编写好了,我们需要做的就是编写具体的设备驱动。

1、i2c设备信息描述

1)未使用设备树的时候
首先肯定要描述 I2C 设备节点信息,先来看一下没有使用设备树的时候是如何在 BSP 里面描述 I2C 设备信息的,在未使用设备树的时候需要在 BSP 里面使用 i2c_board_info 结构体来描述一个具体的 I2C 设备。 i2c_board_info 结构体如下:

struct i2c_board_info {
	char		type[I2C_NAME_SIZE]; /* i2c设备名字 */
	unsigned short	flags;		/* 标志 */
	unsigned short	addr;		/* i2c器件地址 */
	void		*platform_data;
	struct dev_archdata	*archdata;
	struct device_node *of_node;
	struct fwnode_handle *fwnode;
	int		irq;
};

typeaddr 这两个成员变量是必须要设置的,一个是 I2C 设备的名字,一个是 I2C 设备的器件地址。I2C_BOARD_INFO 宏设置 i2c_board_info 的 type 和 addr 这两个成员变量。

Linux 源码里面全局搜索 i2c_board_info,会找到大量以 i2c_board_info 定义的 I2C 设备信息,这些就是未使用设备树的时候 I2C 设备的描述方式,当采用了设备树以后就不会再使用 i2c_board_info 来描述 I2C 设备了。

2)使用设备树的时候
使用设备树的时候 I2C 设备信息通过创建相应的节点就行了,比如 NXP 官方的 EVK 开发板在 I2C1 上接了 mag3110 这个磁力计芯片,因此必须在 i2c1 节点下创建 mag3110 子节点,然后在这个子节点内描述 mag3110 这个芯片的相关信息。打开 imx6ull-14x14-evk.dts 这个设备树文件,然后找到如下内容:

&i2c1 {
	clock-frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>;
	status = "okay";

	mag3110@0e {  /* 子节点名字,‘@’后面的 0e 就是其 i2c 器件地址 */
		compatible = "fsl,mag3110";
		reg = <0x0e>; /* 器件地址 */
		position = <2>;
	};
	
	... ...
};

2、i2c设备数据收发处理流程

I2C 设备驱动首先要做的就是初始化 i2c_driver 并向 Linux 注册。当设备与驱动匹配以后 i2c_driver 里面的 probe 函数就会执行,probe 函数里面所做的就是字符设备那一套了。一般需要在 probe 函数里面初始化 I2C 设备,要初始化 I2C 设备就必须能够对 I2C 设备寄存器进行读写操作,这里就要用到 i2c_transfer 函数了。 i2c_transfer 函数最终会调用 I2C 适配器中 i2c_algorithm 里面的 master_xfer 函数,对于 I.MX6U 而言就是
i2c_imx_xfer 这个函数。 i2c_transfer 函数原型如下:

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
参数:
	adap: 所使用的 I2C 适配器, i2c_client 会保存其对应的 i2c_adapter。
	msgs: I2C 要发送的一个或多个消息。
	num: 消息数量,也就是 msgs 的数量。
返回值: 
	负值,失败,其他非负值,发送的 msgs 数量

重点来看一下 msgs 这个参数,这是一个 i2c_msg 类型的指针参数, I2C 进行数据收发说白了就是消息的传递, Linux 内核使用 i2c_msg 结构体来描述一个消息。 i2c_msg 结构体定义在 include/uapi/linux/i2c.h 文件中,结构体内容如下:

struct i2c_msg {
	__u16 addr;	  /* 从机地址	*/
	__u16 flags;
	#define I2C_M_TEN		0x0010	/* this is a ten bit chip address */
	#define I2C_M_RD		0x0001	/* read data, from slave to master */
	#define I2C_M_STOP		0x8000	/* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_NOSTART */
	#define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */
	__u16 len;		/* msg length				*/
	__u8 *buf;		/* pointer to msg data			*/
};

使用 i2c_transfer 函数发送数据之前要先构建好 i2c_msg。

另外还有两个API函数分别用于I2C数据的收发操作,这两个函数最终都会调用i2c_transfer。
首先来看一下 I2C 数据发送函数 i2c_master_send,函数原型如下:

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)类型	的数据。
返回值:
	负值,失败,其他非负值,发送的字节数。

I2C 数据接收函数为 i2c_master_recv,函数原型如下:

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)类型的数据。
返回值:
	负值,失败,其他非负值,发送的字节数。

2.3.3、I2C设备驱动实例

具体设备:ap3216c

1、添加设备节点

imx6ull-alientek-emmc.dts文件中添加设备节点信息,如下:

1)I2C设备所使用的 IO 引脚:

pinctrl_i2c1: i2c1grp {
			fsl,pins = <
				MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
				MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
			>;
		};

2)添加 i2c1 的节点信息

&i2c1 {
	clock-frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>;
	status = "okay";

	ap3216c@1e {
		compatible = "alientek,ap3216c";
		reg = <0x1e>;
	};
};

2、驱动程序

ap3216c.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#include "ap3216creg.h"

#define AP3216C_NAME     "ap3216c"
#define AP3216C_CNT      1

/* 设备结构体 */
struct ap3216c_dev {
    dev_t devid;
    int major;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    /*
	 * private_data 成员变量用于存放 ap3216c 对应的 i2c_client
	 */
    void *private_data;  /* 私有数据 */
    unsigned short ir, als, ps; /* 三个光传感器数据 */
};

static struct ap3216c_dev ap3216cdev;

/* 
 * @description     : 从ap3216c读取多个寄存器数据
 * @param - dev     : ap3216c设备
 * @param - reg     : 要读取的寄存器首地址
 * @param - val     : 读取到的数据
 * @param - len     : 读取的字节数
 * @return          : 0:成功, 非0:失败
 */

/*
 * ap3216c_read_regs 函数实现多字节读取,但是 AP3216C 好像不支持连续多字节读取,此函数在测试其他 I2C 设备的时候可	
 * 以实现多给字节连续读取,但是在 AP3216C 上不能连续读取多个字节。不过读取一个字节没有问题的。
 */
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, unsigned char *val, int len)
{
    int ret = 0;
    struct i2c_msg msg[2];
    struct i2c_client *client = (struct i2c_client *)dev->private_data;

    /* msg[0]为发送要读取的首地址 */
    msg[0].addr = client->addr;     /* 器件地址 */
    msg[0].flags = 0;               /* 标记为发送数据,即写 */
    msg[0].buf = &reg;              /* 发送要读取的首地址 */
    msg[0].len = 1;                 /* 发送的长度为1(reg为1个字节) */

    /* msg[1]读取数据 */
    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(ret != 2){
        printk("i2c transfer failed!\r\n");
        return -EREMOTEIO;
    }

    return 0;
}


/* 
 * @description     : 向ap3216c写入多个寄存器数据
 * @param - dev     : ap3216c设备
 * @param - reg     : 要写入的寄存器首地址
 * @param - buf     : 要写入的缓冲区
 * @param - len     : 写入的字节数
 * @return          : 0:成功, 非0:失败
 */

static int ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, unsigned char *buf, int len)
{
    int ret = 0;
    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;    /* 器件地址 */
    msg.flags = 0;              /* 标记为发送 */
    msg.buf = b;                /* 要发送的数据 */
    msg.len = len + 1;          /* 发送的字节数 */

    ret = i2c_transfer(client->adapter, &msg, 1);
    if(ret != 1){
        printk("ap3216c write failed!\r\n");
        return -EREMOTEIO;
    }

    return 0;
}

/* 读取ap3216c指定寄存器值,读取一个寄存器 */
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
    u8 data;

    ap3216c_read_regs(dev, reg, &data, 1);

    return data;
}

/* 向ap3216c指定寄存器写入指定的值, 写一个寄存器 */
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{
    u8 buf = 0;
    buf = data;
    ap3216c_write_regs(dev, reg, &buf, 1);
}

/*
 * @description	: 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!
 *				: 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
 * @param - dev	: ap3216c设备
 * @return 		: 无。
 */
static void ap3216c_readdata(struct ap3216c_dev *dev)
{
    unsigned char i;
    unsigned char buf[6] = {0};     

    /* 循环读取所有传感器数据 */
    for(i = 0; i < 6; i++){
        buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);
    }

    if(buf[0] & 0X80) 	/* IR_OF位为1,则数据无效 */
        dev->ir = 0;
    else        /* 读取IR传感器数据 */
        dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0x3);
    
    dev->als = ((unsigned short)buf[3] << 8) | buf[2]; /* 读取ALS传感器数据 */

    if(buf[4] & 0X40) /* IR_OF位为1,则数据无效 */
        dev->ps = 0;
    else        /* 读取PS传感器数据 */
        dev->ps = ((unsigned short)buf[5] << 4) | (buf[4] & 0xf);
}


int ap3216c_open(struct inode *node, struct file *filp)
{
    filp->private_data = &ap3216cdev;

    /* 初始化ap3216c */
    ap3216c_write_reg(&ap3216cdev, AP3216c_SYSTEMCONG, 0x04);   /* 复位ap3216c */
    mdelay(50);         /* ap3216c复位最少10ms */
    ap3216c_write_reg(&ap3216cdev, AP3216c_SYSTEMCONG, 0x03);   /* 开启IR、ALS、PS */

    return 0;
}

static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data;
    unsigned short data[3] = {0};

    ap3216c_readdata(dev);

    data[0] = dev->ir;
    data[1] = dev->als;
    data[2] = dev->ps;

    ret = copy_to_user(buf, data, sizeof(data));
    if(ret < 0){
        printk("kernel read failed!\r\n");
        return -EINVAL;
    }

    return 0;
}

static int ap3216c_release(struct inode *node, struct file *filp)
{
    return 0;
}

/* file_operations操作集 */
static struct file_operations ap3216c_fops = {
    .owner = THIS_MODULE,
    .open = ap3216c_open,
    .read = ap3216c_read,
    .release = ap3216c_release,
};

/* 设备与驱动匹配成功后执行此函数 */
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret = 0;

    printk("i2c device and driver was matched!\r\n");

    /* 注册字符设备 */
    ap3216cdev.major = 0;
    if(ap3216cdev.major){
        ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);
        ret = register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);
    } else {
        ret = alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME);
    }
    if(ret < 0){
        printk("register chrdev failed!\r\n");
        ret = -EINVAL;
        goto fail_devid;
    }

    ap3216cdev.cdev.owner = THIS_MODULE;
    cdev_init(&ap3216cdev.cdev, &ap3216c_fops);
    ret = cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);
    if(ret < 0){
        printk("cdev add failed!\r\n");
        ret = -EINVAL;
        goto fail_cdev;
    }

    ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
    if(IS_ERR(ap3216cdev.class)){
        printk("class create failed!\r\n");
        ret = PTR_ERR(ap3216cdev.class);
        goto fail_class;
    }

    ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);
    if(IS_ERR(ap3216cdev.device)){
        printk("device create failed!\r\n");
        ret = PTR_ERR(ap3216cdev.device);
        goto fail_device;
    }

    ap3216cdev.private_data = client;

    return 0;

fail_device:
    class_destroy(ap3216cdev.class);
fail_class:
    cdev_del(&ap3216cdev.cdev);
fail_cdev:
    unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);
fail_devid:
    return ret;
}

/* 当注销i2c_driver时会执行此函数 */
static int ap3216c_remove(struct i2c_client *client)
{
    device_destroy(ap3216cdev.class, ap3216cdev.devid);
    class_destroy(ap3216cdev.class);
    cdev_del(&ap3216cdev.cdev);
    unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);
    return 0;
}

/* 传统方式列表 */
static const struct i2c_device_id ap3216c_id[] = {
    {"alientek,ap3216c", 0},
    { },
};

/* 设备树匹配表 */
static const struct of_device_id ap3216c_of_match[] = {
    {.compatible = "alientek,ap3216c"},
    { },
};

/* i2c驱动结构体 */
static struct i2c_driver ap3216c_driver = {
    .probe = ap3216c_probe,
    .remove = ap3216c_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "ap3216c",
        .of_match_table = ap3216c_of_match,
    },
    .id_table = ap3216c_id,
};

/* 入口 */
static int __init ap3216c_init(void)
{
    return i2c_register_driver(THIS_MODULE, &ap3216c_driver);  
}

/* 出口 */
static void __exit ap3216c_exit(void)
{
    i2c_del_driver(&ap3216c_driver);
}

module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YANG");

3、应用程序

ap3216cAPP.c

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

int main(int argc, char *argv[])
{
    int fd;
    char *filename;
    unsigned short databuf[3] = {0};
    unsigned short ir, als, ps;
    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("open file %s failed!\r\n", filename);
        return -1;
    }

    while(1){
        ret = read(fd, databuf, sizeof(databuf));
        if(ret < 0){
            printf("user read failed!\r\n");
            close(fd);
            return -1;
        }
        ir  = databuf[0];
        als = databuf[1];
        ps  = databuf[2];
        printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);
        usleep(200000);  /* 200ms */
    }

    close(fd);

    return 0;
}
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 深蓝海洋 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读