IIC驱动理论与实例分析


前言

`
不啰嗦,直接从驱动开讲,需要学习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 = &reg;            /* 读取的首地址 */
    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 驱动里面把传感器模块的业务给封装起来就是传感器模块驱动了。
开发步骤:

  1. 修改设备树:
    在 pinctrl 节点中添加:引脚号、复用类型,是否中断(中断要在gpio节点中配置中断引脚,图中的7号引脚AP_INT就是中断引脚。再添加内容引用到了 IIC 控制器节点上,使能控制器并设置频率、匹配驱动名、从机地址。
  2. 复制粘贴 IIC 设备驱动框架。
  3. 把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触摸屏驱动

内容较大,移步到另一篇博客:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值