obj-y += io.o
obj-y += irq.o legacy_irq.o
obj-y += clock.o
+obj-y += dvfs.o
obj-y += timer.o
obj-y += gpio.o
obj-y += pinmux.o
obj-$(CONFIG_CPU_V7) += cortex-a9.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += pinmux-t2-tables.o
-ifeq ($(CONFIG_SMP),y)
-obj-y += platsmp.o localtimer.o
+obj-$(CONFIG_SMP) += localtimer.o
+obj-$(CONFIG_SMP) += platsmp.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += headsmp-t2.o
-endif
obj-$(CONFIG_TEGRA_SYSTEM_DMA) += dma.o
obj-$(CONFIG_CPU_FREQ) += cpu-tegra.o
obj-$(CONFIG_CPU_IDLE) += cpuidle.o
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/seq_file.h>
-#include <linux/regulator/consumer.h>
#include <asm/clkdev.h>
+#include <mach/clk.h>
-#include "clock.h"
#include "board.h"
-#include "fuse.h"
+#include "clock.h"
+#include "dvfs.h"
static LIST_HEAD(clocks);
+/*
+ * clock_lock must be held when:
+ * Accessing any clock register non-atomically
+ * or
+ * Relying on any state of a clk struct not to change, unless clk_is_dvfs
+ * returns true on that clk struct, and dvfs_lock is held instead.
+ *
+ * Any function that changes the state of a clk struct must hold
+ * the dvfs_lock if clk_is_auto_dvfs(clk) is true, and the clock_lock.
+ *
+ * When taking dvfs_lock and clock_lock, dvfs_lock must be taken first.
+ */
static DEFINE_SPINLOCK(clock_lock);
-static DEFINE_MUTEX(dvfs_lock);
-static int clk_is_dvfs(struct clk *c)
+static inline bool clk_is_auto_dvfs(struct clk *c)
{
- return (c->dvfs != NULL);
+ smp_rmb();
+ return c->auto_dvfs;
};
-static int dvfs_set_rate(struct dvfs *d, unsigned long rate)
-{
- struct dvfs_table *t;
-
- if (d->table == NULL)
- return -ENODEV;
-
- for (t = d->table; t->rate != 0; t++) {
- if (rate <= t->rate) {
- if (!d->reg)
- return 0;
-
- return regulator_set_voltage(d->reg,
- t->millivolts * 1000,
- d->max_millivolts * 1000);
- }
- }
-
- return -EINVAL;
-}
-
-static void dvfs_init(struct clk *c)
+static inline bool clk_is_dvfs(struct clk *c)
{
- int process_id;
- int i;
- struct dvfs_table *table;
-
- process_id = c->dvfs->cpu ? tegra_core_process_id() :
- tegra_cpu_process_id();
-
- for (i = 0; i < c->dvfs->process_id_table_length; i++)
- if (process_id == c->dvfs->process_id_table[i].process_id)
- c->dvfs->table = c->dvfs->process_id_table[i].table;
-
- if (c->dvfs->table == NULL) {
- pr_err("Failed to find dvfs table for clock %s process %d\n",
- c->name, process_id);
- return;
- }
-
- c->dvfs->max_millivolts = 0;
- for (table = c->dvfs->table; table->rate != 0; table++)
- if (c->dvfs->max_millivolts < table->millivolts)
- c->dvfs->max_millivolts = table->millivolts;
-
- c->dvfs->reg = regulator_get(NULL, c->dvfs->reg_id);
-
- if (IS_ERR(c->dvfs->reg)) {
- pr_err("Failed to get regulator %s for clock %s\n",
- c->dvfs->reg_id, c->name);
- c->dvfs->reg = NULL;
- return;
- }
-
- if (c->refcnt > 0)
- dvfs_set_rate(c->dvfs, c->rate);
-}
+ smp_rmb();
+ return c->is_dvfs;
+};
struct clk *tegra_get_clock_by_name(const char *name)
{
return ret;
}
-static void clk_recalculate_rate(struct clk *c)
+static unsigned long clk_predict_rate_from_parent(struct clk *c, struct clk *p)
{
u64 rate;
- if (!c->parent)
- return;
-
- rate = c->parent->rate;
+ rate = p->rate;
if (c->mul != 0 && c->div != 0) {
rate = rate * c->mul;
do_div(rate, c->div);
}
+ return rate;
+}
+
+static void clk_recalculate_rate(struct clk *c)
+{
+ unsigned long rate;
+
+ if (!c->parent)
+ return;
+
+ rate = clk_predict_rate_from_parent(c, c->parent);
+
if (rate > c->max_rate)
- pr_warn("clocks: Set clock %s to rate %llu, max is %lu\n",
+ pr_warn("clocks: Set clock %s to rate %lu, max is %lu\n",
c->name, rate, c->max_rate);
c->rate = rate;
int clk_reparent(struct clk *c, struct clk *parent)
{
- pr_debug("%s: %s\n", __func__, c->name);
c->parent = parent;
list_del(&c->sibling);
list_add_tail(&c->sibling, &parent->children);
static void propagate_rate(struct clk *c)
{
struct clk *clkp;
- pr_debug("%s: %s\n", __func__, c->name);
+
list_for_each_entry(clkp, &c->children, sibling) {
- pr_debug(" %s\n", clkp->name);
clk_recalculate_rate(clkp);
propagate_rate(clkp);
}
{
unsigned long flags;
- pr_debug("%s: %s\n", __func__, c->name);
-
spin_lock_irqsave(&clock_lock, flags);
INIT_LIST_HEAD(&c->children);
INIT_LIST_HEAD(&c->sibling);
+ INIT_LIST_HEAD(&c->dvfs);
if (c->ops && c->ops->init)
c->ops->init(c);
+ if (!c->ops || !c->ops->enable) {
+ c->refcnt++;
+ c->set = true;
+ if (c->parent)
+ c->state = c->parent->state;
+ else
+ c->state = ON;
+ }
+
clk_recalculate_rate(c);
list_add(&c->node, &clocks);
int clk_enable_locked(struct clk *c)
{
int ret;
- pr_debug("%s: %s\n", __func__, c->name);
+
if (c->refcnt == 0) {
if (c->parent) {
ret = clk_enable_locked(c->parent);
return ret;
}
c->state = ON;
-#ifdef CONFIG_DEBUG_FS
- c->set = 1;
-#endif
+ c->set = true;
}
}
c->refcnt++;
return 0;
}
-int clk_enable_cansleep(struct clk *c)
-{
- int ret;
- unsigned long flags;
-
- mutex_lock(&dvfs_lock);
-
- if (clk_is_dvfs(c) && c->refcnt > 0)
- dvfs_set_rate(c->dvfs, c->rate);
-
- spin_lock_irqsave(&clock_lock, flags);
- ret = clk_enable_locked(c);
- spin_unlock_irqrestore(&clock_lock, flags);
-
- mutex_unlock(&dvfs_lock);
-
- return ret;
-}
-EXPORT_SYMBOL(clk_enable_cansleep);
-
int clk_enable(struct clk *c)
{
int ret;
unsigned long flags;
- if (clk_is_dvfs(c))
- BUG();
+ if (clk_is_auto_dvfs(c)) {
+ lock_dvfs();
+ ret = tegra_dvfs_set_rate(c, c->rate);
+ if (ret)
+ goto out;
+ }
spin_lock_irqsave(&clock_lock, flags);
ret = clk_enable_locked(c);
spin_unlock_irqrestore(&clock_lock, flags);
+out:
+ if (clk_is_auto_dvfs(c))
+ unlock_dvfs();
+
return ret;
}
EXPORT_SYMBOL(clk_enable);
void clk_disable_locked(struct clk *c)
{
- pr_debug("%s: %s\n", __func__, c->name);
if (c->refcnt == 0) {
WARN(1, "Attempting to disable clock %s with refcnt 0", c->name);
return;
c->refcnt--;
}
-void clk_disable_cansleep(struct clk *c)
-{
- unsigned long flags;
-
- mutex_lock(&dvfs_lock);
-
- spin_lock_irqsave(&clock_lock, flags);
- clk_disable_locked(c);
- spin_unlock_irqrestore(&clock_lock, flags);
-
- if (clk_is_dvfs(c) && c->refcnt == 0)
- dvfs_set_rate(c->dvfs, c->rate);
-
- mutex_unlock(&dvfs_lock);
-}
-EXPORT_SYMBOL(clk_disable_cansleep);
-
void clk_disable(struct clk *c)
{
unsigned long flags;
- if (clk_is_dvfs(c))
- BUG();
+ if (clk_is_auto_dvfs(c))
+ lock_dvfs();
spin_lock_irqsave(&clock_lock, flags);
clk_disable_locked(c);
spin_unlock_irqrestore(&clock_lock, flags);
+
+ if (clk_is_auto_dvfs(c)) {
+ if (c->refcnt == 0)
+ tegra_dvfs_set_rate(c, 0);
+ unlock_dvfs();
+ }
}
EXPORT_SYMBOL(clk_disable);
{
int ret;
- pr_debug("%s: %s\n", __func__, c->name);
-
if (!c->ops || !c->ops->set_parent)
return -ENOSYS;
int clk_set_parent(struct clk *c, struct clk *parent)
{
- int ret;
+ int ret = 0;
unsigned long flags;
+ unsigned long new_rate = clk_predict_rate_from_parent(c, parent);
+
+
+ if (clk_is_auto_dvfs(c)) {
+ lock_dvfs();
+ if (c->refcnt > 0 && (!c->parent || new_rate > c->rate))
+ ret = tegra_dvfs_set_rate(c, new_rate);
+ if (!ret)
+ goto out;
+ }
+
spin_lock_irqsave(&clock_lock, flags);
ret = clk_set_parent_locked(c, parent);
spin_unlock_irqrestore(&clock_lock, flags);
+ if (!ret)
+ goto out;
+
+ if (clk_is_auto_dvfs(c) && c->refcnt > 0)
+ ret = tegra_dvfs_set_rate(c, new_rate);
+
+out:
+ if (clk_is_auto_dvfs(c))
+ unlock_dvfs();
+
return ret;
}
EXPORT_SYMBOL(clk_set_parent);
{
int ret;
+ if (rate == c->requested_rate)
+ return 0;
+
if (rate > c->max_rate)
rate = c->max_rate;
if (!c->ops || !c->ops->set_rate)
return -ENOSYS;
+ c->requested_rate = rate;
+
ret = c->ops->set_rate(c, rate);
if (ret)
return 0;
}
-int clk_set_rate_cansleep(struct clk *c, unsigned long rate)
+int clk_set_rate(struct clk *c, unsigned long rate)
{
int ret = 0;
unsigned long flags;
- pr_debug("%s: %s\n", __func__, c->name);
-
- mutex_lock(&dvfs_lock);
-
- if (rate > c->rate)
- ret = dvfs_set_rate(c->dvfs, rate);
- if (ret)
- goto out;
+ if (clk_is_auto_dvfs(c)) {
+ lock_dvfs();
+ if (rate > c->rate && c->refcnt > 0)
+ ret = tegra_dvfs_set_rate(c, rate);
+ if (ret)
+ goto out;
+ }
spin_lock_irqsave(&clock_lock, flags);
ret = clk_set_rate_locked(c, rate);
if (ret)
goto out;
- ret = dvfs_set_rate(c->dvfs, rate);
+ if (clk_is_auto_dvfs(c) && c->refcnt > 0)
+ ret = tegra_dvfs_set_rate(c, rate);
out:
- mutex_unlock(&dvfs_lock);
- return ret;
-}
-EXPORT_SYMBOL(clk_set_rate_cansleep);
-
-int clk_set_rate(struct clk *c, unsigned long rate)
-{
- int ret = 0;
- unsigned long flags;
-
- pr_debug("%s: %s\n", __func__, c->name);
-
- if (clk_is_dvfs(c))
- BUG();
-
- spin_lock_irqsave(&clock_lock, flags);
- ret = clk_set_rate_locked(c, rate);
- spin_unlock_irqrestore(&clock_lock, flags);
-
+ if (clk_is_auto_dvfs(c))
+ unlock_dvfs();
return ret;
}
EXPORT_SYMBOL(clk_set_rate);
spin_lock_irqsave(&clock_lock, flags);
- pr_debug("%s: %s\n", __func__, c->name);
-
ret = c->rate;
spin_unlock_irqrestore(&clock_lock, flags);
long clk_round_rate(struct clk *c, unsigned long rate)
{
- pr_debug("%s: %s\n", __func__, c->name);
-
if (!c->ops || !c->ops->round_rate)
return -ENOSYS;
tegra2_init_clocks();
}
-int __init tegra_init_dvfs(void)
+void __init tegra_clk_set_dvfs_rates(void)
{
- struct clk *c, *safe;
+ struct clk *c;
+ list_for_each_entry(c, &clocks, node) {
+ if (clk_is_auto_dvfs(c)) {
+ if (c->refcnt > 0)
+ tegra_dvfs_set_rate(c, c->rate);
+ else
+ tegra_dvfs_set_rate(c, 0);
+ } else if (clk_is_dvfs(c)) {
+ tegra_dvfs_set_rate(c, c->dvfs_rate);
+ }
+ }
+}
- mutex_lock(&dvfs_lock);
+int __init tegra_disable_boot_clocks(void)
+{
+ unsigned long flags;
+ struct clk *c;
- list_for_each_entry_safe(c, safe, &clocks, node)
- if (c->dvfs)
- dvfs_init(c);
+ lock_dvfs();
+ spin_lock_irqsave(&clock_lock, flags);
- mutex_unlock(&dvfs_lock);
+ list_for_each_entry(c, &clocks, node) {
+ if (c->refcnt == 0 && c->state == ON &&
+ c->ops && c->ops->disable) {
+ pr_warning("Disabling clock %s left on by bootloader\n",
+ c->name);
+ c->ops->disable(c);
+ c->state = OFF;
+ }
+ }
+ spin_unlock_irqrestore(&clock_lock, flags);
+ unlock_dvfs();
return 0;
}
-
-late_initcall(tegra_init_dvfs);
+late_initcall(tegra_disable_boot_clocks);
#ifdef CONFIG_DEBUG_FS
static struct dentry *clk_debugfs_root;
+static void dvfs_show_one(struct seq_file *s, struct dvfs *d, int level)
+{
+ seq_printf(s, "%*s %-*s%21s%d mV\n",
+ level * 3 + 1, "",
+ 30 - level * 3, d->reg_id,
+ "",
+ d->cur_millivolts);
+}
static void clock_tree_show_one(struct seq_file *s, struct clk *c, int level)
{
struct clk *child;
struct clk *safe;
+ struct dvfs *d;
const char *state = "uninit";
char div[8] = {0};
!c->set ? '*' : ' ',
30 - level * 3, c->name,
state, c->refcnt, div, c->rate);
+
+ list_for_each_entry(d, &c->dvfs, node)
+ dvfs_show_one(s, d, level + 1);
+
list_for_each_entry_safe(child, safe, &c->children, sibling) {
clock_tree_show_one(s, child, level + 1);
}
if (!d)
goto err_out;
+ if (dvfs_debugfs_init(clk_debugfs_root))
+ goto err_out;
+
list_for_each_entry(c, &clocks, node) {
err = clk_debugfs_register(c);
if (err)
#define ENABLE_ON_INIT (1 << 28)
struct clk;
-struct regulator;
-
-struct dvfs_table {
- unsigned long rate;
- int millivolts;
-};
-
-struct dvfs_process_id_table {
- int process_id;
- struct dvfs_table *table;
-};
-
-
-struct dvfs {
- struct regulator *reg;
- struct dvfs_table *table;
- int max_millivolts;
-
- int process_id_table_length;
- const char *reg_id;
- bool cpu;
- struct dvfs_process_id_table process_id_table[];
-};
struct clk_mux_sel {
struct clk *input;
u32 value;
};
-struct clk_pll_table {
+struct clk_pll_freq_table {
unsigned long input_rate;
unsigned long output_rate;
u16 n;
struct clk {
/* node for master clocks list */
- struct list_head node;
- struct list_head children; /* list of children */
- struct list_head sibling; /* node for children */
-#ifdef CONFIG_DEBUG_FS
- struct dentry *dent;
- struct dentry *parent_dent;
-#endif
- struct clk_ops *ops;
- struct clk *parent;
- struct clk_lookup lookup;
- unsigned long rate;
- unsigned long max_rate;
- u32 flags;
- u32 refcnt;
- const char *name;
- u32 reg;
- u32 reg_shift;
- unsigned int clk_num;
- enum clk_state state;
+ struct list_head node; /* node for list of all clocks */
+ struct list_head children; /* list of children */
+ struct list_head sibling; /* node for children */
+ struct list_head dvfs; /* list of dvfs dependencies */
+ struct clk_lookup lookup;
+
#ifdef CONFIG_DEBUG_FS
- bool set;
+ struct dentry *dent;
+ struct dentry *parent_dent;
#endif
+ bool set;
+ struct clk_ops *ops;
+ unsigned long dvfs_rate;
+ unsigned long rate;
+ unsigned long max_rate;
+ bool is_dvfs;
+ bool auto_dvfs;
+ u32 flags;
+ const char *name;
+
+ u32 refcnt;
+ unsigned long requested_rate;
+ enum clk_state state;
+ struct clk *parent;
+ u32 div;
+ u32 mul;
- /* PLL */
- unsigned long input_min;
- unsigned long input_max;
- unsigned long cf_min;
- unsigned long cf_max;
- unsigned long vco_min;
- unsigned long vco_max;
- const struct clk_pll_table *pll_table;
-
- /* DIV */
- u32 div;
- u32 mul;
-
- /* MUX */
const struct clk_mux_sel *inputs;
- u32 sel;
- u32 reg_mask;
-
- /* Virtual cpu clock */
- struct clk *main;
- struct clk *backup;
+ u32 reg;
+ u32 reg_shift;
- struct dvfs *dvfs;
+ union {
+ struct {
+ unsigned int clk_num;
+ } periph;
+ struct {
+ unsigned long input_min;
+ unsigned long input_max;
+ unsigned long cf_min;
+ unsigned long cf_max;
+ unsigned long vco_min;
+ unsigned long vco_max;
+ const struct clk_pll_freq_table *freq_table;
+ int lock_delay;
+ } pll;
+ struct {
+ u32 sel;
+ u32 reg_mask;
+ } mux;
+ struct {
+ struct clk *main;
+ struct clk *backup;
+ } cpu;
+ struct {
+ struct list_head list;
+ unsigned long min_rate;
+ } shared_bus;
+ struct {
+ struct list_head node;
+ bool enabled;
+ unsigned long rate;
+ } shared_bus_user;
+ } u;
};
int clk_set_rate_locked(struct clk *c, unsigned long rate);
int clk_reparent(struct clk *c, struct clk *parent);
void tegra_clk_init_from_table(struct tegra_clk_init_table *table);
+void tegra_clk_set_dvfs_rates(void);
#endif
void (*tegra_reset)(char mode, const char *cmd);
static __initdata struct tegra_clk_init_table common_clk_init_table[] = {
+ /* set up clocks that should always be on */
/* name parent rate enabled */
{ "clk_m", NULL, 0, true },
{ "pll_p", "clk_m", 216000000, true },
{ "sclk", "pll_m_out1", 240000000, true },
{ "hclk", "sclk", 240000000, true },
{ "pclk", "hclk", 120000000, true },
+ { "pll_x", NULL, 0, true },
+ { "cpu", NULL, 0, true },
+ { "emc", NULL, 0, true },
+ { "csite", NULL, 0, true },
+ { "timer", NULL, 0, true },
+ { "rtc", NULL, 0, true },
+
+ /* set frequencies of some device clocks */
{ "pll_u", "clk_m", 480000000, false },
{ "sdmmc1", "pll_p", 48000000, false},
{ "sdmmc2", "pll_p", 48000000, false},
return;
}
- pr_info("%s: %08lx %08lx %08lx %p", __func__, to, from, size, to_io);
-
if (pfn_valid(page_to_pfn(phys_to_page(from)))) {
for (i = 0 ; i < size; i += PAGE_SIZE) {
page = phys_to_page(from + i);
return rate;
}
+#ifdef CONFIG_HAVE_ARM_TWD
static void tegra_cpufreq_rescale_twd_other_cpu(void *data) {
unsigned long new_rate = *(unsigned long *)data;
twd_recalc_prescaler(new_rate);
twd_recalc_prescaler(new_rate);
smp_call_function(tegra_cpufreq_rescale_twd_other_cpu, &new_rate, 1);
}
+#else
+static inline void tegra_cpufreq_rescale_twds(unsigned long new_rate)
+{
+}
+#endif
static int tegra_update_cpu_speed(unsigned long rate)
{
freqs.old, freqs.new);
#endif
- ret = clk_set_rate_cansleep(cpu_clk, freqs.new * 1000);
+ ret = clk_set_rate(cpu_clk, freqs.new * 1000);
if (ret) {
pr_err("cpu-tegra: Failed to set cpu frequency to %d kHz\n",
freqs.new);
#include <linux/tick.h>
#include <asm/cacheflush.h>
+#include <asm/hardware/gic.h>
#include <asm/localtimer.h>
#include <mach/iomap.h>
static bool lp2_disabled_by_suspend;
module_param(lp2_in_idle, bool, 0644);
-static s64 tegra_cpu1_idle_time;
+static s64 tegra_cpu1_idle_time = LLONG_MAX;;
static int tegra_lp2_exit_latency;
static int tegra_lp2_power_off_time;
unsigned int lp2_completed_count;
unsigned int lp2_count_bin[32];
unsigned int lp2_completed_count_bin[32];
+ unsigned int lp2_int_count[NR_IRQS];
+ unsigned int last_lp2_int_count[NR_IRQS];
} idle_stats;
struct cpuidle_driver tegra_idle = {
static inline unsigned int time_to_bin(unsigned int time)
{
- unsigned int bin = 0;
- int i;
-
- for (i = 4; i >= 0; i--) {
- if (time > (1 << (1 << i)) - 1) {
- time >>= (1 << i);
- bin += (1 << i);
- }
- }
-
- return bin;
+ return fls(time);
}
static inline void tegra_unmask_irq(int irq)
reg = __raw_readl(flow_ctrl);
}
+#ifdef CONFIG_SMP
static inline bool tegra_wait_for_both_idle(struct cpuidle_device *dev)
{
int wake_int;
return !!(readl(CLK_RST_CONTROLLER_RST_CPU_CMPLX_SET) & (1 << cpu));
}
+static int tegra_tear_down_cpu1(void)
+{
+ u32 reg;
+
+ /* Signal to CPU1 to tear down */
+ tegra_legacy_force_irq_set(TEGRA_CPUIDLE_TEAR_DOWN);
+
+ /* At this point, CPU0 can no longer abort LP2, but CP1 can */
+ /* TODO: any way not to poll here? Use the LP2 timer to wfi? */
+ /* takes ~80 us */
+ while (!tegra_cpu_in_reset(1) &&
+ tegra_legacy_force_irq_status(TEGRA_CPUIDLE_BOTH_IDLE))
+ cpu_relax();
+
+ tegra_legacy_force_irq_clr(TEGRA_CPUIDLE_TEAR_DOWN);
+
+ /* If CPU1 aborted LP2, restart the process */
+ if (!tegra_legacy_force_irq_status(TEGRA_CPUIDLE_BOTH_IDLE))
+ return -EAGAIN;
+
+ /* CPU1 is ready for LP2, clock gate it */
+ reg = readl(CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
+ writel(reg | (1<<9), CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
+
+ return 0;
+}
+
+static void tegra_wake_cpu1(void)
+{
+ unsigned long boot_vector;
+ unsigned long old_boot_vector;
+ unsigned long timeout;
+ u32 reg;
+
+ boot_vector = virt_to_phys(tegra_hotplug_startup);
+ old_boot_vector = readl(EVP_CPU_RESET_VECTOR);
+ writel(boot_vector, EVP_CPU_RESET_VECTOR);
+
+ /* enable cpu clock on cpu */
+ reg = readl(CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
+ writel(reg & ~(1 << (8 + 1)), CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
+
+ reg = 0x1111 << 1;
+ writel(reg, CLK_RST_CONTROLLER_RST_CPU_CMPLX_CLR);
+
+ /* unhalt the cpu */
+ writel(0, IO_ADDRESS(TEGRA_FLOW_CTRL_BASE) + 0x14);
+
+ timeout = jiffies + msecs_to_jiffies(1000);
+ while (time_before(jiffies, timeout)) {
+ if (readl(EVP_CPU_RESET_VECTOR) != boot_vector)
+ break;
+ udelay(10);
+ }
+
+ /* put the old boot vector back */
+ writel(old_boot_vector, EVP_CPU_RESET_VECTOR);
+
+ /* CPU1 is now started */
+}
+#else
+static inline bool tegra_wait_for_both_idle(struct cpuidle_device *dev)
+{
+ return true;
+}
+
+static inline int tegra_tear_down_cpu1(void)
+{
+ return 0;
+}
+
+static inline void tegra_wake_cpu1(void)
+{
+}
+#endif
+
static void tegra_idle_enter_lp2_cpu0(struct cpuidle_device *dev,
struct cpuidle_state *state)
{
s64 request;
- u32 reg;
ktime_t enter;
ktime_t exit;
bool sleep_completed = false;
int bin;
- unsigned long boot_vector;
- unsigned long old_boot_vector;
- unsigned long timeout;
restart:
if (!tegra_wait_for_both_idle(dev))
/* CPU1 woke CPU0 because both are idle */
request = ktime_to_us(tick_nohz_get_sleep_length());
- if (request < tegra_lp2_exit_latency) {
+ if (request < state->target_residency) {
/* Not enough time left to enter LP2 */
tegra_flow_wfi(dev);
return;
}
- /* Signal to CPU1 to tear down */
- tegra_legacy_force_irq_set(TEGRA_CPUIDLE_TEAR_DOWN);
-
- /* At this point, CPU0 can no longer abort LP2, but CP1 can */
- /* TODO: any way not to poll here? Use the LP2 timer to wfi? */
- /* takes ~80 us */
- while (!tegra_cpu_in_reset(1) &&
- tegra_legacy_force_irq_status(TEGRA_CPUIDLE_BOTH_IDLE))
- cpu_relax();
-
- tegra_legacy_force_irq_clr(TEGRA_CPUIDLE_TEAR_DOWN);
-
idle_stats.tear_down_count++;
- /* If CPU1 aborted LP2, restart the process */
- if (!tegra_legacy_force_irq_status(TEGRA_CPUIDLE_BOTH_IDLE))
+ if (tegra_tear_down_cpu1())
goto restart;
- /* CPU1 is ready for LP2, clock gate it */
- reg = readl(CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
- writel(reg | (1<<9), CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
-
/* Enter LP2 */
request = ktime_to_us(tick_nohz_get_sleep_length());
smp_rmb();
request = min_t(s64, request, tegra_cpu1_idle_time);
enter = ktime_get();
- if (request > tegra_lp2_exit_latency + state->target_residency) {
+ if (request > state->target_residency) {
s64 sleep_time = request - tegra_lp2_exit_latency;
bin = time_to_bin((u32)request / 1000);
if (tegra_suspend_lp2(sleep_time) == 0)
sleep_completed = true;
+ else
+ idle_stats.lp2_int_count[tegra_pending_interrupt()]++;
}
/* Bring CPU1 out of LP2 */
/* set the reset vector to point to the secondary_startup routine */
smp_wmb();
- boot_vector = virt_to_phys(tegra_hotplug_startup);
- old_boot_vector = readl(EVP_CPU_RESET_VECTOR);
- writel(boot_vector, EVP_CPU_RESET_VECTOR);
-
- /* enable cpu clock on cpu */
- reg = readl(CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
- writel(reg & ~(1 << (8 + 1)), CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
-
- reg = 0x1111 << 1;
- writel(reg, CLK_RST_CONTROLLER_RST_CPU_CMPLX_CLR);
- /* unhalt the cpu */
- writel(0, IO_ADDRESS(TEGRA_FLOW_CTRL_BASE) + 0x14);
-
- timeout = jiffies + msecs_to_jiffies(1000);
- while (time_before(jiffies, timeout)) {
- if (readl(EVP_CPU_RESET_VECTOR) != boot_vector)
- break;
- udelay(10);
- }
-
- /* put the old boot vector back */
- writel(old_boot_vector, EVP_CPU_RESET_VECTOR);
-
- /* CPU1 is now started */
+ tegra_wake_cpu1();
/*
* TODO: is it worth going back to wfi if no interrupt is pending
}
}
+#ifdef CONFIG_SMP
static void tegra_idle_enter_lp2_cpu1(struct cpuidle_device *dev,
struct cpuidle_state *state)
{
out:
tegra_legacy_force_irq_clr(TEGRA_CPUIDLE_BOTH_IDLE);
}
+#endif
static int tegra_idle_enter_lp3(struct cpuidle_device *dev,
struct cpuidle_state *state)
idle_stats.cpu_ready_count[dev->cpu]++;
+#ifdef CONFIG_SMP
if (dev->cpu == 0)
tegra_idle_enter_lp2_cpu0(dev, state);
else
tegra_idle_enter_lp2_cpu1(dev, state);
+#else
+ tegra_idle_enter_lp2_cpu0(dev, state);
+#endif
exit = ktime_sub(ktime_get(), enter);
us = ktime_to_us(exit);
state->flags = CPUIDLE_FLAG_BALANCED | CPUIDLE_FLAG_TIME_VALID;
state->enter = tegra_idle_enter_lp2;
+ dev->power_specified = 1;
dev->safe_state = state;
dev->state_count++;
static int tegra_lp2_debug_show(struct seq_file *s, void *data)
{
int bin;
+ int i;
seq_printf(s, " cpu0 cpu1\n");
seq_printf(s, "-------------------------------------------------\n");
seq_printf(s, "cpu ready: %8u %8u\n",
idle_stats.lp2_count_bin[bin]);
}
+ seq_printf(s, "\n");
+ seq_printf(s, "%3s %20s %6s %10s\n",
+ "int", "name", "count", "last count");
+ seq_printf(s, "--------------------------------------------\n");
+ for (i = 0; i < NR_IRQS; i++) {
+ if (idle_stats.lp2_int_count[i] == 0)
+ continue;
+ seq_printf(s, "%3d %20s %6d %10d\n",
+ i, irq_to_desc(i)->action ?
+ irq_to_desc(i)->action->name ?: "???" : "???",
+ idle_stats.lp2_int_count[i],
+ idle_stats.lp2_int_count[i] -
+ idle_stats.last_lp2_int_count[i]);
+ idle_stats.last_lp2_int_count[i] = idle_stats.lp2_int_count[i];
+ };
return 0;
}
--- /dev/null
+/*
+ *
+ * Copyright (C) 2010 Google, Inc.
+ *
+ * Author:
+ * Colin Cross <ccross@google.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/list.h>
+#include <linux/init.h>
+#include <linux/list_sort.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+#include <linux/seq_file.h>
+#include <linux/regulator/consumer.h>
+#include <asm/clkdev.h>
+#include <mach/clk.h>
+
+#include "board.h"
+#include "clock.h"
+#include "dvfs.h"
+
+struct dvfs_reg {
+ struct list_head node; /* node in dvfs_reg_list */
+ struct list_head dvfs; /* list head of attached dvfs clocks */
+ const char *reg_id;
+ struct regulator *reg;
+ int max_millivolts;
+ int millivolts;
+};
+
+static LIST_HEAD(dvfs_list);
+static LIST_HEAD(dvfs_debug_list);
+static LIST_HEAD(dvfs_reg_list);
+
+static DEFINE_MUTEX(dvfs_lock);
+
+void lock_dvfs(void)
+{
+ mutex_lock(&dvfs_lock);
+}
+
+void unlock_dvfs(void)
+{
+ mutex_unlock(&dvfs_lock);
+}
+
+static int dvfs_reg_set_voltage(struct dvfs_reg *dvfs_reg)
+{
+ int millivolts = 0;
+ struct dvfs *d;
+
+ list_for_each_entry(d, &dvfs_reg->dvfs, reg_node)
+ millivolts = max(d->cur_millivolts, millivolts);
+
+ if (millivolts == dvfs_reg->millivolts)
+ return 0;
+
+ dvfs_reg->millivolts = millivolts;
+
+ return regulator_set_voltage(dvfs_reg->reg,
+ millivolts * 1000, dvfs_reg->max_millivolts * 1000);
+}
+
+static int dvfs_reg_get_voltage(struct dvfs_reg *dvfs_reg)
+{
+ int ret = regulator_get_voltage(dvfs_reg->reg);
+
+ if (ret > 0)
+ return ret / 1000;
+
+ return ret;
+}
+
+static struct dvfs_reg *get_dvfs_reg(struct dvfs *d)
+{
+ struct dvfs_reg *dvfs_reg;
+ struct regulator *reg;
+
+ list_for_each_entry(dvfs_reg, &dvfs_reg_list, node)
+ if (!strcmp(d->reg_id, dvfs_reg->reg_id))
+ return dvfs_reg;
+
+ reg = regulator_get(NULL, d->reg_id);
+ if (IS_ERR(reg))
+ return NULL;
+
+ dvfs_reg = kzalloc(sizeof(struct dvfs_reg), GFP_KERNEL);
+ if (!dvfs_reg) {
+ pr_err("%s: Failed to allocate dvfs_reg\n", __func__);
+ regulator_put(reg);
+ return NULL;
+ }
+
+ INIT_LIST_HEAD(&dvfs_reg->dvfs);
+ dvfs_reg->reg = reg;
+ dvfs_reg->reg_id = kstrdup(d->reg_id, GFP_KERNEL);
+
+ list_add_tail(&dvfs_reg->node, &dvfs_reg_list);
+
+ return dvfs_reg;
+}
+
+static struct dvfs_reg *attach_dvfs_reg(struct dvfs *d)
+{
+ struct dvfs_reg *dvfs_reg;
+
+ dvfs_reg = get_dvfs_reg(d);
+ if (!dvfs_reg)
+ return NULL;
+
+ list_add_tail(&d->reg_node, &dvfs_reg->dvfs);
+ d->dvfs_reg = dvfs_reg;
+ if (d->max_millivolts > d->dvfs_reg->max_millivolts)
+ d->dvfs_reg->max_millivolts = d->max_millivolts;
+
+ d->cur_millivolts = dvfs_reg_get_voltage(d->dvfs_reg);
+
+ return dvfs_reg;
+}
+
+static int
+__tegra_dvfs_set_rate(struct clk *c, struct dvfs *d, unsigned long rate)
+{
+ int i = 0;
+ int ret;
+
+ if (d->freqs == NULL || d->millivolts == NULL)
+ return -ENODEV;
+
+ if (rate > d->freqs[d->num_freqs - 1]) {
+ pr_warn("tegra_dvfs: rate %lu too high for dvfs on %s\n", rate,
+ c->name);
+ return -EINVAL;
+ }
+
+ if (rate == 0) {
+ d->cur_millivolts = 0;
+ } else {
+ while (i < d->num_freqs && rate > d->freqs[i])
+ i++;
+
+ d->cur_millivolts = d->millivolts[i];
+ }
+
+ d->cur_rate = rate;
+
+ if (!d->dvfs_reg)
+ return 0;
+
+ ret = dvfs_reg_set_voltage(d->dvfs_reg);
+ if (ret)
+ pr_err("Failed to set regulator %s for clock %s to %d mV\n",
+ d->dvfs_reg->reg_id, c->name, d->cur_millivolts);
+
+ return ret;
+}
+
+int tegra_dvfs_set_rate(struct clk *c, unsigned long rate)
+{
+ struct dvfs *d;
+ int ret = 0;
+ bool freq_up;
+
+ c->dvfs_rate = rate;
+
+ freq_up = (c->refcnt == 0) || (rate > c->rate);
+
+ list_for_each_entry(d, &c->dvfs, node) {
+ if (d->higher == freq_up)
+ ret = __tegra_dvfs_set_rate(c, d, rate);
+ if (ret)
+ return ret;
+ }
+
+ list_for_each_entry(d, &c->dvfs, node) {
+ if (d->higher != freq_up)
+ ret = __tegra_dvfs_set_rate(c, d, rate);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(tegra_dvfs_set_rate);
+
+int tegra_enable_dvfs_on_clk(struct clk *c, struct dvfs *d)
+{
+ int i;
+ struct dvfs_reg *dvfs_reg;
+
+ dvfs_reg = attach_dvfs_reg(d);
+ if (!dvfs_reg) {
+ pr_err("Failed to get regulator %s for clock %s\n",
+ d->reg_id, c->name);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < MAX_DVFS_FREQS; i++) {
+ if (d->millivolts[i] == 0)
+ break;
+
+ d->freqs[i] *= d->freqs_mult;
+
+ /* If final frequencies are 0, pad with previous frequency */
+ if (d->freqs[i] == 0 && i > 1)
+ d->freqs[i] = d->freqs[i - 1];
+ }
+ d->num_freqs = i;
+
+ if (d->auto_dvfs)
+ c->auto_dvfs = true;
+
+ c->is_dvfs = true;
+ smp_wmb();
+
+ list_add_tail(&d->node, &c->dvfs);
+
+ list_add_tail(&d->debug_node, &dvfs_debug_list);
+
+ return 0;
+}
+
+int __init tegra_init_dvfs(void)
+{
+ lock_dvfs();
+ tegra2_init_dvfs();
+
+ tegra_clk_set_dvfs_rates();
+ unlock_dvfs();
+
+ return 0;
+}
+late_initcall(tegra_init_dvfs);
+
+#ifdef CONFIG_DEBUG_FS
+static int dvfs_tree_sort_cmp(void *p, struct list_head *a, struct list_head *b)
+{
+ struct dvfs *da = list_entry(a, struct dvfs, debug_node);
+ struct dvfs *db = list_entry(b, struct dvfs, debug_node);
+ int ret;
+
+ ret = strcmp(da->reg_id, db->reg_id);
+ if (ret != 0)
+ return ret;
+
+ if (da->cur_millivolts < db->cur_millivolts)
+ return 1;
+ if (da->cur_millivolts > db->cur_millivolts)
+ return -1;
+
+ return strcmp(da->clk_name, db->clk_name);
+}
+
+static int dvfs_tree_show(struct seq_file *s, void *data)
+{
+ struct dvfs *d;
+ const char *last_reg = "";
+
+ seq_printf(s, " clock rate mV\n");
+ seq_printf(s, "--------------------------------\n");
+
+ lock_dvfs();
+
+ list_sort(NULL, &dvfs_debug_list, dvfs_tree_sort_cmp);
+
+ list_for_each_entry(d, &dvfs_debug_list, debug_node) {
+ if (strcmp(last_reg, d->dvfs_reg->reg_id) != 0) {
+ last_reg = d->dvfs_reg->reg_id;
+ seq_printf(s, "%s %d mV:\n", d->dvfs_reg->reg_id,
+ d->dvfs_reg->millivolts);
+ }
+
+ seq_printf(s, " %-10s %-10lu %-4d mV\n", d->clk_name,
+ d->cur_rate, d->cur_millivolts);
+ }
+
+ unlock_dvfs();
+
+ return 0;
+}
+
+static int dvfs_tree_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, dvfs_tree_show, inode->i_private);
+}
+
+static const struct file_operations dvfs_tree_fops = {
+ .open = dvfs_tree_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+int __init dvfs_debugfs_init(struct dentry *clk_debugfs_root)
+{
+ struct dentry *d;
+
+ d = debugfs_create_file("dvfs", S_IRUGO, clk_debugfs_root, NULL,
+ &dvfs_tree_fops);
+ if (!d)
+ return -ENOMEM;
+
+ return 0;
+}
+
+#endif
--- /dev/null
+/*
+ *
+ * Copyright (C) 2010 Google, Inc.
+ *
+ * Author:
+ * Colin Cross <ccross@google.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#ifndef _TEGRA_DVFS_H_
+#define _TEGRA_DVFS_H_
+
+#define MAX_DVFS_FREQS 16
+
+struct clk;
+
+struct dvfs {
+ /* Used only by tegra2_clock.c */
+ const char *clk_name;
+ int process_id;
+ bool cpu;
+
+ /* Must be initialized before tegra_dvfs_init */
+ const char *reg_id;
+ int freqs_mult;
+ unsigned long freqs[MAX_DVFS_FREQS];
+ unsigned long millivolts[MAX_DVFS_FREQS];
+ bool auto_dvfs;
+ bool higher;
+
+ /* Filled in by tegra_dvfs_init */
+ int max_millivolts;
+ int num_freqs;
+ struct dvfs_reg *dvfs_reg;
+
+ int cur_millivolts;
+ unsigned long cur_rate;
+ struct list_head node;
+ struct list_head debug_node;
+ struct list_head reg_node;
+};
+
+void lock_dvfs(void);
+void unlock_dvfs(void);
+
+void tegra2_init_dvfs(void);
+int tegra_enable_dvfs_on_clk(struct clk *c, struct dvfs *d);
+int dvfs_debugfs_init(struct dentry *clk_debugfs_root);
+
+#endif
str \val, [\tmp]
.endm
+#ifdef CONFIG_SMP
/*
* tegra_secondary_startup
*
poke_ev r0, r1
b secondary_startup
ENDPROC(tegra_secondary_startup)
+#endif
/*
* __restart_pllx
.long tegra_pgd_phys
.long __cortex_a9_restore
.size __tegra_hotplug_data, . - __tegra_hotplug_data
-#endif
\ No newline at end of file
+#endif
#ifndef __MACH_CLK_H
#define __MACH_CLK_H
+struct dvfs;
+
void tegra_periph_reset_deassert(struct clk *c);
void tegra_periph_reset_assert(struct clk *c);
-int clk_enable_cansleep(struct clk *clk);
-void clk_disable_cansleep(struct clk *clk);
-int clk_set_rate_cansleep(struct clk *clk, unsigned long rate);
-int clk_set_parent_cansleep(struct clk *clk, struct clk *parent);
+int tegra_dvfs_set_rate(struct clk *c, unsigned long rate);
#endif
#include <linux/kernel.h>
#include <linux/delay.h>
+#include <linux/debugfs.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/io.h>
+#include <linux/seq_file.h>
#include <asm/hardware/gic.h>
static u32 tegra_lp0_wake_level;
static u32 tegra_lp0_wake_level_any;
+static unsigned int tegra_wake_irq_count[32];
+
/* ensures that sufficient time is passed for a register write to
* serialize into the 32KHz domain */
static void pmc_32kwritel(u32 val, unsigned long offs)
pr_info("Resume caused by WAKE%d, %s\n", wake,
desc->action->name);
+ tegra_wake_irq_count[wake]++;
+
generic_handle_irq(irq);
}
}
tegra_legacy_irq_resume();
tegra_irq_handle_wake();
}
+
+#ifdef CONFIG_DEBUG_FS
+static int tegra_wake_irq_debug_show(struct seq_file *s, void *data)
+{
+ int wake;
+ int irq;
+ struct irq_desc *desc;
+ const char *irq_name;
+
+ seq_printf(s, "wake irq count name\n");
+ seq_printf(s, "----------------------\n");
+ for (wake = 0; wake < 32; wake++) {
+ irq = tegra_wake_to_irq(wake);
+ if (irq < 0)
+ continue;
+
+ desc = irq_to_desc(irq);
+ if (tegra_wake_irq_count[wake] == 0 && desc->action == NULL)
+ continue;
+
+ if (!(desc->status & IRQ_WAKEUP))
+ continue;
+
+ irq_name = (desc->action && desc->action->name) ?
+ desc->action->name : "???";
+
+ seq_printf(s, "%4d %3d %5d %s\n",
+ wake, irq, tegra_wake_irq_count[wake], irq_name);
+ }
+ return 0;
+}
+
+static int tegra_wake_irq_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, tegra_wake_irq_debug_show, NULL);
+}
+
+static const struct file_operations tegra_wake_irq_debug_fops = {
+ .open = tegra_wake_irq_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int __init tegra_irq_debug_init(void)
+{
+ struct dentry *d;
+
+ d = debugfs_create_file("wake_irq", 0755, NULL, NULL,
+ &tegra_wake_irq_debug_fops);
+ if (!d) {
+ pr_info("Failed to create suspend_mode debug file\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+late_initcall(tegra_irq_debug_init);
+#endif
#include <asm/smp_scu.h>
#include <asm/cpu.h>
#include <asm/mmu_context.h>
-#include <asm/pgalloc.h>
#include <mach/iomap.h>
static DEFINE_SPINLOCK(boot_lock);
static void __iomem *scu_base = IO_ADDRESS(TEGRA_ARM_PERIF_BASE);
-extern void __cortex_a9_restore(void);
-extern void __shut_off_mmu(void);
#ifdef CONFIG_HOTPLUG_CPU
static DEFINE_PER_CPU(struct completion, cpu_killed);
#define CLK_RST_CONTROLLER_RST_CPU_CMPLX_CLR \
(IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x344)
-unsigned long tegra_pgd_phys; /* pgd used by hotplug & LP2 bootup */
-static pgd_t *tegra_pgd;
-void *tegra_context_area = NULL;
-
void __cpuinit platform_secondary_init(unsigned int cpu)
{
trace_hardirqs_off();
cpu_set(i, cpu_possible_map);
}
-static int create_suspend_pgtable(void)
-{
- int i;
- pmd_t *pmd;
- /* arrays of virtual-to-physical mappings which must be
- * present to safely boot hotplugged / LP2-idled CPUs.
- * tegra_hotplug_startup (hotplug reset vector) is mapped
- * VA=PA so that the translation post-MMU is the same as
- * pre-MMU, IRAM is mapped VA=PA so that SDRAM self-refresh
- * can safely disable the MMU */
- unsigned long addr_v[] = {
- PHYS_OFFSET,
- IO_IRAM_PHYS,
- (unsigned long)tegra_context_area,
- (unsigned long)virt_to_phys(tegra_hotplug_startup),
- (unsigned long)__cortex_a9_restore,
- (unsigned long)virt_to_phys(__shut_off_mmu),
- };
- unsigned long addr_p[] = {
- PHYS_OFFSET,
- IO_IRAM_PHYS,
- (unsigned long)virt_to_phys(tegra_context_area),
- (unsigned long)virt_to_phys(tegra_hotplug_startup),
- (unsigned long)virt_to_phys(__cortex_a9_restore),
- (unsigned long)virt_to_phys(__shut_off_mmu),
- };
- unsigned int flags = PMD_TYPE_SECT | PMD_SECT_AP_WRITE |
- PMD_SECT_WBWA | PMD_SECT_S;
-
- tegra_pgd = pgd_alloc(&init_mm);
- if (!tegra_pgd)
- return -ENOMEM;
-
- for (i=0; i<ARRAY_SIZE(addr_p); i++) {
- unsigned long v = addr_v[i];
- pmd = pmd_offset(tegra_pgd + pgd_index(v), v);
- *pmd = __pmd((addr_p[i] & PGDIR_MASK) | flags);
- flush_pmd_entry(pmd);
- outer_clean_range(__pa(pmd), __pa(pmd + 1));
- }
-
- tegra_pgd_phys = virt_to_phys(tegra_pgd);
- __cpuc_flush_dcache_area(&tegra_pgd_phys,
- sizeof(tegra_pgd_phys));
- outer_clean_range(__pa(&tegra_pgd_phys),
- __pa(&tegra_pgd_phys+1));
-
- __cpuc_flush_dcache_area(&tegra_context_area,
- sizeof(tegra_context_area));
- outer_clean_range(__pa(&tegra_context_area),
- __pa(&tegra_context_area+1));
-
- return 0;
-}
-
void __init smp_prepare_cpus(unsigned int max_cpus)
{
unsigned int ncores = scu_get_core_count(scu_base);
if (max_cpus > ncores)
max_cpus = ncores;
- tegra_context_area = kzalloc(CONTEXT_SIZE_BYTES * ncores, GFP_KERNEL);
-
- if (tegra_context_area && create_suspend_pgtable()) {
- kfree(tegra_context_area);
- tegra_context_area = NULL;
- }
-
/*
* Initialise the present map, which describes the set of CPUs
* actually populated at the present time.
#define TEGRA_IRAM_CODE_SIZE SZ_4K
#ifndef __ASSEMBLY__
+extern void *tegra_context_area;
+
+u64 tegra_rtc_read_ms(void);
void tegra_lp2_set_trigger(unsigned long cycles);
unsigned long tegra_lp2_timer_remain(void);
void __cortex_a9_save(unsigned int mode);
+void __cortex_a9_restore(void);
+void __shut_off_mmu(void);
void tegra_lp2_startup(void);
unsigned int tegra_suspend_lp2(unsigned int us);
void tegra_hotplug_startup(void);
#include <asm/hardware/cache-l2x0.h>
#include <asm/hardware/gic.h>
#include <asm/localtimer.h>
+#include <asm/pgalloc.h>
#include <asm/tlbflush.h>
#include <mach/iomap.h>
volatile struct suspend_context tegra_sctx;
-#ifdef CONFIG_HOTPLUG_CPU
static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
static void __iomem *clk_rst = IO_ADDRESS(TEGRA_CLK_RESET_BASE);
static void __iomem *flow_ctrl = IO_ADDRESS(TEGRA_FLOW_CTRL_BASE);
static void __iomem *evp_reset = IO_ADDRESS(TEGRA_EXCEPTION_VECTORS_BASE)+0x100;
static void __iomem *tmrus = IO_ADDRESS(TEGRA_TMRUS_BASE);
-#endif
#define PMC_CTRL 0x0
#define PMC_CTRL_LATCH_WAKEUPS (1 << 5)
#define FLOW_CTRL_CPU_CSR 0x8
#define FLOW_CTRL_CPU1_CSR 0x18
+unsigned long tegra_pgd_phys; /* pgd used by hotplug & LP2 bootup */
+static pgd_t *tegra_pgd;
+void *tegra_context_area = NULL;
+
static struct clk *tegra_pclk = NULL;
static const struct tegra_suspend_platform_data *pdata = NULL;
static unsigned long wb0_restore = 0;
static enum tegra_suspend_mode current_suspend_mode;
+static unsigned int tegra_time_in_suspend[32];
+
+static inline unsigned int time_to_bin(unsigned int time)
+{
+ return fls(time);
+}
+
unsigned long tegra_cpu_power_good_time(void)
{
if (WARN_ON_ONCE(!pdata))
last_pclk = pclk;
}
+static int create_suspend_pgtable(void)
+{
+ int i;
+ pmd_t *pmd;
+ /* arrays of virtual-to-physical mappings which must be
+ * present to safely boot hotplugged / LP2-idled CPUs.
+ * tegra_hotplug_startup (hotplug reset vector) is mapped
+ * VA=PA so that the translation post-MMU is the same as
+ * pre-MMU, IRAM is mapped VA=PA so that SDRAM self-refresh
+ * can safely disable the MMU */
+ unsigned long addr_v[] = {
+ PHYS_OFFSET,
+ IO_IRAM_PHYS,
+ (unsigned long)tegra_context_area,
+#ifdef CONFIG_HOTPLUG_CPU
+ (unsigned long)virt_to_phys(tegra_hotplug_startup),
+#endif
+ (unsigned long)__cortex_a9_restore,
+ (unsigned long)virt_to_phys(__shut_off_mmu),
+ };
+ unsigned long addr_p[] = {
+ PHYS_OFFSET,
+ IO_IRAM_PHYS,
+ (unsigned long)virt_to_phys(tegra_context_area),
+#ifdef CONFIG_HOTPLUG_CPU
+ (unsigned long)virt_to_phys(tegra_hotplug_startup),
+#endif
+ (unsigned long)virt_to_phys(__cortex_a9_restore),
+ (unsigned long)virt_to_phys(__shut_off_mmu),
+ };
+ unsigned int flags = PMD_TYPE_SECT | PMD_SECT_AP_WRITE |
+ PMD_SECT_WBWA | PMD_SECT_S;
+
+ tegra_pgd = pgd_alloc(&init_mm);
+ if (!tegra_pgd)
+ return -ENOMEM;
+
+ for (i=0; i<ARRAY_SIZE(addr_p); i++) {
+ unsigned long v = addr_v[i];
+ pmd = pmd_offset(tegra_pgd + pgd_index(v), v);
+ *pmd = __pmd((addr_p[i] & PGDIR_MASK) | flags);
+ flush_pmd_entry(pmd);
+ outer_clean_range(__pa(pmd), __pa(pmd + 1));
+ }
+
+ tegra_pgd_phys = virt_to_phys(tegra_pgd);
+ __cpuc_flush_dcache_area(&tegra_pgd_phys,
+ sizeof(tegra_pgd_phys));
+ outer_clean_range(__pa(&tegra_pgd_phys),
+ __pa(&tegra_pgd_phys+1));
+
+ __cpuc_flush_dcache_area(&tegra_context_area,
+ sizeof(tegra_context_area));
+ outer_clean_range(__pa(&tegra_context_area),
+ __pa(&tegra_context_area+1));
+
+ return 0;
+}
+
+
+
/*
* suspend_cpu_complex
*
writel(reg, flow_ctrl + FLOW_CTRL_CPU_CSR);
wmb();
+#ifdef CONFIG_HAVE_ARM_TWD
writel(tegra_sctx.twd_ctrl, twd_base + 0x8);
writel(tegra_sctx.twd_load, twd_base + 0);
+#endif
gic_dist_restore(0);
get_irq_chip(IRQ_LOCALTIMER)->unmask(IRQ_LOCALTIMER);
tegra_sctx.pllx_misc = readl(clk_rst + CLK_RESET_PLLX_MISC);
tegra_sctx.cclk_divider = readl(clk_rst + CLK_RESET_CCLK_DIVIDER);
+#ifdef CONFIG_HAVE_ARM_TWD
tegra_sctx.twd_ctrl = readl(twd_base + 0x8);
tegra_sctx.twd_load = readl(twd_base + 0);
local_timer_stop();
+#endif
reg = readl(flow_ctrl + FLOW_CTRL_CPU_CSR);
/* clear any pending events, set the WFE bitmap to specify just
bool do_lp0 = (current_suspend_mode == TEGRA_SUSPEND_LP0);
bool do_lp2 = (current_suspend_mode == TEGRA_SUSPEND_LP2);
int lp_state;
+ u64 rtc_before;
+ u64 rtc_after;
+ u64 secs;
+ u32 ms;
if (do_lp2)
lp_state = 2;
}
}
+ rtc_before = tegra_rtc_read_ms();
+
if (do_lp2)
tegra_suspend_lp2(0);
else
tegra_suspend_dram(do_lp0);
+ rtc_after = tegra_rtc_read_ms();
+
for_each_irq_desc(irq, desc) {
if ((desc->status & IRQ_WAKEUP) &&
(desc->status & IRQ_SUSPENDED)) {
tegra_irq_resume();
}
+ secs = rtc_after - rtc_before;
+ ms = do_div(secs, 1000);
+ pr_info("Suspended for %llu.%03u seconds\n", secs, ms);
+
+ tegra_time_in_suspend[time_to_bin(secs)]++;
+
local_irq_restore(flags);
return 0;
plat->suspend_mode = TEGRA_SUSPEND_LP1;
}
+ tegra_context_area = kzalloc(CONTEXT_SIZE_BYTES * NR_CPUS, GFP_KERNEL);
+ pr_info("%s: %p\n", __func__, tegra_context_area);
+
+ if (tegra_context_area && create_suspend_pgtable()) {
+ kfree(tegra_context_area);
+ tegra_context_area = NULL;
+ }
+
#ifdef CONFIG_PM
iram_save_size = (unsigned long)__tegra_iram_end;
iram_save_size -= (unsigned long)__tegra_lp1_reset;
.release = single_release,
};
+static int tegra_suspend_time_debug_show(struct seq_file *s, void *data)
+{
+ int bin;
+ seq_printf(s, "time (secs) count\n");
+ seq_printf(s, "------------------\n");
+ for (bin = 0; bin < 32; bin++) {
+ if (tegra_time_in_suspend[bin] == 0)
+ continue;
+ seq_printf(s, "%4d - %4d %4u\n",
+ bin ? 1 << (bin - 1) : 0, 1 << bin,
+ tegra_time_in_suspend[bin]);
+ }
+ return 0;
+}
+
+static int tegra_suspend_time_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, tegra_suspend_time_debug_show, NULL);
+}
+
+static const struct file_operations tegra_suspend_time_debug_fops = {
+ .open = tegra_suspend_time_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
static int __init tegra_suspend_debug_init(void)
{
struct dentry *d;
return -ENOMEM;
}
+ d = debugfs_create_file("suspend_time", 0755, NULL, NULL,
+ &tegra_suspend_time_debug_fops);
+ if (!d) {
+ pr_info("Failed to create suspend_time debug file\n");
+ return -ENOMEM;
+ }
+
return 0;
}
#include "clock.h"
#include "fuse.h"
-#include "tegra2_dvfs.h"
#define RST_DEVICES 0x004
#define RST_DEVICES_SET 0x300
#define PLL_BASE_ENABLE (1<<30)
#define PLL_BASE_REF_ENABLE (1<<29)
#define PLL_BASE_OVERRIDE (1<<28)
-#define PLL_BASE_LOCK (1<<27)
#define PLL_BASE_DIVP_MASK (0x7<<20)
#define PLL_BASE_DIVP_SHIFT 20
#define PLL_BASE_DIVN_MASK (0x3FF<<8)
#define PLL_OUT_RESET_DISABLE (1<<0)
#define PLL_MISC(c) (((c)->flags & PLL_ALT_MISC_REG) ? 0x4 : 0xc)
-#define PLL_MISC_LOCK_ENABLE(c) (((c)->flags & PLLU) ? (1<<22) : (1<<18))
#define PLL_MISC_DCCON_SHIFT 20
#define PLL_MISC_CPCON_SHIFT 8
#define PLLD_MISC_DIV_RST (1<<23)
#define PLLD_MISC_DCCON_SHIFT 12
-#define PERIPH_CLK_TO_ENB_REG(c) ((c->clk_num / 32) * 4)
-#define PERIPH_CLK_TO_ENB_SET_REG(c) ((c->clk_num / 32) * 8)
-#define PERIPH_CLK_TO_ENB_BIT(c) (1 << (c->clk_num % 32))
+#define PERIPH_CLK_TO_ENB_REG(c) ((c->u.periph.clk_num / 32) * 4)
+#define PERIPH_CLK_TO_ENB_SET_REG(c) ((c->u.periph.clk_num / 32) * 8)
+#define PERIPH_CLK_TO_ENB_BIT(c) (1 << (c->u.periph.clk_num % 32))
#define SUPER_CLK_MUX 0x00
#define SUPER_STATE_SHIFT 28
static void __iomem *reg_clk_base = IO_ADDRESS(TEGRA_CLK_RESET_BASE);
static void __iomem *reg_pmc_base = IO_ADDRESS(TEGRA_PMC_BASE);
+/*
+ * Some peripheral clocks share an enable bit, so refcount the enable bits
+ * in registers CLK_ENABLE_L, CLK_ENABLE_H, and CLK_ENABLE_U
+ */
+static int tegra_periph_clk_enable_refcount[3 * 32];
+
#define clk_writel(value, reg) \
__raw_writel(value, (u32)reg_clk_base + (reg))
#define clk_readl(reg) \
}
BUG_ON(sel->input == NULL);
c->parent = sel->input;
+
+ INIT_LIST_HEAD(&c->u.shared_bus.list);
}
static int tegra2_super_clk_enable(struct clk *c)
static int tegra2_cpu_clk_set_rate(struct clk *c, unsigned long rate)
{
int ret;
- ret = clk_set_parent_locked(c->parent, c->backup);
+ /*
+ * Take an extra reference to the main pll so it doesn't turn
+ * off when we move the cpu off of it
+ */
+ clk_enable_locked(c->u.cpu.main);
+
+ ret = clk_set_parent_locked(c->parent, c->u.cpu.backup);
if (ret) {
- pr_err("Failed to switch cpu to clock %s\n", c->backup->name);
- return ret;
+ pr_err("Failed to switch cpu to clock %s\n", c->u.cpu.backup->name);
+ goto out;
}
- if (rate == c->backup->rate)
+ if (rate == c->u.cpu.backup->rate)
goto out;
- ret = clk_set_rate_locked(c->main, rate);
+ ret = clk_set_rate_locked(c->u.cpu.main, rate);
if (ret) {
pr_err("Failed to change cpu pll to %lu\n", rate);
- return ret;
+ goto out;
}
- ret = clk_set_parent_locked(c->parent, c->main);
+ ret = clk_set_parent_locked(c->parent, c->u.cpu.main);
if (ret) {
- pr_err("Failed to switch cpu to clock %s\n", c->main->name);
- return ret;
+ pr_err("Failed to switch cpu to clock %s\n", c->u.cpu.main->name);
+ goto out;
}
out:
- return 0;
+ clk_disable_locked(c->u.cpu.main);
+ return ret;
}
static struct clk_ops tegra_cpu_ops = {
/* virtual cop clock functions. Used to acquire the fake 'cop' clock to
* reset the COP block (i.e. AVP) */
-static void tegra2_cop_clk_init(struct clk *c)
-{
- c->state = c->parent->state;
-}
-
-static int tegra2_cop_clk_enable(struct clk *c)
-{
- return 0;
-}
-
static void tegra2_cop_clk_reset(struct clk *c, bool assert)
{
unsigned long reg = assert ? RST_DEVICES_SET : RST_DEVICES_CLR;
}
static struct clk_ops tegra_cop_ops = {
- .init = tegra2_cop_clk_init,
- .enable = tegra2_cop_clk_enable,
.reset = tegra2_cop_clk_reset,
};
/* PLL Functions */
static int tegra2_pll_clk_wait_for_lock(struct clk *c)
{
- ktime_t before;
-
- before = ktime_get();
-
- while (!(clk_readl(c->reg + PLL_BASE) & PLL_BASE_LOCK)) {
- if (ktime_us_delta(ktime_get(), before) > 5000) {
- pr_err("Timed out waiting for lock bit on pll %s",
- c->name);
- return -1;
- }
- }
+ udelay(c->u.pll.lock_delay);
return 0;
}
val |= PLL_BASE_ENABLE;
clk_writel(val, c->reg + PLL_BASE);
- val = clk_readl(c->reg + PLL_MISC(c));
- val |= PLL_MISC_LOCK_ENABLE(c);
- clk_writel(val, c->reg + PLL_MISC(c));
-
tegra2_pll_clk_wait_for_lock(c);
return 0;
{
u32 val;
unsigned long input_rate;
- const struct clk_pll_table *sel;
+ const struct clk_pll_freq_table *sel;
pr_debug("%s: %s %lu\n", __func__, c->name, rate);
- BUG_ON(c->refcnt != 0);
input_rate = c->parent->rate;
- for (sel = c->pll_table; sel->input_rate != 0; sel++) {
+ for (sel = c->u.pll.freq_table; sel->input_rate != 0; sel++) {
if (sel->input_rate == input_rate && sel->output_rate == rate) {
c->mul = sel->n;
c->div = sel->m * sel->p;
u32 val;
pr_debug("%s on clock %s\n", __func__, c->name);
+ tegra_periph_clk_enable_refcount[c->u.periph.clk_num]++;
+ if (tegra_periph_clk_enable_refcount[c->u.periph.clk_num] > 1)
+ return 0;
+
clk_writel(PERIPH_CLK_TO_ENB_BIT(c),
CLK_OUT_ENB_SET + PERIPH_CLK_TO_ENB_SET_REG(c));
if (!(c->flags & PERIPH_NO_RESET) && !(c->flags & PERIPH_MANUAL_RESET))
{
pr_debug("%s on clock %s\n", __func__, c->name);
- clk_writel(PERIPH_CLK_TO_ENB_BIT(c),
- CLK_OUT_ENB_CLR + PERIPH_CLK_TO_ENB_SET_REG(c));
+ if (c->refcnt)
+ tegra_periph_clk_enable_refcount[c->u.periph.clk_num]--;
+
+ if (tegra_periph_clk_enable_refcount[c->u.periph.clk_num] == 0)
+ clk_writel(PERIPH_CLK_TO_ENB_BIT(c),
+ CLK_OUT_ENB_CLR + PERIPH_CLK_TO_ENB_SET_REG(c));
}
static void tegra2_periph_clk_reset(struct clk *c, bool assert)
.disable = &tegra2_cdev_clk_disable,
};
+/* shared bus ops */
+/*
+ * Some clocks may have multiple downstream users that need to request a
+ * higher clock rate. Shared bus clocks provide a unique shared_bus_user
+ * clock to each user. The frequency of the bus is set to the highest
+ * enabled shared_bus_user clock, with a minimum value set by the
+ * shared bus.
+ */
+static void tegra_clk_shared_bus_update(struct clk *bus)
+{
+ struct clk *c;
+ unsigned long rate = bus->u.shared_bus.min_rate;
+
+ list_for_each_entry(c, &bus->u.shared_bus.list, u.shared_bus_user.node)
+ if (c->u.shared_bus_user.enabled)
+ rate = max(c->u.shared_bus_user.rate, rate);
+
+ if (rate != bus->rate)
+ clk_set_rate_locked(bus, rate);
+};
+
+static void tegra_clk_shared_bus_init(struct clk *c)
+{
+ c->max_rate = c->parent->max_rate;
+ c->u.shared_bus_user.rate = c->parent->max_rate;
+ c->state = OFF;
+ c->set = true;
+
+ list_add_tail(&c->u.shared_bus_user.node,
+ &c->parent->u.shared_bus.list);
+}
+
+static int tegra_clk_shared_bus_set_rate(struct clk *c, unsigned long rate)
+{
+ c->u.shared_bus_user.rate = rate;
+ tegra_clk_shared_bus_update(c->parent);
+ return 0;
+}
+
+static int tegra_clk_shared_bus_enable(struct clk *c)
+{
+ c->u.shared_bus_user.enabled = true;
+ tegra_clk_shared_bus_update(c->parent);
+ return 0;
+}
+
+static void tegra_clk_shared_bus_disable(struct clk *c)
+{
+ c->u.shared_bus_user.enabled = false;
+ tegra_clk_shared_bus_update(c->parent);
+}
+
+static struct clk_ops tegra_clk_shared_bus_ops = {
+ .init = tegra_clk_shared_bus_init,
+ .enable = tegra_clk_shared_bus_enable,
+ .disable = tegra_clk_shared_bus_disable,
+ .set_rate = tegra_clk_shared_bus_set_rate,
+};
+
+
/* Clock definitions */
static struct clk tegra_clk_32k = {
.name = "clk_32k",
.max_rate = 32768,
};
-static struct clk_pll_table tegra_pll_s_table[] = {
+static struct clk_pll_freq_table tegra_pll_s_freq_table[] = {
{32768, 12000000, 366, 1, 1, 0},
{32768, 13000000, 397, 1, 1, 0},
{32768, 19200000, 586, 1, 1, 0},
.name = "pll_s",
.flags = PLL_ALT_MISC_REG,
.ops = &tegra_pll_ops,
- .reg = 0xf0,
- .input_min = 32768,
- .input_max = 32768,
.parent = &tegra_clk_32k,
- .cf_min = 0, /* FIXME */
- .cf_max = 0, /* FIXME */
- .vco_min = 12000000,
- .vco_max = 26000000,
- .pll_table = tegra_pll_s_table,
.max_rate = 26000000,
+ .reg = 0xf0,
+ .u.pll = {
+ .input_min = 32768,
+ .input_max = 32768,
+ .cf_min = 0, /* FIXME */
+ .cf_max = 0, /* FIXME */
+ .vco_min = 12000000,
+ .vco_max = 26000000,
+ .freq_table = tegra_pll_s_freq_table,
+ .lock_delay = 300,
+ },
};
static struct clk_mux_sel tegra_clk_m_sel[] = {
{ .input = &tegra_pll_s, .value = 1},
{ 0, 0},
};
+
static struct clk tegra_clk_m = {
.name = "clk_m",
.flags = ENABLE_ON_INIT,
.ops = &tegra_clk_m_ops,
.inputs = tegra_clk_m_sel,
.reg = 0x1fc,
- .reg_mask = (1<<28),
.reg_shift = 28,
.max_rate = 26000000,
};
-static struct clk_pll_table tegra_pll_c_table[] = {
+static struct clk_pll_freq_table tegra_pll_c_freq_table[] = {
{ 0, 0, 0, 0, 0, 0 },
};
.flags = PLL_HAS_CPCON,
.ops = &tegra_pll_ops,
.reg = 0x80,
- .input_min = 2000000,
- .input_max = 31000000,
.parent = &tegra_clk_m,
- .cf_min = 1000000,
- .cf_max = 6000000,
- .vco_min = 20000000,
- .vco_max = 1400000000,
- .pll_table = tegra_pll_c_table,
.max_rate = 600000000,
+ .u.pll = {
+ .input_min = 2000000,
+ .input_max = 31000000,
+ .cf_min = 1000000,
+ .cf_max = 6000000,
+ .vco_min = 20000000,
+ .vco_max = 1400000000,
+ .freq_table = tegra_pll_c_freq_table,
+ .lock_delay = 300,
+ },
};
static struct clk tegra_pll_c_out1 = {
.max_rate = 600000000,
};
-static struct clk_pll_table tegra_pll_m_table[] = {
+static struct clk_pll_freq_table tegra_pll_m_freq_table[] = {
{ 12000000, 666000000, 666, 12, 1, 8},
{ 13000000, 666000000, 666, 13, 1, 8},
{ 19200000, 666000000, 555, 16, 1, 8},
.flags = PLL_HAS_CPCON,
.ops = &tegra_pll_ops,
.reg = 0x90,
- .input_min = 2000000,
- .input_max = 31000000,
.parent = &tegra_clk_m,
- .cf_min = 1000000,
- .cf_max = 6000000,
- .vco_min = 20000000,
- .vco_max = 1200000000,
- .pll_table = tegra_pll_m_table,
.max_rate = 800000000,
+ .u.pll = {
+ .input_min = 2000000,
+ .input_max = 31000000,
+ .cf_min = 1000000,
+ .cf_max = 6000000,
+ .vco_min = 20000000,
+ .vco_max = 1200000000,
+ .freq_table = tegra_pll_m_freq_table,
+ .lock_delay = 300,
+ },
};
static struct clk tegra_pll_m_out1 = {
.max_rate = 600000000,
};
-static struct clk_pll_table tegra_pll_p_table[] = {
+static struct clk_pll_freq_table tegra_pll_p_freq_table[] = {
{ 12000000, 216000000, 432, 12, 2, 8},
{ 13000000, 216000000, 432, 13, 2, 8},
{ 19200000, 216000000, 90, 4, 2, 1},
.flags = ENABLE_ON_INIT | PLL_FIXED | PLL_HAS_CPCON,
.ops = &tegra_pll_ops,
.reg = 0xa0,
- .input_min = 2000000,
- .input_max = 31000000,
.parent = &tegra_clk_m,
- .cf_min = 1000000,
- .cf_max = 6000000,
- .vco_min = 20000000,
- .vco_max = 1400000000,
- .pll_table = tegra_pll_p_table,
.max_rate = 432000000,
+ .u.pll = {
+ .input_min = 2000000,
+ .input_max = 31000000,
+ .cf_min = 1000000,
+ .cf_max = 6000000,
+ .vco_min = 20000000,
+ .vco_max = 1400000000,
+ .freq_table = tegra_pll_p_freq_table,
+ .lock_delay = 300,
+ },
};
static struct clk tegra_pll_p_out1 = {
.max_rate = 432000000,
};
-static struct clk_pll_table tegra_pll_a_table[] = {
+static struct clk_pll_freq_table tegra_pll_a_freq_table[] = {
{ 28800000, 56448000, 49, 25, 1, 1},
{ 28800000, 73728000, 64, 25, 1, 1},
{ 28800000, 11289600, 49, 25, 1, 1},
.flags = PLL_HAS_CPCON,
.ops = &tegra_pll_ops,
.reg = 0xb0,
- .input_min = 2000000,
- .input_max = 31000000,
.parent = &tegra_pll_p_out1,
- .cf_min = 1000000,
- .cf_max = 6000000,
- .vco_min = 20000000,
- .vco_max = 1400000000,
- .pll_table = tegra_pll_a_table,
.max_rate = 56448000,
+ .u.pll = {
+ .input_min = 2000000,
+ .input_max = 31000000,
+ .cf_min = 1000000,
+ .cf_max = 6000000,
+ .vco_min = 20000000,
+ .vco_max = 1400000000,
+ .freq_table = tegra_pll_a_freq_table,
+ .lock_delay = 300,
+ },
};
static struct clk tegra_pll_a_out0 = {
.max_rate = 56448000,
};
-static struct clk_pll_table tegra_pll_d_table[] = {
+static struct clk_pll_freq_table tegra_pll_d_freq_table[] = {
{ 12000000, 216000000, 216, 12, 1, 4},
{ 13000000, 216000000, 216, 13, 1, 4},
{ 19200000, 216000000, 135, 12, 1, 3},
.flags = PLL_HAS_CPCON | PLLD,
.ops = &tegra_pll_ops,
.reg = 0xd0,
- .input_min = 2000000,
- .input_max = 40000000,
.parent = &tegra_clk_m,
- .cf_min = 1000000,
- .cf_max = 6000000,
- .vco_min = 40000000,
- .vco_max = 1000000000,
- .pll_table = tegra_pll_d_table,
.max_rate = 1000000000,
+ .u.pll = {
+ .input_min = 2000000,
+ .input_max = 40000000,
+ .cf_min = 1000000,
+ .cf_max = 6000000,
+ .vco_min = 40000000,
+ .vco_max = 1000000000,
+ .freq_table = tegra_pll_d_freq_table,
+ .lock_delay = 1000,
+ },
};
static struct clk tegra_pll_d_out0 = {
.max_rate = 500000000,
};
-static struct clk_pll_table tegra_pll_u_table[] = {
+static struct clk_pll_freq_table tegra_pll_u_freq_table[] = {
{ 12000000, 480000000, 960, 12, 2, 0},
{ 13000000, 480000000, 960, 13, 2, 0},
{ 19200000, 480000000, 200, 4, 2, 0},
.flags = PLLU,
.ops = &tegra_pll_ops,
.reg = 0xc0,
- .input_min = 2000000,
- .input_max = 40000000,
.parent = &tegra_clk_m,
- .cf_min = 1000000,
- .cf_max = 6000000,
- .vco_min = 480000000,
- .vco_max = 960000000,
- .pll_table = tegra_pll_u_table,
.max_rate = 480000000,
-};
-
-static struct clk_pll_table tegra_pll_x_table[] = {
+ .u.pll = {
+ .input_min = 2000000,
+ .input_max = 40000000,
+ .cf_min = 1000000,
+ .cf_max = 6000000,
+ .vco_min = 480000000,
+ .vco_max = 960000000,
+ .freq_table = tegra_pll_u_freq_table,
+ .lock_delay = 1000,
+ },
+};
+
+static struct clk_pll_freq_table tegra_pll_x_freq_table[] = {
/* 1 GHz */
{ 12000000, 1000000000, 1000, 12, 1, 12},
{ 13000000, 1000000000, 1000, 13, 1, 12},
.flags = PLL_HAS_CPCON | PLL_ALT_MISC_REG,
.ops = &tegra_pllx_ops,
.reg = 0xe0,
- .input_min = 2000000,
- .input_max = 31000000,
.parent = &tegra_clk_m,
- .cf_min = 1000000,
- .cf_max = 6000000,
- .vco_min = 20000000,
- .vco_max = 1200000000,
- .pll_table = tegra_pll_x_table,
.max_rate = 1000000000,
+ .u.pll = {
+ .input_min = 2000000,
+ .input_max = 31000000,
+ .cf_min = 1000000,
+ .cf_max = 6000000,
+ .vco_min = 20000000,
+ .vco_max = 1200000000,
+ .freq_table = tegra_pll_x_freq_table,
+ .lock_delay = 300,
+ },
};
static struct clk tegra_clk_d = {
.name = "clk_d",
.flags = PERIPH_NO_RESET,
.ops = &tegra_clk_double_ops,
- .clk_num = 90,
.reg = 0x34,
.reg_shift = 12,
.parent = &tegra_clk_m,
.max_rate = 52000000,
+ .u.periph = {
+ .clk_num = 90,
+ },
};
/* dap_mclk1, belongs to the cdev1 pingroup. */
static struct clk tegra_dev1_clk = {
.name = "clk_dev1",
.ops = &tegra_cdev_clk_ops,
- .clk_num = 94,
.rate = 26000000,
.max_rate = 26000000,
+ .u.periph = {
+ .clk_num = 94,
+ },
};
/* dap_mclk2, belongs to the cdev2 pingroup. */
static struct clk tegra_dev2_clk = {
.name = "clk_dev2",
.ops = &tegra_cdev_clk_ops,
- .clk_num = 93,
.rate = 26000000,
.max_rate = 26000000,
+ .u.periph = {
+ .clk_num = 93,
+ },
};
/* initialized before peripheral clocks */
.flags = PERIPH_NO_RESET,
.max_rate = 48000000,
.ops = &tegra_clk_double_ops,
- .clk_num = 89,
.reg = 0x34,
.reg_shift = 8,
.parent = &tegra_clk_audio,
+ .u.periph = {
+ .clk_num = 89,
+ },
};
struct clk_lookup tegra_audio_clk_lookups[] = {
.reg = 0x28,
.ops = &tegra_super_ops,
.max_rate = 240000000,
+ .u.shared_bus = {
+ .min_rate = 120000000,
+ },
};
static struct clk tegra_clk_virtual_cpu = {
.name = "cpu",
.parent = &tegra_clk_cclk,
- .main = &tegra_pll_x,
- .backup = &tegra_pll_p,
.ops = &tegra_cpu_ops,
.max_rate = 1000000000,
- .dvfs = &tegra_dvfs_virtual_cpu_dvfs,
+ .u.cpu = {
+ .main = &tegra_pll_x,
+ .backup = &tegra_pll_p,
+ },
};
static struct clk tegra_clk_cop = {
.con_id = _con, \
}, \
.ops = &tegra_periph_clk_ops, \
- .clk_num = _clk_num, \
.reg = _reg, \
.inputs = _inputs, \
.flags = _flags, \
.max_rate = _max, \
+ .u.periph = { \
+ .clk_num = _clk_num, \
+ }, \
+ }
+
+#define SHARED_CLK(_name, _dev, _con, _parent) \
+ { \
+ .name = _name, \
+ .lookup = { \
+ .dev_id = _dev, \
+ .con_id = _con, \
+ }, \
+ .ops = &tegra_clk_shared_bus_ops, \
+ .parent = _parent, \
}
-struct clk tegra_periph_clks[] = {
+struct clk tegra_list_clks[] = {
PERIPH_CLK("rtc", "rtc-tegra", NULL, 4, 0, 32768, mux_clk_32k, PERIPH_NO_RESET),
PERIPH_CLK("timer", "timer", NULL, 5, 0, 26000000, mux_clk_m, 0),
PERIPH_CLK("i2s1", "i2s.0", NULL, 11, 0x100, 26000000, mux_pllaout0_audio2x_pllp_clkm, MUX | DIV_U71),
PERIPH_CLK("i2s2", "i2s.1", NULL, 18, 0x104, 26000000, mux_pllaout0_audio2x_pllp_clkm, MUX | DIV_U71),
- /* FIXME: spdif has 2 clocks but 1 enable */
PERIPH_CLK("spdif_out", "spdif_out", NULL, 10, 0x108, 100000000, mux_pllaout0_audio2x_pllp_clkm, MUX | DIV_U71),
PERIPH_CLK("spdif_in", "spdif_in", NULL, 10, 0x10c, 100000000, mux_pllp_pllc_pllm, MUX | DIV_U71),
PERIPH_CLK("pwm", "pwm", NULL, 17, 0x110, 432000000, mux_pllp_pllc_audio_clkm_clk32, MUX | DIV_U71),
PERIPH_CLK("sbc4", "spi_tegra.3", NULL, 68, 0x1b4, 160000000, mux_pllp_pllc_pllm_clkm, MUX | DIV_U71),
PERIPH_CLK("ide", "ide", NULL, 25, 0x144, 100000000, mux_pllp_pllc_pllm_clkm, MUX | DIV_U71), /* requires min voltage */
PERIPH_CLK("ndflash", "tegra_nand", NULL, 13, 0x160, 164000000, mux_pllp_pllc_pllm_clkm, MUX | DIV_U71), /* scales with voltage */
- /* FIXME: vfir shares an enable with uartb */
PERIPH_CLK("vfir", "vfir", NULL, 7, 0x168, 72000000, mux_pllp_pllc_pllm_clkm, MUX | DIV_U71),
PERIPH_CLK("sdmmc1", "sdhci-tegra.0", NULL, 14, 0x150, 52000000, mux_pllp_pllc_pllm_clkm, MUX | DIV_U71), /* scales with voltage */
PERIPH_CLK("sdmmc2", "sdhci-tegra.1", NULL, 9, 0x154, 52000000, mux_pllp_pllc_pllm_clkm, MUX | DIV_U71), /* scales with voltage */
PERIPH_CLK("sdmmc3", "sdhci-tegra.2", NULL, 69, 0x1bc, 52000000, mux_pllp_pllc_pllm_clkm, MUX | DIV_U71), /* scales with voltage */
PERIPH_CLK("sdmmc4", "sdhci-tegra.3", NULL, 15, 0x164, 52000000, mux_pllp_pllc_pllm_clkm, MUX | DIV_U71), /* scales with voltage */
+ PERIPH_CLK("vcp", "vcp", NULL, 29, 0, 250000000, mux_clk_m, 0),
+ PERIPH_CLK("bsea", "bsea", NULL, 62, 0, 250000000, mux_clk_m, 0),
PERIPH_CLK("vde", "vde", NULL, 61, 0x1c8, 250000000, mux_pllp_pllc_pllm_clkm, MUX | DIV_U71), /* scales with voltage and process_id */
PERIPH_CLK("csite", "csite", NULL, 73, 0x1d4, 144000000, mux_pllp_pllc_pllm_clkm, MUX | DIV_U71), /* max rate ??? */
/* FIXME: what is la? */
PERIPH_CLK("uarte", "uart.4", NULL, 66, 0x1c4, 600000000, mux_pllp_pllc_pllm_clkm, MUX),
PERIPH_CLK("3d", "3d", NULL, 24, 0x158, 300000000, mux_pllm_pllc_pllp_plla, MUX | DIV_U71 | PERIPH_MANUAL_RESET), /* scales with voltage and process_id */
PERIPH_CLK("2d", "2d", NULL, 21, 0x15c, 300000000, mux_pllm_pllc_pllp_plla, MUX | DIV_U71), /* scales with voltage and process_id */
- /* FIXME: vi and vi_sensor share an enable */
PERIPH_CLK("vi", "tegra_camera", "vi", 20, 0x148, 150000000, mux_pllm_pllc_pllp_plla, MUX | DIV_U71), /* scales with voltage and process_id */
PERIPH_CLK("vi_sensor", "tegra_camera", "vi_sensor", 20, 0x1a8, 150000000, mux_pllm_pllc_pllp_plla, MUX | DIV_U71 | PERIPH_NO_RESET), /* scales with voltage and process_id */
PERIPH_CLK("epp", "epp", NULL, 19, 0x16c, 300000000, mux_pllm_pllc_pllp_plla, MUX | DIV_U71), /* scales with voltage and process_id */
PERIPH_CLK("mpe", "mpe", NULL, 60, 0x170, 250000000, mux_pllm_pllc_pllp_plla, MUX | DIV_U71), /* scales with voltage and process_id */
PERIPH_CLK("host1x", "host1x", NULL, 28, 0x180, 166000000, mux_pllm_pllc_pllp_plla, MUX | DIV_U71), /* scales with voltage and process_id */
- /* FIXME: cve and tvo share an enable */
PERIPH_CLK("cve", "cve", NULL, 49, 0x140, 250000000, mux_pllp_plld_pllc_clkm, MUX | DIV_U71), /* requires min voltage */
PERIPH_CLK("tvo", "tvo", NULL, 49, 0x188, 250000000, mux_pllp_plld_pllc_clkm, MUX | DIV_U71), /* requires min voltage */
PERIPH_CLK("hdmi", "hdmi", NULL, 51, 0x18c, 600000000, mux_pllp_plld_pllc_clkm, MUX | DIV_U71), /* requires min voltage */
PERIPH_CLK("csi", "tegra_camera", "csi", 52, 0, 72000000, mux_pllp_out3, 0),
PERIPH_CLK("isp", "tegra_camera", "isp", 23, 0, 150000000, mux_clk_m, 0), /* same frequency as VI */
PERIPH_CLK("csus", "tegra_camera", "csus", 92, 0, 150000000, mux_clk_m, PERIPH_NO_RESET),
+
+ SHARED_CLK("avp.sclk", "tegra-avp", "sclk", &tegra_clk_sclk),
};
#define CLK_DUPLICATE(_name, _dev, _con) \
CLK_DUPLICATE("pwm", "tegra_pwm.1", NULL),
CLK_DUPLICATE("pwm", "tegra_pwm.2", NULL),
CLK_DUPLICATE("pwm", "tegra_pwm.3", NULL),
+ CLK_DUPLICATE("host1x", "tegra_grhost", "host1x"),
+ CLK_DUPLICATE("2d", "tegra_grhost", "gr2d"),
+ CLK_DUPLICATE("3d", "tegra_grhost", "gr3d"),
+ CLK_DUPLICATE("epp", "tegra_grhost", "epp"),
+ CLK_DUPLICATE("mpe", "tegra_grhost", "mpe"),
+ CLK_DUPLICATE("cop", "tegra-avp", "cop"),
};
#define CLK(dev, con, ck) \
.clk = ck, \
}
-struct clk_lookup tegra_clk_lookups[] = {
- /* external root sources */
- CLK(NULL, "32k_clk", &tegra_clk_32k),
- CLK(NULL, "pll_s", &tegra_pll_s),
- CLK(NULL, "clk_m", &tegra_clk_m),
- CLK(NULL, "pll_m", &tegra_pll_m),
- CLK(NULL, "pll_m_out1", &tegra_pll_m_out1),
- CLK(NULL, "pll_c", &tegra_pll_c),
- CLK(NULL, "pll_c_out1", &tegra_pll_c_out1),
- CLK(NULL, "pll_p", &tegra_pll_p),
- CLK(NULL, "pll_p_out1", &tegra_pll_p_out1),
- CLK(NULL, "pll_p_out2", &tegra_pll_p_out2),
- CLK(NULL, "pll_p_out3", &tegra_pll_p_out3),
- CLK(NULL, "pll_p_out4", &tegra_pll_p_out4),
- CLK(NULL, "pll_a", &tegra_pll_a),
- CLK(NULL, "pll_a_out0", &tegra_pll_a_out0),
- CLK(NULL, "pll_d", &tegra_pll_d),
- CLK(NULL, "pll_d_out0", &tegra_pll_d_out0),
- CLK(NULL, "pll_u", &tegra_pll_u),
- CLK(NULL, "pll_x", &tegra_pll_x),
- CLK(NULL, "cclk", &tegra_clk_cclk),
- CLK(NULL, "sclk", &tegra_clk_sclk),
- CLK(NULL, "hclk", &tegra_clk_hclk),
- CLK(NULL, "pclk", &tegra_clk_pclk),
- CLK(NULL, "clk_d", &tegra_clk_d),
- CLK(NULL, "clk_dev1", &tegra_dev1_clk),
- CLK(NULL, "clk_dev2", &tegra_dev2_clk),
- CLK(NULL, "cpu", &tegra_clk_virtual_cpu),
- CLK(NULL, "blink", &tegra_clk_blink),
- CLK("tegra-avp", "cop", &tegra_clk_cop),
-};
+struct clk *tegra_ptr_clks[] = {
+ &tegra_clk_32k,
+ &tegra_pll_s,
+ &tegra_clk_m,
+ &tegra_pll_m,
+ &tegra_pll_m_out1,
+ &tegra_pll_c,
+ &tegra_pll_c_out1,
+ &tegra_pll_p,
+ &tegra_pll_p_out1,
+ &tegra_pll_p_out2,
+ &tegra_pll_p_out3,
+ &tegra_pll_p_out4,
+ &tegra_pll_a,
+ &tegra_pll_a_out0,
+ &tegra_pll_d,
+ &tegra_pll_d_out0,
+ &tegra_pll_u,
+ &tegra_pll_x,
+ &tegra_clk_cclk,
+ &tegra_clk_sclk,
+ &tegra_clk_hclk,
+ &tegra_clk_pclk,
+ &tegra_clk_d,
+ &tegra_dev1_clk,
+ &tegra_dev2_clk,
+ &tegra_clk_virtual_cpu,
+ &tegra_clk_blink,
+ &tegra_clk_cop,
+};
+
+static void tegra2_init_one_clock(struct clk *c)
+{
+ clk_init(c);
+ if (!c->lookup.dev_id && !c->lookup.con_id)
+ c->lookup.con_id = c->name;
+ c->lookup.clk = c;
+ clkdev_add(&c->lookup);
+}
void __init tegra2_init_clocks(void)
{
int i;
- struct clk_lookup *cl;
struct clk *c;
- struct clk_duplicate *cd;
- for (i = 0; i < ARRAY_SIZE(tegra_clk_lookups); i++) {
- cl = &tegra_clk_lookups[i];
- clk_init(cl->clk);
- clkdev_add(cl);
- }
+ for (i = 0; i < ARRAY_SIZE(tegra_ptr_clks); i++)
+ tegra2_init_one_clock(tegra_ptr_clks[i]);
- for (i = 0; i < ARRAY_SIZE(tegra_periph_clks); i++) {
- c = &tegra_periph_clks[i];
- cl = &c->lookup;
- cl->clk = c;
-
- clk_init(cl->clk);
- clkdev_add(cl);
- }
+ for (i = 0; i < ARRAY_SIZE(tegra_list_clks); i++)
+ tegra2_init_one_clock(&tegra_list_clks[i]);
for (i = 0; i < ARRAY_SIZE(tegra_clk_duplicates); i++) {
- cd = &tegra_clk_duplicates[i];
- c = tegra_get_clock_by_name(cd->name);
- if (c) {
- cl = &cd->lookup;
- cl->clk = c;
- clkdev_add(cl);
- } else {
+ c = tegra_get_clock_by_name(tegra_clk_duplicates[i].name);
+ if (!c) {
pr_err("%s: Unknown duplicate clock %s\n", __func__,
- cd->name);
+ tegra_clk_duplicates[i].name);
+ continue;
}
+
+ tegra_clk_duplicates[i].lookup.clk = c;
+ clkdev_add(&tegra_clk_duplicates[i].lookup);
}
init_audio_sync_clock_mux();
void tegra_clk_suspend(void)
{
unsigned long off, i;
- u32 pllx_misc;
u32 *ctx = clk_rst_suspend;
*ctx++ = clk_readl(OSC_CTRL) & OSC_CTRL_MASK;
*ctx++ = clk_readl(MISC_CLK_ENB);
*ctx++ = clk_readl(CLK_MASK_ARM);
-
- pllx_misc = clk_readl(tegra_pll_x.reg + PLL_MISC(&tegra_pll_x));
- pllx_misc &= ~PLL_MISC_LOCK_ENABLE(&tegra_pll_x);
- clk_writel(pllx_misc, tegra_pll_x.reg + PLL_MISC(&tegra_pll_x));
}
void tegra_clk_resume(void)
*/
#include <linux/kernel.h>
+#include <linux/string.h>
#include "clock.h"
-#include "tegra2_dvfs.h"
-
-static struct dvfs_table virtual_cpu_process_0[] = {
- {314000000, 750},
- {456000000, 825},
- {608000000, 900},
- {760000000, 975},
- {817000000, 1000},
- {912000000, 1050},
- {1000000000, 1100},
- {0, 0},
-};
+#include "dvfs.h"
+#include "fuse.h"
-static struct dvfs_table virtual_cpu_process_1[] = {
- {314000000, 750},
- {456000000, 825},
- {618000000, 900},
- {770000000, 975},
- {827000000, 1000},
- {922000000, 1050},
- {1000000000, 1100},
- {0, 0},
-};
+#define CORE_REGULATOR "vdd_core"
+#define CPU_REGULATOR "vdd_cpu"
-static struct dvfs_table virtual_cpu_process_2[] = {
- {494000000, 750},
- {675000000, 825},
- {817000000, 875},
- {922000000, 925},
- {1000000000, 975},
- {0, 0},
-};
+static const int core_millivolts[MAX_DVFS_FREQS] =
+ {950, 1000, 1100, 1200, 1275};
+static const int cpu_millivolts[MAX_DVFS_FREQS] =
+ {750, 775, 800, 825, 875, 900, 925, 975, 1000, 1050, 1100};
+static int cpu_core_millivolts[MAX_DVFS_FREQS];
-static struct dvfs_table virtual_cpu_process_3[] = {
- {730000000, 750},
- {760000000, 775},
- {845000000, 800},
- {1000000000, 875},
- {0, 0},
-};
+#define CORE_MAX_MILLIVOLTS 1275
+#define CPU_MAX_MILLIVOLTS 1100
+
+#define KHZ 1000
+#define MHZ 1000000
+
+#define CPU_DVFS(_clk_name, _process_id, _mult, _freqs...) \
+ { \
+ .clk_name = _clk_name, \
+ .reg_id = CORE_REGULATOR, \
+ .cpu = false, \
+ .process_id = _process_id, \
+ .freqs = {_freqs}, \
+ .freqs_mult = _mult, \
+ .auto_dvfs = true, \
+ .higher = true, \
+ .max_millivolts = CORE_MAX_MILLIVOLTS \
+ }, \
+ { \
+ .clk_name = _clk_name, \
+ .reg_id = CPU_REGULATOR, \
+ .cpu = true, \
+ .process_id = _process_id, \
+ .freqs = {_freqs}, \
+ .freqs_mult = _mult, \
+ .auto_dvfs = true, \
+ .max_millivolts = CPU_MAX_MILLIVOLTS \
+ }
+
+#define CORE_DVFS(_clk_name, _auto, _mult, _freqs...) \
+ { \
+ .clk_name = _clk_name, \
+ .reg_id = CORE_REGULATOR, \
+ .process_id = -1, \
+ .freqs = {_freqs}, \
+ .freqs_mult = _mult, \
+ .auto_dvfs = _auto, \
+ .max_millivolts = CORE_MAX_MILLIVOLTS \
+ }
+
+static struct dvfs dvfs_init[] = {
+ /* Cpu voltages (mV): 750, 775, 800, 825, 875, 900, 925, 975, 1000, 1050, 1100 */
+ CPU_DVFS("cpu", 0, MHZ, 314, 314, 314, 456, 456, 608, 608, 760, 817, 912, 1000),
+ CPU_DVFS("cpu", 1, MHZ, 314, 314, 314, 456, 456, 618, 618, 770, 827, 922, 1000),
+ CPU_DVFS("cpu", 2, MHZ, 494, 675, 675, 675, 817, 817, 922, 1000),
+ CPU_DVFS("cpu", 3, MHZ, 730, 760, 845, 845, 1000),
+
+ /* Core voltages (mV): 950, 1000, 1100, 1200, 1275 */
+ CORE_DVFS("sdmmc1", 1, KHZ, 44000, 52000, 52000, 52000, 52000),
+ CORE_DVFS("sdmmc2", 1, KHZ, 44000, 52000, 52000, 52000, 52000),
+ CORE_DVFS("sdmmc3", 1, KHZ, 44000, 52000, 52000, 52000, 52000),
+ CORE_DVFS("sdmmc4", 1, KHZ, 44000, 52000, 52000, 52000, 52000),
+ CORE_DVFS("ndflash", 1, KHZ, 130000, 150000, 158000, 164000, 164000),
+ CORE_DVFS("nor", 1, KHZ, 0, 92000, 92000, 92000, 92000),
+ CORE_DVFS("ide", 1, KHZ, 0, 0, 100000, 100000, 100000),
+ CORE_DVFS("mipi", 1, KHZ, 0, 40000, 40000, 40000, 60000),
+ CORE_DVFS("usbd", 1, KHZ, 0, 0, 480000, 480000, 480000),
+ CORE_DVFS("usb2", 1, KHZ, 0, 0, 480000, 480000, 480000),
+ CORE_DVFS("usb3", 1, KHZ, 0, 0, 480000, 480000, 480000),
+ CORE_DVFS("pcie", 1, KHZ, 0, 0, 0, 250000, 250000),
+ CORE_DVFS("dsi", 1, KHZ, 100000, 100000, 100000, 500000, 500000),
+ CORE_DVFS("tvo", 1, KHZ, 0, 0, 0, 250000, 250000),
+ CORE_DVFS("hdmi", 1, KHZ, 0, 0, 0, 148500, 148500),
+
+ /*
+ * The clock rate for the display controllers that determines the
+ * necessary core voltage depends on a divider that is internal
+ * to the display block. Disable auto-dvfs on the display clocks,
+ * and let the display driver call tegra_dvfs_set_rate manually
+ */
+ CORE_DVFS("disp1", 0, KHZ, 158000, 158000, 190000, 190000, 190000),
+ CORE_DVFS("disp2", 0, KHZ, 158000, 158000, 190000, 190000, 190000),
-struct dvfs tegra_dvfs_virtual_cpu_dvfs = {
- .reg_id = "vdd_cpu",
- .process_id_table = {
- {
- .process_id = 0,
- .table = virtual_cpu_process_0,
- },
- {
- .process_id = 1,
- .table = virtual_cpu_process_1,
- },
- {
- .process_id = 2,
- .table = virtual_cpu_process_2,
- },
- {
- .process_id = 3,
- .table = virtual_cpu_process_3,
- },
- },
- .process_id_table_length = 4,
- .cpu = 1,
+ /*
+ * These clocks technically depend on the core process id,
+ * but just use the worst case value for now
+ */
+ CORE_DVFS("host1x", 1, KHZ, 104500, 133000, 166000, 166000, 166000),
+ CORE_DVFS("epp", 1, KHZ, 133000, 171000, 247000, 300000, 300000),
+ CORE_DVFS("2d", 1, KHZ, 133000, 171000, 247000, 300000, 300000),
+ CORE_DVFS("3d", 1, KHZ, 114000, 161500, 247000, 300000, 300000),
+ CORE_DVFS("mpe", 1, KHZ, 104500, 152000, 228000, 250000, 250000),
+ CORE_DVFS("vi", 1, KHZ, 85000, 100000, 150000, 150000, 150000),
+ CORE_DVFS("sclk", 1, KHZ, 95000, 133000, 190000, 250000, 250000),
+ CORE_DVFS("vde", 1, KHZ, 95000, 123500, 209000, 250000, 250000),
+ /* What is this? */
+ CORE_DVFS("NVRM_DEVID_CLK_SRC", 1, MHZ, 480, 600, 800, 1067, 1067),
};
+
+void tegra2_init_dvfs(void)
+{
+ int i;
+ struct clk *c;
+ struct dvfs *d;
+ int process_id;
+ int ret;
+
+ int cpu_process_id = tegra_cpu_process_id();
+ int core_process_id = tegra_core_process_id();
+
+ /*
+ * VDD_CORE must always be at least 50 mV higher than VDD_CPU
+ * Fill out cpu_core_millivolts based on cpu_millivolts
+ */
+ for (i = 0; i < ARRAY_SIZE(cpu_millivolts); i++)
+ if (cpu_millivolts[i])
+ cpu_core_millivolts[i] = cpu_millivolts[i] + 50;
+
+ for (i = 0; i < ARRAY_SIZE(dvfs_init); i++) {
+ d = &dvfs_init[i];
+
+ process_id = d->cpu ? cpu_process_id : core_process_id;
+ if (d->process_id != -1 && d->process_id != process_id) {
+ pr_debug("tegra_dvfs: rejected %s %d, process_id %d\n",
+ d->clk_name, d->process_id, process_id);
+ continue;
+ }
+
+ c = tegra_get_clock_by_name(d->clk_name);
+
+ if (!c) {
+ pr_debug("tegra_dvfs: no clock found for %s\n",
+ d->clk_name);
+ continue;
+ }
+
+ if (d->cpu)
+ memcpy(d->millivolts, cpu_millivolts,
+ sizeof(cpu_millivolts));
+ else if (!strcmp(d->clk_name, "cpu"))
+ memcpy(d->millivolts, cpu_core_millivolts,
+ sizeof(cpu_core_millivolts));
+ else
+ memcpy(d->millivolts, core_millivolts,
+ sizeof(core_millivolts));
+
+ ret = tegra_enable_dvfs_on_clk(c, d);
+ if (ret)
+ pr_err("tegra_dvfs: failed to enable dvfs on %s\n",
+ c->name);
+ }
+}
+++ /dev/null
-/*
- * arch/arm/mach-tegra/tegra2_dvfs.h
- *
- * Copyright (C) 2010 Google, Inc.
- *
- * Author:
- * Colin Cross <ccross@google.com>
- *
- * This software is licensed under the terms of the GNU General Public
- * License version 2, as published by the Free Software Foundation, and
- * may be copied, distributed, and modified under those terms.
- *
- * 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.
- *
- */
-
-extern struct dvfs tegra_dvfs_virtual_cpu_dvfs;
#include "board.h"
#include "clock.h"
+#include "power.h"
#define RTC_SECONDS 0x08
#define RTC_SHADOW_SECONDS 0x0c
void tegra_clocksource_us_suspend(struct clocksource *cs)
{
- tegra_us_resume_offset = tegra_clocksource_us_read(cs);
+ tegra_us_resume_offset = tegra_clocksource_us_read(cs) -
+ tegra_rtc_read_ms() * 1000;
}
void tegra_clocksource_us_resume(struct clocksource *cs)
{
- tegra_us_clocksource_offset = tegra_us_resume_offset;
-}
-
-static cycle_t tegra_clocksource_32k_read(struct clocksource *cs)
-{
- u32 ms = readl(rtc_base + RTC_MILLISECONDS);
- u32 s = readl(rtc_base + RTC_SHADOW_SECONDS);
- return (u64)s * 1000 + ms;
+ tegra_us_clocksource_offset += tegra_us_resume_offset +
+ tegra_rtc_read_ms() * 1000 -
+ tegra_clocksource_us_read(cs);
}
static struct clock_event_device tegra_clockevent = {
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
};
-static struct clocksource tegra_clocksource_32k = {
- .name = "rtc_32k",
- .rating = 100,
- .read = tegra_clocksource_32k_read,
- .mask = 0x7FFFFFFFFFFFFFFFULL,
- .flags = CLOCK_SOURCE_IS_CONTINUOUS,
-};
-
unsigned long long sched_clock(void)
{
return tegra_clocksource_us.read(&tegra_clocksource_us) * 1000;
}
-/**
+
+/*
+ * tegra_rtc_read - Reads the Tegra RTC registers
+ * Care must be taken that this funciton is not called while the
+ * tegra_rtc driver could be executing to avoid race conditions
+ * on the RTC shadow register
+ */
+u64 tegra_rtc_read_ms(void)
+{
+ u32 ms = readl(rtc_base + RTC_MILLISECONDS);
+ u32 s = readl(rtc_base + RTC_SHADOW_SECONDS);
+ return (u64)s * 1000 + ms;
+}
+
+/*
* read_persistent_clock - Return time from a persistent clock.
*
* Reads the time from a source which isn't disabled during PM, the
* 32k sync timer. Convert the cycles elapsed since last read into
* nsecs and adds to a monotonically increasing timespec.
+ * Care must be taken that this funciton is not called while the
+ * tegra_rtc driver could be executing to avoid race conditions
+ * on the RTC shadow register
*/
static struct timespec persistent_ts;
-static cycles_t cycles, last_cycles;
+static u64 persistent_ms, last_persistent_ms;
void read_persistent_clock(struct timespec *ts)
{
- unsigned long long nsecs;
- cycles_t delta;
+ u64 delta;
struct timespec *tsp = &persistent_ts;
- last_cycles = cycles;
- cycles = tegra_clocksource_32k.read(&tegra_clocksource_32k);
- delta = cycles - last_cycles;
-
- nsecs = clocksource_cyc2ns(delta,
- tegra_clocksource_32k.mult,
- tegra_clocksource_32k.shift);
+ last_persistent_ms = persistent_ms;
+ persistent_ms = tegra_rtc_read_ms();
+ delta = persistent_ms - last_persistent_ms;
- timespec_add_ns(tsp, nsecs);
+ timespec_add_ns(tsp, delta * 1000000);
*ts = *tsp;
}
BUG();
}
- if (clocksource_register_hz(&tegra_clocksource_32k, 1000)) {
- printk(KERN_ERR "Failed to register 32k clocksource\n");
- BUG();
- }
-
ret = setup_irq(tegra_timer_irq.irq, &tegra_timer_irq);
if (ret) {
printk(KERN_ERR "Failed to register timer IRQ: %d\n", ret);
}
}
+static unsigned long tegra_dc_pclk_round_rate(struct tegra_dc *dc, int pclk)
+{
+ unsigned long rate;
+ unsigned long div;
+
+ rate = clk_get_rate(dc->clk);
+
+ div = DIV_ROUND_CLOSEST(rate * 2, pclk);
+
+ if (div < 2)
+ return 0;
+
+ return rate * 2 / div;
+}
+
static int tegra_dc_program_mode(struct tegra_dc *dc, struct tegra_dc_mode *mode)
{
unsigned long val;
unsigned long rate;
unsigned long div;
+ unsigned long pclk;
tegra_dc_writel(dc, 0x0, DC_DISP_DISP_TIMING_OPTIONS);
tegra_dc_writel(dc, mode->h_ref_to_sync | (mode->v_ref_to_sync << 16),
rate = clk_get_rate(dc->clk);
- div = ((rate * 2 + mode->pclk / 2) / mode->pclk) - 2;
-
- if (rate * 2 / (div + 2) < (mode->pclk / 100 * 99) ||
- rate * 2 / (div + 2) > (mode->pclk / 100 * 109)) {
+ pclk = tegra_dc_pclk_round_rate(dc, mode->pclk);
+ if (pclk < (mode->pclk / 100 * 99) ||
+ pclk > (mode->pclk / 100 * 109)) {
dev_err(&dc->ndev->dev,
"can't divide %ld clock to %d -1/+9%% %ld %d %d\n",
rate, mode->pclk,
- rate / div, (mode->pclk / 100 * 99),
+ pclk, (mode->pclk / 100 * 99),
(mode->pclk / 100 * 109));
return -EINVAL;
}
+ div = (rate * 2 / pclk) - 2;
+
tegra_dc_writel(dc, 0x00010001,
DC_DISP_SHIFT_CLOCK_OPTIONS);
tegra_dc_writel(dc, PIXEL_CLK_DIVIDER_PCD1 | SHIFT_CLK_DIVIDER(div),
static bool _tegra_dc_enable(struct tegra_dc *dc)
{
+ int pclk;
+
if (dc->mode.pclk == 0)
return false;
tegra_dc_setup_clk(dc, dc->clk);
+ pclk = tegra_dc_pclk_round_rate(dc, dc->mode.pclk);
+ tegra_dvfs_set_rate(dc->clk, pclk);
+
clk_enable(dc->clk);
enable_irq(dc->irq);
disable_irq(dc->irq);
clk_disable(dc->clk);
+ tegra_dvfs_set_rate(dc->clk, 0);
if (dc->out && dc->out->disable)
dc->out->disable();