@@ -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
new file mode 100644
@@ -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);
@@ -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