summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra/tegra3_actmon.c
diff options
context:
space:
mode:
authorAlex Frid <afrid@nvidia.com>2011-06-11 23:29:55 -0700
committerNiket Sirsi <nsirsi@nvidia.com>2011-06-24 21:35:31 -0700
commit7266c3e8aa29f324fb3d25def97cb74955e6166a (patch)
tree3851c3330a1a6e0e06be94546d8a7c813ee5ba61 /arch/arm/mach-tegra/tegra3_actmon.c
parente92c859d8346a6b9d651160774d83753476932bd (diff)
ARM: tegra: clock: Add Tegra3 EMC activity monitor support
Added EMC clock control using Tegra3 activity monitoring device. The target EMC frequency floor is set based on average activity and short term boost. Average EMC activity is obtained directly from monitoring h/w featuring 1st order IIR activity filter. The boost frequency is calculated by s/w - exponentially increasing/ decreasing when sampled EMC activity has crossed upper/lower boost watermarks. The implementation is interrupt driven - periodic sampling is hidden by h/w. The tune-able debugfs parameters are: /sys/kernel/debug/tegra_actmon/emc/boost_step - boost rate increase step (% of max EMC frequency) /sys/kernel/debug/tegra_actmon/emc/boost_rate_inc - boost rate increase factor (%) /sys/kernel/debug/tegra_actmon/emc/boost_rate_dec - boost rate decrease factor (%) /sys/kernel/debug/tegra_actmon/emc/boost_threshold_up - upper activity watermark for boost increase (% of current EMC frequency) /sys/kernel/debug/tegra_actmon/emc/boost_threshold_dn - lower activity watermark for boost decrease (% of current EMC frequency) Change-Id: I385c6e0a75da42dada792db6b4018b68fea8f23b Reviewed-on: http://git-master/r/36790 Reviewed-by: Niket Sirsi <nsirsi@nvidia.com> Tested-by: Niket Sirsi <nsirsi@nvidia.com>
Diffstat (limited to 'arch/arm/mach-tegra/tegra3_actmon.c')
-rw-r--r--arch/arm/mach-tegra/tegra3_actmon.c782
1 files changed, 782 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/tegra3_actmon.c b/arch/arm/mach-tegra/tegra3_actmon.c
new file mode 100644
index 000000000000..7629a82e1e8e
--- /dev/null
+++ b/arch/arm/mach-tegra/tegra3_actmon.c
@@ -0,0 +1,782 @@
+/*
+ * Copyright (c) 2011, NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/suspend.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+
+#include <mach/iomap.h>
+#include <mach/irqs.h>
+#include <mach/clk.h>
+
+#include "clock.h"
+
+#define ACTMON_GLB_STATUS 0x00
+#define ACTMON_GLB_PERIOD_CTRL 0x04
+
+#define ACTMON_DEV_CTRL 0x00
+#define ACTMON_DEV_CTRL_ENB (0x1 << 31)
+#define ACTMON_DEV_CTRL_UP_WMARK_ENB (0x1 << 30)
+#define ACTMON_DEV_CTRL_DOWN_WMARK_ENB (0x1 << 29)
+#define ACTMON_DEV_CTRL_UP_WMARK_NUM_SHIFT 26
+#define ACTMON_DEV_CTRL_UP_WMARK_NUM_MASK (0x7 << 26)
+#define ACTMON_DEV_CTRL_DOWN_WMARK_NUM_SHIFT 23
+#define ACTMON_DEV_CTRL_DOWN_WMARK_NUM_MASK (0x7 << 23)
+#define ACTMON_DEV_CTRL_AVG_UP_WMARK_ENB (0x1 << 21)
+#define ACTMON_DEV_CTRL_AVG_DOWN_WMARK_ENB (0x1 << 20)
+#define ACTMON_DEV_CTRL_PERIODIC_ENB (0x1 << 18)
+#define ACTMON_DEV_CTRL_K_VAL_SHIFT 10
+#define ACTMON_DEV_CTRL_K_VAL_MASK (0x7 << 10)
+
+#define ACTMON_DEV_UP_WMARK 0x04
+#define ACTMON_DEV_DOWN_WMARK 0x08
+#define ACTMON_DEV_INIT_AVG 0x0c
+#define ACTMON_DEV_AVG_UP_WMARK 0x10
+#define ACTMON_DEV_AVG_DOWN_WMARK 0x14
+
+#define ACTMON_DEV_COUNT_WEGHT 0x18
+#define ACTMON_DEV_COUNT 0x1c
+#define ACTMON_DEV_AVG_COUNT 0x20
+
+#define ACTMON_DEV_INTR_STATUS 0x24
+#define ACTMON_DEV_INTR_UP_WMARK (0x1 << 31)
+#define ACTMON_DEV_INTR_DOWN_WMARK (0x1 << 30)
+#define ACTMON_DEV_INTR_AVG_DOWN_WMARK (0x1 << 25)
+#define ACTMON_DEV_INTR_AVG_UP_WMARK (0x1 << 24)
+
+#define ACTMON_DEFAULT_AVG_WINDOW_LOG2 6
+
+enum actmon_type {
+ ACTMON_LOAD_SAMPLER,
+ ACTMON_FREQ_SAMPLER,
+};
+
+enum actmon_state {
+ ACTMON_UNINITIALIZED = -1,
+ ACTMON_OFF = 0,
+ ACTMON_ON = 1,
+ ACTMON_SUSPENDED = 2,
+};
+
+#define ACTMON_DEFAULT_SAMPLING_PERIOD 10
+static u8 actmon_sampling_period;
+
+static unsigned long actmon_clk_freq;
+
+
+/* Units:
+ * - frequency in kHz
+ * - coefficients, and thresholds in %
+ * - sampling period in ms
+ * - window in sample periods (value = setting + 1)
+ */
+struct actmon_dev {
+ u32 reg;
+ u32 glb_status_irq_mask;
+ const char *dev_id;
+ const char *con_id;
+ struct clk *clk;
+
+ unsigned long max_freq;
+ unsigned long target_freq;
+ unsigned long cur_freq;
+
+ unsigned long avg_actv_freq;
+ unsigned long avg_band_freq;
+ unsigned int avg_sustain_coef;
+ u32 avg_count;
+
+ unsigned long boost_freq;
+ unsigned long boost_freq_step;
+ unsigned int boost_up_coef;
+ unsigned int boost_down_coef;
+ unsigned int boost_up_threshold;
+ unsigned int boost_down_threshold;
+
+ u8 up_wmark_window;
+ u8 down_wmark_window;
+ u8 avg_window_log2;
+ u32 count_weight;
+
+ enum actmon_type type;
+ enum actmon_state state;
+ enum actmon_state saved_state;
+
+ spinlock_t lock;
+
+ struct notifier_block rate_change_nb;
+};
+
+static void __iomem *actmon_base = IO_ADDRESS(TEGRA_ACTMON_BASE);
+
+static inline u32 actmon_readl(u32 offset)
+{
+ return __raw_readl((u32)actmon_base + offset);
+}
+static inline void actmon_writel(u32 val, u32 offset)
+{
+ __raw_writel(val, (u32)actmon_base + offset);
+}
+static inline void actmon_wmb(void)
+{
+ wmb();
+ actmon_readl(ACTMON_GLB_STATUS);
+}
+
+#define offs(x) (dev->reg + x)
+
+static inline unsigned long do_percent(unsigned long val, unsigned int pct)
+{
+ return val * pct / 100;
+}
+
+static inline void actmon_dev_up_wmark_set(struct actmon_dev *dev)
+{
+ u32 val;
+ unsigned long freq = (dev->type == ACTMON_FREQ_SAMPLER) ?
+ dev->cur_freq : actmon_clk_freq;
+
+ val = freq * actmon_sampling_period;
+ actmon_writel(do_percent(val, dev->boost_up_threshold),
+ offs(ACTMON_DEV_UP_WMARK));
+}
+
+static inline void actmon_dev_down_wmark_set(struct actmon_dev *dev)
+{
+ u32 val;
+ unsigned long freq = (dev->type == ACTMON_FREQ_SAMPLER) ?
+ dev->cur_freq : actmon_clk_freq;
+
+ val = freq * actmon_sampling_period;
+ actmon_writel(do_percent(val, dev->boost_down_threshold),
+ offs(ACTMON_DEV_DOWN_WMARK));
+}
+
+static inline void actmon_dev_wmark_set(struct actmon_dev *dev)
+{
+ u32 val;
+ unsigned long freq = (dev->type == ACTMON_FREQ_SAMPLER) ?
+ dev->cur_freq : actmon_clk_freq;
+
+ val = freq * actmon_sampling_period;
+ actmon_writel(do_percent(val, dev->boost_up_threshold),
+ offs(ACTMON_DEV_UP_WMARK));
+ actmon_writel(do_percent(val, dev->boost_down_threshold),
+ offs(ACTMON_DEV_DOWN_WMARK));
+}
+
+static inline void actmon_dev_avg_wmark_set(struct actmon_dev *dev)
+{
+ u32 avg = dev->avg_count;
+ u32 band = dev->avg_band_freq * actmon_sampling_period;
+
+ actmon_writel(avg + band, offs(ACTMON_DEV_AVG_UP_WMARK));
+ avg = max(avg, band);
+ actmon_writel(avg - band, offs(ACTMON_DEV_AVG_DOWN_WMARK));
+}
+
+/* Activity monitor sampling operations */
+irqreturn_t actmon_dev_isr(int irq, void *dev_id)
+{
+ u32 val;
+ unsigned long flags;
+ struct actmon_dev *dev = (struct actmon_dev *)dev_id;
+
+ val = actmon_readl(ACTMON_GLB_STATUS) & dev->glb_status_irq_mask;
+ if (!val)
+ return IRQ_NONE;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ dev->avg_count = actmon_readl(offs(ACTMON_DEV_AVG_COUNT));
+ actmon_dev_avg_wmark_set(dev);
+
+ val = actmon_readl(offs(ACTMON_DEV_INTR_STATUS));
+ if (val & ACTMON_DEV_INTR_UP_WMARK) {
+ val = actmon_readl(offs(ACTMON_DEV_CTRL)) |
+ ACTMON_DEV_CTRL_UP_WMARK_ENB |
+ ACTMON_DEV_CTRL_DOWN_WMARK_ENB;
+
+ dev->boost_freq = dev->boost_freq_step +
+ do_percent(dev->boost_freq, dev->boost_up_coef);
+ if (dev->boost_freq >= dev->max_freq) {
+ dev->boost_freq = dev->max_freq;
+ val &= ~ACTMON_DEV_CTRL_UP_WMARK_ENB;
+ }
+ actmon_writel(val, offs(ACTMON_DEV_CTRL));
+ } else if (val & ACTMON_DEV_INTR_DOWN_WMARK) {
+ val = actmon_readl(offs(ACTMON_DEV_CTRL)) |
+ ACTMON_DEV_CTRL_UP_WMARK_ENB |
+ ACTMON_DEV_CTRL_DOWN_WMARK_ENB;
+
+ dev->boost_freq =
+ do_percent(dev->boost_freq, dev->boost_down_coef);
+ if (dev->boost_freq < (dev->boost_freq_step >> 1)) {
+ dev->boost_freq = 0;
+ val &= ~ACTMON_DEV_CTRL_DOWN_WMARK_ENB;
+ }
+ actmon_writel(val, offs(ACTMON_DEV_CTRL));
+ }
+
+ actmon_writel(0xffffffff, offs(ACTMON_DEV_INTR_STATUS)); /* clr all */
+ actmon_wmb();
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return IRQ_WAKE_THREAD;
+}
+
+irqreturn_t actmon_dev_fn(int irq, void *dev_id)
+{
+ unsigned long flags;
+ unsigned long freq = 0;
+ struct actmon_dev *dev = (struct actmon_dev *)dev_id;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ if (dev->state == ACTMON_ON) {
+ if (dev->type == ACTMON_FREQ_SAMPLER) {
+ freq = dev->avg_count / actmon_sampling_period;
+ }
+ else {
+ u64 tmp = (u64)dev->avg_count * dev->cur_freq;
+ freq = actmon_clk_freq * actmon_sampling_period;
+ do_div(tmp, freq);
+ freq = (u32)tmp;
+ }
+
+ dev->avg_actv_freq = freq;
+ freq = do_percent(freq, dev->avg_sustain_coef);
+ freq += dev->boost_freq;
+ dev->target_freq = freq;
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ if (freq) {
+ pr_debug("%s.%s(kHz): avg: %lu, target: %lu current: %lu\n",
+ dev->dev_id, dev->con_id, dev->avg_actv_freq,
+ dev->target_freq, dev->cur_freq);
+ clk_set_rate(dev->clk, freq * 1000);
+ }
+ return IRQ_HANDLED;
+}
+
+static int actmon_rate_notify_cb(
+ struct notifier_block *nb, unsigned long rate, void *v)
+{
+ unsigned long flags;
+ struct actmon_dev *dev = container_of(
+ nb, struct actmon_dev, rate_change_nb);
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ dev->cur_freq = rate / 1000;
+ if (dev->type == ACTMON_FREQ_SAMPLER) {
+ actmon_dev_wmark_set(dev);
+ actmon_wmb();
+ }
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return NOTIFY_OK;
+};
+
+/* Activity monitor configuration and control */
+static void actmon_dev_configure(struct actmon_dev *dev, unsigned long freq)
+{
+ u32 val;
+
+ dev->cur_freq = freq;
+ dev->target_freq = freq;
+ dev->avg_actv_freq = freq;
+
+ dev->avg_count = (dev->type == ACTMON_FREQ_SAMPLER) ?
+ dev->cur_freq : actmon_clk_freq;
+ dev->avg_count *= actmon_sampling_period;
+ actmon_writel(dev->avg_count, offs(ACTMON_DEV_INIT_AVG));
+
+ BUG_ON(!dev->boost_up_threshold);
+ dev->avg_sustain_coef = 100 * 100 / dev->boost_up_threshold;
+ actmon_dev_avg_wmark_set(dev);
+ actmon_dev_wmark_set(dev);
+
+ actmon_writel(dev->count_weight, offs(ACTMON_DEV_COUNT_WEGHT));
+ actmon_writel(0xffffffff, offs(ACTMON_DEV_INTR_STATUS)); /* clr all */
+
+ val = ACTMON_DEV_CTRL_PERIODIC_ENB | ACTMON_DEV_CTRL_AVG_UP_WMARK_ENB |
+ ACTMON_DEV_CTRL_AVG_DOWN_WMARK_ENB;
+ val |= ((dev->avg_window_log2 - 1) << ACTMON_DEV_CTRL_K_VAL_SHIFT) &
+ ACTMON_DEV_CTRL_K_VAL_MASK;
+ val |= ((dev->down_wmark_window - 1) <<
+ ACTMON_DEV_CTRL_DOWN_WMARK_NUM_SHIFT) &
+ ACTMON_DEV_CTRL_DOWN_WMARK_NUM_MASK;
+ val |= ((dev->up_wmark_window - 1) <<
+ ACTMON_DEV_CTRL_UP_WMARK_NUM_SHIFT) &
+ ACTMON_DEV_CTRL_UP_WMARK_NUM_MASK;
+ val |= ACTMON_DEV_CTRL_DOWN_WMARK_ENB | ACTMON_DEV_CTRL_UP_WMARK_ENB;
+ actmon_writel(val, offs(ACTMON_DEV_CTRL));
+ actmon_wmb();
+}
+
+static void actmon_dev_enable(struct actmon_dev *dev)
+{
+ u32 val;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ if (dev->state == ACTMON_OFF) {
+ dev->state = ACTMON_ON;
+
+ val = actmon_readl(offs(ACTMON_DEV_CTRL));
+ val |= ACTMON_DEV_CTRL_ENB;
+ actmon_writel(val, offs(ACTMON_DEV_CTRL));
+ actmon_wmb();
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+static void actmon_dev_disable(struct actmon_dev *dev)
+{
+ u32 val;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ if (dev->state == ACTMON_ON) {
+ dev->state = ACTMON_OFF;
+
+ val = actmon_readl(offs(ACTMON_DEV_CTRL));
+ val &= ~ACTMON_DEV_CTRL_ENB;
+ actmon_writel(val, offs(ACTMON_DEV_CTRL));
+ actmon_writel(0xffffffff, offs(ACTMON_DEV_INTR_STATUS));
+ actmon_wmb();
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+static void actmon_dev_suspend(struct actmon_dev *dev)
+{
+ u32 val;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ if ((dev->state == ACTMON_ON) || (dev->state == ACTMON_OFF)){
+ dev->saved_state = dev->state;
+ dev->state = ACTMON_SUSPENDED;
+
+ val = actmon_readl(offs(ACTMON_DEV_CTRL));
+ val &= ~ACTMON_DEV_CTRL_ENB;
+ actmon_writel(val, offs(ACTMON_DEV_CTRL));
+ actmon_writel(0xffffffff, offs(ACTMON_DEV_INTR_STATUS));
+ actmon_wmb();
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+static void actmon_dev_resume(struct actmon_dev *dev)
+{
+ u32 val;
+ unsigned long flags, freq;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ if (dev->state == ACTMON_SUSPENDED) {
+ freq = clk_get_rate(dev->clk) / 1000;
+ actmon_dev_configure(dev, freq);
+ dev->state = dev->saved_state;
+ if (dev->state == ACTMON_ON) {
+ val = actmon_readl(offs(ACTMON_DEV_CTRL));
+ val |= ACTMON_DEV_CTRL_ENB;
+ actmon_writel(val, offs(ACTMON_DEV_CTRL));
+ actmon_wmb();
+ }
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+static int __init actmon_dev_init(struct actmon_dev *dev)
+{
+ int ret;
+ struct clk *p;
+ unsigned long freq;
+
+ spin_lock_init(&dev->lock);
+
+ dev->clk = clk_get_sys(dev->dev_id, dev->con_id);
+ if (IS_ERR(dev->clk)) {
+ pr_err("Failed to find %s.%s clock\n",
+ dev->dev_id, dev->con_id);
+ return -ENODEV;
+ }
+ dev->max_freq = clk_get_max_rate(dev->clk) / 1000;
+ freq = clk_get_rate(dev->clk) / 1000;
+ actmon_dev_configure(dev, freq);
+
+ /* actmon device controls shared bus user clock, but rate
+ change notification should come from bus clock itself */
+ p = clk_get_parent(dev->clk);
+ BUG_ON(!p);
+
+ if (dev->rate_change_nb.notifier_call) {
+ ret = tegra_register_clk_rate_notifier(p, &dev->rate_change_nb);
+ if (ret) {
+ pr_err("Failed to register %s rate change notifier"
+ " for %s\n", p->name, dev->dev_id);
+ return ret;
+ }
+ }
+
+ ret = request_threaded_irq(INT_ACTMON, actmon_dev_isr, actmon_dev_fn,
+ IRQF_SHARED, dev->dev_id, dev);
+ if (ret) {
+ pr_err("Failed irq %d request for %s.%s\n",
+ INT_ACTMON, dev->dev_id, dev->con_id);
+ tegra_unregister_clk_rate_notifier(p, &dev->rate_change_nb);
+ return ret;
+ }
+
+ dev->state = ACTMON_OFF;
+ actmon_dev_enable(dev);
+ clk_enable(dev->clk);
+ return 0;
+}
+
+/* EMC activity monitor: frequency sampling device */
+static struct actmon_dev actmon_dev_emc = {
+ .reg = 0x1c0,
+ .glb_status_irq_mask = (0x1 << 26),
+ .dev_id = "tegra_actmon",
+ .con_id = "emc",
+
+ .avg_band_freq = 3000,
+
+ .boost_freq_step = 16000,
+ .boost_up_coef = 200,
+ .boost_down_coef = 50,
+ .boost_up_threshold = 60,
+ .boost_down_threshold = 40,
+
+ .up_wmark_window = 1,
+ .down_wmark_window = 3,
+ .avg_window_log2 = ACTMON_DEFAULT_AVG_WINDOW_LOG2,
+ .count_weight = 0x200,
+
+ .type = ACTMON_FREQ_SAMPLER,
+ .state = ACTMON_UNINITIALIZED,
+
+ .rate_change_nb = {
+ .notifier_call = actmon_rate_notify_cb,
+ },
+};
+
+static struct actmon_dev *actmon_devices[] = {
+ &actmon_dev_emc,
+};
+
+/* Activity monitor suspend/resume */
+static int actmon_pm_notify(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ int i;
+
+ switch (event) {
+ case PM_SUSPEND_PREPARE:
+ for (i = 0; i < ARRAY_SIZE(actmon_devices); i++)
+ actmon_dev_suspend(actmon_devices[i]);
+ break;
+ case PM_POST_SUSPEND:
+ for (i = 0; i < ARRAY_SIZE(actmon_devices); i++)
+ actmon_dev_resume(actmon_devices[i]);
+ break;
+ }
+
+ return NOTIFY_OK;
+};
+
+static struct notifier_block actmon_pm_nb = {
+ .notifier_call = actmon_pm_notify,
+};
+
+#ifdef CONFIG_DEBUG_FS
+
+#define RW_MODE (S_IWUSR | S_IRUGO)
+#define RO_MODE S_IRUGO
+
+static struct dentry *clk_debugfs_root;
+
+static int type_show(struct seq_file *s, void *data)
+{
+ struct actmon_dev *dev = s->private;
+
+ seq_printf(s, "%s\n", (dev->type == ACTMON_LOAD_SAMPLER) ?
+ "Load Activity Monitor" : "Frequency Activity Monitor");
+ return 0;
+}
+static int type_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, type_show, inode->i_private);
+}
+static const struct file_operations type_fops = {
+ .open = type_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int step_get(void *data, u64 *val)
+{
+ struct actmon_dev *dev = data;
+ *val = dev->boost_freq_step * 100 / dev->max_freq;
+ return 0;
+}
+static int step_set(void *data, u64 val)
+{
+ unsigned long flags;
+ struct actmon_dev *dev = data;
+
+ if (val > 100)
+ val = 100;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ dev->boost_freq_step = do_percent(dev->max_freq, (unsigned int)val);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(step_fops, step_get, step_set, "%llu\n");
+
+static int up_threshold_get(void *data, u64 *val)
+{
+ struct actmon_dev *dev = data;
+ *val = dev->boost_up_threshold;
+ return 0;
+}
+static int up_threshold_set(void *data, u64 val)
+{
+ unsigned long flags;
+ struct actmon_dev *dev = data;
+ unsigned int up_threshold = (unsigned int)val;
+
+ if (up_threshold > 100)
+ up_threshold = 100;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ if (up_threshold <= dev->boost_down_threshold)
+ up_threshold = dev->boost_down_threshold;
+ if (up_threshold)
+ dev->avg_sustain_coef = 100 * 100 / up_threshold;
+ dev->boost_up_threshold = up_threshold;
+
+ actmon_dev_up_wmark_set(dev);
+ actmon_wmb();
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(up_threshold_fops, up_threshold_get,
+ up_threshold_set, "%llu\n");
+
+static int down_threshold_get(void *data, u64 *val)
+{
+ struct actmon_dev *dev = data;
+ *val = dev->boost_down_threshold;
+ return 0;
+}
+static int down_threshold_set(void *data, u64 val)
+{
+ unsigned long flags;
+ struct actmon_dev *dev = data;
+ unsigned int down_threshold = (unsigned int)val;
+
+ if (down_threshold < 0)
+ down_threshold = 0;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ if (down_threshold >= dev->boost_up_threshold)
+ down_threshold = dev->boost_up_threshold;
+ dev->boost_down_threshold = down_threshold;
+
+ actmon_dev_down_wmark_set(dev);
+ actmon_wmb();
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(down_threshold_fops, down_threshold_get,
+ down_threshold_set, "%llu\n");
+
+static int state_get(void *data, u64 *val)
+{
+ struct actmon_dev *dev = data;
+ *val = dev->state;
+ return 0;
+}
+static int state_set(void *data, u64 val)
+{
+ struct actmon_dev *dev = data;
+
+ if (val)
+ actmon_dev_enable(dev);
+ else
+ actmon_dev_disable(dev);
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(state_fops, state_get, state_set, "%llu\n");
+
+static int period_get(void *data, u64 *val)
+{
+ *val = actmon_sampling_period;
+ return 0;
+}
+static int period_set(void *data, u64 val)
+{
+ u8 period = (u8)val;
+
+ if (period) {
+ actmon_sampling_period = period;
+ actmon_writel(period - 1, ACTMON_GLB_PERIOD_CTRL);
+ /* FIXME: update up/down wm for load sampler */
+ return 0;
+ }
+ return -EINVAL;
+}
+DEFINE_SIMPLE_ATTRIBUTE(period_fops, period_get, period_set, "%llu\n");
+
+
+static int actmon_debugfs_create_dev(struct actmon_dev *dev)
+{
+ struct dentry *dir, *d;
+
+ if (dev->state == ACTMON_UNINITIALIZED)
+ return 0;
+
+ dir = debugfs_create_dir(dev->con_id, clk_debugfs_root);
+ if (!dir)
+ return -ENOMEM;
+
+ d = debugfs_create_file(
+ "actv_type", RO_MODE, dir, dev, &type_fops);
+ if (!d)
+ return -ENOMEM;
+
+ d = debugfs_create_u32(
+ "avg_activity", RO_MODE, dir, (u32 *)&dev->avg_actv_freq);
+ if (!d)
+ return -ENOMEM;
+
+ d = debugfs_create_file(
+ "boost_step", RW_MODE, dir, dev, &step_fops);
+ if (!d)
+ return -ENOMEM;
+
+ d = debugfs_create_u32(
+ "boost_rate_dec", RW_MODE, dir, (u32 *)&dev->boost_down_coef);
+ if (!d)
+ return -ENOMEM;
+
+ d = debugfs_create_u32(
+ "boost_rate_inc", RW_MODE, dir, (u32 *)&dev->boost_up_coef);
+ if (!d)
+ return -ENOMEM;
+
+ d = debugfs_create_file(
+ "boost_threshold_dn", RW_MODE, dir, dev, &down_threshold_fops);
+ if (!d)
+ return -ENOMEM;
+
+ d = debugfs_create_file(
+ "boost_threshold_up", RW_MODE, dir, dev, &up_threshold_fops);
+ if (!d)
+ return -ENOMEM;
+
+ d = debugfs_create_file(
+ "state", RW_MODE, dir, dev, &state_fops);
+ if (!d)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int __init actmon_debugfs_init(void)
+{
+ int i;
+ int ret = -ENOMEM;
+ struct dentry *d;
+
+ d = debugfs_create_dir("tegra_actmon", NULL);
+ if (!d)
+ return ret;
+ clk_debugfs_root = d;
+
+ d = debugfs_create_file("period", RW_MODE, d, NULL, &period_fops);
+ if (!d)
+ goto err_out;
+
+ for (i = 0; i < ARRAY_SIZE(actmon_devices); i++) {
+ ret = actmon_debugfs_create_dev(actmon_devices[i]);
+ if (ret)
+ goto err_out;
+ }
+ return 0;
+
+err_out:
+ debugfs_remove_recursive(clk_debugfs_root);
+ return ret;
+}
+
+#endif
+
+static int __init tegra_actmon_init(void)
+{
+ int i, ret;
+ struct clk *c = tegra_get_clock_by_name("actmon");
+
+ if (!c) {
+ pr_err("%s: Failed to find actmon clock\n", __func__);
+ return 0;
+ }
+ actmon_clk_freq = clk_get_rate(c) / 1000;
+ ret = clk_enable(c);
+ if (ret) {
+ pr_err("%s: Failed to enable actmon clock\n", __func__);
+ return 0;
+ }
+ actmon_sampling_period = ACTMON_DEFAULT_SAMPLING_PERIOD;
+ actmon_writel(actmon_sampling_period - 1, ACTMON_GLB_PERIOD_CTRL);
+
+ for (i = 0; i < ARRAY_SIZE(actmon_devices); i++) {
+ ret = actmon_dev_init(actmon_devices[i]);
+ pr_info("%s.%s: %s initialization (%d)\n",
+ actmon_devices[i]->dev_id, actmon_devices[i]->con_id,
+ ret ? "Failed" : "Completed", ret);
+ }
+ register_pm_notifier(&actmon_pm_nb);
+
+#ifdef CONFIG_DEBUG_FS
+ actmon_debugfs_init();
+#endif
+ return 0;
+}
+late_initcall(tegra_actmon_init);