Linux驱动开发笔记(二十三)—— regmap
资料:《【正点原子】I.MX6U开发指南V1.81.pdf》七十四章Linux 下大部分设备的驱动开发都是操作寄存器,既包括如I2C/SPI设备的设备寄存器,也包括PWM、定时器等芯片内部寄存器。Linux使用i2c_transfer来读写I2C设备寄存器,使用spi_write/spi_read读写SPI设备寄存器。I2C/SPI芯片又非常的多,因此Linux内核里充斥了大量的i2c_trans
视频:第35.1讲 Linux Regmap API实验-Regmap驱动框架详解_哔哩哔哩_bilibili
资料:《【正点原子】I.MX6U开发指南V1.81.pdf》七十四章
Linux 下大部分设备的驱动开发都是操作寄存器,既包括如I2C/SPI设备的设备寄存器,也包括PWM、定时器等芯片内部寄存器。
Linux使用i2c_transfer来读写I2C设备寄存器,使用spi_write/spi_read读写SPI设备寄存器。I2C/SPI芯片又非常的多,因此Linux内核里充斥了大量的i2c_transfer这类的冗余代码。
基于代码复用的原则,Linux内核引入了regmap模型,将寄存器访问的共同逻辑抽象出来,驱动开发人员不需要再去纠结使用SPI或者I2C接口,而是统一使用regmapAPI函数,降低了代码冗余,提高了驱动的可以移植性。
一、regmap
1.1 regmap结构体
Linux内核将regmap框架抽象为regmap结构体:
// 定义在drivers/base/regmap/internal.h
struct regmap {
union {
struct mutex mutex;
struct {
spinlock_t spinlock;
unsigned long spinlock_flags;
};
};
regmap_lock lock;
regmap_unlock unlock;
void *lock_arg; /* This is passed to lock/unlock functions */
struct device *dev; /* Device we do I/O on */
void *work_buf; /* Scratch buffer used to format I/O */
struct regmap_format format; /* Buffer format */
const struct regmap_bus *bus;
void *bus_context;
const char *name;
bool async;
spinlock_t async_lock;
wait_queue_head_t async_waitq;
struct list_head async_list;
struct list_head async_free;
int async_ret;
…………
unsigned int max_register;
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);
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;
int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
int (*reg_write)(void *context, unsigned int reg, unsigned int val);
bool defer_caching;
u8 read_flag_mask;
u8 write_flag_mask;
/* number of bits to (left) shift the reg value when formatting*/
int reg_shift;
int reg_stride;
/* regcache specific members */
const struct regcache_ops *cache_ops;
enum regcache_type cache_type;
/* number of bytes in reg_defaults_raw */
unsigned int cache_size_raw;
/* number of bytes per word in reg_defaults_raw */
unsigned int cache_word_size;
/* number of entries in reg_defaults */
unsigned int num_reg_defaults;
/* number of entries in reg_defaults_raw */
unsigned int num_reg_defaults_raw;
/* if set, only the cache is modified not the HW */
u32 cache_only;
/* if set, only the HW is modified not the cache */
u32 cache_bypass;
/* if set, remember to free reg_defaults_raw */
bool cache_free;
struct reg_default *reg_defaults;
const void *reg_defaults_raw;
void *cache;
/* if set, the cache contains newer data than the HW */
u32 cache_dirty;
/* if set, the HW registers are known to match map->reg_defaults */
bool no_sync_defaults;
struct reg_default *patch;
int patch_regs;
/* if set, converts bulk rw to single rw */
bool use_single_rw;
/* if set, the device supports multi write mode */
bool can_multi_write;
struct rb_root range_tree;
void *selector_work_buf; /* Scratch buffer used for selector */
};
1.2 regmap_config
用于regmap初始化。
// 定义在include/linux/regmap.h
struct regmap_config {
const char *name; //
int reg_bits; // 寄存器地址位数 必填!!!!
int reg_stride; // 寄存器地址步长
int pad_bits; // 寄存器和值之间的填充位数
int val_bits; // 寄存器值位数 必填!!!!
bool (*writeable_reg)(struct device *dev, unsigned int reg); // 可选的可写回调函数,寄存器可写的话此回调函数就会被调用,并返回 true
bool (*readable_reg)(struct device *dev, unsigned int reg); // 可选的可读回调函数,寄存器可读的话此回调函数就会被调用,并返回 true
bool (*volatile_reg)(struct device *dev, unsigned int reg); // 可选的回调函数,当寄存器值不能缓存的时候此回调函数就会被调用,并返回 true
bool (*precious_reg)(struct device *dev, unsigned int reg); // 当寄存器值不能被读出来的时候此回调函数会被调用,比如很多中断状态寄存器读清零,
// 读这些寄存器就可以清除中断标志位,但是并没有读出这些寄存器内部的值
regmap_lock lock;
regmap_unlock unlock;
void *lock_arg;
int (*reg_read)(void *context, unsigned int reg, unsigned int *val);// 可选的读操作回调函数,所有读寄存器的操作此回调函数就会执行
int (*reg_write)(void *context, unsigned int reg, unsigned int val);// 可选的写操作回调函数,所有写寄存器的操作此回调函数就会执行
bool fast_io; // 快速IO,使用spinlock替代mutex来提升锁性能
unsigned int max_register; // 有效的最大寄存器地址,可选
const struct regmap_access_table *wr_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 reg_default *reg_defaults; // 寄存器模式值,此结构体有两个成员变量:reg是寄存器地址,def是默认值
unsigned int num_reg_defaults; // 默认寄存器表中的元素个数
enum regcache_type cache_type; // 读标志掩码
const void *reg_defaults_raw; // 写标志掩码
unsigned int num_reg_defaults_raw;
u8 read_flag_mask; // 见1.2.1
u8 write_flag_mask; // 见1.2.1
bool use_single_rw;
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;
};
/**
* Configuration for the register map of a device.
*
* @name: Optional name of the regmap. Useful when a device has multiple
* register regions.
*
* @reg_bits: Number of bits in a register address, mandatory.
* @reg_stride: The register address stride. Valid register addresses are a
* multiple of this value. If set to 0, a value of 1 will be
* used.
* @pad_bits: Number of bits of padding between register and value.
* @val_bits: Number of bits in a register value, mandatory.
*
* @writeable_reg: Optional callback returning true if the register
* can be written to. If this field is NULL but wr_table
* (see below) is not, the check is performed on such table
* (a register is writeable if it belongs to one of the ranges
* specified by wr_table).
* @readable_reg: Optional callback returning true if the register
* can be read from. If this field is NULL but rd_table
* (see below) is not, the check is performed on such table
* (a register is readable if it belongs to one of the ranges
* specified by rd_table).
* @volatile_reg: Optional callback returning true if the register
* value can't be cached. If this field is NULL but
* volatile_table (see below) is not, the check is performed on
* such table (a register is volatile if it belongs to one of
* the ranges specified by volatile_table).
* @precious_reg: Optional callback returning true if the register
* should not be read outside of a call from the driver
* (e.g., a clear on read interrupt status register). If this
* field is NULL but precious_table (see below) is not, the
* check is performed on such table (a register is precious if
* it belongs to one of the ranges specified by precious_table).
* @lock: Optional lock callback (overrides regmap's default lock
* function, based on spinlock or mutex).
* @unlock: As above for unlocking.
* @lock_arg: this field is passed as the only argument of lock/unlock
* functions (ignored in case regular lock/unlock functions
* are not overridden).
* @reg_read: Optional callback that if filled will be used to perform
* all the reads from the registers. Should only be provided for
* devices whose read operation cannot be represented as a simple
* read operation on a bus such as SPI, I2C, etc. Most of the
* devices do not need this.
* @reg_write: Same as above for writing.
* @fast_io: Register IO is fast. Use a spinlock instead of a mutex
* to perform locking. This field is ignored if custom lock/unlock
* functions are used (see fields lock/unlock of struct regmap_config).
* This field is a duplicate of a similar file in
* 'struct regmap_bus' and serves exact same purpose.
* Use it only for "no-bus" cases.
* @max_register: Optional, specifies the maximum valid register index.
* @wr_table: Optional, points to a struct regmap_access_table specifying
* valid ranges for write access.
* @rd_table: As above, for read access.
* @volatile_table: As above, for volatile registers.
* @precious_table: As above, for precious registers.
* @reg_defaults: Power on reset values for registers (for use with
* register cache support).
* @num_reg_defaults: Number of elements in reg_defaults.
*
* @read_flag_mask: Mask to be set in the top byte of the register when doing
* a read.
* @write_flag_mask: Mask to be set in the top byte of the register when doing
* a write. If both read_flag_mask and write_flag_mask are
* empty the regmap_bus default masks are used.
* @use_single_rw: If set, converts the bulk read and write operations into
* a series of single read and write operations. This is useful
* for device that does not support bulk read and write.
* @can_multi_write: If set, the device supports the multi write mode of bulk
* write operations, if clear multi write requests will be
* split into individual write operations
*
* @cache_type: The actual cache type.
* @reg_defaults_raw: Power on reset values for registers (for use with
* register cache support).
* @num_reg_defaults_raw: Number of elements in reg_defaults_raw.
* @reg_format_endian: Endianness for formatted register addresses. If this is
* DEFAULT, the @reg_format_endian_default value from the
* regmap bus is used.
* @val_format_endian: Endianness for formatted register values. If this is
* DEFAULT, the @reg_format_endian_default value from the
* regmap bus is used.
*
* @ranges: Array of configuration entries for virtual address ranges.
* @num_ranges: Number of range configuration entries.
*/
虽然很多,但大部分都是平时用不到的。只有reg_bits和val_bits是必填。
read_flag_mask和write_flag_mask则参照1.2.1部分按需修改。
1.2.1 flag_mask 掩码设置
结构体regmap_config里面有两个关于掩码的成员变量:read_flag_mask 和 write_flag_mask,
icm20608使用spi接口的时候,读取寄存器时,地址最高位必须置1;写寄存器时,地址最高位要置0。因此这里就涉及到对寄存器地址最高位的操作,在之前SPI的驱动代码中,我们手动将寄存器地址的最高位置1,代码如下所示:
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len){
int ret;
struct spi_device *spi = (struct spi_device *)dev->private_data;
u8 tx = reg | 0x80; // 最高位=1 表示读
ret = spi_write_then_read(spi, &tx, 1, buf, len);
if (ret < 0)
pr_err("icm20608: spi_read reg 0x%x failed %d\n", reg, ret);
return ret;
}
通过使用mask,就不需要手动将寄存器地址的最高位bit7置1。
初始化regmap_config时直接将read_flag_mask设置为0X80,通过regmap读取SPI内部寄存器时就会将寄存器地址与read_flag_mask进行或运算,结果就是将bit7置1,整个过程不需要我们来操作,全部由regmap框架来完成的。
同理write_flag_mask用法也一样的,只是write_flag_mask用于写寄存器操作。
regmap_spi默认将read_flag_mask设置为0X80(如下面的代码)。如果使用的SPI设备不需要读掩码,在初始化regmap_config时一定要将read_flag_mask设置为0X00。
// drivers/base/regmap/regmap-spi.c
static struct regmap_bus regmap_spi = {
.write = regmap_spi_write,
.gather_write = regmap_spi_gather_write,
.async_write = regmap_spi_async_write,
.async_alloc = regmap_spi_async_alloc,
.read = regmap_spi_read,
.read_flag_mask = 0x80, // 注意这里
.reg_format_endian_default = REGMAP_ENDIAN_BIG,
.val_format_endian_default = REGMAP_ENDIAN_BIG,
};
1.3 申请、初始化、释放
SPI接口:
struct regmap * regmap_init_spi(
struct spi_device *spi, // 需要使用regmap的spi_device
const struct regmap_config *config // 将一个regmap_config实例的地址赋到这里
) // return:申请并初始化的regmap
IIC接口:
struct regmap * regmap_init_i2c(
struct i2c_client *i2c, // 需要使用regmap的i2c_client
const struct regmap_config *config // 将一个regmap_config实例的地址赋到这里
) // 申请并初始化的regmap
释放:
退出驱动的时候需要释放掉申请到的regmap。
不管是什么接口,全部使用regmap_exit这个函数来释放。
void regmap_exit(struct regmap *map) // map:要释放的regmap
1.4 regmap设备访问函数
读写寄存器:
int regmap_read(struct regmap *map, // 要操作的regmap
unsigned int reg, // 要读的寄存器
unsigned int *val) // 读到的值
// return:0成功,else失败
int regmap_write(struct regmap *map, // 要操作的regmap
unsigned int reg, // 要写的寄存器
unsigned int *val) // 要写的值
// return:0成功,else失败
写寄存器某几位的值:
int regmap_update_bits (
struct regmap *map, // 要操作的regmap
unsigned int reg, // 要操作的寄存器
unsigned int mask, // 掩码,要更新的位 设置为1
unsigned int val) // 要更新的位值
// return:0成功,else失败
比如要将寄存器的bit1和bit2置1,那么mask设置为0X00000011,val的bit1和bit2设置为1,也就是0Xxxxxxx11。
如果要清除寄存器的bit4和bit7,那么mask设置为0X10010000,va 的bit4和bit7设置为0,也就是0X0xx0xxxx。
读写多个寄存器的值:
int regmap_bulk_read(
struct regmap *map, // 要操作的regmap
unsigned int reg, // 要读的第一个寄存器
void *val, // 存放读取到的数据的缓冲区
size_t val_count) // 要读的寄存器数量
// return:0成功,else失败
int regmap_bulk_write(
struct regmap *map, // 要操作的regmap
unsigned int reg, // 要写的第一个寄存器
const void *val, // 要写的数据的缓冲区
size_t val_count) // 要写的寄存器数量
// return:0成功,else失败
二、驱动代码
因为代码基本与SPI (ICM20608驱动+0.96寸OLED驱动)、IIC(AP3216C驱动+MPU6050驱动)代码一致,这里不再重新写了,只写一些修改部分的代码。这里以SPI的icm20608代码为例:
2.1 设备结构体
结构体最后增加regmap和用于初始化的regmap_config成员:
struct icm20608_dev{
int major;
int minor;
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
void *private_data; // spi设备指针挂这里
int cs_gpio; // 片选引脚
struct device_node *nd; // 设备树节点
signed int gyro_x; // 陀螺仪三轴原始值
signed int gyro_y;
signed int gyro_z;
signed int accel_x; // 加速度计三轴原始值
signed int accel_y;
signed int accel_z;
signed int temp_adc;// 温度计原始值
struct regmap *regmap;
struct regmap_config regmap_config;
};
2.2 regmap初始化部分函数
在probe函数中添加regmap初始化代码:
static int icm20608_probe(struct spi_device* spi){
int ret = 0;
// 1、devid
icm20608dev.major = 0;
if(icm20608dev.major){ // 已给定主设备号
icm20608dev.devid = MKDEV(icm20608dev.major,0);
ret = register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);
} else { // 系统分配主设备号
ret = alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);
icm20608dev.major = MAJOR(icm20608dev.devid);
icm20608dev.minor = MINOR(icm20608dev.devid);
}
if(ret < 0){
printk("chrdev region error!\r\n");
goto fail_devid;
}
printk("major = %d, minor = %d\r\n", icm20608dev.major, icm20608dev.minor);
// 2、 cdev
icm20608dev.cdev.owner = THIS_MODULE;
cdev_init(&icm20608dev.cdev, &icm20608_fops);
ret = cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
if(ret<0){
printk("cdev error!\r\n");
goto fail_cdev;
}
// 3、class
icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
if(IS_ERR(icm20608dev.class)){
ret = PTR_ERR(icm20608dev.class);
goto fail_class;
}
// 4、device
icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);
if(IS_ERR(icm20608dev.device)){
ret = PTR_ERR(icm20608dev.device);
goto fail_device;
}
// regmap初始化
icm20608dev.regmap_config.reg_bits = 8; // 寄存器地址位数
icm20608dev.regmap_config.val_bits = 8; // 寄存器值的位数
icm20608dev.regmap_config.read_flag_mask = 0x80;
icm20608dev.regmap = regmap_init_spi(spi,&icm20608dev.regmap_config);
// 5、设置设备结构体的私有数据
icm20608dev.private_data = spi;
// 6、spi设备初始化
spi->mode = SPI_MODE_0; // 原始MicroWire,MODE0,CPOL=0,CPHA=0
spi_setup(spi);
// 7、设备初始化
icm20608_dev_init(&icm20608dev);
return 0;
fail_request:
fail_gpio:
fail_device:
class_destroy(icm20608dev.class);
fail_class:
cdev_del(&icm20608dev.cdev);
fail_cdev:
unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
fail_devid:
return ret;
}
2.3 寄存器读写函数
读寄存器
// SPI读
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len){
int ret;
ret = regmap_bulk_read(dev->regmap, reg, buf, len);
if (ret)
pr_err("icm20608: spi_read reg 0x%x failed %d\n", reg, ret);
return ret;
}
static int icm20608_read_one_reg(struct icm20608_dev *dev, u8 reg){
int ret = 0;
unsigned int data = 0;
ret = regmap_read(dev->regmap, reg, &data);
if(ret){
pr_err("icm20608: spi_read reg 0x%x failed %d\n", reg, ret);
return ret;
}
return data;
}
写寄存器
// SPI写
static int icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, int len){
int ret = 0;
ret = regmap_bulk_write(dev->regmap, reg, buf, len);
if (ret < 0)
pr_err("icm20608: spi_write reg 0x%x failed %d\n", reg, ret);
return ret;
}
static int icm20608_write_one_reg(struct icm20608_dev *dev, u8 reg, u8 value){
int ret = 0;
ret = regmap_write(dev->regmap, reg, value);
if (ret < 0)
pr_err("icm20608: spi_write reg 0x%x failed: %d\n", reg, ret);
return ret;
}
2.4 释放regmap
在remove函数中增加regmap_exit:
static int icm20608_remove(struct spi_device* spi){
int ret = 0;
device_destroy(icm20608dev.class, icm20608dev.devid);
class_destroy(icm20608dev.class);
cdev_del(&icm20608dev.cdev);
unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
regmap_exit(icm20608dev.regmap);
return 0; // 懒得写错误处理了
}
更多推荐


所有评论(0)