build_embed_linux_system

regmap框架和使用说明

了解过I2C、SPI这类接口,可以看到都是通过总线连接,且以寄存器的形式去访问器件中的数据。那么这些访问能否抽象成统一的接口,以读地址的方式去读写相应的数据?regmap框架正是在这一思路下实现,用于屏蔽访问接口差异,以寄存器的方式统一进行处理。

本节regmap框架的使用和说明,具体目录如下所示。

📑 系统框架结构
📑 接口和结构功能说明
📑 驱动接口实现
📑 总结说明

系统框架结构

对于regmap系统来说,其框架结构如下所示。

image

🚀 底层物理总线:regmap框架对不同的物理总线进行了封装。支持的主要物理总线包括I2C、SPI、MMIO(内存映射I/O)等。这一层负责提供与具体物理总线相关的操作函数
🚀 regmap核心层:regmap框架的核心功能,包括寄存器的访问、缓存管理等。开发人员不需要关心这一层的具体实现细节
🚀 regmap API抽象层:向驱动开发人员提供了统一的API接口,用于操作具体的芯片设备。驱动开发时使用这些API接口来读写寄存器、更新寄存器的位等

regmap抽象封装SPI、I2C、mmio(Memory Map I/O)、mdio、spmi(System Power management Interface)等硬件接口,统一使用寄存器的方式进行访问。

对于支持regmap的驱动,其在内核中的结构如下所示。

image

可以看到,regmap框架只是取代了驱动中I2C、SPI接口读写的部分;对于设备树、设备注册、应用访问并没有区别。使用regmap框架来开发驱动时,它本身是什么类型的设备,其处理流程并没有变化。

对于I2C接口的传感器,仍然要实现设备树、创建字符设备、实现操作接口,这部分是一致的。只是操作接口访问寄存器读写时,将原先的硬件总线接口替换成regmap_xxx接口;

和总线接口配置类似,regmap也需要调用相应的init初始化用于适配器件特性。基于前述知识,可以把regmap看作对总线接口更高层次的封装;使用regmap框架时,要对原有硬件总线接口有一个清晰的认知,才能更好的使用这也是理解regmap框架的基础。

接口和结构功能说明

关于regmap的相关接口,主要如下所示。

devm_regmap_init_spi:初始化SPI对应的regmap接口
devm_regmap_init_i2c:初始化I2C对应的regmap接口
devm_regmap_init_mdio:初始化mido对应的regmap接口
devm_regmap_init_mmio:初始化mmio对应的regmap接口
devm_regmap_init_spmi_base:初始化spmi对应得regmap接口
regmap_read:读取单个寄存器的值
regmap_bulk_read:批量读取指定数据长度的寄存器值(地址连续递增)
regmap_raw_read:读取原始值,值不经过config的配置处理
regmap_noinc_read:批量写入寄存器,但不增加地址位置
regmap_write:写入单个寄存器的值
regmap_write_async:异步写入单个寄存器
regmap_bulk_write:批量写入指定数据长度的寄存器值(地址连续递增)
regmap_update_bits_base:更新单个寄存器的指定位

这里列出使用到的类型接口和功能说明,下面列出进一步的访问接口说明。

// 为SPI设备创建并初始化一个由devm管理的regmap对象
// @dev: SPI设备对象
// @config: 配置参数,定义SPI设备的访问格式
// @lock_key: 用于Linux内核锁调试机制(lockdep),驱动无需关注
// @lock_name: 内部锁名称,用于lockdep调试显示, 驱动无需关注
// @返回:返回struct regmap指针,失败返回ERR_PTR(errno)
#define devm_regmap_init_spi(dev, config) __regmap_lockdep_wrapper(__devm_regmap_init_spi, #config, dev, config)
struct regmap *__devm_regmap_init_spi(struct spi_device *dev,
            const struct regmap_config *config,
            struct lock_class_key *lock_key,
            const char *lock_name);

// 为I2C设备创建并初始化一个由devm管理的regmap对象
// @dev: I2C设备对象
// @config: 配置参数,定义I2C设备的访问格式
// @lock_key: 用于Linux内核锁调试机制(lockdep),驱动无需关注
// @lock_name: 内部锁名称,用于lockdep调试显示, 驱动无需关注
// @返回:返回struct regmap指针,失败返回ERR_PTR(errno)
#define devm_regmap_init_i2c(dev, config) __regmap_lockdep_wrapper(__devm_regmap_init_i2c, #config, dev, config)
struct regmap *__devm_regmap_init_i2c(struct i2c_client *i2c,
                    const struct regmap_config *config,
                    struct lock_class_key *lock_key,
                    const char *lock_name);

// 通过regmap框架读取设备指定寄存器的值。
// @map: regmap对象指针
// @reg: 要读取的寄存器的地址
// @val: 寄存器读取返回的值
// @返回: 成功返回0,失败返回负的错误码
int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val);

// 通过regmap框架读取设备指定的一组寄存器的值。
// @map: regmap对象指针
// @reg: 要读取的寄存器的首地址
// @val: 寄存器读取返回数据的指针首地址
// @val_count: 需要读取的值的数量
// @返回: 成功返回0,失败返回负的错误码
int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, size_t val_count);

// 通过regmap框架向设备指定寄存器的写入值。
// @map: regmap对象指针
// @reg: 要写入的寄存器的地址
// @val: 寄存器读取返回的值
// @返回: 成功返回0,失败返回负的错误码
int regmap_write(struct regmap *map, unsigned int reg, unsigned int val);

// 通过regmap框架向设备指定寄存器带掩码的写入值。
// @map: 管理regmap的格式
// @reg: 寄存器的首地址
// @mask: 设置掩码,设置为1的位才会被修改
// @val: 需要更新的值
// @返回: 成功返回0,失败返回负的错误码
int regmap_update_bits(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val)

// 通过regmap框架向设备指定的一组寄存器写入值。
// @map: regmap对象指针
// @reg: 要写入的寄存器的首地址
// @val: 寄存器写入数据的指针首地址
// @val_count: 需要写入的值的数量
// @返回: 成功返回0,失败返回负的错误码
int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val, size_t val_count);

可以看到,对于接口中,最重要的结构就是用于初始化的struct regmap_config和保存信息的struct regmap属性;其中struct regmap大部分都是config中的信息,因此这里以regmap_config进行解释。

regmap_config的详细说明如下所示。

// regmap_config 
// 用于初始化,管理regmap_config特性的配置项
struct regmap_config {
    const char *name;       // 可选的regmap名称。当一个设备有多个寄存器区域时很有用

    int reg_bits;           // *必选,寄存器地址的长度*
    int reg_stride;         // 寄存器地址步长,用于多字节操作时地址递增的个数
    int reg_downshift;      // 执行任何操作前,寄存器地址下移的数目
    unsigned int reg_base;  // 执行任何操作前,寄存器需要增加的基地址
    int pad_bits;           // 寄存器和值之间的填充位数
    int val_bits;           // **必选,一个寄存器值的长度**

    bool (*writeable_reg)(struct device *dev, unsigned int reg);        //回调函数,确定寄存器是否可写
    bool (*readable_reg)(struct device *dev, unsigned int reg);         //回调函数,确定寄存器是否可读
    bool (*volatile_reg)(struct device *dev, unsigned int reg);         //回调函数,确定寄存器是否每次都必须实际读取
    bool (*precious_reg)(struct device *dev, unsigned int reg);         //回调函数,返回寄存器是否必须在驱动中读取
    bool (*writeable_noinc_reg)(struct device *dev, unsigned int reg);  //回调函数,返回寄存器是否写不自增
    bool (*readable_noinc_reg)(struct device *dev, unsigned int reg);   //回调函数,返回寄存器是否读不自增

    bool disable_locking;       //是否关闭内部保护,关闭后不能够从多线程访问
    bool disable_debugfs;       //可选,不要为这个regmap创建debugfs条目
    regmap_lock lock;           //可选的用于回调函数的访问锁
    regmap_unlock unlock;       //用于访问锁的释放
    void *lock_arg;             //lock/unlock传递的数据

    int (*reg_read)(void *context, unsigned int reg, unsigned int *val); //可选的回调,如果填充,将用于执行读操作在总线上不能表示为简单读操作的设备,如SPI、I2C等。
    int (*reg_write)(void *context, unsigned int reg, unsigned int val); //可选的回调,如果填充,将用于执行读操作在总线上不能表示为简单写操作的设备,如SPI、I2C等。
    int (*reg_update_bits)(void *context, unsigned int reg,
                    unsigned int mask, unsigned int val);               //可选的回调函数,如果填充则执行所有update_bits(rmw)操作。
    /* Bulk read/write */
    int (*read)(void *context, const void *reg_buf, size_t reg_size,    //可选的回调,用于自定义的批量读数据操作
            void *val_buf, size_t val_size);
    int (*write)(void *context, const void *data, size_t count);        //可选的回调,用于自定义的批量写数据操作
    size_t max_raw_read;                                                //单次允许的最大读取长度
    size_t max_raw_write;                                               //单次允许的最大写入长度

    bool fast_io;                                                       //是否使用自旋锁代替lock/unlock
    bool io_port;                                                       //支持IO端口访问器。只有当MMIO和IO端口访问可以区分时才有意义。

    unsigned int max_register;                                          //可选,指定最大有效的寄存器地址
    const struct regmap_access_table *wr_table;                         //指定寄存器的可写表格
    const struct regmap_access_table *rd_table;                         //指定寄存器的可读表格
    const struct regmap_access_table *volatile_table;                   //指定寄存器的可缓存表格
    const struct regmap_access_table *precious_table;                   //指定寄存器是否只能驱动访问的表格
    const struct regmap_access_table *wr_noinc_table;                   //定义寄存器写入是否自增的表格
    const struct regmap_access_table *rd_noinc_table;                   //定义寄存器读取是否自增的表格
    const struct reg_default *reg_defaults;             //寄存器的默认值(上电)
    unsigned int num_reg_defaults;                      //寄存器默认值的数目
    enum regcache_type cache_type;                      //实际的缓存类型
    const void *reg_defaults_raw;                       //寄存器上电的复位值数组(用于支持寄存器缓存)
    unsigned int num_reg_defaults_raw;                  //复位值数组的长度

    unsigned long read_flag_mask;                       //**在读操作时,在寄存器的顶部字节设置掩码(需要根据器件确定)**
    unsigned long write_flag_mask;                      //**在写操作时,在寄存器的顶部字节设置掩码(需要根据器件确定**
    bool zero_flag_mask;                                //如果设置,即使read_flag_mask和write_flag_mask都是空的,也会使用它们。

    bool use_single_read;                               //是否将多次读取转换为单次读取
    bool use_single_write;                              //是否将多次写入转换为单次写入    
    bool use_relaxed_mmio;                              //定义mmio操作不使用内核屏障
    bool can_multi_write;                               //是否支持多写的批量写操作                    

    enum regmap_endian reg_format_endian;               //格式化寄存器地址的字节序。
    enum regmap_endian val_format_endian;               //格式化寄存器值的字节序

    const struct regmap_range_cfg *ranges;              //虚拟地址范围的配置项数组。
    unsigned int num_ranges;                            //虚拟地址内部的数目

    bool use_hwlock;                                    //说明是否应该使用硬件自旋锁。
    bool use_raw_spinlock;                              //说明是否应该使用原始自旋锁。
    unsigned int hwlock_id;                             //指定硬件自旋锁id
    unsigned int hwlock_mode;                           //硬件自旋锁模式,应该是HWLOCK_IRQSTATE、HWLOCK_IRQ或0。

    bool can_sleep;                                     //定义设备是否允许休眠
};

理解了这些,似乎就可以实现regmap的访问了。

然而使用regmap框架进行驱动开发的现实面临两大问题。

为什么这么多配置选项,在开发中主要配置有哪些
对于具体的I2C,SPI器件或者内部模块,该如何定义初始化配置,实现功能

这里解释下,前面说到regmap框架用于统一管理内部寄存器和硬件接口,访问相应器件。对于这类器件,除了接口,对于硬件上访问的差异是客观存在的,这里列出一部分。

器件单次读写长度不一致:例如传感器有1字节的(扩展芯片pcf8563)、 4字节的(内部模块寄存器)、还有一些64位芯片,内部模块寄存器需要8字节读取
器件单次支持读写的个数不一致:有的器件支持单次多读或单次多写,而有的器件每次只能写单个长度,不支持自动递增 ✅ 对于器件内部的寄存器也有读写权限的差异:包含只读(r-o),只写(w-o),可读可写(rw),写清除(wc),读清除(rc)等 ✅ 一些外部器件,地址位的某些位定义对器件的读/写方式

这些差异最终要反映到输出的电平信号上,也就需要软件来支持配置项去定义。对于开发者来说,配置项虽然多,不过大部分并不需要关系,常用的需要配置的选项如下所示。

📄 reg_bits: 必选配置,定义寄存器地址的bits数,1字节为8,2字节为16,依次类推
📄 val_bits: 必选配置,定义单次读取寄存器内部值得bits数,1字节为8,2字节为16,依次类推(注意:数据位数和寄存器地址位数没有必然联系,例如地址0x1800, 值0x01也是允许的,不过对于内部模块寄存器,数据位数和地址位数是一致的)
📄 reg_base: 访问寄存器时,定义的基地址,实际地址为(reg_base + reg_addr), 以实际地址0x120为例,如果定义基地址为0x100, 这样寄存器使用0x20既可以访问,这对于内部模块寄存器访问可以简化操作,大部分使用默认0即可
📄 reg_stride: 定义寄存器地址的步进字节,有效的寄存器地址是该值的倍数,设置为0,当成1使用。例如寄存器地址0x00,0x04, 0x08…,reg_bits位数就是8bit,stride步进就是4,而对于地址0x1c000000, 0x1c000001…,reg_bits位数就是32,stride的步进是1;地址0x1d000000,0x1d000004…,reg_bits位数就是32,stride的步进是4;可以看到,寄存器位数和步进并没有确定关系,不过寄存器的地址一定是步进的倍数
📄 reg_downshift: 访问寄存器时,寄存器默认右移的位数,大部分使用默认0即可
📄 read_flag_mask: 应对有些器件的寄存器通过指定bit的位置位表示读,寄存器地址发送时置相应位
📄 write_flag_mask: 应对有些器件的寄存器通过指定bit的位置位表示写,寄存器地址发送时置相应位
📄 max_registers: 指定最大有效的寄存器地址,为reg_base+max_register长度

至于其它参数,虽然也同样各有用途,不过只在特定器件或者希望附加功能时配置,使用默认的值也基本能实现功能,可以先不关注,遇到了在总结即可。

下面将以实际应用来分析regmap的实现,因为这些器件的注册和使用在其它章节都有详细说明,这里不在描述如何加载驱动,注册设备文件的实现,专注于封装操作硬件的接口。

驱动接口实现

regmap是对器件操作的封装,那么我们在使用时,最重要的就是理解器件说明中的关键信息。

对于硬件接口,主要关注的信息如下所示。

🚀 访问寄存器的字节数
🚀 寄存器数据的位数
🚀 读写是否需要不同的掩码(主要对于SPI设备,I2C设备会在使用init接口时自动处理)
🚀 是否支持多读,还是单个读取

这些都是在芯片手册定义的,这里以之前讲解过的ap3216、icm20608以及RTC芯片PCF8563进行说明。

regmap驱动实现(基于ap3216器件)

下面器件来自于ap3216c的手册,写入和读取协议如下。

image

image

参考文档说明,器件的特性如下。

🚀 操作为I2C接口
🚀 访问寄存器的字节数为8bit
🚀 寄存器内数据的位数为8bit
🚀 不支持单次多个读取,因此只能使用regmap_read/write接口,使用regmap_bulk_read/write接口,需要置位use_single_read

则其访问硬件的接口实现如下所示。

// 定义ap3216的配置接口
const struct regmap_config ap3216_regmap_config = {
    .reg_bits = 8,  // 寄存器地址位数,8bit(1Byte)
    .val_bits = 8,  // 寄存器数据位数,8bit(1Byte)
    //其它默认为0,可省略
    //不使用bulk接口,reg_stride可不设置,默认0,当作1
};

static int i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    //......

    //2.初始化regmap i2c控制结构
    chip->map = devm_regmap_init_i2c(client, &ap3216_regmap_config);
    if (IS_ERR(chip->map))
    {
        dev_err(&client->dev, "chip map init failed\n");
        return -ENXIO;
    }

    //......
}

static int read_values(struct ap3216_data* chip, int *readbuf)
{
    u8 i;
    int ret;

    // 通过regmap循环多次读取
    for (i = 0; i < 6; i++) {
        ret = regmap_read(chip->map, AP3216C_IRDATALOW + i, &readbuf[i]);
        if (ret) {
            dev_err(&chip->client->dev, "ap316_read err:%s, %d", __func__, ret);
            return -EIO;
        }
    }

    if (readbuf[0]&(1<<7)){
        chip->data.ir = 0;
    } else{
        chip->data.ir = ((unsigned short)readbuf[1] << 2) | (readbuf[0] & 0X03);
    }

    chip->data.als = ((unsigned short)readbuf[3] << 8) | readbuf[2];
    if (readbuf[4]&(1<<6)) {
        chip->data.ps = 0;
    } else {
        chip->data.ps = ((unsigned short)(readbuf[5] & 0X3F) << 4) | (readbuf[4] & 0X0F); 
    }

    return 0;
}

使用的接口如下。

📌 devm_regmap_init_i2c:用于初始化
📌 regmap_read:用于读取器件内部寄存器
📌 regmap_write:用于写入器件内部寄存器

详细的驱动代码实现如下所示。

📀 ap3216驱动实现:file/ch03-10/regmap_ap3216c/kernel_regmap_i2c.c

regmap驱动实现(基于pcf8563器件)

下面器件来自于pcf8563的手册,写入和读取协议如下。

image

image

参考文档说明,器件的特性如下。

🚀 操作为I2C接口 🚀 访问寄存器的字节数为8bit 🚀 寄存器内数据的位数为8bit 🚀 支持单次多个读取,可以使用regmap_bulk_read/write接口,当然也支持regmap_read/write

则其访问硬件的接口实现如下所示。

// 定义pcf8563的配置接口
const struct regmap_config rtc_regmap_config = {
    .reg_bits = 8,          // 寄存器地址位数,8bit(1Byte)
    .val_bits = 8,          // 寄存器数据位数,8bit(1Byte)
    .reg_stride = 1,        // 寄存器地址步长,1Byte,用于支持bulk_read/write
    .max_register = 255,    // 允许读取的最大寄存器地址
    //其它默认为0,可省略
};

static int i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    //......

    // 2.初始化regmap i2c控制结构
    chip->map = devm_regmap_init_i2c(client, &rtc_regmap_config);
    if (IS_ERR(chip->map))
    {
        dev_err(&client->dev, "chip map init failed\n");
        return -ENXIO;
    }
   
    //......
}

static int pcf8563_get_time(struct device *dev, struct rtc_time *tm)
{
    struct i2c_client *client = to_i2c_client(dev);
    struct pcf8563_data *chip = i2c_get_clientdata(client);
    unsigned char buf[9];
    int err;

    // 单次多字节读取寄存器值
    err = regmap_bulk_read(chip->map, PCF8563_REG_CONTROL1, buf, 9);
    if (err) {
        dev_err(&client->dev,
            "dev read block issue!.\n");
        return err;
    }

    if (buf[PCF8563_REG_SECONDS] & (1<<7)) {
        dev_err(&client->dev,
            "low voltage detected, date/time is not reliable.\n");
        return -EINVAL;
    }

    tm->tm_sec = bcdToDec(buf[PCF8563_REG_SECONDS] & 0x7F);
    tm->tm_min = bcdToDec(buf[PCF8563_REG_MINUTES] & 0x7F);
    tm->tm_hour = bcdToDec(buf[PCF8563_REG_HOURS] & 0x3F); /* rtc hr 0-23 */
    tm->tm_mday = bcdToDec(buf[PCF8563_REG_DAYS] & 0x3F);
    tm->tm_wday = buf[PCF8563_REG_WEEKDAYS] & 0x07;
    tm->tm_mon = bcdToDec(buf[PCF8563_REG_MONTHS] & 0x1F) - 1; /* rtc mn 1-12 */
    tm->tm_year = bcdToDec(buf[PCF8563_REG_YEARS]) + 100;
    chip->c_polarity = (buf[PCF8563_REG_MONTHS] & (1<<7)) ?
        (tm->tm_year >= 100) : (tm->tm_year < 100);

    dev_info(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d, "
        "mday=%d, mon=%d, year=%d, wday=%d\n",
        __func__,
        tm->tm_sec, tm->tm_min, tm->tm_hour,
        tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);

    return 0;
}

static int pcf8563_set_time(struct device *dev, struct rtc_time *tm)
{
    struct i2c_client *client = to_i2c_client(dev);
    struct pcf8563_data *chip = i2c_get_clientdata(client);
    unsigned char buf[9];
    int err;

    dev_info(&client->dev, "%s: secs=%d, mins=%d, hours=%d, "
        "mday=%d, mon=%d, year=%d, wday=%d\n",
        __func__,
        tm->tm_sec, tm->tm_min, tm->tm_hour,
        tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);

    /* hours, minutes and seconds */
    buf[PCF8563_REG_SECONDS] = decToBcd(tm->tm_sec);
    buf[PCF8563_REG_MINUTES] = decToBcd(tm->tm_min);
    buf[PCF8563_REG_HOURS] = decToBcd(tm->tm_hour);
    buf[PCF8563_REG_DAYS] = decToBcd(tm->tm_mday);
    buf[PCF8563_REG_WEEKDAYS] = tm->tm_wday & 0x07;

    /* month, 1 - 12 */
    buf[PCF8563_REG_MONTHS] = decToBcd(tm->tm_mon + 1);

    /* year and century */
    buf[PCF8563_REG_YEARS] = decToBcd(tm->tm_year - 100);
    if (chip->c_polarity ? (tm->tm_year >= 100) : (tm->tm_year < 100)) {
        buf[PCF8563_REG_MONTHS] |= (1<<7);
    }

    // 单次多字节写入寄存器值
    err =  regmap_bulk_write(chip->map, PCF8563_REG_SECONDS,
                buf + PCF8563_REG_SECONDS, 9 - PCF8563_REG_SECONDS);
    if (err) {
        dev_err(&client->dev, "dev read block issue!.\n");
        return err;  
    }
    return 0;
}

使用的接口如下。

📌 devm_regmap_init_i2c:用于初始化 📌 regmap_bulk_read:用于多字节读取器件内部寄存器 📌 regmap_bulk_write:用于多字节写入器件内部寄存器

详细的实现如下所示。

regmap驱动实现(基于icm20608器件)

关于SPI的配置说明来自于icm20608的手册,具体如下所示。

image

参考文档说明,器件的特性如下。

🚀 操作为SPI接口
🚀 访问寄存器的字节数为8bit
🚀 寄存器内数据的位数为8bit
🚀 支持单次多个读取,可以使用regmap_bulk_read/write接口,当然也支持regmap_read/write
🚀 寄存器地址的MSB位标识R/W,其中1表示读取

则其访问硬件的接口实现如下所示。

const struct regmap_config icm20608_regmap_config = {
    .reg_bits = 8,              // 寄存器地址位数,8bit(1Byte)
    .val_bits = 8,              // 寄存器地址位数,8bit(1Byte)
    .reg_stride = 1,            // 寄存器地址步长,1Byte,用于支持bulk_read/write
    .read_flag_mask = BIT(7),   // 寄存器地址MSB位标识R/W,其中1表示读取,0表示写入
    .max_register = 255,        // 允许读取的最大寄存器地址
    //其它默认为0,可省略
};

static int spi_hardware_init(struct spi_icm_data *chip)
{
    int data = 0;
    struct spi_device *spi;

    // 1.配置spi位regmap操作模式
    spi = (struct spi_device *)chip->private_data;
    chip->map = devm_regmap_init_spi(spi, &icm20608_regmap_config);
    if (IS_ERR(chip->map))
    {
        dev_err(&spi->dev, "chip map init failed\n");
    }

    // 2.复位芯片
    regmap_write(chip->map, ICM20_PWR_MGMT_1, 0x80);
    mdelay(50);
    regmap_write(chip->map, ICM20_PWR_MGMT_1, 0x01);
    mdelay(50);

    // 3.读取芯片ID
    regmap_read(chip->map, ICM20_WHO_AM_I, &data);
    dev_info(&spi->dev, "ICM20608 ID = %#X\n", data);

    // 4.解析设备树,写入寄存器配置信息
    icm20608_parse_dt(chip);
    regmap_write(chip->map, ICM20_SMPLRT_DIV, chip->reg_config.smplrt_div);
    regmap_write(chip->map, ICM20_GYRO_CONFIG, chip->reg_config.gyro_config);
    regmap_write(chip->map, ICM20_ACCEL_CONFIG, chip->reg_config.accel_config);
    regmap_write(chip->map, ICM20_CONFIG, chip->reg_config.config);
    regmap_write(chip->map, ICM20_ACCEL_CONFIG2, chip->reg_config.accel_config2);
    regmap_write(chip->map, ICM20_PWR_MGMT_2, chip->reg_config.pwr_mgmt_2);
    regmap_write(chip->map, ICM20_LP_MODE_CFG, chip->reg_config.lp_mode_cfg);
    regmap_write(chip->map, ICM20_FIFO_EN, chip->reg_config.fifo_en);

    return 0;
}

static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
    signed int data[7];
    int ret;
    unsigned char readbuf[14];
    struct spi_icm_data *chip = (struct spi_icm_data *)filp->private_data;
    struct spi_device *spi = (struct spi_device *)chip->private_data;

    // 读取寄存器的值
    ret = regmap_bulk_read(chip->map, ICM20_ACCEL_XOUT_H, readbuf, 14);
    if (ret < 0)
    {
        dev_err(&spi->dev, "icm20608 read failed:%d\n", ret);
        return 0;
    }

    // 读取数据转换内部格式,并提交到应用buffer内
    chip->data.accel_x_adc = (signed short)((readbuf[0] << 8) | readbuf[1]);
    chip->data.accel_y_adc = (signed short)((readbuf[2] << 8) | readbuf[3]);
    chip->data.accel_z_adc = (signed short)((readbuf[4] << 8) | readbuf[5]);
    chip->data.temp_adc    = (signed short)((readbuf[6] << 8) | readbuf[7]);
    chip->data.gyro_x_adc  = (signed short)((readbuf[8] << 8) | readbuf[9]);
    chip->data.gyro_y_adc  = (signed short)((readbuf[10] << 8) | readbuf[11]);
    chip->data.gyro_z_adc  = (signed short)((readbuf[12] << 8) | readbuf[13]);

    data[0] = chip->data.gyro_x_adc;
    data[1] = chip->data.gyro_y_adc;
    data[2] = chip->data.gyro_z_adc;
    data[3] = chip->data.accel_x_adc;
    data[4] = chip->data.accel_y_adc;
    data[5] = chip->data.accel_z_adc;
    data[6] = chip->data.temp_adc;
    ret = copy_to_user(buf, data, sizeof(data));
    if (ret < 0)
    {
        dev_err(&spi->dev, "copy_to_user failed:%d\n", ret);
        return ret;
    }

    return cnt;
}

详细的驱动代码实现如下所示。

📀 I2C20608驱动代码:file/ch03-10/regmap_icm202608/kernel_regmap_spi.c

总结说明

可以看到,regmap是对现有的总线访问接口的封装;通过统一处理,简化了驱动实现的难度。

从regmap的操作流程来看,都是先写寄存器地址,再写操作数据,这样就只能用于带寄存器的外部器件。对于部分I2C或SPI器件,不存在寄存器,只支持直接操作数据,这时候就不符合regmap的操作方式,只能使用基础的总线框架进行管理。为了应对开发中的复杂情况,开发人员仍然需要理解基础接口的实现方式,才能更方便的使用regmap实现具体功能。

返回目录

返回目录