LQF376 / Linux-arm-mystudy

Store my embedded learning markdown
5 stars 0 forks source link

4-1. I2C Linux #9

Open LQF376 opened 1 year ago

LQF376 commented 1 year ago

I2C Linux

1. I2C 简介

1683187836742

1.1 I2C 总线通信过程

  1. 主机发送起始信号启用总线
  2. 主机发送一个字节数据指明从机地址和后续字节的传送方向(0:主->从;1:从->主)
  3. 被寻址的从机发送应答信号回应主机
  4. 发送器发送一个字节数据
  5. 接收器发送应答信号回应发送器 ....(循环重复 4.5)
  6. 通信完成后主机发送停止信号释放总线

1.2 I2C 总线寻址方式

1.3 I2C 信号的实现

起始信号和停止信号

1683187836742

字节传送与应答

同步信号

1683187886343

1.4 典型 I2C 时序

2. I.MX6U I2C 简介

2.1 I2Cx_IADR(x =1~4)I2C 地址寄存器

1685346038534

2.2 I2C_IFDR 分频寄存器

1685346107129

2.3 I2Cx_I2CR 控制寄存器

1685346251426

2.4 I2Cx_I2SR 状态寄存器

1685346450848

2.5 I2Cx_I2DR 数据寄存器

3. I.MX6U 的 I2C 文件

/* bsp_i2c.h */

#ifndef _BSP_I2C_H
#define _BSP_I2C_H

#include "imx6ul.h"

/* 相关宏定义 */
#define I2C_STATUS_OK               (0)
#define I2C_STATUS_BUSY             (1)
#define I2C_STATUS_IDLE             (2)
#define I2C_STATUS_NAK              (3)
#define I2C_STATUS_ARBITRATIONLOST  (4)
#define I2C_STATUS_TIMEOUT          (5)
#define I2C_STATUS_ADDRNAK          (6)

/*
* I2C 方向枚举类型
*/
enum i2c_direction
{
    kI2C_Write = 0x0, /* 主机向从机写数据 */
    kI2C_Read = 0x1, /* 主机从从机读数据 */
};

/*
* 主机传输结构体
*/
struct i2c_transfer
{
    unsigned char slaveAddress;         /* 7 位从机地址 */
    enum i2c_direction direction;       /* 传输方向 */
    unsigned int subaddress;            /* 寄存器地址 */
    unsigned char subaddressSize;       /* 寄存器地址长度 */
    unsigned char *volatile data;       /* 数据缓冲区 */
    volatile unsigned int dataSize;     /* 数据缓冲区长度 */
};

// 函数声明
void i2c_init(I2C_Type *base);
unsigned char i2c_master_start(I2C_Type *base,
                            unsigned char address,
                            enum i2c_direction direction);
unsigned char i2c_master_repeated_start(I2C_Type *base,
                                    unsigned char address,
                                    enum i2c_direction direction);
unsigned char i2c_check_and_clear_error(I2C_Type *base,
                                    unsigned int status);
unsigned char i2c_master_stop(I2C_Type *base);
void i2c_master_write(I2C_Type *base, const unsigned char *buf,
                    unsigned int size);
void i2c_master_read(I2C_Type *base, unsigned char *buf,
                    unsigned int size);
unsigned char i2c_master_transfer(I2C_Type *base,
                                struct i2c_transfer *xfer);

#endif
/* bsp_i2c.c */
#include "bsp_i2c.h"
#include "bsp_delay.h"
#include "stdio.h"

/*
初始化 I2C,波特率 100KHZ
1. 关闭 I2C
2. 设置分频
3. 开启 I2C
*/
void i2c_init(I2C_Type *base)
{
    /* 1、配置 I2C */
    base->I2CR &= ~(1 << 7); /* 要访问 I2C 的寄存器,首先需要先关闭 I2C */

    /* 设置波特率为 100K
    * I2C 的时钟源来源于 IPG_CLK_ROOT=66Mhz
    * IFDR 设置为 0X15,也就是 640 分频,
    * 66000000/640=103.125KHz≈100KHz。
    */
    base->IFDR = 0X15 << 0;
    /* 设置寄存器 I2CR,开启 I2C */
    base->I2CR |= (1<<7);   
}

/* 发送重新开始信号 */
unsigned char i2c_master_repeated_start(I2C_Type *base,
                                    unsigned char address,
                                    enum i2c_direction direction)
{
    /* I2C 忙并且工作在从模式,跳出 */
    if(base->I2SR & (1 << 5) && (((base->I2CR) & (1 << 5)) == 0))
        return 1;

    /*
    * 设置寄存器 I2CR
    * bit[4]: 1 发送
    * bit[2]: 1 产生重新开始信号
    */
    base->I2CR |= (1 << 4) | (1 << 2);

    /*
    * 设置寄存器 I2DR, bit[7:0] : 要发送的数据,这里写入从设备地址
    */
    base->I2DR = ((unsigned int)address << 1) |
                ((direction == kI2C_Read)? 1 : 0);
    return 0;
}

/* 发送开始信号 */
unsigned char i2c_master_start(I2C_Type *base,
                            unsigned char address,
                               enum i2c_direction direction)
{
    if(base->I2SR & (1 << 5)) /* I2C 忙 */
        return 1;

    /*
    设置寄存器 I2CR
    bit[5]: 1 主模式
    bit[4]: 1 发送
    */
    base->I2CR |= (1 << 5) | (1 << 4);

    /*
    * 设置寄存器 I2DR, bit[7:0] : 要发送的数据,这里写入从设备地址
    */
    base->I2DR = ((unsigned int)address << 1) | ((direction == kI2C_Read)? 1 : 0);
    return 0;
}

/*  检查并清除错误 */
unsigned char i2c_check_and_clear_error(I2C_Type *base,
                                    unsigned int status)
{
    if(status & (1<<4))         /* 检查是否发生仲裁丢失错误 */
    {
        base->I2SR &= ~(1<<4);      /* 清除仲裁丢失错误位 */
        base->I2CR &= ~(1 << 7);    /* 先关闭 I2C */
        base->I2CR |= (1 << 7);     /* 重新打开 I2C */
        return I2C_STATUS_ARBITRATIONLOST;
    }
    else if(status & (1 << 0))      /* 没有接收到从机的应答信号 */
    {
        return I2C_STATUS_NAK; /* 返回 NAK(No acknowledge) */
    }
    return I2C_STATUS_OK;
}

/* 停止信号 */
unsigned char i2c_master_stop(I2C_Type *base)
{
    unsigned short timeout = 0XFFFF;

    /* 清除 I2CR 的 bit[5:3]这三位 */
    base->I2CR &= ~((1 << 5) | (1 << 4) | (1 << 3));
    while((base->I2SR & (1 << 5)))      /* 等待忙结束 */
    {
        timeout--;
        if(timeout == 0)    /* 超时跳出 */
            return I2C_STATUS_TIMEOUT;
    }
    return I2C_STATUS_OK;
}

/* 发送数据 */
void i2c_master_write(I2C_Type *base, const unsigned char *buf,
                    unsigned int size)
{
    while(!(base->I2SR & (1 << 7)));        /* 等待传输完成 */
    base->I2SR &= ~(1 << 1);                /* 清除标志位 */
    base->I2CR |= 1 << 4;                   /* 发送数据 */
    while(size--)
    {
        base->I2DR = *buf++;        /* 将 buf 中的数据写入到 I2DR 寄存器 */
        while(!(base->I2SR & (1 << 1)));    /* 等待传输完成 */
        base->I2SR &= ~(1 << 1);        /* 清除标志位 */

        /* 检查 ACK */
        if(i2c_check_and_clear_error(base, base->I2SR))
            break;
    }
    base->I2SR &= ~(1 << 1);
    i2c_master_stop(base);      /* 发送停止信号 */
}

/* 读取数据 */
void i2c_master_read(I2C_Type *base, unsigned char *buf,
                    unsigned int size)
{
    volatile uint8_t dummy = 0;

    dummy++;        /* 防止编译报错 */
    while(!(base->I2SR & (1 << 7)));    /* 等待传输完成 */
    base->I2SR &= ~(1 << 1);            /* 清除中断挂起位 */
    base->I2CR &= ~((1 << 4) | (1 << 3));   /* 接收数据 */
    if(size == 1)   /* 如果只接收一个字节数据的话发送 NACK 信号 */
        base->I2CR |= (1 << 3);

    dummy = base->I2DR;         /* 假读 */
    while(size--)
    {
        while(!(base->I2SR & (1 << 1)));    /* 等待传输完成 */
        base->I2SR &= ~(1 << 1);    /* 清除标志位 */

        if(size == 0)
            i2c_master_stop(base);  /* 发送停止信号 */
        if(size == 1)
            base->I2CR |= (1 << 3);
        *buf++ = base->I2DR;
    }
}

/* I2C 数据传输,包括读和写 */
unsigned char i2c_master_transfer(I2C_Type *base,
                                struct i2c_transfer *xfer)
{
    unsigned char ret = 0;
    enum i2c_direction direction = xfer->direction;

    base->I2SR &= ~((1 << 1) | (1 << 4));   /* 清除标志位 */
    while(!((base->I2SR >> 7) & 0X1)){};    /* 等待传输完成 */
    /* 如果是读的话,要先发送寄存器地址,所以要先将方向改为写 */
    if ((xfer->subaddressSize > 0) && (xfer->direction == kI2C_Read))
        direction = kI2C_Write;
    ret = i2c_master_start(base, xfer->slaveAddress, direction);
    if(ret)
        return ret;
    while(!(base->I2SR & (1 << 1))){};  /* 等待传输完成 */
    ret = i2c_check_and_clear_error(base, base->I2SR);
    if(ret)
    {
        i2c_master_stop(base);   /* 发送出错,发送停止信号 */
        return ret;
    }

    /* 发送寄存器地址 */
    if(xfer->subaddressSize)
    {
        do
        {
            base->I2SR &= ~(1 << 1);    /* 清除标志位 */
            xfer->subaddressSize--;     /* 地址长度减一 */
            base->I2DR = ((xfer->subaddress) >> (8 * xfer->subaddressSize));
            while(!(base->I2SR & (1 << 1)));    /* 等待传输完成 */
            /* 检查是否有错误发生 */
            ret = i2c_check_and_clear_error(base, base->I2SR);
            if(ret)
            {
                i2c_master_stop(base);  /* 发送停止信号 */
                return ret;
            }
        }while ((xfer->subaddressSize > 0) && (ret == I2C_STATUS_OK));

        if(xfer->direction == kI2C_Read)    /* 读取数据 */
        {
            base->I2SR &= ~(1 << 1);    /* 清除中断挂起位 */
            i2c_master_repeated_start(base, xfer->slaveAddress, kI2C_Read);
            while(!(base->I2SR & (1 << 1))){}; /* 等待传输完成 */

            /* 检查是否有错误发生 */
            ret = i2c_check_and_clear_error(base, base->I2SR);
            if(ret)
            {
                ret = I2C_STATUS_ADDRNAK;
                i2c_master_stop(base);  /* 发送停止信号 */
                return ret;
            }
        }
    }

    /* 发送数据 */
    if ((xfer->direction == kI2C_Write) && (xfer->dataSize > 0))
        i2c_master_write(base, xfer->data, xfer->dataSize);
    /* 读取数据 */
    if ((xfer->direction == kI2C_Read) && (xfer->dataSize > 0))
        i2c_master_read(base, xfer->data, xfer->dataSize);
    return 0;
}

4. AP3216C 简介

AP3216C 支持 环境光强度(ALS)、接近距离(PS) 和 红外线强度(IR) 这三个环境参数检测

1685350466357

1685350539536

1685350556766

原理图

1685350670827

5. 配置 AP3216C

/* bsp_ap3216c.h */
#ifndef _BSP_AP3216C_H
#define _BSP_AP3216C_H

#include "imx6ul.h"

#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 数据高字节 */

/* 函数声明 */
unsigned char ap3216c_init(void);
unsigned char ap3216c_readonebyte(unsigned char addr,
                                unsigned char reg);
unsigned char ap3216c_writeonebyte(unsigned char addr,
                                unsigned char reg,
                                unsigned char data);
void ap3216c_readdata(unsigned short *ir, unsigned short *ps,
                    unsigned short *als);
#endif
/* bsp_ap3216c.c */
#include "bsp_ap3216c.h"
#include "bsp_i2c.h"
#include "bsp_delay.h"
#include "cc.h"
#include "stdio.h"

/* 初始化 AP3216C */
unsigned char ap3216c_init(void)
{
    unsigned char data = 0;

    /* 1、 IO 初始化,配置 I2C IO 属性
    * I2C1_SCL -> UART4_TXD
    * I2C1_SDA -> UART4_RXD
    */
    IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL, 1);
    IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA, 1);
    IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_I2C1_SCL, 0x70B0);
    IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_I2C1_SDA, 0X70B0);

    /* 2、初始化 I2C1 */
    i2c_init(I2C1);

    /* 3、初始化 AP3216C */
    /* 复位 AP3216C */
    ap3216c_writeonebyte(AP3216C_ADDR, AP3216C_SYSTEMCONG, 0X04);
    delayms(50); /* AP33216C 复位至少 10ms */

    /* 开启 ALS、 PS+IR */
    ap3216c_writeonebyte(AP3216C_ADDR, AP3216C_SYSTEMCONG, 0X03);

    /* 读取刚刚写进去的 0X03 */
    data = ap3216c_readonebyte(AP3216C_ADDR, AP3216C_SYSTEMCONG);

    if(data == 0X03)
        return 0;   /* AP3216C 正常 */
    else
        return 1;   /* AP3216C 失败 */
}

/* 向 AP3216C 写入数据 */
unsigned char ap3216c_writeonebyte(unsigned char addr,
                                unsigned char reg,
                                unsigned char data)
{
    unsigned char status=0;
    unsigned char writedata=data;
    struct i2c_transfer masterXfer;

    /* 配置 I2C xfer 结构体 */
    masterXfer.slaveAddress = addr;     /* 设备地址 */
    masterXfer.direction = kI2C_Write;  /* 写入数据 */
    masterXfer.subaddress = reg;        /* 要写入的寄存器地址 */
    masterXfer.subaddressSize = 1;      /* 地址长度一个字节 */
    masterXfer.data = &writedata;       /* 要写入的数据 */
    masterXfer.dataSize = 1;            /* 写入数据长度 1 个字节 */

    if(i2c_master_transfer(I2C1, &masterXfer))
        status=1;

    return status;
}

/* 从 AP3216C 读取一个字节的数据 */
unsigned char ap3216c_readonebyte(unsigned char addr,
                                unsigned char reg)
{
    unsigned char val=0;

    struct i2c_transfer masterXfer;
    masterXfer.slaveAddress = addr;     /* 设备地址 */
    masterXfer.direction = kI2C_Read;   /* 读取数据 */
    masterXfer.subaddress = reg;        /* 要读取的寄存器地址 */
    masterXfer.subaddressSize = 1;      /* 地址长度一个字节 */
    masterXfer.data = &val;             /* 接收数据缓冲区 */
    masterXfer.dataSize = 1;            /* 读取数据长度 1 个字节 */
    i2c_master_transfer(I2C1, &masterXfer);

    return val;
}

/* 读取 AP3216C 的原始数据,包括 ALS,PS 和 IR, 注意!如果
 * 同时打开 ALS,IR+PS 两次数据读取的时间间隔要大于 112.5ms 
 */
void ap3216c_readdata(unsigned short *ir, unsigned short *ps,
                    unsigned short *als)
{
    unsigned char buf[6];
    unsigned char i;

    /* 循环读取所有传感器数据 */
    for(i = 0; i < 6; i++)
    {
        buf[i] = ap3216c_readonebyte(AP3216C_ADDR, AP3216C_IRDATALOW + i);
    }

    if(buf[0] & 0X80)    /* IR_OF 位为 1,则数据无效 */
        *ir = 0;
    else                /* 读取 IR 传感器的数据 */
        *ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);

    *als = ((unsigned short)buf[3] << 8) | buf[2];  /* 读取 ALS 数据 */
    if(buf[4] & 0x40)   /* IR_OF 位为 1,则数据无效 */
        *ps = 0;
    else                /* 读取 PS 传感器的数据 */
        *ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
}

6. Linux I2C 驱动框架

Linux 内核将 I2C 驱动分成两部分:

6.1 I2C 总线驱动(I2C 适配器)

I2C 总线驱动的重点是 I2C 适配器驱动(SOC 的 I2C 接口控制器)

两个重要结构体: i2c_adapter 和 i2c_algorithm

Linux 将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter (include/linux/i2c.h)

1685352607762

i2c_algorithm ,对外提供读写 API 函数,设备驱动程序使用这些 API 来完成读写操作, 即 I2C 适配器 和 I2C 设备进行通信的方法 (include/linux/i2c.h)

1685352794087

/* 注册 */
int i2c_add_adapter(struct i2c_adapter *adapter)    // 使用动态的总线号
int i2c_add_numbered_adapter(struct i2c_adapter *adap)  // 使用静态的总线号

返回值:0, 成功; 负值 失败;

/* 删除 */
void i2c_del_adapter(struct i2c_adapter * adap)

6.2 I2C 设备驱动

两个重要结构体:i2c_client (描述设备信息)和 i2c_driver(描述驱动内容)

I2C 设备驱动编写,重点就是构建 i2c_driver,然后向内核注册这个 i2c_driver

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
owner:一般为 THIS_MODULE
driver:要注册的结构体
返回值:0 成功;负值 失败

/* 也可以用这个注册 */
#define i2c_add_driver(driver) \
    i2c_register_driver(THIS_MODULE, driver)

void i2c_del_driver(struct i2c_driver *driver)      // 注销 i2c_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 xxx_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);

6.3 I2C 设备和驱动匹配过程

I2C 设备和驱动匹配的过程由 I2C 核心来完成, drivers/i2c.i2c-core.c 就是 I2C 核心,提供了一套与硬件无关的 API

/* i2c_adapter 注册/注销 函数 */
int i2c_add_adapter(struct i2c_adapter *adapter);
int i2c_add_numbered_adapter(struct i2c_adapter *adap);
void i2c_del_adapter(struct i2c_adapter * adap);

/* i2c_driver 注册/注销函数 */
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
int i2c_add_driver (struct i2c_driver *driver);
void i2c_del_driver(struct i2c_driver *driver);

设备和驱动的匹配过程是由 I2C 总线来完成的,I2C总线的数据结构为 i2c_bus_type(类似于 platform,只不过其是虚拟总线);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,
};

.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;

    /* 完成设备树设备和驱动匹配 */
    if (of_driver_match_device(dev, drv))
        return 1;

    /* ACPI 形式的匹配 */
    if (acpi_driver_match_device(dev, drv))
        return 1;

    driver = to_i2c_driver(drv);

    /* 用于传统的、无设备树的 I2C 设备和驱动匹配过程 */
    if (driver->id_table)
        return i2c_match_id(driver->id_table, client) != NULL;

    return 0;
}

.probe 函数 在 设备和驱动匹配成功之后,就会执行;主要工作内容:

1685363004250

1685363103736

1685363134898

1685363149672

其中 i2c_imx_algo:

static struct i2c_algorithm i2c_imx_algo = {
    .master_xfer = i2c_imx_xfer,
    .functionality = i2c_imx_func,      // 用于返回此 I3C 适配器支持什么样的通信协议
};
static u32 i2c_imx_func(struct i2c_adapter *adapter)
{
    return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL
    | I2C_FUNC_SMBUS_READ_BLOCK_DATA;
}

i2c_imx_xfer 完成与 I2C 设备通信的

1685363470856

1685363513594

7. I2C 设备驱动编写流程

7.1 I2C 设备信息描述

1. 未使用设备树

未使用设备树的时候需要在 BSP 里面使用 i2c_board_info 结构体来描述一个 I2C 设备

1685363689322

#define I2C_BOARD_INFO(dev_type, dev_addr) \
    .type = dev_type, .addr = (dev_addr)

static struct i2c_board_info mx27_3ds_i2c_camera = {
    I2C_BOARD_INFO("ov2640", 0x30),
};

2. 使用设备树的时候

I2C1 上接了 mag3110 这个磁力计芯片,因此必须在 i2c1 节点下创建 mag3110 子节点,然后在这个子节点内描述 mag3110 这个芯片的相关信息

&i2c1 {
    clock-frequency = <100000>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c1>;
    status = "okay";

    mag3110@0e {
        compatible = "fsl,mag3110";     // 用于匹配驱动(重点!!!)
        reg = <0x0e>;       // 设置 mag3110 的器件地址(重点!!!)
        position = <2>;
    };
...
};

7.2 I2C 设备数据收发流程

i2c_transfer 用于对 I2C 设备寄存器进行读写操作;i2c_transfer 会调用 I2C 适配器中的 i2c_algorithm 里面的 master_xfer 函数(I.MX SOC来说是 i2c_imx_xfer)

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
adapt:所使用的 I2C 适配器
msgs:I2C 要发送的一个或者多个消息
num:消息数量,msgs 的数量
返回值: 负值,失败;非负值,发送的 msgs 的数量

Linux 内核使用 i2c_msg 结构体 来描述一个消息(include/uapi/linux/i2c.h)

struct i2c_msg {
    __u16 addr; /* 从机地址 */
    __u16 flags; /* 标志 */
    #define I2C_M_TEN 0x0010
    #define I2C_M_RD 0x0001
    #define I2C_M_STOP 0x8000
    #define I2C_M_NOSTART 0x4000
    #define I2C_M_REV_DIR_ADDR 0x2000
    #define I2C_M_IGNORE_NAK 0x1000
    #define I2C_M_NO_RD_ACK 0x0800
    #define I2C_M_RECV_LEN 0x0400
    __u16 len; /* 消息(本 msg)长度 */
    __u8 *buf; /* 消息数据 */
};
/* 使用 i2c_transfer 进行数据收发 demo */
/* 设备结构体 */
struct xxx_dev {
    ......
    void *private_data; /* 私有数据,一般会设置为 i2c_client */
};

/* 读取 I2C 设备多个寄存器数据 */
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 设备多个寄存器写入数据 */
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);
}

两个封装 i2c_transfer 的 API

int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
client:I2C 设备对应的 i2c_client
buf:要发送的数据
count:要发送的数据字节数,要小于64KB
返回值:负值,失败;其他非负值,发送的字节数

int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
client:I2C 设备对应的 i2c_client
buf:要接收的数据
count:要接收的数据字节数,要小于64KB
返回值:负值,失败;其他非负值,发送的字节数

8. 以 AP3216C 为例,编写 I2C 设备驱动

8.0 电路原理图

1685350670827

8.1 修改设备树

端口复用, IO修改或添加

pinctrl_i2c1: i2c1grp {
    fsl,pins = <
        MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0    // pinctrl 端口复用
        MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0    // pinctrl 端口复用
    >;
};

i2c1 节点追加 ap3216c 子节点

&i2c1 {
    clock-frequency = <100000>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c1>;
    status = "okay";

    ap3216c@1e {
        compatible = "alientek,ap3216c";
        reg = <0x1e>;
    };
};

make dtbs 编译设备树后;可在 /sys/bus/i2c/devices/0-001e/ 看到设备名字

8.2 AP3216C 驱动编写

/* ap3216creg.h */
/* 一些寄存器定义 */
#ifndef AP3216C_H
#define AP3216C_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 数据高字节 */

#endif
/* ap3216c.c */
#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; /* cdev */
    struct class *class; /* 类 */
    struct device *device; /* 设备 */
    struct device_node *nd; /* 设备节点 */
    int major; /* 主设备号 */
    void *private_data; /* 私有数据 */
    unsigned short ir, als, ps; /* 三个光传感器数据 */
};

static struct ap3216c_dev ap3216cdev;

/* 从 ap3216c 读取多个寄存器数据 */
tatic 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 = &reg;              /* 读取的首地址 */
    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;
}

/* 向 ap3216c 多个寄存器写入数据 */
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);
}

/* 读取 ap3216c 指定寄存器值,读取一个寄存器 */
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
    u8 data = 0;

    ap3216c_read_regs(dev, reg, &data, 1);
    return data;

#if 0
    struct i2c_client *client = (struct i2c_client *) dev->private_data;
    return i2c_smbus_read_byte_data(client, reg);
#endif
}

/* 向 ap3216c 指定寄存器写入指定的值,写一个寄存器 */
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);
}

/*  
读取 AP3216C 的数据,读取原始数据,包括 ALS,PS 和 IR,
同时打开 ALS,IR+PS 的话两次数据读取的间隔要大于 112.5ms
*/
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);
}

/* 打开设备 */
static int ap3216c_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &ap3216cdev;

    /* 初始化 AP3216C */
    ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04);
    mdelay(50);     /* AP3216C 复位最少 10ms */
    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;
}

/* AP3216C 操作函数 */
static const struct file_operations ap3216c_ops = {
    .owner = THIS_MODULE,
    .open = ap3216c_open,
    .read = ap3216c_read,
    .release = ap3216c_release,
};

/* 
i2c 驱动的 probe 函数,当驱动与设备匹配以后此函数就会执行
*/
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;
}

/*
i2c 驱动的 remove 函数,移除 i2c 驱动此函数会执行
*/
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;
}

/* 传统匹配方式 ID 列表 */
static const struct i2c_device_id ap3216c_id[] = {
    {"alientek,ap3216c", 0},
    {}
};

/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {
    { .compatible = "alientek,ap3216c" },
    { /* Sentinel */ }
};

/* i2c 驱动结构体 */
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);
}

module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lvqifeng");
/* 测试 ap3216cApp.c */
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    int fd;
    char *filename;
    unsigned short databuf[3];
    unsigned short ir, als, ps;
    int ret = 0;

    if (argc != 2) 
    {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];
    fd = open(filename, O_RDWR);
    if(fd < 0)
    {
        printf("can't open file %s\r\n", filename);
        return -1;
    }

    while (1) 
    {
        ret = read(fd, databuf, sizeof(databuf));
        if(ret == 0)
        {
            /* 数据读取成功 */
            ir = databuf[0];    /* ir 传感器数据 */
            als = databuf[1];   /* als 传感器数据 */
            ps = databuf[2];    /* ps 传感器数据 */
            printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);
        }
        usleep(200000);     /* 200ms */
    }
    close(fd);  /* 关闭文件 */
    return 0;
}

1685372239336

LQF376 commented 1 year ago

I2C 驱动

I2C 驱动分为了 总线驱动(SOC的I2C控制器驱动、适配器驱动) 和 设备驱动(针对具体 I2C 设备而编写的驱动)

I2C总线驱动

从总体上来看,I2C 总线驱动是一个标准的 platform 驱动

重点在 i2c_adapter,内核将 SOC 的 I2C 适配器抽象成 i2c_adapter;其内部有一个结构体 i2c_algorithm 提供适配器对外读写数据的 API 接口

struct i2c_adapter{
    ...
    const struct i2c_algorithm *algo;   // 提供对外数据读写的 API
    ...
}

i2c_algorithm 中的 master_xfer,即 I2C 适配器的传输函数

struct i2c_algorithm{
    ...
    /* I2C 适配器的传输函数 */
    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);
    ...
}

IMX6ULL 的 I2C 适配器驱动(一般由SOC 厂商编写完成)

设备树信息(imx6ull.dtsi)

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";
};

驱动部分是一个标准的 platform 驱动类型;

i2c_imx_probe 主要工作内容:

  1. 初始化 i2c_adapter,设置 i2c_algorithm 为 i2c_imx_algo,最后向内核注册 i2c_adapter
  2. 初始化 I2C1 控制器的相关寄存器
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 struct i2c_algorithm i2c_imx_alfo = {
    .master_xfer = i2c_imx_xfer,    // 
    .functionality = i2c_imx_func,  // 此 I2C 适配器支持什么样的通信协议
};

/* I2C 通信接口;对外 API 接口(设备驱动会调用) */
static int i2c_imx_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num);

I2C 设备驱动(需要根据具体I2C设备去编写)

设备驱动 是 I2C 总线驱动模型,重点在 i2c_client(描述设备信息)和 i2c_driver (描述驱动内容)

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_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个 i2c_client

i2c_driver 对于 I2C 设备驱动编写来说是重点,构建完 i2c_driver 后需要向内核进行注册

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)
{
    return i2c_add_driver(&xxx_driver);
}

mag3110 磁力芯片的设备驱动分析

设备信息描述,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;
};

使用设备树情况下

&i2c1{
    clock-frequency = <100000>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c1>;
    status = "okay";

    mag3110@0e {
        compatible = "fsl,mag3110";     // 重点
        reg = <0x0e>;           // 重点
        position = <2>;
    };
}

内核使用 i2c_msg 结构体表示一个消息

struct i2c_msg {
    __u16 addr;     // 从机地址
    __u16 flags;    // 标志 (发送数据还是读数据)
    __u16 len;      // 消息长度
    __u8 *buf;      // 消息内容
}

构建好 i2c_msg,之后,便可以使用 i2c_transfer 进行 I2C 数据收发

struct xxx_dev {
    ...
    void *private_data;     // 一般设置为 i2c_client 
};
/* 读多条数据 */
static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val, int len)
{
    msg[0].addr = client->addr;
    msg[0].flags = 0;       // 发送数据
    msg[0].buf = &reg;      // 读取的首地址
    msg[0].len = 1;         // reg 的长度

    msg[1].addr = client->addr;
    msg[1].flag = I2C_M_RD;
    msg[1].buf = val;
    msg[1].len = len;

    i2c_transfer(client->adapter, msg, 2);
}
/* 多个寄存器写入数据 */
static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf, u8 len)
{
    u8 b[256];

    b[0] = reg;
    memcpy(&b[1], buf, len);

    msg.addr = client->addr;
    msg.flag = 0;       // 写数据
    msg.buf = b;
    msg.len = len + 1;

    return i2c_transfer(client->adapter, &msg, 1);
}
LQF376 commented 1 year ago

ap3216c 设备 I2C 驱动(设备驱动)

设备树添加 ap3216c

&i2c1 {
    clock-frequency = <100000>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c1>;
    status = "okay";

    ap3216c@1e {
        compatible = "alientek,ap3216c";
        reg = <0x1e>;
    };
};

驱动部分

struct ap3216c_dev{
    ...
    void *private_data;
    unsigned short ir, als, ps;
};

static struct ap3216c_dev ap3216cdev;

/* 读取多个寄存器数据,底层调用 i2c_transfer 发送数据 */
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len);

/* 往多个寄存器写入数据,底层调用 i2c_transfer 发送数据 */
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len);

/* 读取一个寄存器的数据,底层调 ap3216c_read_regs(dev, reg, &data, 1); */
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg);

/* 向指定寄存器写入数据,底层 ap3216c_write_regs(dev, reg, &buf, 1) */
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data);

/* 读取 ir ps als 数据寄存器内的数据 */
void ap3216c_readdata(struct ap3216c_dev *dev);

/* 打开设备 file_operations 成员 */
static int ap3216c_open(struct inode *inode, struct file *filp);

/* file_operations 中的 read */
static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
    ap3216c_readdata(dev);

    copy_to_user(buf, data, sizeof(data));
}

/* file_opeartions 中的 release */
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. 构建设备号
    // 2. 注册设备  (会绑定 file_operations)
    // 3. 创建类
    // 4. 创建设备
    // 5. ap3216cdev.private_data = client
}
/* 移除函数,完成注销相关操作 */
static int ap3216c_remove(struct i2c_client *client);

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)
{
    return  i2c_add_driver(&ap3216c_driver);
}

static void __exit ap3216c_exit(void)
{
    i2c_del_driver(&ap3216c_driver);
}