目录
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_adapter
或 i2c_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 适配器驱动。
- 在
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_algorithm
为 i2c_imx_algo
,最后向Linux
内核注册 i2c_adapter
。
②、初始化 I2C1 控制器的相关寄存器。
其中 i2c_imx_algo
包含 I2C1 适配器与 I2C 设备的通信函数 master_xfer
,而 masetr_xfer
函数里面调用了i2c_imx_start
、 i2c_imx_read
、 i2c_imx_write
和 i2c_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;
};
type
和 addr
这两个成员变量是必须要设置的,一个是 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 = ® /* 发送要读取的首地址 */
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;
}