簡介
I2C是我們在單片機開發時時常會用到的通訊接口,用來與一些字符型設備進行通信,比如:陀螺儀、溫度傳感器等等,同樣的在Linux下I2C驅動也是十分重要的。有了操作系統的加持,我們不用像在32上那樣去軟件實現IIC協議,更多的是去學習Linux I2c的驅動框架,首先我們先來了解一下它的驅動框架,如下圖
Linux 的I2C體系結構主要分爲3個部分
- I2C核心
提供了I2C總線驅動和設備註冊、註銷方法,I2C通信方法(Algorithm)上層的與具體適配器無關的代碼以及探測設備、探測設備地址的上層代碼等 - I2C總線驅動
對I2C適配器端的實現,適配器可由CPU控制,甚至可以集成在CPU內部(一般CPU都會有I2C適配器)
I2C總線驅動中主要包含I2C適配器數據結構i2c_adapter、I2C適配器的Algorithm數據結構i2c_algorithm和控制I2C適配器產生通信信號的函數
經過 I2C總線驅動的代碼,我們可以控制I2C適配器產生開始位、停止位、讀寫週期以及讀寫、產生ACK等信號 - I2C設備驅動
也稱爲客戶驅動是對I2C硬件體系結構中設備端的實現,設備一般掛載在受CPU控制的I2C適配器上,通過I2C適配器與CPU交換數據
主要包含數據結構i2c_driver和i2c_client
文件結構
i2c相關的代碼主要存放在/driver/i2c
- i2c-core.c、i2c-core.h是I2C核心的實現
- I2C-dev.c 實現了I2C適配器設備文件的功能,每一個I2C適配器都被分配了一個設備
- busses文件夾
包含了各種廠家的I2C主機控制器(I2C適配器)的驅動
就比如我們使用的恩智浦家的i2c-imx.c - algos文件夾
實現了一些I2C總線適配器的通信方法
I2C總線驅動
I2C總線驅動重點是I2C適配器(也就是SOC的I2C接口控制器)
用到兩個重要的數據結構:i2c_adapter和i2c_algorithm
1、i2c_adapter
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;
};
2、i2c_algorithm
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
};
master_xfer是I2C適配器的傳輸函數,可以通過此函數來完成與IIC設備之間的通信
smbus_xfer就是SMBUS總線的傳輸函數
I2C總線驅動,或者說 I2C適配器驅動的主要工作就是初始化 i2c_adapter結構體變量,然後設置 i2c_algorithm中的 master_xfer函數。完成以後通過 i2c_add_numbered_adapter或 i2c_add_adapter這兩個函數向系統註冊設置好的 i2c_adapter
註冊函數:
int i2c_add_adapter(struct i2c_adapter *);
int i2c_add_numbered_adapter(struct i2c_adapter *);
刪除I2C適配器:
void i2c_del_adapter(struct i2c_adapter *);
一般SOC的I2C總線驅動都是由半導體廠商編寫的,不需要用戶去編寫,對於我們這些SOC使用者來說是被屏蔽掉的,我們主要專注於I2C設備驅動即可
I2C設備驅動
數據結構
主要關注兩個數據結構: i2c_client和i2c_driver
1、i2c_client
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
};
2、i2c_driver
類似於platform_driver,是我們編寫設備驅動重點要處理的內容
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;
};
這裏面也有設備和驅動匹配成功後會執行的probe函數
id_table是傳統的未使用設備樹的設備匹配ID表
對於我們I2C設備驅動編寫人來說,重點工作是構建i2c_driver
註冊i2c_driver,使用i2c_register_driver
int i2c_register_driver(struct module *, struct i2c_driver *);
也可以使用i2c_add_driver
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
實際也是調用i2c_register_driver
註銷i2c_driver,使用i2c_del_driver
void i2c_del_driver(struct i2c_driver *);
設備和驅動的匹配過程
I2C設備和驅動的匹配過程是由I2C核心來完成的,\drivers\i2c\i2c-core.c,I2C核心提供了一些與具體硬件無關的API函數
1、 i2c_adapter註冊 /註銷函數
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
void i2c_del_adapter(struct i2c_adapter * adap)
2、 i2c_driver註冊 /註銷函數
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
int i2c_add_driver (struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)
設備和驅動的匹配過程也是有I2C總線完成的,I2C總線的數據結構爲i2c_bus_type
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
i2c_device_match函數就是總線額定設備和驅動匹配函數
I2C適配器驅動分析
I2C適配器就是SOC的I2C控制器驅動
在設備樹中找到IMX6U的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";
};
重點關注compatible 屬性值,適配器驅動將根據這個來進行驅動和設備的匹配
在在\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, },
{ .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 = {
.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,
};
其中的i2c_imx_dt_ids就是根據設備樹進行匹配的匹配表
我們可以看出imx6的i2c適配器驅動是一個典型的platform驅動
在probe函數中的主要工作
- 初始化 i2c_adapter,設置 i2c_algorithm爲i2c_imx_algo,最後向 Linux內核註冊i2c_adapter
- 初始化I2C1控制器的相關寄存器
i2c_imx_algo包含I2C1適配器與I2C設備的通信函數,master_xfer
static struct i2c_algorithm i2c_imx_algo = {
.master_xfer = i2c_imx_xfer,
.functionality = i2c_imx_func,
};
functionality用於返回此I2C適配器支持什麼樣的通信協議,這裏的就是 i2c_imx_func
重點關注i2c_imx_xfer,最終就是通過此函數來完成與I2C設備通信
I2C設備驅動編寫流程
I2C設備描述信息
未使用設備樹時
在BSP裏面使用i2c_board_info結構體來描述一個具體的 I2C設備。
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr;
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來設置
#define I2C_BOARD_INFO(dev_type, dev_addr)
.type = dev_type, .addr = (dev_addr)
例子:static struct i2c_board_info mx27_3ds_i2c_camera = {
I2C_BOARD_INFO("ov2640", 0x30),
};
名字ov2640,期間地址0x30
使用設備樹以後
I2C設備描述信息通過創建對應的節點
例子:
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
mag3110@0e {
compatible = "fsl,mag3110";
reg = <0x0e>;
position = <2>;
};
ap3216c@1e {
compatible = "ap3216c";
reg = <0x1e>;
};
};
在I2C1上兩個節點mag3110和ap3216c,重點關注compatible和reg,一個用於匹配驅動,一個用於設置器件地址
I2C設備數據收發處理流程
I2C設備驅動首先要做的就是初始化 i2c_driver並向 Linux內核註冊。當設備和驅動匹配以後 i2c_driver裏面的 probe函數就會執行
一般需要在probe函數中初始化I2C設備,要初始化I2C設備就必須能夠對I2C設備寄存器進行讀寫操作,這裏就要用到 i2c_transfer函數了
i2c_transfer函數最終會調用I2C適配器中的master_xfer函數,對於imx6u來說就是 i2c_imx_xfer這個函數
原型如下:
int i2c_transfer(struct i2c_adapter *adap,
struct i2c_msg *msgs,
int num)
adap:所使用的I2C適配器,i2c_client會保存其對應的i2c_adapter
msgs:I2C要發送的一個或多個消息
num:消息數量
重點關注msgs這個參數,這是一個i2c_msg類型的指針,I2C進行數據收發就是消息的傳遞,Linux內核使用i2c_msg結構體來描述一個消息,結構體內容如下
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)*/
__u8 *buf; /* 消息數據*/
};
還有兩個API函數分別用於I2C數據的收發操作,這兩個函數最終都會調 i2c_transfer
int i2c_master_send(const struct i2c_client *client,
const char *buf,
int count);
int i2c_master_recv(const struct i2c_client *client,
char *buf,
int count);
注意count要小於64KB,因爲i2c_msg的len成員變量是一個u16類型的數據
實驗代碼及分析
我們要實現AP3216C的設備驅動, AP3216C是由DYNA IMAGE推出的一款傳感器,其支持環境光強度 (ALS)、接近距離 (PS)和紅外線強度 (IR)這三個環境參數檢測。該芯片可以通過IIC接口與主控相連,並支持中斷
實驗代碼
修改設備樹
1、pinctrl子系統
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
設置引腳爲I2C的功能
2、i2c1節點的設置
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
mag3110@0e {
compatible = "fsl,mag3110";
reg = <0x0e>;
position = <2>;
};
fxls8471@1e {
compatible = "fsl,fxls8471";
reg = <0x1e>;
position = <0>;
interrupt-parent = <&gpio5>;
interrupts = <0 8>;
};
ap3216c@1e {
compatible = "ap3216c";
reg = <0x1e>;
};
};
在i2c1節點中添加ap3216c節點
clock-frequency屬性爲I2C的頻率,這裏設置爲100KHZ
pinctrl-0屬性指定pinctrl節點
1e爲I2C設備的地址
驅動代碼
1、ap3216c_reg.h
存放器件相關寄存器地址
#ifndef AP3216C_REG_H
#define AP3216C_REG_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數據高字節 */
#endif
2、ap2116c_driver.c
#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>
#include "ap3216c_reg.h"
#define AP3216C_CNT 1
#define AP3216C_NAME "ap3216c"
struct ap3216c_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int major;
void *private_data;
unsigned short ir,als,ps;
};
static struct ap3216c_dev ap3216cdev;
static int ap3216c_read_regs(struct ap3216c_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] addr to read*/
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].buf = ®
msg[0].len = 1;
/*msg[1] read data*/
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)
{
ret = 0;
}
else
{
printk(KERN_EMERG "i2c read failed=%d reg %06x len=%d \n", ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, int 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);
msg.addr = client->addr;
msg.flags = 0;
msg.buf = b;
msg.len = len + 1;
return i2c_transfer(client->adapter, &msg, 1);
}
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
u8 data = 0;
ap3216c_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
}
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);
}
void ap3216c_readdata(struct ap3216c_dev *dev)
{
unsigned char i = 0;
unsigned char buf[6];
for(i=0; i<6; i++)
{
buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);
}
if(buf[0] & 0x80)
dev->ir = 0;
else //read IR data
dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0x03);
dev->als = ((unsigned short)buf[3] << 8) | buf[2]; //read ALS data
if(buf[4] & 0x40)
dev->ps = 0;
else //read PS data
dev->ps = ((unsigned short)(buf[5] & 0x3F) << 4) | (buf[4] & 0x0F);
}
static int ap3216c_open(struct inode *inode, struct file *filp)
{
filp->private_data = &ap3216cdev;
/*init AP3216C*/
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04);
mdelay(50);//at lease 10ms
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x03);
return 0;
}
static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
short data[3];
long err = 0;
struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data;
ap3216c_readdata(dev);
data[0] = dev->ir;
data[1] = dev->als;
data[2] = dev->ps;
err = copy_to_user(buf, data, sizeof(data));
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,
};
/*ap3216c_probe*/
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
/*1.get device id*/
if(ap3216cdev.major)
{
ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);
register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);
}
else
{
alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME);
ap3216cdev.major = MAJOR(ap3216cdev.devid);
}
/*2.register device*/
cdev_init(&ap3216cdev.cdev, &ap3216c_ops);
cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);
/*3.create class*/
ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
if(IS_ERR(ap3216cdev.class))
{
return PTR_ERR(ap3216cdev.class);
}
/*4.create device*/
ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);
if(IS_ERR(ap3216cdev.device))
{
return PTR_ERR(ap3216cdev.device);
}
ap3216cdev.private_data = client;
return 0;
}
/*ap3216c_remove*/
static int ap3216c_remove(struct i2c_client *client)
{
/*delete device*/
cdev_del(&ap3216cdev.cdev);
unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);
/*unregister class and device*/
device_destroy(ap3216cdev.class, ap3216cdev.devid);
class_destroy(ap3216cdev.class);
return 0;
}
/* 傳統匹配方式ID列表 */
static const struct i2c_device_id ap3216c_id[] = {
{"alientek,ap3216c", 0},
{}
};
/* 設備樹匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {
{ .compatible = "alientek,ap3216c" },
{ /* Sentinel */ }
};
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)
{
int ret = 0;
ret = i2c_add_driver(&ap3216c_driver);
return ret;
}
static void __exit ap3216c_exit(void)
{
i2c_del_driver(&ap3216c_driver);
}
module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("gyy");
應用代碼
#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>
int main(int argc, char *argv[])
{
int fd;
char *filename;
unsigned short databuf[3];
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("can't open file %s\r\n", filename);
return -1;
}
while (1) {
ret = read(fd, databuf, sizeof(databuf));
if(ret == 0) { /* 數據讀取成功 */
ir = databuf[0]; /* ir傳感器數據 */
als = databuf[1]; /* als傳感器數據 */
ps = databuf[2]; /* ps傳感器數據 */
printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);
}
usleep(200000); /*200ms */
}
close(fd); /* 關閉文件 */
return 0;
}
代碼分析
驅動部分
- 在init函數中我們調用i2c_add_driver來註冊一個驅動,傳入的參數就是i2c_driver 類型的i2c_add_driver,在i2c_add_driver中我們定義了probe函數、remove函數以及匹配表,當我們加載該驅動文件時會執行init函數,完成驅動和設備(driver和client)的匹配工作,當匹配成功時便會執行probe函數
- I2C設備的client一般放在我們自定義的設備結構體的private_data成員變量中,在probe函數中我們完成字符設備的註冊並將client寫入private_data
- I2C設備驅動實際上也是一個字符設備驅動所以自然我們也還需要file_operations結構體,在file_operations結構體中我們可以實現供用戶層調用的函數
- 在file_operations操作集合中我們使用i2c_transfer來完成數據的發送和讀取,這其實就是調用I2C適配器中的master_xfer函數,對於imx6u來說就是 i2c_imx_xfer這個函數,我們需要做的就是填寫i2c_msg結構體
- 在open函數中我們一般要完成I2C硬件設備的初始阿(一般通過配置寄存器)
- 在read、write函數中則會進行對數據的讀寫
- 具體的讀取以及初始化邏輯可能各個芯片是不相同的,我們重點要學習的是I2C的驅動框架,理解驅動的操作流程
應用程序部分
應用程序部分的邏輯比較簡單就是循環調用read函數從模塊讀取數據並打印
總結
在宋寶華老師的Linux設備驅動開發詳解中有這麼一張圖指明瞭I2C設備各個結構體的關係
仔細閱讀這個圖你會發現I2C設備的驅動框架就在你的心中展開