msm: clock-krait: Clock ops library for Krait subsystem clocks
The Krait subsystem has its own clock tree. Implement a clock-krait library
that provides a set of clock ops that model all the clock components
present in the Krait subsystem clock tree.
Change-Id: I41bb4243c28cd666ebe8e7c5a814c85401d92308
Signed-off-by: Saravana Kannan <skannan@codeaurora.org>
diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile
index cd71104..e4a647f 100644
--- a/arch/arm/mach-msm/Makefile
+++ b/arch/arm/mach-msm/Makefile
@@ -24,7 +24,7 @@
obj-y += acpuclock.o
obj-$(CONFIG_HW_PERF_EVENTS) += perf_trace_counters.o
-obj-$(CONFIG_ARCH_MSM_KRAIT) += acpuclock-krait.o
+obj-$(CONFIG_ARCH_MSM_KRAIT) += acpuclock-krait.o clock-krait.o
ifdef CONFIG_ARCH_MSM_KRAIT
obj-$(CONFIG_DEBUG_FS) += acpuclock-krait-debug.o
endif
diff --git a/arch/arm/mach-msm/clock-krait.c b/arch/arm/mach-msm/clock-krait.c
new file mode 100644
index 0000000..b96ba62
--- /dev/null
+++ b/arch/arm/mach-msm/clock-krait.c
@@ -0,0 +1,521 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+
+#include <linux/clk.h>
+#include <mach/clk-provider.h>
+#include <mach/clk.h>
+#include <mach/clock-generic.h>
+#include <mach/msm-krait-l2-accessors.h>
+#include "clock-krait.h"
+#include "avs.h"
+
+static DEFINE_SPINLOCK(kpss_clock_reg_lock);
+
+static void __kpss_mux_set_sel(struct mux_clk *mux, int sel)
+{
+ unsigned long flags;
+ u32 regval;
+
+ spin_lock_irqsave(&kpss_clock_reg_lock, flags);
+ regval = get_l2_indirect_reg(mux->offset);
+ regval &= ~(mux->mask << mux->shift);
+ regval |= (sel & mux->mask) << mux->shift;
+ set_l2_indirect_reg(mux->offset, regval);
+ spin_unlock_irqrestore(&kpss_clock_reg_lock, flags);
+
+ /* Wait for switch to complete. */
+ mb();
+ udelay(1);
+}
+static int kpss_mux_set_sel(struct mux_clk *mux, int sel)
+{
+ mux->en_mask = sel;
+ if (mux->c.count)
+ __kpss_mux_set_sel(mux, sel);
+ return 0;
+}
+
+static int kpss_mux_get_sel(struct mux_clk *mux)
+{
+ u32 sel;
+
+ sel = get_l2_indirect_reg(mux->offset);
+ sel >>= mux->shift;
+ sel &= mux->mask;
+ mux->en_mask = sel;
+
+ return sel;
+}
+
+static int kpss_mux_enable(struct mux_clk *mux)
+{
+ __kpss_mux_set_sel(mux, mux->en_mask);
+ return 0;
+}
+
+static void kpss_mux_disable(struct mux_clk *mux)
+{
+ __kpss_mux_set_sel(mux, mux->safe_sel);
+}
+
+struct clk_mux_ops clk_mux_ops_kpss = {
+ .enable = kpss_mux_enable,
+ .disable = kpss_mux_disable,
+ .set_mux_sel = kpss_mux_set_sel,
+ .get_mux_sel = kpss_mux_get_sel,
+};
+
+/*
+ * The divider can divide by 2, 4, 6 and 8. But we only really need div-2. So
+ * force it to div-2 during handoff and treat it like a fixed div-2 clock.
+ */
+static int kpss_div2_get_div(struct div_clk *div)
+{
+ unsigned long flags;
+ u32 regval;
+ int val;
+
+ spin_lock_irqsave(&kpss_clock_reg_lock, flags);
+ regval = get_l2_indirect_reg(div->offset);
+ val = (regval >> div->shift) && div->mask;
+ regval &= ~(div->mask << div->shift);
+ set_l2_indirect_reg(div->offset, regval);
+ spin_unlock_irqrestore(&kpss_clock_reg_lock, flags);
+
+ val = (val + 1) * 2;
+ WARN(val != 2, "Divider %s was configured to div-%d instead of 2!\n",
+ div->c.dbg_name, val);
+
+ return 2;
+}
+
+struct clk_div_ops clk_div_ops_kpss_div2 = {
+ .get_div = kpss_div2_get_div,
+};
+
+#define LOCK_BIT BIT(16)
+
+/* Initialize a HFPLL at a given rate and enable it. */
+static void __hfpll_clk_init_once(struct clk *c)
+{
+ struct hfpll_clk *h = to_hfpll_clk(c);
+ struct hfpll_data const *hd = h->d;
+
+ if (likely(h->init_done))
+ return;
+
+ /* Configure PLL parameters for integer mode. */
+ writel_relaxed(hd->config_val, h->base + hd->config_offset);
+ writel_relaxed(0, h->base + hd->m_offset);
+ writel_relaxed(1, h->base + hd->n_offset);
+
+ if (hd->user_offset) {
+ u32 regval = hd->user_val;
+ unsigned long rate;
+
+ rate = readl_relaxed(h->base + hd->l_offset) * h->src_rate;
+
+ /* Pick the right VCO. */
+ if (rate > hd->low_vco_max_rate)
+ regval |= hd->user_vco_mask;
+ writel_relaxed(regval, h->base + hd->user_offset);
+ }
+
+ if (hd->droop_offset)
+ writel_relaxed(hd->droop_val, h->base + hd->droop_offset);
+
+ h->init_done = true;
+}
+
+/* Enable an already-configured HFPLL. */
+static int hfpll_clk_enable(struct clk *c)
+{
+ struct hfpll_clk *h = to_hfpll_clk(c);
+ struct hfpll_data const *hd = h->d;
+
+ if (!h->base)
+ return -ENODEV;
+
+ __hfpll_clk_init_once(c);
+
+ /* Disable PLL bypass mode. */
+ writel_relaxed(0x2, h->base + hd->mode_offset);
+
+ /*
+ * H/W requires a 5us delay between disabling the bypass and
+ * de-asserting the reset. Delay 10us just to be safe.
+ */
+ mb();
+ udelay(10);
+
+ /* De-assert active-low PLL reset. */
+ writel_relaxed(0x6, h->base + hd->mode_offset);
+
+ /* Wait for PLL to lock. */
+ if (hd->status_offset) {
+ while (!(readl_relaxed(h->base + hd->status_offset) & LOCK_BIT))
+ ;
+ } else {
+ mb();
+ udelay(60);
+ }
+
+ /* Enable PLL output. */
+ writel_relaxed(0x7, h->base + hd->mode_offset);
+
+ /* Make sure the enable is done before returning. */
+ mb();
+
+ return 0;
+}
+
+static void hfpll_clk_disable(struct clk *c)
+{
+ struct hfpll_clk *h = to_hfpll_clk(c);
+ struct hfpll_data const *hd = h->d;
+
+ /*
+ * Disable the PLL output, disable test mode, enable the bypass mode,
+ * and assert the reset.
+ */
+ writel_relaxed(0, h->base + hd->mode_offset);
+}
+
+static long hfpll_clk_round_rate(struct clk *c, unsigned long rate)
+{
+ struct hfpll_clk *h = to_hfpll_clk(c);
+ struct hfpll_data const *hd = h->d;
+ unsigned long rrate;
+
+ if (!h->src_rate)
+ return 0;
+
+ rate = max(rate, hd->min_rate);
+ rate = min(rate, hd->max_rate);
+
+ rrate = DIV_ROUND_UP(rate, h->src_rate) * h->src_rate;
+ if (rrate > hd->max_rate)
+ rrate -= h->src_rate;
+
+ return rrate;
+}
+
+/*
+ * For optimization reasons, assumes no downstream clocks are actively using
+ * it.
+ */
+static int hfpll_clk_set_rate(struct clk *c, unsigned long rate)
+{
+ struct hfpll_clk *h = to_hfpll_clk(c);
+ struct hfpll_data const *hd = h->d;
+ unsigned long flags;
+ u32 l_val;
+
+ if (!h->base)
+ return -ENODEV;
+
+ if (rate != hfpll_clk_round_rate(c, rate))
+ return -EINVAL;
+
+ l_val = rate / h->src_rate;
+
+ spin_lock_irqsave(&c->lock, flags);
+
+ if (c->count)
+ hfpll_clk_disable(c);
+
+ /* Pick the right VCO. */
+ if (hd->user_offset) {
+ u32 regval;
+ regval = readl_relaxed(h->base + hd->user_offset);
+ if (rate <= hd->low_vco_max_rate)
+ regval &= ~hd->user_vco_mask;
+ else
+ regval |= hd->user_vco_mask;
+ writel_relaxed(regval, h->base + hd->user_offset);
+ }
+
+ writel_relaxed(l_val, h->base + hd->l_offset);
+
+ if (c->count)
+ hfpll_clk_enable(c);
+
+ spin_unlock_irqrestore(&c->lock, flags);
+
+ return 0;
+}
+
+static enum handoff hfpll_clk_handoff(struct clk *c)
+{
+ struct hfpll_clk *h = to_hfpll_clk(c);
+ struct hfpll_data const *hd = h->d;
+ u32 l_val, mode;
+
+ if (!hd)
+ return HANDOFF_DISABLED_CLK;
+
+ if (!h->base)
+ return HANDOFF_DISABLED_CLK;
+
+ /* Assume parent rate doesn't change and cache it. */
+ h->src_rate = clk_get_rate(c->parent);
+ l_val = readl_relaxed(h->base + hd->l_offset);
+ c->rate = l_val * h->src_rate;
+
+ mode = readl_relaxed(h->base + hd->mode_offset) & 0x7;
+ if (mode != 0x7) {
+ __hfpll_clk_init_once(c);
+ return HANDOFF_DISABLED_CLK;
+ }
+
+ if (hd->status_offset &&
+ !(readl_relaxed(h->base + hd->status_offset) & LOCK_BIT)) {
+ WARN(1, "HFPLL %s is ON, but not locked!\n", c->dbg_name);
+ hfpll_clk_disable(c);
+ __hfpll_clk_init_once(c);
+ return HANDOFF_DISABLED_CLK;
+ }
+
+ WARN(c->rate < hd->min_rate || c->rate > hd->max_rate,
+ "HFPLL %s rate %lu outside spec!\n", c->dbg_name, c->rate);
+
+ return HANDOFF_ENABLED_CLK;
+}
+
+struct clk_ops clk_ops_hfpll = {
+ .enable = hfpll_clk_enable,
+ .disable = hfpll_clk_disable,
+ .round_rate = hfpll_clk_round_rate,
+ .set_rate = hfpll_clk_set_rate,
+ .handoff = hfpll_clk_handoff,
+};
+
+struct cpu_hwcg_action {
+ bool read;
+ bool enable;
+};
+
+static void cpu_hwcg_rw(void *info)
+{
+ struct cpu_hwcg_action *action = info;
+
+ u32 val;
+ asm volatile ("mrc p15, 7, %[cpmr0], c15, c0, 5\n\t"
+ : [cpmr0]"=r" (val));
+
+ if (action->read) {
+ action->enable = !(val & BIT(0));
+ return;
+ }
+
+ if (action->enable)
+ val &= ~BIT(0);
+ else
+ val |= BIT(0);
+
+ asm volatile ("mcr p15, 7, %[cpmr0], c15, c0, 5\n\t"
+ : : [cpmr0]"r" (val));
+}
+
+static void kpss_cpu_enable_hwcg(struct clk *c)
+{
+ struct kpss_core_clk *cpu = to_kpss_core_clk(c);
+ struct cpu_hwcg_action action = { .enable = true };
+
+ smp_call_function_single(cpu->id, cpu_hwcg_rw, &action, 1);
+}
+
+static void kpss_cpu_disable_hwcg(struct clk *c)
+{
+ struct kpss_core_clk *cpu = to_kpss_core_clk(c);
+ struct cpu_hwcg_action action = { .enable = false };
+
+ smp_call_function_single(cpu->id, cpu_hwcg_rw, &action, 1);
+}
+
+static int kpss_cpu_in_hwcg_mode(struct clk *c)
+{
+ struct kpss_core_clk *cpu = to_kpss_core_clk(c);
+ struct cpu_hwcg_action action = { .read = true };
+
+ smp_call_function_single(cpu->id, cpu_hwcg_rw, &action, 1);
+ return action.enable;
+}
+
+static enum handoff kpss_cpu_handoff(struct clk *c)
+{
+ struct kpss_core_clk *cpu = to_kpss_core_clk(c);
+
+ c->rate = clk_get_rate(c->parent);
+
+ /*
+ * Don't unnecessarily turn on the parents for an offline CPU and
+ * then have them turned off at late init.
+ */
+ return (cpu_online(cpu->id) ?
+ HANDOFF_ENABLED_CLK : HANDOFF_DISABLED_CLK);
+}
+
+u32 find_dscr(struct avs_data *t, unsigned long rate)
+{
+ int i;
+
+ if (!t)
+ return 0;
+
+ for (i = 0; i < t->num; i++) {
+ if (t->rate[i] == rate)
+ return t->dscr[i];
+ }
+
+ return 0;
+}
+
+static int kpss_cpu_pre_set_rate(struct clk *c, unsigned long new_rate)
+{
+ struct kpss_core_clk *cpu = to_kpss_core_clk(c);
+ u32 dscr = find_dscr(cpu->avs_tbl, c->rate);
+
+ if (!c->prepare_count)
+ return -ENODEV;
+
+ if (dscr)
+ AVS_DISABLE(cpu->id);
+ return 0;
+}
+
+static long kpss_core_round_rate(struct clk *c, unsigned long rate)
+{
+ if (c->fmax && c->num_fmax)
+ rate = min(rate, c->fmax[c->num_fmax-1]);
+
+ return clk_round_rate(c->parent, rate);
+}
+
+static int kpss_core_set_rate(struct clk *c, unsigned long rate)
+{
+ if (!c->prepare_count)
+ return -ENODEV;
+
+ return clk_set_rate(c->parent, rate);
+}
+
+static void kpss_cpu_post_set_rate(struct clk *c, unsigned long old_rate)
+{
+ struct kpss_core_clk *cpu = to_kpss_core_clk(c);
+ u32 dscr = find_dscr(cpu->avs_tbl, c->rate);
+
+ /*
+ * FIXME: If AVS enable/disable needs to be done in the
+ * enable/disable op to correctly handle power collapse, then might
+ * need to grab the spinlock here.
+ */
+ if (dscr)
+ AVS_ENABLE(cpu->id, dscr);
+}
+
+static unsigned long kpss_core_get_rate(struct clk *c)
+{
+ return clk_get_rate(c->parent);
+}
+
+static long kpss_core_list_rate(struct clk *c, unsigned n)
+{
+ if (!c->fmax || c->num_fmax <= n)
+ return -ENXIO;
+
+ return c->fmax[n];
+}
+
+struct clk_ops clk_ops_kpss_cpu = {
+ .enable_hwcg = kpss_cpu_enable_hwcg,
+ .disable_hwcg = kpss_cpu_disable_hwcg,
+ .in_hwcg_mode = kpss_cpu_in_hwcg_mode,
+ .pre_set_rate = kpss_cpu_pre_set_rate,
+ .round_rate = kpss_core_round_rate,
+ .set_rate = kpss_core_set_rate,
+ .post_set_rate = kpss_cpu_post_set_rate,
+ .get_rate = kpss_core_get_rate,
+ .list_rate = kpss_core_list_rate,
+ .handoff = kpss_cpu_handoff,
+};
+
+#define SLPDLY_SHIFT 10
+#define SLPDLY_MASK 0x3
+static void kpss_l2_enable_hwcg(struct clk *c)
+{
+ struct kpss_core_clk *l2 = to_kpss_core_clk(c);
+ u32 regval;
+ unsigned long flags;
+
+ spin_lock_irqsave(&kpss_clock_reg_lock, flags);
+ regval = get_l2_indirect_reg(l2->cp15_iaddr);
+ regval &= ~(SLPDLY_MASK << SLPDLY_SHIFT);
+ regval |= l2->l2_slp_delay;
+ set_l2_indirect_reg(l2->cp15_iaddr, regval);
+ spin_unlock_irqrestore(&kpss_clock_reg_lock, flags);
+}
+
+static void kpss_l2_disable_hwcg(struct clk *c)
+{
+ struct kpss_core_clk *l2 = to_kpss_core_clk(c);
+ u32 regval;
+ unsigned long flags;
+
+ /*
+ * NOTE: Should not be called when HW clock gating is already
+ * disabled.
+ */
+ spin_lock_irqsave(&kpss_clock_reg_lock, flags);
+ regval = get_l2_indirect_reg(l2->cp15_iaddr);
+ l2->l2_slp_delay = regval & (SLPDLY_MASK << SLPDLY_SHIFT);
+ regval |= (SLPDLY_MASK << SLPDLY_SHIFT);
+ set_l2_indirect_reg(l2->cp15_iaddr, regval);
+ spin_unlock_irqrestore(&kpss_clock_reg_lock, flags);
+}
+
+static int kpss_l2_in_hwcg_mode(struct clk *c)
+{
+ struct kpss_core_clk *l2 = to_kpss_core_clk(c);
+ u32 regval;
+
+ regval = get_l2_indirect_reg(l2->cp15_iaddr);
+ regval >>= SLPDLY_SHIFT;
+ regval &= SLPDLY_MASK;
+ return (regval != SLPDLY_MASK);
+}
+
+static enum handoff kpss_l2_handoff(struct clk *c)
+{
+ c->rate = clk_get_rate(c->parent);
+ return HANDOFF_ENABLED_CLK;
+}
+
+struct clk_ops clk_ops_kpss_l2 = {
+ .enable_hwcg = kpss_l2_enable_hwcg,
+ .disable_hwcg = kpss_l2_disable_hwcg,
+ .in_hwcg_mode = kpss_l2_in_hwcg_mode,
+ .round_rate = kpss_core_round_rate,
+ .set_rate = kpss_core_set_rate,
+ .get_rate = kpss_core_get_rate,
+ .list_rate = kpss_core_list_rate,
+ .handoff = kpss_l2_handoff,
+};
diff --git a/arch/arm/mach-msm/clock-krait.h b/arch/arm/mach-msm/clock-krait.h
new file mode 100644
index 0000000..2691a8c
--- /dev/null
+++ b/arch/arm/mach-msm/clock-krait.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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 __ARCH_ARM_MACH_MSM_CLOCK_KRAIT_H
+#define __ARCH_ARM_MACH_MSM_CLOCK_KRAIT_H
+
+#include <mach/clk-provider.h>
+#include <mach/clock-generic.h>
+
+extern struct clk_mux_ops clk_mux_ops_kpss;
+extern struct clk_div_ops clk_div_ops_kpss_div2;
+
+#define DEFINE_KPSS_DIV2_CLK(clk_name, _parent, _offset) \
+static struct div_clk clk_name = { \
+ .div = 2, \
+ .min_div = 2, \
+ .max_div = 2, \
+ .ops = &clk_div_ops_kpss_div2, \
+ .offset = _offset, \
+ .mask = 0x3, \
+ .shift = 6, \
+ .c = { \
+ .parent = _parent, \
+ .dbg_name = #clk_name, \
+ .ops = &clk_ops_div, \
+ .flags = CLKFLAG_NO_RATE_CACHE, \
+ CLK_INIT(clk_name.c), \
+ } \
+}
+
+struct hfpll_data {
+ const u32 mode_offset;
+ const u32 l_offset;
+ const u32 m_offset;
+ const u32 n_offset;
+ const u32 user_offset;
+ const u32 droop_offset;
+ const u32 config_offset;
+ const u32 status_offset;
+
+ const u32 droop_val;
+ const u32 config_val;
+ const u32 user_val;
+ const u32 user_vco_mask;
+ unsigned long low_vco_max_rate;
+
+ unsigned long min_rate;
+ unsigned long max_rate;
+};
+
+struct hfpll_clk {
+ void * __iomem base;
+ struct hfpll_data const *d;
+ unsigned long src_rate;
+ int init_done;
+
+ struct clk c;
+};
+
+static inline struct hfpll_clk *to_hfpll_clk(struct clk *c)
+{
+ return container_of(c, struct hfpll_clk, c);
+}
+
+extern struct clk_ops clk_ops_hfpll;
+
+struct avs_data {
+ unsigned long *rate;
+ u32 *dscr;
+ int num;
+};
+
+struct kpss_core_clk {
+ int id;
+ u32 cp15_iaddr;
+ u32 l2_slp_delay;
+ struct avs_data *avs_tbl;
+ struct clk c;
+};
+
+static inline struct kpss_core_clk *to_kpss_core_clk(struct clk *c)
+{
+ return container_of(c, struct kpss_core_clk, c);
+}
+
+extern struct clk_ops clk_ops_kpss_cpu;
+extern struct clk_ops clk_ops_kpss_l2;
+
+#endif