From patchwork Wed Aug 4 22:43:53 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Benoit Cousson X-Patchwork-Id: 117170 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter.kernel.org (8.14.4/8.14.3) with ESMTP id o74MiMQo015141 for ; Wed, 4 Aug 2010 22:44:22 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1758086Ab0HDWoF (ORCPT ); Wed, 4 Aug 2010 18:44:05 -0400 Received: from bear.ext.ti.com ([192.94.94.41]:60624 "EHLO bear.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1757567Ab0HDWoD (ORCPT ); Wed, 4 Aug 2010 18:44:03 -0400 Received: from dlep35.itg.ti.com ([157.170.170.118]) by bear.ext.ti.com (8.13.7/8.13.7) with ESMTP id o74MhxrV017632 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO); Wed, 4 Aug 2010 17:43:59 -0500 Received: from localhost.localdomain (localhost [127.0.0.1]) by dlep35.itg.ti.com (8.13.7/8.13.7) with ESMTP id o74MhsBQ001532; Wed, 4 Aug 2010 17:43:54 -0500 (CDT) From: Benoit Cousson To: linux-omap@vger.kernel.org, khilman@deeprootsystems.com, paul@pwsan.com Cc: rnayak@ti.com, santosh.shilimkar@ti.com, Benoit Cousson Subject: [RFC PATCH] OMAP: hwmod: Add debugfs support for omap_hwmod Date: Thu, 5 Aug 2010 00:43:53 +0200 Message-Id: <1280961833-25002-1-git-send-email-b-cousson@ti.com> X-Mailer: git-send-email 1.6.1.3 Sender: linux-omap-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-omap@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.3 (demeter.kernel.org [140.211.167.41]); Wed, 04 Aug 2010 22:44:22 +0000 (UTC) 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +/* + * 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