summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra/sysfs-cluster.c
diff options
context:
space:
mode:
authorScott Williams <scwilliams@nvidia.com>2010-12-07 11:19:20 -0800
committerDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:42:02 -0800
commit816ab05de4f2171e20171db01fa916634ed7bee6 (patch)
tree963ce2c1724c63598deb279d9f6ed54cfc93d1e3 /arch/arm/mach-tegra/sysfs-cluster.c
parent7d8324d8f3dfc27407d7514fcbd605649c9bd210 (diff)
[ARM/tegra] Add Tegra3 support
Bug 764354 Original-Change-Id: I8a390eb4dae87dceacb97461f23d13554868b046 Reviewed-on: http://git-master/r/12228 Reviewed-by: Scott Williams <scwilliams@nvidia.com> Tested-by: Scott Williams <scwilliams@nvidia.com> Original-Change-Id: I8e6b8303898796419fb5a759cd16edff9aeac081 Rebase-Id: R2866240384c6c24f46bd7ef54bc3dc9140d9e96b
Diffstat (limited to 'arch/arm/mach-tegra/sysfs-cluster.c')
-rw-r--r--arch/arm/mach-tegra/sysfs-cluster.c408
1 files changed, 408 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/sysfs-cluster.c b/arch/arm/mach-tegra/sysfs-cluster.c
new file mode 100644
index 000000000000..50f9535cb4d9
--- /dev/null
+++ b/arch/arm/mach-tegra/sysfs-cluster.c
@@ -0,0 +1,408 @@
+/*
+ * Copyright (c) 2010 NVIDIA Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NVIDIA Corporation nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/*
+ * This driver creates the /sys/kernel/cluster node and attributes for CPU
+ * switch testing. Node attributes:
+ *
+ * active: currently active CPU (G or LP)
+ * write: 'g' = switch to G CPU
+ * 'lp' = switch to LP CPU
+ * 'toggle' = switch to the other CPU
+ * read: returns the currently active CPU (g or lp)
+ *
+ * force: force switch even if already on target CPU
+ * write: '0' = do not perform switch if
+ * active CPU == target CPU (default)
+ * '1' = force switch regardless of
+ * currently active CPU
+ * read: returns the current status of the force flag
+ *
+ * immediate: request immediate wake-up from switch request
+ * write: '0' = non-immediate wake-up on next interrupt (default)
+ * '1' = immediate wake-up
+ * read: returns the current status of the immediate flag
+ *
+ * power_mode: power mode to use for switch (LP1 or LP2)
+ * write: '1' = use LP1 power mode
+ * '2' = use LP2 power mode (default)
+ * read: returns the current status of the immediate flag
+ *
+ * wake_ms: wake time (in milliseconds) -- ignored if immediate==1
+ * write: '0' = wake up at the next non-timer interrupt
+ * 'n' = (n > 0) wake-up after 'n' milliseconds or the
+ * next non-timer interrupt (whichever comes first)
+ * read: returns the current wake_ms value
+ *
+ * Writing the force, immediate and wake_ms attributes simply updates the
+ * state of internal variables that will be used for the next switch request.
+ * Writing to the active attribute initates a switch request using the
+ * current values of the force, immediate, and wake_ms attributes.
+ *
+ * The OS tick timer is not a valid interrupt source for waking up following
+ * a switch request. This is because the kernel uses local timers that are
+ * part of the CPU complex. These get shut down when the CPU complex is
+ * placed into reset by the switch request. If you want a timed wake up
+ * from a switch, you must specify a positive wake_ms value. This will
+ * ensure that a non-local timer is programmed to fire an interrupt
+ * after the desired interval.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sysfs.h>
+#include <linux/kobject.h>
+#include <linux/smp.h>
+#include <linux/io.h>
+
+#include <mach/iomap.h>
+#include "power.h"
+
+#define SYSFS_CLUSTER_PRINTS 1 /* Nonzero: enable status prints */
+#define SYSFS_CLUSTER_DEBUG_PRINTS 0 /* Nonzero: enable debug prints */
+#define SYSFS_CLUSTER_POWER_MODE 1 /* Nonzero: use power modes other than LP2*/
+
+#if SYSFS_CLUSTER_DEBUG_PRINTS
+#define DEBUG_CLUSTER(x) printk x
+#else
+#define DEBUG_CLUSTER(x)
+#endif
+
+#if SYSFS_CLUSTER_PRINTS
+#define PRINT_CLUSTER(x) printk x
+#else
+#define PRINT_CLUSTER(x)
+#endif
+
+#define FLOW_CTRL_CLUSTER_CONTROL \
+ (IO_ADDRESS(TEGRA_FLOW_CTRL_BASE) + 0x2c)
+#define FLOW_CTRL_CPUx_CSR(cpu) \
+ (IO_ADDRESS(TEGRA_FLOW_CTRL_BASE + ((cpu)?(((cpu)-1)*8 + 0x18) : 0x8)))
+
+static struct kobject *cluster_kobj;
+static spinlock_t cluster_lock;
+static unsigned int flags = 0;
+static unsigned int power_mode = 2;
+static unsigned int wake_ms = 0;
+
+static ssize_t sysfscluster_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf);
+
+static ssize_t sysfscluster_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count);
+
+/* Active CPU: "G", "LP", "toggle" */
+static struct kobj_attribute cluster_active_attr =
+ __ATTR(active, 0640, sysfscluster_show, sysfscluster_store);
+
+/* Immediate wake-up when performing switch: 0, 1 */
+static struct kobj_attribute cluster_immediate_attr =
+ __ATTR(immediate, 0640, sysfscluster_show, sysfscluster_store);
+
+/* Force power transition even if already on the desired CPU: 0, 1 */
+static struct kobj_attribute cluster_force_attr =
+ __ATTR(force, 0640, sysfscluster_show, sysfscluster_store);
+
+/* Wake time (in milliseconds) */
+static struct kobj_attribute cluster_wake_ms_attr =
+ __ATTR(wake_ms, 0640, sysfscluster_show, sysfscluster_store);
+
+#if SYSFS_CLUSTER_POWER_MODE
+/* LPx power mode to use when switching CPUs: 1, 2 */
+static struct kobj_attribute cluster_powermode_attr =
+ __ATTR(power_mode, 0640, sysfscluster_show, sysfscluster_store);
+#endif
+
+typedef enum
+{
+ ClusterAttr_Invalid = 0,
+ ClusterAttr_Active,
+ ClusterAttr_Immediate,
+ ClusterAttr_Force,
+ ClusterAttr_WakeMs,
+#if SYSFS_CLUSTER_POWER_MODE
+ ClusterAttr_PowerMode
+#endif
+} ClusterAttr;
+
+static ClusterAttr GetClusterAttr(const char *name)
+{
+ if (!strcmp(name, "active"))
+ return ClusterAttr_Active;
+ if (!strcmp(name, "immediate"))
+ return ClusterAttr_Immediate;
+ if (!strcmp(name, "force"))
+ return ClusterAttr_Force;
+ if (!strcmp(name, "wake_ms"))
+ return ClusterAttr_WakeMs;
+#if SYSFS_CLUSTER_POWER_MODE
+ if (!strcmp(name, "power_mode"))
+ return ClusterAttr_PowerMode;
+#endif
+ DEBUG_CLUSTER(("GetClusterAttr(%s): invalid\n", name));
+ return ClusterAttr_Invalid;
+}
+
+static ssize_t sysfscluster_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ ClusterAttr type;
+ ssize_t len;
+
+ DEBUG_CLUSTER(("+sysfscluster_show\n"));
+
+ type = GetClusterAttr(attr->attr.name);
+ switch (type) {
+ case ClusterAttr_Active:
+ len = sprintf(buf, "%s\n", is_lp_cluster() ? "LP" : "G");
+ break;
+
+ case ClusterAttr_Immediate:
+ len = sprintf(buf, "%d\n",
+ ((flags & TEGRA_POWER_CLUSTER_IMMEDIATE) != 0));
+ break;
+
+ case ClusterAttr_Force:
+ len = sprintf(buf, "%d\n",
+ ((flags & TEGRA_POWER_CLUSTER_FORCE) != 0));
+ break;
+
+ case ClusterAttr_WakeMs:
+ len = sprintf(buf, "%d\n", wake_ms);
+ break;
+
+#if SYSFS_CLUSTER_POWER_MODE
+ case ClusterAttr_PowerMode:
+ len = sprintf(buf, "%d\n", power_mode);
+ break;
+#endif
+
+ default:
+ len = sprintf(buf, "invalid\n");
+ break;
+ }
+
+ DEBUG_CLUSTER(("-sysfscluster_show\n"));
+ return len;
+}
+
+static ssize_t sysfscluster_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ ClusterAttr type;
+ ssize_t ret = count--;
+ unsigned request;
+ int e;
+ int tmp;
+ int cnt;
+
+ DEBUG_CLUSTER(("+sysfscluster_store: %p, %d\n", buf, count));
+
+ /* The count includes data bytes follow by a line feed character. */
+ if (!buf || (count < 1)) {
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ type = GetClusterAttr(attr->attr.name);
+
+ spin_lock(&cluster_lock);
+
+ switch (type) {
+ case ClusterAttr_Active:
+ if (!strncasecmp(buf, "g", count)) {
+ flags &= ~TEGRA_POWER_CLUSTER_MASK;
+ flags |= TEGRA_POWER_CLUSTER_G;
+ } else if (!strncasecmp(buf, "lp", count)) {
+ flags &= ~TEGRA_POWER_CLUSTER_MASK;
+ flags |= TEGRA_POWER_CLUSTER_LP;
+ } else if (!strncasecmp(buf, "toggle", count)) {
+ flags &= ~TEGRA_POWER_CLUSTER_MASK;
+ if (is_lp_cluster())
+ flags |= TEGRA_POWER_CLUSTER_G;
+ else
+ flags |= TEGRA_POWER_CLUSTER_LP;
+ } else {
+ PRINT_CLUSTER(("cluster/active: '%*.*s' invalid, "
+ " must be g, lp, or toggle\n",
+ count, count, buf));
+ ret = -EINVAL;
+ break;
+ }
+ PRINT_CLUSTER(("cluster/active -> %s\n",
+ (flags & TEGRA_POWER_CLUSTER_G) ? "G" : "LP"));
+
+ request = flags;
+#if SYSFS_CLUSTER_POWER_MODE
+ if (power_mode == 1) {
+ request |= TEGRA_POWER_SDRAM_SELFREFRESH;
+ }
+#endif
+ e = tegra_cluster_control(wake_ms * 1000, request);
+ if (e) {
+ PRINT_CLUSTER(("cluster/active: request failed (%d)\n",
+ e));
+ ret = e;
+ }
+ break;
+
+ case ClusterAttr_Immediate:
+ if ((count == 1) && (*buf == '0'))
+ flags &= ~TEGRA_POWER_CLUSTER_IMMEDIATE;
+ else if ((count == 1) && *buf == '1')
+ flags |= TEGRA_POWER_CLUSTER_IMMEDIATE;
+ else {
+ PRINT_CLUSTER(("cluster/immediate: '%*.*s' invalid, "
+ "must be 0 or 1\n", count, count, buf));
+ ret = -EINVAL;
+ break;
+ }
+ PRINT_CLUSTER(("cluster/immediate -> %c\n",
+ (flags & TEGRA_POWER_CLUSTER_IMMEDIATE) ? '1' : '0'));
+ break;
+
+ case ClusterAttr_Force:
+ if ((count == 1) && (*buf == '0'))
+ flags &= ~TEGRA_POWER_CLUSTER_FORCE;
+ else if ((count == 1) && (*buf == '1'))
+ flags |= TEGRA_POWER_CLUSTER_FORCE;
+ else {
+ PRINT_CLUSTER(("cluster/force: '%*.*s' invalid, "
+ "must be 0 or 1\n", count, count, buf));
+ ret = -EINVAL;
+ break;
+ }
+ PRINT_CLUSTER(("cluster/force -> %c\n",
+ (flags & TEGRA_POWER_CLUSTER_FORCE) ? '1' : '0'));
+ break;
+
+ case ClusterAttr_WakeMs:
+ tmp = 0;
+ cnt = sscanf(buf, "%d\n", &tmp);
+ if ((cnt != 1) || (tmp < 0)) {
+ PRINT_CLUSTER(("cluster/wake_ms: '%*.*s' is invalid\n",
+ count, count, buf));
+ ret = -EINVAL;
+ break;
+ }
+ wake_ms = tmp;
+ PRINT_CLUSTER(("cluster/wake_ms -> %d\n", wake_ms));
+ break;
+
+#if SYSFS_CLUSTER_POWER_MODE
+ case ClusterAttr_PowerMode:
+ if ((count == 1) && (*buf == '2'))
+ power_mode = 2;
+ else if ((count == 1) && *buf == '1')
+ power_mode = 1;
+ else {
+ PRINT_CLUSTER(("cluster/power_mode: '%*.*s' invalid, "
+ "must be 2 or 1\n", count, count, buf));
+ ret = -EINVAL;
+ break;
+ }
+ PRINT_CLUSTER(("cluster/power_mode -> %d\n", power_mode));
+ break;
+#endif
+
+ default:
+ ret = -ENOENT;
+ break;
+ }
+
+ spin_unlock(&cluster_lock);
+
+fail:
+ DEBUG_CLUSTER(("-sysfscluster_store: %d\n", count));
+ return ret;
+}
+
+#define CREATE_FILE(x) \
+ do { \
+ e = sysfs_create_file(cluster_kobj, &cluster_##x##_attr.attr); \
+ if (e) { \
+ DEBUG_CLUSTER(("cluster/" __stringify(x) \
+ ": sysfs_create_file failed!\n")); \
+ goto fail; \
+ } \
+ } while (0)
+
+static int __init sysfscluster_init(void)
+{
+ int e;
+
+ DEBUG_CLUSTER(("+sysfscluster_init\n"));
+
+ spin_lock_init(&cluster_lock);
+ cluster_kobj = kobject_create_and_add("cluster", kernel_kobj);
+
+ CREATE_FILE(active);
+ CREATE_FILE(immediate);
+ CREATE_FILE(force);
+ CREATE_FILE(wake_ms);
+#if SYSFS_CLUSTER_POWER_MODE
+ CREATE_FILE(powermode);
+#endif
+
+ spin_lock(&cluster_lock);
+ if (is_lp_cluster())
+ flags |= TEGRA_POWER_CLUSTER_LP;
+ else
+ flags |= TEGRA_POWER_CLUSTER_G;
+ spin_unlock(&cluster_lock);
+
+fail:
+ DEBUG_CLUSTER(("-sysfscluster_init\n"));
+ return e;
+}
+
+#define REMOVE_FILE(x) \
+ sysfs_remove_file(cluster_kobj, &cluster_##x##_attr.attr)
+
+static void __exit sysfscluster_exit(void)
+{
+ DEBUG_CLUSTER(("+sysfscluster_exit\n"));
+#if SYSFS_CLUSTER_POWER_MODE
+ REMOVE_FILE(powermode);
+#endif
+ REMOVE_FILE(wake_ms);
+ REMOVE_FILE(force);
+ REMOVE_FILE(immediate);
+ REMOVE_FILE(active);
+ kobject_del(cluster_kobj);
+ DEBUG_CLUSTER(("-sysfscluster_exit\n"));
+}
+
+module_init(sysfscluster_init);
+module_exit(sysfscluster_exit);
+MODULE_LICENSE("GPL");