前言
`
不啰嗦,直接从驱动开讲,需要学习IIC协议基础知识的可以先看这篇文章:https://www.rstk.cn/news/369263.html?action=onClick
提示:需要了解 IIC 的帧格式、读写/命令模式才能彻底了解 i2c_msg 的使用。不想进半导体厂的驱动工程师直接看 设备驱动和设备驱动编写 !!!
IIC 驱动框架
IIC 驱动分为主机驱动、设备驱动,主机驱动是 SOC 上IIC控制器驱动,主机驱动由原厂BSP工程师编写;设备驱动是通过调用主机驱动提供的API从而驱动从机上的IIC设备。这个思维很重要,后面的 SPI、USB、以太网都是这样的。
总线驱动
类似 platform 总线,总线就是用来挂载设备的;但是又不同于 platform ,因为 IIC 总线是真实的总线。
IIC 总线驱动的重点就是完成 IIC 适配器(也叫IIC控制器)的驱动。
/* include/linux/i2c.h */
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* 总线访问算法,读写操作 */
void *algo_data;
/* data fields that are valid for all devices */
struct rt_mutex bus_lock;
int timeout; /* 单位为 jiffies */
int retries;
struct device dev;
int nr; /*i2c bus 编号, 若置为-1, 则代表动态分配*/
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
};
struct i2c_algorithm {
/* 如果adapter不能支持i2c访问, 则置 master_xfer 为NULL */
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
/* 如果adapter支持SMBus访问, 则设置smbus_xfer, 若 smbus_xfer 为NULL, 则使用I2C访问来模拟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);
u32 (*functionality) (struct i2c_adapter *); /* 用于查询i2c adapter支持那些function */
};
/* i2c_algorithm 中的通信函数以 ic2_msg 为通信的基本单位 */
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_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 */
#define I2C_M_NEED_DELAY 0x0020 // add by kfx
#define I2C_M_REG8_DIRECT 0x0040 // add by kfx
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
注册 i2c_adapter 的方法:
int i2c_add_numbered_adapter(struct i2c_adapter *adap); //使用竞态的总线号
int i2c_add_adapter(struct i2c_adapter *adapter); //使用动态的总线号
/* 返回值:0,成功;负值,失败。*/
删除 i2c_adapter 的方法:
void i2c_del_adapter(struct i2c_adapter *adap);
设备驱动
i2c_client 就是描述设备信息的,i2c_driver 描述驱动内容。
i2c_client 对应真实的物理设备,每一个 i2c 设备都需要一个 i2c_client 来表示。
/* include/linux/i2c.h */
struct i2c_client {
unsigned short flags;
unsigned short addr; //设备地址
char name[I2C_NAME_SIZE]; //设备名称
struct i2c_adapter * adapter; //设备所在的i2c总线的adapter
struct i2c_driver * driver; //绑定的driver
struct device dev;
int irq; //设备的irq号
struct list_head detected; //用于将同一个i2c_driver所驱动的i2c_client形成链表
};
i2c_driver 对应一个驱动的方法,不对应任何的物理实体。
/* include/linux/i2c.h */
struct i2c_driver {
unsigned int class;
int (* attach_adapter) (struct i2c_adapter *); /* 旧式i2c driver的方法, 不要再使用 */
int (* probe) (struct i2c_client *, const struct i2c_device_id *);
int (* remove) (struct i2c_client *);
void (* shutdown) (struct i2c_client *);
int (* suspend) (struct i2c_client *, pm_message_t mesg);
int (* resume) (struct i2c_client *);
void (* alert) (struct i2c_client *, unsigned int data);
int (* command) (struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver; /* 若使用设备树,需要设置 device_driver 的 of_match_table 成员兼容 compatible 属性 */
const struct i2c_device_id * id_table; /* 未使用设备树之前的设备匹配ID表 */
int (* detect) (struct i2c_client *, struct i2c_board_info *);
const unsigned short * address_list;
struct list_head clients;
};
i2c_device_id 用于描述 ic2_driver 和 i2c_client 匹配的条件。
struct i2c_device_id {
char name[I2C_NAME_SIZE]; //该name和i2c_client.name相同,则i2c_client和i2c_driver匹配成功, 进行后续的probe过程
kernel_ulong_t driver_data; //传递给driver的私有数据, 不使用则置0
};
例如:
struct i2c_device_id kxtj2_id[] = {
{ "kxtj2", 0, }
{ "kxtj9", 0, }
{} /*空成员, 用于标识结尾*/
};
struct i2c_driver kxtj2_driver = {
.driver = {
.name = "gsensor-kxtj2", /* "gsensor-kxtj2" 用于匹配设备树 */
.owner = THIS_MODULE,
},
.id_table = kxtj2_id, /* 即上面的 kxtj2_id[] */
......
}
注册 i2c_driver 的方法:
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
int i2c_add_driver(driver);
/* 返回值:0,成功;负值,失败. */
注销 i2c_driver 的方法:
void i2c_del_driver(struct i2c_driver *driver);
设备匹配方式:
/* 传统匹配方式 ID 列表 */
static const struct i2c_device_id xxx_id[] = {
{"xxx", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "xxx" },
{}
};
/* i2c 驱动结构体 */
static struct i2c_driver xxx_driver = {
.probe = xxx_probe,
.remove = xxx_remove,
.driver = {
.owner = THIS_MODULE,
.name = "xxx",
.of_match_table = xxx_of_match, //上面的设备树匹配列表
},
.id_table = xxx_id,
};
设备和驱动的匹配过程
I2C 设备和驱动的匹配过程是由 I2C 核心来完成的,drivers/i2c/i2c-core.c 就是 I2C 的核心
部分,I2C 核心提供了一些与具体硬件无关的 API 函数。
I2C 总线的数据结构为 i2c_bus_type:
/* drivers/i2c/i2c-core.c */
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() 是总线上设备和驱动的匹配函数:
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;
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv)) /* 设备树的匹配方式 */
return 1;
/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv)) /* 这个不用了 */
return 1;
driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL; /*传统的匹配方式*/
return 0;
}
看到这里已经和之前讲过的 platform 总线的设备驱动匹配过程没有差别了。
下面引用正点原子的 I.MX6U 的 I2C 适配器驱动分析,更好的理解 IIC 设备树的匹配过程。
i2c1: i2c@021a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>; //控制器基址为 0X021A0000
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_I2C1>;
status = "disabled";
}
适配器驱动源码:
static struct platform_device_id imx_i2c_devtype[] = { //平台设备匹配表,听我的,不要用了
......
, {
.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,imx21-i2c", // "fsl,imx21-i2c" 必须对应设备树上 compatible 属性
.data = &imx21_i2c_hwdata, //硬件数据,这里不讨论,后面实例分析的时候会讲
},
{}
};
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, //听我的,这个直接 NULL 就好了
};
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);
可以看到实际上 IIC 总线是外壳,实际上工作的是 platform 总线。
当设备和驱动匹配成功就会调用 i2c_imx_probe 函数,i2c_imx_probe 函数
就会完成 I2C 适配器初始化工作。
控制器驱动的 probe 非常地精彩,使用到了很多设备驱动不常用的高级驱动知识。
主机(控制器/适配器)驱动节选
static int i2c_imx_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id = of_match_device(i2c_imx_dt_ids, &pdev->dev);//一上来就匹配设备树上的设备
struct imx_i2c_struct *i2c_imx; //NXP 公司封装了厂商信息的 i2c_adapter
struct resource *res; //硬件资源
struct imxi2c_platform_data *pdata = dev_get_platdata(&pdev->dev);
void __iomem *base; //IO地址的基址
int irq, ret; //中断号和...emmm...
dma_addr_t phy_addr; //使用 DMA 的映射地址
irq = platform_get_irq(pdev, 0); //申请中断号
......
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取平台设备资源
base = devm_ioremap_resource(&pdev->dev, res); //直接对IO基址转换为虚拟地址
if (IS_ERR(base))
return PTR_ERR(base);
phy_addr = (dma_addr_t)res->start; //设置 DMA 映射的地址
i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx), GFP_KERNEL); //给控制器申请内存
if (!i2c_imx)
return -ENOMEM;
if (of_id)
i2c_imx->hwdata = of_id->data;
else
i2c_imx->hwdata = (struct imx_i2c_hwdata *)
platform_get_device_id(pdev)->driver_data;
/* 初始化 i2c_adapter 的各个成员 */
strlcpy(i2c_imx->adapter.name, pdev->name,
sizeof(i2c_imx->adapter.name));
i2c_imx->adapter.owner = THIS_MODULE;
i2c_imx->adapter.algo = &i2c_imx_algo;
i2c_imx->adapter.dev.parent = &pdev->dev;
i2c_imx->adapter.nr = pdev->id;
i2c_imx->adapter.dev.of_node = pdev->dev.of_node;
i2c_imx->base = base;
/* 获取 IIC 时钟 */
i2c_imx->clk = devm_clk_get(&pdev->dev, NULL);
......
ret = clk_prepare_enable(i2c_imx->clk);
......
/* 设置控制器中断,中断服务函数为 i2c_imx_isr() */
ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr, IRQF_NO_SUSPEND, pdev->name, i2c_imx);
......
/* 初始化等待队列 */
init_waitqueue_head(&i2c_imx->queue);
/* Set up adapter data */
i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);
/* Set up clock divider */
i2c_imx->bitrate = IMX_I2C_BIT_RATE; //IMX_I2C_BIT_RATE=100KHz,即频率为100KHZ
ret = of_property_read_u32(pdev->dev.of_node, "clock-frequency", &i2c_imx->bitrate);/*如果设备树上有时钟频率,就用设备树的*/
if (ret < 0 && pdata && pdata->bitrate)
i2c_imx->bitrate = pdata->bitrate;
/* Set up chip registers to defaults,I2C1 控制器的 I2CR 和 I2SR 寄存器 */
imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN, i2c_imx, IMX_I2C_I2CR);
imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR);
/* Add I2C adapter */
ret = i2c_add_numbered_adapter(&i2c_imx->adapter); //向内核注册 i2c_adapter
if (ret < 0) {
dev_err(&pdev->dev, "registration failed\n");
goto clk_disable;
}
/* Set up platform driver data */
platform_set_drvdata(pdev, i2c_imx);
clk_disable_unprepare(i2c_imx->clk);
......
/* Init DMA config if supported */
i2c_imx_dma_request(i2c_imx, phy_addr); //申请DMA
return 0; /* Return OK */
clk_disable:
clk_disable_unprepare(i2c_imx->clk);
return ret;
}
传输函数:
static struct i2c_algorithm i2c_imx_algo = {
.master_xfer = i2c_imx_xfer, //IIC适配器和从机通信的函数
.functionality = i2c_imx_func, //返回此I2C适配器支持的通信协议
};
static u32 i2c_imx_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_READ_BLOCK_DATA;
}
static int i2c_imx_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num)
{
unsigned int i, temp;
int result;
bool is_lastmsg = false;
struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);
dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
/* Start I2C transfer */
result = i2c_imx_start(i2c_imx); //开启I2C通信
if (result)
goto fail0;
/* read/write data */
for (i = 0; i < num; i++) {
if (i == num - 1)
is_lastmsg = true;
if (i) {
dev_dbg(&i2c_imx->adapter.dev, "<%s> repeated start\n", __func__);
temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
temp |= I2CR_RSTA;
imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
result = i2c_imx_bus_busy(i2c_imx, 1);
if (result)
goto fail0;
}
dev_dbg(&i2c_imx->adapter.dev, "<%s> transfer message: %d\n", __func__, i);
/* write/read data */
......
if (msgs[i].flags & I2C_M_RD)
result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg); /* 如果是从 I2C 设备读数据的话就调用 i2c_imx_read 函数 */
else {
if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD) /* 如果有使用 DMA,就用 DMA 完成 write 操作 */
result = i2c_imx_dma_write(i2c_imx, &msgs[i]);
else
result = i2c_imx_write(i2c_imx, &msgs[i]);
}
if (result)
goto fail0;
}
fail0:
/* Stop I2C transfer */
i2c_imx_stop(i2c_imx); //停止 I2C 通信
dev_dbg(&i2c_imx->adapter.dev, "<%s> exit with: %s: %d\n", __func__, (result < 0) ? "error" : "success msg", (result < 0) ? result : num);
return (result < 0) ? result : num;
}
i2c_imx_start、i2c_imx_read、i2c_imx_write 和 i2c_imx_stop 这些函数就是 I2C 寄存器的具
体操作函数,函数内容基本就是裸机操作 IIC 读写,但是是通过寄存器的,不是管脚模拟的。
设备驱动编写
首先需要在设备树上创建一个节点用于给驱动提供硬件信息。
&i2c1 {
clock-frequency = <100000>; //时钟频率
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>; //使用管脚的第一个功能,引用的功能正好是复用为 IIC
status = "okay"; //使能
/* 上面那些都是修改IIC控制器的,下面这个才是描述设备的 */
mag3110@0e {
compatible = "fsl,mag3110"; //匹配驱动的名字
reg = <0x0e>; //从机相对地址,主机发送包含该地址帧的msg,可以被该从机接收
position = <2>; //不用管,别的处理器也不一定有
};
......
};
一些规则有必要解释一下:
- 如何确定修改哪一个设备树?
首先厂商会提供对应处理器的设备树,但是这个设备树上只有控制器描述,并且都是 disabled 的;自己的设备节点需要我们引用控制器节点后,使能控制器节点,并在这个基础上添加设备的描述信息。
如果是购买方案的,那么方案商一定提供了开发板的设备树,找到对应的设备树,在 IIC 设备节点之下再创建一个子节点,然后描述设备信息。 - 需要添加哪些信息?
时钟、地址、驱动匹配名是必须的,但是每个厂商的设备树语法会有差异,可以根据其他类似节点修改。
IIC 数据传输函数:
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
/* num:消息数量,也就是 msgs 的数量。
返回值:负值,失败,其他非负值,发送的 msgs 数量。*/
使用 i2c_transfer 函数发送数据之前要先构建好 i2c_msg:
由于读取之前必须先告诉模块我们要什么数据,所以读操作会比写操作多一个 i2c_msg 。
/* 读取 I2C 设备多个寄存器数据的函数
dev IIC设备
reg 要读取的寄存器的首地址
val 读取到的数据
len 读取的数据长度
*/
static int xxx_read_regs(struct xxx_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; /* I2C 器件地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg 长度 */
/* msg[1],第二条读消息,读取寄存器数据 */
msg[1].addr = client->addr; /* I2C 器件地址 */
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 {
ret = -EREMOTEIO;
}
return ret;
}
/* 向 I2C 设备多个寄存器写入数据
dev IIC设备
reg 要写入的寄存器的首地址
buf 要写入的数据缓冲区
len 写入的数据长度
*/
static s32 xxx_write_regs(struct xxx_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; /* I2C 器件地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要发送的数据缓冲区 */
msg.len = len + 1; /* 要发送的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
可以看到是收是发,完全是由 msg 决定的,和函数没有关系。
还有两个 API 函数是和收发相关的:
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);
/* buf:要接收的数据
count:要接收的数据字节数,要小于 64KB,因为 i2c_msg 的 len 成员变量是一个 u16(无
符号 16 位)类型的数据
返回值:负值,失败,其他非负值,发送的字节数
*/
实例1:读取IIC接口传感器的数据
传感器模块通过 IIC 接线处理器,那么只能在 IIC 驱动里面把传感器模块的业务给封装起来就是传感器模块驱动了。
开发步骤:
- 修改设备树:
在 pinctrl 节点中添加:引脚号、复用类型,是否中断(中断要在gpio节点中配置中断引脚,图中的7号引脚AP_INT就是中断引脚。再添加内容引用到了 IIC 控制器节点上,使能控制器并设置频率、匹配驱动名、从机地址。 - 复制粘贴 IIC 设备驱动框架。
- 把EEPROM读写的规则封装到 IIC 传输函数中。
修改设备树:
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 是引脚号,0x4001b8b0 是复用的电气属性,
这个知道是设置复用就好了,不同厂商的 pinctrl 规则不一样。
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default"; //默认就是第0个功能
pinctrl-0 = <&pinctrl_i2c1>; //第0个功能:引用上面设置的复用
status = "okay";
ap3216c@1e {
compatible = "alientek,ap3216c"; //正点原子的IIC驱动名
reg = <0x1e>; //从机地址,IIC 地址帧需要用到
};
};
从设备模型那一讲中,可以知道设备注册和驱动注册时分开的,所以设备树添加了设备之后,在 sys/bus/i2c/devices/ 目录是可以看到设备文件的。
IIC 驱动框架:
#define AP3216C_CNT 1
#define AP3216C_NAME "ap3216c"
struct ap3216c_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int major; /* 主设备号 */
void *private_data; /* 私有数据 */
};
static struct ap3216c_dev ap3216cdev;
static const struct of_device_id ap3216c_of_match[] = {
{ .compatible = "alientek,ap3216c" },
{}
};
static int ap3216c_open(struct inode *inode, struct file *filp);
static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off);
static int ap3216c_release(struct inode *inode, struct file *filp);
static const struct file_operations ap3216c_ops = {
.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)
{
/* 1、构建设备号 */
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、注册设备 */
cdev_init(&ap3216cdev.cdev, &ap3216c_ops);
cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);
/* 3、创建类 */
ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
if (IS_ERR(ap3216cdev.class)) {
return PTR_ERR(ap3216cdev.class);
}
/* 4、创建设备 */
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;
}
static int ap3216c_remove(struct i2c_client *client)
{
/* 删除设备 */
cdev_del(&ap3216cdev.cdev);
unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);
/* 注销掉类和设备 */
device_destroy(ap3216cdev.class, ap3216cdev.devid);
class_destroy(ap3216cdev.class);
return 0;
}
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);
}
添加业务:
#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 数据高字节 */
以上这些是外设自己的处理器的寄存器的地址。模块本身是有MCU的,也有自己的固件程序,只能通过IIC发送特定的数据,让模块固件程序回复我们想要的内容,具体什么数据,那得看看模块的文档了。
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len);
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len);
/* 专门用来读取传感器数据的函数 */
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) /* 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);
}
/* 注意:读取的数据的格式是模块固件设定的,这个函数并没有太多参考意义
重要的是构建 i2c_msg 并使用 i2c_transfer() 发送
*/
首先目的是读取三个传感器的数据。
那么需要在设备结构体中增加这三个成员:
struct ap3216c_dev {
......
unsigned short ir, als, ps; /* 三个光传感器数据 */
};
open 操作:打开设备文件时对设备进行复位。
static int ap3216c_open(struct inode *inode, 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); /* 开启ALS、PS+IR */
return 0;
}
read 操作:简单暴力,直接调用 ap3216c_readdata() 读取三个寄存器的值
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;
}
release 操作:设备打开的时候并没有申请任何动态内存,也没有进行内存映射。因此这里直接写个空函数都可以了。
略…
实例2:RK3568触摸屏驱动
内容较大,移步到另一篇博客: