diff options
Diffstat (limited to 'drivers/base/regmap/regcache-maple.c')
| -rw-r--r-- | drivers/base/regmap/regcache-maple.c | 282 | 
1 files changed, 282 insertions, 0 deletions
diff --git a/drivers/base/regmap/regcache-maple.c b/drivers/base/regmap/regcache-maple.c new file mode 100644 index 000000000000..c2e3a0f6c218 --- /dev/null +++ b/drivers/base/regmap/regcache-maple.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Register cache access API - maple tree based cache +// +// Copyright 2023 Arm, Ltd +// +// Author: Mark Brown <[email protected]> + +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/maple_tree.h> +#include <linux/slab.h> + +#include "internal.h" + +static int regcache_maple_read(struct regmap *map, +			       unsigned int reg, unsigned int *value) +{ +	struct maple_tree *mt = map->cache; +	MA_STATE(mas, mt, reg, reg); +	unsigned long *entry; + +	rcu_read_lock(); + +	entry = mas_walk(&mas); +	if (!entry) { +		rcu_read_unlock(); +		return -ENOENT; +	} + +	*value = entry[reg - mas.index]; + +	rcu_read_unlock(); + +	return 0; +} + +static int regcache_maple_write(struct regmap *map, unsigned int reg, +				unsigned int val) +{ +	struct maple_tree *mt = map->cache; +	MA_STATE(mas, mt, reg, reg); +	unsigned long *entry, *upper, *lower; +	unsigned long index, last; +	size_t lower_sz, upper_sz; +	int ret; + +	rcu_read_lock(); + +	entry = mas_walk(&mas); +	if (entry) { +		entry[reg - mas.index] = val; +		rcu_read_unlock(); +		return 0; +	} + +	/* Any adjacent entries to extend/merge? */ +	mas_set_range(&mas, reg - 1, reg + 1); +	index = reg; +	last = reg; + +	lower = mas_find(&mas, reg - 1); +	if (lower) { +		index = mas.index; +		lower_sz = (mas.last - mas.index + 1) * sizeof(unsigned long); +	} + +	upper = mas_find(&mas, reg + 1); +	if (upper) { +		last = mas.last; +		upper_sz = (mas.last - mas.index + 1) * sizeof(unsigned long); +	} + +	rcu_read_unlock(); + +	entry = kmalloc((last - index + 1) * sizeof(unsigned long), +			GFP_KERNEL); +	if (!entry) +		return -ENOMEM; + +	if (lower) +		memcpy(entry, lower, lower_sz); +	entry[reg - index] = val; +	if (upper) +		memcpy(&entry[reg - index + 1], upper, upper_sz); + +	/* +	 * This is safe because the regmap lock means the Maple lock +	 * is redundant, but we need to take it due to lockdep asserts +	 * in the maple tree code. +	 */ +	mas_lock(&mas); + +	mas_set_range(&mas, index, last); +	ret = mas_store_gfp(&mas, entry, GFP_KERNEL); + +	mas_unlock(&mas); + +	if (ret == 0) { +		kfree(lower); +		kfree(upper); +	} +	 +	return ret; +} + +static int regcache_maple_drop(struct regmap *map, unsigned int min, +			       unsigned int max) +{ +	struct maple_tree *mt = map->cache; +	MA_STATE(mas, mt, min, max); +	unsigned long *entry, *lower, *upper; +	unsigned long lower_index, lower_last; +	unsigned long upper_index, upper_last; +	int ret; + +	lower = NULL; +	upper = NULL; + +	mas_lock(&mas); + +	mas_for_each(&mas, entry, max) { +		/* +		 * This is safe because the regmap lock means the +		 * Maple lock is redundant, but we need to take it due +		 * to lockdep asserts in the maple tree code. +		 */ +		mas_unlock(&mas); + +		/* Do we need to save any of this entry? */ +		if (mas.index < min) { +			lower_index = mas.index; +			lower_last = min -1; + +			lower = kmemdup(entry, ((min - mas.index) * +						sizeof(unsigned long)), +					GFP_KERNEL); +			if (!lower) { +				ret = -ENOMEM; +				goto out_unlocked; +			} +		} + +		if (mas.last > max) { +			upper_index = max + 1; +			upper_last = mas.last; + +			upper = kmemdup(&entry[max + 1], +					((mas.last - max) * +					 sizeof(unsigned long)), +					GFP_KERNEL); +			if (!upper) { +				ret = -ENOMEM; +				goto out_unlocked; +			} +		} + +		kfree(entry); +		mas_lock(&mas); +		mas_erase(&mas); + +		/* Insert new nodes with the saved data */ +		if (lower) { +			mas_set_range(&mas, lower_index, lower_last); +			ret = mas_store_gfp(&mas, lower, GFP_KERNEL); +			if (ret != 0) +				goto out; +			lower = NULL; +		} + +		if (upper) { +			mas_set_range(&mas, upper_index, upper_last); +			ret = mas_store_gfp(&mas, upper, GFP_KERNEL); +			if (ret != 0) +				goto out; +			upper = NULL; +		} +	} + +out: +	mas_unlock(&mas); +out_unlocked: +	kfree(lower); +	kfree(upper); + +	return ret; +} + +static int regcache_maple_sync(struct regmap *map, unsigned int min, +			       unsigned int max) +{ +	struct maple_tree *mt = map->cache; +	unsigned long *entry; +	MA_STATE(mas, mt, min, max); +	unsigned long lmin = min; +	unsigned long lmax = max; +	unsigned int r; +	int ret; + +	map->cache_bypass = true; + +	rcu_read_lock(); + +	mas_for_each(&mas, entry, max) { +		for (r = max(mas.index, lmin); r <= min(mas.last, lmax); r++) { +			mas_pause(&mas); +			rcu_read_unlock(); +			ret = regcache_sync_val(map, r, entry[r - mas.index]); +			if (ret != 0) +				goto out; +			rcu_read_lock(); +		} +	} + +	rcu_read_unlock(); + +out: +	map->cache_bypass = false; + +	return ret; +} + +static int regcache_maple_exit(struct regmap *map) +{ +	struct maple_tree *mt = map->cache; +	MA_STATE(mas, mt, 0, UINT_MAX); +	unsigned int *entry;; + +	/* if we've already been called then just return */ +	if (!mt) +		return 0; + +	mas_lock(&mas); +	mas_for_each(&mas, entry, UINT_MAX) +		kfree(entry); +	__mt_destroy(mt); +	mas_unlock(&mas); + +	kfree(mt); +	map->cache = NULL; + +	return 0; +} + +static int regcache_maple_init(struct regmap *map) +{ +	struct maple_tree *mt; +	int i; +	int ret; + +	mt = kmalloc(sizeof(*mt), GFP_KERNEL); +	if (!mt) +		return -ENOMEM; +	map->cache = mt; + +	mt_init(mt); + +	for (i = 0; i < map->num_reg_defaults; i++) { +		ret = regcache_maple_write(map, +					   map->reg_defaults[i].reg, +					   map->reg_defaults[i].def); +		if (ret) +			goto err; +	} + +	return 0; + +err: +	regcache_maple_exit(map); +	return ret; +} + +struct regcache_ops regcache_maple_ops = { +	.type = REGCACHE_MAPLE, +	.name = "maple", +	.init = regcache_maple_init, +	.exit = regcache_maple_exit, +	.read = regcache_maple_read, +	.write = regcache_maple_write, +	.drop = regcache_maple_drop, +	.sync = regcache_maple_sync, +};  |