linux regmap and regmap_config

本文概述了regmap--它在剔除Linux子系统中的常见代码方面有多大作用,以及如何有效利用它在Linux中编写驱动程序。

Linux被划分为许多子系统,以便将不同部分的通用代码因子化,并简化驱动开发,这有助于代码维护。

Linux有一些子系统,如I2C和SPI,它们被用来连接驻扎在这些总线上的设备。这两种总线都有一个共同的功能,即从连接到它们的设备上读写寄存器。因此,读写这些寄存器、缓存寄存器的值等的代码必须存在于这两个子系统中。这导致冗余的代码存在于所有具有这种寄存器读写功能的子系统中。

为了避免这种情况的发生,并将普通的代码因素化,同时也为了方便驱动程序的维护和开发,Linux开发者从3.1版本开始引入了一个新的内核API,这个API被称为regmap。这个基础设施以前存在于Linux AsoC(ALSA)子系统中,但现在已经通过regmap API提供给整个Linux了。

早些时候,如果为驻留在SPI总线上的设备编写驱动程序,那么该驱动程序直接使用SPI子系统的SPI总线读写调用来与设备对话。现在,它使用regmap API来完成这一工作。regmap子系统负责调用SPI子系统的相关调用。因此,两个设备--一个驻留在I2C总线上,一个驻留在SPI总线上--将有相同的regmap读写调用来与总线上的各自设备对话。

这个子系统在Linux 3.1中首次引入,后来又引入了以下不同的功能。

  • 支持SPMI、MMIO
  • Spinlock和自定义锁机制
  • 缓存支持
  • 内联性转换
  • 寄存器范围检查
  • 支持IRQ
  • 只读和只写寄存器
  • 珍贵寄存器和易失性寄存器
  • 寄存器页

实现regmap
Linux中的regmap提供的API在include/linux/regmap.h中声明,在drivers/base/regmap/中实现。

在Linux的regmap中需要理解的重要数据结构

结构regmap_config

/**
 * 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.
 */
struct regmap_config {
	const char *name; 可选,寄存器名字

	int reg_bits; 寄存器地址位宽,必须填写
	int reg_stride;// 寄存器操作宽度,比如为1时,所有寄存器可操作,为2时,只有2^n可操作
	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);
	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;

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

	u8 read_flag_mask;
	u8 write_flag_mask;

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


这是一个由regmap子系统使用的每个设备配置结构,用于与设备对话。它由驱动代码定义,包含所有与设备的寄存器相关的信息。它的重要字段的描述列在下面。

这是该设备寄存器中的位数,例如,如果是1字节的寄存器,它将被设置为8的值。

val_bits: 这是将在设备寄存器中设置的值的位数。
writeable_reg:这是一个写在驱动程序代码中的用户定义的函数,每当要写一个寄存器时,就会调用这个函数。每当驱动调用regmap子系统写一个寄存器时,这个驱动函数就会被调用;如果这个寄存器不可写,它将返回 "false",写操作将向驱动返回一个错误。这是一个 "每个寄存器 "的写操作回调,是可选的。
wr_table。如果驱动没有提供writeable_reg回调,那么在进行写操作之前,regmap会检查wr_table。如果寄存器地址在wr_table提供的范围内,那么就执行写操作。这也是可选的,驱动可以省略它的定义,也可以将它设置为NULL。

readable_reg: 这是一个用户定义的函数,写在驱动代码中,每当要读一个寄存器时就会调用。每当驱动调用regmap子系统读取一个寄存器时,这个驱动函数就会被调用以确保该寄存器是可读的。如果这个寄存器不可读,驱动函数将返回 "false",读操作将向驱动返回一个错误。这是一个 "每个寄存器 "的读操作回调,是可选的。

rd_table: 如果一个驱动没有提供readable_reg回调,那么在进行读操作之前,regmap会检查rd_table。如果寄存器地址在rd_table提供的范围内,那么读操作就被执行。这也是可选的,驱动程序可以省略它的定义,可以将它设置为NULL。
volatile_reg: 这是一个写在驱动代码中的用户定义的函数,每当通过缓存写入或读出一个寄存器时就会调用这个函数。每当驱动通过regmap缓存读取或写入一个寄存器时,这个函数首先被调用,如果它返回 "false",才会使用缓存方法;否则,寄存器被直接写入或读取,因为寄存器是易失性的,不需要使用缓存。这是一个 "每个寄存器 "的操作回调,是可选的。

volatile_table: 如果一个驱动没有提供volatile_reg回调,那么volatile_table就会被regmap检查,看寄存器是否是volatile的。如果寄存器地址在volatile_table提供的范围内,则不使用缓存操作。这也是可选的,驱动程序可以省略它的定义,也可以将它设置为NULL。

锁定。这是一个用户定义的回调函数,写在驱动代码中,在开始任何读或写操作之前被调用。该函数应该接受一个锁,并返回它。这是一个可选的函数--如果不提供,regmap将提供自己的锁定机制。

解锁。这是用户定义的回调,写在驱动代码中,用于解锁,它是由锁例程创建的。这是可选的,如果不提供,将由regmap的内部锁定机制取代。

lock_arg: 这是传递给锁定和解锁回调例程的参数。
fast_io。如果没有提供自定义的锁和解锁机制,regmap内部使用mutex来锁和解锁。如果驱动希望regmap使用自旋锁,那么fast_io应该设置为 "true";否则,regmap将使用基于mutex的锁。

max_register。每当要执行任何读或写操作时,regmap首先检查寄存器地址是否小于max_register,只有当它是,才执行操作。 max_register如果被设置为0,则被忽略。

read_flag_mask。通常,在SPI或I2C中,一个写或读将在最高字节中设置最高位,以区分写和读操作。这个掩码被设置在寄存器值的较高字节中。
write_flag_mask。这个掩码也被设置在寄存器值的较高字节中。

这些是结构regmap_config的字段,这个配置被传递给regmap_init,它创建结构regmap并返回可以在读写操作中使用的内容。
wr_table, rd_table和volatile_table是可选的表(仅在没有提供相应的回调时使用),在写和读操作时被regmap用来进行范围检查。这些被实现为regmap_access_table结构。以下是它的字段。
yes_ranges:这些是被认为是有效范围的地址范围。
n_yes_ranges:这是yes_ranges中的条目数。
no_ranges:这些是被认为是无效范围的地址范围。
n_no_ranges:这是no_ranges中的条目数。


struct regmap  

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;

#ifdef CONFIG_DEBUG_FS
	struct dentry *debugfs;
	const char *debugfs_name;

	unsigned int debugfs_reg_len;
	unsigned int debugfs_val_len;
	unsigned int debugfs_tot_len;

	struct list_head debugfs_off_cache;
	struct mutex cache_lock;
#endif

	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;
	u32 cache_dirty;

	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 */
};

regmap_init主要是把config和描述i2c设备的bus设置到regmap中.

 

struct regmap *regmap_init(struct device *dev,
               const struct regmap_bus *bus,
               void *bus_context,
               const struct regmap_config *config)
{
    struct regmap *map;
    int ret = -EINVAL;
    enum regmap_endian reg_endian, val_endian;

    map = kzalloc(sizeof(*map), GFP_KERNEL);
    // 将regmap_config定义的参数赋值到regmap中
    map->format.reg_bytes = DIV_ROUND_UP(config->reg_bits, 8);
    map->format.pad_bytes = config->pad_bits / 8;
    map->format.val_bytes = DIV_ROUND_UP(config->val_bits, 8);
    map->format.buf_size = DIV_ROUND_UP(config->reg_bits +
            config->val_bits + config->pad_bits, 8);
    map->reg_shift = config->pad_bits % 8;
    if (config->reg_stride)
        map->reg_stride = config->reg_stride;
    else
        map->reg_stride = 1;
    map->use_single_rw = config->use_single_rw;
    map->can_multi_write = config->can_multi_write;
    map->dev = dev;
    map->bus = bus;
    map->bus_context = bus_context;
    map->max_register = config->max_register;
    map->wr_table = config->wr_table;
    map->rd_table = config->rd_table;
    map->volatile_table = config->volatile_table;
    map->precious_table = config->precious_table;
    map->writeable_reg = config->writeable_reg;
    map->readable_reg = config->readable_reg;
    map->volatile_reg = config->volatile_reg;
    map->precious_reg = config->precious_reg;
    map->cache_type = config->cache_type;
    map->name = config->name;

    /* regmap中有reg_read操作方法,通过bus是否为空赋值 */
    if (!bus) {
        // 对于该驱动,此处只会走这里 
        map->reg_read  = config->reg_read;
        map->reg_write = config->reg_write;

        map->defer_caching = false;
        goto skip_format_initialization;
    } else if (!bus->read || !bus->write) {
        map->reg_read = _regmap_bus_reg_read;
        map->reg_write = _regmap_bus_reg_write;

        map->defer_caching = false;
        goto skip_format_initialization;
    } else {
        map->reg_read  = _regmap_bus_read;
    }
    // 设置地址和寄存器值的大小端
    reg_endian = regmap_get_reg_endian(bus, config);
    val_endian = regmap_get_val_endian(dev, bus, config);
    // 根据寄存器地址位宽和大小端解析寄存器地址
    switch (config->reg_bits + map->reg_shift) {
        case 32:
        switch (reg_endian) {
        case REGMAP_ENDIAN_BIG:
            map->format.format_reg = regmap_format_32_be;
            break;
        case REGMAP_ENDIAN_NATIVE:
            map->format.format_reg = regmap_format_32_native;
            break;
        default:
            goto err_map;
        }
        break;
    }
    // 根据寄存器值位宽和大小端解析寄存器值
    switch (config->val_bits) {
        case 16:
        switch (val_endian) {
        case REGMAP_ENDIAN_BIG:
            map->format.format_val = regmap_format_16_be;
            map->format.parse_val = regmap_parse_16_be;
            map->format.parse_inplace = regmap_parse_16_be_inplace;
            break;
        case REGMAP_ENDIAN_LITTLE:
            map->format.format_val = regmap_format_16_le;
            map->format.parse_val = regmap_parse_16_le;
            map->format.parse_inplace = regmap_parse_16_le_inplace;
            break;
        case REGMAP_ENDIAN_NATIVE:
            map->format.format_val = regmap_format_16_native;
            map->format.parse_val = regmap_parse_16_native;
            break;
        default:
            goto err_map;
        }
        break;
    }

    /* 对于val_bits = 16,reg_bits=16,regmap写函数选择_regmap_bus_raw_write */
    if (map->format.format_write) {
        map->defer_caching = false;
        map->reg_write = _regmap_bus_formatted_write;
    } else if (map->format.format_val) {
        map->defer_caching = true;
        map->reg_write = _regmap_bus_raw_write;
    }
    // 缓存初始化
    ret = regcache_init(map, config);
}

regmap_init_i2c

当设备驱动配置好config以后,调用对应的regmap_init_xx,例如,这里是regmap_init_i2c

// drivers/base/regmap/regmap-i2c.c

struct regmap *regmap_init_i2c(struct i2c_client *i2c,
                               const struct regmap_config *config)
{
    // regmap_bus 定位了对应总线的读写函数。
    const struct regmap_bus *bus = regmap_get_i2c_bus(i2c, config);

    if (IS_ERR(bus))
        return ERR_CAST(bus);

    return regmap_init(&i2c->dev, bus, &i2c->dev, config);
}

注意到2个新的数据结构:

  • struct regmap_bus *bus
  • struct regmap *regmap_init_i2c(..

regmap_get_i2c_bus

对于普通I2C设备,regmap_bus为:

// drivers/base/regmap/regmap-i2c.c
static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c,
                    const struct regmap_config *config)
{
    if (i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C))
        return &regmap_i2c;
    // ...

    return ERR_PTR(-ENOTSUPP);
}

static struct regmap_bus regmap_i2c = {
    .write = regmap_i2c_write,
    .gather_write = regmap_i2c_gather_write,
    .read = regmap_i2c_read,
    .reg_format_endian_default = REGMAP_ENDIAN_BIG,
    .val_format_endian_default = REGMAP_ENDIAN_BIG,
};

regmap_bus结构定义了读写函数和默认的寄存器地址和寄存器值的大小端。

regmap_bus原型

/**
 * struct regmap_bus - Description of a hardware bus for the register map
 *                     infrastructure.
 *
 * @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).
 * @write: Write operation.
 * @gather_write: Write operation with split register/value, return -ENOTSUPP
 *                if not implemented  on a given device.
 * @async_write: Write operation which completes asynchronously, optional and
 *               must serialise with respect to non-async I/O.
 * @reg_write: Write a single register value to the given register address. This
 *             write operation has to complete when returning from the function.
 * @reg_update_bits: Update bits operation to be used against volatile
 *                   registers, intended for devices supporting some mechanism
 *                   for setting clearing bits without having to
 *                   read/modify/write.
 * @read: Read operation.  Data is returned in the buffer used to transmit
 *         data.
 * @reg_read: Read a single register value from a given register address.
 * @free_context: Free context.
 * @async_alloc: Allocate a regmap_async() structure.
 * @read_flag_mask: Mask to be set in the top byte of the register when doing
 *                  a read.
 * @reg_format_endian_default: Default endianness for formatted register
 *     addresses. Used when the regmap_config specifies DEFAULT. If this is
 *     DEFAULT, BIG is assumed.
 * @val_format_endian_default: Default endianness for formatted register
 *     values. Used when the regmap_config specifies DEFAULT. If this is
 *     DEFAULT, BIG is assumed.
 * @max_raw_read: Max raw read size that can be used on the bus.
 * @max_raw_write: Max raw write size that can be used on the bus.
 */
struct regmap_bus {
    bool fast_io;
    regmap_hw_write write;
    regmap_hw_gather_write gather_write;
    regmap_hw_async_write async_write;
    regmap_hw_reg_write reg_write;
    regmap_hw_reg_update_bits reg_update_bits;
    regmap_hw_read read;
    regmap_hw_reg_read reg_read;
    regmap_hw_free_context free_context;
    regmap_hw_async_alloc async_alloc;
    u8 read_flag_mask;
    enum regmap_endian reg_format_endian_default;
    enum regmap_endian val_format_endian_default;
    size_t max_raw_read;
    size_t max_raw_write;
};


 

regmap APIs
regmap的API在include/linux/regmap.h中声明,以下是重要API的细节。
初始化例程。下面的例程根据SPI配置初始化regmap数据结构。

struct regmap * devm_regmap_init_spi(struct spi_device *spi, const struct regmap_config)。

下面的例程根据I2C配置初始化regmap数据结构。

struct regmap * devm_regmap_init_i2c(struct i2c_client *i2c, const struct regmap_config)。

在regmap初始化例程中,摄取regmap_config配置;然后分配regmap结构并将配置复制到其中。各个总线的读/写功能也被复制到regmap结构中。例如,在SPI总线的情况下,regmap的读写功能指针将指向SPI的读写功能。
regmap初始化后,驱动程序可以使用以下例程与设备对话。

int regmap_write(struct regmap *map, unsigned int reg, unsigned int val)。

int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val)。

regmap_write。这个函数用来向设备写数据。它接收初始化期间返回的regmap结构,注册地址和要设置的值。以下是regmap_write例程执行的步骤。

首先,regmap_write接收锁,如果regmap_config中的fast_io被设置,它将是spinlock;否则,它将是mutex。

接下来,如果在regmap_config中设置了max_register,那么它将检查传递的寄存器地址是否小于max_register。如果小于max_register,那么只执行写操作;否则,返回-EIO(无效的I/O)。

之后,如果在regmap_config中设置了writeable_reg回调,那么将调用该回调。如果该回调返回 "true",则进行进一步操作;如果它返回 "false",则返回错误-EIO。这个步骤只有在设置了writeable_reg的情况下才会执行。

如果没有设置writeable_reg,但是设置了wr_table,那么会检查寄存器地址是否位于no_ranges,在这种情况下会返回-EIO错误;否则,会检查它是否位于yes_ranges。如果它不在那里,那么就会返回-EIO错误,并且操作被终止。如果它在yes_ranges中,那么将执行进一步的操作。这一步只有在wr_table被设置的情况下才会执行,否则就会跳过这一步。

现在检查是否允许缓存。如果允许,那么寄存器的值将被缓存,而不是直接写入硬件,并且操作在这一步完成。如果不允许缓存,则进入下一个步骤。缓存将在后面讨论。

在调用这个硬件写入例程将值写入硬件寄存器后,这个函数将写入_flag_mask到值的第一个字节,值被写入设备。
完成写操作后,写之前的锁被释放,函数返回。

regmap_read。这个函数用于从设备上读取数据。它接收初始化期间返回的regmap结构,并注册要读取数据的地址和值指针。在寄存器读取过程中,将执行以下步骤。
首先,读函数在执行读操作之前会被锁定。如果在regmap_config中设置了fast_io,这将是一个自旋锁;否则,regmap将使用mutex。
接下来,它将检查传递的寄存器地址是否小于max_register;如果不是,那么将返回-EIO。这一步只在max_register被设置为大于0时进行。
然后,它将检查readable_reg回调是否被设置。如果是,则调用该回调,如果该回调返回'false',则读取操作被终止,返回-EIO错误。如果这个回调返回 "true",那么进一步的操作将被执行。

接下来要检查的是寄存器地址是否在配置中rd_table的no_ranges范围内。如果是,那么将返回一个-EIO错误。如果它既不在no_ranges也不在yes_ranges中,那么也会返回-EIO错误。只有当它位于yes_ranges中时,才能进行进一步的操作。这个步骤只有在设置了rd_table的情况下才会执行。
现在,如果允许缓存,那么就从缓存中读取寄存器的值,函数返回被读取的值。如果缓存被设置为旁路,那么将执行下一步。
在采取上述步骤后,调用硬件读取操作来读取寄存器的值,并且用返回的值来更新被传递的变量的值。
在开始这个操作之前的锁现在被释放,函数返回。

编写一个基于regmap的驱动
让我们试着写一个基于regmap框架的驱动程序。
让我们假设有一个设备X连接在SPI总线上,它有以下属性。

  • 8位寄存器地址
  • 8位寄存器值
  • 0x80作为写入掩码
  • 它是一个快速I/O设备,所以应该使用自旋锁
  • 有效的地址范围。
    1. 0x20到0x4F
    2. 0x60到0x7F
    驱动程序可以按以下方式编写。
//include other include files
#include <linux/regmap.h>
  
static struct custom_drv_private_struct
{
            //other fields relevant to device
            struct regmap *map;
};
  
static const struct regmap_range wr_rd_range[] = 
{
            {
                        .range_min = 0x20,
                        .range_max = 0x4F,
            }, {
                        .range_min = 0x60,
                        .range_max = 0x7F
            },
}; 
struct regmap_access_table drv_wr_table = 
{
            .yes_ranges =   wr_rd_range,
.n_yes_ranges = ARRAY_SIZE(wr_rd_range),
};
  
struct regmap_access_table drv_rd_table = 
{
         .yes_ranges =   wr_rd_range,
         .n_yes_ranges = ARRAY_SIZE(wr_rd_range),
};
  
static bool writeable_reg(struct device *dev, unsigned int reg)
{
            if(reg >= 0x20 && reg <= 0x4F)
                        return true;
  
            if(reg >= 0x60 && reg <= 0x7F)
                        return true;
  
            return false;
}
  
static bool readable_reg(struct device *dev, unsigned int reg)
{
            if(reg >= 0x20 && reg <= 0x4F)
                        return true;
  
            if(reg >= 0x60 && reg <= 0x7F)
                        return true;
  
            return false;
}
static int custom_drv_probe(struct spi_device *dev)
{
     struct regmap_config config;
     struct custom_drv_private_struct *priv;
     unsigned int data; 
    //configure the regmap configuration
    memset(&config, 0, sizeof(config));
    config.reg_bits = 8;
    config.val_bits = 8;
    config.write_flag_mask = 0x80;
    config.max_register = 0x80;
    config.fast_io = true;
    config.writeable_reg = drv_writeable_reg;
    config.readable_reg = drv_readable_reg;
    //only set below two things if  
    //writeable_reg
    //and readable_reg is not set
    //config.wr_table = drv_wr_table;
    //config.rd_table = drv_rd_table;
 
    //allocate the private data structures as
    //priv = devm_kzalloc 
    //Init the regmap spi configuration
    priv->map = devm_regmap_init_spi(dev,             &config); 
  //devm_regmap_init_i2c in case of i2c bus 
//following operation will remain same in
//case of both i2c and spi or other bus            
//read from the device, data variable will //contain device data
regmap_read(priv->map, 0x23, &data); 
data = 0x24;
//write to the device
regmap_write(priv->map, 0x23, data); 
if(regmap_read(priv->map, 0x85, &data)
< 0)
    {
            ///error since address is out of range
    } 
            return 0;
}

在上面的例子中,你可以看到基于不同总线子系统的驱动程序中的冗余代码如何变成类似的代码,这使得驱动程序的编写和维护更加容易。在当前的regmap子系统中也有缓存支持。

缓存避免了直接在设备上执行操作。相反,它缓存了在设备和驱动之间传输的值,并将其作为未来的参考。最初,缓存只使用平面数组,这对32位地址不利。后来,这个问题通过更好的缓存类型得到了解决。

  • rbtree将连续的寄存器块存储在一个红/黑树中
  • 压缩存储压缩的数据块
    两者都依赖于现有的内核库。

enum regcache_type cache_type。

对tracepoint的支持在regmap中可用。更多信息,见debugfs/trace/events/regmap。

regmap_reg_write 0-001b reg=3b val=1a

regmap_reg_read 0-001b reg=1d val=1d

你也可以定义LOG_DEVICE用于早期启动日志

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值