First quick & dirty attempt to add some debugfs hooks in order
to ease the debug of hwmods.

I send it just in case someone else find that useful.

It is based on lo/for-next + for-next-fixes + pm-wip/hwmods-reset and is 
available here:
git://dev.omapzoom.org/pub/scm/swarch/linux-omap-adv.git pm-wip/hwmods-debugfs

Benoit


Allow to dump each hwmods internal state and change manually
the module state and the reset state if applicable.

Usage:

- Mount the debugfs filesystem
mount -t debugfs none /sys/kernel/debug

- dump a individual hwmod status:
cat /sys/kernel/debug/hwmods/mcbsp1/summary
  mcbsp1
    state:        idle:5
    class:        mcbsp
    flags:        0x0000
    address:      0x40122000-0x401220ff (mpu)
    address:      0x49022000-0x490220ff
    clock:        mcbsp1_fck
    irqs(1):
      (null):49
    dmas(2):
      tx:33
      rx:34

- change the state of an hwmod:
echo enable > /sys/kernel/debug/hwmods/ipu/state
echo idle > /sys/kernel/debug/hwmods/ipu/state
echo disable > /sys/kernel/debug/hwmods/ipu/state

- assert an hardreset line
echo 1 > /sys/kernel/debug/hwmods/ipu/resets/mmu_cache
- deassert a hardreset line
echo 0 > /sys/kernel/debug/hwmods/ipu/resets/mmu_cache

- trigger a softreset in the module
echo reset > /sys/kernel/debug/hwmods/XXX/state

Signed-off-by: Benoit Cousson <b-cous...@ti.com>
Cc: Paul Walmsley <p...@pwsan.com>
Cc: Kevin Hilman <khil...@deeprootsystems.com>
---
 arch/arm/mach-omap2/Makefile                 |    4 +-
 arch/arm/mach-omap2/omap_hwmod_debug.c       |  387 ++++++++++++++++++++++++++
 arch/arm/plat-omap/include/plat/omap_hwmod.h |    9 +-
 3 files changed, 398 insertions(+), 2 deletions(-)
 create mode 100644 arch/arm/mach-omap2/omap_hwmod_debug.c

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
-- 
1.6.1.3

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to