视频:第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; // 懒得写错误处理了
}
Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐