一:Linux I2C驱动框架
Linux内核将I2C驱动分为两部分:
(1)I2C总线驱动,I2C总线驱动就是SOC的I2C控制器驱动,也叫做I2C适配器驱动
(2)I2C设备驱动,I2C设备驱动就是针对具体的I2C设备而编写的驱动。
作为SOC开发者来讲无需关心i2c总线驱动,只需关注i2c设备驱动即可。
1、I2C总线驱动
I2C设备与platform设备不同的是,I2C拥有自己的总线即I2C总线而不需要虚拟出一条来。I2C总线驱动重点是I2C适配器(SOC的I2C接口控制器)驱动,其中有两个重要的数据结构:i2c_adapter和i2c_algorithm,Linux内核将I2C适配器抽象成i2c_adapter结构体:
include/linux/i2c.h
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;
};
对于一个i2c适配器,需要对外提供读写API函数,设备驱动程序就可以使用这些API函数来完成读写操作,其中i2c_algorithm类型的指针变量algo就是i2c适配器与i2c设备进行通信的方法。
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适配器的传输函数,可以通过此函数来完成与i2c设备之间的通信。
smbus_xfer:SMBUS总线的传输函数。
i2c总线驱动的主要工作任务就是初始化i2c_adapter结构体变量,然后设置i2c_algorithm中的master_xfer函数。完成后通过i2c_add_numberd_adapter和i2c_add_adapter这两个函数向系统注册设置好的i2c_adapter,原型如下:
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
其中adapter 和 adap:要添加到 Linux 内核中的 i2c_adapter,也就是 I2C 适配器。
2、I2C设备驱动
i2c设备驱动重点关注两个数据结构:i2c_client和i2c_driver。根据总线,设备,驱动模型划分,i2c_client描述设备信息,i2c_driver描述驱动内容,类似于platform_driver。
(1)i2c_client:一个设备对应一个i2c_client,每检测到一个i2c设备就会分配一个该数据
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,当i2c设备与驱动匹配成功后probe函数就会执行。
struct i2c_driver {
unsigned int class;
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 *);
void (*shutdown)(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;
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;
};
如果使用设备树的话,需要设置device_driver的of_match_table成员变量,也就是驱动的兼容属性compatible。
对于i2c设备驱动来讲,重点工作就是构建i2c_driver,构建完成以后需要向linux内核注册这个i2c_driver。注册函数为i2c_register_driver:
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
或者使用注册宏定义:i2c_add_driver
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
注销函数:i2c_del_driver
void i2c_del_driver(struct i2c_driver *driver)
i2c_driver注册示例:
/* i2c 驱动的 probe 函数 */
static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
/* 函数具体程序 */
return 0;
}
/* i2c 驱动的 remove 函数 */
static int ap3216c_remove(struct i2c_client *client)
{
/* 函数具体程序 */
return 0;
}
/* 传统匹配方式 ID 列表 */
static const struct i2c_device_id xxx_id[] = {
{"xxx", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "xxx" },
{ /* Sentinel */ }
};
/* 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,
};
/* 驱动入口函数 */
static int __init xxx_init(void)
{
int ret = 0;
ret = i2c_add_driver(&xxx_driver);
return ret;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
i2c_del_driver(&xxx_driver);
}
module_init(xxx_init);
module_exit(xxx_exit);
3、I2C设备和驱动匹配
设备和驱动的匹配过程是由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,
};
EXPORT_SYMBOL_GPL(i2c_bus_type);
成员函数match就是i2c总线的设备和驱动的匹配函数: 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;
}
二:I2C适配器驱动分析
一般情况i2c适配器驱动不需要我们编写,做个简单分析。
在对应SOC设备树共用文件(imx6ull.dtsi)中找到i2c1控制器节点: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属性即可找到i2c适配器驱动文件:
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,
};
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);
分析适配器驱动可以看出i2c适配器驱动是个标准的platform驱动框架,而i2c设备驱动用的是i2c总线驱动框架。
当设备和驱动匹配成功后,i2c_imx_probe函数就会执行:
static int i2c_imx_probe(struct platform_device *pdev)
i2c_imx_probe函数主要的工作有两点:
(1)初始化i2c_adapter,设置i2c_algorithm 为 i2c_imx_algo,最后想Linux内核注册i2c_adapter
(2)初始化i2c1控制器的相关寄存器
i2c_imx->adapter.algo = &i2c_imx_algo;
static struct i2c_algorithm i2c_imx_algo = {
.master_xfer = i2c_imx_xfer,
.functionality = i2c_imx_func,
};
.functionality用于返回i2c适配器支持什么样的通讯协议:
static u32 i2c_imx_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL
| I2C_FUNC_SMBUS_READ_BLOCK_DATA;
}
.master_xfer用来完成与i2c设备通信
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);
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);
else {
if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)
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);
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寄存器的具体操作函数。
三:I2C设备驱动流程
1、未使用设备树
未使用设备树的时候需要在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设备的器件地址。举例OV2640设备
static struct i2c_board_info mx27_3ds_i2c_camera = {
I2C_BOARD_INFO("ov2640", 0x30),
};
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr)
2、使用设备树
使用设备树时i2c设备信息通过创建相应的节点就可以,例如 imx6ull-14x14-evk.dts
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
mag3110@0e {
compatible = "fsl,mag3110";
reg = <0x0e>;
position = <2>;
};
... ...
};
"mag3110@0e"为子节点名称,“@”后面的“0e”为mag3110的i2c设备地址,与"reg = <0x0e>"相同。
compatible = "fsl,mag3110";用于设置i2c设备属性用于匹配驱动。
3、设备数据收发处理
第一章节中已经讲过,i2c设备驱动首先要做的就是初始化i2c_driver并想Linux内核注册。当设备和驱动匹配以后i2c_driver里面的probe函数就会执行,probe函数里面所做的就是字符设备驱动的那一套,一般需要初始化i2c设备,初始化i2c设备就需要对i2c设备寄存器进行读写操作,就需要要用到i2c_transfer函数,该函数最终会调用i2c适配器中i2c_algorithm里面的master_xfer函数,对于imx6u来说就是i2c_imx_xfer函数。
int i2c_transfer(struct i2c_adapter *adap,
struct i2c_msg *msgs,
int num)
其中msgs这个参数是一个i2c_msg类型的指针参数,Linux内核中i2c设备的数据收发消息就用i2c_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_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。
除过i2c_transfer函数还有两个API可以使用:i2c_master_send和i2c_master_recv,最终还是会调用到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)
4、举例说明:mag3110设备
根据设备节点compatible属性搜寻:
#define MAG3110_DRV_NAME "mag3110"
static const struct i2c_device_id mag3110_id[] = {
{MAG3110_DRV_NAME, 0},
{}
};
MODULE_DEVICE_TABLE(i2c, mag3110_id);
static struct i2c_driver mag3110_driver = {
.driver = {
.name = MAG3110_DRV_NAME,
.owner = THIS_MODULE,
.pm = MAG3110_DEV_PM_OPS,
},
.probe = mag3110_probe,
.remove = mag3110_remove,
.id_table = mag3110_id,
};
static int __init mag3110_init(void)
{
return i2c_add_driver(&mag3110_driver);
}
static void __exit mag3110_exit(void)
{
i2c_del_driver(&mag3110_driver);
}
分析probe函数结构:mag3110_probe
static int mag3110_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
... ...
/* Initialize mag3110 chip */
mag3110_init_client(client);
}
-->>mag3110_init_client(client);
-->> mag3110_write_reg(client, MAG3110_CTRL_REG2, val);
-->> i2c_smbus_write_byte_data(client, reg, value);
-->> i2c_smbus_xfer(client->adapter, client->addr, client->flags,
I2C_SMBUS_WRITE, command,
I2C_SMBUS_BYTE_DATA, &data);
-->> i2c_smbus_xfer_emulated(adapter, addr, flags, read_write,
command, protocol, data);
-->> i2c_transfer(adapter, msg, num);
接着第三小节举例说明i2c_transfer函数中msg参数的构建过程:具体含义参考第三小节i2c_msg结构体
static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr,
unsigned short flags,
char read_write, u8 command, int size,
union i2c_smbus_data *data)
{
unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];
unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];
struct i2c_msg msg[2] = {
{
.addr = addr,
.flags = flags,
.len = 1,
.buf = msgbuf0,
}, {
.addr = addr,
.flags = flags | I2C_M_RD,
.len = 0,
.buf = msgbuf1,
},
};
... ...
case I2C_SMBUS_PROC_CALL:
num = 2; /* Special case */
read_write = I2C_SMBUS_READ;
msg[0].len = 3;
msg[1].len = 2;
msgbuf0[1] = data->word & 0xff;
msgbuf0[2] = data->word >> 8;
break;
... ...
status = i2c_transfer(adapter, msg, num);
}
四:示例代码
1、修改设备树
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
ap3216c@1e {
compatible = "alientek,ap3216c";
reg = <0x1e>;
};
};
2、驱动
#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 "ap3216creg.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;
};
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]为发送要读取的首地址 */
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;
}
static s32 ap3216c_write_regs(struct ap3216c_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; /* ap3216c 地址 */
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;
}
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
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
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;
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04);
mdelay(50);
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 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)
{
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);
}
cdev_init(&ap3216cdev.cdev, &ap3216c_fops);
cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);
ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);
ap3216cdev.private_data = client;
return 0;
}
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[] = {
{"ap3216c", 0},
{}
};
static const struct of_device_id ap3216c_of_match[] = {
{.compatible = "alientek, ap3216c"},
{}
};
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 ap3216c_init(void)
{
int ret = 0;
ret = i2c_add_driver(&ap3216c_driver);
return ret;
}
static void ap3216c_exit(void)
{
i2c_del_driver(&ap3216c_driver);
}
// module_i2c_driver(ap3216c_driver);
module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");