在内核中写IIC驱动

概要

在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 = &reg;          /*读取的首地址*/
    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 = &reg;          /*读取的首地址*/
    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");

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值