Browse Source

Merge remote-tracking branch 'regmap/topic/debugfs' into regmap-next

Mark Brown 12 years ago
parent
commit
bcf86687d6
4 changed files with 260 additions and 64 deletions
  1. 16 0
      drivers/base/regmap/internal.h
  2. 132 16
      drivers/base/regmap/regmap-debugfs.c
  3. 107 47
      drivers/base/regmap/regmap.c
  4. 5 1
      include/linux/regmap.h

+ 16 - 0
drivers/base/regmap/internal.h

@@ -15,10 +15,18 @@
 
 #include <linux/regmap.h>
 #include <linux/fs.h>
+#include <linux/list.h>
 
 struct regmap;
 struct regcache_ops;
 
+struct regmap_debugfs_off_cache {
+	struct list_head list;
+	off_t min;
+	off_t max;
+	unsigned int base_reg;
+};
+
 struct regmap_format {
 	size_t buf_size;
 	size_t reg_bytes;
@@ -50,6 +58,12 @@ struct regmap {
 #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;
 #endif
 
 	unsigned int max_register;
@@ -120,6 +134,8 @@ int _regmap_write(struct regmap *map, unsigned int reg,
 
 struct regmap_range_node {
 	struct rb_node node;
+	const char *name;
+	struct regmap *map;
 
 	unsigned int range_min;
 	unsigned int range_max;

+ 132 - 16
drivers/base/regmap/regmap-debugfs.c

@@ -56,17 +56,74 @@ static const struct file_operations regmap_name_fops = {
 	.llseek = default_llseek,
 };
 
-static ssize_t regmap_map_read_file(struct file *file, char __user *user_buf,
-				    size_t count, loff_t *ppos)
+/*
+ * Work out where the start offset maps into register numbers, bearing
+ * in mind that we suppress hidden registers.
+ */
+static unsigned int regmap_debugfs_get_dump_start(struct regmap *map,
+						  unsigned int base,
+						  loff_t from,
+						  loff_t *pos)
 {
-	int reg_len, val_len, tot_len;
-	size_t buf_pos = 0;
+	struct regmap_debugfs_off_cache *c = NULL;
 	loff_t p = 0;
+	unsigned int i, ret;
+
+	/*
+	 * If we don't have a cache build one so we don't have to do a
+	 * linear scan each time.
+	 */
+	if (list_empty(&map->debugfs_off_cache)) {
+		for (i = base; i <= map->max_register; i += map->reg_stride) {
+			/* Skip unprinted registers, closing off cache entry */
+			if (!regmap_readable(map, i) ||
+			    regmap_precious(map, i)) {
+				if (c) {
+					c->max = p - 1;
+					list_add_tail(&c->list,
+						      &map->debugfs_off_cache);
+					c = NULL;
+				}
+
+				continue;
+			}
+
+			/* No cache entry?  Start a new one */
+			if (!c) {
+				c = kzalloc(sizeof(*c), GFP_KERNEL);
+				if (!c)
+					break;
+				c->min = p;
+				c->base_reg = i;
+			}
+
+			p += map->debugfs_tot_len;
+		}
+	}
+
+	/* Find the relevant block */
+	list_for_each_entry(c, &map->debugfs_off_cache, list) {
+		if (*pos >= c->min && *pos <= c->max) {
+			*pos = c->min;
+			return c->base_reg;
+		}
+
+		ret = c->max;
+	}
+
+	return ret;
+}
+
+static ssize_t regmap_read_debugfs(struct regmap *map, unsigned int from,
+				   unsigned int to, char __user *user_buf,
+				   size_t count, loff_t *ppos)
+{
+	size_t buf_pos = 0;
+	loff_t p = *ppos;
 	ssize_t ret;
 	int i;
-	struct regmap *map = file->private_data;
 	char *buf;
-	unsigned int val;
+	unsigned int val, start_reg;
 
 	if (*ppos < 0 || !count)
 		return -EINVAL;
@@ -76,11 +133,18 @@ static ssize_t regmap_map_read_file(struct file *file, char __user *user_buf,
 		return -ENOMEM;
 
 	/* Calculate the length of a fixed format  */
-	reg_len = regmap_calc_reg_len(map->max_register, buf, count);
-	val_len = 2 * map->format.val_bytes;
-	tot_len = reg_len + val_len + 3;      /* : \n */
+	if (!map->debugfs_tot_len) {
+		map->debugfs_reg_len = regmap_calc_reg_len(map->max_register,
+							   buf, count);
+		map->debugfs_val_len = 2 * map->format.val_bytes;
+		map->debugfs_tot_len = map->debugfs_reg_len +
+			map->debugfs_val_len + 3;      /* : \n */
+	}
 
-	for (i = 0; i <= map->max_register; i += map->reg_stride) {
+	/* Work out which register we're starting at */
+	start_reg = regmap_debugfs_get_dump_start(map, from, *ppos, &p);
+
+	for (i = start_reg; i <= to; i += map->reg_stride) {
 		if (!regmap_readable(map, i))
 			continue;
 
@@ -90,26 +154,27 @@ static ssize_t regmap_map_read_file(struct file *file, char __user *user_buf,
 		/* If we're in the region the user is trying to read */
 		if (p >= *ppos) {
 			/* ...but not beyond it */
-			if (buf_pos >= count - 1 - tot_len)
+			if (buf_pos + 1 + map->debugfs_tot_len >= count)
 				break;
 
 			/* Format the register */
 			snprintf(buf + buf_pos, count - buf_pos, "%.*x: ",
-				 reg_len, i);
-			buf_pos += reg_len + 2;
+				 map->debugfs_reg_len, i - from);
+			buf_pos += map->debugfs_reg_len + 2;
 
 			/* Format the value, write all X if we can't read */
 			ret = regmap_read(map, i, &val);
 			if (ret == 0)
 				snprintf(buf + buf_pos, count - buf_pos,
-					 "%.*x", val_len, val);
+					 "%.*x", map->debugfs_val_len, val);
 			else
-				memset(buf + buf_pos, 'X', val_len);
+				memset(buf + buf_pos, 'X',
+				       map->debugfs_val_len);
 			buf_pos += 2 * map->format.val_bytes;
 
 			buf[buf_pos++] = '\n';
 		}
-		p += tot_len;
+		p += map->debugfs_tot_len;
 	}
 
 	ret = buf_pos;
@@ -126,6 +191,15 @@ out:
 	return ret;
 }
 
+static ssize_t regmap_map_read_file(struct file *file, char __user *user_buf,
+				    size_t count, loff_t *ppos)
+{
+	struct regmap *map = file->private_data;
+
+	return regmap_read_debugfs(map, 0, map->max_register, user_buf,
+				   count, ppos);
+}
+
 #undef REGMAP_ALLOW_WRITE_DEBUGFS
 #ifdef REGMAP_ALLOW_WRITE_DEBUGFS
 /*
@@ -174,6 +248,22 @@ static const struct file_operations regmap_map_fops = {
 	.llseek = default_llseek,
 };
 
+static ssize_t regmap_range_read_file(struct file *file, char __user *user_buf,
+				      size_t count, loff_t *ppos)
+{
+	struct regmap_range_node *range = file->private_data;
+	struct regmap *map = range->map;
+
+	return regmap_read_debugfs(map, range->range_min, range->range_max,
+				   user_buf, count, ppos);
+}
+
+static const struct file_operations regmap_range_fops = {
+	.open = simple_open,
+	.read = regmap_range_read_file,
+	.llseek = default_llseek,
+};
+
 static ssize_t regmap_access_read_file(struct file *file,
 				       char __user *user_buf, size_t count,
 				       loff_t *ppos)
@@ -244,6 +334,11 @@ static const struct file_operations regmap_access_fops = {
 
 void regmap_debugfs_init(struct regmap *map, const char *name)
 {
+	struct rb_node *next;
+	struct regmap_range_node *range_node;
+
+	INIT_LIST_HEAD(&map->debugfs_off_cache);
+
 	if (name) {
 		map->debugfs_name = kasprintf(GFP_KERNEL, "%s-%s",
 					      dev_name(map->dev), name);
@@ -276,11 +371,32 @@ void regmap_debugfs_init(struct regmap *map, const char *name)
 		debugfs_create_bool("cache_bypass", 0400, map->debugfs,
 				    &map->cache_bypass);
 	}
+
+	next = rb_first(&map->range_tree);
+	while (next) {
+		range_node = rb_entry(next, struct regmap_range_node, node);
+
+		if (range_node->name)
+			debugfs_create_file(range_node->name, 0400,
+					    map->debugfs, range_node,
+					    &regmap_range_fops);
+
+		next = rb_next(&range_node->node);
+	}
 }
 
 void regmap_debugfs_exit(struct regmap *map)
 {
+	struct regmap_debugfs_off_cache *c;
+
 	debugfs_remove_recursive(map->debugfs);
+	while (!list_empty(&map->debugfs_off_cache)) {
+		c = list_first_entry(&map->debugfs_off_cache,
+				     struct regmap_debugfs_off_cache,
+				     list);
+		list_del(&c->list);
+		kfree(c);
+	}
 	kfree(map->debugfs_name);
 }
 

+ 107 - 47
drivers/base/regmap/regmap.c

@@ -519,20 +519,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 +558,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 +579,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 +590,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 +606,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 +765,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;
-
-		if (val_num > 1) {
-			/* Bulk write shouldn't cross range boundary */
-			if (*reg + val_num - 1 > range->range_max)
-				return -EINVAL;
+	win_offset = (*reg - range->range_min) % range->window_len;
+	win_page = (*reg - range->range_min) / range->window_len;
 
-			/* ... or single page boundary */
-			if (val_num > range->window_len - win_offset)
-				return -EINVAL;
-		}
+	if (val_num > 1) {
+		/* Bulk write shouldn't cross range boundary */
+		if (*reg + val_num - 1 > range->range_max)
+			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;
+		/* ... or single page boundary */
+		if (val_num > range->window_len - win_offset)
+			return -EINVAL;
+	}
 
-			ret = _regmap_update_bits(map, range->selector_reg,
-					range->selector_mask,
-					win_page << range->selector_shift,
-					&page_chg);
+	/* 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;
 
-			map->work_buf = orig_work_buf;
+		ret = _regmap_update_bits(map, range->selector_reg,
+					  range->selector_mask,
+					  win_page << range->selector_shift,
+					  &page_chg);
 
-			if (ret < 0)
-				return ret;
-		}
+		map->work_buf = orig_work_buf;
 
-		*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;
@@ -825,9 +850,35 @@ static int _regmap_raw_write(struct regmap *map, unsigned int reg,
 		}
 	}
 
-	ret = _regmap_select_page(map, &reg, 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, &reg, range, val_num);
+		if (ret != 0)
+			return ret;
+	}
 
 	map->format.format_reg(map->work_buf, reg, map->reg_shift);
 
@@ -876,6 +927,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 +949,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, &reg, 1);
-		if (ret < 0)
-			return ret;
+		range = _regmap_range_lookup(map, reg);
+		if (range) {
+			ret = _regmap_select_page(map, &reg, range, 1);
+			if (ret != 0)
+				return ret;
+		}
 
 		map->format.format_write(map, reg, val);
 
@@ -1055,12 +1110,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, &reg, val_len / map->format.val_bytes);
-	if (ret < 0)
-		return ret;
+	range = _regmap_range_lookup(map, reg);
+	if (range) {
+		ret = _regmap_select_page(map, &reg, range,
+					  val_len / map->format.val_bytes);
+		if (ret != 0)
+			return ret;
+	}
 
 	map->format.format_reg(map->work_buf, reg, map->reg_shift);
 

+ 5 - 1
include/linux/regmap.h

@@ -133,7 +133,7 @@ struct regmap_config {
 	enum regmap_endian val_format_endian;
 
 	const struct regmap_range_cfg *ranges;
-	unsigned int n_ranges;
+	unsigned int num_ranges;
 };
 
 /**
@@ -142,6 +142,8 @@ struct regmap_config {
  *     1. page selector register update;
  *     2. access through data window registers.
  *
+ * @name: Descriptive name for diagnostics
+ *
  * @range_min: Address of the lowest register address in virtual range.
  * @range_max: Address of the highest register in virtual range.
  *
@@ -153,6 +155,8 @@ struct regmap_config {
  * @window_len: Number of registers in data window.
  */
 struct regmap_range_cfg {
+	const char *name;
+
 	/* Registers of virtual address range */
 	unsigned int range_min;
 	unsigned int range_max;