diff mbox

[RFC] OMAP: hwmod: Add debugfs support for omap_hwmod

Message ID 1280961833-25002-1-git-send-email-b-cousson@ti.com (mailing list archive)
State New, archived
Headers show

Commit Message

Benoit Cousson Aug. 4, 2010, 10:43 p.m. UTC
None
diff mbox

Patch

diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile
index 63b2d88..2f2b25b 100644
--- a/arch/arm/mach-omap2/Makefile
+++ b/arch/arm/mach-omap2/Makefile
@@ -17,7 +17,9 @@  obj-$(CONFIG_ARCH_OMAP2) += $(omap-2-3-common) $(prcm-common) $(hwmod-common)
 obj-$(CONFIG_ARCH_OMAP3) += $(omap-2-3-common) $(prcm-common) $(hwmod-common)
 obj-$(CONFIG_ARCH_OMAP4) += $(prcm-common) $(hwmod-common)
 
-obj-$(CONFIG_OMAP_MCBSP) += mcbsp.o
+obj-$(CONFIG_OMAP_MCBSP)		+= mcbsp.o
+obj-$(CONFIG_DEBUG_FS)			+= omap_hwmod_debug.o
+
 
 # SMP support ONLY available for OMAP4
 obj-$(CONFIG_SMP)			+= omap-smp.o omap-headsmp.o
diff --git a/arch/arm/mach-omap2/omap_hwmod_debug.c b/arch/arm/mach-omap2/omap_hwmod_debug.c
new file mode 100644
index 0000000..3f48977
--- /dev/null
+++ b/arch/arm/mach-omap2/omap_hwmod_debug.c
@@ -0,0 +1,387 @@ 
+/*
+ * omap_hwmod debugfs implementation
+ *
+ * Copyright (C) 2010 Texas Instruments, Inc.
+ *
+ * Benoit Cousson
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Expose a debufs interface in order to check and modify hwmod state
+ * The current directory structure is:
+ *
+ * hwmods
+ *  +-hwmod_mpu
+ *  +-hwmod_dsp
+ *  .
+ *  .
+ *  +-hwmod_xxx   : hwmod node
+ *     +-state    : internal state (RO)
+ *     +-summary  : global view of hwmod definition
+ *     +-resets   : reset lines
+ *        +-rst1  : reset state / control (RW)
+ *        +-rst2  :
+ *
+ * To do:
+ * - Add irq / dma dump
+ * - Add clock dump
+ */
+#undef DEBUG
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+
+#include <plat/clock.h>
+#include <plat/common.h>
+#include <plat/omap_hwmod.h>
+
+
+/*
+ * DEBUGFS helper macros
+ *
+ * This code is already used in several omap drivers, so eventually it will be
+ * good to move that to plat-omap and share the same code.
+ */
+
+#define DEFINE_DEBUGFS_SHOW(__fops, __show)				\
+static int __fops ## _open(struct inode *inode, struct file *file)	\
+{									\
+	return single_open(file, __show, inode->i_private);		\
+}									\
+static const struct file_operations __fops = {				\
+	.owner	 	= THIS_MODULE,					\
+	.open	 	= __fops ## _open,				\
+	.read           = seq_read,					\
+	.llseek         = seq_lseek,					\
+	.release        = single_release,				\
+};
+
+/*
+ * Aggregate reset information in a specific structure, because the reset
+ * node does not contain any link to the parent hwmod structure
+ */
+struct reset_info {
+	struct omap_hwmod 	*oh;
+	const char		*name;
+	u8			rst_shift;
+};
+
+/* internal hwmod states */
+static const char* states[_HWMOD_STATE_LAST + 1] = {
+	[_HWMOD_STATE_UNKNOWN] 		= "unknown",
+	[_HWMOD_STATE_REGISTERED] 	= "registered",
+	[_HWMOD_STATE_CLKS_INITED] 	= "clks_inited",
+	[_HWMOD_STATE_INITIALIZED] 	= "initialized",
+	[_HWMOD_STATE_ENABLED] 		= "enabled",
+	[_HWMOD_STATE_IDLE] 		= "idle",
+	[_HWMOD_STATE_DISABLED] 	= "disabled",
+};
+
+const char * _state_str(int state)
+{
+	if (state < 0 || state > _HWMOD_STATE_LAST)
+		return "invalid_state";
+
+	return states[state];
+}
+
+static int _set_state(void *data, u64 val)
+{
+	*(u8 *)data = val;
+	return 0;
+}
+static int _get_state(void *data, u64 *val)
+{
+	*val = *(u8 *)data;
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(state_fops, _get_state, _set_state, "%llu\n");
+
+static int default_open(struct inode *inode, struct file *file)
+{
+	if (inode->i_private)
+		file->private_data = inode->i_private;
+
+	return 0;
+}
+
+#define MAX_BUFFER_SIZE 16
+
+static ssize_t read_hwmod_state(struct file *file, char __user *user_buf,
+			        size_t count, loff_t *ppos)
+{
+	char buf[MAX_BUFFER_SIZE];
+	struct omap_hwmod *oh = file->private_data;
+	const char *msg = _state_str(oh->_state);
+
+	int len = snprintf(buf, MAX_BUFFER_SIZE, "%s\n", msg);
+
+	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+}
+
+static ssize_t write_hwmod_state(struct file *file, const char __user *user_buf,
+			         size_t count, loff_t *ppos)
+{
+	char buf[32];
+	int buf_size;
+	char *p;
+	int len;
+	struct omap_hwmod *oh = file->private_data;
+
+	if (!oh || !oh->name)
+		return -EINVAL;
+
+	buf_size = min(count, (sizeof(buf)-1));
+	if (copy_from_user(buf, user_buf, buf_size))
+		return -EFAULT;
+
+	p = memchr(buf, '\n', buf_size);
+	len = p ? p - buf : buf_size;
+	buf[len] = '\0';
+
+	if (!strncmp(buf, "enable", len))
+		omap_hwmod_enable(oh);
+	else if (!strncmp(buf, "idle", len))
+		omap_hwmod_idle(oh);
+	else if (!strncmp(buf, "disable", len))
+		omap_hwmod_shutdown(oh);
+	else if (!strncmp(buf, "reset", len))
+		omap_hwmod_reset(oh);
+	else
+		pr_warning("write_hwmod_state: invalid state: %s\n", buf);
+
+	return count;
+}
+
+static const struct file_operations hwmod_state_fops = {
+	.read =		read_hwmod_state,
+	.write =	write_hwmod_state,
+	.open =		default_open,
+};
+
+/**
+ * _reset_get - helper function for debugfs read to reset line
+ * @data: pointer to data initialized during debugfs_create_file. In this
+ * case this is a pointer to struct reset_info
+ * @val: pointer to the value that will be read and propagate to the debufs
+ * interface
+ *
+ * Returns: 0 if the reset is deasserted or asserted (0 or 1), any other states
+ * are invalid, so return -EINVAL in that case.
+ */
+static int _reset_get(void *data, u64 *val)
+{
+	struct reset_info *info = data;
+	int state;
+
+	state = omap_hwmod_hardreset_state(info->oh, info->name);
+
+	pr_debug("_reset_get: %s:%d %d\n", info->name, info->rst_shift, state);
+
+	*val = (u64)state;
+
+	if (state >= 0)
+		return 0;
+
+	return *val;
+}
+
+/**
+ * _reset_set - helper function for debugfs write to reset line
+ * @data: pointer to data initialized during debugfs_create_file. In this
+ * case this is a pointer to struct reset_info
+ * @val: value that should written from the debufs interface
+ *
+ * Assert or deassert the reset line depending of the value written
+ * on the debugfs "rst" file entry
+ * Returns -EINVAL if val is not 0 or 1.
+ */
+static int _reset_set(void *data, u64 val)
+{
+	struct reset_info *info = data;
+	int ret = -EINVAL;
+
+	pr_debug("_reset_set: %s[%d]: %llu\n", info->name, info->rst_shift,
+					       val);
+
+	if (val == 1)
+		ret = omap_hwmod_hardreset_assert(info->oh, info->name);
+	else if	(val == 0)
+		ret = omap_hwmod_hardreset_deassert(info->oh, info->name);
+
+	return ret;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(reset_fops, _reset_get, _reset_set, "%llu\n");
+
+
+
+static struct omap_hwmod_addr_space* _print_addresses(struct seq_file *s,
+						      struct omap_hwmod *oh)
+{
+	struct omap_hwmod_ocp_if *os = NULL;
+	struct omap_hwmod_addr_space *mem;
+	int j;
+
+	if (!oh || oh->slaves_cnt == 0) {
+		seq_printf(s, "  address:      N/A\n");
+		return NULL;
+	}
+
+	for (j = 0; j < oh->slaves_cnt; j++) {
+		int i;
+
+		os = oh->slaves[j];
+		if (!os->addr)
+			continue;
+
+		if (os->user & OCP_USER_MPU) {
+			for (i = 0, mem = os->addr; i < os->addr_cnt; i++, mem++)
+				seq_printf(s, "  address:      0x%08x-0x%08x (mpu)\n",
+					   mem->pa_start, mem->pa_end);
+		} else {
+			for (i = 0, mem = os->addr; i < os->addr_cnt; i++, mem++)
+				seq_printf(s, "  address:      0x%08x-0x%08x\n",
+					   mem->pa_start, mem->pa_end);
+		}
+	}
+
+	return NULL;
+}
+
+static int show_hwmod_summary(struct omap_hwmod *oh, void* param)
+{
+	struct seq_file *s = param;
+	int i;
+
+	if (!oh || !oh->name)
+		return -EINVAL;
+
+	seq_printf(s, "%s\n", oh->name);
+	seq_printf(s, "  state:        %s:%d\n", _state_str(oh->_state),
+		   oh->_state);
+	seq_printf(s, "  class:        %s\n", oh->class->name);
+	seq_printf(s, "  flags:        0x%04x\n", oh->flags);
+
+	_print_addresses(s, oh);
+
+	if (!IS_ERR_OR_NULL(oh->_clk))
+		seq_printf(s, "  clock:        %s\n", oh->_clk->name);
+
+	if (oh->rst_lines_cnt > 0)
+		seq_printf(s, "  resets(%d):\n", oh->rst_lines_cnt);
+	for (i = 0; i < oh->rst_lines_cnt; i++) {
+		int state = omap_hwmod_hardreset_state(oh,
+						       oh->rst_lines[i].name);
+		seq_printf(s, "    %s:%d\n", oh->rst_lines[i].name,
+			   state);
+	}
+
+	if (oh->mpu_irqs_cnt > 0)
+		seq_printf(s, "  irqs(%d):\n", oh->mpu_irqs_cnt);
+	for (i = 0; i < oh->mpu_irqs_cnt; i++)
+		seq_printf(s, "    %s:%d\n", oh->mpu_irqs[i].name,
+			   oh->mpu_irqs[i].irq);
+
+	if (oh->sdma_reqs_cnt > 0)
+		seq_printf(s, "  dmas(%d):\n", oh->sdma_reqs_cnt);
+	for (i = 0; i < oh->sdma_reqs_cnt; i++)
+		seq_printf(s, "    %s:%d\n", oh->sdma_reqs[i].name,
+			   oh->sdma_reqs[i].dma_req);
+
+	return 0;
+}
+
+static int show_hwmod(struct seq_file *s, void *unused)
+{
+	struct omap_hwmod *oh = s->private;
+
+	show_hwmod_summary(oh, s);
+
+	return 0;
+}
+DEFINE_DEBUGFS_SHOW(_hwmod_fops, show_hwmod);
+
+/**
+ * create_debugfs_entry - create a debugfs entry per omap_hwmod
+ * @oh: struct omap_hwmod *
+ * @parent: reference to the parent directory dentry needed for the creation
+ * of the files inside the debugfs directory
+ *
+ * Create a directory entry for each hwmod.
+ * Each directory will contain at least one state file and potentially
+ * some files that will represent the HW reset lines of that IP.
+ */
+static int create_debugfs_entry(struct omap_hwmod *oh, void* parent)
+{
+	struct dentry *d, *fd;
+	int i;
+
+	if (!oh || !oh->name)
+		return -EINVAL;
+
+	pr_debug("creating debugfs entry: %s[%p]\n", oh->name, oh);
+
+	d = debugfs_create_dir(oh->name, parent);
+	if (IS_ERR(d))
+		return PTR_ERR(d);
+
+	fd = debugfs_create_file("summary", S_IRUGO, d, oh, &_hwmod_fops);
+	if (IS_ERR(fd))
+		return PTR_ERR(fd);
+
+	fd = debugfs_create_file("state", S_IRUGO|S_IWUSR, d, oh,
+				 &hwmod_state_fops);
+	if (IS_ERR(fd))
+		return PTR_ERR(fd);
+
+	if (oh->rst_lines_cnt > 0) {
+		struct dentry *drst = debugfs_create_dir("resets", d);
+		if (IS_ERR(drst))
+			return PTR_ERR(drst);
+
+		/* Create one directory entry per reset line */
+		for (i = 0; i < oh->rst_lines_cnt; i++) {
+			struct reset_info *info;
+			const char *name = oh->rst_lines[i].name;
+
+			/* XXX need to find a way to free that memory */
+			info = kmalloc(sizeof(struct reset_info), GFP_KERNEL);
+			if (!info)
+				return -ENOMEM;
+
+			info->oh 	= oh;
+			info->name 	= oh->rst_lines[i].name;
+			info->rst_shift = oh->rst_lines[i].rst_shift;
+
+			debugfs_create_file(name, S_IRUGO|S_IWUSR, drst, info,
+					    &reset_fops);
+		}
+	}
+
+	return 0;
+}
+
+/* hwmod_debugfs_init - Initialize the debugfs directory tree for hwmods */
+static int __init hwmod_debugfs_init(void)
+{
+	struct dentry *d;
+
+	pr_warning("omap_hwmod: Initialize debugfs support");
+
+	d = debugfs_create_dir("hwmods", NULL);
+	if (IS_ERR(d))
+		return PTR_ERR(d);
+
+	omap_hwmod_for_each(create_debugfs_entry, d);
+
+	return 0;
+}
+late_initcall(hwmod_debugfs_init);
diff --git a/arch/arm/plat-omap/include/plat/omap_hwmod.h b/arch/arm/plat-omap/include/plat/omap_hwmod.h
index 5941183..4a0b723 100644
--- a/arch/arm/plat-omap/include/plat/omap_hwmod.h
+++ b/arch/arm/plat-omap/include/plat/omap_hwmod.h
@@ -394,7 +394,13 @@  struct omap_hwmod_omap4_prcm {
  * INITIALIZED: reset (optionally), initialized, enabled, disabled
  *              (optionally)
  *
- *
+ * _HWMOD_STATE_UNKNOWN
+ * _HWMOD_STATE_REGISTERED
+ * _HWMOD_STATE_CLKS_INITED
+ * _HWMOD_STATE_INITIALIZED
+ * _HWMOD_STATE_ENABLED
+ * _HWMOD_STATE_IDLE
+ * _HWMOD_STATE_DISABLED
  */
 #define _HWMOD_STATE_UNKNOWN			0
 #define _HWMOD_STATE_REGISTERED			1
@@ -403,6 +409,7 @@  struct omap_hwmod_omap4_prcm {
 #define _HWMOD_STATE_ENABLED			4
 #define _HWMOD_STATE_IDLE			5
 #define _HWMOD_STATE_DISABLED			6
+#define _HWMOD_STATE_LAST			_HWMOD_STATE_DISABLED
 
 /**
  * struct omap_hwmod_class - the type of an IP block