一、概述
I2C
子系统:作用是完成驱动和设备的匹配。
MISC
子系统:作用是简化字符设备注册过程。
I2C
控制器驱动使用 platform
总线驱动框架。
I2C
设备驱动使用 I2C
总线驱动框架。
二、I2C 协议
I²C(IIC)总线协议详解—完整版 - 知乎 (zhihu.com)
1、I2C 写时序
2、I2C 读时序
三、Linux I2C 驱动框架中的重要对象
I2C
驱动分为 2
部分:I2C
控制器驱动(也叫 I2C
适配器驱动)和 I2C
设备驱动。
1、I2C 总线
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
EXPORT_SYMBOL_GPL(i2c_bus_type);
I2C
总线主要作用是匹配 I2C
设备和 I2C
驱动。
2、I2C 控制器驱动
/*
* 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_adapter
表示 I2C
控制器驱动,用于驱动 SOC
集成的 I2C
控制器。
/**
* 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
};
struct i2c_algorithm
表示 I2C
时序规则。
3、I2C 驱动
/**
* 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;
};
struct i2c_driver
表示 I2C
控制器外接的 I2C
设备的驱动程序。
4、I2C 设备
/**
* struct i2c_client - represent an I2C slave device
* @flags: I2C_CLIENT_TEN indicates the device uses a ten bit chip address;
* I2C_CLIENT_PEC indicates it uses SMBus Packet Error Checking
* @addr: Address used on the I2C bus connected to the parent adapter.
* @name: Indicates the type of the device, usually a chip name that's
* generic enough to hide second-sourcing and compatible revisions.
* @adapter: manages the bus segment hosting this I2C device
* @dev: Driver model device node for the slave.
* @irq: indicates the IRQ generated by this device (if any)
* @detected: member of an i2c_driver.clients list or i2c-core's
* userspace_devices list
* @slave_cb: Callback when I2C slave mode of an adapter is used. The adapter
* calls it to pass on slave events to the slave driver.
*
* An i2c_client identifies a single device (i.e. chip) connected to an
* i2c bus. The behaviour exposed to Linux is defined by the driver
* managing the device.
*/
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* the adapter we sit on */
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
};
struct i2c_client
结构体描述 I2C
控制器外接的 I2C
设备的相关硬件信息。
5、I2C传输数据
/**
* struct i2c_msg - an I2C transaction segment beginning with START
* @addr: Slave address, either seven or ten bits. When this is a ten
* bit address, I2C_M_TEN must be set in @flags and the adapter
* must support I2C_FUNC_10BIT_ADDR.
* @flags: I2C_M_RD is handled by all adapters. No other flags may be
* provided unless the adapter exported the relevant I2C_FUNC_*
* flags through i2c_check_functionality().
* @len: Number of data bytes in @buf being read from or written to the
* I2C slave address. For read transactions where I2C_M_RECV_LEN
* is set, the caller guarantees that this buffer can hold up to
* 32 bytes in addition to the initial length byte sent by the
* slave (plus, if used, the SMBus PEC); and this value will be
* incremented by the number of block data bytes received.
* @buf: The buffer into which data is read, or from which it's written.
*
* An i2c_msg is the low level representation of one segment of an I2C
* transaction. It is visible to drivers in the @i2c_transfer() procedure,
* to userspace from i2c-dev, and to I2C adapter drivers through the
* @i2c_adapter.@master_xfer() method.
*
* Except when I2C "protocol mangling" is used, all I2C adapters implement
* the standard rules for I2C transactions. Each transaction begins with a
* START. That is followed by the slave address, and a bit encoding read
* versus write. Then follow all the data bytes, possibly including a byte
* with SMBus PEC. The transfer terminates with a NAK, or when all those
* bytes have been transferred and ACKed. If this is the last message in a
* group, it is followed by a STOP. Otherwise it is followed by the next
* @i2c_msg transaction segment, beginning with a (repeated) START.
*
* Alternatively, when the adapter supports I2C_FUNC_PROTOCOL_MANGLING then
* passing certain @flags may have changed those standard protocol behaviors.
* Those flags are only for use with broken/nonconforming slaves, and with
* adapters which are known to support the specific mangling options they
* need (one or more of IGNORE_NAK, NO_RD_ACK, NOSTART, and REV_DIR_ADDR).
*/
struct i2c_msg {
__u16 addr; /* slave address */
__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_msg
结构的变量,代表着一次单方向的传输。
6、小结
I2C
驱动有 4
个重要的东西,I2C
总线、I2C
驱动、I2C
设备、I2C
控制器驱动
I2C
总线:维护着两个链表(I2C
驱动、I2C
设备),管理I2C
设备和I2C
驱动的匹配和删除等。I2C
驱动:对应的就是I2C
设备的驱动程序。I2C
设备:是具体硬件设备的一个抽象。I2C
控制器驱动:用于I2C
驱动和I2C
设备间的通用,是SOC
上I2C
控制器驱动程序。
四、I2C 驱动相关API
I2C
相关 API
请参考 linux-4.1.15\include\linux\i2c.h
文件。
五、AP3216C 使用教程
AP3216C
是由敦南科技推出的一款传感器,其支持环境光强度(ALS
)、接近距离(PS
)和红外线强度(IR
)这三个环境参数检测。该芯片可以通过 IIC
接口与主控制相连,并且支持中断。其他详细信息见 AP3216C
官方参考手册。
1、AP3216C 结构框图
2、AP3216C 寄存器描述
六、AP3216C 驱动编写思路
1、熟悉 AP3216C 使用。
2、添加 I2C 总线驱动框架。
#include "linux/init.h"
#include "linux/module.h"
#include "linux/i2c.h"
/*
* @description : i2c驱动的probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - client : i2c设备
* @param - id : i2c设备ID
* @return : 0,成功;其他负值,失败
*/
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
return ret;
}
/*
* @description : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
* @param - client : i2c设备
* @return : 0,成功;其他负值,失败
*/
static int ap3216c_remove(struct i2c_client *client)
{
int ret = 0;
return ret;
}
/* 传统匹配方式ID列表 */
static const struct i2c_device_id ap3216c_id[] = {
{"lsc,ap3216c", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {
{ .compatible = "lsc,ap3216c" },
{ /* Sentinel */ }
};
/* 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,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init ap3216c_init(void)
{
int ret = 0;
ret = i2c_add_driver(&ap3216c_driver);
return ret;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit ap3216c_exit(void)
{
i2c_del_driver(&ap3216c_driver);
}
/* module_i2c_driver(ap3216c_driver) */
module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
3、使用MISC子系统注册字符设备驱动
#include "linux/init.h"
#include "linux/module.h"
#include "linux/printk.h"
#include "linux/i2c.h"
#include "linux/miscdevice.h"
#include "linux/fs.h"
#define NEWCHRDEV_MINOR 255 /* 次设备号(让MISC自动分配) */
#define NEWCHRDEV_NAME "ap3216c" /* 名子 */
typedef struct{
}newchrdev_t;
newchrdev_t newchrdev;
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int ap3216c_open(struct inode *inode, struct file *filp)
{
printk("ap3216c_open!\r\n");
filp->private_data = &newchrdev; /* 设置私有数据 */
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
printk("ap3216c_read!\r\n");
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int ap3216c_release(struct inode *inode, struct file *filp)
{
printk("ap3216c_release!\r\n");
return 0;
}
static const struct file_operations ap3216c_ops = {
.owner = THIS_MODULE,
.open = ap3216c_open,
.read = ap3216c_read,
.release = ap3216c_release,
};
/* MISC设备结构体 */
static struct miscdevice ap3216c_miscdev = {
.minor = NEWCHRDEV_MINOR,
.name = NEWCHRDEV_NAME,
.fops = &ap3216c_ops,
};
/*
* @description : i2c驱动的probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - client : i2c设备
* @param - id : i2c设备ID
* @return : 0,成功;其他负值,失败
*/
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
ret = misc_register(&ap3216c_miscdev);
if(ret < 0){
printk("ap3216c misc device register failed!\r\n");
ret = -EFAULT;
}
return ret;
}
/*
* @description : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
* @param - client : i2c设备
* @return : 0,成功;其他负值,失败
*/
static int ap3216c_remove(struct i2c_client *client)
{
int ret = 0;
/* MISC 驱动框架卸载 */
misc_deregister(&ap3216c_miscdev);
return ret;
}
/* 传统匹配方式ID列表 */
static const struct i2c_device_id ap3216c_id[] = {
{"lsc,ap3216c", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {
{ .compatible = "lsc,ap3216c" },
{ /* Sentinel */ }
};
/* 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,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init ap3216c_init(void)
{
int ret = 0;
ret = i2c_add_driver(&ap3216c_driver);
return ret;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit ap3216c_exit(void)
{
i2c_del_driver(&ap3216c_driver);
}
/* module_i2c_driver(ap3216c_driver) */
module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
4、添加ap3216c操作
1、初始化 ap3216c
设备。
2、获取 ap3216c
传感器相关数据。
七、添加设备树
1、imx6ull 中 I2C 控制器设备树添加规则
参考:linux-4.1.15/Documentation\devicetree\bindings\i2c\i2c-imx.txt
。
1、必须属性
compatible
:驱动和设备匹配字符串。
reg
:I2C
寄存器地址和长度。
interrupts
:I2C
中断配置。
clocks
:I2C
时钟配置。
clock-frequency
:I2C
总线时钟频率,以 Hz
为单位。
dmas
:DMA
说明符。
dma-names
:必须是 rx-tx
。
2、imx6ull 中 I2C 具体设备设备树添加规则
1、必须属性
compatible
:驱动和设备匹配字符串。
reg
:I2C
设备地址。
3、确定 AP3216C 使用的 I2C 引脚
通过原理图分析,AP3216C
连接在 I2C1
上,使用的接口为 UART4_TXD
和 UART4_RXD
。中断连接引脚为 GIO1_IO01
。
4、查找引脚定义是否冲突
5、添加 pinctrl 子系统相关配置
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
6、在 i2c1 节点追加 ap3216c 子节点
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
ap3216c@1e {
compatible = "lsc,ap3216c";
reg = <0x1e>;
};
};
7、编译设备树
onlylove@ubuntu:~/my/linux/linux-imx-4.1.15$ make dtbs
CHK include/config/kernel.release
CHK include/generated/uapi/linux/version.h
CHK include/generated/utsrelease.h
make[1]: 'include/generated/mach-types.h' is up to date.
CHK include/generated/bounds.h
CHK include/generated/asm-offsets.h
CALL scripts/checksyscalls.sh
DTC arch/arm/boot/dts/imx6ull-alientek-emmc.dtb
DTC arch/arm/boot/dts/imx6ull-alientek-nand.dtb
onlylove@ubuntu:~/my/linux/linux-imx-4.1.15$
8、测试
# pwd
/proc/device-tree/soc/aips-bus@02100000/i2c@021a0000
# ls
#address-cells clock-frequency interrupts pinctrl-names
#size-cells clocks name reg
ap3216c@1e compatible pinctrl-0 status
# cd ap3216c@1e/
# ls
compatible name reg
# cat compatible
lsc,ap3216c#
# cat name
ap3216c#
# pwd
/proc/device-tree/soc/aips-bus@02100000/i2c@021a0000/ap3216c@1e
#
八、驱动编写
1、Makefile
KERNELDIR := /home/onlylove/my/linux/linux-imx-4.1.15
CURRENT_PATH := $(shell pwd)
obj-m := ap3216c.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
2、ap3216c.c
#include "linux/init.h"
#include "linux/module.h"
#include "linux/printk.h"
#include "linux/i2c.h"
#include "linux/miscdevice.h"
#include "linux/fs.h"
#include "linux/delay.h"
#include "asm-generic/uaccess.h"
#define AP3216C_ADDR 0X1E /* AP3216C器件地址 */
/* AP3316C寄存器 */
#define AP3216C_SYSTEMCONG 0x00 /* 配置寄存器 */
#define AP3216C_INTSTATUS 0X01 /* 中断状态寄存器 */
#define AP3216C_INTCLEAR 0X02 /* 中断清除寄存器 */
#define AP3216C_IRDATALOW 0x0A /* IR数据低字节 */
#define AP3216C_IRDATAHIGH 0x0B /* IR数据高字节 */
#define AP3216C_ALSDATALOW 0x0C /* ALS数据低字节 */
#define AP3216C_ALSDATAHIGH 0X0D /* ALS数据高字节 */
#define AP3216C_PSDATALOW 0X0E /* PS数据低字节 */
#define AP3216C_PSDATAHIGH 0X0F /* PS数据高字节 */
#define NEWCHRDEV_MINOR 255 /* 次设备号(让MISC自动分配) */
#define NEWCHRDEV_NAME "ap3216c" /* 名子 */
typedef struct{
struct i2c_client *client; /* i2C设备 */
unsigned short ir, als, ps; /* 传感器数据 */
}newchrdev_t;
newchrdev_t newchrdev;
/*
* @description : 从ap3216c读取多个寄存器数据
* @param - dev: ap3216c设备
* @param - reg: 要读取的寄存器首地址
* @param - val: 读取到的数据
* @param - len: 要读取的数据长度
* @return : 操作结果
*/
static int ap3216c_read_regs(newchrdev_t *dev, unsigned char reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = dev->client;
/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr; /* ap3216c地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg长度*/
/* msg[1]读取数据 */
msg[1].addr = client->addr; /* ap3216c地址 */
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 : 向ap3216c多个寄存器写入数据
* @param - dev: ap3216c设备
* @param - reg: 要写入的寄存器首地址
* @param - val: 要写入的数据缓冲区
* @param - len: 要写入的数据长度
* @return : 操作结果
*/
static int ap3216c_write_regs(newchrdev_t *dev, unsigned char reg, unsigned char *buf, unsigned char len)
{
unsigned char b[256];
struct i2c_msg msg;
struct i2c_client *client = dev->client;
b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */
msg.addr = client->addr; /* ap3216c地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 1; /* 要写入的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
/*
* @description : 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!
* : 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
* @param - ir : ir数据
* @param - ps : ps数据
* @param - ps : als数据
* @return : 无。
*/
void ap3216c_read_data(newchrdev_t *dev)
{
unsigned char i =0;
unsigned char buf[6];
/* 循环读取所有传感器数据 */
for(i = 0; i < 6; i++)
{
ap3216c_read_regs(dev, AP3216C_IRDATALOW + i, buf+i, 1);
}
if(buf[0] & 0X80) /* IR_OF位为1,则数据无效 */
dev->ir = 0;
else /* 读取IR传感器的数据 */
dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
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] & 0X3F) << 4) | (buf[4] & 0X0F);
}
static int lcs_ap3216c_init(newchrdev_t *dev)
{
unsigned char buf = 0;
/* 1、复位ap3216c */
buf = 0x04;
ap3216c_write_regs(dev, AP3216C_SYSTEMCONG, &buf, 1);
/* 2、至少延时10ms */
mdelay(50);
/* 3、开启ALS、PS+IR */
buf = 0x03;
ap3216c_write_regs(dev, AP3216C_SYSTEMCONG, &buf, 1);
return 0;
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int ap3216c_open(struct inode *inode, struct file *filp)
{
/* 1、设置私有数据 */
filp->private_data = &newchrdev;
/* 2、初始化ap3216c */
lcs_ap3216c_init(&newchrdev);
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
short data[3];
long err = 0;
newchrdev_t *dev = (newchrdev_t*)filp->private_data;
ap3216c_read_data(dev);
data[0] = dev->ir;
data[1] = dev->als;
data[2] = dev->ps;
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int ap3216c_release(struct inode *inode, struct file *filp)
{
return 0;
}
static const struct file_operations ap3216c_ops = {
.owner = THIS_MODULE,
.open = ap3216c_open,
.read = ap3216c_read,
.release = ap3216c_release,
};
/* MISC设备结构体 */
static struct miscdevice ap3216c_miscdev = {
.minor = NEWCHRDEV_MINOR,
.name = NEWCHRDEV_NAME,
.fops = &ap3216c_ops,
};
/*
* @description : i2c驱动的probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - client : i2c设备
* @param - id : i2c设备ID
* @return : 0,成功;其他负值,失败
*/
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
/* 1、注册 MISC 子系统 */
ret = misc_register(&ap3216c_miscdev);
if(ret < 0){
printk("ap3216c misc device register failed!\r\n");
ret = -EFAULT;
}
/* 2、提取 i2c 设备 */
newchrdev.client = client;
return ret;
}
/*
* @description : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
* @param - client : i2c设备
* @return : 0,成功;其他负值,失败
*/
static int ap3216c_remove(struct i2c_client *client)
{
int ret = 0;
/* MISC 驱动框架卸载 */
misc_deregister(&ap3216c_miscdev);
return ret;
}
/* 传统匹配方式ID列表 */
static const struct i2c_device_id ap3216c_id[] = {
{"lsc,ap3216c", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {
{ .compatible = "lsc,ap3216c" },
{ /* Sentinel */ }
};
/* 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,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init ap3216c_init(void)
{
int ret = 0;
ret = i2c_add_driver(&ap3216c_driver);
return ret;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit ap3216c_exit(void)
{
i2c_del_driver(&ap3216c_driver);
}
/* module_i2c_driver(ap3216c_driver) */
module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
九、应用程序编写
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"
#include <errno.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int fd = 0;
int err = 0;
unsigned short databuf[3];
unsigned short ir, als, ps;
if(argc != 2){
printf("Error Usage!\r\n");
return -1;
}
fd = open(argv[1],O_RDWR);
if(fd < 0){
printf("Can't open file %s\r\n", argv[1]);
return -1;
}
while(1){
err = read(fd, databuf, sizeof(databuf));
if(err == 0){
ir = databuf[0];
als = databuf[1];
ps = databuf[2];
printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);
}
sleep(1);
}
close(fd);
// return 0;
}
十、测试
# pwd
/root
# ls
ap3216c.ko ap3216c_app
# ls -l /dev/ap3216c
ls: /dev/ap3216c: No such file or directory
# insmod ap3216c.ko
# ls -l /dev/ap3216c
crw-rw---- 1 root root 10, 56 Jan 1 06:43 /dev/ap3216c
# rmmod ap3216c.ko
# ls -l /dev/ap3216c
ls: /dev/ap3216c: No such file or directory
# insmod ap3216crandom: nonblocking pool is initialized
# insmod ap3216c.ko
# ls -l /dev/ap3216c
crw-rw---- 1 root root 10, 56 Jan 1 06:43 /dev/ap3216c
#
# ./ap3216c_app /dev/ap3216c
ir = 0, als = 0, ps = 0
ir = 6, als = 281, ps = 823
ir = 0, als = 277, ps = 883
ir = 6, als = 0, ps = 1023
ir = 16, als = 0, ps = 1023
ir = 0, als = 0, ps = 1023
ir = 0, als = 0, ps = 1023
ir = 0, als = 284, ps = 856
ir = 12, als = 386, ps = 831
ir = 30, als = 392, ps = 827
ir = 9, als = 388, ps = 828
ir = 0, als = 363, ps = 875
ir = 28, als = 384, ps = 881
ir = 0, als = 381, ps = 848
^C
#