summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra/tegra3_actmon.c
diff options
context:
space:
mode:
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);