Linux 核心裝置驅動程式的IO暫存器存取 (下)

2023-08-31 06:00:33

Linux 核心裝置驅動程式通過 devm_regmap_init_mmio() 等函數獲得 struct regmap 結構物件,該物件包含可用於存取裝置暫存器的全部資訊,包括定義存取操作如何執行的 bus,定義了各個裝置暫存器的讀寫屬性的 config,以及加速裝置暫存器存取的 cache。

Linux 核心裝置驅動程式可以通過 regmap_write()regmap_read()regmap_update_bits() 等函數讀寫裝置暫存器,通過 regcache_sync()regcache_cache_only()regcache_cache_bypass()regcache_mark_dirty() 等函數操作快取。

基於 I2C 的 regmap

通過 I2C 存取的裝置暫存器,可以使用 regmap 機制來存取。如在 ALC 5651 audio codec 核心裝置驅動程式裡,在 probe 操作中建立 regmap 物件 (位於 sound/soc/codecs/rt5651.c):

static const struct regmap_config rt5651_regmap = {
	.reg_bits = 8,
	.val_bits = 16,

	.max_register = RT5651_DEVICE_ID + 1 + (ARRAY_SIZE(rt5651_ranges) *
					       RT5651_PR_SPACING),
	.volatile_reg = rt5651_volatile_register,
	.readable_reg = rt5651_readable_register,

	.cache_type = REGCACHE_RBTREE,
	.reg_defaults = rt5651_reg,
	.num_reg_defaults = ARRAY_SIZE(rt5651_reg),
	.ranges = rt5651_ranges,
	.num_ranges = ARRAY_SIZE(rt5651_ranges),
	.use_single_read = true,
	.use_single_write = true,
};
 . . . . . .
static int rt5651_i2c_probe(struct i2c_client *i2c,
		    const struct i2c_device_id *id)
{
	struct rt5651_priv *rt5651;
	int ret;
	int err;

	rt5651 = devm_kzalloc(&i2c->dev, sizeof(*rt5651),
				GFP_KERNEL);
	if (NULL == rt5651)
		return -ENOMEM;

	i2c_set_clientdata(i2c, rt5651);

	rt5651->regmap = devm_regmap_init_i2c(i2c, &rt5651_regmap);
	if (IS_ERR(rt5651->regmap)) {
		ret = PTR_ERR(rt5651->regmap);
		dev_err(&i2c->dev, "Failed to allocate register map: %d\n",
			ret);
		return ret;
	}
 . . . . . .

之後對裝置暫存器的存取方法,同 mmio 的一樣。這裡建立 regmap 物件的方法為呼叫 devm_regmap_init_i2c(),這是一個宏,其定義 (位於 include/linux/regmap.h) 如下:

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);
. . . . . .
/**
* devm_regmap_init_i2c() - Initialise managed register map
*
* @i2c: Device that will be interacted with
* @config: Configuration for register map
*
* The return value will be an ERR_PTR() on error or a valid pointer
* to a struct regmap.  The regmap will be automatically freed by the
* device management code.
*/
#define devm_regmap_init_i2c(i2c, config)				\
   __regmap_lockdep_wrapper

這個宏呼叫了 __devm_regmap_init_i2c() 函數,該函數定義 (位於 drivers/base/regmap/regmap-i2c.c) 如下:

static const struct regmap_bus regmap_smbus_byte = {
	.reg_write = regmap_smbus_byte_reg_write,
	.reg_read = regmap_smbus_byte_reg_read,
};
 . . . . . .
static const struct regmap_bus regmap_smbus_word = {
	.reg_write = regmap_smbus_word_reg_write,
	.reg_read = regmap_smbus_word_reg_read,
};
 . . . . . .
static const struct regmap_bus regmap_smbus_word_swapped = {
	.reg_write = regmap_smbus_word_write_swapped,
	.reg_read = regmap_smbus_word_read_swapped,
};
 . . . . . .
static const 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,
};
 . . . . . .
static const struct regmap_bus regmap_i2c_smbus_i2c_block = {
	.write = regmap_i2c_smbus_i2c_write,
	.read = regmap_i2c_smbus_i2c_read,
	.max_raw_read = I2C_SMBUS_BLOCK_MAX,
	.max_raw_write = I2C_SMBUS_BLOCK_MAX,
};
 . . . . . .
static const struct regmap_bus regmap_i2c_smbus_i2c_block_reg16 = {
	.write = regmap_i2c_smbus_i2c_write_reg16,
	.read = regmap_i2c_smbus_i2c_read_reg16,
	.max_raw_read = I2C_SMBUS_BLOCK_MAX,
	.max_raw_write = I2C_SMBUS_BLOCK_MAX,
};

static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c,
					const struct regmap_config *config)
{
	const struct i2c_adapter_quirks *quirks;
	const struct regmap_bus *bus = NULL;
	struct regmap_bus *ret_bus;
	u16 max_read = 0, max_write = 0;

	if (i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C))
		bus = &regmap_i2c;
	else if (config->val_bits == 8 && config->reg_bits == 8 &&
		 i2c_check_functionality(i2c->adapter,
					 I2C_FUNC_SMBUS_I2C_BLOCK))
		bus = &regmap_i2c_smbus_i2c_block;
	else if (config->val_bits == 8 && config->reg_bits == 16 &&
		i2c_check_functionality(i2c->adapter,
					I2C_FUNC_SMBUS_I2C_BLOCK))
		bus = &regmap_i2c_smbus_i2c_block_reg16;
	else if (config->val_bits == 16 && config->reg_bits == 8 &&
		 i2c_check_functionality(i2c->adapter,
					 I2C_FUNC_SMBUS_WORD_DATA))
		switch (regmap_get_val_endian(&i2c->dev, NULL, config)) {
		case REGMAP_ENDIAN_LITTLE:
			bus = &regmap_smbus_word;
			break;
		case REGMAP_ENDIAN_BIG:
			bus = &regmap_smbus_word_swapped;
			break;
		default:		/* everything else is not supported */
			break;
		}
	else if (config->val_bits == 8 && config->reg_bits == 8 &&
		 i2c_check_functionality(i2c->adapter,
					 I2C_FUNC_SMBUS_BYTE_DATA))
		bus = &regmap_smbus_byte;

	if (!bus)
		return ERR_PTR(-ENOTSUPP);

	quirks = i2c->adapter->quirks;
	if (quirks) {
		if (quirks->max_read_len &&
		    (bus->max_raw_read == 0 || bus->max_raw_read > quirks->max_read_len))
			max_read = quirks->max_read_len;

		if (quirks->max_write_len &&
		    (bus->max_raw_write == 0 || bus->max_raw_write > quirks->max_write_len))
			max_write = quirks->max_write_len;

		if (max_read || max_write) {
			ret_bus = kmemdup(bus, sizeof(*bus), GFP_KERNEL);
			if (!ret_bus)
				return ERR_PTR(-ENOMEM);
			ret_bus->free_on_exit = true;
			ret_bus->max_raw_read = max_read;
			ret_bus->max_raw_write = max_write;
			bus = ret_bus;
		}
	}

	return bus;
}
 . . . . . .
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)
{
	const struct regmap_bus *bus = regmap_get_i2c_bus(i2c, config);

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

	return __devm_regmap_init(&i2c->dev, bus, &i2c->dev, config,
				  lock_key, lock_name);
}
EXPORT_SYMBOL_GPL(__devm_regmap_init_i2c);

__devm_regmap_init_i2c() 函數首先根據暫存器對映的設定,如暫存器地址的位數,暫存器值的位數,I2C 匯流排的功能特性,大尾端還是小尾端等,選擇裝置暫存器的存取操作,即 struct regmap_bus,然後如同 __devm_regmap_init_mmio_clk() 函數一樣,通過 __devm_regmap_init() 函數建立並初始化 regmap 物件。

這裡通過 i2c_check_functionality() 函數判斷 I2C 匯流排的功能特性,這個函數定義 (位於 include/linux/i2c.h) 如下:

/* Return the functionality mask */
static inline u32 i2c_get_functionality(struct i2c_adapter *adap)
{
	return adap->algo->functionality(adap);
}

/* Return 1 if adapter supports everything we need, 0 if not. */
static inline int i2c_check_functionality(struct i2c_adapter *adap, u32 func)
{
	return (func & i2c_get_functionality(adap)) == func;
}

即通過 I2C 匯流排介面卡驅動程式實現的 functionality 操作來判斷。ALC 5651 Linux 核心驅動程式的暫存器對映設定,暫存器地址為 8 位,值為 16 位,對於標準的 I2C 匯流排,對應的 I2C IO 操作如下:

static int regmap_i2c_write(void *context, const void *data, size_t count)
{
	struct device *dev = context;
	struct i2c_client *i2c = to_i2c_client(dev);
	int ret;

	ret = i2c_master_send(i2c, data, count);
	if (ret == count)
		return 0;
	else if (ret < 0)
		return ret;
	else
		return -EIO;
}

static int regmap_i2c_gather_write(void *context,
				   const void *reg, size_t reg_size,
				   const void *val, size_t val_size)
{
	struct device *dev = context;
	struct i2c_client *i2c = to_i2c_client(dev);
	struct i2c_msg xfer[2];
	int ret;

	/* If the I2C controller can't do a gather tell the core, it
	 * will substitute in a linear write for us.
	 */
	if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_NOSTART))
		return -ENOTSUPP;

	xfer[0].addr = i2c->addr;
	xfer[0].flags = 0;
	xfer[0].len = reg_size;
	xfer[0].buf = (void *)reg;

	xfer[1].addr = i2c->addr;
	xfer[1].flags = I2C_M_NOSTART;
	xfer[1].len = val_size;
	xfer[1].buf = (void *)val;

	ret = i2c_transfer(i2c->adapter, xfer, 2);
	if (ret == 2)
		return 0;
	if (ret < 0)
		return ret;
	else
		return -EIO;
}

static int regmap_i2c_read(void *context,
			   const void *reg, size_t reg_size,
			   void *val, size_t val_size)
{
	struct device *dev = context;
	struct i2c_client *i2c = to_i2c_client(dev);
	struct i2c_msg xfer[2];
	int ret;

	xfer[0].addr = i2c->addr;
	xfer[0].flags = 0;
	xfer[0].len = reg_size;
	xfer[0].buf = (void *)reg;

	xfer[1].addr = i2c->addr;
	xfer[1].flags = I2C_M_RD;
	xfer[1].len = val_size;
	xfer[1].buf = val;

	ret = i2c_transfer(i2c->adapter, xfer, 2);
	if (ret == 2)
		return 0;
	else if (ret < 0)
		return ret;
	else
		return -EIO;
}

static const 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,
};

這裡構造訊息給 I2C 匯流排驅動程式,呼叫 Linux 核心 I2C 子系統提供的 i2c_master_send()i2c_transfer() 等操作,完成對裝置暫存器的讀寫。Linux 核心 I2C 子系統及 I2C 匯流排驅動程式的更多細節這裡不多贅述。

寫裝置暫存器

Linux 核心裝置驅動程式通過 regmap_write() 等函數寫裝置暫存器,相關的這些函數原型 (位於 include/linux/regmap.h) 如下:

int regmap_write(struct regmap *map, unsigned int reg, unsigned int val);
int regmap_write_async(struct regmap *map, unsigned int reg, unsigned int val);
int regmap_raw_write(struct regmap *map, unsigned int reg,
		     const void *val, size_t val_len);
int regmap_noinc_write(struct regmap *map, unsigned int reg,
		     const void *val, size_t val_len);
int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val,
			size_t val_count);
int regmap_multi_reg_write(struct regmap *map, const struct reg_sequence *regs,
			int num_regs);
int regmap_multi_reg_write_bypassed(struct regmap *map,
				    const struct reg_sequence *regs,
				    int num_regs);
int regmap_raw_write_async(struct regmap *map, unsigned int reg,
			   const void *val, size_t val_len);

regmap_write()regmap_write_async() 函數分別同步和非同步地寫一個裝置暫存器,這兩個函數定義 (位於 drivers/base/regmap/regmap.c) 如下:

bool regmap_reg_in_ranges(unsigned int reg,
			  const struct regmap_range *ranges,
			  unsigned int nranges)
{
	const struct regmap_range *r;
	int i;

	for (i = 0, r = ranges; i < nranges; i++, r++)
		if (regmap_reg_in_range(reg, r))
			return true;
	return false;
}
EXPORT_SYMBOL_GPL(regmap_reg_in_ranges);

bool regmap_check_range_table(struct regmap *map, unsigned int reg,
			      const struct regmap_access_table *table)
{
	/* Check "no ranges" first */
	if (regmap_reg_in_ranges(reg, table->no_ranges, table->n_no_ranges))
		return false;

	/* In case zero "yes ranges" are supplied, any reg is OK */
	if (!table->n_yes_ranges)
		return true;

	return regmap_reg_in_ranges(reg, table->yes_ranges,
				    table->n_yes_ranges);
}
EXPORT_SYMBOL_GPL(regmap_check_range_table);

bool regmap_writeable(struct regmap *map, unsigned int reg)
{
	if (map->max_register && reg > map->max_register)
		return false;

	if (map->writeable_reg)
		return map->writeable_reg(map->dev, reg);

	if (map->wr_table)
		return regmap_check_range_table(map, reg, map->wr_table);

	return true;
}
 . . . . . .
static inline void *_regmap_map_get_context(struct regmap *map)
{
	return (map->bus) ? map : map->bus_context;
}

int _regmap_write(struct regmap *map, unsigned int reg,
		  unsigned int val)
{
	int ret;
	void *context = _regmap_map_get_context(map);

	if (!regmap_writeable(map, reg))
		return -EIO;

	if (!map->cache_bypass && !map->defer_caching) {
		ret = regcache_write(map, reg, val);
		if (ret != 0)
			return ret;
		if (map->cache_only) {
			map->cache_dirty = true;
			return 0;
		}
	}

	if (regmap_should_log(map))
		dev_info(map->dev, "%x <= %x\n", reg, val);

	trace_regmap_reg_write(map, reg, val);

	return map->reg_write(context, reg, val);
}

/**
 * regmap_write() - Write a value to a single register
 *
 * @map: Register map to write to
 * @reg: Register to write to
 * @val: Value to be written
 *
 * A value of zero will be returned on success, a negative errno will
 * be returned in error cases.
 */
int regmap_write(struct regmap *map, unsigned int reg, unsigned int val)
{
	int ret;

	if (!IS_ALIGNED(reg, map->reg_stride))
		return -EINVAL;

	map->lock(map->lock_arg);

	ret = _regmap_write(map, reg, val);

	map->unlock(map->lock_arg);

	return ret;
}
EXPORT_SYMBOL_GPL(regmap_write);

/**
 * regmap_write_async() - Write a value to a single register asynchronously
 *
 * @map: Register map to write to
 * @reg: Register to write to
 * @val: Value to be written
 *
 * A value of zero will be returned on success, a negative errno will
 * be returned in error cases.
 */
int regmap_write_async(struct regmap *map, unsigned int reg, unsigned int val)
{
	int ret;

	if (!IS_ALIGNED(reg, map->reg_stride))
		return -EINVAL;

	map->lock(map->lock_arg);

	map->async = true;

	ret = _regmap_write(map, reg, val);

	map->async = false;

	map->unlock(map->lock_arg);

	return ret;
}
EXPORT_SYMBOL_GPL(regmap_write_async);

像眾多 regmap 機制提供的裝置暫存器存取操作函數一樣,這兩個函數,在開始任何操作前先加了鎖,並在結束操作後解鎖,regmap 機制提供了對裝置暫存器的互斥存取

regmap_write_async() 函數在加鎖後,將 map->async 賦值為 true,並在解鎖前將其賦值為 false,從 _regmap_write() 函數的實現來看,這裡的非同步寫疑似沒有工作。

regmap_write()regmap_write_async() 函數都通過 _regmap_write() 函數完成對裝置暫存器的寫操作。

_regmap_write() 函數的執行過程是簡單的三步:

  1. 檢查要寫入的暫存器是否可寫。這裡的檢查按照裝置暫存器的讀寫屬性設定進行。首先,檢查暫存器是否超過了設定的暫存器,若超過了,則顯然不可寫;其次,當 writeable_reg 回撥函數設定時,由該回撥函數判斷;然後,當 wr_table 可寫暫存器表設定時,根據該表做判斷;否則,認為暫存器可寫。對於暫存器是否可寫的判斷,如果同時設定了 writeable_reg 回撥函數和 wr_table 可寫暫存器表,則前者的優先順序高於後者,後者將被忽略;如果兩者都沒有設定,則認為暫存器可寫。

  2. 使用了 cache,而沒開延遲 cache 時,將要寫入暫存器的值先寫入 cached。如果寫入失敗,則直接返回,否則繼續執行。如果設定了 map->cache_only,則將 map->cache_dirty 置為 true 並返回,否則繼續執行。map->cache_only 標記表示不希望真正地寫裝置暫存器。

  3. 呼叫 struct regmapreg_write 操作向裝置暫存器寫入值。

這裡的寫操作基本上是一個無條件的寫,即在寫入裝置暫存器之前,不會檢查快取中是否已經存在了相同值。

regcache_write() 函數定義 (位於 drivers/base/regmap/regcache.c) 如下:

int regcache_write(struct regmap *map,
		   unsigned int reg, unsigned int value)
{
	if (map->cache_type == REGCACHE_NONE)
		return 0;

	BUG_ON(!map->cache_ops);

	if (!regmap_volatile(map, reg))
		return map->cache_ops->write(map, reg, value);

	return 0;
}

regcache_write() 函數,首先,檢查是否開啟了 cache,如果沒有則直接返回,否則繼續執行;其次,檢查要寫入的暫存器是否為 volatile 的,如果不是,則通過 cache 實現的 write 回撥寫入 cache,否則返回。

regmap_volatile() 函數用以檢查暫存器是否為 volatile 的,這個函數定義 (位於 drivers/base/regmap/regmap.c) 如下:

bool regmap_readable(struct regmap *map, unsigned int reg)
{
	if (!map->reg_read)
		return false;

	if (map->max_register && reg > map->max_register)
		return false;

	if (map->format.format_write)
		return false;

	if (map->readable_reg)
		return map->readable_reg(map->dev, reg);

	if (map->rd_table)
		return regmap_check_range_table(map, reg, map->rd_table);

	return true;
}

bool regmap_volatile(struct regmap *map, unsigned int reg)
{
	if (!map->format.format_write && !regmap_readable(map, reg))
		return false;

	if (map->volatile_reg)
		return map->volatile_reg(map->dev, reg);

	if (map->volatile_table)
		return regmap_check_range_table(map, reg, map->volatile_table);

	if (map->cache_ops)
		return false;
	else
		return true;
}

對暫存器是否為 volatile 的檢查,暗含著對它是否可讀的檢查。如果暫存器不是 volatile 的,會被認為是可快取的。regmap_volatile() 函數的檢查過程如下:

  1. 沒有定義 format_write 操作,同時暫存器不可讀,則認為暫存器不是 volatile 的。這裡有個坑。如果暫存器是隻寫的,比如 W1C 寫 1 清的暫存器等 (對於硬體裝置,這樣的暫存器比較常見),在這裡會被判定為非 volatile 的,如果開了快取即是可快取的。在 regmap_update_bits() 操作中會出問題

  2. 和對暫存器的 writable 判斷類似,先檢查設定的 volatile_reg 回撥操作,再檢查 volatile_table 表。

  3. 如果既沒有設定 volatile_reg 回撥操作,也沒有設定 volatile_table 表,則根據快取設定判斷。如果開了快取,則認為所有暫存器都是非 volatile 的,即都可以快取,否則都是 volatile 的。

regmap_readable() 函數中對於暫存器是否可讀的判斷,與對暫存器是否可寫的判斷類似。但多了對 map->reg_read 暫存器讀操作的檢查,及格式化寫的檢查。

這裡不再詳細分析 regmap_raw_write()regmap_noinc_write()regmap_bulk_write() 等更復雜的裝置暫存器寫操作。

讀裝置暫存器

Linux 核心裝置驅動程式通過 regmap_read() 等函數讀裝置暫存器,相關的這些函數原型 (位於 include/linux/regmap.h) 如下:

int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val);
int regmap_raw_read(struct regmap *map, unsigned int reg,
		    void *val, size_t val_len);
int regmap_noinc_read(struct regmap *map, unsigned int reg,
		      void *val, size_t val_len);
int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val,
		     size_t val_count);

regmap_read() 函數同步地讀一個裝置暫存器,這個函數定義 (位於 drivers/base/regmap/regmap.c) 如下:

static int _regmap_read(struct regmap *map, unsigned int reg,
			unsigned int *val)
{
	int ret;
	void *context = _regmap_map_get_context(map);

	if (!map->cache_bypass) {
		ret = regcache_read(map, reg, val);
		if (ret == 0)
			return 0;
	}

	if (map->cache_only)
		return -EBUSY;

	if (!regmap_readable(map, reg))
		return -EIO;

	ret = map->reg_read(context, reg, val);
	if (ret == 0) {
		if (regmap_should_log(map))
			dev_info(map->dev, "%x => %x\n", reg, *val);

		trace_regmap_reg_read(map, reg, *val);

		if (!map->cache_bypass)
			regcache_write(map, reg, *val);
	}

	return ret;
}

/**
 * regmap_read() - Read a value from a single register
 *
 * @map: Register map to read from
 * @reg: Register to be read from
 * @val: Pointer to store read value
 *
 * A value of zero will be returned on success, a negative errno will
 * be returned in error cases.
 */
int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val)
{
	int ret;

	if (!IS_ALIGNED(reg, map->reg_stride))
		return -EINVAL;

	map->lock(map->lock_arg);

	ret = _regmap_read(map, reg, val);

	map->unlock(map->lock_arg);

	return ret;
}
EXPORT_SYMBOL_GPL(regmap_read);

regmap_write() 函數類似, regmap_read() 函數,首先,對 regmap 加鎖;然後,呼叫 _regmap_read() 函數執行讀操作;最後,解鎖並返回。_regmap_read() 函數的執行過程是清晰的幾個步驟:

  1. 如果開啟了快取,則先從快取讀,如果成功則返回,否則繼續執行。

  2. 如果設定了 map->cache_only,則報錯返回。裝置驅動程式掛起時,可以設定 map->cache_only,以防止意外地對裝置暫存器讀寫。

  3. 判斷暫存器是否可讀,如果不可讀,則報錯返回,否則繼續執行。對於只寫的裝置暫存器,如果開啟了快取,在這個函數中將讀到上次寫入的值。在邏輯上,這樣的返回值不太合適。這個函數更好的實現方法,似乎是將暫存器是否可讀的判斷,放在從快取讀暫存器前面。

  4. 讀取裝置暫存器。

  5. 讀取裝置暫存器成功,且開啟了快取,則將讀取的值寫入快取。

從快取中讀取裝置暫存器的值的函數 regcache_read() 定義 (位於 drivers/base/regmap/regcache.c) 如下:

int regcache_read(struct regmap *map,
		  unsigned int reg, unsigned int *value)
{
	int ret;

	if (map->cache_type == REGCACHE_NONE)
		return -ENOSYS;

	BUG_ON(!map->cache_ops);

	if (!regmap_volatile(map, reg)) {
		ret = map->cache_ops->read(map, reg, value);

		if (ret == 0)
			trace_regmap_reg_read_cache(map, reg, *value);

		return ret;
	}

	return -EINVAL;
}

快取操作針對開啟了快取的 regmap 的非 volatile 的暫存器。在 regcache_read() 函數中,它從快取實現中讀取暫存器的值。

這裡不再詳細分析 regmap_raw_read()regmap_noinc_read()regmap_bulk_read() 等更復雜的裝置暫存器讀操作。

裝置暫存器位更新

Linux 核心裝置驅動程式通過 regmap_update_bits() 等函數更新裝置暫存器的特定位,相關的這些函數原型 (位於 include/linux/regmap.h) 如下:

int regmap_update_bits_base(struct regmap *map, unsigned int reg,
			    unsigned int mask, unsigned int val,
			    bool *change, bool async, bool force);

static inline int regmap_update_bits(struct regmap *map, unsigned int reg,
				     unsigned int mask, unsigned int val)
{
	return regmap_update_bits_base(map, reg, mask, val, NULL, false, false);
}

static inline int regmap_update_bits_async(struct regmap *map, unsigned int reg,
					   unsigned int mask, unsigned int val)
{
	return regmap_update_bits_base(map, reg, mask, val, NULL, true, false);
}

static inline int regmap_update_bits_check(struct regmap *map, unsigned int reg,
					   unsigned int mask, unsigned int val,
					   bool *change)
{
	return regmap_update_bits_base(map, reg, mask, val,
				       change, false, false);
}

static inline int
regmap_update_bits_check_async(struct regmap *map, unsigned int reg,
			       unsigned int mask, unsigned int val,
			       bool *change)
{
	return regmap_update_bits_base(map, reg, mask, val,
				       change, true, false);
}

static inline int regmap_write_bits(struct regmap *map, unsigned int reg,
				    unsigned int mask, unsigned int val)
{
	return regmap_update_bits_base(map, reg, mask, val, NULL, false, true);
}

regmap_update_bits() 等函數傳入不同的引數呼叫 regmap_update_bits_base() 函數,後者定義 (位於 drivers/base/regmap/regmap.c) 如下:

static int _regmap_update_bits(struct regmap *map, unsigned int reg,
			       unsigned int mask, unsigned int val,
			       bool *change, bool force_write)
{
	int ret;
	unsigned int tmp, orig;

	if (change)
		*change = false;

	if (regmap_volatile(map, reg) && map->reg_update_bits) {
		ret = map->reg_update_bits(map->bus_context, reg, mask, val);
		if (ret == 0 && change)
			*change = true;
	} else {
		ret = _regmap_read(map, reg, &orig);
		if (ret != 0)
			return ret;

		tmp = orig & ~mask;
		tmp |= val & mask;

		if (force_write || (tmp != orig)) {
			ret = _regmap_write(map, reg, tmp);
			if (ret == 0 && change)
				*change = true;
		}
	}

	return ret;
}

/**
 * regmap_update_bits_base() - Perform a read/modify/write cycle on a register
 *
 * @map: Register map to update
 * @reg: Register to update
 * @mask: Bitmask to change
 * @val: New value for bitmask
 * @change: Boolean indicating if a write was done
 * @async: Boolean indicating asynchronously
 * @force: Boolean indicating use force update
 *
 * Perform a read/modify/write cycle on a register map with change, async, force
 * options.
 *
 * If async is true:
 *
 * With most buses the read must be done synchronously so this is most useful
 * for devices with a cache which do not need to interact with the hardware to
 * determine the current register value.
 *
 * Returns zero for success, a negative number on error.
 */
int regmap_update_bits_base(struct regmap *map, unsigned int reg,
			    unsigned int mask, unsigned int val,
			    bool *change, bool async, bool force)
{
	int ret;

	map->lock(map->lock_arg);

	map->async = async;

	ret = _regmap_update_bits(map, reg, mask, val, change, force);

	map->async = false;

	map->unlock(map->lock_arg);

	return ret;
}
EXPORT_SYMBOL_GPL(regmap_update_bits_base);

regmap_write()regmap_read() 函數類似,regmap_update_bits_base() 函數,首先,對 regmap 加鎖;然後,設定 map->async 標誌,呼叫 _regmap_update_bits() 函數執行暫存器位更新操作;最後,重置 map->async 標誌,解鎖並返回。_regmap_update_bits() 函數的執行分成幾種情況來處理:

  1. 暫存器為 volatile 的,同時設定了 reg_update_bits 回撥函數,則執行 reg_update_bits 回撥函數並返回結果。暫存器為 volatile 的,所以可以忽略對 cached 的操作。只寫暫存器會被判定為非 volatile 的,因而它們不會由 reg_update_bits 回撥函數處理。

  2. 其它情況。先讀取暫存器。特別需要關注的是對只寫暫存器的處理。第一次讀取只寫暫存器時,會讀取失敗並返回錯誤。如果之前對只寫暫存器有過寫入操作,且開了 cache,則會讀取之前寫入的值。隨後,如果要求強制寫,或要寫入的位的值與讀取的值不同,則將值寫入暫存器。如果開了 cache,寫操作會更新 cache。

考慮只寫暫存器通過 regmap_update_bits_base() 函數來更新,則要麼更新失敗,要麼很可能發現要更新的值和快取中的值一致,而不會實際去更新。對只寫暫存器的任何更新,regmap_write()regmap_write_bits() 函數是更好的選擇。

要使得對 regmap 各函數呼叫的行為符合預期,還是需要對這些函數的行為實現有所瞭解,並適當的設定驅動程式中各個暫存器的讀寫屬性。

整體看下來,regmap 機制提供的能力有這樣一些:

  1. 提供的裝置暫存器存取操作函數可以執行對裝置暫存器的互斥存取。
  2. 提高效率的 cache,其中包含多個 cache 策略可選。
  3. 統一的方便的 IO 存取操作函數存取 mmio,i2c 等不同匯流排的裝置 IO 暫存器。
  4. 通過 debugfs 偵錯相關裝置 IO 暫存器的能力。
  5. 良好的擴充套件能力。如未來要通過 regmap 機制支援一種新的存取裝置 IO 暫存器的匯流排,則僅需實現 struct regmap_bus 即可。

Done.