概要
在linux内核中,位于物理bus上的设备被称为bus controller(总线控制器),而对应bus的驱动被称为 controller driver(控制器驱动)。控制器驱动负责管理挂在总线上的各个设备之间的数据传输。在内核中没个总线驱动都有对应的实例,本节以一个I2C设备为案例介绍如何在内核中编写IIC设备驱动。
IIC驱动流程以及各类接口
IIC 驱动需要的资源
驱动是为设备服务的,在实现驱动前需要先确定支持的设备资源。这里使用设备树的方法描述设备,在dts的i2c总线下描述设备
&i2c2 { /* Phandle of the bus node */
pcf8523: rtc@68 {
compatible = "nxp,pcf8523";
reg = <0x68>;
};
eeprom: ee24lc512@55 { /* eeprom device */
compatible = "packt,ee24lc512";
reg = <0x55>;
};
};
reg作为设备的地址,每个I2C设备都应该有个地址来让总线来找到它,而compatible,常见的属性,用来作为设备匹配的关键词。
确定了支持的设备类型后就可以进行驱动资源的准备了,i2c设备驱动在内核中以i2c_driver结构体表达,定义如下
struct i2c_driver {
/* 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 *);
struct device_driver driver;
const struct i2c_device_id *id_table;
};
可以看出来,其结构的表达形式和platform设备基本一致,两者的区别在于函数所传递的设备实例不同。platform 使用 platform_device 表示设备而IIC使用 i2c_client来表示一个i2c设备,下面是它在内核中的定义
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7 bit */
/* 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
};
了解这两个结构体后,其实我们基本上就可以了解到一个IIC设备驱动至少需要哪些资源了
- i2c_driver(i2c设备驱动的主体),实例(instance)一个i2c_driver然后使用module_i2c_driver(instance),你就成功得到一个内核i2c设备驱动了。
- 准备 一个 of_match_table (or id_table), 用来匹配支持的IIC设备,使用MODULE_DEVICE_TABLE(i2c(or of), id_table(or of_table))告诉内核你所支持的设备
- probe,该初始化的初始化,该注册的注册
- remove,尘归尘土归土,设备获取的资源全部释放掉
- 设备数据处理
OK,完成以上步骤,就可以完成一个i2c设备驱动了,so easy。下面简单介绍下一些接口,后面以实际案例来实现一个IIC设备驱动。
IIC的probe流程以及remove流程
当有IIC设备匹配到驱动时,probe函数就会执行。按照正常的设备驱动流程来编写注册
- 检查设备是否正确
- 使用i2c_check_functionality检查Soc是否有对应的i2c函数接口
- 初始化设备
- 设置设备数据
- 根据需求注册对应的内核驱动。
内核中i2c通过下面两个函数存储以及获取数据
/* set the data */
void i2c_set_clientdata(struct i2c_client *client, void *data);
/* get the data */
void *i2c_get_clientdata(const struct i2c_client *client);
remove函数的实现流程一般为释放我们在probe中申请注册的资源,它会在设备被卸载时被调用。
IIC数据传输
先给出三个常用的接口
int i2c_master_send(struct i2c_client *client, const char *buf, int count);
int i2c_master_recv(struct i2c_client *client, char *buf, int count);
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msg,int num);
前面两个接口就是很常规的trans(destinatioin, buf, count)类型函数,主要介绍下第3个接口,也是最常用的接口。
它使用了一个 i2c_msg的结构体,我们可以在include/uapi/linux/i2c.h中找到定义
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags; /* Message flags */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
i2c_transfer非常简单,直接上代码,通过i2c_msg描述要传递给i2c总线的信息,然后通过i2c_transfer将信息告诉总线。
ssize_t eep_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
[...]
int _reg_addr = dev->current_pointer;
u8 reg_addr[2];
reg_addr[0] = (u8)(_reg_addr>> 8);
reg_addr[1] = (u8)(_reg_addr& 0xFF);
struct i2c_msg msg[2];
msg[0].addr = dev->client->addr;
msg[0].flags = 0; /* Write */
msg[0].len = 2; /* Address is 2 bytes coded */
msg[0].buf = reg_addr;
msg[1].addr = dev->client->addr;
msg[1].flags = I2C_M_RD; /* We need to read */
msg[1].len = count;
msg[1].buf = dev->data;
if (i2c_transfer(dev->client->adapter, msg, 2) < 0)
pr_err("ee24lc512: i2c_transfer failed\n");
if (copy_to_user(buf, dev->data, count) != 0) {
retval = -EIO;
goto end_read;
}
[...]
}
其实就是把IIC需要的地址、数据、长度、读写方向放到了 i2c_msg中,然后调用i2c_transfer接口就行了,easy。
实战,开始写一个IIC设备驱动
接下来以一个ap32c16c设备为案例,进行设备驱动的编写,这里使用的平台是nxp的im6ull,适配的设备为ap3216c。
驱动编写流程
在写驱动之前先了解确定好自己需要哪些资源,先把框架搭建起来。一般就是以下几步:
1、准备好struct i2_driver需要的资源probe(...)、remove(...)、driver信息、id_table信息,以及设备树提供设备信息。
2、填充driver中的of_device_id信息以及id_table信息,并将信息注册到linux内核中。
3、实现probe函数,在函数中初始化设备资源
4、实现remove函数,在函数中释放设备资源
5、实现各类文件处理接口以及数据处理接口
6、向内核注册设备
下面给一个demo
#include "linux/init.h"
#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/of.h"
#include "linux/of_device.h"
#include "linux/of_gpio.h"
#include "linux/device.h"
#include "linux/cdev.h"
#include "linux/errno.h"
#include "linux/fs.h"
#include "linux/platform_device.h"
#include "linux/types.h"
#include "asm/mach/map.h"
#include "asm/uaccess.h"
#include "asm/io.h"
#include "linux/i2c.h"
static int ap3216c_probe(struct i2c_client *_i2c_client, const struct i2c_device_id *_i2c_device_id);
static int ap3216c_remove(struct i2c_client *_i2c_client);
const struct i2c_device_id ap3216c_device_id[] = {
{.name = "ap3216c", .driver_data = 0},
{}
};
MODULE_DEVICE_TABLE(i2c, ap3216c_device_id);
const struct of_device_id ap3216c_match_table[] = {
{.compatible = "ap3216c"},
{}
};
MODULE_DEVICE_TABLE(of, ap3216c_match_table);
static struct i2c_driver ap3216c_driver = {
.probe = ap3216c_probe,
.remove = ap3216c_remove,
.driver = {
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(ap3216c_match_table),
},
.id_table = ap3216c_device_id,
};
struct i2c_ap32c16c {
int device_id;
struct cdev mcdev;
struct class *mclass;
struct device *mdevice;
void *date;
}ap32c16c;
ssize_t ap32c16c_read (struct file *pfile, char __user *puserdate, size_t size, loff_t *poffset);
ssize_t ap32c16c_write (struct file *pfile, const char __user *puserdate, size_t size, loff_t *poffset);
int ap32c16c_release (struct inode *pnode, struct file *pfile);
int ap32c16c_open (struct inode *pnode, struct file *pfile);
struct file_operations ap32c16c_fopreationn = {
.owner = THIS_MODULE,
.open = ap32c16c_open,
.release = ap32c16c_release,
.read = ap32c16c_read,
.write = ap32c16c_write,
};
ssize_t ap32c16c_read (struct file *pfile, char __user *puserdate, size_t size, loff_t *poffset)
{
/*实现设备信息的读取*/
return 0;
}
ssize_t ap32c16c_write (struct file *pfile, const char __user *puserdate, size_t size, loff_t *poffset)
{
/*实现设备信息的写入*/
return 0;
}
int ap32c16c_open (struct inode *pnode, struct file *pfile)
{
/*打开设备*/
return 0;
}
int ap32c16c_release (struct inode *pnode, struct file *pfile)
{
/*实现创建的资源释放*/
return 0;
}
static int ap3216c_probe(struct i2c_client *_i2c_client, const struct i2c_device_id *_i2c_device_id)
{
/*初始化匹配到的设备*/
return 0;
};
static int ap3216c_remove(struct i2c_client *_i2c_client)
{
/*释放资源*/
return 0;
}
static int ap3216c_remove(struct i2c_client *);
module_i2c_driver(ap3216c_driver);
MODULE_LICENSE("GPL");
编辑设备树
本章使用的设备树的形式进行设备资源的描述。
1.设备初始化使用pinctrl的方式,需要在iomuxc下添加i2c总线对应的pin脚信息。(总线驱动一般soc厂家已经给好了,我们直接拿来用就行。)
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
2.设备是挂在i2c1总线上的,所以在i2c1总线中添加设备信息
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
ap3216c@1e {
compatible = "ap3216c";
reg = <0x1e>;
};
mag3110@0e {
compatible = "fsl,mag3110";
reg = <0x0e>;
position = <2>;
};
};
probe和remove实现
本次设备的初始化放在了设备打开的时候。故probe和remove的编辑与其他的cdev设备驱动编辑几乎一致,唯一的区别就在于需要将当前匹配的设备信息保存一下,以便于后面打开设备时的设备资源获取。
static int ap3216c_probe(struct i2c_client *_i2c_client, const struct i2c_device_id *_i2c_device_id)
{
pr_info("ap3216c device was matched! \r\n");
/*1.设置设备号*/
if(ap32c16c.major){
ap32c16c.device_id = MKDEV(ap32c16c.major, 0);
register_chrdev_region(ap32c16c.device_id, AP3216_CNT, AP3216C_NAME);
}
else{
alloc_chrdev_region(&ap32c16c.device_id, 0, AP3216_CNT, AP3216C_NAME);
ap32c16c.major = MAJOR(ap32c16c.device_id);
}
/*2.注册设备*/
cdev_init(&ap32c16c.mcdev, &ap32c16c_fopreationn);
cdev_add(&ap32c16c.mcdev, ap32c16c.device_id, AP3216_CNT);
/*3.创建类*/
ap32c16c.mclass = class_create(THIS_MODULE, AP3216C_NAME);
if(IS_ERR(ap32c16c.mclass)){
return PTR_ERR(ap32c16c.mclass);
}
/*4.创建设备*/
ap32c16c.mdevice = device_create(ap32c16c.mclass, NULL, ap32c16c.device_id, NULL, AP3216C_NAME);
if(IS_ERR(ap32c16c.mdevice)){
return PTR_ERR(ap32c16c.mdevice);
}
ap32c16c.data = _i2c_client;
return 0;
};
static int ap3216c_remove(struct i2c_client *_i2c_client)
{
cdev_del(&ap32c16c.mcdev);
unregister_chrdev_region(ap32c16c.device_id, AP3216_CNT);
device_destroy(ap32c16c.mclass, ap32c16c.device_id);
class_destroy(ap32c16c.mclass);
return 0;
}
i2c设备的初始话一般就是往设备的各种寄存器里面设置一些配置,然后读一下设备id看下设备没有设置好,所以我们要实现一些i2c的读写接口
i2c读写接口实现
为了方便数据处理以及代码重用,我们需要实现i2c读写寄存器的操作。一般为如下几个步骤
1.给每个需要使用的寄存器打上宏定义
2.实现xxx_read_reg 和 xxx_read_regs.
3.实现xxx_write_reg 和 xxx_write_regs.
read_reg 可直接调用xxx_read_regs(偷懒),xxx_write也可直接调用xxx_write_regs。
先上demo
static inline int ap32c16c_read_regs(struct i2c_ap32c16c *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *) dev->data;
msg[0].addr = client->addr; /*设备地址*/
msg[0].flags = 0; /*标记为发送*/
msg[0].buf = ® /*读取的首地址*/
msg[0].len = 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){
return 0;
}
else {
pr_info("i2c read error: %d", ret);
return -EREMOTEIO;
}
}
static inline unsigned char ap32c16c_read_reg(struct i2c_ap32c16c *dev, u8 reg)
{
u8 data = 0;
ap32c16c_read_regs(dev, reg, &data, 1);
return data;
}
static inline s32 ap32c16c_write_regs(struct i2c_ap32c16c *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->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 inline void ap32c16c_write_reg(struct i2c_ap32c16c *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
ap32c16c_write_regs(dev, reg, &buf, 1);
}
实现驱动的过程中需要调用i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)来告诉总线需要的操作。i2c_adapter 是总线的驱动控制器,有兴趣可以研究一下,i2c_msg是传递给总线的操作信息,num描述了需要传递几个i2c_msg.可以在include/uapi/linux/i2c.h看到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 */
};
open和release实现
很常规的操作,open用来初始化设备,release用来释放资源(虽然这里没有什么要释放的)
int ap32c16c_open (struct inode *pnode, struct file *pfile)
{
/*初始化设备,参考设备数据手册*/
ap32c16c_write_reg(&ap32c16c, AP3216C_SYSTEMCONG, 0x04);
mdelay(50);
ap32c16c_write_reg(&ap32c16c, AP3216C_SYSTEMCONG, 0x03);
return 0;
}
int ap32c16c_release (struct inode *pnode, struct file *pfile)
{
return 0;
}
read实现
由于只需要从设备中读取到设备信息,我们也只需要实现read函数就可以了。这个根据具体设备的功能来实现,以及根据需要进行数据处理。
ssize_t ap32c16c_read (struct file *pfile, char __user *puserdate, size_t size, loff_t *poffset)
{
/*实现设备信息的读取*/
short data[3];
unsigned char i = 0;
unsigned char buf[6];
long err;
for(i = 0; i < 6; i++){
buf[i] = ap32c16c_read_reg(&ap32c16c, AP3216C_IRDATALOW + i);
}
if(buf[0] & 0x80)
ap32c16c.ir = 0;
else
ap32c16c.ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
ap32c16c.als = ((unsigned short)buf[3] << 8) | buf[2];
if(buf[4] & 0x40)
ap32c16c.ps = 0;
else
ap32c16c.ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
data[0] = ap32c16c.ir;
data[1] = ap32c16c.als;
data[2] = ap32c16c.ps;
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
总结
对于实现一个i2c client端的设备驱动来说,本质上就是i2c_driver的实例化过程,在这个实例化过程前,还需要编辑dts来描述我们所驱动(当然也可以用老方法i2c_board_info,但还是极力推荐使用设备树)。
在实例化过程中,我们需要提供of_match_table、probe方法、remove方法、各类文件操作方法。了解了如何实现这些资源以及方法后,驱动的编写便可以变得特别简单了。
附录
附录为整体代码,可做编写时的demo。
#include "linux/init.h"
#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/of.h"
#include "linux/of_device.h"
#include "linux/of_gpio.h"
#include "linux/device.h"
#include "linux/cdev.h"
#include "linux/errno.h"
#include "linux/fs.h"
#include "linux/platform_device.h"
#include "linux/types.h"
#include "asm/mach/map.h"
#include "asm/uaccess.h"
#include "asm/io.h"
#include "linux/i2c.h"
#include "linux/delay.h"
/* 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 AP3216C_NAME "ap3216c"
#define AP3216_CNT 1
static int ap3216c_probe(struct i2c_client *_i2c_client, const struct i2c_device_id *_i2c_device_id);
static int ap3216c_remove(struct i2c_client *_i2c_client);
const struct i2c_device_id ap3216c_device_id[] = {
{.name = "ap3216c", .driver_data = 0},
{}
};
MODULE_DEVICE_TABLE(i2c, ap3216c_device_id);
const struct of_device_id ap3216c_match_table[] = {
{.compatible = "ap3216c"},
{}
};
MODULE_DEVICE_TABLE(of, ap3216c_match_table);
static struct i2c_driver ap3216c_driver = {
.probe = ap3216c_probe,
.remove = ap3216c_remove,
.driver = {
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(ap3216c_match_table),
},
.id_table = ap3216c_device_id,
};
struct i2c_ap32c16c {
int device_id;
struct cdev mcdev;
struct class *mclass;
struct device *mdevice;
int major;
struct i2c_client *data;
unsigned short ir, als, ps;
}ap32c16c;
ssize_t ap32c16c_read (struct file *pfile, char __user *puserdate, size_t size, loff_t *poffset);
ssize_t ap32c16c_write (struct file *pfile, const char __user *puserdate, size_t size, loff_t *poffset);
int ap32c16c_release (struct inode *pnode, struct file *pfile);
int ap32c16c_open (struct inode *pnode, struct file *pfile);
struct file_operations ap32c16c_fopreationn = {
.owner = THIS_MODULE,
.open = ap32c16c_open,
.release = ap32c16c_release,
.read = ap32c16c_read,
.write = ap32c16c_write,
};
static inline int ap32c16c_read_regs(struct i2c_ap32c16c *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *) dev->data;
msg[0].addr = client->addr; /*设备地址*/
msg[0].flags = 0; /*标记为发送*/
msg[0].buf = ® /*读取的首地址*/
msg[0].len = 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){
return 0;
}
else {
pr_info("i2c read error: %d", ret);
return -EREMOTEIO;
}
}
static inline unsigned char ap32c16c_read_reg(struct i2c_ap32c16c *dev, u8 reg)
{
u8 data = 0;
ap32c16c_read_regs(dev, reg, &data, 1);
return data;
}
static inline s32 ap32c16c_write_regs(struct i2c_ap32c16c *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->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 inline void ap32c16c_write_reg(struct i2c_ap32c16c *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
ap32c16c_write_regs(dev, reg, &buf, 1);
}
ssize_t ap32c16c_read (struct file *pfile, char __user *puserdate, size_t size, loff_t *poffset)
{
/*实现设备信息的读取*/
short data[3];
unsigned char i = 0;
unsigned char buf[6];
long err;
for(i = 0; i < 6; i++){
buf[i] = ap32c16c_read_reg(&ap32c16c, AP3216C_IRDATALOW + i);
}
if(buf[0] & 0x80)
ap32c16c.ir = 0;
else
ap32c16c.ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
ap32c16c.als = ((unsigned short)buf[3] << 8) | buf[2];
if(buf[4] & 0x40)
ap32c16c.ps = 0;
else
ap32c16c.ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
data[0] = ap32c16c.ir;
data[1] = ap32c16c.als;
data[2] = ap32c16c.ps;
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
ssize_t ap32c16c_write (struct file *pfile, const char __user *puserdate, size_t size, loff_t *poffset)
{
/*实现设备信息的写入*/
return 0;
}
int ap32c16c_open (struct inode *pnode, struct file *pfile)
{
/*初始化设备,参考设备数据手册*/
ap32c16c_write_reg(&ap32c16c, AP3216C_SYSTEMCONG, 0x04);
mdelay(50);
ap32c16c_write_reg(&ap32c16c, AP3216C_SYSTEMCONG, 0x03);
return 0;
}
int ap32c16c_release (struct inode *pnode, struct file *pfile)
{
return 0;
}
static int ap3216c_probe(struct i2c_client *_i2c_client, const struct i2c_device_id *_i2c_device_id)
{
pr_info("ap3216c device was matched! \r\n");
/*1.设置设备号*/
if(ap32c16c.major){
ap32c16c.device_id = MKDEV(ap32c16c.major, 0);
register_chrdev_region(ap32c16c.device_id, AP3216_CNT, AP3216C_NAME);
}
else{
alloc_chrdev_region(&ap32c16c.device_id, 0, AP3216_CNT, AP3216C_NAME);
ap32c16c.major = MAJOR(ap32c16c.device_id);
}
/*2.注册设备*/
cdev_init(&ap32c16c.mcdev, &ap32c16c_fopreationn);
cdev_add(&ap32c16c.mcdev, ap32c16c.device_id, AP3216_CNT);
/*3.创建类*/
ap32c16c.mclass = class_create(THIS_MODULE, AP3216C_NAME);
if(IS_ERR(ap32c16c.mclass)){
return PTR_ERR(ap32c16c.mclass);
}
/*4.创建设备*/
ap32c16c.mdevice = device_create(ap32c16c.mclass, NULL, ap32c16c.device_id, NULL, AP3216C_NAME);
if(IS_ERR(ap32c16c.mdevice)){
return PTR_ERR(ap32c16c.mdevice);
}
ap32c16c.data = _i2c_client;
return 0;
};
static int ap3216c_remove(struct i2c_client *_i2c_client)
{
cdev_del(&ap32c16c.mcdev);
unregister_chrdev_region(ap32c16c.device_id, AP3216_CNT);
device_destroy(ap32c16c.mclass, ap32c16c.device_id);
class_destroy(ap32c16c.mclass);
return 0;
}
static int ap3216c_remove(struct i2c_client *);
module_i2c_driver(ap3216c_driver);
MODULE_LICENSE("GPL");