diff options
Diffstat (limited to 'drivers/base/regmap/regmap.c')
| -rw-r--r-- | drivers/base/regmap/regmap.c | 269 | 
1 files changed, 193 insertions, 76 deletions
diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 52069d29ff1..42d5cb0f503 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -34,6 +34,36 @@ static int _regmap_update_bits(struct regmap *map, unsigned int reg,  			       unsigned int mask, unsigned int val,  			       bool *change); +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); + +static 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); +} +  bool regmap_writeable(struct regmap *map, unsigned int reg)  {  	if (map->max_register && reg > map->max_register) @@ -42,6 +72,9 @@ bool regmap_writeable(struct regmap *map, unsigned int reg)  	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;  } @@ -56,6 +89,9 @@ bool regmap_readable(struct regmap *map, unsigned int reg)  	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;  } @@ -67,6 +103,9 @@ bool regmap_volatile(struct regmap *map, unsigned int reg)  	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); +  	return true;  } @@ -78,11 +117,14 @@ bool regmap_precious(struct regmap *map, unsigned int reg)  	if (map->precious_reg)  		return map->precious_reg(map->dev, reg); +	if (map->precious_table) +		return _regmap_check_range_table(map, reg, map->precious_table); +  	return false;  }  static bool regmap_volatile_range(struct regmap *map, unsigned int reg, -	unsigned int num) +	size_t num)  {  	unsigned int i; @@ -214,23 +256,27 @@ static unsigned int regmap_parse_32_native(void *buf)  	return *(u32 *)buf;  } -static void regmap_lock_mutex(struct regmap *map) +static void regmap_lock_mutex(void *__map)  { +	struct regmap *map = __map;  	mutex_lock(&map->mutex);  } -static void regmap_unlock_mutex(struct regmap *map) +static void regmap_unlock_mutex(void *__map)  { +	struct regmap *map = __map;  	mutex_unlock(&map->mutex);  } -static void regmap_lock_spinlock(struct regmap *map) +static void regmap_lock_spinlock(void *__map)  { +	struct regmap *map = __map;  	spin_lock(&map->spinlock);  } -static void regmap_unlock_spinlock(struct regmap *map) +static void regmap_unlock_spinlock(void *__map)  { +	struct regmap *map = __map;  	spin_unlock(&map->spinlock);  } @@ -335,14 +381,21 @@ struct regmap *regmap_init(struct device *dev,  		goto err;  	} -	if (bus->fast_io) { -		spin_lock_init(&map->spinlock); -		map->lock = regmap_lock_spinlock; -		map->unlock = regmap_unlock_spinlock; +	if (config->lock && config->unlock) { +		map->lock = config->lock; +		map->unlock = config->unlock; +		map->lock_arg = config->lock_arg;  	} else { -		mutex_init(&map->mutex); -		map->lock = regmap_lock_mutex; -		map->unlock = regmap_unlock_mutex; +		if (bus->fast_io) { +			spin_lock_init(&map->spinlock); +			map->lock = regmap_lock_spinlock; +			map->unlock = regmap_unlock_spinlock; +		} else { +			mutex_init(&map->mutex); +			map->lock = regmap_lock_mutex; +			map->unlock = regmap_unlock_mutex; +		} +		map->lock_arg = map;  	}  	map->format.reg_bytes = DIV_ROUND_UP(config->reg_bits, 8);  	map->format.pad_bytes = config->pad_bits / 8; @@ -359,6 +412,10 @@ struct regmap *regmap_init(struct device *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; @@ -519,20 +576,38 @@ struct regmap *regmap_init(struct device *dev,  	}  	map->range_tree = RB_ROOT; -	for (i = 0; i < config->n_ranges; i++) { +	for (i = 0; i < config->num_ranges; i++) {  		const struct regmap_range_cfg *range_cfg = &config->ranges[i];  		struct regmap_range_node *new;  		/* Sanity check */ -		if (range_cfg->range_max < range_cfg->range_min || -		    range_cfg->range_max > map->max_register || -		    range_cfg->selector_reg > map->max_register || -		    range_cfg->window_len == 0) +		if (range_cfg->range_max < range_cfg->range_min) { +			dev_err(map->dev, "Invalid range %d: %d < %d\n", i, +				range_cfg->range_max, range_cfg->range_min);  			goto err_range; +		} + +		if (range_cfg->range_max > map->max_register) { +			dev_err(map->dev, "Invalid range %d: %d > %d\n", i, +				range_cfg->range_max, map->max_register); +			goto err_range; +		} + +		if (range_cfg->selector_reg > map->max_register) { +			dev_err(map->dev, +				"Invalid range %d: selector out of map\n", i); +			goto err_range; +		} + +		if (range_cfg->window_len == 0) { +			dev_err(map->dev, "Invalid range %d: window_len 0\n", +				i); +			goto err_range; +		}  		/* Make sure, that this register range has no selector  		   or data window within its boundary */ -		for (j = 0; j < config->n_ranges; j++) { +		for (j = 0; j < config->num_ranges; j++) {  			unsigned sel_reg = config->ranges[j].selector_reg;  			unsigned win_min = config->ranges[j].window_start;  			unsigned win_max = win_min + @@ -540,11 +615,17 @@ struct regmap *regmap_init(struct device *dev,  			if (range_cfg->range_min <= sel_reg &&  			    sel_reg <= range_cfg->range_max) { +				dev_err(map->dev, +					"Range %d: selector for %d in window\n", +					i, j);  				goto err_range;  			}  			if (!(win_max < range_cfg->range_min ||  			      win_min > range_cfg->range_max)) { +				dev_err(map->dev, +					"Range %d: window for %d in window\n", +					i, j);  				goto err_range;  			}  		} @@ -555,6 +636,8 @@ struct regmap *regmap_init(struct device *dev,  			goto err_range;  		} +		new->map = map; +		new->name = range_cfg->name;  		new->range_min = range_cfg->range_min;  		new->range_max = range_cfg->range_max;  		new->selector_reg = range_cfg->selector_reg; @@ -564,6 +647,7 @@ struct regmap *regmap_init(struct device *dev,  		new->window_len = range_cfg->window_len;  		if (_regmap_range_add(map, new) == false) { +			dev_err(map->dev, "Failed to add range %d\n", i);  			kfree(new);  			goto err_range;  		} @@ -579,7 +663,7 @@ struct regmap *regmap_init(struct device *dev,  	}  	ret = regcache_init(map, config); -	if (ret < 0) +	if (ret != 0)  		goto err_range;  	regmap_debugfs_init(map, config->name); @@ -738,59 +822,57 @@ struct regmap *dev_get_regmap(struct device *dev, const char *name)  EXPORT_SYMBOL_GPL(dev_get_regmap);  static int _regmap_select_page(struct regmap *map, unsigned int *reg, +			       struct regmap_range_node *range,  			       unsigned int val_num)  { -	struct regmap_range_node *range;  	void *orig_work_buf;  	unsigned int win_offset;  	unsigned int win_page;  	bool page_chg;  	int ret; -	range = _regmap_range_lookup(map, *reg); -	if (range) { -		win_offset = (*reg - range->range_min) % range->window_len; -		win_page = (*reg - range->range_min) / range->window_len; +	win_offset = (*reg - range->range_min) % range->window_len; +	win_page = (*reg - range->range_min) / range->window_len; -		if (val_num > 1) { -			/* Bulk write shouldn't cross range boundary */ -			if (*reg + val_num - 1 > range->range_max) -				return -EINVAL; +	if (val_num > 1) { +		/* Bulk write shouldn't cross range boundary */ +		if (*reg + val_num - 1 > range->range_max) +			return -EINVAL; -			/* ... or single page boundary */ -			if (val_num > range->window_len - win_offset) -				return -EINVAL; -		} +		/* ... or single page boundary */ +		if (val_num > range->window_len - win_offset) +			return -EINVAL; +	} -		/* It is possible to have selector register inside data window. -		   In that case, selector register is located on every page and -		   it needs no page switching, when accessed alone. */ -		if (val_num > 1 || -		    range->window_start + win_offset != range->selector_reg) { -			/* Use separate work_buf during page switching */ -			orig_work_buf = map->work_buf; -			map->work_buf = map->selector_work_buf; +	/* It is possible to have selector register inside data window. +	   In that case, selector register is located on every page and +	   it needs no page switching, when accessed alone. */ +	if (val_num > 1 || +	    range->window_start + win_offset != range->selector_reg) { +		/* Use separate work_buf during page switching */ +		orig_work_buf = map->work_buf; +		map->work_buf = map->selector_work_buf; -			ret = _regmap_update_bits(map, range->selector_reg, -					range->selector_mask, -					win_page << range->selector_shift, -					&page_chg); +		ret = _regmap_update_bits(map, range->selector_reg, +					  range->selector_mask, +					  win_page << range->selector_shift, +					  &page_chg); -			map->work_buf = orig_work_buf; +		map->work_buf = orig_work_buf; -			if (ret < 0) -				return ret; -		} - -		*reg = range->window_start + win_offset; +		if (ret != 0) +			return ret;  	} +	*reg = range->window_start + win_offset; +  	return 0;  }  static int _regmap_raw_write(struct regmap *map, unsigned int reg,  			     const void *val, size_t val_len)  { +	struct regmap_range_node *range;  	u8 *u8 = map->work_buf;  	void *buf;  	int ret = -ENOTSUPP; @@ -814,7 +896,7 @@ static int _regmap_raw_write(struct regmap *map, unsigned int reg,  					     ival);  			if (ret) {  				dev_err(map->dev, -				   "Error in caching of register: %u ret: %d\n", +					"Error in caching of register: %x ret: %d\n",  					reg + i, ret);  				return ret;  			} @@ -825,9 +907,35 @@ static int _regmap_raw_write(struct regmap *map, unsigned int reg,  		}  	} -	ret = _regmap_select_page(map, ®, val_len / map->format.val_bytes); -	if (ret < 0) -		return ret; +	range = _regmap_range_lookup(map, reg); +	if (range) { +		int val_num = val_len / map->format.val_bytes; +		int win_offset = (reg - range->range_min) % range->window_len; +		int win_residue = range->window_len - win_offset; + +		/* If the write goes beyond the end of the window split it */ +		while (val_num > win_residue) { +			dev_dbg(map->dev, "Writing window %d/%zu\n", +				win_residue, val_len / map->format.val_bytes); +			ret = _regmap_raw_write(map, reg, val, win_residue * +						map->format.val_bytes); +			if (ret != 0) +				return ret; + +			reg += win_residue; +			val_num -= win_residue; +			val += win_residue * map->format.val_bytes; +			val_len -= win_residue * map->format.val_bytes; + +			win_offset = (reg - range->range_min) % +				range->window_len; +			win_residue = range->window_len - win_offset; +		} + +		ret = _regmap_select_page(map, ®, range, val_num); +		if (ret != 0) +			return ret; +	}  	map->format.format_reg(map->work_buf, reg, map->reg_shift); @@ -876,6 +984,7 @@ static int _regmap_raw_write(struct regmap *map, unsigned int reg,  int _regmap_write(struct regmap *map, unsigned int reg,  		  unsigned int val)  { +	struct regmap_range_node *range;  	int ret;  	BUG_ON(!map->format.format_write && !map->format.format_val); @@ -897,9 +1006,12 @@ int _regmap_write(struct regmap *map, unsigned int reg,  	trace_regmap_reg_write(map->dev, reg, val);  	if (map->format.format_write) { -		ret = _regmap_select_page(map, ®, 1); -		if (ret < 0) -			return ret; +		range = _regmap_range_lookup(map, reg); +		if (range) { +			ret = _regmap_select_page(map, ®, range, 1); +			if (ret != 0) +				return ret; +		}  		map->format.format_write(map, reg, val); @@ -939,11 +1051,11 @@ int regmap_write(struct regmap *map, unsigned int reg, unsigned int val)  	if (reg % map->reg_stride)  		return -EINVAL; -	map->lock(map); +	map->lock(map->lock_arg);  	ret = _regmap_write(map, reg, val); -	map->unlock(map); +	map->unlock(map->lock_arg);  	return ret;  } @@ -975,11 +1087,11 @@ int regmap_raw_write(struct regmap *map, unsigned int reg,  	if (reg % map->reg_stride)  		return -EINVAL; -	map->lock(map); +	map->lock(map->lock_arg);  	ret = _regmap_raw_write(map, reg, val, val_len); -	map->unlock(map); +	map->unlock(map->lock_arg);  	return ret;  } @@ -1011,7 +1123,7 @@ int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val,  	if (reg % map->reg_stride)  		return -EINVAL; -	map->lock(map); +	map->lock(map->lock_arg);  	/* No formatting is require if val_byte is 1 */  	if (val_bytes == 1) { @@ -1047,7 +1159,7 @@ int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val,  		kfree(wval);  out: -	map->unlock(map); +	map->unlock(map->lock_arg);  	return ret;  }  EXPORT_SYMBOL_GPL(regmap_bulk_write); @@ -1055,12 +1167,17 @@ EXPORT_SYMBOL_GPL(regmap_bulk_write);  static int _regmap_raw_read(struct regmap *map, unsigned int reg, void *val,  			    unsigned int val_len)  { +	struct regmap_range_node *range;  	u8 *u8 = map->work_buf;  	int ret; -	ret = _regmap_select_page(map, ®, val_len / map->format.val_bytes); -	if (ret < 0) -		return ret; +	range = _regmap_range_lookup(map, reg); +	if (range) { +		ret = _regmap_select_page(map, ®, range, +					  val_len / map->format.val_bytes); +		if (ret != 0) +			return ret; +	}  	map->format.format_reg(map->work_buf, reg, map->reg_shift); @@ -1137,11 +1254,11 @@ int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val)  	if (reg % map->reg_stride)  		return -EINVAL; -	map->lock(map); +	map->lock(map->lock_arg);  	ret = _regmap_read(map, reg, val); -	map->unlock(map); +	map->unlock(map->lock_arg);  	return ret;  } @@ -1171,7 +1288,7 @@ int regmap_raw_read(struct regmap *map, unsigned int reg, void *val,  	if (reg % map->reg_stride)  		return -EINVAL; -	map->lock(map); +	map->lock(map->lock_arg);  	if (regmap_volatile_range(map, reg, val_count) || map->cache_bypass ||  	    map->cache_type == REGCACHE_NONE) { @@ -1193,7 +1310,7 @@ int regmap_raw_read(struct regmap *map, unsigned int reg, void *val,  	}   out: -	map->unlock(map); +	map->unlock(map->lock_arg);  	return ret;  } @@ -1300,9 +1417,9 @@ int regmap_update_bits(struct regmap *map, unsigned int reg,  	bool change;  	int ret; -	map->lock(map); +	map->lock(map->lock_arg);  	ret = _regmap_update_bits(map, reg, mask, val, &change); -	map->unlock(map); +	map->unlock(map->lock_arg);  	return ret;  } @@ -1326,9 +1443,9 @@ int regmap_update_bits_check(struct regmap *map, unsigned int reg,  {  	int ret; -	map->lock(map); +	map->lock(map->lock_arg);  	ret = _regmap_update_bits(map, reg, mask, val, change); -	map->unlock(map); +	map->unlock(map->lock_arg);  	return ret;  }  EXPORT_SYMBOL_GPL(regmap_update_bits_check); @@ -1357,7 +1474,7 @@ int regmap_register_patch(struct regmap *map, const struct reg_default *regs,  	if (map->patch)  		return -EBUSY; -	map->lock(map); +	map->lock(map->lock_arg);  	bypass = map->cache_bypass; @@ -1385,7 +1502,7 @@ int regmap_register_patch(struct regmap *map, const struct reg_default *regs,  out:  	map->cache_bypass = bypass; -	map->unlock(map); +	map->unlock(map->lock_arg);  	return ret;  }  |