mirror of https://github.com/hak5/openwrt.git
ipq806x: add & enable cpufreq support
This change set enables frequency scaling on ipq806x, which speeds-up the CPU and allows it to achieve its max frequency. These patches are cherry-picked & backported from the following location: *130-132: linux-next *133-143: LKML - https://lkml.org/lkml/2015/3/21/15 *144: derived from other qcom similar dts *145: derived from https://chromium.googlesource.com/chromiumos/third_party/kernel/+/chromeos-3.14/drivers/cpufreq/cpufreq-krait.c Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org> SVN-Revision: 45730lede-17.01
parent
0b24527e4d
commit
8e49b4b902
|
@ -40,10 +40,12 @@ CONFIG_ARM_ARCH_TIMER_EVTSTREAM=y
|
|||
CONFIG_ARM_CPU_SUSPEND=y
|
||||
CONFIG_ARM_GIC=y
|
||||
CONFIG_ARM_HAS_SG_CHAIN=y
|
||||
# CONFIG_ARM_KIRKWOOD_CPUFREQ is not set
|
||||
CONFIG_ARM_L1_CACHE_SHIFT=6
|
||||
CONFIG_ARM_L1_CACHE_SHIFT_6=y
|
||||
# CONFIG_ARM_LPAE is not set
|
||||
CONFIG_ARM_PATCH_PHYS_VIRT=y
|
||||
CONFIG_ARM_QCOM_CPUFREQ=y
|
||||
# CONFIG_ARM_SP805_WATCHDOG is not set
|
||||
CONFIG_ARM_THUMB=y
|
||||
# CONFIG_ARM_THUMBEE is not set
|
||||
|
@ -64,6 +66,7 @@ CONFIG_COMMON_CLK=y
|
|||
CONFIG_COMMON_CLK_QCOM=y
|
||||
CONFIG_COMPACTION=y
|
||||
CONFIG_COREDUMP=y
|
||||
# CONFIG_CPUFREQ_DT is not set
|
||||
CONFIG_CPU_32v6K=y
|
||||
CONFIG_CPU_32v7=y
|
||||
CONFIG_CPU_ABRT_EV7=y
|
||||
|
@ -73,11 +76,25 @@ CONFIG_CPU_CACHE_VIPT=y
|
|||
CONFIG_CPU_COPY_V6=y
|
||||
CONFIG_CPU_CP15=y
|
||||
CONFIG_CPU_CP15_MMU=y
|
||||
CONFIG_CPU_FREQ=y
|
||||
# CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE is not set
|
||||
# CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND is not set
|
||||
CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y
|
||||
# CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE is not set
|
||||
# CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE is not set
|
||||
# CONFIG_CPU_FREQ_GOV_CONSERVATIVE is not set
|
||||
# CONFIG_CPU_FREQ_GOV_ONDEMAND is not set
|
||||
CONFIG_CPU_FREQ_GOV_PERFORMANCE=y
|
||||
# CONFIG_CPU_FREQ_GOV_POWERSAVE is not set
|
||||
# CONFIG_CPU_FREQ_GOV_USERSPACE is not set
|
||||
CONFIG_CPU_FREQ_STAT=y
|
||||
# CONFIG_CPU_FREQ_STAT_DETAILS is not set
|
||||
CONFIG_CPU_HAS_ASID=y
|
||||
# CONFIG_CPU_ICACHE_DISABLE is not set
|
||||
CONFIG_CPU_PABRT_V7=y
|
||||
CONFIG_CPU_PM=y
|
||||
CONFIG_CPU_RMAP=y
|
||||
# CONFIG_CPU_THERMAL is not set
|
||||
CONFIG_CPU_TLB_V7=y
|
||||
CONFIG_CPU_V7=y
|
||||
CONFIG_CRC16=y
|
||||
|
@ -111,6 +128,7 @@ CONFIG_GENERIC_BUG=y
|
|||
CONFIG_GENERIC_CLOCKEVENTS=y
|
||||
CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y
|
||||
CONFIG_GENERIC_CLOCKEVENTS_BUILD=y
|
||||
CONFIG_GENERIC_CPUFREQ_KRAIT=y
|
||||
CONFIG_GENERIC_IDLE_POLL_SETUP=y
|
||||
CONFIG_GENERIC_IO=y
|
||||
CONFIG_GENERIC_IRQ_SHOW=y
|
||||
|
@ -203,6 +221,10 @@ CONFIG_IRQCHIP=y
|
|||
CONFIG_IRQ_DOMAIN=y
|
||||
CONFIG_IRQ_FORCED_THREADING=y
|
||||
CONFIG_IRQ_WORK=y
|
||||
CONFIG_KPSS_XCC=y
|
||||
CONFIG_KRAITCC=y
|
||||
CONFIG_KRAIT_CLOCKS=y
|
||||
CONFIG_KRAIT_L2_ACCESSORS=y
|
||||
# CONFIG_LEDS_REGULATOR is not set
|
||||
CONFIG_LIBFDT=y
|
||||
CONFIG_LOCKUP_DETECTOR=y
|
||||
|
@ -281,6 +303,7 @@ CONFIG_PINCTRL_MSM8X74=y
|
|||
CONFIG_PM=y
|
||||
CONFIG_PM_CLK=y
|
||||
# CONFIG_PM_DEBUG is not set
|
||||
CONFIG_PM_OPP=y
|
||||
CONFIG_PM_SLEEP=y
|
||||
CONFIG_PM_SLEEP_SMP=y
|
||||
CONFIG_POWER_RESET=y
|
||||
|
@ -299,6 +322,7 @@ CONFIG_PRINTK_TIME=y
|
|||
CONFIG_PROC_PAGE_MONITOR=y
|
||||
CONFIG_QCOM_BAM_DMA=y
|
||||
CONFIG_QCOM_GSBI=y
|
||||
CONFIG_QCOM_HFPLL=y
|
||||
CONFIG_QCOM_SCM=y
|
||||
CONFIG_QCOM_WDT=y
|
||||
CONFIG_RAS=y
|
||||
|
|
|
@ -41,10 +41,12 @@ CONFIG_ARM_ARCH_TIMER_EVTSTREAM=y
|
|||
CONFIG_ARM_CPU_SUSPEND=y
|
||||
CONFIG_ARM_GIC=y
|
||||
CONFIG_ARM_HAS_SG_CHAIN=y
|
||||
# CONFIG_ARM_KIRKWOOD_CPUFREQ is not set
|
||||
CONFIG_ARM_L1_CACHE_SHIFT=6
|
||||
CONFIG_ARM_L1_CACHE_SHIFT_6=y
|
||||
# CONFIG_ARM_LPAE is not set
|
||||
CONFIG_ARM_PATCH_PHYS_VIRT=y
|
||||
CONFIG_ARM_QCOM_CPUFREQ=y
|
||||
# CONFIG_ARM_SMMU is not set
|
||||
# CONFIG_ARM_SP805_WATCHDOG is not set
|
||||
CONFIG_ARM_THUMB=y
|
||||
|
@ -66,6 +68,7 @@ CONFIG_COMMON_CLK=y
|
|||
CONFIG_COMMON_CLK_QCOM=y
|
||||
CONFIG_COMPACTION=y
|
||||
CONFIG_COREDUMP=y
|
||||
# CONFIG_CPUFREQ_DT is not set
|
||||
CONFIG_CPU_32v6K=y
|
||||
CONFIG_CPU_32v7=y
|
||||
CONFIG_CPU_ABRT_EV7=y
|
||||
|
@ -76,11 +79,25 @@ CONFIG_CPU_CACHE_VIPT=y
|
|||
CONFIG_CPU_COPY_V6=y
|
||||
CONFIG_CPU_CP15=y
|
||||
CONFIG_CPU_CP15_MMU=y
|
||||
CONFIG_CPU_FREQ=y
|
||||
# CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE is not set
|
||||
# CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND is not set
|
||||
CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y
|
||||
# CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE is not set
|
||||
# CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE is not set
|
||||
# CONFIG_CPU_FREQ_GOV_CONSERVATIVE is not set
|
||||
# CONFIG_CPU_FREQ_GOV_ONDEMAND is not set
|
||||
CONFIG_CPU_FREQ_GOV_PERFORMANCE=y
|
||||
# CONFIG_CPU_FREQ_GOV_POWERSAVE is not set
|
||||
# CONFIG_CPU_FREQ_GOV_USERSPACE is not set
|
||||
CONFIG_CPU_FREQ_STAT=y
|
||||
# CONFIG_CPU_FREQ_STAT_DETAILS is not set
|
||||
CONFIG_CPU_HAS_ASID=y
|
||||
# CONFIG_CPU_ICACHE_DISABLE is not set
|
||||
CONFIG_CPU_PABRT_V7=y
|
||||
CONFIG_CPU_PM=y
|
||||
CONFIG_CPU_RMAP=y
|
||||
# CONFIG_CPU_THERMAL is not set
|
||||
CONFIG_CPU_TLB_V7=y
|
||||
CONFIG_CPU_V7=y
|
||||
CONFIG_CRC16=y
|
||||
|
@ -114,6 +131,7 @@ CONFIG_GENERIC_BUG=y
|
|||
CONFIG_GENERIC_CLOCKEVENTS=y
|
||||
CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y
|
||||
CONFIG_GENERIC_CLOCKEVENTS_BUILD=y
|
||||
CONFIG_GENERIC_CPUFREQ_KRAIT=y
|
||||
CONFIG_GENERIC_IDLE_POLL_SETUP=y
|
||||
CONFIG_GENERIC_IO=y
|
||||
CONFIG_GENERIC_IRQ_SHOW=y
|
||||
|
@ -209,6 +227,10 @@ CONFIG_IRQ_DOMAIN=y
|
|||
CONFIG_IRQ_DOMAIN_HIERARCHY=y
|
||||
CONFIG_IRQ_FORCED_THREADING=y
|
||||
CONFIG_IRQ_WORK=y
|
||||
CONFIG_KPSS_XCC=y
|
||||
CONFIG_KRAITCC=y
|
||||
CONFIG_KRAIT_CLOCKS=y
|
||||
CONFIG_KRAIT_L2_ACCESSORS=y
|
||||
# CONFIG_LEDS_REGULATOR is not set
|
||||
CONFIG_LIBFDT=y
|
||||
CONFIG_LOCKUP_DETECTOR=y
|
||||
|
@ -222,6 +244,7 @@ CONFIG_MDIO_BOARDINFO=y
|
|||
CONFIG_MDIO_GPIO=y
|
||||
CONFIG_MFD_QCOM_RPM=y
|
||||
# CONFIG_MFD_SPMI_PMIC is not set
|
||||
CONFIG_MFD_SYSCON=y
|
||||
CONFIG_MIGHT_HAVE_CACHE_L2X0=y
|
||||
CONFIG_MIGHT_HAVE_PCI=y
|
||||
CONFIG_MIGRATION=y
|
||||
|
@ -293,6 +316,7 @@ CONFIG_PINCTRL_MSM8X74=y
|
|||
CONFIG_PM=y
|
||||
CONFIG_PM_CLK=y
|
||||
# CONFIG_PM_DEBUG is not set
|
||||
CONFIG_PM_OPP=y
|
||||
CONFIG_PM_SLEEP=y
|
||||
CONFIG_PM_SLEEP_SMP=y
|
||||
CONFIG_POWER_RESET=y
|
||||
|
@ -311,6 +335,7 @@ CONFIG_PRINTK_TIME=y
|
|||
CONFIG_PROC_PAGE_MONITOR=y
|
||||
CONFIG_QCOM_BAM_DMA=y
|
||||
CONFIG_QCOM_GSBI=y
|
||||
CONFIG_QCOM_HFPLL=y
|
||||
CONFIG_QCOM_SCM=y
|
||||
CONFIG_QCOM_WDT=y
|
||||
CONFIG_RAS=y
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
From 6793b3cd5da817c4be218bd8632f07cf4d2b0d26 Mon Sep 17 00:00:00 2001
|
||||
From: Hans de Goede <hdegoede@redhat.com>
|
||||
Date: Wed, 19 Nov 2014 14:48:59 +0100
|
||||
Subject: [PATCH] clk_mux: Fix set_parent doing the wrong thing when INDEX_BIT
|
||||
&& index >= 3
|
||||
|
||||
If CLK_MUX_INDEX_BIT is set, then each bit turns on / off a single parent,
|
||||
so theoretically multiple parents could be enabled at the same time, but in
|
||||
practice only one bit should ever be 1. So to select parent 0, set
|
||||
the register (*) to 0x01, to select parent 1 set it 0x02, parent 2, 0x04,
|
||||
parent 3, 0x08, etc.
|
||||
|
||||
But the current code does:
|
||||
|
||||
if (mux->flags & CLK_MUX_INDEX_BIT)
|
||||
index = (1 << ffs(index));
|
||||
|
||||
Which means that:
|
||||
|
||||
For an input index of 0, ffs returns 0, so we set the register
|
||||
to 0x01, ok.
|
||||
|
||||
For an input index of 1, ffs returns 1, so we set the register
|
||||
to 0x02, ok.
|
||||
|
||||
For an input index of 2, ffs returns 2, so we set the register
|
||||
to 0x04, ok.
|
||||
|
||||
For an input index of 3, ffs returns 1, so we set the register
|
||||
to 0x02, not good!
|
||||
|
||||
The code should simply be:
|
||||
|
||||
if (mux->flags & CLK_MUX_INDEX_BIT)
|
||||
index = 1 << index;
|
||||
|
||||
Which always does the right thing, this commit fixes this.
|
||||
|
||||
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
||||
Signed-off-by: Michael Turquette <mturquette@linaro.org>
|
||||
---
|
||||
drivers/clk/clk-mux.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/drivers/clk/clk-mux.c b/drivers/clk/clk-mux.c
|
||||
index 4f96ff3..6e1ecf9 100644
|
||||
--- a/drivers/clk/clk-mux.c
|
||||
+++ b/drivers/clk/clk-mux.c
|
||||
@@ -77,7 +77,7 @@ static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
|
||||
|
||||
else {
|
||||
if (mux->flags & CLK_MUX_INDEX_BIT)
|
||||
- index = (1 << ffs(index));
|
||||
+ index = 1 << index;
|
||||
|
||||
if (mux->flags & CLK_MUX_INDEX_ONE)
|
||||
index++;
|
||||
--
|
||||
2.1.4
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
From 15a02c1f6dd7c2bb150c61d00ffb33f584ff2288 Mon Sep 17 00:00:00 2001
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Date: Mon, 19 Jan 2015 18:05:28 -0800
|
||||
Subject: [PATCH] clk: Add __clk_mux_determine_rate_closest
|
||||
|
||||
Some clock drivers want to find the closest rate on the input of
|
||||
a mux instead of a rate that's less than or equal to the desired
|
||||
rate. Add a generic mux function to support this.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Tested-by: Kenneth Westfield <kwestfie@codeaurora.org>
|
||||
Signed-off-by: Michael Turquette <mturquette@linaro.org>
|
||||
---
|
||||
drivers/clk/clk.c | 47 +++++++++++++++++++++++++++++++++++---------
|
||||
include/linux/clk-provider.h | 8 +++++++-
|
||||
2 files changed, 45 insertions(+), 10 deletions(-)
|
||||
|
||||
--- a/drivers/clk/clk.c
|
||||
+++ b/drivers/clk/clk.c
|
||||
@@ -695,14 +695,20 @@ struct clk *__clk_lookup(const char *nam
|
||||
return NULL;
|
||||
}
|
||||
|
||||
-/*
|
||||
- * Helper for finding best parent to provide a given frequency. This can be used
|
||||
- * directly as a determine_rate callback (e.g. for a mux), or from a more
|
||||
- * complex clock that may combine a mux with other operations.
|
||||
- */
|
||||
-long __clk_mux_determine_rate(struct clk_hw *hw, unsigned long rate,
|
||||
- unsigned long *best_parent_rate,
|
||||
- struct clk **best_parent_p)
|
||||
+static bool mux_is_better_rate(unsigned long rate, unsigned long now,
|
||||
+ unsigned long best, unsigned long flags)
|
||||
+{
|
||||
+ if (flags & CLK_MUX_ROUND_CLOSEST)
|
||||
+ return abs(now - rate) < abs(best - rate);
|
||||
+
|
||||
+ return now <= rate && now > best;
|
||||
+}
|
||||
+
|
||||
+static long
|
||||
+clk_mux_determine_rate_flags(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long *best_parent_rate,
|
||||
+ struct clk **best_parent_p,
|
||||
+ unsigned long flags)
|
||||
{
|
||||
struct clk *clk = hw->clk, *parent, *best_parent = NULL;
|
||||
int i, num_parents;
|
||||
@@ -730,7 +736,7 @@ long __clk_mux_determine_rate(struct clk
|
||||
parent_rate = __clk_round_rate(parent, rate);
|
||||
else
|
||||
parent_rate = __clk_get_rate(parent);
|
||||
- if (parent_rate <= rate && parent_rate > best) {
|
||||
+ if (mux_is_better_rate(rate, parent_rate, best, flags)) {
|
||||
best_parent = parent;
|
||||
best = parent_rate;
|
||||
}
|
||||
@@ -743,8 +749,31 @@ out:
|
||||
|
||||
return best;
|
||||
}
|
||||
+
|
||||
+/*
|
||||
+ * Helper for finding best parent to provide a given frequency. This can be used
|
||||
+ * directly as a determine_rate callback (e.g. for a mux), or from a more
|
||||
+ * complex clock that may combine a mux with other operations.
|
||||
+ */
|
||||
+long __clk_mux_determine_rate(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long *best_parent_rate,
|
||||
+ struct clk **best_parent_p)
|
||||
+{
|
||||
+ return clk_mux_determine_rate_flags(hw, rate, best_parent_rate,
|
||||
+ best_parent_p, 0);
|
||||
+}
|
||||
EXPORT_SYMBOL_GPL(__clk_mux_determine_rate);
|
||||
|
||||
+long __clk_mux_determine_rate_closest(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long *best_parent_rate,
|
||||
+ struct clk **best_parent_p)
|
||||
+{
|
||||
+ return clk_mux_determine_rate_flags(hw, rate, best_parent_rate,
|
||||
+ best_parent_p,
|
||||
+ CLK_MUX_ROUND_CLOSEST);
|
||||
+}
|
||||
+EXPORT_SYMBOL_GPL(__clk_mux_determine_rate_closest);
|
||||
+
|
||||
/*** clk api ***/
|
||||
|
||||
void __clk_unprepare(struct clk *clk)
|
||||
--- a/include/linux/clk-provider.h
|
||||
+++ b/include/linux/clk-provider.h
|
||||
@@ -382,6 +382,8 @@ struct clk *clk_register_divider_table(s
|
||||
* register, and mask of mux bits are in higher 16-bit of this register.
|
||||
* While setting the mux bits, higher 16-bit should also be updated to
|
||||
* indicate changing mux bits.
|
||||
+ * CLK_MUX_ROUND_CLOSEST - Use the parent rate that is closest to the desired
|
||||
+ * frequency.
|
||||
*/
|
||||
struct clk_mux {
|
||||
struct clk_hw hw;
|
||||
@@ -396,7 +398,8 @@ struct clk_mux {
|
||||
#define CLK_MUX_INDEX_ONE BIT(0)
|
||||
#define CLK_MUX_INDEX_BIT BIT(1)
|
||||
#define CLK_MUX_HIWORD_MASK BIT(2)
|
||||
-#define CLK_MUX_READ_ONLY BIT(3) /* mux setting cannot be changed */
|
||||
+#define CLK_MUX_READ_ONLY BIT(3) /* mux can't be changed */
|
||||
+#define CLK_MUX_ROUND_CLOSEST BIT(4)
|
||||
|
||||
extern const struct clk_ops clk_mux_ops;
|
||||
extern const struct clk_ops clk_mux_ro_ops;
|
||||
@@ -554,6 +557,9 @@ struct clk *__clk_lookup(const char *nam
|
||||
long __clk_mux_determine_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long *best_parent_rate,
|
||||
struct clk **best_parent_p);
|
||||
+long __clk_mux_determine_rate_closest(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long *best_parent_rate,
|
||||
+ struct clk **best_parent_p);
|
||||
|
||||
/*
|
||||
* FIXME clock api without lock protection
|
|
@ -0,0 +1,115 @@
|
|||
From 4e3c021fb995bcbb5d1f814d00584cb80eb904a8 Mon Sep 17 00:00:00 2001
|
||||
From: Krzysztof Kozlowski <k.kozlowski@samsung.com>
|
||||
Date: Mon, 5 Jan 2015 10:52:40 +0100
|
||||
Subject: [PATCH] clk: Add clk_unregister_{divider, gate, mux} to close memory
|
||||
leak
|
||||
|
||||
The common clk_register_{divider,gate,mux} functions allocated memory
|
||||
for internal data which wasn't freed anywhere. Drivers using these
|
||||
helpers could only unregister clocks but the memory would still leak.
|
||||
|
||||
Add corresponding unregister functions which will release all resources.
|
||||
|
||||
Signed-off-by: Krzysztof Kozlowski <k.kozlowski@samsung.com>
|
||||
Reviewed-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Signed-off-by: Michael Turquette <mturquette@linaro.org>
|
||||
---
|
||||
drivers/clk/clk-divider.c | 16 ++++++++++++++++
|
||||
drivers/clk/clk-gate.c | 16 ++++++++++++++++
|
||||
drivers/clk/clk-mux.c | 16 ++++++++++++++++
|
||||
include/linux/clk-provider.h | 4 ++++
|
||||
4 files changed, 52 insertions(+)
|
||||
|
||||
--- a/drivers/clk/clk-divider.c
|
||||
+++ b/drivers/clk/clk-divider.c
|
||||
@@ -463,3 +463,19 @@ struct clk *clk_register_divider_table(s
|
||||
width, clk_divider_flags, table, lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(clk_register_divider_table);
|
||||
+
|
||||
+void clk_unregister_divider(struct clk *clk)
|
||||
+{
|
||||
+ struct clk_divider *div;
|
||||
+ struct clk_hw *hw;
|
||||
+
|
||||
+ hw = __clk_get_hw(clk);
|
||||
+ if (!hw)
|
||||
+ return;
|
||||
+
|
||||
+ div = to_clk_divider(hw);
|
||||
+
|
||||
+ clk_unregister(clk);
|
||||
+ kfree(div);
|
||||
+}
|
||||
+EXPORT_SYMBOL_GPL(clk_unregister_divider);
|
||||
--- a/drivers/clk/clk-gate.c
|
||||
+++ b/drivers/clk/clk-gate.c
|
||||
@@ -162,3 +162,19 @@ struct clk *clk_register_gate(struct dev
|
||||
return clk;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(clk_register_gate);
|
||||
+
|
||||
+void clk_unregister_gate(struct clk *clk)
|
||||
+{
|
||||
+ struct clk_gate *gate;
|
||||
+ struct clk_hw *hw;
|
||||
+
|
||||
+ hw = __clk_get_hw(clk);
|
||||
+ if (!hw)
|
||||
+ return;
|
||||
+
|
||||
+ gate = to_clk_gate(hw);
|
||||
+
|
||||
+ clk_unregister(clk);
|
||||
+ kfree(gate);
|
||||
+}
|
||||
+EXPORT_SYMBOL_GPL(clk_unregister_gate);
|
||||
--- a/drivers/clk/clk-mux.c
|
||||
+++ b/drivers/clk/clk-mux.c
|
||||
@@ -177,3 +177,19 @@ struct clk *clk_register_mux(struct devi
|
||||
NULL, lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(clk_register_mux);
|
||||
+
|
||||
+void clk_unregister_mux(struct clk *clk)
|
||||
+{
|
||||
+ struct clk_mux *mux;
|
||||
+ struct clk_hw *hw;
|
||||
+
|
||||
+ hw = __clk_get_hw(clk);
|
||||
+ if (!hw)
|
||||
+ return;
|
||||
+
|
||||
+ mux = to_clk_mux(hw);
|
||||
+
|
||||
+ clk_unregister(clk);
|
||||
+ kfree(mux);
|
||||
+}
|
||||
+EXPORT_SYMBOL_GPL(clk_unregister_mux);
|
||||
--- a/include/linux/clk-provider.h
|
||||
+++ b/include/linux/clk-provider.h
|
||||
@@ -294,6 +294,7 @@ struct clk *clk_register_gate(struct dev
|
||||
const char *parent_name, unsigned long flags,
|
||||
void __iomem *reg, u8 bit_idx,
|
||||
u8 clk_gate_flags, spinlock_t *lock);
|
||||
+void clk_unregister_gate(struct clk *clk);
|
||||
|
||||
struct clk_div_table {
|
||||
unsigned int val;
|
||||
@@ -361,6 +362,7 @@ struct clk *clk_register_divider_table(s
|
||||
void __iomem *reg, u8 shift, u8 width,
|
||||
u8 clk_divider_flags, const struct clk_div_table *table,
|
||||
spinlock_t *lock);
|
||||
+void clk_unregister_divider(struct clk *clk);
|
||||
|
||||
/**
|
||||
* struct clk_mux - multiplexer clock
|
||||
@@ -414,6 +416,8 @@ struct clk *clk_register_mux_table(struc
|
||||
void __iomem *reg, u8 shift, u32 mask,
|
||||
u8 clk_mux_flags, u32 *table, spinlock_t *lock);
|
||||
|
||||
+void clk_unregister_mux(struct clk *clk);
|
||||
+
|
||||
void of_fixed_factor_clk_setup(struct device_node *node);
|
||||
|
||||
/**
|
|
@ -0,0 +1,144 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,01/13] ARM: Add Krait L2 register accessor functions
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063051
|
||||
Message-Id: <1426920332-9340-2-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>,
|
||||
Mark Rutland <mark.rutland@arm.com>, Russell King <linux@arm.linux.org.uk>,
|
||||
Courtney Cavin <courtney.cavin@sonymobile.com>
|
||||
Date: Fri, 20 Mar 2015 23:45:20 -0700
|
||||
|
||||
Krait CPUs have a handful of L2 cache controller registers that
|
||||
live behind a cp15 based indirection register. First you program
|
||||
the indirection register (l2cpselr) to point the L2 'window'
|
||||
register (l2cpdr) at what you want to read/write. Then you
|
||||
read/write the 'window' register to do what you want. The
|
||||
l2cpselr register is not banked per-cpu so we must lock around
|
||||
accesses to it to prevent other CPUs from re-pointing l2cpdr
|
||||
underneath us.
|
||||
|
||||
Cc: Mark Rutland <mark.rutland@arm.com>
|
||||
Cc: Russell King <linux@arm.linux.org.uk>
|
||||
Cc: Courtney Cavin <courtney.cavin@sonymobile.com>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
arch/arm/common/Kconfig | 3 ++
|
||||
arch/arm/common/Makefile | 1 +
|
||||
arch/arm/common/krait-l2-accessors.c | 58 +++++++++++++++++++++++++++++++
|
||||
arch/arm/include/asm/krait-l2-accessors.h | 20 +++++++++++
|
||||
4 files changed, 82 insertions(+)
|
||||
create mode 100644 arch/arm/common/krait-l2-accessors.c
|
||||
create mode 100644 arch/arm/include/asm/krait-l2-accessors.h
|
||||
|
||||
--- a/arch/arm/common/Kconfig
|
||||
+++ b/arch/arm/common/Kconfig
|
||||
@@ -9,6 +9,9 @@ config DMABOUNCE
|
||||
bool
|
||||
select ZONE_DMA
|
||||
|
||||
+config KRAIT_L2_ACCESSORS
|
||||
+ bool
|
||||
+
|
||||
config SHARP_LOCOMO
|
||||
bool
|
||||
|
||||
--- a/arch/arm/common/Makefile
|
||||
+++ b/arch/arm/common/Makefile
|
||||
@@ -7,6 +7,7 @@ obj-y += firmware.o
|
||||
obj-$(CONFIG_ICST) += icst.o
|
||||
obj-$(CONFIG_SA1111) += sa1111.o
|
||||
obj-$(CONFIG_DMABOUNCE) += dmabounce.o
|
||||
+obj-$(CONFIG_KRAIT_L2_ACCESSORS) += krait-l2-accessors.o
|
||||
obj-$(CONFIG_SHARP_LOCOMO) += locomo.o
|
||||
obj-$(CONFIG_SHARP_PARAM) += sharpsl_param.o
|
||||
obj-$(CONFIG_SHARP_SCOOP) += scoop.o
|
||||
--- /dev/null
|
||||
+++ b/arch/arm/common/krait-l2-accessors.c
|
||||
@@ -0,0 +1,58 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2011-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/spinlock.h>
|
||||
+#include <linux/export.h>
|
||||
+
|
||||
+#include <asm/barrier.h>
|
||||
+#include <asm/krait-l2-accessors.h>
|
||||
+
|
||||
+static DEFINE_RAW_SPINLOCK(krait_l2_lock);
|
||||
+
|
||||
+void krait_set_l2_indirect_reg(u32 addr, u32 val)
|
||||
+{
|
||||
+ unsigned long flags;
|
||||
+
|
||||
+ raw_spin_lock_irqsave(&krait_l2_lock, flags);
|
||||
+ /*
|
||||
+ * Select the L2 window by poking l2cpselr, then write to the window
|
||||
+ * via l2cpdr.
|
||||
+ */
|
||||
+ asm volatile ("mcr p15, 3, %0, c15, c0, 6 @ l2cpselr" : : "r" (addr));
|
||||
+ isb();
|
||||
+ asm volatile ("mcr p15, 3, %0, c15, c0, 7 @ l2cpdr" : : "r" (val));
|
||||
+ isb();
|
||||
+
|
||||
+ raw_spin_unlock_irqrestore(&krait_l2_lock, flags);
|
||||
+}
|
||||
+EXPORT_SYMBOL(krait_set_l2_indirect_reg);
|
||||
+
|
||||
+u32 krait_get_l2_indirect_reg(u32 addr)
|
||||
+{
|
||||
+ u32 val;
|
||||
+ unsigned long flags;
|
||||
+
|
||||
+ raw_spin_lock_irqsave(&krait_l2_lock, flags);
|
||||
+ /*
|
||||
+ * Select the L2 window by poking l2cpselr, then read from the window
|
||||
+ * via l2cpdr.
|
||||
+ */
|
||||
+ asm volatile ("mcr p15, 3, %0, c15, c0, 6 @ l2cpselr" : : "r" (addr));
|
||||
+ isb();
|
||||
+ asm volatile ("mrc p15, 3, %0, c15, c0, 7 @ l2cpdr" : "=r" (val));
|
||||
+
|
||||
+ raw_spin_unlock_irqrestore(&krait_l2_lock, flags);
|
||||
+
|
||||
+ return val;
|
||||
+}
|
||||
+EXPORT_SYMBOL(krait_get_l2_indirect_reg);
|
||||
--- /dev/null
|
||||
+++ b/arch/arm/include/asm/krait-l2-accessors.h
|
||||
@@ -0,0 +1,20 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2011-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 __ASMARM_KRAIT_L2_ACCESSORS_H
|
||||
+#define __ASMARM_KRAIT_L2_ACCESSORS_H
|
||||
+
|
||||
+extern void krait_set_l2_indirect_reg(u32 addr, u32 val);
|
||||
+extern u32 krait_get_l2_indirect_reg(u32 addr);
|
||||
+
|
||||
+#endif
|
|
@ -0,0 +1,192 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,02/13] clk: mux: Split out register accessors for reuse
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063111
|
||||
Message-Id: <1426920332-9340-3-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:21 -0700
|
||||
|
||||
We want to reuse the logic in clk-mux.c for other clock drivers
|
||||
that don't use readl as register accessors. Fortunately, there
|
||||
really isn't much to the mux code besides the table indirection
|
||||
and quirk flags if you assume any bit shifting and masking has
|
||||
been done already. Pull that logic out into reusable functions
|
||||
that operate on an optional table and some flags so that other
|
||||
drivers can use the same logic.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
drivers/clk/clk-mux.c | 76 +++++++++++++++++++++++++++-----------------
|
||||
include/linux/clk-provider.h | 9 ++++--
|
||||
2 files changed, 54 insertions(+), 31 deletions(-)
|
||||
|
||||
--- a/drivers/clk/clk-mux.c
|
||||
+++ b/drivers/clk/clk-mux.c
|
||||
@@ -29,35 +29,24 @@
|
||||
|
||||
#define to_clk_mux(_hw) container_of(_hw, struct clk_mux, hw)
|
||||
|
||||
-static u8 clk_mux_get_parent(struct clk_hw *hw)
|
||||
+unsigned int clk_mux_get_parent(struct clk_hw *hw, unsigned int val,
|
||||
+ unsigned int *table, unsigned long flags)
|
||||
{
|
||||
- struct clk_mux *mux = to_clk_mux(hw);
|
||||
int num_parents = __clk_get_num_parents(hw->clk);
|
||||
- u32 val;
|
||||
|
||||
- /*
|
||||
- * FIXME need a mux-specific flag to determine if val is bitwise or numeric
|
||||
- * e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1
|
||||
- * to 0x7 (index starts at one)
|
||||
- * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so
|
||||
- * val = 0x4 really means "bit 2, index starts at bit 0"
|
||||
- */
|
||||
- val = clk_readl(mux->reg) >> mux->shift;
|
||||
- val &= mux->mask;
|
||||
-
|
||||
- if (mux->table) {
|
||||
+ if (table) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_parents; i++)
|
||||
- if (mux->table[i] == val)
|
||||
+ if (table[i] == val)
|
||||
return i;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
- if (val && (mux->flags & CLK_MUX_INDEX_BIT))
|
||||
+ if (val && (flags & CLK_MUX_INDEX_BIT))
|
||||
val = ffs(val) - 1;
|
||||
|
||||
- if (val && (mux->flags & CLK_MUX_INDEX_ONE))
|
||||
+ if (val && (flags & CLK_MUX_INDEX_ONE))
|
||||
val--;
|
||||
|
||||
if (val >= num_parents)
|
||||
@@ -65,24 +54,53 @@ static u8 clk_mux_get_parent(struct clk_
|
||||
|
||||
return val;
|
||||
}
|
||||
+EXPORT_SYMBOL_GPL(clk_mux_get_parent);
|
||||
|
||||
-static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
|
||||
+static u8 _clk_mux_get_parent(struct clk_hw *hw)
|
||||
{
|
||||
struct clk_mux *mux = to_clk_mux(hw);
|
||||
u32 val;
|
||||
- unsigned long flags = 0;
|
||||
|
||||
- if (mux->table)
|
||||
- index = mux->table[index];
|
||||
+ /*
|
||||
+ * FIXME need a mux-specific flag to determine if val is bitwise or numeric
|
||||
+ * e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1
|
||||
+ * to 0x7 (index starts at one)
|
||||
+ * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so
|
||||
+ * val = 0x4 really means "bit 2, index starts at bit 0"
|
||||
+ */
|
||||
+ val = clk_readl(mux->reg) >> mux->shift;
|
||||
+ val &= mux->mask;
|
||||
+
|
||||
+ return clk_mux_get_parent(hw, val, mux->table, mux->flags);
|
||||
+}
|
||||
|
||||
- else {
|
||||
- if (mux->flags & CLK_MUX_INDEX_BIT)
|
||||
- index = 1 << index;
|
||||
+unsigned int clk_mux_reindex(u8 index, unsigned int *table,
|
||||
+ unsigned long flags)
|
||||
+{
|
||||
+ unsigned int val = index;
|
||||
|
||||
- if (mux->flags & CLK_MUX_INDEX_ONE)
|
||||
- index++;
|
||||
+ if (table) {
|
||||
+ val = table[val];
|
||||
+ } else {
|
||||
+ if (flags & CLK_MUX_INDEX_BIT)
|
||||
+ val = 1 << index;
|
||||
+
|
||||
+ if (flags & CLK_MUX_INDEX_ONE)
|
||||
+ val++;
|
||||
}
|
||||
|
||||
+ return val;
|
||||
+}
|
||||
+EXPORT_SYMBOL_GPL(clk_mux_reindex);
|
||||
+
|
||||
+static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
|
||||
+{
|
||||
+ struct clk_mux *mux = to_clk_mux(hw);
|
||||
+ u32 val;
|
||||
+ unsigned long flags = 0;
|
||||
+
|
||||
+ index = clk_mux_reindex(index, mux->table, mux->flags);
|
||||
+
|
||||
if (mux->lock)
|
||||
spin_lock_irqsave(mux->lock, flags);
|
||||
|
||||
@@ -102,21 +120,21 @@ static int clk_mux_set_parent(struct clk
|
||||
}
|
||||
|
||||
const struct clk_ops clk_mux_ops = {
|
||||
- .get_parent = clk_mux_get_parent,
|
||||
+ .get_parent = _clk_mux_get_parent,
|
||||
.set_parent = clk_mux_set_parent,
|
||||
.determine_rate = __clk_mux_determine_rate,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(clk_mux_ops);
|
||||
|
||||
const struct clk_ops clk_mux_ro_ops = {
|
||||
- .get_parent = clk_mux_get_parent,
|
||||
+ .get_parent = _clk_mux_get_parent,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(clk_mux_ro_ops);
|
||||
|
||||
struct clk *clk_register_mux_table(struct device *dev, const char *name,
|
||||
const char **parent_names, u8 num_parents, unsigned long flags,
|
||||
void __iomem *reg, u8 shift, u32 mask,
|
||||
- u8 clk_mux_flags, u32 *table, spinlock_t *lock)
|
||||
+ u8 clk_mux_flags, unsigned int *table, spinlock_t *lock)
|
||||
{
|
||||
struct clk_mux *mux;
|
||||
struct clk *clk;
|
||||
--- a/include/linux/clk-provider.h
|
||||
+++ b/include/linux/clk-provider.h
|
||||
@@ -390,7 +390,7 @@ void clk_unregister_divider(struct clk *
|
||||
struct clk_mux {
|
||||
struct clk_hw hw;
|
||||
void __iomem *reg;
|
||||
- u32 *table;
|
||||
+ unsigned int *table;
|
||||
u32 mask;
|
||||
u8 shift;
|
||||
u8 flags;
|
||||
@@ -406,6 +406,11 @@ struct clk_mux {
|
||||
extern const struct clk_ops clk_mux_ops;
|
||||
extern const struct clk_ops clk_mux_ro_ops;
|
||||
|
||||
+unsigned int clk_mux_get_parent(struct clk_hw *hw, unsigned int val,
|
||||
+ unsigned int *table, unsigned long flags);
|
||||
+unsigned int clk_mux_reindex(u8 index, unsigned int *table,
|
||||
+ unsigned long flags);
|
||||
+
|
||||
struct clk *clk_register_mux(struct device *dev, const char *name,
|
||||
const char **parent_names, u8 num_parents, unsigned long flags,
|
||||
void __iomem *reg, u8 shift, u8 width,
|
||||
@@ -414,7 +419,7 @@ struct clk *clk_register_mux(struct devi
|
||||
struct clk *clk_register_mux_table(struct device *dev, const char *name,
|
||||
const char **parent_names, u8 num_parents, unsigned long flags,
|
||||
void __iomem *reg, u8 shift, u32 mask,
|
||||
- u8 clk_mux_flags, u32 *table, spinlock_t *lock);
|
||||
+ u8 clk_mux_flags, unsigned int *table, spinlock_t *lock);
|
||||
|
||||
void clk_unregister_mux(struct clk *clk);
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3, 03/13] clk: Avoid sending high rates to downstream clocks during
|
||||
set_rate
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063271
|
||||
Message-Id: <1426920332-9340-4-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:22 -0700
|
||||
|
||||
If a clock is on and we call clk_set_rate() on it we may get into
|
||||
a situation where the clock temporarily increases in rate
|
||||
dramatically while we walk the tree and call .set_rate() ops. For
|
||||
example, consider a case where a PLL feeds into a divider.
|
||||
Initially the divider is set to divide by 1 and the PLL is
|
||||
running fairly slow (100MHz). The downstream consumer of the
|
||||
divider output can only handle rates =< 400 MHz, but the divider
|
||||
can only choose between divisors of 1 and 4.
|
||||
|
||||
+-----+ +----------------+
|
||||
| PLL |-->| div 1 or div 4 |---> consumer device
|
||||
+-----+ +----------------+
|
||||
|
||||
To achieve a rate of 400MHz on the output of the divider, we
|
||||
would have to set the rate of the PLL to 1.6 GHz and then divide
|
||||
it by 4. The current code would set the PLL to 1.6GHz first while
|
||||
the divider is still set to 1, thus causing the downstream
|
||||
consumer of the clock to receive a few clock cycles of 1.6GHz
|
||||
clock (far beyond it's maximum acceptable rate). We should be
|
||||
changing the divider first before increasing the PLL rate to
|
||||
avoid this problem.
|
||||
|
||||
Therefore, set the rate of any child clocks that are increasing
|
||||
in rate from their current rate so that they can increase their
|
||||
dividers if necessary. We assume that there isn't such a thing as
|
||||
minimum rate requirements.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
drivers/clk/clk.c | 34 ++++++++++++++++++++++------------
|
||||
1 file changed, 22 insertions(+), 12 deletions(-)
|
||||
|
||||
--- a/drivers/clk/clk.c
|
||||
+++ b/drivers/clk/clk.c
|
||||
@@ -1476,21 +1476,23 @@ static struct clk *clk_propagate_rate_ch
|
||||
* walk down a subtree and set the new rates notifying the rate
|
||||
* change on the way
|
||||
*/
|
||||
-static void clk_change_rate(struct clk *clk)
|
||||
+static void clk_change_rate(struct clk *clk, unsigned long best_parent_rate)
|
||||
{
|
||||
struct clk *child;
|
||||
struct hlist_node *tmp;
|
||||
unsigned long old_rate;
|
||||
- unsigned long best_parent_rate = 0;
|
||||
bool skip_set_rate = false;
|
||||
struct clk *old_parent;
|
||||
|
||||
- old_rate = clk->rate;
|
||||
+ hlist_for_each_entry(child, &clk->children, child_node) {
|
||||
+ /* Skip children who will be reparented to another clock */
|
||||
+ if (child->new_parent && child->new_parent != clk)
|
||||
+ continue;
|
||||
+ if (child->new_rate > child->rate)
|
||||
+ clk_change_rate(child, clk->new_rate);
|
||||
+ }
|
||||
|
||||
- if (clk->new_parent)
|
||||
- best_parent_rate = clk->new_parent->rate;
|
||||
- else if (clk->parent)
|
||||
- best_parent_rate = clk->parent->rate;
|
||||
+ old_rate = clk->rate;
|
||||
|
||||
if (clk->new_parent && clk->new_parent != clk->parent) {
|
||||
old_parent = __clk_set_parent_before(clk, clk->new_parent);
|
||||
@@ -1510,7 +1512,7 @@ static void clk_change_rate(struct clk *
|
||||
if (!skip_set_rate && clk->ops->set_rate)
|
||||
clk->ops->set_rate(clk->hw, clk->new_rate, best_parent_rate);
|
||||
|
||||
- clk->rate = clk_recalc(clk, best_parent_rate);
|
||||
+ clk->rate = clk->new_rate;
|
||||
|
||||
if (clk->notifier_count && old_rate != clk->rate)
|
||||
__clk_notify(clk, POST_RATE_CHANGE, old_rate, clk->rate);
|
||||
@@ -1523,12 +1525,13 @@ static void clk_change_rate(struct clk *
|
||||
/* Skip children who will be reparented to another clock */
|
||||
if (child->new_parent && child->new_parent != clk)
|
||||
continue;
|
||||
- clk_change_rate(child);
|
||||
+ if (child->new_rate != child->rate)
|
||||
+ clk_change_rate(child, clk->new_rate);
|
||||
}
|
||||
|
||||
/* handle the new child who might not be in clk->children yet */
|
||||
- if (clk->new_child)
|
||||
- clk_change_rate(clk->new_child);
|
||||
+ if (clk->new_child && clk->new_child->new_rate != clk->new_child->rate)
|
||||
+ clk_change_rate(clk->new_child, clk->new_rate);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1556,6 +1559,7 @@ int clk_set_rate(struct clk *clk, unsign
|
||||
{
|
||||
struct clk *top, *fail_clk;
|
||||
int ret = 0;
|
||||
+ unsigned long parent_rate;
|
||||
|
||||
if (!clk)
|
||||
return 0;
|
||||
@@ -1589,8 +1593,13 @@ int clk_set_rate(struct clk *clk, unsign
|
||||
goto out;
|
||||
}
|
||||
|
||||
+ if (top->parent)
|
||||
+ parent_rate = top->parent->rate;
|
||||
+ else
|
||||
+ parent_rate = 0;
|
||||
+
|
||||
/* change the rates */
|
||||
- clk_change_rate(top);
|
||||
+ clk_change_rate(top, parent_rate);
|
||||
|
||||
out:
|
||||
clk_prepare_unlock();
|
|
@ -0,0 +1,170 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,04/13] clk: Add safe switch hook
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063211
|
||||
Message-Id: <1426920332-9340-5-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:23 -0700
|
||||
|
||||
Sometimes clocks can't accept their parent source turning off
|
||||
while the source is reprogrammed to a different rate. Most
|
||||
notably CPU clocks require a way to switch away from the current
|
||||
PLL they're running on, reprogram that PLL to a new rate, and
|
||||
then switch back to the PLL with the new rate once they're done.
|
||||
Add a hook that drivers can implement allowing them to return a
|
||||
'safe parent' that they can switch their parent to while the
|
||||
upstream source is reprogrammed to support this.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
This patch is good enough for Krait, but soon I'll need to
|
||||
support a "safe rate" where we ask a clock what rate it needs to be running
|
||||
at to be sure it's within voltage constraints. Right now safe parent
|
||||
handles that problem on Krait, but on other platforms it won't work.
|
||||
|
||||
drivers/clk/clk.c | 61 ++++++++++++++++++++++++++++++++++++++------
|
||||
include/linux/clk-provider.h | 1 +
|
||||
2 files changed, 54 insertions(+), 8 deletions(-)
|
||||
|
||||
--- a/drivers/clk/clk.c
|
||||
+++ b/drivers/clk/clk.c
|
||||
@@ -1350,7 +1350,8 @@ out:
|
||||
static void clk_calc_subtree(struct clk *clk, unsigned long new_rate,
|
||||
struct clk *new_parent, u8 p_index)
|
||||
{
|
||||
- struct clk *child;
|
||||
+ struct clk *child, *parent;
|
||||
+ struct clk_hw *parent_hw;
|
||||
|
||||
clk->new_rate = new_rate;
|
||||
clk->new_parent = new_parent;
|
||||
@@ -1360,6 +1361,18 @@ static void clk_calc_subtree(struct clk
|
||||
if (new_parent && new_parent != clk->parent)
|
||||
new_parent->new_child = clk;
|
||||
|
||||
+ if (clk->ops->get_safe_parent) {
|
||||
+ parent_hw = clk->ops->get_safe_parent(clk->hw);
|
||||
+ if (parent_hw) {
|
||||
+ parent = parent_hw->clk;
|
||||
+ p_index = clk_fetch_parent_index(clk, parent);
|
||||
+ clk->safe_parent_index = p_index;
|
||||
+ clk->safe_parent = parent;
|
||||
+ }
|
||||
+ } else {
|
||||
+ clk->safe_parent = NULL;
|
||||
+ }
|
||||
+
|
||||
hlist_for_each_entry(child, &clk->children, child_node) {
|
||||
child->new_rate = clk_recalc(child, new_rate);
|
||||
clk_calc_subtree(child, child->new_rate, NULL, 0);
|
||||
@@ -1439,17 +1452,47 @@ out:
|
||||
* so that in case of an error we can walk down the whole tree again and
|
||||
* abort the change.
|
||||
*/
|
||||
-static struct clk *clk_propagate_rate_change(struct clk *clk, unsigned long event)
|
||||
+static struct clk *clk_propagate_rate_change(struct clk *clk,
|
||||
+ unsigned long event)
|
||||
{
|
||||
struct clk *child, *tmp_clk, *fail_clk = NULL;
|
||||
+ struct clk *old_parent;
|
||||
int ret = NOTIFY_DONE;
|
||||
|
||||
- if (clk->rate == clk->new_rate)
|
||||
+ if (clk->rate == clk->new_rate && event != POST_RATE_CHANGE)
|
||||
return NULL;
|
||||
|
||||
+ switch (event) {
|
||||
+ case PRE_RATE_CHANGE:
|
||||
+ if (clk->safe_parent)
|
||||
+ clk->ops->set_parent(clk->hw, clk->safe_parent_index);
|
||||
+ clk->old_rate = clk->rate;
|
||||
+ break;
|
||||
+ case POST_RATE_CHANGE:
|
||||
+ if (clk->safe_parent) {
|
||||
+ old_parent = __clk_set_parent_before(clk,
|
||||
+ clk->new_parent);
|
||||
+ if (clk->ops->set_rate_and_parent) {
|
||||
+ clk->ops->set_rate_and_parent(clk->hw,
|
||||
+ clk->new_rate,
|
||||
+ clk->new_parent ?
|
||||
+ clk->new_parent->rate : 0,
|
||||
+ clk->new_parent_index);
|
||||
+ } else if (clk->ops->set_parent) {
|
||||
+ clk->ops->set_parent(clk->hw,
|
||||
+ clk->new_parent_index);
|
||||
+ }
|
||||
+ __clk_set_parent_after(clk, clk->new_parent,
|
||||
+ old_parent);
|
||||
+ }
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
if (clk->notifier_count) {
|
||||
- ret = __clk_notify(clk, event, clk->rate, clk->new_rate);
|
||||
- if (ret & NOTIFY_STOP_MASK)
|
||||
+ if (event != POST_RATE_CHANGE || clk->old_rate != clk->rate)
|
||||
+ ret = __clk_notify(clk, event, clk->old_rate,
|
||||
+ clk->new_rate);
|
||||
+ if (ret & NOTIFY_STOP_MASK && event != POST_RATE_CHANGE)
|
||||
fail_clk = clk;
|
||||
}
|
||||
|
||||
@@ -1494,7 +1537,8 @@ static void clk_change_rate(struct clk *
|
||||
|
||||
old_rate = clk->rate;
|
||||
|
||||
- if (clk->new_parent && clk->new_parent != clk->parent) {
|
||||
+ if (clk->new_parent && clk->new_parent != clk->parent &&
|
||||
+ !clk->safe_parent) {
|
||||
old_parent = __clk_set_parent_before(clk, clk->new_parent);
|
||||
|
||||
if (clk->ops->set_rate_and_parent) {
|
||||
@@ -1514,9 +1558,6 @@ static void clk_change_rate(struct clk *
|
||||
|
||||
clk->rate = clk->new_rate;
|
||||
|
||||
- if (clk->notifier_count && old_rate != clk->rate)
|
||||
- __clk_notify(clk, POST_RATE_CHANGE, old_rate, clk->rate);
|
||||
-
|
||||
/*
|
||||
* Use safe iteration, as change_rate can actually swap parents
|
||||
* for certain clock types.
|
||||
@@ -1601,6 +1642,8 @@ int clk_set_rate(struct clk *clk, unsign
|
||||
/* change the rates */
|
||||
clk_change_rate(top, parent_rate);
|
||||
|
||||
+ clk_propagate_rate_change(top, POST_RATE_CHANGE);
|
||||
+
|
||||
out:
|
||||
clk_prepare_unlock();
|
||||
|
||||
--- a/include/linux/clk-provider.h
|
||||
+++ b/include/linux/clk-provider.h
|
||||
@@ -179,6 +179,7 @@ struct clk_ops {
|
||||
struct clk **best_parent_clk);
|
||||
int (*set_parent)(struct clk_hw *hw, u8 index);
|
||||
u8 (*get_parent)(struct clk_hw *hw);
|
||||
+ struct clk_hw *(*get_safe_parent)(struct clk_hw *hw);
|
||||
int (*set_rate)(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate);
|
||||
int (*set_rate_and_parent)(struct clk_hw *hw,
|
||||
--- a/include/linux/clk-private.h
|
||||
+++ b/include/linux/clk-private.h
|
||||
@@ -38,8 +38,11 @@ struct clk {
|
||||
struct clk **parents;
|
||||
u8 num_parents;
|
||||
u8 new_parent_index;
|
||||
+ u8 safe_parent_index;
|
||||
unsigned long rate;
|
||||
+ unsigned long old_rate;
|
||||
unsigned long new_rate;
|
||||
+ struct clk *safe_parent;
|
||||
struct clk *new_parent;
|
||||
struct clk *new_child;
|
||||
unsigned long flags;
|
|
@ -0,0 +1,351 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,05/13] clk: qcom: Add support for High-Frequency PLLs (HFPLLs)
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063261
|
||||
Message-Id: <1426920332-9340-6-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:24 -0700
|
||||
|
||||
HFPLLs are the main frequency source for Krait CPU clocks. Add
|
||||
support for changing the rate of these PLLs.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
I'd really like to get rid of __clk_hfpll_init_once() if possible...
|
||||
|
||||
drivers/clk/qcom/Makefile | 1 +
|
||||
drivers/clk/qcom/clk-hfpll.c | 253 +++++++++++++++++++++++++++++++++++++++++++
|
||||
drivers/clk/qcom/clk-hfpll.h | 54 +++++++++
|
||||
3 files changed, 308 insertions(+)
|
||||
create mode 100644 drivers/clk/qcom/clk-hfpll.c
|
||||
create mode 100644 drivers/clk/qcom/clk-hfpll.h
|
||||
|
||||
--- a/drivers/clk/qcom/Makefile
|
||||
+++ b/drivers/clk/qcom/Makefile
|
||||
@@ -6,6 +6,7 @@ clk-qcom-y += clk-pll.o
|
||||
clk-qcom-y += clk-rcg.o
|
||||
clk-qcom-y += clk-rcg2.o
|
||||
clk-qcom-y += clk-branch.o
|
||||
+clk-qcom-y += clk-hfpll.o
|
||||
clk-qcom-y += reset.o
|
||||
|
||||
obj-$(CONFIG_APQ_GCC_8084) += gcc-apq8084.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/clk-hfpll.c
|
||||
@@ -0,0 +1,253 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2013-2014, 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/export.h>
|
||||
+#include <linux/regmap.h>
|
||||
+#include <linux/delay.h>
|
||||
+#include <linux/err.h>
|
||||
+#include <linux/clk-provider.h>
|
||||
+#include <linux/spinlock.h>
|
||||
+
|
||||
+#include "clk-regmap.h"
|
||||
+#include "clk-hfpll.h"
|
||||
+
|
||||
+#define PLL_OUTCTRL BIT(0)
|
||||
+#define PLL_BYPASSNL BIT(1)
|
||||
+#define PLL_RESET_N BIT(2)
|
||||
+
|
||||
+/* Initialize a HFPLL at a given rate and enable it. */
|
||||
+static void __clk_hfpll_init_once(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+
|
||||
+ if (likely(h->init_done))
|
||||
+ return;
|
||||
+
|
||||
+ /* Configure PLL parameters for integer mode. */
|
||||
+ if (hd->config_val)
|
||||
+ regmap_write(regmap, hd->config_reg, hd->config_val);
|
||||
+ regmap_write(regmap, hd->m_reg, 0);
|
||||
+ regmap_write(regmap, hd->n_reg, 1);
|
||||
+
|
||||
+ if (hd->user_reg) {
|
||||
+ u32 regval = hd->user_val;
|
||||
+ unsigned long rate;
|
||||
+
|
||||
+ rate = __clk_get_rate(hw->clk);
|
||||
+
|
||||
+ /* Pick the right VCO. */
|
||||
+ if (hd->user_vco_mask && rate > hd->low_vco_max_rate)
|
||||
+ regval |= hd->user_vco_mask;
|
||||
+ regmap_write(regmap, hd->user_reg, regval);
|
||||
+ }
|
||||
+
|
||||
+ if (hd->droop_reg)
|
||||
+ regmap_write(regmap, hd->droop_reg, hd->droop_val);
|
||||
+
|
||||
+ h->init_done = true;
|
||||
+}
|
||||
+
|
||||
+static void __clk_hfpll_enable(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+ u32 val;
|
||||
+
|
||||
+ __clk_hfpll_init_once(hw);
|
||||
+
|
||||
+ /* Disable PLL bypass mode. */
|
||||
+ regmap_update_bits(regmap, hd->mode_reg, PLL_BYPASSNL, PLL_BYPASSNL);
|
||||
+
|
||||
+ /*
|
||||
+ * H/W requires a 5us delay between disabling the bypass and
|
||||
+ * de-asserting the reset. Delay 10us just to be safe.
|
||||
+ */
|
||||
+ udelay(10);
|
||||
+
|
||||
+ /* De-assert active-low PLL reset. */
|
||||
+ regmap_update_bits(regmap, hd->mode_reg, PLL_RESET_N, PLL_RESET_N);
|
||||
+
|
||||
+ /* Wait for PLL to lock. */
|
||||
+ if (hd->status_reg) {
|
||||
+ do {
|
||||
+ regmap_read(regmap, hd->status_reg, &val);
|
||||
+ } while (!(val & BIT(hd->lock_bit)));
|
||||
+ } else {
|
||||
+ udelay(60);
|
||||
+ }
|
||||
+
|
||||
+ /* Enable PLL output. */
|
||||
+ regmap_update_bits(regmap, hd->mode_reg, PLL_OUTCTRL, PLL_OUTCTRL);
|
||||
+}
|
||||
+
|
||||
+/* Enable an already-configured HFPLL. */
|
||||
+static int clk_hfpll_enable(struct clk_hw *hw)
|
||||
+{
|
||||
+ unsigned long flags;
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+ u32 mode;
|
||||
+
|
||||
+ spin_lock_irqsave(&h->lock, flags);
|
||||
+ regmap_read(regmap, hd->mode_reg, &mode);
|
||||
+ if (!(mode & (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)))
|
||||
+ __clk_hfpll_enable(hw);
|
||||
+ spin_unlock_irqrestore(&h->lock, flags);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void __clk_hfpll_disable(struct clk_hfpll *h)
|
||||
+{
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+
|
||||
+ /*
|
||||
+ * Disable the PLL output, disable test mode, enable the bypass mode,
|
||||
+ * and assert the reset.
|
||||
+ */
|
||||
+ regmap_update_bits(regmap, hd->mode_reg,
|
||||
+ PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL, 0);
|
||||
+}
|
||||
+
|
||||
+static void clk_hfpll_disable(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ unsigned long flags;
|
||||
+
|
||||
+ spin_lock_irqsave(&h->lock, flags);
|
||||
+ __clk_hfpll_disable(h);
|
||||
+ spin_unlock_irqrestore(&h->lock, flags);
|
||||
+}
|
||||
+
|
||||
+static long clk_hfpll_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long *parent_rate)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ unsigned long rrate;
|
||||
+
|
||||
+ rate = clamp(rate, hd->min_rate, hd->max_rate);
|
||||
+
|
||||
+ rrate = DIV_ROUND_UP(rate, *parent_rate) * *parent_rate;
|
||||
+ if (rrate > hd->max_rate)
|
||||
+ rrate -= *parent_rate;
|
||||
+
|
||||
+ return rrate;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * For optimization reasons, assumes no downstream clocks are actively using
|
||||
+ * it.
|
||||
+ */
|
||||
+static int clk_hfpll_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long parent_rate)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+ unsigned long flags;
|
||||
+ u32 l_val, val;
|
||||
+ bool enabled;
|
||||
+
|
||||
+ l_val = rate / parent_rate;
|
||||
+
|
||||
+ spin_lock_irqsave(&h->lock, flags);
|
||||
+
|
||||
+ enabled = __clk_is_enabled(hw->clk);
|
||||
+ if (enabled)
|
||||
+ __clk_hfpll_disable(h);
|
||||
+
|
||||
+ /* Pick the right VCO. */
|
||||
+ if (hd->user_reg && hd->user_vco_mask) {
|
||||
+ regmap_read(regmap, hd->user_reg, &val);
|
||||
+ if (rate <= hd->low_vco_max_rate)
|
||||
+ val &= ~hd->user_vco_mask;
|
||||
+ else
|
||||
+ val |= hd->user_vco_mask;
|
||||
+ regmap_write(regmap, hd->user_reg, val);
|
||||
+ }
|
||||
+
|
||||
+ regmap_write(regmap, hd->l_reg, l_val);
|
||||
+
|
||||
+ if (enabled)
|
||||
+ __clk_hfpll_enable(hw);
|
||||
+
|
||||
+ spin_unlock_irqrestore(&h->lock, flags);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static unsigned long clk_hfpll_recalc_rate(struct clk_hw *hw,
|
||||
+ unsigned long parent_rate)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+ u32 l_val;
|
||||
+
|
||||
+ regmap_read(regmap, hd->l_reg, &l_val);
|
||||
+
|
||||
+ return l_val * parent_rate;
|
||||
+}
|
||||
+
|
||||
+static void clk_hfpll_init(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+ u32 mode, status;
|
||||
+
|
||||
+ regmap_read(regmap, hd->mode_reg, &mode);
|
||||
+ if (mode != (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)) {
|
||||
+ __clk_hfpll_init_once(hw);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ if (hd->status_reg) {
|
||||
+ regmap_read(regmap, hd->status_reg, &status);
|
||||
+ if (!(status & BIT(hd->lock_bit))) {
|
||||
+ WARN(1, "HFPLL %s is ON, but not locked!\n",
|
||||
+ __clk_get_name(hw->clk));
|
||||
+ clk_hfpll_disable(hw);
|
||||
+ __clk_hfpll_init_once(hw);
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static int hfpll_is_enabled(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+ u32 mode;
|
||||
+
|
||||
+ regmap_read(regmap, hd->mode_reg, &mode);
|
||||
+ mode &= 0x7;
|
||||
+ return mode == (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL);
|
||||
+}
|
||||
+
|
||||
+const struct clk_ops clk_ops_hfpll = {
|
||||
+ .enable = clk_hfpll_enable,
|
||||
+ .disable = clk_hfpll_disable,
|
||||
+ .is_enabled = hfpll_is_enabled,
|
||||
+ .round_rate = clk_hfpll_round_rate,
|
||||
+ .set_rate = clk_hfpll_set_rate,
|
||||
+ .recalc_rate = clk_hfpll_recalc_rate,
|
||||
+ .init = clk_hfpll_init,
|
||||
+};
|
||||
+EXPORT_SYMBOL_GPL(clk_ops_hfpll);
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/clk-hfpll.h
|
||||
@@ -0,0 +1,54 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2013-2014, 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 __QCOM_CLK_HFPLL_H__
|
||||
+#define __QCOM_CLK_HFPLL_H__
|
||||
+
|
||||
+#include <linux/clk-provider.h>
|
||||
+#include <linux/spinlock.h>
|
||||
+#include "clk-regmap.h"
|
||||
+
|
||||
+struct hfpll_data {
|
||||
+ u32 mode_reg;
|
||||
+ u32 l_reg;
|
||||
+ u32 m_reg;
|
||||
+ u32 n_reg;
|
||||
+ u32 user_reg;
|
||||
+ u32 droop_reg;
|
||||
+ u32 config_reg;
|
||||
+ u32 status_reg;
|
||||
+ u8 lock_bit;
|
||||
+
|
||||
+ u32 droop_val;
|
||||
+ u32 config_val;
|
||||
+ u32 user_val;
|
||||
+ u32 user_vco_mask;
|
||||
+ unsigned long low_vco_max_rate;
|
||||
+
|
||||
+ unsigned long min_rate;
|
||||
+ unsigned long max_rate;
|
||||
+};
|
||||
+
|
||||
+struct clk_hfpll {
|
||||
+ struct hfpll_data const *d;
|
||||
+ int init_done;
|
||||
+
|
||||
+ struct clk_regmap clkr;
|
||||
+ spinlock_t lock;
|
||||
+};
|
||||
+
|
||||
+#define to_clk_hfpll(_hw) \
|
||||
+ container_of(to_clk_regmap(_hw), struct clk_hfpll, clkr)
|
||||
+
|
||||
+extern const struct clk_ops clk_ops_hfpll;
|
||||
+
|
||||
+#endif
|
|
@ -0,0 +1,206 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,06/13] clk: qcom: Add HFPLL driver
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063231
|
||||
Message-Id: <1426920332-9340-7-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>, <devicetree@vger.kernel.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:25 -0700
|
||||
|
||||
On some devices (MSM8974 for example), the HFPLLs are
|
||||
instantiated within the Krait processor subsystem as separate
|
||||
register regions. Add a driver for these PLLs so that we can
|
||||
provide HFPLL clocks for use by the system.
|
||||
|
||||
Cc: <devicetree@vger.kernel.org>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
.../devicetree/bindings/clock/qcom,hfpll.txt | 40 ++++++++
|
||||
drivers/clk/qcom/Kconfig | 8 ++
|
||||
drivers/clk/qcom/Makefile | 1 +
|
||||
drivers/clk/qcom/hfpll.c | 109 +++++++++++++++++++++
|
||||
4 files changed, 158 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/clock/qcom,hfpll.txt
|
||||
create mode 100644 drivers/clk/qcom/hfpll.c
|
||||
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/clock/qcom,hfpll.txt
|
||||
@@ -0,0 +1,40 @@
|
||||
+High-Frequency PLL (HFPLL)
|
||||
+
|
||||
+PROPERTIES
|
||||
+
|
||||
+- compatible:
|
||||
+ Usage: required
|
||||
+ Value type: <string>
|
||||
+ Definition: must be "qcom,hfpll"
|
||||
+
|
||||
+- reg:
|
||||
+ Usage: required
|
||||
+ Value type: <prop-encoded-array>
|
||||
+ Definition: address and size of HPLL registers. An optional second
|
||||
+ element specifies the address and size of the alias
|
||||
+ register region.
|
||||
+
|
||||
+- clock-output-names:
|
||||
+ Usage: required
|
||||
+ Value type: <string>
|
||||
+ Definition: Name of the PLL. Typically hfpllX where X is a CPU number
|
||||
+ starting at 0. Otherwise hfpll_Y where Y is more specific
|
||||
+ such as "l2".
|
||||
+
|
||||
+Example:
|
||||
+
|
||||
+1) An HFPLL for the L2 cache.
|
||||
+
|
||||
+ clock-controller@f9016000 {
|
||||
+ compatible = "qcom,hfpll";
|
||||
+ reg = <0xf9016000 0x30>;
|
||||
+ clock-output-names = "hfpll_l2";
|
||||
+ };
|
||||
+
|
||||
+2) An HFPLL for CPU0. This HFPLL has the alias register region.
|
||||
+
|
||||
+ clock-controller@f908a000 {
|
||||
+ compatible = "qcom,hfpll";
|
||||
+ reg = <0xf908a000 0x30>, <0xf900a000 0x30>;
|
||||
+ clock-output-names = "hfpll0";
|
||||
+ };
|
||||
--- a/drivers/clk/qcom/Kconfig
|
||||
+++ b/drivers/clk/qcom/Kconfig
|
||||
@@ -70,3 +70,11 @@ config MSM_MMCC_8974
|
||||
Support for the multimedia clock controller on msm8974 devices.
|
||||
Say Y if you want to support multimedia devices such as display,
|
||||
graphics, video encode/decode, camera, etc.
|
||||
+
|
||||
+config QCOM_HFPLL
|
||||
+ tristate "High-Frequency PLL (HFPLL) Clock Controller"
|
||||
+ depends on COMMON_CLK_QCOM
|
||||
+ help
|
||||
+ Support for the high-frequency PLLs present on Qualcomm devices.
|
||||
+ Say Y if you want to support CPU frequency scaling on devices
|
||||
+ such as MSM8974, APQ8084, etc.
|
||||
--- a/drivers/clk/qcom/Makefile
|
||||
+++ b/drivers/clk/qcom/Makefile
|
||||
@@ -17,3 +17,4 @@ obj-$(CONFIG_MSM_GCC_8960) += gcc-msm896
|
||||
obj-$(CONFIG_MSM_GCC_8974) += gcc-msm8974.o
|
||||
obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8960.o
|
||||
obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o
|
||||
+obj-$(CONFIG_QCOM_HFPLL) += hfpll.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/hfpll.c
|
||||
@@ -0,0 +1,109 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2013-2014, 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/init.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/clk.h>
|
||||
+#include <linux/clk-provider.h>
|
||||
+#include <linux/regmap.h>
|
||||
+
|
||||
+#include "clk-regmap.h"
|
||||
+#include "clk-hfpll.h"
|
||||
+
|
||||
+static const struct hfpll_data hdata = {
|
||||
+ .mode_reg = 0x00,
|
||||
+ .l_reg = 0x04,
|
||||
+ .m_reg = 0x08,
|
||||
+ .n_reg = 0x0c,
|
||||
+ .user_reg = 0x10,
|
||||
+ .config_reg = 0x14,
|
||||
+ .config_val = 0x430405d,
|
||||
+ .status_reg = 0x1c,
|
||||
+ .lock_bit = 16,
|
||||
+
|
||||
+ .user_val = 0x8,
|
||||
+ .user_vco_mask = 0x100000,
|
||||
+ .low_vco_max_rate = 1248000000,
|
||||
+ .min_rate = 537600000UL,
|
||||
+ .max_rate = 2900000000UL,
|
||||
+};
|
||||
+
|
||||
+static const struct of_device_id qcom_hfpll_match_table[] = {
|
||||
+ { .compatible = "qcom,hfpll" },
|
||||
+ { }
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, qcom_hfpll_match_table);
|
||||
+
|
||||
+static const struct regmap_config hfpll_regmap_config = {
|
||||
+ .reg_bits = 32,
|
||||
+ .reg_stride = 4,
|
||||
+ .val_bits = 32,
|
||||
+ .max_register = 0x30,
|
||||
+ .fast_io = true,
|
||||
+};
|
||||
+
|
||||
+static int qcom_hfpll_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct clk *clk;
|
||||
+ struct resource *res;
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ void __iomem *base;
|
||||
+ struct regmap *regmap;
|
||||
+ struct clk_hfpll *h;
|
||||
+ struct clk_init_data init = {
|
||||
+ .parent_names = (const char *[]){ "xo" },
|
||||
+ .num_parents = 1,
|
||||
+ .ops = &clk_ops_hfpll,
|
||||
+ };
|
||||
+
|
||||
+ h = devm_kzalloc(dev, sizeof(*h), GFP_KERNEL);
|
||||
+ if (!h)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
+ base = devm_ioremap_resource(dev, res);
|
||||
+ if (IS_ERR(base))
|
||||
+ return PTR_ERR(base);
|
||||
+
|
||||
+ regmap = devm_regmap_init_mmio(&pdev->dev, base, &hfpll_regmap_config);
|
||||
+ if (IS_ERR(regmap))
|
||||
+ return PTR_ERR(regmap);
|
||||
+
|
||||
+ if (of_property_read_string_index(dev->of_node, "clock-output-names",
|
||||
+ 0, &init.name))
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ h->d = &hdata;
|
||||
+ h->clkr.hw.init = &init;
|
||||
+ spin_lock_init(&h->lock);
|
||||
+
|
||||
+ clk = devm_clk_register_regmap(&pdev->dev, &h->clkr);
|
||||
+
|
||||
+ return PTR_ERR_OR_ZERO(clk);
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver qcom_hfpll_driver = {
|
||||
+ .probe = qcom_hfpll_probe,
|
||||
+ .driver = {
|
||||
+ .name = "qcom-hfpll",
|
||||
+ .of_match_table = qcom_hfpll_match_table,
|
||||
+ },
|
||||
+};
|
||||
+module_platform_driver(qcom_hfpll_driver);
|
||||
+
|
||||
+MODULE_DESCRIPTION("QCOM HFPLL Clock Driver");
|
||||
+MODULE_LICENSE("GPL v2");
|
||||
+MODULE_ALIAS("platform:qcom-hfpll");
|
|
@ -0,0 +1,127 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,08/13] clk: qcom: Add IPQ806X's HFPLLs
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063241
|
||||
Message-Id: <1426920332-9340-9-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:27 -0700
|
||||
|
||||
Describe the HFPLLs present on IPQ806X devices.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
drivers/clk/qcom/gcc-ipq806x.c | 83 ++++++++++++++++++++++++++++++++++++++++++
|
||||
1 file changed, 83 insertions(+)
|
||||
|
||||
--- a/drivers/clk/qcom/gcc-ipq806x.c
|
||||
+++ b/drivers/clk/qcom/gcc-ipq806x.c
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "clk-pll.h"
|
||||
#include "clk-rcg.h"
|
||||
#include "clk-branch.h"
|
||||
+#include "clk-hfpll.h"
|
||||
#include "reset.h"
|
||||
|
||||
static struct clk_pll pll0 = {
|
||||
@@ -102,6 +103,85 @@ static struct clk_regmap pll8_vote = {
|
||||
},
|
||||
};
|
||||
|
||||
+static struct hfpll_data hfpll0_data = {
|
||||
+ .mode_reg = 0x3200,
|
||||
+ .l_reg = 0x3208,
|
||||
+ .m_reg = 0x320c,
|
||||
+ .n_reg = 0x3210,
|
||||
+ .config_reg = 0x3204,
|
||||
+ .status_reg = 0x321c,
|
||||
+ .config_val = 0x7845c665,
|
||||
+ .droop_reg = 0x3214,
|
||||
+ .droop_val = 0x0108c000,
|
||||
+ .min_rate = 600000000UL,
|
||||
+ .max_rate = 1800000000UL,
|
||||
+};
|
||||
+
|
||||
+static struct clk_hfpll hfpll0 = {
|
||||
+ .d = &hfpll0_data,
|
||||
+ .clkr.hw.init = &(struct clk_init_data){
|
||||
+ .parent_names = (const char *[]){ "pxo" },
|
||||
+ .num_parents = 1,
|
||||
+ .name = "hfpll0",
|
||||
+ .ops = &clk_ops_hfpll,
|
||||
+ .flags = CLK_IGNORE_UNUSED,
|
||||
+ },
|
||||
+ .lock = __SPIN_LOCK_UNLOCKED(hfpll0.lock),
|
||||
+};
|
||||
+
|
||||
+static struct hfpll_data hfpll1_data = {
|
||||
+ .mode_reg = 0x3240,
|
||||
+ .l_reg = 0x3248,
|
||||
+ .m_reg = 0x324c,
|
||||
+ .n_reg = 0x3250,
|
||||
+ .config_reg = 0x3244,
|
||||
+ .status_reg = 0x325c,
|
||||
+ .config_val = 0x7845c665,
|
||||
+ .droop_reg = 0x3314,
|
||||
+ .droop_val = 0x0108c000,
|
||||
+ .min_rate = 600000000UL,
|
||||
+ .max_rate = 1800000000UL,
|
||||
+};
|
||||
+
|
||||
+static struct clk_hfpll hfpll1 = {
|
||||
+ .d = &hfpll1_data,
|
||||
+ .clkr.hw.init = &(struct clk_init_data){
|
||||
+ .parent_names = (const char *[]){ "pxo" },
|
||||
+ .num_parents = 1,
|
||||
+ .name = "hfpll1",
|
||||
+ .ops = &clk_ops_hfpll,
|
||||
+ .flags = CLK_IGNORE_UNUSED,
|
||||
+ },
|
||||
+ .lock = __SPIN_LOCK_UNLOCKED(hfpll1.lock),
|
||||
+};
|
||||
+
|
||||
+static struct hfpll_data hfpll_l2_data = {
|
||||
+ .mode_reg = 0x3300,
|
||||
+ .l_reg = 0x3308,
|
||||
+ .m_reg = 0x330c,
|
||||
+ .n_reg = 0x3310,
|
||||
+ .config_reg = 0x3304,
|
||||
+ .status_reg = 0x331c,
|
||||
+ .config_val = 0x7845c665,
|
||||
+ .droop_reg = 0x3314,
|
||||
+ .droop_val = 0x0108c000,
|
||||
+ .min_rate = 600000000UL,
|
||||
+ .max_rate = 1800000000UL,
|
||||
+};
|
||||
+
|
||||
+static struct clk_hfpll hfpll_l2 = {
|
||||
+ .d = &hfpll_l2_data,
|
||||
+ .clkr.hw.init = &(struct clk_init_data){
|
||||
+ .parent_names = (const char *[]){ "pxo" },
|
||||
+ .num_parents = 1,
|
||||
+ .name = "hfpll_l2",
|
||||
+ .ops = &clk_ops_hfpll,
|
||||
+ .flags = CLK_IGNORE_UNUSED,
|
||||
+ },
|
||||
+ .lock = __SPIN_LOCK_UNLOCKED(hfpll_l2.lock),
|
||||
+};
|
||||
+
|
||||
+
|
||||
static struct clk_pll pll14 = {
|
||||
.l_reg = 0x31c4,
|
||||
.m_reg = 0x31c8,
|
||||
@@ -2261,6 +2341,9 @@ static struct clk_regmap *gcc_ipq806x_cl
|
||||
[USB_FS1_XCVR_SRC] = &usb_fs1_xcvr_clk_src.clkr,
|
||||
[USB_FS1_XCVR_CLK] = &usb_fs1_xcvr_clk.clkr,
|
||||
[USB_FS1_SYSTEM_CLK] = &usb_fs1_sys_clk.clkr,
|
||||
+ [PLL9] = &hfpll0.clkr,
|
||||
+ [PLL10] = &hfpll1.clkr,
|
||||
+ [PLL12] = &hfpll_l2.clkr,
|
||||
};
|
||||
|
||||
static const struct qcom_reset_map gcc_ipq806x_resets[] = {
|
|
@ -0,0 +1,271 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,09/13] clk: qcom: Add support for Krait clocks
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063251
|
||||
Message-Id: <1426920332-9340-10-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:28 -0700
|
||||
|
||||
The Krait clocks are made up of a series of muxes and a divider
|
||||
that choose between a fixed rate clock and dedicated HFPLLs for
|
||||
each CPU. Instead of using mmio accesses to remux parents, the
|
||||
Krait implementation exposes the remux control via cp15
|
||||
registers. Support these clocks.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
drivers/clk/qcom/Kconfig | 4 ++
|
||||
drivers/clk/qcom/Makefile | 1 +
|
||||
drivers/clk/qcom/clk-krait.c | 166 +++++++++++++++++++++++++++++++++++++++++++
|
||||
drivers/clk/qcom/clk-krait.h | 49 +++++++++++++
|
||||
4 files changed, 220 insertions(+)
|
||||
create mode 100644 drivers/clk/qcom/clk-krait.c
|
||||
create mode 100644 drivers/clk/qcom/clk-krait.h
|
||||
|
||||
--- a/drivers/clk/qcom/Kconfig
|
||||
+++ b/drivers/clk/qcom/Kconfig
|
||||
@@ -78,3 +78,7 @@ config QCOM_HFPLL
|
||||
Support for the high-frequency PLLs present on Qualcomm devices.
|
||||
Say Y if you want to support CPU frequency scaling on devices
|
||||
such as MSM8974, APQ8084, etc.
|
||||
+
|
||||
+config KRAIT_CLOCKS
|
||||
+ bool
|
||||
+ select KRAIT_L2_ACCESSORS
|
||||
--- a/drivers/clk/qcom/Makefile
|
||||
+++ b/drivers/clk/qcom/Makefile
|
||||
@@ -6,6 +6,7 @@ clk-qcom-y += clk-pll.o
|
||||
clk-qcom-y += clk-rcg.o
|
||||
clk-qcom-y += clk-rcg2.o
|
||||
clk-qcom-y += clk-branch.o
|
||||
+clk-qcom-$(CONFIG_KRAIT_CLOCKS) += clk-krait.o
|
||||
clk-qcom-y += clk-hfpll.o
|
||||
clk-qcom-y += reset.o
|
||||
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/clk-krait.c
|
||||
@@ -0,0 +1,166 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2013-2014, 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/clk-provider.h>
|
||||
+#include <linux/spinlock.h>
|
||||
+
|
||||
+#include <asm/krait-l2-accessors.h>
|
||||
+
|
||||
+#include "clk-krait.h"
|
||||
+
|
||||
+/* Secondary and primary muxes share the same cp15 register */
|
||||
+static DEFINE_SPINLOCK(krait_clock_reg_lock);
|
||||
+
|
||||
+#define LPL_SHIFT 8
|
||||
+static void __krait_mux_set_sel(struct krait_mux_clk *mux, int sel)
|
||||
+{
|
||||
+ unsigned long flags;
|
||||
+ u32 regval;
|
||||
+
|
||||
+ spin_lock_irqsave(&krait_clock_reg_lock, flags);
|
||||
+ regval = krait_get_l2_indirect_reg(mux->offset);
|
||||
+ regval &= ~(mux->mask << mux->shift);
|
||||
+ regval |= (sel & mux->mask) << mux->shift;
|
||||
+ if (mux->lpl) {
|
||||
+ regval &= ~(mux->mask << (mux->shift + LPL_SHIFT));
|
||||
+ regval |= (sel & mux->mask) << (mux->shift + LPL_SHIFT);
|
||||
+ }
|
||||
+ krait_set_l2_indirect_reg(mux->offset, regval);
|
||||
+ spin_unlock_irqrestore(&krait_clock_reg_lock, flags);
|
||||
+
|
||||
+ /* Wait for switch to complete. */
|
||||
+ mb();
|
||||
+ udelay(1);
|
||||
+}
|
||||
+
|
||||
+static int krait_mux_set_parent(struct clk_hw *hw, u8 index)
|
||||
+{
|
||||
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
|
||||
+ u32 sel;
|
||||
+
|
||||
+ sel = clk_mux_reindex(index, mux->parent_map, 0);
|
||||
+ mux->en_mask = sel;
|
||||
+ /* Don't touch mux if CPU is off as it won't work */
|
||||
+ if (__clk_is_enabled(hw->clk))
|
||||
+ __krait_mux_set_sel(mux, sel);
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static u8 krait_mux_get_parent(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
|
||||
+ u32 sel;
|
||||
+
|
||||
+ sel = krait_get_l2_indirect_reg(mux->offset);
|
||||
+ sel >>= mux->shift;
|
||||
+ sel &= mux->mask;
|
||||
+ mux->en_mask = sel;
|
||||
+
|
||||
+ return clk_mux_get_parent(hw, sel, mux->parent_map, 0);
|
||||
+}
|
||||
+
|
||||
+static struct clk_hw *krait_mux_get_safe_parent(struct clk_hw *hw)
|
||||
+{
|
||||
+ int i;
|
||||
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
|
||||
+ int num_parents = __clk_get_num_parents(hw->clk);
|
||||
+
|
||||
+ i = mux->safe_sel;
|
||||
+ for (i = 0; i < num_parents; i++)
|
||||
+ if (mux->safe_sel == mux->parent_map[i])
|
||||
+ break;
|
||||
+
|
||||
+ return __clk_get_hw(clk_get_parent_by_index(hw->clk, i));
|
||||
+}
|
||||
+
|
||||
+static int krait_mux_enable(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
|
||||
+
|
||||
+ __krait_mux_set_sel(mux, mux->en_mask);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void krait_mux_disable(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
|
||||
+
|
||||
+ __krait_mux_set_sel(mux, mux->safe_sel);
|
||||
+}
|
||||
+
|
||||
+const struct clk_ops krait_mux_clk_ops = {
|
||||
+ .enable = krait_mux_enable,
|
||||
+ .disable = krait_mux_disable,
|
||||
+ .set_parent = krait_mux_set_parent,
|
||||
+ .get_parent = krait_mux_get_parent,
|
||||
+ .determine_rate = __clk_mux_determine_rate_closest,
|
||||
+ .get_safe_parent = krait_mux_get_safe_parent,
|
||||
+};
|
||||
+EXPORT_SYMBOL_GPL(krait_mux_clk_ops);
|
||||
+
|
||||
+/* The divider can divide by 2, 4, 6 and 8. But we only really need div-2. */
|
||||
+static long krait_div2_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long *parent_rate)
|
||||
+{
|
||||
+ *parent_rate = __clk_round_rate(__clk_get_parent(hw->clk), rate * 2);
|
||||
+ return DIV_ROUND_UP(*parent_rate, 2);
|
||||
+}
|
||||
+
|
||||
+static int krait_div2_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long parent_rate)
|
||||
+{
|
||||
+ struct krait_div2_clk *d = to_krait_div2_clk(hw);
|
||||
+ unsigned long flags;
|
||||
+ u32 val;
|
||||
+ u32 mask = BIT(d->width) - 1;
|
||||
+
|
||||
+ if (d->lpl)
|
||||
+ mask = mask << (d->shift + LPL_SHIFT) | mask << d->shift;
|
||||
+
|
||||
+ spin_lock_irqsave(&krait_clock_reg_lock, flags);
|
||||
+ val = krait_get_l2_indirect_reg(d->offset);
|
||||
+ val &= ~mask;
|
||||
+ krait_set_l2_indirect_reg(d->offset, val);
|
||||
+ spin_unlock_irqrestore(&krait_clock_reg_lock, flags);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static unsigned long
|
||||
+krait_div2_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
||||
+{
|
||||
+ struct krait_div2_clk *d = to_krait_div2_clk(hw);
|
||||
+ u32 mask = BIT(d->width) - 1;
|
||||
+ u32 div;
|
||||
+
|
||||
+ div = krait_get_l2_indirect_reg(d->offset);
|
||||
+ div >>= d->shift;
|
||||
+ div &= mask;
|
||||
+ div = (div + 1) * 2;
|
||||
+
|
||||
+ return DIV_ROUND_UP(parent_rate, div);
|
||||
+}
|
||||
+
|
||||
+const struct clk_ops krait_div2_clk_ops = {
|
||||
+ .round_rate = krait_div2_round_rate,
|
||||
+ .set_rate = krait_div2_set_rate,
|
||||
+ .recalc_rate = krait_div2_recalc_rate,
|
||||
+};
|
||||
+EXPORT_SYMBOL_GPL(krait_div2_clk_ops);
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/clk-krait.h
|
||||
@@ -0,0 +1,49 @@
|
||||
+/*
|
||||
+ * 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 __QCOM_CLK_KRAIT_H
|
||||
+#define __QCOM_CLK_KRAIT_H
|
||||
+
|
||||
+#include <linux/clk-provider.h>
|
||||
+
|
||||
+struct krait_mux_clk {
|
||||
+ unsigned int *parent_map;
|
||||
+ bool has_safe_parent;
|
||||
+ u8 safe_sel;
|
||||
+ u32 offset;
|
||||
+ u32 mask;
|
||||
+ u32 shift;
|
||||
+ u32 en_mask;
|
||||
+ bool lpl;
|
||||
+
|
||||
+ struct clk_hw hw;
|
||||
+};
|
||||
+
|
||||
+#define to_krait_mux_clk(_hw) container_of(_hw, struct krait_mux_clk, hw)
|
||||
+
|
||||
+extern const struct clk_ops krait_mux_clk_ops;
|
||||
+
|
||||
+struct krait_div2_clk {
|
||||
+ u32 offset;
|
||||
+ u8 width;
|
||||
+ u32 shift;
|
||||
+ bool lpl;
|
||||
+
|
||||
+ struct clk_hw hw;
|
||||
+};
|
||||
+
|
||||
+#define to_krait_div2_clk(_hw) container_of(_hw, struct krait_div2_clk, hw)
|
||||
+
|
||||
+extern const struct clk_ops krait_div2_clk_ops;
|
||||
+
|
||||
+#endif
|
|
@ -0,0 +1,205 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,10/13] clk: qcom: Add KPSS ACC/GCC driver
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063201
|
||||
Message-Id: <1426920332-9340-11-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>, <devicetree@vger.kernel.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:29 -0700
|
||||
|
||||
The ACC and GCC regions present in KPSSv1 contain registers to
|
||||
control clocks and power to each Krait CPU and L2. For CPUfreq
|
||||
purposes probe these devices and expose a mux clock that chooses
|
||||
between PXO and PLL8.
|
||||
|
||||
Cc: <devicetree@vger.kernel.org>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
.../devicetree/bindings/arm/msm/qcom,kpss-acc.txt | 7 ++
|
||||
.../devicetree/bindings/arm/msm/qcom,kpss-gcc.txt | 28 +++++++
|
||||
drivers/clk/qcom/Kconfig | 8 ++
|
||||
drivers/clk/qcom/Makefile | 1 +
|
||||
drivers/clk/qcom/kpss-xcc.c | 95 ++++++++++++++++++++++
|
||||
5 files changed, 139 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/arm/msm/qcom,kpss-gcc.txt
|
||||
create mode 100644 drivers/clk/qcom/kpss-xcc.c
|
||||
|
||||
--- a/Documentation/devicetree/bindings/arm/msm/qcom,kpss-acc.txt
|
||||
+++ b/Documentation/devicetree/bindings/arm/msm/qcom,kpss-acc.txt
|
||||
@@ -21,10 +21,17 @@ PROPERTIES
|
||||
the register region. An optional second element specifies
|
||||
the base address and size of the alias register region.
|
||||
|
||||
+- clock-output-names:
|
||||
+ Usage: optional
|
||||
+ Value type: <string>
|
||||
+ Definition: Name of the output clock. Typically acpuX_aux where X is a
|
||||
+ CPU number starting at 0.
|
||||
+
|
||||
Example:
|
||||
|
||||
clock-controller@2088000 {
|
||||
compatible = "qcom,kpss-acc-v2";
|
||||
reg = <0x02088000 0x1000>,
|
||||
<0x02008000 0x1000>;
|
||||
+ clock-output-names = "acpu0_aux";
|
||||
};
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/arm/msm/qcom,kpss-gcc.txt
|
||||
@@ -0,0 +1,28 @@
|
||||
+Krait Processor Sub-system (KPSS) Global Clock Controller (GCC)
|
||||
+
|
||||
+PROPERTIES
|
||||
+
|
||||
+- compatible:
|
||||
+ Usage: required
|
||||
+ Value type: <string>
|
||||
+ Definition: should be one of:
|
||||
+ "qcom,kpss-gcc"
|
||||
+
|
||||
+- reg:
|
||||
+ Usage: required
|
||||
+ Value type: <prop-encoded-array>
|
||||
+ Definition: base address and size of the register region
|
||||
+
|
||||
+- clock-output-names:
|
||||
+ Usage: required
|
||||
+ Value type: <string>
|
||||
+ Definition: Name of the output clock. Typically acpu_l2_aux indicating
|
||||
+ an L2 cache auxiliary clock.
|
||||
+
|
||||
+Example:
|
||||
+
|
||||
+ l2cc: clock-controller@2011000 {
|
||||
+ compatible = "qcom,kpss-gcc";
|
||||
+ reg = <0x2011000 0x1000>;
|
||||
+ clock-output-names = "acpu_l2_aux";
|
||||
+ };
|
||||
--- a/drivers/clk/qcom/Kconfig
|
||||
+++ b/drivers/clk/qcom/Kconfig
|
||||
@@ -79,6 +79,14 @@ config QCOM_HFPLL
|
||||
Say Y if you want to support CPU frequency scaling on devices
|
||||
such as MSM8974, APQ8084, etc.
|
||||
|
||||
+config KPSS_XCC
|
||||
+ tristate "KPSS Clock Controller"
|
||||
+ depends on COMMON_CLK_QCOM
|
||||
+ help
|
||||
+ Support for the Krait ACC and GCC clock controllers. Say Y
|
||||
+ if you want to support CPU frequency scaling on devices such
|
||||
+ as MSM8960, APQ8064, etc.
|
||||
+
|
||||
config KRAIT_CLOCKS
|
||||
bool
|
||||
select KRAIT_L2_ACCESSORS
|
||||
--- a/drivers/clk/qcom/Makefile
|
||||
+++ b/drivers/clk/qcom/Makefile
|
||||
@@ -18,4 +18,5 @@ obj-$(CONFIG_MSM_GCC_8960) += gcc-msm896
|
||||
obj-$(CONFIG_MSM_GCC_8974) += gcc-msm8974.o
|
||||
obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8960.o
|
||||
obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o
|
||||
+obj-$(CONFIG_KPSS_XCC) += kpss-xcc.o
|
||||
obj-$(CONFIG_QCOM_HFPLL) += hfpll.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/kpss-xcc.c
|
||||
@@ -0,0 +1,95 @@
|
||||
+/* Copyright (c) 2014-2015, 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/init.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/err.h>
|
||||
+#include <linux/io.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/of_device.h>
|
||||
+#include <linux/clk.h>
|
||||
+#include <linux/clk-provider.h>
|
||||
+
|
||||
+static const char *aux_parents[] = {
|
||||
+ "pll8_vote",
|
||||
+ "pxo",
|
||||
+};
|
||||
+
|
||||
+static unsigned int aux_parent_map[] = {
|
||||
+ 3,
|
||||
+ 0,
|
||||
+};
|
||||
+
|
||||
+static const struct of_device_id kpss_xcc_match_table[] = {
|
||||
+ { .compatible = "qcom,kpss-acc-v1", .data = (void *)1UL },
|
||||
+ { .compatible = "qcom,kpss-gcc" },
|
||||
+ {}
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, kpss_xcc_match_table);
|
||||
+
|
||||
+static int kpss_xcc_driver_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ const struct of_device_id *id;
|
||||
+ struct clk *clk;
|
||||
+ struct resource *res;
|
||||
+ void __iomem *base;
|
||||
+ const char *name;
|
||||
+
|
||||
+ id = of_match_device(kpss_xcc_match_table, &pdev->dev);
|
||||
+ if (!id)
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
+ base = devm_ioremap_resource(&pdev->dev, res);
|
||||
+ if (IS_ERR(base))
|
||||
+ return PTR_ERR(base);
|
||||
+
|
||||
+ if (id->data) {
|
||||
+ if (of_property_read_string_index(pdev->dev.of_node,
|
||||
+ "clock-output-names", 0, &name))
|
||||
+ return -ENODEV;
|
||||
+ base += 0x14;
|
||||
+ } else {
|
||||
+ name = "acpu_l2_aux";
|
||||
+ base += 0x28;
|
||||
+ }
|
||||
+
|
||||
+ clk = clk_register_mux_table(&pdev->dev, name, aux_parents,
|
||||
+ ARRAY_SIZE(aux_parents), 0, base, 0, 0x3,
|
||||
+ 0, aux_parent_map, NULL);
|
||||
+
|
||||
+ platform_set_drvdata(pdev, clk);
|
||||
+
|
||||
+ return PTR_ERR_OR_ZERO(clk);
|
||||
+}
|
||||
+
|
||||
+static int kpss_xcc_driver_remove(struct platform_device *pdev)
|
||||
+{
|
||||
+ clk_unregister_mux(platform_get_drvdata(pdev));
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver kpss_xcc_driver = {
|
||||
+ .probe = kpss_xcc_driver_probe,
|
||||
+ .remove = kpss_xcc_driver_remove,
|
||||
+ .driver = {
|
||||
+ .name = "kpss-xcc",
|
||||
+ .of_match_table = kpss_xcc_match_table,
|
||||
+ },
|
||||
+};
|
||||
+module_platform_driver(kpss_xcc_driver);
|
||||
+
|
||||
+MODULE_DESCRIPTION("Krait Processor Sub System (KPSS) Clock Driver");
|
||||
+MODULE_LICENSE("GPL v2");
|
||||
+MODULE_ALIAS("platform:kpss-xcc");
|
|
@ -0,0 +1,435 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,11/13] clk: qcom: Add Krait clock controller driver
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063121
|
||||
Message-Id: <1426920332-9340-12-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>, <devicetree@vger.kernel.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:30 -0700
|
||||
|
||||
The Krait CPU clocks are made up of a primary mux and secondary
|
||||
mux for each CPU and the L2, controlled via cp15 accessors. For
|
||||
Kraits within KPSSv1 each secondary mux accepts a different aux
|
||||
source, but on KPSSv2 each secondary mux accepts the same aux
|
||||
source.
|
||||
|
||||
Cc: <devicetree@vger.kernel.org>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
.../devicetree/bindings/clock/qcom,krait-cc.txt | 22 ++
|
||||
drivers/clk/qcom/Kconfig | 8 +
|
||||
drivers/clk/qcom/Makefile | 1 +
|
||||
drivers/clk/qcom/krait-cc.c | 352 +++++++++++++++++++++
|
||||
4 files changed, 383 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/clock/qcom,krait-cc.txt
|
||||
create mode 100644 drivers/clk/qcom/krait-cc.c
|
||||
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/clock/qcom,krait-cc.txt
|
||||
@@ -0,0 +1,22 @@
|
||||
+Krait Clock Controller
|
||||
+
|
||||
+PROPERTIES
|
||||
+
|
||||
+- compatible:
|
||||
+ Usage: required
|
||||
+ Value type: <string>
|
||||
+ Definition: must be one of:
|
||||
+ "qcom,krait-cc-v1"
|
||||
+ "qcom,krait-cc-v2"
|
||||
+
|
||||
+- #clock-cells:
|
||||
+ Usage: required
|
||||
+ Value type: <u32>
|
||||
+ Definition: must be 1
|
||||
+
|
||||
+Example:
|
||||
+
|
||||
+ kraitcc: clock-controller {
|
||||
+ compatible = "qcom,krait-cc-v1";
|
||||
+ #clock-cells = <1>;
|
||||
+ };
|
||||
--- a/drivers/clk/qcom/Kconfig
|
||||
+++ b/drivers/clk/qcom/Kconfig
|
||||
@@ -87,6 +87,14 @@ config KPSS_XCC
|
||||
if you want to support CPU frequency scaling on devices such
|
||||
as MSM8960, APQ8064, etc.
|
||||
|
||||
+config KRAITCC
|
||||
+ tristate "Krait Clock Controller"
|
||||
+ depends on COMMON_CLK_QCOM && ARM
|
||||
+ select KRAIT_CLOCKS
|
||||
+ help
|
||||
+ Support for the Krait CPU clocks on Qualcomm devices.
|
||||
+ Say Y if you want to support CPU frequency scaling.
|
||||
+
|
||||
config KRAIT_CLOCKS
|
||||
bool
|
||||
select KRAIT_L2_ACCESSORS
|
||||
--- a/drivers/clk/qcom/Makefile
|
||||
+++ b/drivers/clk/qcom/Makefile
|
||||
@@ -20,3 +20,4 @@ obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8
|
||||
obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o
|
||||
obj-$(CONFIG_KPSS_XCC) += kpss-xcc.o
|
||||
obj-$(CONFIG_QCOM_HFPLL) += hfpll.o
|
||||
+obj-$(CONFIG_KRAITCC) += krait-cc.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/krait-cc.c
|
||||
@@ -0,0 +1,352 @@
|
||||
+/* Copyright (c) 2013-2015, 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/init.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/err.h>
|
||||
+#include <linux/io.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/of_device.h>
|
||||
+#include <linux/clk.h>
|
||||
+#include <linux/clk-provider.h>
|
||||
+#include <linux/slab.h>
|
||||
+
|
||||
+#include "clk-krait.h"
|
||||
+
|
||||
+static unsigned int sec_mux_map[] = {
|
||||
+ 2,
|
||||
+ 0,
|
||||
+};
|
||||
+
|
||||
+static unsigned int pri_mux_map[] = {
|
||||
+ 1,
|
||||
+ 2,
|
||||
+ 0,
|
||||
+};
|
||||
+
|
||||
+static int
|
||||
+krait_add_div(struct device *dev, int id, const char *s, unsigned offset)
|
||||
+{
|
||||
+ struct krait_div2_clk *div;
|
||||
+ struct clk_init_data init = {
|
||||
+ .num_parents = 1,
|
||||
+ .ops = &krait_div2_clk_ops,
|
||||
+ .flags = CLK_SET_RATE_PARENT,
|
||||
+ };
|
||||
+ const char *p_names[1];
|
||||
+ struct clk *clk;
|
||||
+
|
||||
+ div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
|
||||
+ if (!div)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ div->width = 2;
|
||||
+ div->shift = 6;
|
||||
+ div->lpl = id >= 0;
|
||||
+ div->offset = offset;
|
||||
+ div->hw.init = &init;
|
||||
+
|
||||
+ init.name = kasprintf(GFP_KERNEL, "hfpll%s_div", s);
|
||||
+ if (!init.name)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ init.parent_names = p_names;
|
||||
+ p_names[0] = kasprintf(GFP_KERNEL, "hfpll%s", s);
|
||||
+ if (!p_names[0]) {
|
||||
+ kfree(init.name);
|
||||
+ return -ENOMEM;
|
||||
+ }
|
||||
+
|
||||
+ clk = devm_clk_register(dev, &div->hw);
|
||||
+ kfree(p_names[0]);
|
||||
+ kfree(init.name);
|
||||
+
|
||||
+ return PTR_ERR_OR_ZERO(clk);
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+krait_add_sec_mux(struct device *dev, int id, const char *s, unsigned offset,
|
||||
+ bool unique_aux)
|
||||
+{
|
||||
+ struct krait_mux_clk *mux;
|
||||
+ static const char *sec_mux_list[] = {
|
||||
+ "acpu_aux",
|
||||
+ "qsb",
|
||||
+ };
|
||||
+ struct clk_init_data init = {
|
||||
+ .parent_names = sec_mux_list,
|
||||
+ .num_parents = ARRAY_SIZE(sec_mux_list),
|
||||
+ .ops = &krait_mux_clk_ops,
|
||||
+ .flags = CLK_SET_RATE_PARENT,
|
||||
+ };
|
||||
+ struct clk *clk;
|
||||
+
|
||||
+ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
|
||||
+ if (!mux)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ mux->offset = offset;
|
||||
+ mux->lpl = id >= 0;
|
||||
+ mux->has_safe_parent = true;
|
||||
+ mux->safe_sel = 2;
|
||||
+ mux->mask = 0x3;
|
||||
+ mux->shift = 2;
|
||||
+ mux->parent_map = sec_mux_map;
|
||||
+ mux->hw.init = &init;
|
||||
+
|
||||
+ init.name = kasprintf(GFP_KERNEL, "krait%s_sec_mux", s);
|
||||
+ if (!init.name)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ if (unique_aux) {
|
||||
+ sec_mux_list[0] = kasprintf(GFP_KERNEL, "acpu%s_aux", s);
|
||||
+ if (!sec_mux_list[0]) {
|
||||
+ clk = ERR_PTR(-ENOMEM);
|
||||
+ goto err_aux;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ clk = devm_clk_register(dev, &mux->hw);
|
||||
+
|
||||
+ if (unique_aux)
|
||||
+ kfree(sec_mux_list[0]);
|
||||
+err_aux:
|
||||
+ kfree(init.name);
|
||||
+ return PTR_ERR_OR_ZERO(clk);
|
||||
+}
|
||||
+
|
||||
+static struct clk *
|
||||
+krait_add_pri_mux(struct device *dev, int id, const char *s, unsigned offset)
|
||||
+{
|
||||
+ struct krait_mux_clk *mux;
|
||||
+ const char *p_names[3];
|
||||
+ struct clk_init_data init = {
|
||||
+ .parent_names = p_names,
|
||||
+ .num_parents = ARRAY_SIZE(p_names),
|
||||
+ .ops = &krait_mux_clk_ops,
|
||||
+ .flags = CLK_SET_RATE_PARENT,
|
||||
+ };
|
||||
+ struct clk *clk;
|
||||
+
|
||||
+ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
|
||||
+ if (!mux)
|
||||
+ return ERR_PTR(-ENOMEM);
|
||||
+
|
||||
+ mux->has_safe_parent = true;
|
||||
+ mux->safe_sel = 0;
|
||||
+ mux->mask = 0x3;
|
||||
+ mux->shift = 0;
|
||||
+ mux->offset = offset;
|
||||
+ mux->lpl = id >= 0;
|
||||
+ mux->parent_map = pri_mux_map;
|
||||
+ mux->hw.init = &init;
|
||||
+
|
||||
+ init.name = kasprintf(GFP_KERNEL, "krait%s_pri_mux", s);
|
||||
+ if (!init.name)
|
||||
+ return ERR_PTR(-ENOMEM);
|
||||
+
|
||||
+ p_names[0] = kasprintf(GFP_KERNEL, "hfpll%s", s);
|
||||
+ if (!p_names[0]) {
|
||||
+ clk = ERR_PTR(-ENOMEM);
|
||||
+ goto err_p0;
|
||||
+ }
|
||||
+
|
||||
+ p_names[1] = kasprintf(GFP_KERNEL, "hfpll%s_div", s);
|
||||
+ if (!p_names[1]) {
|
||||
+ clk = ERR_PTR(-ENOMEM);
|
||||
+ goto err_p1;
|
||||
+ }
|
||||
+
|
||||
+ p_names[2] = kasprintf(GFP_KERNEL, "krait%s_sec_mux", s);
|
||||
+ if (!p_names[2]) {
|
||||
+ clk = ERR_PTR(-ENOMEM);
|
||||
+ goto err_p2;
|
||||
+ }
|
||||
+
|
||||
+ clk = devm_clk_register(dev, &mux->hw);
|
||||
+
|
||||
+ kfree(p_names[2]);
|
||||
+err_p2:
|
||||
+ kfree(p_names[1]);
|
||||
+err_p1:
|
||||
+ kfree(p_names[0]);
|
||||
+err_p0:
|
||||
+ kfree(init.name);
|
||||
+ return clk;
|
||||
+}
|
||||
+
|
||||
+/* id < 0 for L2, otherwise id == physical CPU number */
|
||||
+static struct clk *krait_add_clks(struct device *dev, int id, bool unique_aux)
|
||||
+{
|
||||
+ int ret;
|
||||
+ unsigned offset;
|
||||
+ void *p = NULL;
|
||||
+ const char *s;
|
||||
+ struct clk *clk;
|
||||
+
|
||||
+ if (id >= 0) {
|
||||
+ offset = 0x4501 + (0x1000 * id);
|
||||
+ s = p = kasprintf(GFP_KERNEL, "%d", id);
|
||||
+ if (!s)
|
||||
+ return ERR_PTR(-ENOMEM);
|
||||
+ } else {
|
||||
+ offset = 0x500;
|
||||
+ s = "_l2";
|
||||
+ }
|
||||
+
|
||||
+ ret = krait_add_div(dev, id, s, offset);
|
||||
+ if (ret) {
|
||||
+ clk = ERR_PTR(ret);
|
||||
+ goto err;
|
||||
+ }
|
||||
+
|
||||
+ ret = krait_add_sec_mux(dev, id, s, offset, unique_aux);
|
||||
+ if (ret) {
|
||||
+ clk = ERR_PTR(ret);
|
||||
+ goto err;
|
||||
+ }
|
||||
+
|
||||
+ clk = krait_add_pri_mux(dev, id, s, offset);
|
||||
+err:
|
||||
+ kfree(p);
|
||||
+ return clk;
|
||||
+}
|
||||
+
|
||||
+static struct clk *krait_of_get(struct of_phandle_args *clkspec, void *data)
|
||||
+{
|
||||
+ unsigned int idx = clkspec->args[0];
|
||||
+ struct clk **clks = data;
|
||||
+
|
||||
+ if (idx >= 5) {
|
||||
+ pr_err("%s: invalid clock index %d\n", __func__, idx);
|
||||
+ return ERR_PTR(-EINVAL);
|
||||
+ }
|
||||
+
|
||||
+ return clks[idx] ? : ERR_PTR(-ENODEV);
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id krait_cc_match_table[] = {
|
||||
+ { .compatible = "qcom,krait-cc-v1", (void *)1UL },
|
||||
+ { .compatible = "qcom,krait-cc-v2" },
|
||||
+ {}
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, krait_cc_match_table);
|
||||
+
|
||||
+static int krait_cc_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ const struct of_device_id *id;
|
||||
+ unsigned long cur_rate, aux_rate;
|
||||
+ int cpu;
|
||||
+ struct clk *clk;
|
||||
+ struct clk **clks;
|
||||
+ struct clk *l2_pri_mux_clk;
|
||||
+
|
||||
+ id = of_match_device(krait_cc_match_table, dev);
|
||||
+ if (!id)
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ /* Rate is 1 because 0 causes problems for __clk_mux_determine_rate */
|
||||
+ clk = clk_register_fixed_rate(dev, "qsb", NULL, CLK_IS_ROOT, 1);
|
||||
+ if (IS_ERR(clk))
|
||||
+ return PTR_ERR(clk);
|
||||
+
|
||||
+ if (!id->data) {
|
||||
+ clk = clk_register_fixed_factor(dev, "acpu_aux",
|
||||
+ "gpll0_vote", 0, 1, 2);
|
||||
+ if (IS_ERR(clk))
|
||||
+ return PTR_ERR(clk);
|
||||
+ }
|
||||
+
|
||||
+ /* Krait configurations have at most 4 CPUs and one L2 */
|
||||
+ clks = devm_kcalloc(dev, 5, sizeof(*clks), GFP_KERNEL);
|
||||
+ if (!clks)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ for_each_possible_cpu(cpu) {
|
||||
+ clk = krait_add_clks(dev, cpu, id->data);
|
||||
+ if (IS_ERR(clk))
|
||||
+ return PTR_ERR(clk);
|
||||
+ clks[cpu] = clk;
|
||||
+ }
|
||||
+
|
||||
+ l2_pri_mux_clk = krait_add_clks(dev, -1, id->data);
|
||||
+ if (IS_ERR(l2_pri_mux_clk))
|
||||
+ return PTR_ERR(l2_pri_mux_clk);
|
||||
+ clks[4] = l2_pri_mux_clk;
|
||||
+
|
||||
+ /*
|
||||
+ * We don't want the CPU or L2 clocks to be turned off at late init
|
||||
+ * if CPUFREQ or HOTPLUG configs are disabled. So, bump up the
|
||||
+ * refcount of these clocks. Any cpufreq/hotplug manager can assume
|
||||
+ * that the clocks have already been prepared and enabled by the time
|
||||
+ * they take over.
|
||||
+ */
|
||||
+ for_each_online_cpu(cpu) {
|
||||
+ clk_prepare_enable(l2_pri_mux_clk);
|
||||
+ WARN(clk_prepare_enable(clks[cpu]),
|
||||
+ "Unable to turn on CPU%d clock", cpu);
|
||||
+ }
|
||||
+
|
||||
+ /*
|
||||
+ * Force reinit of HFPLLs and muxes to overwrite any potential
|
||||
+ * incorrect configuration of HFPLLs and muxes by the bootloader.
|
||||
+ * While at it, also make sure the cores are running at known rates
|
||||
+ * and print the current rate.
|
||||
+ *
|
||||
+ * The clocks are set to aux clock rate first to make sure the
|
||||
+ * secondary mux is not sourcing off of QSB. The rate is then set to
|
||||
+ * two different rates to force a HFPLL reinit under all
|
||||
+ * circumstances.
|
||||
+ */
|
||||
+ cur_rate = clk_get_rate(l2_pri_mux_clk);
|
||||
+ aux_rate = 384000000;
|
||||
+ if (cur_rate == 1) {
|
||||
+ pr_info("L2 @ QSB rate. Forcing new rate.\n");
|
||||
+ cur_rate = aux_rate;
|
||||
+ }
|
||||
+ clk_set_rate(l2_pri_mux_clk, aux_rate);
|
||||
+ clk_set_rate(l2_pri_mux_clk, 2);
|
||||
+ clk_set_rate(l2_pri_mux_clk, cur_rate);
|
||||
+ pr_info("L2 @ %lu KHz\n", clk_get_rate(l2_pri_mux_clk) / 1000);
|
||||
+ for_each_possible_cpu(cpu) {
|
||||
+ clk = clks[cpu];
|
||||
+ cur_rate = clk_get_rate(clk);
|
||||
+ if (cur_rate == 1) {
|
||||
+ pr_info("CPU%d @ QSB rate. Forcing new rate.\n", cpu);
|
||||
+ cur_rate = aux_rate;
|
||||
+ }
|
||||
+ clk_set_rate(clk, aux_rate);
|
||||
+ clk_set_rate(clk, 2);
|
||||
+ clk_set_rate(clk, cur_rate);
|
||||
+ pr_info("CPU%d @ %lu KHz\n", cpu, clk_get_rate(clk) / 1000);
|
||||
+ }
|
||||
+
|
||||
+ of_clk_add_provider(dev->of_node, krait_of_get, clks);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver krait_cc_driver = {
|
||||
+ .probe = krait_cc_probe,
|
||||
+ .driver = {
|
||||
+ .name = "krait-cc",
|
||||
+ .of_match_table = krait_cc_match_table,
|
||||
+ },
|
||||
+};
|
||||
+module_platform_driver(krait_cc_driver);
|
||||
+
|
||||
+MODULE_DESCRIPTION("Krait CPU Clock Driver");
|
||||
+MODULE_LICENSE("GPL v2");
|
||||
+MODULE_ALIAS("platform:krait-cc");
|
|
@ -0,0 +1,304 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,12/13] cpufreq: Add module to register cpufreq on Krait CPUs
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063191
|
||||
Message-Id: <1426920332-9340-13-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>, <devicetree@vger.kernel.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:31 -0700
|
||||
|
||||
Register a cpufreq-generic device whenever we detect that a
|
||||
"qcom,krait" compatible CPU is present in DT.
|
||||
|
||||
Cc: <devicetree@vger.kernel.org>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
.../devicetree/bindings/arm/msm/qcom,pvs.txt | 38 ++++
|
||||
drivers/cpufreq/Kconfig.arm | 9 +
|
||||
drivers/cpufreq/Makefile | 1 +
|
||||
drivers/cpufreq/qcom-cpufreq.c | 204 +++++++++++++++++++++
|
||||
4 files changed, 252 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/arm/msm/qcom,pvs.txt
|
||||
create mode 100644 drivers/cpufreq/qcom-cpufreq.c
|
||||
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/arm/msm/qcom,pvs.txt
|
||||
@@ -0,0 +1,38 @@
|
||||
+Qualcomm Process Voltage Scaling Tables
|
||||
+
|
||||
+The node name is required to be "qcom,pvs". There shall only be one
|
||||
+such node present in the root of the tree.
|
||||
+
|
||||
+PROPERTIES
|
||||
+
|
||||
+- qcom,pvs-format-a or qcom,pvs-format-b:
|
||||
+ Usage: required
|
||||
+ Value type: <empty>
|
||||
+ Definition: Indicates the format of qcom,speedX-pvsY-bin-vZ properties.
|
||||
+ If qcom,pvs-format-a is used the table is two columns
|
||||
+ (frequency and voltage in that order). If qcom,pvs-format-b is used the table is three columns (frequency, voltage,
|
||||
+ and current in that order).
|
||||
+
|
||||
+- qcom,speedX-pvsY-bin-vZ:
|
||||
+ Usage: required
|
||||
+ Value type: <prop-encoded-array>
|
||||
+ Definition: The PVS table corresponding to the speed bin X, pvs bin Y,
|
||||
+ and version Z.
|
||||
+Example:
|
||||
+
|
||||
+ qcom,pvs {
|
||||
+ qcom,pvs-format-a;
|
||||
+ qcom,speed0-pvs0-bin-v0 =
|
||||
+ < 384000000 950000 >,
|
||||
+ < 486000000 975000 >,
|
||||
+ < 594000000 1000000 >,
|
||||
+ < 702000000 1025000 >,
|
||||
+ < 810000000 1075000 >,
|
||||
+ < 918000000 1100000 >,
|
||||
+ < 1026000000 1125000 >,
|
||||
+ < 1134000000 1175000 >,
|
||||
+ < 1242000000 1200000 >,
|
||||
+ < 1350000000 1225000 >,
|
||||
+ < 1458000000 1237500 >,
|
||||
+ < 1512000000 1250000 >;
|
||||
+ };
|
||||
--- a/drivers/cpufreq/Kconfig.arm
|
||||
+++ b/drivers/cpufreq/Kconfig.arm
|
||||
@@ -129,6 +129,15 @@ config ARM_OMAP2PLUS_CPUFREQ
|
||||
depends on ARCH_OMAP2PLUS
|
||||
default ARCH_OMAP2PLUS
|
||||
|
||||
+config ARM_QCOM_CPUFREQ
|
||||
+ tristate "Qualcomm based"
|
||||
+ depends on ARCH_QCOM
|
||||
+ select PM_OPP
|
||||
+ help
|
||||
+ This adds the CPUFreq driver for Qualcomm SoC based boards.
|
||||
+
|
||||
+ If in doubt, say N.
|
||||
+
|
||||
config ARM_S3C_CPUFREQ
|
||||
bool
|
||||
help
|
||||
--- a/drivers/cpufreq/Makefile
|
||||
+++ b/drivers/cpufreq/Makefile
|
||||
@@ -64,6 +64,7 @@ obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += o
|
||||
obj-$(CONFIG_PXA25x) += pxa2xx-cpufreq.o
|
||||
obj-$(CONFIG_PXA27x) += pxa2xx-cpufreq.o
|
||||
obj-$(CONFIG_PXA3xx) += pxa3xx-cpufreq.o
|
||||
+obj-$(CONFIG_ARM_QCOM_CPUFREQ) += qcom-cpufreq.o
|
||||
obj-$(CONFIG_ARM_S3C24XX_CPUFREQ) += s3c24xx-cpufreq.o
|
||||
obj-$(CONFIG_ARM_S3C24XX_CPUFREQ_DEBUGFS) += s3c24xx-cpufreq-debugfs.o
|
||||
obj-$(CONFIG_ARM_S3C2410_CPUFREQ) += s3c2410-cpufreq.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/cpufreq/qcom-cpufreq.c
|
||||
@@ -0,0 +1,204 @@
|
||||
+/* Copyright (c) 2014, 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/cpu.h>
|
||||
+#include <linux/err.h>
|
||||
+#include <linux/init.h>
|
||||
+#include <linux/io.h>
|
||||
+#include <linux/kernel.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/pm_opp.h>
|
||||
+#include <linux/slab.h>
|
||||
+#include <linux/cpufreq-dt.h>
|
||||
+
|
||||
+static void __init get_krait_bin_format_a(int *speed, int *pvs, int *pvs_ver)
|
||||
+{
|
||||
+ void __iomem *base;
|
||||
+ u32 pte_efuse;
|
||||
+
|
||||
+ *speed = *pvs = *pvs_ver = 0;
|
||||
+
|
||||
+ base = ioremap(0x007000c0, 4);
|
||||
+ if (!base) {
|
||||
+ pr_warn("Unable to read efuse data. Defaulting to 0!\n");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ pte_efuse = readl_relaxed(base);
|
||||
+ iounmap(base);
|
||||
+
|
||||
+ *speed = pte_efuse & 0xf;
|
||||
+ if (*speed == 0xf)
|
||||
+ *speed = (pte_efuse >> 4) & 0xf;
|
||||
+
|
||||
+ if (*speed == 0xf) {
|
||||
+ *speed = 0;
|
||||
+ pr_warn("Speed bin: Defaulting to %d\n", *speed);
|
||||
+ } else {
|
||||
+ pr_info("Speed bin: %d\n", *speed);
|
||||
+ }
|
||||
+
|
||||
+ *pvs = (pte_efuse >> 10) & 0x7;
|
||||
+ if (*pvs == 0x7)
|
||||
+ *pvs = (pte_efuse >> 13) & 0x7;
|
||||
+
|
||||
+ if (*pvs == 0x7) {
|
||||
+ *pvs = 0;
|
||||
+ pr_warn("PVS bin: Defaulting to %d\n", *pvs);
|
||||
+ } else {
|
||||
+ pr_info("PVS bin: %d\n", *pvs);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static void __init get_krait_bin_format_b(int *speed, int *pvs, int *pvs_ver)
|
||||
+{
|
||||
+ u32 pte_efuse, redundant_sel;
|
||||
+ void __iomem *base;
|
||||
+
|
||||
+ *speed = 0;
|
||||
+ *pvs = 0;
|
||||
+ *pvs_ver = 0;
|
||||
+
|
||||
+ base = ioremap(0xfc4b80b0, 8);
|
||||
+ if (!base) {
|
||||
+ pr_warn("Unable to read efuse data. Defaulting to 0!\n");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ pte_efuse = readl_relaxed(base);
|
||||
+ redundant_sel = (pte_efuse >> 24) & 0x7;
|
||||
+ *speed = pte_efuse & 0x7;
|
||||
+ /* 4 bits of PVS are in efuse register bits 31, 8-6. */
|
||||
+ *pvs = ((pte_efuse >> 28) & 0x8) | ((pte_efuse >> 6) & 0x7);
|
||||
+ *pvs_ver = (pte_efuse >> 4) & 0x3;
|
||||
+
|
||||
+ switch (redundant_sel) {
|
||||
+ case 1:
|
||||
+ *speed = (pte_efuse >> 27) & 0xf;
|
||||
+ break;
|
||||
+ case 2:
|
||||
+ *pvs = (pte_efuse >> 27) & 0xf;
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ /* Check SPEED_BIN_BLOW_STATUS */
|
||||
+ if (pte_efuse & BIT(3)) {
|
||||
+ pr_info("Speed bin: %d\n", *speed);
|
||||
+ } else {
|
||||
+ pr_warn("Speed bin not set. Defaulting to 0!\n");
|
||||
+ *speed = 0;
|
||||
+ }
|
||||
+
|
||||
+ /* Check PVS_BLOW_STATUS */
|
||||
+ pte_efuse = readl_relaxed(base + 0x4) & BIT(21);
|
||||
+ if (pte_efuse) {
|
||||
+ pr_info("PVS bin: %d\n", *pvs);
|
||||
+ } else {
|
||||
+ pr_warn("PVS bin not set. Defaulting to 0!\n");
|
||||
+ *pvs = 0;
|
||||
+ }
|
||||
+
|
||||
+ pr_info("PVS version: %d\n", *pvs_ver);
|
||||
+ iounmap(base);
|
||||
+}
|
||||
+
|
||||
+static int __init qcom_cpufreq_populate_opps(void)
|
||||
+{
|
||||
+ int len, rows, cols, i, k, speed, pvs, pvs_ver;
|
||||
+ char table_name[] = "qcom,speedXX-pvsXX-bin-vXX";
|
||||
+ struct device_node *np;
|
||||
+ struct device *dev;
|
||||
+ int cpu = 0;
|
||||
+
|
||||
+ np = of_find_node_by_name(NULL, "qcom,pvs");
|
||||
+ if (!np)
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ if (of_property_read_bool(np, "qcom,pvs-format-a")) {
|
||||
+ get_krait_bin_format_a(&speed, &pvs, &pvs_ver);
|
||||
+ cols = 2;
|
||||
+ } else if (of_property_read_bool(np, "qcom,pvs-format-b")) {
|
||||
+ get_krait_bin_format_b(&speed, &pvs, &pvs_ver);
|
||||
+ cols = 3;
|
||||
+ } else {
|
||||
+ return -ENODEV;
|
||||
+ }
|
||||
+
|
||||
+ snprintf(table_name, sizeof(table_name),
|
||||
+ "qcom,speed%d-pvs%d-bin-v%d", speed, pvs, pvs_ver);
|
||||
+
|
||||
+ if (!of_find_property(np, table_name, &len))
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ len /= sizeof(u32);
|
||||
+ if (len % cols || len == 0)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ rows = len / cols;
|
||||
+
|
||||
+ for (i = 0, k = 0; i < rows; i++) {
|
||||
+ u32 freq, volt;
|
||||
+
|
||||
+ of_property_read_u32_index(np, table_name, k++, &freq);
|
||||
+ of_property_read_u32_index(np, table_name, k++, &volt);
|
||||
+ while (k % cols)
|
||||
+ k++; /* Skip uA entries if present */
|
||||
+ for (cpu = 0; cpu < num_possible_cpus(); cpu++) {
|
||||
+ dev = get_cpu_device(cpu);
|
||||
+ if (!dev)
|
||||
+ return -ENODEV;
|
||||
+ if (dev_pm_opp_add(dev, freq, volt))
|
||||
+ pr_warn("failed to add OPP %u\n", freq);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int __init qcom_cpufreq_driver_init(void)
|
||||
+{
|
||||
+ struct cpufreq_dt_platform_data pdata = { .independent_clocks = true };
|
||||
+ struct platform_device_info devinfo = {
|
||||
+ .name = "cpufreq-dt",
|
||||
+ .data = &pdata,
|
||||
+ .size_data = sizeof(pdata),
|
||||
+ };
|
||||
+ struct device *cpu_dev;
|
||||
+ struct device_node *np;
|
||||
+ int ret;
|
||||
+
|
||||
+ cpu_dev = get_cpu_device(0);
|
||||
+ if (!cpu_dev)
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ np = of_node_get(cpu_dev->of_node);
|
||||
+ if (!np)
|
||||
+ return -ENOENT;
|
||||
+
|
||||
+ if (!of_device_is_compatible(np, "qcom,krait")) {
|
||||
+ of_node_put(np);
|
||||
+ return -ENODEV;
|
||||
+ }
|
||||
+ of_node_put(np);
|
||||
+
|
||||
+ ret = qcom_cpufreq_populate_opps();
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ return PTR_ERR_OR_ZERO(platform_device_register_full(&devinfo));
|
||||
+}
|
||||
+module_init(qcom_cpufreq_driver_init);
|
||||
+
|
||||
+MODULE_DESCRIPTION("Qualcomm CPUfreq driver");
|
||||
+MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,100 @@
|
|||
--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
@@ -23,6 +23,11 @@
|
||||
next-level-cache = <&L2>;
|
||||
qcom,acc = <&acc0>;
|
||||
qcom,saw = <&saw0>;
|
||||
+ clocks = <&kraitcc 0>;
|
||||
+ clock-names = "cpu";
|
||||
+ clock-latency = <100000>;
|
||||
+ core-supply = <&smb208_s2a>;
|
||||
+ voltage-tolerance = <5>;
|
||||
};
|
||||
|
||||
cpu@1 {
|
||||
@@ -33,11 +38,24 @@
|
||||
next-level-cache = <&L2>;
|
||||
qcom,acc = <&acc1>;
|
||||
qcom,saw = <&saw1>;
|
||||
+ clocks = <&kraitcc 1>;
|
||||
+ clock-names = "cpu";
|
||||
+ clock-latency = <100000>;
|
||||
+ core-supply = <&smb208_s2b>;
|
||||
};
|
||||
|
||||
L2: l2-cache {
|
||||
compatible = "cache";
|
||||
cache-level = <2>;
|
||||
+ clocks = <&kraitcc 4>;
|
||||
+ clock-names = "cache";
|
||||
+ cache-points-kHz = <
|
||||
+ /* kHz uV CPU kHz */
|
||||
+ 1200000 1150000 1200000
|
||||
+ 1000000 1100000 600000
|
||||
+ 384000 1100000 384000
|
||||
+ >;
|
||||
+ vdd_dig-supply = <&smb208_s1a>;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -70,6 +88,46 @@
|
||||
};
|
||||
};
|
||||
|
||||
+ kraitcc: clock-controller {
|
||||
+ compatible = "qcom,krait-cc-v1";
|
||||
+ #clock-cells = <1>;
|
||||
+ };
|
||||
+
|
||||
+ qcom,pvs {
|
||||
+ qcom,pvs-format-a;
|
||||
+ qcom,speed0-pvs0-bin-v0 =
|
||||
+ < 1400000000 1250000 >,
|
||||
+ < 1200000000 1200000 >,
|
||||
+ < 1000000000 1150000 >,
|
||||
+ < 800000000 1100000 >,
|
||||
+ < 600000000 1050000 >,
|
||||
+ < 384000000 1000000 >;
|
||||
+
|
||||
+ qcom,speed0-pvs1-bin-v0 =
|
||||
+ < 1400000000 1175000 >,
|
||||
+ < 1200000000 1125000 >,
|
||||
+ < 1000000000 1075000 >,
|
||||
+ < 800000000 1025000 >,
|
||||
+ < 600000000 975000 >,
|
||||
+ < 384000000 925000 >;
|
||||
+
|
||||
+ qcom,speed0-pvs2-bin-v0 =
|
||||
+ < 1400000000 1125000 >,
|
||||
+ < 1200000000 1075000 >,
|
||||
+ < 1000000000 1025000 >,
|
||||
+ < 800000000 995000 >,
|
||||
+ < 600000000 925000 >,
|
||||
+ < 384000000 875000 >;
|
||||
+
|
||||
+ qcom,speed0-pvs3-bin-v0 =
|
||||
+ < 1400000000 1050000 >,
|
||||
+ < 1200000000 1000000 >,
|
||||
+ < 1000000000 950000 >,
|
||||
+ < 800000000 900000 >,
|
||||
+ < 600000000 850000 >,
|
||||
+ < 384000000 800000 >;
|
||||
+ };
|
||||
+
|
||||
soc: soc {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
@@ -170,11 +228,13 @@
|
||||
acc0: clock-controller@2088000 {
|
||||
compatible = "qcom,kpss-acc-v1";
|
||||
reg = <0x02088000 0x1000>, <0x02008000 0x1000>;
|
||||
+ clock-output-names = "acpu0_aux";
|
||||
};
|
||||
|
||||
acc1: clock-controller@2098000 {
|
||||
compatible = "qcom,kpss-acc-v1";
|
||||
reg = <0x02098000 0x1000>, <0x02008000 0x1000>;
|
||||
+ clock-output-names = "acpu1_aux";
|
||||
};
|
||||
|
||||
l2cc: clock-controller@2011000 {
|
|
@ -0,0 +1,461 @@
|
|||
From dd77db4143290689d3a5e1ec61627233d0711b66 Mon Sep 17 00:00:00 2001
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Date: Fri, 30 May 2014 16:36:11 -0700
|
||||
Subject: [PATCH] FROMLIST: cpufreq: Add a cpufreq-krait based on cpufreq-cpu0
|
||||
|
||||
Krait processors have individual clocks for each CPU that can
|
||||
scale independently from one another. cpufreq-cpu0 is fairly
|
||||
close to this, but assumes that there is only one clock for all
|
||||
CPUs. Add a driver to support the Krait configuration.
|
||||
|
||||
TODO: Merge into cpufreq-cpu0? Or make generic?
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
drivers/cpufreq/Kconfig | 13 +++
|
||||
drivers/cpufreq/Makefile | 1 +
|
||||
drivers/cpufreq/cpufreq-krait.c | 190 ++++++++++++++++++++++++++++++++++++++++
|
||||
3 files changed, 204 insertions(+)
|
||||
create mode 100644 drivers/cpufreq/cpufreq-krait.c
|
||||
|
||||
--- a/drivers/cpufreq/Kconfig
|
||||
+++ b/drivers/cpufreq/Kconfig
|
||||
@@ -196,6 +196,19 @@ config CPUFREQ_DT
|
||||
|
||||
If in doubt, say N.
|
||||
|
||||
+config GENERIC_CPUFREQ_KRAIT
|
||||
+ tristate "Krait cpufreq driver"
|
||||
+ depends on HAVE_CLK && OF
|
||||
+ # if CPU_THERMAL is on and THERMAL=m, CPU0 cannot be =y:
|
||||
+ depends on !CPU_THERMAL || THERMAL
|
||||
+ select PM_OPP
|
||||
+ help
|
||||
+ This adds a generic cpufreq driver for CPU0 frequency management.
|
||||
+ It supports both uniprocessor (UP) and symmetric multiprocessor (SMP)
|
||||
+ systems which share clock and voltage across all CPUs.
|
||||
+
|
||||
+ If in doubt, say N.
|
||||
+
|
||||
menu "x86 CPU frequency scaling drivers"
|
||||
depends on X86
|
||||
source "drivers/cpufreq/Kconfig.x86"
|
||||
--- a/drivers/cpufreq/Makefile
|
||||
+++ b/drivers/cpufreq/Makefile
|
||||
@@ -14,6 +14,7 @@ obj-$(CONFIG_CPU_FREQ_GOV_CONSERVATIVE)
|
||||
obj-$(CONFIG_CPU_FREQ_GOV_COMMON) += cpufreq_governor.o
|
||||
|
||||
obj-$(CONFIG_CPUFREQ_DT) += cpufreq-dt.o
|
||||
+obj-$(CONFIG_GENERIC_CPUFREQ_KRAIT) += cpufreq-krait.o
|
||||
|
||||
##################################################################################
|
||||
# x86 drivers.
|
||||
--- /dev/null
|
||||
+++ b/drivers/cpufreq/cpufreq-krait.c
|
||||
@@ -0,0 +1,390 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2012 Freescale Semiconductor, Inc.
|
||||
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
|
||||
+ *
|
||||
+ * The OPP code in function krait_set_target() is reused from
|
||||
+ * drivers/cpufreq/omap-cpufreq.c
|
||||
+ *
|
||||
+ * This program is free software; you can redistribute it and/or modify
|
||||
+ * it under the terms of the GNU General Public License version 2 as
|
||||
+ * published by the Free Software Foundation.
|
||||
+ */
|
||||
+
|
||||
+#include <linux/clk.h>
|
||||
+#include <linux/cpu.h>
|
||||
+#include <linux/cpu_cooling.h>
|
||||
+#include <linux/cpufreq.h>
|
||||
+#include <linux/cpumask.h>
|
||||
+#include <linux/err.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/pm_opp.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/regulator/consumer.h>
|
||||
+#include <linux/slab.h>
|
||||
+#include <linux/thermal.h>
|
||||
+
|
||||
+static unsigned int transition_latency;
|
||||
+static unsigned int voltage_tolerance; /* in percentage */
|
||||
+
|
||||
+static struct device *cpu_dev;
|
||||
+static DEFINE_PER_CPU(struct clk *, krait_cpu_clks);
|
||||
+static DEFINE_PER_CPU(struct regulator *, krait_supply_core);
|
||||
+static struct cpufreq_frequency_table *freq_table;
|
||||
+static struct thermal_cooling_device *cdev;
|
||||
+
|
||||
+struct cache_points {
|
||||
+ unsigned long cache_freq;
|
||||
+ unsigned int cache_volt;
|
||||
+ unsigned long cpu_freq;
|
||||
+};
|
||||
+
|
||||
+static struct regulator *krait_l2_reg;
|
||||
+static struct clk *krait_l2_clk;
|
||||
+static struct cache_points *krait_l2_points;
|
||||
+static int nr_krait_l2_points;
|
||||
+
|
||||
+static int krait_parse_cache_points(struct device *dev,
|
||||
+ struct device_node *of_node)
|
||||
+{
|
||||
+ const struct property *prop;
|
||||
+ const __be32 *val;
|
||||
+ int nr, i;
|
||||
+
|
||||
+ prop = of_find_property(of_node, "cache-points-kHz", NULL);
|
||||
+ if (!prop)
|
||||
+ return -ENODEV;
|
||||
+ if (!prop->value)
|
||||
+ return -ENODATA;
|
||||
+
|
||||
+ /*
|
||||
+ * Each OPP is a set of tuples consisting of frequency and
|
||||
+ * cpu-frequency like <freq-kHz volt-uV freq-kHz>.
|
||||
+ */
|
||||
+ nr = prop->length / sizeof(u32);
|
||||
+ if (nr % 3) {
|
||||
+ dev_err(dev, "%s: Invalid cache points\n", __func__);
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+ nr /= 3;
|
||||
+
|
||||
+ krait_l2_points = devm_kcalloc(dev, nr, sizeof(*krait_l2_points),
|
||||
+ GFP_KERNEL);
|
||||
+ if (!krait_l2_points)
|
||||
+ return -ENOMEM;
|
||||
+ nr_krait_l2_points = nr;
|
||||
+
|
||||
+ for (i = 0, val = prop->value; i < nr; i++) {
|
||||
+ unsigned long cache_freq = be32_to_cpup(val++) * 1000;
|
||||
+ unsigned int cache_volt = be32_to_cpup(val++);
|
||||
+ unsigned long cpu_freq = be32_to_cpup(val++) * 1000;
|
||||
+
|
||||
+ krait_l2_points[i].cache_freq = cache_freq;
|
||||
+ krait_l2_points[i].cache_volt = cache_volt;
|
||||
+ krait_l2_points[i].cpu_freq = cpu_freq;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int krait_set_target(struct cpufreq_policy *policy, unsigned int index)
|
||||
+{
|
||||
+ struct dev_pm_opp *opp;
|
||||
+ unsigned long volt = 0, volt_old = 0, tol = 0;
|
||||
+ unsigned long freq, max_cpu_freq = 0;
|
||||
+ unsigned int old_freq, new_freq;
|
||||
+ long freq_Hz, freq_exact;
|
||||
+ int ret, i;
|
||||
+ struct clk *cpu_clk;
|
||||
+ struct regulator *core;
|
||||
+ unsigned int cpu;
|
||||
+
|
||||
+ cpu_clk = per_cpu(krait_cpu_clks, policy->cpu);
|
||||
+
|
||||
+ freq_Hz = clk_round_rate(cpu_clk, freq_table[index].frequency * 1000);
|
||||
+ if (freq_Hz <= 0)
|
||||
+ freq_Hz = freq_table[index].frequency * 1000;
|
||||
+
|
||||
+ freq_exact = freq_Hz;
|
||||
+ new_freq = freq_Hz / 1000;
|
||||
+ old_freq = clk_get_rate(cpu_clk) / 1000;
|
||||
+
|
||||
+ core = per_cpu(krait_supply_core, policy->cpu);
|
||||
+
|
||||
+ rcu_read_lock();
|
||||
+ opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_Hz);
|
||||
+ if (IS_ERR(opp)) {
|
||||
+ rcu_read_unlock();
|
||||
+ pr_err("failed to find OPP for %ld\n", freq_Hz);
|
||||
+ return PTR_ERR(opp);
|
||||
+ }
|
||||
+ volt = dev_pm_opp_get_voltage(opp);
|
||||
+ rcu_read_unlock();
|
||||
+ tol = volt * voltage_tolerance / 100;
|
||||
+ volt_old = regulator_get_voltage(core);
|
||||
+
|
||||
+ pr_debug("%u MHz, %ld mV --> %u MHz, %ld mV\n",
|
||||
+ old_freq / 1000, volt_old ? volt_old / 1000 : -1,
|
||||
+ new_freq / 1000, volt ? volt / 1000 : -1);
|
||||
+
|
||||
+ /* scaling up? scale voltage before frequency */
|
||||
+ if (new_freq > old_freq) {
|
||||
+ ret = regulator_set_voltage_tol(core, volt, tol);
|
||||
+ if (ret) {
|
||||
+ pr_err("failed to scale voltage up: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ ret = clk_set_rate(cpu_clk, freq_exact);
|
||||
+ if (ret) {
|
||||
+ pr_err("failed to set clock rate: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ /* scaling down? scale voltage after frequency */
|
||||
+ if (new_freq < old_freq) {
|
||||
+ ret = regulator_set_voltage_tol(core, volt, tol);
|
||||
+ if (ret) {
|
||||
+ pr_err("failed to scale voltage down: %d\n", ret);
|
||||
+ clk_set_rate(cpu_clk, old_freq * 1000);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ for_each_possible_cpu(cpu) {
|
||||
+ freq = clk_get_rate(per_cpu(krait_cpu_clks, cpu));
|
||||
+ max_cpu_freq = max(max_cpu_freq, freq);
|
||||
+ }
|
||||
+
|
||||
+ for (i = 0; i < nr_krait_l2_points; i++) {
|
||||
+ if (max_cpu_freq >= krait_l2_points[i].cpu_freq) {
|
||||
+ if (krait_l2_reg) {
|
||||
+ ret = regulator_set_voltage_tol(krait_l2_reg,
|
||||
+ krait_l2_points[i].cache_volt,
|
||||
+ tol);
|
||||
+ if (ret) {
|
||||
+ pr_err("failed to scale l2 voltage: %d\n",
|
||||
+ ret);
|
||||
+ }
|
||||
+ }
|
||||
+ ret = clk_set_rate(krait_l2_clk,
|
||||
+ krait_l2_points[i].cache_freq);
|
||||
+ if (ret)
|
||||
+ pr_err("failed to scale l2 clk: %d\n", ret);
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ }
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int krait_cpufreq_init(struct cpufreq_policy *policy)
|
||||
+{
|
||||
+ int ret;
|
||||
+
|
||||
+ policy->clk = per_cpu(krait_cpu_clks, policy->cpu);
|
||||
+
|
||||
+ ret = cpufreq_table_validate_and_show(policy, freq_table);
|
||||
+ if (ret) {
|
||||
+ pr_err("%s: invalid frequency table: %d\n", __func__, ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ policy->cpuinfo.transition_latency = transition_latency;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct cpufreq_driver krait_cpufreq_driver = {
|
||||
+ .flags = CPUFREQ_STICKY,
|
||||
+ .verify = cpufreq_generic_frequency_table_verify,
|
||||
+ .target_index = krait_set_target,
|
||||
+ .get = cpufreq_generic_get,
|
||||
+ .init = krait_cpufreq_init,
|
||||
+ .name = "generic_krait",
|
||||
+ .attr = cpufreq_generic_attr,
|
||||
+};
|
||||
+
|
||||
+static int krait_cpufreq_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device_node *np, *cache;
|
||||
+ int ret, i;
|
||||
+ unsigned int cpu;
|
||||
+ struct device *dev;
|
||||
+ struct clk *clk;
|
||||
+ struct regulator *core;
|
||||
+ unsigned long freq_Hz, freq, max_cpu_freq;
|
||||
+ struct dev_pm_opp *opp;
|
||||
+ unsigned long volt, tol;
|
||||
+
|
||||
+ cpu_dev = get_cpu_device(0);
|
||||
+ if (!cpu_dev) {
|
||||
+ pr_err("failed to get krait device\n");
|
||||
+ return -ENODEV;
|
||||
+ }
|
||||
+
|
||||
+ np = of_node_get(cpu_dev->of_node);
|
||||
+ if (!np) {
|
||||
+ pr_err("failed to find krait node\n");
|
||||
+ return -ENOENT;
|
||||
+ }
|
||||
+
|
||||
+ ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
|
||||
+ if (ret) {
|
||||
+ pr_err("failed to init cpufreq table: %d\n", ret);
|
||||
+ goto out_put_node;
|
||||
+ }
|
||||
+
|
||||
+ of_property_read_u32(np, "voltage-tolerance", &voltage_tolerance);
|
||||
+
|
||||
+ if (of_property_read_u32(np, "clock-latency", &transition_latency))
|
||||
+ transition_latency = CPUFREQ_ETERNAL;
|
||||
+
|
||||
+ cache = of_find_next_cache_node(np);
|
||||
+ if (cache) {
|
||||
+ struct device_node *vdd;
|
||||
+
|
||||
+ vdd = of_parse_phandle(cache, "vdd_dig-supply", 0);
|
||||
+ if (vdd) {
|
||||
+ krait_l2_reg = regulator_get(NULL, vdd->name);
|
||||
+ if (IS_ERR(krait_l2_reg)) {
|
||||
+ pr_warn("failed to get l2 vdd_dig supply\n");
|
||||
+ krait_l2_reg = NULL;
|
||||
+ }
|
||||
+ of_node_put(vdd);
|
||||
+ }
|
||||
+
|
||||
+ krait_l2_clk = of_clk_get(cache, 0);
|
||||
+ if (!IS_ERR(krait_l2_clk)) {
|
||||
+ ret = krait_parse_cache_points(&pdev->dev, cache);
|
||||
+ if (ret)
|
||||
+ clk_put(krait_l2_clk);
|
||||
+ }
|
||||
+ if (IS_ERR(krait_l2_clk) || ret)
|
||||
+ krait_l2_clk = NULL;
|
||||
+ }
|
||||
+
|
||||
+ for_each_possible_cpu(cpu) {
|
||||
+ dev = get_cpu_device(cpu);
|
||||
+ if (!dev) {
|
||||
+ pr_err("failed to get krait device\n");
|
||||
+ ret = -ENOENT;
|
||||
+ goto out_free_table;
|
||||
+ }
|
||||
+ per_cpu(krait_cpu_clks, cpu) = clk = devm_clk_get(dev, NULL);
|
||||
+ if (IS_ERR(clk)) {
|
||||
+ ret = PTR_ERR(clk);
|
||||
+ goto out_free_table;
|
||||
+ }
|
||||
+ core = devm_regulator_get(dev, "core");
|
||||
+ if (IS_ERR(core)) {
|
||||
+ pr_debug("failed to get core regulator\n");
|
||||
+ ret = PTR_ERR(core);
|
||||
+ goto out_free_table;
|
||||
+ }
|
||||
+ per_cpu(krait_supply_core, cpu) = core;
|
||||
+
|
||||
+ freq_Hz = clk_get_rate(clk);
|
||||
+
|
||||
+ rcu_read_lock();
|
||||
+ opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_Hz);
|
||||
+ if (IS_ERR(opp)) {
|
||||
+ rcu_read_unlock();
|
||||
+ pr_err("failed to find OPP for %ld\n", freq_Hz);
|
||||
+ ret = PTR_ERR(opp);
|
||||
+ goto out_free_table;
|
||||
+ }
|
||||
+ volt = dev_pm_opp_get_voltage(opp);
|
||||
+ rcu_read_unlock();
|
||||
+
|
||||
+ tol = volt * voltage_tolerance / 100;
|
||||
+ ret = regulator_set_voltage_tol(core, volt, tol);
|
||||
+ if (ret) {
|
||||
+ pr_err("failed to scale voltage up: %d\n", ret);
|
||||
+ goto out_free_table;
|
||||
+ }
|
||||
+ ret = regulator_enable(core);
|
||||
+ if (ret) {
|
||||
+ pr_err("failed to enable regulator: %d\n", ret);
|
||||
+ goto out_free_table;
|
||||
+ }
|
||||
+ max_cpu_freq = max(max_cpu_freq, freq);
|
||||
+ }
|
||||
+
|
||||
+ for (i = 0; i < nr_krait_l2_points; i++) {
|
||||
+ if (max_cpu_freq >= krait_l2_points[i].cpu_freq) {
|
||||
+ if (krait_l2_reg) {
|
||||
+ ret = regulator_set_voltage_tol(krait_l2_reg,
|
||||
+ krait_l2_points[i].cache_volt,
|
||||
+ tol);
|
||||
+ if (ret)
|
||||
+ pr_err("failed to scale l2 voltage: %d\n",
|
||||
+ ret);
|
||||
+ ret = regulator_enable(krait_l2_reg);
|
||||
+ if (ret)
|
||||
+ pr_err("failed to enable l2 voltage: %d\n",
|
||||
+ ret);
|
||||
+ }
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ }
|
||||
+
|
||||
+ ret = cpufreq_register_driver(&krait_cpufreq_driver);
|
||||
+ if (ret) {
|
||||
+ pr_err("failed register driver: %d\n", ret);
|
||||
+ goto out_free_table;
|
||||
+ }
|
||||
+ of_node_put(np);
|
||||
+
|
||||
+ /*
|
||||
+ * For now, just loading the cooling device;
|
||||
+ * thermal DT code takes care of matching them.
|
||||
+ */
|
||||
+ for_each_possible_cpu(cpu) {
|
||||
+ dev = get_cpu_device(cpu);
|
||||
+ np = of_node_get(dev->of_node);
|
||||
+ if (of_find_property(np, "#cooling-cells", NULL)) {
|
||||
+ cdev = of_cpufreq_cooling_register(np, cpumask_of(cpu));
|
||||
+ if (IS_ERR(cdev))
|
||||
+ pr_err("running cpufreq without cooling device: %ld\n",
|
||||
+ PTR_ERR(cdev));
|
||||
+ }
|
||||
+ of_node_put(np);
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+
|
||||
+out_free_table:
|
||||
+ regulator_put(krait_l2_reg);
|
||||
+ clk_put(krait_l2_clk);
|
||||
+ dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
|
||||
+out_put_node:
|
||||
+ of_node_put(np);
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int krait_cpufreq_remove(struct platform_device *pdev)
|
||||
+{
|
||||
+ cpufreq_cooling_unregister(cdev);
|
||||
+ cpufreq_unregister_driver(&krait_cpufreq_driver);
|
||||
+ dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
|
||||
+ clk_put(krait_l2_clk);
|
||||
+ regulator_put(krait_l2_reg);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver krait_cpufreq_platdrv = {
|
||||
+ .driver = {
|
||||
+ .name = "cpufreq-krait",
|
||||
+ .owner = THIS_MODULE,
|
||||
+ },
|
||||
+ .probe = krait_cpufreq_probe,
|
||||
+ .remove = krait_cpufreq_remove,
|
||||
+};
|
||||
+module_platform_driver(krait_cpufreq_platdrv);
|
||||
+
|
||||
+MODULE_DESCRIPTION("Krait CPUfreq driver");
|
||||
+MODULE_LICENSE("GPL v2");
|
||||
--- a/drivers/cpufreq/qcom-cpufreq.c
|
||||
+++ b/drivers/cpufreq/qcom-cpufreq.c
|
||||
@@ -168,11 +168,8 @@ static int __init qcom_cpufreq_populate_
|
||||
|
||||
static int __init qcom_cpufreq_driver_init(void)
|
||||
{
|
||||
- struct cpufreq_dt_platform_data pdata = { .independent_clocks = true };
|
||||
struct platform_device_info devinfo = {
|
||||
- .name = "cpufreq-dt",
|
||||
- .data = &pdata,
|
||||
- .size_data = sizeof(pdata),
|
||||
+ .name = "cpufreq-krait",
|
||||
};
|
||||
struct device *cpu_dev;
|
||||
struct device_node *np;
|
|
@ -127,7 +127,7 @@
|
|||
|
||||
/ {
|
||||
model = "Qualcomm IPQ8064";
|
||||
@@ -577,5 +578,42 @@
|
||||
@@ -637,5 +638,42 @@
|
||||
dr_mode = "host";
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,01/13] ARM: Add Krait L2 register accessor functions
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063051
|
||||
Message-Id: <1426920332-9340-2-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>,
|
||||
Mark Rutland <mark.rutland@arm.com>, Russell King <linux@arm.linux.org.uk>,
|
||||
Courtney Cavin <courtney.cavin@sonymobile.com>
|
||||
Date: Fri, 20 Mar 2015 23:45:20 -0700
|
||||
|
||||
Krait CPUs have a handful of L2 cache controller registers that
|
||||
live behind a cp15 based indirection register. First you program
|
||||
the indirection register (l2cpselr) to point the L2 'window'
|
||||
register (l2cpdr) at what you want to read/write. Then you
|
||||
read/write the 'window' register to do what you want. The
|
||||
l2cpselr register is not banked per-cpu so we must lock around
|
||||
accesses to it to prevent other CPUs from re-pointing l2cpdr
|
||||
underneath us.
|
||||
|
||||
Cc: Mark Rutland <mark.rutland@arm.com>
|
||||
Cc: Russell King <linux@arm.linux.org.uk>
|
||||
Cc: Courtney Cavin <courtney.cavin@sonymobile.com>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
arch/arm/common/Kconfig | 3 ++
|
||||
arch/arm/common/Makefile | 1 +
|
||||
arch/arm/common/krait-l2-accessors.c | 58 +++++++++++++++++++++++++++++++
|
||||
arch/arm/include/asm/krait-l2-accessors.h | 20 +++++++++++
|
||||
4 files changed, 82 insertions(+)
|
||||
create mode 100644 arch/arm/common/krait-l2-accessors.c
|
||||
create mode 100644 arch/arm/include/asm/krait-l2-accessors.h
|
||||
|
||||
--- a/arch/arm/common/Kconfig
|
||||
+++ b/arch/arm/common/Kconfig
|
||||
@@ -9,6 +9,9 @@ config DMABOUNCE
|
||||
bool
|
||||
select ZONE_DMA
|
||||
|
||||
+config KRAIT_L2_ACCESSORS
|
||||
+ bool
|
||||
+
|
||||
config SHARP_LOCOMO
|
||||
bool
|
||||
|
||||
--- a/arch/arm/common/Makefile
|
||||
+++ b/arch/arm/common/Makefile
|
||||
@@ -7,6 +7,7 @@ obj-y += firmware.o
|
||||
obj-$(CONFIG_ICST) += icst.o
|
||||
obj-$(CONFIG_SA1111) += sa1111.o
|
||||
obj-$(CONFIG_DMABOUNCE) += dmabounce.o
|
||||
+obj-$(CONFIG_KRAIT_L2_ACCESSORS) += krait-l2-accessors.o
|
||||
obj-$(CONFIG_SHARP_LOCOMO) += locomo.o
|
||||
obj-$(CONFIG_SHARP_PARAM) += sharpsl_param.o
|
||||
obj-$(CONFIG_SHARP_SCOOP) += scoop.o
|
||||
--- /dev/null
|
||||
+++ b/arch/arm/common/krait-l2-accessors.c
|
||||
@@ -0,0 +1,58 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2011-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/spinlock.h>
|
||||
+#include <linux/export.h>
|
||||
+
|
||||
+#include <asm/barrier.h>
|
||||
+#include <asm/krait-l2-accessors.h>
|
||||
+
|
||||
+static DEFINE_RAW_SPINLOCK(krait_l2_lock);
|
||||
+
|
||||
+void krait_set_l2_indirect_reg(u32 addr, u32 val)
|
||||
+{
|
||||
+ unsigned long flags;
|
||||
+
|
||||
+ raw_spin_lock_irqsave(&krait_l2_lock, flags);
|
||||
+ /*
|
||||
+ * Select the L2 window by poking l2cpselr, then write to the window
|
||||
+ * via l2cpdr.
|
||||
+ */
|
||||
+ asm volatile ("mcr p15, 3, %0, c15, c0, 6 @ l2cpselr" : : "r" (addr));
|
||||
+ isb();
|
||||
+ asm volatile ("mcr p15, 3, %0, c15, c0, 7 @ l2cpdr" : : "r" (val));
|
||||
+ isb();
|
||||
+
|
||||
+ raw_spin_unlock_irqrestore(&krait_l2_lock, flags);
|
||||
+}
|
||||
+EXPORT_SYMBOL(krait_set_l2_indirect_reg);
|
||||
+
|
||||
+u32 krait_get_l2_indirect_reg(u32 addr)
|
||||
+{
|
||||
+ u32 val;
|
||||
+ unsigned long flags;
|
||||
+
|
||||
+ raw_spin_lock_irqsave(&krait_l2_lock, flags);
|
||||
+ /*
|
||||
+ * Select the L2 window by poking l2cpselr, then read from the window
|
||||
+ * via l2cpdr.
|
||||
+ */
|
||||
+ asm volatile ("mcr p15, 3, %0, c15, c0, 6 @ l2cpselr" : : "r" (addr));
|
||||
+ isb();
|
||||
+ asm volatile ("mrc p15, 3, %0, c15, c0, 7 @ l2cpdr" : "=r" (val));
|
||||
+
|
||||
+ raw_spin_unlock_irqrestore(&krait_l2_lock, flags);
|
||||
+
|
||||
+ return val;
|
||||
+}
|
||||
+EXPORT_SYMBOL(krait_get_l2_indirect_reg);
|
||||
--- /dev/null
|
||||
+++ b/arch/arm/include/asm/krait-l2-accessors.h
|
||||
@@ -0,0 +1,20 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2011-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 __ASMARM_KRAIT_L2_ACCESSORS_H
|
||||
+#define __ASMARM_KRAIT_L2_ACCESSORS_H
|
||||
+
|
||||
+extern void krait_set_l2_indirect_reg(u32 addr, u32 val);
|
||||
+extern u32 krait_get_l2_indirect_reg(u32 addr);
|
||||
+
|
||||
+#endif
|
|
@ -0,0 +1,192 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,02/13] clk: mux: Split out register accessors for reuse
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063111
|
||||
Message-Id: <1426920332-9340-3-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:21 -0700
|
||||
|
||||
We want to reuse the logic in clk-mux.c for other clock drivers
|
||||
that don't use readl as register accessors. Fortunately, there
|
||||
really isn't much to the mux code besides the table indirection
|
||||
and quirk flags if you assume any bit shifting and masking has
|
||||
been done already. Pull that logic out into reusable functions
|
||||
that operate on an optional table and some flags so that other
|
||||
drivers can use the same logic.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
drivers/clk/clk-mux.c | 76 +++++++++++++++++++++++++++-----------------
|
||||
include/linux/clk-provider.h | 9 ++++--
|
||||
2 files changed, 54 insertions(+), 31 deletions(-)
|
||||
|
||||
--- a/drivers/clk/clk-mux.c
|
||||
+++ b/drivers/clk/clk-mux.c
|
||||
@@ -29,35 +29,24 @@
|
||||
|
||||
#define to_clk_mux(_hw) container_of(_hw, struct clk_mux, hw)
|
||||
|
||||
-static u8 clk_mux_get_parent(struct clk_hw *hw)
|
||||
+unsigned int clk_mux_get_parent(struct clk_hw *hw, unsigned int val,
|
||||
+ unsigned int *table, unsigned long flags)
|
||||
{
|
||||
- struct clk_mux *mux = to_clk_mux(hw);
|
||||
int num_parents = __clk_get_num_parents(hw->clk);
|
||||
- u32 val;
|
||||
|
||||
- /*
|
||||
- * FIXME need a mux-specific flag to determine if val is bitwise or numeric
|
||||
- * e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1
|
||||
- * to 0x7 (index starts at one)
|
||||
- * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so
|
||||
- * val = 0x4 really means "bit 2, index starts at bit 0"
|
||||
- */
|
||||
- val = clk_readl(mux->reg) >> mux->shift;
|
||||
- val &= mux->mask;
|
||||
-
|
||||
- if (mux->table) {
|
||||
+ if (table) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_parents; i++)
|
||||
- if (mux->table[i] == val)
|
||||
+ if (table[i] == val)
|
||||
return i;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
- if (val && (mux->flags & CLK_MUX_INDEX_BIT))
|
||||
+ if (val && (flags & CLK_MUX_INDEX_BIT))
|
||||
val = ffs(val) - 1;
|
||||
|
||||
- if (val && (mux->flags & CLK_MUX_INDEX_ONE))
|
||||
+ if (val && (flags & CLK_MUX_INDEX_ONE))
|
||||
val--;
|
||||
|
||||
if (val >= num_parents)
|
||||
@@ -65,24 +54,53 @@ static u8 clk_mux_get_parent(struct clk_
|
||||
|
||||
return val;
|
||||
}
|
||||
+EXPORT_SYMBOL_GPL(clk_mux_get_parent);
|
||||
|
||||
-static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
|
||||
+static u8 _clk_mux_get_parent(struct clk_hw *hw)
|
||||
{
|
||||
struct clk_mux *mux = to_clk_mux(hw);
|
||||
u32 val;
|
||||
- unsigned long flags = 0;
|
||||
|
||||
- if (mux->table)
|
||||
- index = mux->table[index];
|
||||
+ /*
|
||||
+ * FIXME need a mux-specific flag to determine if val is bitwise or numeric
|
||||
+ * e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1
|
||||
+ * to 0x7 (index starts at one)
|
||||
+ * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so
|
||||
+ * val = 0x4 really means "bit 2, index starts at bit 0"
|
||||
+ */
|
||||
+ val = clk_readl(mux->reg) >> mux->shift;
|
||||
+ val &= mux->mask;
|
||||
+
|
||||
+ return clk_mux_get_parent(hw, val, mux->table, mux->flags);
|
||||
+}
|
||||
|
||||
- else {
|
||||
- if (mux->flags & CLK_MUX_INDEX_BIT)
|
||||
- index = 1 << index;
|
||||
+unsigned int clk_mux_reindex(u8 index, unsigned int *table,
|
||||
+ unsigned long flags)
|
||||
+{
|
||||
+ unsigned int val = index;
|
||||
|
||||
- if (mux->flags & CLK_MUX_INDEX_ONE)
|
||||
- index++;
|
||||
+ if (table) {
|
||||
+ val = table[val];
|
||||
+ } else {
|
||||
+ if (flags & CLK_MUX_INDEX_BIT)
|
||||
+ val = 1 << index;
|
||||
+
|
||||
+ if (flags & CLK_MUX_INDEX_ONE)
|
||||
+ val++;
|
||||
}
|
||||
|
||||
+ return val;
|
||||
+}
|
||||
+EXPORT_SYMBOL_GPL(clk_mux_reindex);
|
||||
+
|
||||
+static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
|
||||
+{
|
||||
+ struct clk_mux *mux = to_clk_mux(hw);
|
||||
+ u32 val;
|
||||
+ unsigned long flags = 0;
|
||||
+
|
||||
+ index = clk_mux_reindex(index, mux->table, mux->flags);
|
||||
+
|
||||
if (mux->lock)
|
||||
spin_lock_irqsave(mux->lock, flags);
|
||||
|
||||
@@ -102,21 +120,21 @@ static int clk_mux_set_parent(struct clk
|
||||
}
|
||||
|
||||
const struct clk_ops clk_mux_ops = {
|
||||
- .get_parent = clk_mux_get_parent,
|
||||
+ .get_parent = _clk_mux_get_parent,
|
||||
.set_parent = clk_mux_set_parent,
|
||||
.determine_rate = __clk_mux_determine_rate,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(clk_mux_ops);
|
||||
|
||||
const struct clk_ops clk_mux_ro_ops = {
|
||||
- .get_parent = clk_mux_get_parent,
|
||||
+ .get_parent = _clk_mux_get_parent,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(clk_mux_ro_ops);
|
||||
|
||||
struct clk *clk_register_mux_table(struct device *dev, const char *name,
|
||||
const char **parent_names, u8 num_parents, unsigned long flags,
|
||||
void __iomem *reg, u8 shift, u32 mask,
|
||||
- u8 clk_mux_flags, u32 *table, spinlock_t *lock)
|
||||
+ u8 clk_mux_flags, unsigned int *table, spinlock_t *lock)
|
||||
{
|
||||
struct clk_mux *mux;
|
||||
struct clk *clk;
|
||||
--- a/include/linux/clk-provider.h
|
||||
+++ b/include/linux/clk-provider.h
|
||||
@@ -409,7 +409,7 @@ void clk_unregister_divider(struct clk *
|
||||
struct clk_mux {
|
||||
struct clk_hw hw;
|
||||
void __iomem *reg;
|
||||
- u32 *table;
|
||||
+ unsigned int *table;
|
||||
u32 mask;
|
||||
u8 shift;
|
||||
u8 flags;
|
||||
@@ -425,6 +425,11 @@ struct clk_mux {
|
||||
extern const struct clk_ops clk_mux_ops;
|
||||
extern const struct clk_ops clk_mux_ro_ops;
|
||||
|
||||
+unsigned int clk_mux_get_parent(struct clk_hw *hw, unsigned int val,
|
||||
+ unsigned int *table, unsigned long flags);
|
||||
+unsigned int clk_mux_reindex(u8 index, unsigned int *table,
|
||||
+ unsigned long flags);
|
||||
+
|
||||
struct clk *clk_register_mux(struct device *dev, const char *name,
|
||||
const char **parent_names, u8 num_parents, unsigned long flags,
|
||||
void __iomem *reg, u8 shift, u8 width,
|
||||
@@ -433,7 +438,7 @@ struct clk *clk_register_mux(struct devi
|
||||
struct clk *clk_register_mux_table(struct device *dev, const char *name,
|
||||
const char **parent_names, u8 num_parents, unsigned long flags,
|
||||
void __iomem *reg, u8 shift, u32 mask,
|
||||
- u8 clk_mux_flags, u32 *table, spinlock_t *lock);
|
||||
+ u8 clk_mux_flags, unsigned int *table, spinlock_t *lock);
|
||||
|
||||
void clk_unregister_mux(struct clk *clk);
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3, 03/13] clk: Avoid sending high rates to downstream clocks during
|
||||
set_rate
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063271
|
||||
Message-Id: <1426920332-9340-4-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:22 -0700
|
||||
|
||||
If a clock is on and we call clk_set_rate() on it we may get into
|
||||
a situation where the clock temporarily increases in rate
|
||||
dramatically while we walk the tree and call .set_rate() ops. For
|
||||
example, consider a case where a PLL feeds into a divider.
|
||||
Initially the divider is set to divide by 1 and the PLL is
|
||||
running fairly slow (100MHz). The downstream consumer of the
|
||||
divider output can only handle rates =< 400 MHz, but the divider
|
||||
can only choose between divisors of 1 and 4.
|
||||
|
||||
+-----+ +----------------+
|
||||
| PLL |-->| div 1 or div 4 |---> consumer device
|
||||
+-----+ +----------------+
|
||||
|
||||
To achieve a rate of 400MHz on the output of the divider, we
|
||||
would have to set the rate of the PLL to 1.6 GHz and then divide
|
||||
it by 4. The current code would set the PLL to 1.6GHz first while
|
||||
the divider is still set to 1, thus causing the downstream
|
||||
consumer of the clock to receive a few clock cycles of 1.6GHz
|
||||
clock (far beyond it's maximum acceptable rate). We should be
|
||||
changing the divider first before increasing the PLL rate to
|
||||
avoid this problem.
|
||||
|
||||
Therefore, set the rate of any child clocks that are increasing
|
||||
in rate from their current rate so that they can increase their
|
||||
dividers if necessary. We assume that there isn't such a thing as
|
||||
minimum rate requirements.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
drivers/clk/clk.c | 34 ++++++++++++++++++++++------------
|
||||
1 file changed, 22 insertions(+), 12 deletions(-)
|
||||
|
||||
--- a/drivers/clk/clk.c
|
||||
+++ b/drivers/clk/clk.c
|
||||
@@ -1688,21 +1688,24 @@ static struct clk_core *clk_propagate_ra
|
||||
* walk down a subtree and set the new rates notifying the rate
|
||||
* change on the way
|
||||
*/
|
||||
-static void clk_change_rate(struct clk_core *clk)
|
||||
+static void
|
||||
+clk_change_rate(struct clk_core *clk, unsigned long best_parent_rate)
|
||||
{
|
||||
struct clk_core *child;
|
||||
struct hlist_node *tmp;
|
||||
unsigned long old_rate;
|
||||
- unsigned long best_parent_rate = 0;
|
||||
bool skip_set_rate = false;
|
||||
struct clk_core *old_parent;
|
||||
|
||||
- old_rate = clk->rate;
|
||||
+ hlist_for_each_entry(child, &clk->children, child_node) {
|
||||
+ /* Skip children who will be reparented to another clock */
|
||||
+ if (child->new_parent && child->new_parent != clk)
|
||||
+ continue;
|
||||
+ if (child->new_rate > child->rate)
|
||||
+ clk_change_rate(child, clk->new_rate);
|
||||
+ }
|
||||
|
||||
- if (clk->new_parent)
|
||||
- best_parent_rate = clk->new_parent->rate;
|
||||
- else if (clk->parent)
|
||||
- best_parent_rate = clk->parent->rate;
|
||||
+ old_rate = clk->rate;
|
||||
|
||||
if (clk->new_parent && clk->new_parent != clk->parent) {
|
||||
old_parent = __clk_set_parent_before(clk, clk->new_parent);
|
||||
@@ -1722,7 +1725,7 @@ static void clk_change_rate(struct clk_c
|
||||
if (!skip_set_rate && clk->ops->set_rate)
|
||||
clk->ops->set_rate(clk->hw, clk->new_rate, best_parent_rate);
|
||||
|
||||
- clk->rate = clk_recalc(clk, best_parent_rate);
|
||||
+ clk->rate = clk->new_rate;
|
||||
|
||||
if (clk->notifier_count && old_rate != clk->rate)
|
||||
__clk_notify(clk, POST_RATE_CHANGE, old_rate, clk->rate);
|
||||
@@ -1735,12 +1738,13 @@ static void clk_change_rate(struct clk_c
|
||||
/* Skip children who will be reparented to another clock */
|
||||
if (child->new_parent && child->new_parent != clk)
|
||||
continue;
|
||||
- clk_change_rate(child);
|
||||
+ if (child->new_rate != child->rate)
|
||||
+ clk_change_rate(child, clk->new_rate);
|
||||
}
|
||||
|
||||
/* handle the new child who might not be in clk->children yet */
|
||||
- if (clk->new_child)
|
||||
- clk_change_rate(clk->new_child);
|
||||
+ if (clk->new_child && clk->new_child->new_rate != clk->new_child->rate)
|
||||
+ clk_change_rate(clk->new_child, clk->new_rate);
|
||||
}
|
||||
|
||||
static int clk_core_set_rate_nolock(struct clk_core *clk,
|
||||
@@ -1749,6 +1753,7 @@ static int clk_core_set_rate_nolock(stru
|
||||
struct clk_core *top, *fail_clk;
|
||||
unsigned long rate = req_rate;
|
||||
int ret = 0;
|
||||
+ unsigned long parent_rate;
|
||||
|
||||
if (!clk)
|
||||
return 0;
|
||||
@@ -1774,8 +1779,13 @@ static int clk_core_set_rate_nolock(stru
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
+ if (top->parent)
|
||||
+ parent_rate = top->parent->rate;
|
||||
+ else
|
||||
+ parent_rate = 0;
|
||||
+
|
||||
/* change the rates */
|
||||
- clk_change_rate(top);
|
||||
+ clk_change_rate(top, parent_rate);
|
||||
|
||||
clk->req_rate = req_rate;
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,04/13] clk: Add safe switch hook
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063211
|
||||
Message-Id: <1426920332-9340-5-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:23 -0700
|
||||
|
||||
Sometimes clocks can't accept their parent source turning off
|
||||
while the source is reprogrammed to a different rate. Most
|
||||
notably CPU clocks require a way to switch away from the current
|
||||
PLL they're running on, reprogram that PLL to a new rate, and
|
||||
then switch back to the PLL with the new rate once they're done.
|
||||
Add a hook that drivers can implement allowing them to return a
|
||||
'safe parent' that they can switch their parent to while the
|
||||
upstream source is reprogrammed to support this.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
This patch is good enough for Krait, but soon I'll need to
|
||||
support a "safe rate" where we ask a clock what rate it needs to be running
|
||||
at to be sure it's within voltage constraints. Right now safe parent
|
||||
handles that problem on Krait, but on other platforms it won't work.
|
||||
|
||||
drivers/clk/clk.c | 61 ++++++++++++++++++++++++++++++++++++++------
|
||||
include/linux/clk-provider.h | 1 +
|
||||
2 files changed, 54 insertions(+), 8 deletions(-)
|
||||
|
||||
--- a/drivers/clk/clk.c
|
||||
+++ b/drivers/clk/clk.c
|
||||
@@ -56,9 +56,12 @@ struct clk_core {
|
||||
struct clk_core **parents;
|
||||
u8 num_parents;
|
||||
u8 new_parent_index;
|
||||
+ u8 safe_parent_index;
|
||||
unsigned long rate;
|
||||
unsigned long req_rate;
|
||||
+ unsigned long old_rate;
|
||||
unsigned long new_rate;
|
||||
+ struct clk_core *safe_parent;
|
||||
struct clk_core *new_parent;
|
||||
struct clk_core *new_child;
|
||||
unsigned long flags;
|
||||
@@ -1549,7 +1552,8 @@ out:
|
||||
static void clk_calc_subtree(struct clk_core *clk, unsigned long new_rate,
|
||||
struct clk_core *new_parent, u8 p_index)
|
||||
{
|
||||
- struct clk_core *child;
|
||||
+ struct clk_core *child, *parent;
|
||||
+ struct clk_hw *parent_hw;
|
||||
|
||||
clk->new_rate = new_rate;
|
||||
clk->new_parent = new_parent;
|
||||
@@ -1559,6 +1563,18 @@ static void clk_calc_subtree(struct clk_
|
||||
if (new_parent && new_parent != clk->parent)
|
||||
new_parent->new_child = clk;
|
||||
|
||||
+ if (clk->ops->get_safe_parent) {
|
||||
+ parent_hw = clk->ops->get_safe_parent(clk->hw);
|
||||
+ if (parent_hw) {
|
||||
+ parent = parent_hw->core;
|
||||
+ p_index = clk_fetch_parent_index(clk, parent);
|
||||
+ clk->safe_parent_index = p_index;
|
||||
+ clk->safe_parent = parent;
|
||||
+ }
|
||||
+ } else {
|
||||
+ clk->safe_parent = NULL;
|
||||
+ }
|
||||
+
|
||||
hlist_for_each_entry(child, &clk->children, child_node) {
|
||||
child->new_rate = clk_recalc(child, new_rate);
|
||||
clk_calc_subtree(child, child->new_rate, NULL, 0);
|
||||
@@ -1654,14 +1670,43 @@ static struct clk_core *clk_propagate_ra
|
||||
unsigned long event)
|
||||
{
|
||||
struct clk_core *child, *tmp_clk, *fail_clk = NULL;
|
||||
+ struct clk_core *old_parent;
|
||||
int ret = NOTIFY_DONE;
|
||||
|
||||
- if (clk->rate == clk->new_rate)
|
||||
+ if (clk->rate == clk->new_rate && event != POST_RATE_CHANGE)
|
||||
return NULL;
|
||||
|
||||
+ switch (event) {
|
||||
+ case PRE_RATE_CHANGE:
|
||||
+ if (clk->safe_parent)
|
||||
+ clk->ops->set_parent(clk->hw, clk->safe_parent_index);
|
||||
+ clk->old_rate = clk->rate;
|
||||
+ break;
|
||||
+ case POST_RATE_CHANGE:
|
||||
+ if (clk->safe_parent) {
|
||||
+ old_parent = __clk_set_parent_before(clk,
|
||||
+ clk->new_parent);
|
||||
+ if (clk->ops->set_rate_and_parent) {
|
||||
+ clk->ops->set_rate_and_parent(clk->hw,
|
||||
+ clk->new_rate,
|
||||
+ clk->new_parent ?
|
||||
+ clk->new_parent->rate : 0,
|
||||
+ clk->new_parent_index);
|
||||
+ } else if (clk->ops->set_parent) {
|
||||
+ clk->ops->set_parent(clk->hw,
|
||||
+ clk->new_parent_index);
|
||||
+ }
|
||||
+ __clk_set_parent_after(clk, clk->new_parent,
|
||||
+ old_parent);
|
||||
+ }
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
if (clk->notifier_count) {
|
||||
- ret = __clk_notify(clk, event, clk->rate, clk->new_rate);
|
||||
- if (ret & NOTIFY_STOP_MASK)
|
||||
+ if (event != POST_RATE_CHANGE || clk->old_rate != clk->rate)
|
||||
+ ret = __clk_notify(clk, event, clk->old_rate,
|
||||
+ clk->new_rate);
|
||||
+ if (ret & NOTIFY_STOP_MASK && event != POST_RATE_CHANGE)
|
||||
fail_clk = clk;
|
||||
}
|
||||
|
||||
@@ -1707,7 +1752,8 @@ clk_change_rate(struct clk_core *clk, un
|
||||
|
||||
old_rate = clk->rate;
|
||||
|
||||
- if (clk->new_parent && clk->new_parent != clk->parent) {
|
||||
+ if (clk->new_parent && clk->new_parent != clk->parent &&
|
||||
+ !clk->safe_parent) {
|
||||
old_parent = __clk_set_parent_before(clk, clk->new_parent);
|
||||
|
||||
if (clk->ops->set_rate_and_parent) {
|
||||
@@ -1727,9 +1773,6 @@ clk_change_rate(struct clk_core *clk, un
|
||||
|
||||
clk->rate = clk->new_rate;
|
||||
|
||||
- if (clk->notifier_count && old_rate != clk->rate)
|
||||
- __clk_notify(clk, POST_RATE_CHANGE, old_rate, clk->rate);
|
||||
-
|
||||
/*
|
||||
* Use safe iteration, as change_rate can actually swap parents
|
||||
* for certain clock types.
|
||||
@@ -1789,6 +1832,8 @@ static int clk_core_set_rate_nolock(stru
|
||||
|
||||
clk->req_rate = req_rate;
|
||||
|
||||
+ clk_propagate_rate_change(top, POST_RATE_CHANGE);
|
||||
+
|
||||
return ret;
|
||||
}
|
||||
|
||||
--- a/include/linux/clk-provider.h
|
||||
+++ b/include/linux/clk-provider.h
|
||||
@@ -183,6 +183,7 @@ struct clk_ops {
|
||||
struct clk_hw **best_parent_hw);
|
||||
int (*set_parent)(struct clk_hw *hw, u8 index);
|
||||
u8 (*get_parent)(struct clk_hw *hw);
|
||||
+ struct clk_hw *(*get_safe_parent)(struct clk_hw *hw);
|
||||
int (*set_rate)(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate);
|
||||
int (*set_rate_and_parent)(struct clk_hw *hw,
|
|
@ -0,0 +1,351 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,05/13] clk: qcom: Add support for High-Frequency PLLs (HFPLLs)
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063261
|
||||
Message-Id: <1426920332-9340-6-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:24 -0700
|
||||
|
||||
HFPLLs are the main frequency source for Krait CPU clocks. Add
|
||||
support for changing the rate of these PLLs.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
I'd really like to get rid of __clk_hfpll_init_once() if possible...
|
||||
|
||||
drivers/clk/qcom/Makefile | 1 +
|
||||
drivers/clk/qcom/clk-hfpll.c | 253 +++++++++++++++++++++++++++++++++++++++++++
|
||||
drivers/clk/qcom/clk-hfpll.h | 54 +++++++++
|
||||
3 files changed, 308 insertions(+)
|
||||
create mode 100644 drivers/clk/qcom/clk-hfpll.c
|
||||
create mode 100644 drivers/clk/qcom/clk-hfpll.h
|
||||
|
||||
--- a/drivers/clk/qcom/Makefile
|
||||
+++ b/drivers/clk/qcom/Makefile
|
||||
@@ -8,6 +8,7 @@ clk-qcom-y += clk-rcg2.o
|
||||
clk-qcom-y += clk-branch.o
|
||||
clk-qcom-y += clk-regmap-divider.o
|
||||
clk-qcom-y += clk-regmap-mux.o
|
||||
+clk-qcom-y += clk-hfpll.o
|
||||
clk-qcom-y += reset.o
|
||||
|
||||
obj-$(CONFIG_APQ_GCC_8084) += gcc-apq8084.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/clk-hfpll.c
|
||||
@@ -0,0 +1,253 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2013-2014, 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/export.h>
|
||||
+#include <linux/regmap.h>
|
||||
+#include <linux/delay.h>
|
||||
+#include <linux/err.h>
|
||||
+#include <linux/clk-provider.h>
|
||||
+#include <linux/spinlock.h>
|
||||
+
|
||||
+#include "clk-regmap.h"
|
||||
+#include "clk-hfpll.h"
|
||||
+
|
||||
+#define PLL_OUTCTRL BIT(0)
|
||||
+#define PLL_BYPASSNL BIT(1)
|
||||
+#define PLL_RESET_N BIT(2)
|
||||
+
|
||||
+/* Initialize a HFPLL at a given rate and enable it. */
|
||||
+static void __clk_hfpll_init_once(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+
|
||||
+ if (likely(h->init_done))
|
||||
+ return;
|
||||
+
|
||||
+ /* Configure PLL parameters for integer mode. */
|
||||
+ if (hd->config_val)
|
||||
+ regmap_write(regmap, hd->config_reg, hd->config_val);
|
||||
+ regmap_write(regmap, hd->m_reg, 0);
|
||||
+ regmap_write(regmap, hd->n_reg, 1);
|
||||
+
|
||||
+ if (hd->user_reg) {
|
||||
+ u32 regval = hd->user_val;
|
||||
+ unsigned long rate;
|
||||
+
|
||||
+ rate = __clk_get_rate(hw->clk);
|
||||
+
|
||||
+ /* Pick the right VCO. */
|
||||
+ if (hd->user_vco_mask && rate > hd->low_vco_max_rate)
|
||||
+ regval |= hd->user_vco_mask;
|
||||
+ regmap_write(regmap, hd->user_reg, regval);
|
||||
+ }
|
||||
+
|
||||
+ if (hd->droop_reg)
|
||||
+ regmap_write(regmap, hd->droop_reg, hd->droop_val);
|
||||
+
|
||||
+ h->init_done = true;
|
||||
+}
|
||||
+
|
||||
+static void __clk_hfpll_enable(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+ u32 val;
|
||||
+
|
||||
+ __clk_hfpll_init_once(hw);
|
||||
+
|
||||
+ /* Disable PLL bypass mode. */
|
||||
+ regmap_update_bits(regmap, hd->mode_reg, PLL_BYPASSNL, PLL_BYPASSNL);
|
||||
+
|
||||
+ /*
|
||||
+ * H/W requires a 5us delay between disabling the bypass and
|
||||
+ * de-asserting the reset. Delay 10us just to be safe.
|
||||
+ */
|
||||
+ udelay(10);
|
||||
+
|
||||
+ /* De-assert active-low PLL reset. */
|
||||
+ regmap_update_bits(regmap, hd->mode_reg, PLL_RESET_N, PLL_RESET_N);
|
||||
+
|
||||
+ /* Wait for PLL to lock. */
|
||||
+ if (hd->status_reg) {
|
||||
+ do {
|
||||
+ regmap_read(regmap, hd->status_reg, &val);
|
||||
+ } while (!(val & BIT(hd->lock_bit)));
|
||||
+ } else {
|
||||
+ udelay(60);
|
||||
+ }
|
||||
+
|
||||
+ /* Enable PLL output. */
|
||||
+ regmap_update_bits(regmap, hd->mode_reg, PLL_OUTCTRL, PLL_OUTCTRL);
|
||||
+}
|
||||
+
|
||||
+/* Enable an already-configured HFPLL. */
|
||||
+static int clk_hfpll_enable(struct clk_hw *hw)
|
||||
+{
|
||||
+ unsigned long flags;
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+ u32 mode;
|
||||
+
|
||||
+ spin_lock_irqsave(&h->lock, flags);
|
||||
+ regmap_read(regmap, hd->mode_reg, &mode);
|
||||
+ if (!(mode & (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)))
|
||||
+ __clk_hfpll_enable(hw);
|
||||
+ spin_unlock_irqrestore(&h->lock, flags);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void __clk_hfpll_disable(struct clk_hfpll *h)
|
||||
+{
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+
|
||||
+ /*
|
||||
+ * Disable the PLL output, disable test mode, enable the bypass mode,
|
||||
+ * and assert the reset.
|
||||
+ */
|
||||
+ regmap_update_bits(regmap, hd->mode_reg,
|
||||
+ PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL, 0);
|
||||
+}
|
||||
+
|
||||
+static void clk_hfpll_disable(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ unsigned long flags;
|
||||
+
|
||||
+ spin_lock_irqsave(&h->lock, flags);
|
||||
+ __clk_hfpll_disable(h);
|
||||
+ spin_unlock_irqrestore(&h->lock, flags);
|
||||
+}
|
||||
+
|
||||
+static long clk_hfpll_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long *parent_rate)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ unsigned long rrate;
|
||||
+
|
||||
+ rate = clamp(rate, hd->min_rate, hd->max_rate);
|
||||
+
|
||||
+ rrate = DIV_ROUND_UP(rate, *parent_rate) * *parent_rate;
|
||||
+ if (rrate > hd->max_rate)
|
||||
+ rrate -= *parent_rate;
|
||||
+
|
||||
+ return rrate;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * For optimization reasons, assumes no downstream clocks are actively using
|
||||
+ * it.
|
||||
+ */
|
||||
+static int clk_hfpll_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long parent_rate)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+ unsigned long flags;
|
||||
+ u32 l_val, val;
|
||||
+ bool enabled;
|
||||
+
|
||||
+ l_val = rate / parent_rate;
|
||||
+
|
||||
+ spin_lock_irqsave(&h->lock, flags);
|
||||
+
|
||||
+ enabled = __clk_is_enabled(hw->clk);
|
||||
+ if (enabled)
|
||||
+ __clk_hfpll_disable(h);
|
||||
+
|
||||
+ /* Pick the right VCO. */
|
||||
+ if (hd->user_reg && hd->user_vco_mask) {
|
||||
+ regmap_read(regmap, hd->user_reg, &val);
|
||||
+ if (rate <= hd->low_vco_max_rate)
|
||||
+ val &= ~hd->user_vco_mask;
|
||||
+ else
|
||||
+ val |= hd->user_vco_mask;
|
||||
+ regmap_write(regmap, hd->user_reg, val);
|
||||
+ }
|
||||
+
|
||||
+ regmap_write(regmap, hd->l_reg, l_val);
|
||||
+
|
||||
+ if (enabled)
|
||||
+ __clk_hfpll_enable(hw);
|
||||
+
|
||||
+ spin_unlock_irqrestore(&h->lock, flags);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static unsigned long clk_hfpll_recalc_rate(struct clk_hw *hw,
|
||||
+ unsigned long parent_rate)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+ u32 l_val;
|
||||
+
|
||||
+ regmap_read(regmap, hd->l_reg, &l_val);
|
||||
+
|
||||
+ return l_val * parent_rate;
|
||||
+}
|
||||
+
|
||||
+static void clk_hfpll_init(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+ u32 mode, status;
|
||||
+
|
||||
+ regmap_read(regmap, hd->mode_reg, &mode);
|
||||
+ if (mode != (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)) {
|
||||
+ __clk_hfpll_init_once(hw);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ if (hd->status_reg) {
|
||||
+ regmap_read(regmap, hd->status_reg, &status);
|
||||
+ if (!(status & BIT(hd->lock_bit))) {
|
||||
+ WARN(1, "HFPLL %s is ON, but not locked!\n",
|
||||
+ __clk_get_name(hw->clk));
|
||||
+ clk_hfpll_disable(hw);
|
||||
+ __clk_hfpll_init_once(hw);
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static int hfpll_is_enabled(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+ u32 mode;
|
||||
+
|
||||
+ regmap_read(regmap, hd->mode_reg, &mode);
|
||||
+ mode &= 0x7;
|
||||
+ return mode == (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL);
|
||||
+}
|
||||
+
|
||||
+const struct clk_ops clk_ops_hfpll = {
|
||||
+ .enable = clk_hfpll_enable,
|
||||
+ .disable = clk_hfpll_disable,
|
||||
+ .is_enabled = hfpll_is_enabled,
|
||||
+ .round_rate = clk_hfpll_round_rate,
|
||||
+ .set_rate = clk_hfpll_set_rate,
|
||||
+ .recalc_rate = clk_hfpll_recalc_rate,
|
||||
+ .init = clk_hfpll_init,
|
||||
+};
|
||||
+EXPORT_SYMBOL_GPL(clk_ops_hfpll);
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/clk-hfpll.h
|
||||
@@ -0,0 +1,54 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2013-2014, 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 __QCOM_CLK_HFPLL_H__
|
||||
+#define __QCOM_CLK_HFPLL_H__
|
||||
+
|
||||
+#include <linux/clk-provider.h>
|
||||
+#include <linux/spinlock.h>
|
||||
+#include "clk-regmap.h"
|
||||
+
|
||||
+struct hfpll_data {
|
||||
+ u32 mode_reg;
|
||||
+ u32 l_reg;
|
||||
+ u32 m_reg;
|
||||
+ u32 n_reg;
|
||||
+ u32 user_reg;
|
||||
+ u32 droop_reg;
|
||||
+ u32 config_reg;
|
||||
+ u32 status_reg;
|
||||
+ u8 lock_bit;
|
||||
+
|
||||
+ u32 droop_val;
|
||||
+ u32 config_val;
|
||||
+ u32 user_val;
|
||||
+ u32 user_vco_mask;
|
||||
+ unsigned long low_vco_max_rate;
|
||||
+
|
||||
+ unsigned long min_rate;
|
||||
+ unsigned long max_rate;
|
||||
+};
|
||||
+
|
||||
+struct clk_hfpll {
|
||||
+ struct hfpll_data const *d;
|
||||
+ int init_done;
|
||||
+
|
||||
+ struct clk_regmap clkr;
|
||||
+ spinlock_t lock;
|
||||
+};
|
||||
+
|
||||
+#define to_clk_hfpll(_hw) \
|
||||
+ container_of(to_clk_regmap(_hw), struct clk_hfpll, clkr)
|
||||
+
|
||||
+extern const struct clk_ops clk_ops_hfpll;
|
||||
+
|
||||
+#endif
|
|
@ -0,0 +1,206 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,06/13] clk: qcom: Add HFPLL driver
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063231
|
||||
Message-Id: <1426920332-9340-7-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>, <devicetree@vger.kernel.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:25 -0700
|
||||
|
||||
On some devices (MSM8974 for example), the HFPLLs are
|
||||
instantiated within the Krait processor subsystem as separate
|
||||
register regions. Add a driver for these PLLs so that we can
|
||||
provide HFPLL clocks for use by the system.
|
||||
|
||||
Cc: <devicetree@vger.kernel.org>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
.../devicetree/bindings/clock/qcom,hfpll.txt | 40 ++++++++
|
||||
drivers/clk/qcom/Kconfig | 8 ++
|
||||
drivers/clk/qcom/Makefile | 1 +
|
||||
drivers/clk/qcom/hfpll.c | 109 +++++++++++++++++++++
|
||||
4 files changed, 158 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/clock/qcom,hfpll.txt
|
||||
create mode 100644 drivers/clk/qcom/hfpll.c
|
||||
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/clock/qcom,hfpll.txt
|
||||
@@ -0,0 +1,40 @@
|
||||
+High-Frequency PLL (HFPLL)
|
||||
+
|
||||
+PROPERTIES
|
||||
+
|
||||
+- compatible:
|
||||
+ Usage: required
|
||||
+ Value type: <string>
|
||||
+ Definition: must be "qcom,hfpll"
|
||||
+
|
||||
+- reg:
|
||||
+ Usage: required
|
||||
+ Value type: <prop-encoded-array>
|
||||
+ Definition: address and size of HPLL registers. An optional second
|
||||
+ element specifies the address and size of the alias
|
||||
+ register region.
|
||||
+
|
||||
+- clock-output-names:
|
||||
+ Usage: required
|
||||
+ Value type: <string>
|
||||
+ Definition: Name of the PLL. Typically hfpllX where X is a CPU number
|
||||
+ starting at 0. Otherwise hfpll_Y where Y is more specific
|
||||
+ such as "l2".
|
||||
+
|
||||
+Example:
|
||||
+
|
||||
+1) An HFPLL for the L2 cache.
|
||||
+
|
||||
+ clock-controller@f9016000 {
|
||||
+ compatible = "qcom,hfpll";
|
||||
+ reg = <0xf9016000 0x30>;
|
||||
+ clock-output-names = "hfpll_l2";
|
||||
+ };
|
||||
+
|
||||
+2) An HFPLL for CPU0. This HFPLL has the alias register region.
|
||||
+
|
||||
+ clock-controller@f908a000 {
|
||||
+ compatible = "qcom,hfpll";
|
||||
+ reg = <0xf908a000 0x30>, <0xf900a000 0x30>;
|
||||
+ clock-output-names = "hfpll0";
|
||||
+ };
|
||||
--- a/drivers/clk/qcom/Kconfig
|
||||
+++ b/drivers/clk/qcom/Kconfig
|
||||
@@ -88,3 +88,11 @@ config MSM_MMCC_8974
|
||||
Support for the multimedia clock controller on msm8974 devices.
|
||||
Say Y if you want to support multimedia devices such as display,
|
||||
graphics, video encode/decode, camera, etc.
|
||||
+
|
||||
+config QCOM_HFPLL
|
||||
+ tristate "High-Frequency PLL (HFPLL) Clock Controller"
|
||||
+ depends on COMMON_CLK_QCOM
|
||||
+ help
|
||||
+ Support for the high-frequency PLLs present on Qualcomm devices.
|
||||
+ Say Y if you want to support CPU frequency scaling on devices
|
||||
+ such as MSM8974, APQ8084, etc.
|
||||
--- a/drivers/clk/qcom/Makefile
|
||||
+++ b/drivers/clk/qcom/Makefile
|
||||
@@ -21,3 +21,4 @@ obj-$(CONFIG_MSM_LCC_8960) += lcc-msm896
|
||||
obj-$(CONFIG_MSM_GCC_8974) += gcc-msm8974.o
|
||||
obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8960.o
|
||||
obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o
|
||||
+obj-$(CONFIG_QCOM_HFPLL) += hfpll.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/hfpll.c
|
||||
@@ -0,0 +1,109 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2013-2014, 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/init.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/clk.h>
|
||||
+#include <linux/clk-provider.h>
|
||||
+#include <linux/regmap.h>
|
||||
+
|
||||
+#include "clk-regmap.h"
|
||||
+#include "clk-hfpll.h"
|
||||
+
|
||||
+static const struct hfpll_data hdata = {
|
||||
+ .mode_reg = 0x00,
|
||||
+ .l_reg = 0x04,
|
||||
+ .m_reg = 0x08,
|
||||
+ .n_reg = 0x0c,
|
||||
+ .user_reg = 0x10,
|
||||
+ .config_reg = 0x14,
|
||||
+ .config_val = 0x430405d,
|
||||
+ .status_reg = 0x1c,
|
||||
+ .lock_bit = 16,
|
||||
+
|
||||
+ .user_val = 0x8,
|
||||
+ .user_vco_mask = 0x100000,
|
||||
+ .low_vco_max_rate = 1248000000,
|
||||
+ .min_rate = 537600000UL,
|
||||
+ .max_rate = 2900000000UL,
|
||||
+};
|
||||
+
|
||||
+static const struct of_device_id qcom_hfpll_match_table[] = {
|
||||
+ { .compatible = "qcom,hfpll" },
|
||||
+ { }
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, qcom_hfpll_match_table);
|
||||
+
|
||||
+static const struct regmap_config hfpll_regmap_config = {
|
||||
+ .reg_bits = 32,
|
||||
+ .reg_stride = 4,
|
||||
+ .val_bits = 32,
|
||||
+ .max_register = 0x30,
|
||||
+ .fast_io = true,
|
||||
+};
|
||||
+
|
||||
+static int qcom_hfpll_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct clk *clk;
|
||||
+ struct resource *res;
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ void __iomem *base;
|
||||
+ struct regmap *regmap;
|
||||
+ struct clk_hfpll *h;
|
||||
+ struct clk_init_data init = {
|
||||
+ .parent_names = (const char *[]){ "xo" },
|
||||
+ .num_parents = 1,
|
||||
+ .ops = &clk_ops_hfpll,
|
||||
+ };
|
||||
+
|
||||
+ h = devm_kzalloc(dev, sizeof(*h), GFP_KERNEL);
|
||||
+ if (!h)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
+ base = devm_ioremap_resource(dev, res);
|
||||
+ if (IS_ERR(base))
|
||||
+ return PTR_ERR(base);
|
||||
+
|
||||
+ regmap = devm_regmap_init_mmio(&pdev->dev, base, &hfpll_regmap_config);
|
||||
+ if (IS_ERR(regmap))
|
||||
+ return PTR_ERR(regmap);
|
||||
+
|
||||
+ if (of_property_read_string_index(dev->of_node, "clock-output-names",
|
||||
+ 0, &init.name))
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ h->d = &hdata;
|
||||
+ h->clkr.hw.init = &init;
|
||||
+ spin_lock_init(&h->lock);
|
||||
+
|
||||
+ clk = devm_clk_register_regmap(&pdev->dev, &h->clkr);
|
||||
+
|
||||
+ return PTR_ERR_OR_ZERO(clk);
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver qcom_hfpll_driver = {
|
||||
+ .probe = qcom_hfpll_probe,
|
||||
+ .driver = {
|
||||
+ .name = "qcom-hfpll",
|
||||
+ .of_match_table = qcom_hfpll_match_table,
|
||||
+ },
|
||||
+};
|
||||
+module_platform_driver(qcom_hfpll_driver);
|
||||
+
|
||||
+MODULE_DESCRIPTION("QCOM HFPLL Clock Driver");
|
||||
+MODULE_LICENSE("GPL v2");
|
||||
+MODULE_ALIAS("platform:qcom-hfpll");
|
|
@ -0,0 +1,127 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,08/13] clk: qcom: Add IPQ806X's HFPLLs
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063241
|
||||
Message-Id: <1426920332-9340-9-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:27 -0700
|
||||
|
||||
Describe the HFPLLs present on IPQ806X devices.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
drivers/clk/qcom/gcc-ipq806x.c | 83 ++++++++++++++++++++++++++++++++++++++++++
|
||||
1 file changed, 83 insertions(+)
|
||||
|
||||
--- a/drivers/clk/qcom/gcc-ipq806x.c
|
||||
+++ b/drivers/clk/qcom/gcc-ipq806x.c
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "clk-pll.h"
|
||||
#include "clk-rcg.h"
|
||||
#include "clk-branch.h"
|
||||
+#include "clk-hfpll.h"
|
||||
#include "reset.h"
|
||||
|
||||
static struct clk_pll pll0 = {
|
||||
@@ -113,6 +114,85 @@ static struct clk_regmap pll8_vote = {
|
||||
},
|
||||
};
|
||||
|
||||
+static struct hfpll_data hfpll0_data = {
|
||||
+ .mode_reg = 0x3200,
|
||||
+ .l_reg = 0x3208,
|
||||
+ .m_reg = 0x320c,
|
||||
+ .n_reg = 0x3210,
|
||||
+ .config_reg = 0x3204,
|
||||
+ .status_reg = 0x321c,
|
||||
+ .config_val = 0x7845c665,
|
||||
+ .droop_reg = 0x3214,
|
||||
+ .droop_val = 0x0108c000,
|
||||
+ .min_rate = 600000000UL,
|
||||
+ .max_rate = 1800000000UL,
|
||||
+};
|
||||
+
|
||||
+static struct clk_hfpll hfpll0 = {
|
||||
+ .d = &hfpll0_data,
|
||||
+ .clkr.hw.init = &(struct clk_init_data){
|
||||
+ .parent_names = (const char *[]){ "pxo" },
|
||||
+ .num_parents = 1,
|
||||
+ .name = "hfpll0",
|
||||
+ .ops = &clk_ops_hfpll,
|
||||
+ .flags = CLK_IGNORE_UNUSED,
|
||||
+ },
|
||||
+ .lock = __SPIN_LOCK_UNLOCKED(hfpll0.lock),
|
||||
+};
|
||||
+
|
||||
+static struct hfpll_data hfpll1_data = {
|
||||
+ .mode_reg = 0x3240,
|
||||
+ .l_reg = 0x3248,
|
||||
+ .m_reg = 0x324c,
|
||||
+ .n_reg = 0x3250,
|
||||
+ .config_reg = 0x3244,
|
||||
+ .status_reg = 0x325c,
|
||||
+ .config_val = 0x7845c665,
|
||||
+ .droop_reg = 0x3314,
|
||||
+ .droop_val = 0x0108c000,
|
||||
+ .min_rate = 600000000UL,
|
||||
+ .max_rate = 1800000000UL,
|
||||
+};
|
||||
+
|
||||
+static struct clk_hfpll hfpll1 = {
|
||||
+ .d = &hfpll1_data,
|
||||
+ .clkr.hw.init = &(struct clk_init_data){
|
||||
+ .parent_names = (const char *[]){ "pxo" },
|
||||
+ .num_parents = 1,
|
||||
+ .name = "hfpll1",
|
||||
+ .ops = &clk_ops_hfpll,
|
||||
+ .flags = CLK_IGNORE_UNUSED,
|
||||
+ },
|
||||
+ .lock = __SPIN_LOCK_UNLOCKED(hfpll1.lock),
|
||||
+};
|
||||
+
|
||||
+static struct hfpll_data hfpll_l2_data = {
|
||||
+ .mode_reg = 0x3300,
|
||||
+ .l_reg = 0x3308,
|
||||
+ .m_reg = 0x330c,
|
||||
+ .n_reg = 0x3310,
|
||||
+ .config_reg = 0x3304,
|
||||
+ .status_reg = 0x331c,
|
||||
+ .config_val = 0x7845c665,
|
||||
+ .droop_reg = 0x3314,
|
||||
+ .droop_val = 0x0108c000,
|
||||
+ .min_rate = 600000000UL,
|
||||
+ .max_rate = 1800000000UL,
|
||||
+};
|
||||
+
|
||||
+static struct clk_hfpll hfpll_l2 = {
|
||||
+ .d = &hfpll_l2_data,
|
||||
+ .clkr.hw.init = &(struct clk_init_data){
|
||||
+ .parent_names = (const char *[]){ "pxo" },
|
||||
+ .num_parents = 1,
|
||||
+ .name = "hfpll_l2",
|
||||
+ .ops = &clk_ops_hfpll,
|
||||
+ .flags = CLK_IGNORE_UNUSED,
|
||||
+ },
|
||||
+ .lock = __SPIN_LOCK_UNLOCKED(hfpll_l2.lock),
|
||||
+};
|
||||
+
|
||||
+
|
||||
static struct clk_pll pll14 = {
|
||||
.l_reg = 0x31c4,
|
||||
.m_reg = 0x31c8,
|
||||
@@ -2273,6 +2353,9 @@ static struct clk_regmap *gcc_ipq806x_cl
|
||||
[USB_FS1_XCVR_SRC] = &usb_fs1_xcvr_clk_src.clkr,
|
||||
[USB_FS1_XCVR_CLK] = &usb_fs1_xcvr_clk.clkr,
|
||||
[USB_FS1_SYSTEM_CLK] = &usb_fs1_sys_clk.clkr,
|
||||
+ [PLL9] = &hfpll0.clkr,
|
||||
+ [PLL10] = &hfpll1.clkr,
|
||||
+ [PLL12] = &hfpll_l2.clkr,
|
||||
};
|
||||
|
||||
static const struct qcom_reset_map gcc_ipq806x_resets[] = {
|
|
@ -0,0 +1,271 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,09/13] clk: qcom: Add support for Krait clocks
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063251
|
||||
Message-Id: <1426920332-9340-10-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:28 -0700
|
||||
|
||||
The Krait clocks are made up of a series of muxes and a divider
|
||||
that choose between a fixed rate clock and dedicated HFPLLs for
|
||||
each CPU. Instead of using mmio accesses to remux parents, the
|
||||
Krait implementation exposes the remux control via cp15
|
||||
registers. Support these clocks.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
drivers/clk/qcom/Kconfig | 4 ++
|
||||
drivers/clk/qcom/Makefile | 1 +
|
||||
drivers/clk/qcom/clk-krait.c | 166 +++++++++++++++++++++++++++++++++++++++++++
|
||||
drivers/clk/qcom/clk-krait.h | 49 +++++++++++++
|
||||
4 files changed, 220 insertions(+)
|
||||
create mode 100644 drivers/clk/qcom/clk-krait.c
|
||||
create mode 100644 drivers/clk/qcom/clk-krait.h
|
||||
|
||||
--- a/drivers/clk/qcom/Kconfig
|
||||
+++ b/drivers/clk/qcom/Kconfig
|
||||
@@ -96,3 +96,7 @@ config QCOM_HFPLL
|
||||
Support for the high-frequency PLLs present on Qualcomm devices.
|
||||
Say Y if you want to support CPU frequency scaling on devices
|
||||
such as MSM8974, APQ8084, etc.
|
||||
+
|
||||
+config KRAIT_CLOCKS
|
||||
+ bool
|
||||
+ select KRAIT_L2_ACCESSORS
|
||||
--- a/drivers/clk/qcom/Makefile
|
||||
+++ b/drivers/clk/qcom/Makefile
|
||||
@@ -8,6 +8,7 @@ clk-qcom-y += clk-rcg2.o
|
||||
clk-qcom-y += clk-branch.o
|
||||
clk-qcom-y += clk-regmap-divider.o
|
||||
clk-qcom-y += clk-regmap-mux.o
|
||||
+clk-qcom-$(CONFIG_KRAIT_CLOCKS) += clk-krait.o
|
||||
clk-qcom-y += clk-hfpll.o
|
||||
clk-qcom-y += reset.o
|
||||
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/clk-krait.c
|
||||
@@ -0,0 +1,166 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2013-2014, 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/clk-provider.h>
|
||||
+#include <linux/spinlock.h>
|
||||
+
|
||||
+#include <asm/krait-l2-accessors.h>
|
||||
+
|
||||
+#include "clk-krait.h"
|
||||
+
|
||||
+/* Secondary and primary muxes share the same cp15 register */
|
||||
+static DEFINE_SPINLOCK(krait_clock_reg_lock);
|
||||
+
|
||||
+#define LPL_SHIFT 8
|
||||
+static void __krait_mux_set_sel(struct krait_mux_clk *mux, int sel)
|
||||
+{
|
||||
+ unsigned long flags;
|
||||
+ u32 regval;
|
||||
+
|
||||
+ spin_lock_irqsave(&krait_clock_reg_lock, flags);
|
||||
+ regval = krait_get_l2_indirect_reg(mux->offset);
|
||||
+ regval &= ~(mux->mask << mux->shift);
|
||||
+ regval |= (sel & mux->mask) << mux->shift;
|
||||
+ if (mux->lpl) {
|
||||
+ regval &= ~(mux->mask << (mux->shift + LPL_SHIFT));
|
||||
+ regval |= (sel & mux->mask) << (mux->shift + LPL_SHIFT);
|
||||
+ }
|
||||
+ krait_set_l2_indirect_reg(mux->offset, regval);
|
||||
+ spin_unlock_irqrestore(&krait_clock_reg_lock, flags);
|
||||
+
|
||||
+ /* Wait for switch to complete. */
|
||||
+ mb();
|
||||
+ udelay(1);
|
||||
+}
|
||||
+
|
||||
+static int krait_mux_set_parent(struct clk_hw *hw, u8 index)
|
||||
+{
|
||||
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
|
||||
+ u32 sel;
|
||||
+
|
||||
+ sel = clk_mux_reindex(index, mux->parent_map, 0);
|
||||
+ mux->en_mask = sel;
|
||||
+ /* Don't touch mux if CPU is off as it won't work */
|
||||
+ if (__clk_is_enabled(hw->clk))
|
||||
+ __krait_mux_set_sel(mux, sel);
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static u8 krait_mux_get_parent(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
|
||||
+ u32 sel;
|
||||
+
|
||||
+ sel = krait_get_l2_indirect_reg(mux->offset);
|
||||
+ sel >>= mux->shift;
|
||||
+ sel &= mux->mask;
|
||||
+ mux->en_mask = sel;
|
||||
+
|
||||
+ return clk_mux_get_parent(hw, sel, mux->parent_map, 0);
|
||||
+}
|
||||
+
|
||||
+static struct clk_hw *krait_mux_get_safe_parent(struct clk_hw *hw)
|
||||
+{
|
||||
+ int i;
|
||||
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
|
||||
+ int num_parents = __clk_get_num_parents(hw->clk);
|
||||
+
|
||||
+ i = mux->safe_sel;
|
||||
+ for (i = 0; i < num_parents; i++)
|
||||
+ if (mux->safe_sel == mux->parent_map[i])
|
||||
+ break;
|
||||
+
|
||||
+ return __clk_get_hw(clk_get_parent_by_index(hw->clk, i));
|
||||
+}
|
||||
+
|
||||
+static int krait_mux_enable(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
|
||||
+
|
||||
+ __krait_mux_set_sel(mux, mux->en_mask);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void krait_mux_disable(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
|
||||
+
|
||||
+ __krait_mux_set_sel(mux, mux->safe_sel);
|
||||
+}
|
||||
+
|
||||
+const struct clk_ops krait_mux_clk_ops = {
|
||||
+ .enable = krait_mux_enable,
|
||||
+ .disable = krait_mux_disable,
|
||||
+ .set_parent = krait_mux_set_parent,
|
||||
+ .get_parent = krait_mux_get_parent,
|
||||
+ .determine_rate = __clk_mux_determine_rate_closest,
|
||||
+ .get_safe_parent = krait_mux_get_safe_parent,
|
||||
+};
|
||||
+EXPORT_SYMBOL_GPL(krait_mux_clk_ops);
|
||||
+
|
||||
+/* The divider can divide by 2, 4, 6 and 8. But we only really need div-2. */
|
||||
+static long krait_div2_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long *parent_rate)
|
||||
+{
|
||||
+ *parent_rate = __clk_round_rate(__clk_get_parent(hw->clk), rate * 2);
|
||||
+ return DIV_ROUND_UP(*parent_rate, 2);
|
||||
+}
|
||||
+
|
||||
+static int krait_div2_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long parent_rate)
|
||||
+{
|
||||
+ struct krait_div2_clk *d = to_krait_div2_clk(hw);
|
||||
+ unsigned long flags;
|
||||
+ u32 val;
|
||||
+ u32 mask = BIT(d->width) - 1;
|
||||
+
|
||||
+ if (d->lpl)
|
||||
+ mask = mask << (d->shift + LPL_SHIFT) | mask << d->shift;
|
||||
+
|
||||
+ spin_lock_irqsave(&krait_clock_reg_lock, flags);
|
||||
+ val = krait_get_l2_indirect_reg(d->offset);
|
||||
+ val &= ~mask;
|
||||
+ krait_set_l2_indirect_reg(d->offset, val);
|
||||
+ spin_unlock_irqrestore(&krait_clock_reg_lock, flags);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static unsigned long
|
||||
+krait_div2_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
||||
+{
|
||||
+ struct krait_div2_clk *d = to_krait_div2_clk(hw);
|
||||
+ u32 mask = BIT(d->width) - 1;
|
||||
+ u32 div;
|
||||
+
|
||||
+ div = krait_get_l2_indirect_reg(d->offset);
|
||||
+ div >>= d->shift;
|
||||
+ div &= mask;
|
||||
+ div = (div + 1) * 2;
|
||||
+
|
||||
+ return DIV_ROUND_UP(parent_rate, div);
|
||||
+}
|
||||
+
|
||||
+const struct clk_ops krait_div2_clk_ops = {
|
||||
+ .round_rate = krait_div2_round_rate,
|
||||
+ .set_rate = krait_div2_set_rate,
|
||||
+ .recalc_rate = krait_div2_recalc_rate,
|
||||
+};
|
||||
+EXPORT_SYMBOL_GPL(krait_div2_clk_ops);
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/clk-krait.h
|
||||
@@ -0,0 +1,49 @@
|
||||
+/*
|
||||
+ * 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 __QCOM_CLK_KRAIT_H
|
||||
+#define __QCOM_CLK_KRAIT_H
|
||||
+
|
||||
+#include <linux/clk-provider.h>
|
||||
+
|
||||
+struct krait_mux_clk {
|
||||
+ unsigned int *parent_map;
|
||||
+ bool has_safe_parent;
|
||||
+ u8 safe_sel;
|
||||
+ u32 offset;
|
||||
+ u32 mask;
|
||||
+ u32 shift;
|
||||
+ u32 en_mask;
|
||||
+ bool lpl;
|
||||
+
|
||||
+ struct clk_hw hw;
|
||||
+};
|
||||
+
|
||||
+#define to_krait_mux_clk(_hw) container_of(_hw, struct krait_mux_clk, hw)
|
||||
+
|
||||
+extern const struct clk_ops krait_mux_clk_ops;
|
||||
+
|
||||
+struct krait_div2_clk {
|
||||
+ u32 offset;
|
||||
+ u8 width;
|
||||
+ u32 shift;
|
||||
+ bool lpl;
|
||||
+
|
||||
+ struct clk_hw hw;
|
||||
+};
|
||||
+
|
||||
+#define to_krait_div2_clk(_hw) container_of(_hw, struct krait_div2_clk, hw)
|
||||
+
|
||||
+extern const struct clk_ops krait_div2_clk_ops;
|
||||
+
|
||||
+#endif
|
|
@ -0,0 +1,205 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,10/13] clk: qcom: Add KPSS ACC/GCC driver
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063201
|
||||
Message-Id: <1426920332-9340-11-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>, <devicetree@vger.kernel.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:29 -0700
|
||||
|
||||
The ACC and GCC regions present in KPSSv1 contain registers to
|
||||
control clocks and power to each Krait CPU and L2. For CPUfreq
|
||||
purposes probe these devices and expose a mux clock that chooses
|
||||
between PXO and PLL8.
|
||||
|
||||
Cc: <devicetree@vger.kernel.org>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
.../devicetree/bindings/arm/msm/qcom,kpss-acc.txt | 7 ++
|
||||
.../devicetree/bindings/arm/msm/qcom,kpss-gcc.txt | 28 +++++++
|
||||
drivers/clk/qcom/Kconfig | 8 ++
|
||||
drivers/clk/qcom/Makefile | 1 +
|
||||
drivers/clk/qcom/kpss-xcc.c | 95 ++++++++++++++++++++++
|
||||
5 files changed, 139 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/arm/msm/qcom,kpss-gcc.txt
|
||||
create mode 100644 drivers/clk/qcom/kpss-xcc.c
|
||||
|
||||
--- a/Documentation/devicetree/bindings/arm/msm/qcom,kpss-acc.txt
|
||||
+++ b/Documentation/devicetree/bindings/arm/msm/qcom,kpss-acc.txt
|
||||
@@ -21,10 +21,17 @@ PROPERTIES
|
||||
the register region. An optional second element specifies
|
||||
the base address and size of the alias register region.
|
||||
|
||||
+- clock-output-names:
|
||||
+ Usage: optional
|
||||
+ Value type: <string>
|
||||
+ Definition: Name of the output clock. Typically acpuX_aux where X is a
|
||||
+ CPU number starting at 0.
|
||||
+
|
||||
Example:
|
||||
|
||||
clock-controller@2088000 {
|
||||
compatible = "qcom,kpss-acc-v2";
|
||||
reg = <0x02088000 0x1000>,
|
||||
<0x02008000 0x1000>;
|
||||
+ clock-output-names = "acpu0_aux";
|
||||
};
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/arm/msm/qcom,kpss-gcc.txt
|
||||
@@ -0,0 +1,28 @@
|
||||
+Krait Processor Sub-system (KPSS) Global Clock Controller (GCC)
|
||||
+
|
||||
+PROPERTIES
|
||||
+
|
||||
+- compatible:
|
||||
+ Usage: required
|
||||
+ Value type: <string>
|
||||
+ Definition: should be one of:
|
||||
+ "qcom,kpss-gcc"
|
||||
+
|
||||
+- reg:
|
||||
+ Usage: required
|
||||
+ Value type: <prop-encoded-array>
|
||||
+ Definition: base address and size of the register region
|
||||
+
|
||||
+- clock-output-names:
|
||||
+ Usage: required
|
||||
+ Value type: <string>
|
||||
+ Definition: Name of the output clock. Typically acpu_l2_aux indicating
|
||||
+ an L2 cache auxiliary clock.
|
||||
+
|
||||
+Example:
|
||||
+
|
||||
+ l2cc: clock-controller@2011000 {
|
||||
+ compatible = "qcom,kpss-gcc";
|
||||
+ reg = <0x2011000 0x1000>;
|
||||
+ clock-output-names = "acpu_l2_aux";
|
||||
+ };
|
||||
--- a/drivers/clk/qcom/Kconfig
|
||||
+++ b/drivers/clk/qcom/Kconfig
|
||||
@@ -97,6 +97,14 @@ config QCOM_HFPLL
|
||||
Say Y if you want to support CPU frequency scaling on devices
|
||||
such as MSM8974, APQ8084, etc.
|
||||
|
||||
+config KPSS_XCC
|
||||
+ tristate "KPSS Clock Controller"
|
||||
+ depends on COMMON_CLK_QCOM
|
||||
+ help
|
||||
+ Support for the Krait ACC and GCC clock controllers. Say Y
|
||||
+ if you want to support CPU frequency scaling on devices such
|
||||
+ as MSM8960, APQ8064, etc.
|
||||
+
|
||||
config KRAIT_CLOCKS
|
||||
bool
|
||||
select KRAIT_L2_ACCESSORS
|
||||
--- a/drivers/clk/qcom/Makefile
|
||||
+++ b/drivers/clk/qcom/Makefile
|
||||
@@ -22,4 +22,5 @@ obj-$(CONFIG_MSM_LCC_8960) += lcc-msm896
|
||||
obj-$(CONFIG_MSM_GCC_8974) += gcc-msm8974.o
|
||||
obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8960.o
|
||||
obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o
|
||||
+obj-$(CONFIG_KPSS_XCC) += kpss-xcc.o
|
||||
obj-$(CONFIG_QCOM_HFPLL) += hfpll.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/kpss-xcc.c
|
||||
@@ -0,0 +1,95 @@
|
||||
+/* Copyright (c) 2014-2015, 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/init.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/err.h>
|
||||
+#include <linux/io.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/of_device.h>
|
||||
+#include <linux/clk.h>
|
||||
+#include <linux/clk-provider.h>
|
||||
+
|
||||
+static const char *aux_parents[] = {
|
||||
+ "pll8_vote",
|
||||
+ "pxo",
|
||||
+};
|
||||
+
|
||||
+static unsigned int aux_parent_map[] = {
|
||||
+ 3,
|
||||
+ 0,
|
||||
+};
|
||||
+
|
||||
+static const struct of_device_id kpss_xcc_match_table[] = {
|
||||
+ { .compatible = "qcom,kpss-acc-v1", .data = (void *)1UL },
|
||||
+ { .compatible = "qcom,kpss-gcc" },
|
||||
+ {}
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, kpss_xcc_match_table);
|
||||
+
|
||||
+static int kpss_xcc_driver_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ const struct of_device_id *id;
|
||||
+ struct clk *clk;
|
||||
+ struct resource *res;
|
||||
+ void __iomem *base;
|
||||
+ const char *name;
|
||||
+
|
||||
+ id = of_match_device(kpss_xcc_match_table, &pdev->dev);
|
||||
+ if (!id)
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
+ base = devm_ioremap_resource(&pdev->dev, res);
|
||||
+ if (IS_ERR(base))
|
||||
+ return PTR_ERR(base);
|
||||
+
|
||||
+ if (id->data) {
|
||||
+ if (of_property_read_string_index(pdev->dev.of_node,
|
||||
+ "clock-output-names", 0, &name))
|
||||
+ return -ENODEV;
|
||||
+ base += 0x14;
|
||||
+ } else {
|
||||
+ name = "acpu_l2_aux";
|
||||
+ base += 0x28;
|
||||
+ }
|
||||
+
|
||||
+ clk = clk_register_mux_table(&pdev->dev, name, aux_parents,
|
||||
+ ARRAY_SIZE(aux_parents), 0, base, 0, 0x3,
|
||||
+ 0, aux_parent_map, NULL);
|
||||
+
|
||||
+ platform_set_drvdata(pdev, clk);
|
||||
+
|
||||
+ return PTR_ERR_OR_ZERO(clk);
|
||||
+}
|
||||
+
|
||||
+static int kpss_xcc_driver_remove(struct platform_device *pdev)
|
||||
+{
|
||||
+ clk_unregister_mux(platform_get_drvdata(pdev));
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver kpss_xcc_driver = {
|
||||
+ .probe = kpss_xcc_driver_probe,
|
||||
+ .remove = kpss_xcc_driver_remove,
|
||||
+ .driver = {
|
||||
+ .name = "kpss-xcc",
|
||||
+ .of_match_table = kpss_xcc_match_table,
|
||||
+ },
|
||||
+};
|
||||
+module_platform_driver(kpss_xcc_driver);
|
||||
+
|
||||
+MODULE_DESCRIPTION("Krait Processor Sub System (KPSS) Clock Driver");
|
||||
+MODULE_LICENSE("GPL v2");
|
||||
+MODULE_ALIAS("platform:kpss-xcc");
|
|
@ -0,0 +1,435 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,11/13] clk: qcom: Add Krait clock controller driver
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063121
|
||||
Message-Id: <1426920332-9340-12-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>, <devicetree@vger.kernel.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:30 -0700
|
||||
|
||||
The Krait CPU clocks are made up of a primary mux and secondary
|
||||
mux for each CPU and the L2, controlled via cp15 accessors. For
|
||||
Kraits within KPSSv1 each secondary mux accepts a different aux
|
||||
source, but on KPSSv2 each secondary mux accepts the same aux
|
||||
source.
|
||||
|
||||
Cc: <devicetree@vger.kernel.org>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
.../devicetree/bindings/clock/qcom,krait-cc.txt | 22 ++
|
||||
drivers/clk/qcom/Kconfig | 8 +
|
||||
drivers/clk/qcom/Makefile | 1 +
|
||||
drivers/clk/qcom/krait-cc.c | 352 +++++++++++++++++++++
|
||||
4 files changed, 383 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/clock/qcom,krait-cc.txt
|
||||
create mode 100644 drivers/clk/qcom/krait-cc.c
|
||||
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/clock/qcom,krait-cc.txt
|
||||
@@ -0,0 +1,22 @@
|
||||
+Krait Clock Controller
|
||||
+
|
||||
+PROPERTIES
|
||||
+
|
||||
+- compatible:
|
||||
+ Usage: required
|
||||
+ Value type: <string>
|
||||
+ Definition: must be one of:
|
||||
+ "qcom,krait-cc-v1"
|
||||
+ "qcom,krait-cc-v2"
|
||||
+
|
||||
+- #clock-cells:
|
||||
+ Usage: required
|
||||
+ Value type: <u32>
|
||||
+ Definition: must be 1
|
||||
+
|
||||
+Example:
|
||||
+
|
||||
+ kraitcc: clock-controller {
|
||||
+ compatible = "qcom,krait-cc-v1";
|
||||
+ #clock-cells = <1>;
|
||||
+ };
|
||||
--- a/drivers/clk/qcom/Kconfig
|
||||
+++ b/drivers/clk/qcom/Kconfig
|
||||
@@ -105,6 +105,14 @@ config KPSS_XCC
|
||||
if you want to support CPU frequency scaling on devices such
|
||||
as MSM8960, APQ8064, etc.
|
||||
|
||||
+config KRAITCC
|
||||
+ tristate "Krait Clock Controller"
|
||||
+ depends on COMMON_CLK_QCOM && ARM
|
||||
+ select KRAIT_CLOCKS
|
||||
+ help
|
||||
+ Support for the Krait CPU clocks on Qualcomm devices.
|
||||
+ Say Y if you want to support CPU frequency scaling.
|
||||
+
|
||||
config KRAIT_CLOCKS
|
||||
bool
|
||||
select KRAIT_L2_ACCESSORS
|
||||
--- a/drivers/clk/qcom/Makefile
|
||||
+++ b/drivers/clk/qcom/Makefile
|
||||
@@ -24,3 +24,4 @@ obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8
|
||||
obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o
|
||||
obj-$(CONFIG_KPSS_XCC) += kpss-xcc.o
|
||||
obj-$(CONFIG_QCOM_HFPLL) += hfpll.o
|
||||
+obj-$(CONFIG_KRAITCC) += krait-cc.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/krait-cc.c
|
||||
@@ -0,0 +1,352 @@
|
||||
+/* Copyright (c) 2013-2015, 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/init.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/err.h>
|
||||
+#include <linux/io.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/of_device.h>
|
||||
+#include <linux/clk.h>
|
||||
+#include <linux/clk-provider.h>
|
||||
+#include <linux/slab.h>
|
||||
+
|
||||
+#include "clk-krait.h"
|
||||
+
|
||||
+static unsigned int sec_mux_map[] = {
|
||||
+ 2,
|
||||
+ 0,
|
||||
+};
|
||||
+
|
||||
+static unsigned int pri_mux_map[] = {
|
||||
+ 1,
|
||||
+ 2,
|
||||
+ 0,
|
||||
+};
|
||||
+
|
||||
+static int
|
||||
+krait_add_div(struct device *dev, int id, const char *s, unsigned offset)
|
||||
+{
|
||||
+ struct krait_div2_clk *div;
|
||||
+ struct clk_init_data init = {
|
||||
+ .num_parents = 1,
|
||||
+ .ops = &krait_div2_clk_ops,
|
||||
+ .flags = CLK_SET_RATE_PARENT,
|
||||
+ };
|
||||
+ const char *p_names[1];
|
||||
+ struct clk *clk;
|
||||
+
|
||||
+ div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
|
||||
+ if (!div)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ div->width = 2;
|
||||
+ div->shift = 6;
|
||||
+ div->lpl = id >= 0;
|
||||
+ div->offset = offset;
|
||||
+ div->hw.init = &init;
|
||||
+
|
||||
+ init.name = kasprintf(GFP_KERNEL, "hfpll%s_div", s);
|
||||
+ if (!init.name)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ init.parent_names = p_names;
|
||||
+ p_names[0] = kasprintf(GFP_KERNEL, "hfpll%s", s);
|
||||
+ if (!p_names[0]) {
|
||||
+ kfree(init.name);
|
||||
+ return -ENOMEM;
|
||||
+ }
|
||||
+
|
||||
+ clk = devm_clk_register(dev, &div->hw);
|
||||
+ kfree(p_names[0]);
|
||||
+ kfree(init.name);
|
||||
+
|
||||
+ return PTR_ERR_OR_ZERO(clk);
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+krait_add_sec_mux(struct device *dev, int id, const char *s, unsigned offset,
|
||||
+ bool unique_aux)
|
||||
+{
|
||||
+ struct krait_mux_clk *mux;
|
||||
+ static const char *sec_mux_list[] = {
|
||||
+ "acpu_aux",
|
||||
+ "qsb",
|
||||
+ };
|
||||
+ struct clk_init_data init = {
|
||||
+ .parent_names = sec_mux_list,
|
||||
+ .num_parents = ARRAY_SIZE(sec_mux_list),
|
||||
+ .ops = &krait_mux_clk_ops,
|
||||
+ .flags = CLK_SET_RATE_PARENT,
|
||||
+ };
|
||||
+ struct clk *clk;
|
||||
+
|
||||
+ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
|
||||
+ if (!mux)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ mux->offset = offset;
|
||||
+ mux->lpl = id >= 0;
|
||||
+ mux->has_safe_parent = true;
|
||||
+ mux->safe_sel = 2;
|
||||
+ mux->mask = 0x3;
|
||||
+ mux->shift = 2;
|
||||
+ mux->parent_map = sec_mux_map;
|
||||
+ mux->hw.init = &init;
|
||||
+
|
||||
+ init.name = kasprintf(GFP_KERNEL, "krait%s_sec_mux", s);
|
||||
+ if (!init.name)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ if (unique_aux) {
|
||||
+ sec_mux_list[0] = kasprintf(GFP_KERNEL, "acpu%s_aux", s);
|
||||
+ if (!sec_mux_list[0]) {
|
||||
+ clk = ERR_PTR(-ENOMEM);
|
||||
+ goto err_aux;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ clk = devm_clk_register(dev, &mux->hw);
|
||||
+
|
||||
+ if (unique_aux)
|
||||
+ kfree(sec_mux_list[0]);
|
||||
+err_aux:
|
||||
+ kfree(init.name);
|
||||
+ return PTR_ERR_OR_ZERO(clk);
|
||||
+}
|
||||
+
|
||||
+static struct clk *
|
||||
+krait_add_pri_mux(struct device *dev, int id, const char *s, unsigned offset)
|
||||
+{
|
||||
+ struct krait_mux_clk *mux;
|
||||
+ const char *p_names[3];
|
||||
+ struct clk_init_data init = {
|
||||
+ .parent_names = p_names,
|
||||
+ .num_parents = ARRAY_SIZE(p_names),
|
||||
+ .ops = &krait_mux_clk_ops,
|
||||
+ .flags = CLK_SET_RATE_PARENT,
|
||||
+ };
|
||||
+ struct clk *clk;
|
||||
+
|
||||
+ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
|
||||
+ if (!mux)
|
||||
+ return ERR_PTR(-ENOMEM);
|
||||
+
|
||||
+ mux->has_safe_parent = true;
|
||||
+ mux->safe_sel = 0;
|
||||
+ mux->mask = 0x3;
|
||||
+ mux->shift = 0;
|
||||
+ mux->offset = offset;
|
||||
+ mux->lpl = id >= 0;
|
||||
+ mux->parent_map = pri_mux_map;
|
||||
+ mux->hw.init = &init;
|
||||
+
|
||||
+ init.name = kasprintf(GFP_KERNEL, "krait%s_pri_mux", s);
|
||||
+ if (!init.name)
|
||||
+ return ERR_PTR(-ENOMEM);
|
||||
+
|
||||
+ p_names[0] = kasprintf(GFP_KERNEL, "hfpll%s", s);
|
||||
+ if (!p_names[0]) {
|
||||
+ clk = ERR_PTR(-ENOMEM);
|
||||
+ goto err_p0;
|
||||
+ }
|
||||
+
|
||||
+ p_names[1] = kasprintf(GFP_KERNEL, "hfpll%s_div", s);
|
||||
+ if (!p_names[1]) {
|
||||
+ clk = ERR_PTR(-ENOMEM);
|
||||
+ goto err_p1;
|
||||
+ }
|
||||
+
|
||||
+ p_names[2] = kasprintf(GFP_KERNEL, "krait%s_sec_mux", s);
|
||||
+ if (!p_names[2]) {
|
||||
+ clk = ERR_PTR(-ENOMEM);
|
||||
+ goto err_p2;
|
||||
+ }
|
||||
+
|
||||
+ clk = devm_clk_register(dev, &mux->hw);
|
||||
+
|
||||
+ kfree(p_names[2]);
|
||||
+err_p2:
|
||||
+ kfree(p_names[1]);
|
||||
+err_p1:
|
||||
+ kfree(p_names[0]);
|
||||
+err_p0:
|
||||
+ kfree(init.name);
|
||||
+ return clk;
|
||||
+}
|
||||
+
|
||||
+/* id < 0 for L2, otherwise id == physical CPU number */
|
||||
+static struct clk *krait_add_clks(struct device *dev, int id, bool unique_aux)
|
||||
+{
|
||||
+ int ret;
|
||||
+ unsigned offset;
|
||||
+ void *p = NULL;
|
||||
+ const char *s;
|
||||
+ struct clk *clk;
|
||||
+
|
||||
+ if (id >= 0) {
|
||||
+ offset = 0x4501 + (0x1000 * id);
|
||||
+ s = p = kasprintf(GFP_KERNEL, "%d", id);
|
||||
+ if (!s)
|
||||
+ return ERR_PTR(-ENOMEM);
|
||||
+ } else {
|
||||
+ offset = 0x500;
|
||||
+ s = "_l2";
|
||||
+ }
|
||||
+
|
||||
+ ret = krait_add_div(dev, id, s, offset);
|
||||
+ if (ret) {
|
||||
+ clk = ERR_PTR(ret);
|
||||
+ goto err;
|
||||
+ }
|
||||
+
|
||||
+ ret = krait_add_sec_mux(dev, id, s, offset, unique_aux);
|
||||
+ if (ret) {
|
||||
+ clk = ERR_PTR(ret);
|
||||
+ goto err;
|
||||
+ }
|
||||
+
|
||||
+ clk = krait_add_pri_mux(dev, id, s, offset);
|
||||
+err:
|
||||
+ kfree(p);
|
||||
+ return clk;
|
||||
+}
|
||||
+
|
||||
+static struct clk *krait_of_get(struct of_phandle_args *clkspec, void *data)
|
||||
+{
|
||||
+ unsigned int idx = clkspec->args[0];
|
||||
+ struct clk **clks = data;
|
||||
+
|
||||
+ if (idx >= 5) {
|
||||
+ pr_err("%s: invalid clock index %d\n", __func__, idx);
|
||||
+ return ERR_PTR(-EINVAL);
|
||||
+ }
|
||||
+
|
||||
+ return clks[idx] ? : ERR_PTR(-ENODEV);
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id krait_cc_match_table[] = {
|
||||
+ { .compatible = "qcom,krait-cc-v1", (void *)1UL },
|
||||
+ { .compatible = "qcom,krait-cc-v2" },
|
||||
+ {}
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, krait_cc_match_table);
|
||||
+
|
||||
+static int krait_cc_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ const struct of_device_id *id;
|
||||
+ unsigned long cur_rate, aux_rate;
|
||||
+ int cpu;
|
||||
+ struct clk *clk;
|
||||
+ struct clk **clks;
|
||||
+ struct clk *l2_pri_mux_clk;
|
||||
+
|
||||
+ id = of_match_device(krait_cc_match_table, dev);
|
||||
+ if (!id)
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ /* Rate is 1 because 0 causes problems for __clk_mux_determine_rate */
|
||||
+ clk = clk_register_fixed_rate(dev, "qsb", NULL, CLK_IS_ROOT, 1);
|
||||
+ if (IS_ERR(clk))
|
||||
+ return PTR_ERR(clk);
|
||||
+
|
||||
+ if (!id->data) {
|
||||
+ clk = clk_register_fixed_factor(dev, "acpu_aux",
|
||||
+ "gpll0_vote", 0, 1, 2);
|
||||
+ if (IS_ERR(clk))
|
||||
+ return PTR_ERR(clk);
|
||||
+ }
|
||||
+
|
||||
+ /* Krait configurations have at most 4 CPUs and one L2 */
|
||||
+ clks = devm_kcalloc(dev, 5, sizeof(*clks), GFP_KERNEL);
|
||||
+ if (!clks)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ for_each_possible_cpu(cpu) {
|
||||
+ clk = krait_add_clks(dev, cpu, id->data);
|
||||
+ if (IS_ERR(clk))
|
||||
+ return PTR_ERR(clk);
|
||||
+ clks[cpu] = clk;
|
||||
+ }
|
||||
+
|
||||
+ l2_pri_mux_clk = krait_add_clks(dev, -1, id->data);
|
||||
+ if (IS_ERR(l2_pri_mux_clk))
|
||||
+ return PTR_ERR(l2_pri_mux_clk);
|
||||
+ clks[4] = l2_pri_mux_clk;
|
||||
+
|
||||
+ /*
|
||||
+ * We don't want the CPU or L2 clocks to be turned off at late init
|
||||
+ * if CPUFREQ or HOTPLUG configs are disabled. So, bump up the
|
||||
+ * refcount of these clocks. Any cpufreq/hotplug manager can assume
|
||||
+ * that the clocks have already been prepared and enabled by the time
|
||||
+ * they take over.
|
||||
+ */
|
||||
+ for_each_online_cpu(cpu) {
|
||||
+ clk_prepare_enable(l2_pri_mux_clk);
|
||||
+ WARN(clk_prepare_enable(clks[cpu]),
|
||||
+ "Unable to turn on CPU%d clock", cpu);
|
||||
+ }
|
||||
+
|
||||
+ /*
|
||||
+ * Force reinit of HFPLLs and muxes to overwrite any potential
|
||||
+ * incorrect configuration of HFPLLs and muxes by the bootloader.
|
||||
+ * While at it, also make sure the cores are running at known rates
|
||||
+ * and print the current rate.
|
||||
+ *
|
||||
+ * The clocks are set to aux clock rate first to make sure the
|
||||
+ * secondary mux is not sourcing off of QSB. The rate is then set to
|
||||
+ * two different rates to force a HFPLL reinit under all
|
||||
+ * circumstances.
|
||||
+ */
|
||||
+ cur_rate = clk_get_rate(l2_pri_mux_clk);
|
||||
+ aux_rate = 384000000;
|
||||
+ if (cur_rate == 1) {
|
||||
+ pr_info("L2 @ QSB rate. Forcing new rate.\n");
|
||||
+ cur_rate = aux_rate;
|
||||
+ }
|
||||
+ clk_set_rate(l2_pri_mux_clk, aux_rate);
|
||||
+ clk_set_rate(l2_pri_mux_clk, 2);
|
||||
+ clk_set_rate(l2_pri_mux_clk, cur_rate);
|
||||
+ pr_info("L2 @ %lu KHz\n", clk_get_rate(l2_pri_mux_clk) / 1000);
|
||||
+ for_each_possible_cpu(cpu) {
|
||||
+ clk = clks[cpu];
|
||||
+ cur_rate = clk_get_rate(clk);
|
||||
+ if (cur_rate == 1) {
|
||||
+ pr_info("CPU%d @ QSB rate. Forcing new rate.\n", cpu);
|
||||
+ cur_rate = aux_rate;
|
||||
+ }
|
||||
+ clk_set_rate(clk, aux_rate);
|
||||
+ clk_set_rate(clk, 2);
|
||||
+ clk_set_rate(clk, cur_rate);
|
||||
+ pr_info("CPU%d @ %lu KHz\n", cpu, clk_get_rate(clk) / 1000);
|
||||
+ }
|
||||
+
|
||||
+ of_clk_add_provider(dev->of_node, krait_of_get, clks);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver krait_cc_driver = {
|
||||
+ .probe = krait_cc_probe,
|
||||
+ .driver = {
|
||||
+ .name = "krait-cc",
|
||||
+ .of_match_table = krait_cc_match_table,
|
||||
+ },
|
||||
+};
|
||||
+module_platform_driver(krait_cc_driver);
|
||||
+
|
||||
+MODULE_DESCRIPTION("Krait CPU Clock Driver");
|
||||
+MODULE_LICENSE("GPL v2");
|
||||
+MODULE_ALIAS("platform:krait-cc");
|
|
@ -0,0 +1,304 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,12/13] cpufreq: Add module to register cpufreq on Krait CPUs
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063191
|
||||
Message-Id: <1426920332-9340-13-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>, <devicetree@vger.kernel.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:31 -0700
|
||||
|
||||
Register a cpufreq-generic device whenever we detect that a
|
||||
"qcom,krait" compatible CPU is present in DT.
|
||||
|
||||
Cc: <devicetree@vger.kernel.org>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
.../devicetree/bindings/arm/msm/qcom,pvs.txt | 38 ++++
|
||||
drivers/cpufreq/Kconfig.arm | 9 +
|
||||
drivers/cpufreq/Makefile | 1 +
|
||||
drivers/cpufreq/qcom-cpufreq.c | 204 +++++++++++++++++++++
|
||||
4 files changed, 252 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/arm/msm/qcom,pvs.txt
|
||||
create mode 100644 drivers/cpufreq/qcom-cpufreq.c
|
||||
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/arm/msm/qcom,pvs.txt
|
||||
@@ -0,0 +1,38 @@
|
||||
+Qualcomm Process Voltage Scaling Tables
|
||||
+
|
||||
+The node name is required to be "qcom,pvs". There shall only be one
|
||||
+such node present in the root of the tree.
|
||||
+
|
||||
+PROPERTIES
|
||||
+
|
||||
+- qcom,pvs-format-a or qcom,pvs-format-b:
|
||||
+ Usage: required
|
||||
+ Value type: <empty>
|
||||
+ Definition: Indicates the format of qcom,speedX-pvsY-bin-vZ properties.
|
||||
+ If qcom,pvs-format-a is used the table is two columns
|
||||
+ (frequency and voltage in that order). If qcom,pvs-format-b is used the table is three columns (frequency, voltage,
|
||||
+ and current in that order).
|
||||
+
|
||||
+- qcom,speedX-pvsY-bin-vZ:
|
||||
+ Usage: required
|
||||
+ Value type: <prop-encoded-array>
|
||||
+ Definition: The PVS table corresponding to the speed bin X, pvs bin Y,
|
||||
+ and version Z.
|
||||
+Example:
|
||||
+
|
||||
+ qcom,pvs {
|
||||
+ qcom,pvs-format-a;
|
||||
+ qcom,speed0-pvs0-bin-v0 =
|
||||
+ < 384000000 950000 >,
|
||||
+ < 486000000 975000 >,
|
||||
+ < 594000000 1000000 >,
|
||||
+ < 702000000 1025000 >,
|
||||
+ < 810000000 1075000 >,
|
||||
+ < 918000000 1100000 >,
|
||||
+ < 1026000000 1125000 >,
|
||||
+ < 1134000000 1175000 >,
|
||||
+ < 1242000000 1200000 >,
|
||||
+ < 1350000000 1225000 >,
|
||||
+ < 1458000000 1237500 >,
|
||||
+ < 1512000000 1250000 >;
|
||||
+ };
|
||||
--- a/drivers/cpufreq/Kconfig.arm
|
||||
+++ b/drivers/cpufreq/Kconfig.arm
|
||||
@@ -137,6 +137,15 @@ config ARM_OMAP2PLUS_CPUFREQ
|
||||
depends on ARCH_OMAP2PLUS
|
||||
default ARCH_OMAP2PLUS
|
||||
|
||||
+config ARM_QCOM_CPUFREQ
|
||||
+ tristate "Qualcomm based"
|
||||
+ depends on ARCH_QCOM
|
||||
+ select PM_OPP
|
||||
+ help
|
||||
+ This adds the CPUFreq driver for Qualcomm SoC based boards.
|
||||
+
|
||||
+ If in doubt, say N.
|
||||
+
|
||||
config ARM_S3C_CPUFREQ
|
||||
bool
|
||||
help
|
||||
--- a/drivers/cpufreq/Makefile
|
||||
+++ b/drivers/cpufreq/Makefile
|
||||
@@ -65,6 +65,7 @@ obj-$(CONFIG_ARM_KIRKWOOD_CPUFREQ) += ki
|
||||
obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o
|
||||
obj-$(CONFIG_ARM_PXA2xx_CPUFREQ) += pxa2xx-cpufreq.o
|
||||
obj-$(CONFIG_PXA3xx) += pxa3xx-cpufreq.o
|
||||
+obj-$(CONFIG_ARM_QCOM_CPUFREQ) += qcom-cpufreq.o
|
||||
obj-$(CONFIG_ARM_S3C24XX_CPUFREQ) += s3c24xx-cpufreq.o
|
||||
obj-$(CONFIG_ARM_S3C24XX_CPUFREQ_DEBUGFS) += s3c24xx-cpufreq-debugfs.o
|
||||
obj-$(CONFIG_ARM_S3C2410_CPUFREQ) += s3c2410-cpufreq.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/cpufreq/qcom-cpufreq.c
|
||||
@@ -0,0 +1,204 @@
|
||||
+/* Copyright (c) 2014, 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/cpu.h>
|
||||
+#include <linux/err.h>
|
||||
+#include <linux/init.h>
|
||||
+#include <linux/io.h>
|
||||
+#include <linux/kernel.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/pm_opp.h>
|
||||
+#include <linux/slab.h>
|
||||
+#include <linux/cpufreq-dt.h>
|
||||
+
|
||||
+static void __init get_krait_bin_format_a(int *speed, int *pvs, int *pvs_ver)
|
||||
+{
|
||||
+ void __iomem *base;
|
||||
+ u32 pte_efuse;
|
||||
+
|
||||
+ *speed = *pvs = *pvs_ver = 0;
|
||||
+
|
||||
+ base = ioremap(0x007000c0, 4);
|
||||
+ if (!base) {
|
||||
+ pr_warn("Unable to read efuse data. Defaulting to 0!\n");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ pte_efuse = readl_relaxed(base);
|
||||
+ iounmap(base);
|
||||
+
|
||||
+ *speed = pte_efuse & 0xf;
|
||||
+ if (*speed == 0xf)
|
||||
+ *speed = (pte_efuse >> 4) & 0xf;
|
||||
+
|
||||
+ if (*speed == 0xf) {
|
||||
+ *speed = 0;
|
||||
+ pr_warn("Speed bin: Defaulting to %d\n", *speed);
|
||||
+ } else {
|
||||
+ pr_info("Speed bin: %d\n", *speed);
|
||||
+ }
|
||||
+
|
||||
+ *pvs = (pte_efuse >> 10) & 0x7;
|
||||
+ if (*pvs == 0x7)
|
||||
+ *pvs = (pte_efuse >> 13) & 0x7;
|
||||
+
|
||||
+ if (*pvs == 0x7) {
|
||||
+ *pvs = 0;
|
||||
+ pr_warn("PVS bin: Defaulting to %d\n", *pvs);
|
||||
+ } else {
|
||||
+ pr_info("PVS bin: %d\n", *pvs);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static void __init get_krait_bin_format_b(int *speed, int *pvs, int *pvs_ver)
|
||||
+{
|
||||
+ u32 pte_efuse, redundant_sel;
|
||||
+ void __iomem *base;
|
||||
+
|
||||
+ *speed = 0;
|
||||
+ *pvs = 0;
|
||||
+ *pvs_ver = 0;
|
||||
+
|
||||
+ base = ioremap(0xfc4b80b0, 8);
|
||||
+ if (!base) {
|
||||
+ pr_warn("Unable to read efuse data. Defaulting to 0!\n");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ pte_efuse = readl_relaxed(base);
|
||||
+ redundant_sel = (pte_efuse >> 24) & 0x7;
|
||||
+ *speed = pte_efuse & 0x7;
|
||||
+ /* 4 bits of PVS are in efuse register bits 31, 8-6. */
|
||||
+ *pvs = ((pte_efuse >> 28) & 0x8) | ((pte_efuse >> 6) & 0x7);
|
||||
+ *pvs_ver = (pte_efuse >> 4) & 0x3;
|
||||
+
|
||||
+ switch (redundant_sel) {
|
||||
+ case 1:
|
||||
+ *speed = (pte_efuse >> 27) & 0xf;
|
||||
+ break;
|
||||
+ case 2:
|
||||
+ *pvs = (pte_efuse >> 27) & 0xf;
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ /* Check SPEED_BIN_BLOW_STATUS */
|
||||
+ if (pte_efuse & BIT(3)) {
|
||||
+ pr_info("Speed bin: %d\n", *speed);
|
||||
+ } else {
|
||||
+ pr_warn("Speed bin not set. Defaulting to 0!\n");
|
||||
+ *speed = 0;
|
||||
+ }
|
||||
+
|
||||
+ /* Check PVS_BLOW_STATUS */
|
||||
+ pte_efuse = readl_relaxed(base + 0x4) & BIT(21);
|
||||
+ if (pte_efuse) {
|
||||
+ pr_info("PVS bin: %d\n", *pvs);
|
||||
+ } else {
|
||||
+ pr_warn("PVS bin not set. Defaulting to 0!\n");
|
||||
+ *pvs = 0;
|
||||
+ }
|
||||
+
|
||||
+ pr_info("PVS version: %d\n", *pvs_ver);
|
||||
+ iounmap(base);
|
||||
+}
|
||||
+
|
||||
+static int __init qcom_cpufreq_populate_opps(void)
|
||||
+{
|
||||
+ int len, rows, cols, i, k, speed, pvs, pvs_ver;
|
||||
+ char table_name[] = "qcom,speedXX-pvsXX-bin-vXX";
|
||||
+ struct device_node *np;
|
||||
+ struct device *dev;
|
||||
+ int cpu = 0;
|
||||
+
|
||||
+ np = of_find_node_by_name(NULL, "qcom,pvs");
|
||||
+ if (!np)
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ if (of_property_read_bool(np, "qcom,pvs-format-a")) {
|
||||
+ get_krait_bin_format_a(&speed, &pvs, &pvs_ver);
|
||||
+ cols = 2;
|
||||
+ } else if (of_property_read_bool(np, "qcom,pvs-format-b")) {
|
||||
+ get_krait_bin_format_b(&speed, &pvs, &pvs_ver);
|
||||
+ cols = 3;
|
||||
+ } else {
|
||||
+ return -ENODEV;
|
||||
+ }
|
||||
+
|
||||
+ snprintf(table_name, sizeof(table_name),
|
||||
+ "qcom,speed%d-pvs%d-bin-v%d", speed, pvs, pvs_ver);
|
||||
+
|
||||
+ if (!of_find_property(np, table_name, &len))
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ len /= sizeof(u32);
|
||||
+ if (len % cols || len == 0)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ rows = len / cols;
|
||||
+
|
||||
+ for (i = 0, k = 0; i < rows; i++) {
|
||||
+ u32 freq, volt;
|
||||
+
|
||||
+ of_property_read_u32_index(np, table_name, k++, &freq);
|
||||
+ of_property_read_u32_index(np, table_name, k++, &volt);
|
||||
+ while (k % cols)
|
||||
+ k++; /* Skip uA entries if present */
|
||||
+ for (cpu = 0; cpu < num_possible_cpus(); cpu++) {
|
||||
+ dev = get_cpu_device(cpu);
|
||||
+ if (!dev)
|
||||
+ return -ENODEV;
|
||||
+ if (dev_pm_opp_add(dev, freq, volt))
|
||||
+ pr_warn("failed to add OPP %u\n", freq);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int __init qcom_cpufreq_driver_init(void)
|
||||
+{
|
||||
+ struct cpufreq_dt_platform_data pdata = { .independent_clocks = true };
|
||||
+ struct platform_device_info devinfo = {
|
||||
+ .name = "cpufreq-dt",
|
||||
+ .data = &pdata,
|
||||
+ .size_data = sizeof(pdata),
|
||||
+ };
|
||||
+ struct device *cpu_dev;
|
||||
+ struct device_node *np;
|
||||
+ int ret;
|
||||
+
|
||||
+ cpu_dev = get_cpu_device(0);
|
||||
+ if (!cpu_dev)
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ np = of_node_get(cpu_dev->of_node);
|
||||
+ if (!np)
|
||||
+ return -ENOENT;
|
||||
+
|
||||
+ if (!of_device_is_compatible(np, "qcom,krait")) {
|
||||
+ of_node_put(np);
|
||||
+ return -ENODEV;
|
||||
+ }
|
||||
+ of_node_put(np);
|
||||
+
|
||||
+ ret = qcom_cpufreq_populate_opps();
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ return PTR_ERR_OR_ZERO(platform_device_register_full(&devinfo));
|
||||
+}
|
||||
+module_init(qcom_cpufreq_driver_init);
|
||||
+
|
||||
+MODULE_DESCRIPTION("Qualcomm CPUfreq driver");
|
||||
+MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,100 @@
|
|||
--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
@@ -23,6 +23,11 @@
|
||||
next-level-cache = <&L2>;
|
||||
qcom,acc = <&acc0>;
|
||||
qcom,saw = <&saw0>;
|
||||
+ clocks = <&kraitcc 0>;
|
||||
+ clock-names = "cpu";
|
||||
+ clock-latency = <100000>;
|
||||
+ core-supply = <&smb208_s2a>;
|
||||
+ voltage-tolerance = <5>;
|
||||
};
|
||||
|
||||
cpu@1 {
|
||||
@@ -33,11 +38,24 @@
|
||||
next-level-cache = <&L2>;
|
||||
qcom,acc = <&acc1>;
|
||||
qcom,saw = <&saw1>;
|
||||
+ clocks = <&kraitcc 1>;
|
||||
+ clock-names = "cpu";
|
||||
+ clock-latency = <100000>;
|
||||
+ core-supply = <&smb208_s2b>;
|
||||
};
|
||||
|
||||
L2: l2-cache {
|
||||
compatible = "cache";
|
||||
cache-level = <2>;
|
||||
+ clocks = <&kraitcc 4>;
|
||||
+ clock-names = "cache";
|
||||
+ cache-points-kHz = <
|
||||
+ /* kHz uV CPU kHz */
|
||||
+ 1200000 1150000 1200000
|
||||
+ 1000000 1100000 600000
|
||||
+ 384000 1100000 384000
|
||||
+ >;
|
||||
+ vdd_dig-supply = <&smb208_s1a>;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -70,6 +88,46 @@
|
||||
};
|
||||
};
|
||||
|
||||
+ kraitcc: clock-controller {
|
||||
+ compatible = "qcom,krait-cc-v1";
|
||||
+ #clock-cells = <1>;
|
||||
+ };
|
||||
+
|
||||
+ qcom,pvs {
|
||||
+ qcom,pvs-format-a;
|
||||
+ qcom,speed0-pvs0-bin-v0 =
|
||||
+ < 1400000000 1250000 >,
|
||||
+ < 1200000000 1200000 >,
|
||||
+ < 1000000000 1150000 >,
|
||||
+ < 800000000 1100000 >,
|
||||
+ < 600000000 1050000 >,
|
||||
+ < 384000000 1000000 >;
|
||||
+
|
||||
+ qcom,speed0-pvs1-bin-v0 =
|
||||
+ < 1400000000 1175000 >,
|
||||
+ < 1200000000 1125000 >,
|
||||
+ < 1000000000 1075000 >,
|
||||
+ < 800000000 1025000 >,
|
||||
+ < 600000000 975000 >,
|
||||
+ < 384000000 925000 >;
|
||||
+
|
||||
+ qcom,speed0-pvs2-bin-v0 =
|
||||
+ < 1400000000 1125000 >,
|
||||
+ < 1200000000 1075000 >,
|
||||
+ < 1000000000 1025000 >,
|
||||
+ < 800000000 995000 >,
|
||||
+ < 600000000 925000 >,
|
||||
+ < 384000000 875000 >;
|
||||
+
|
||||
+ qcom,speed0-pvs3-bin-v0 =
|
||||
+ < 1400000000 1050000 >,
|
||||
+ < 1200000000 1000000 >,
|
||||
+ < 1000000000 950000 >,
|
||||
+ < 800000000 900000 >,
|
||||
+ < 600000000 850000 >,
|
||||
+ < 384000000 800000 >;
|
||||
+ };
|
||||
+
|
||||
soc: soc {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
@@ -170,11 +228,13 @@
|
||||
acc0: clock-controller@2088000 {
|
||||
compatible = "qcom,kpss-acc-v1";
|
||||
reg = <0x02088000 0x1000>, <0x02008000 0x1000>;
|
||||
+ clock-output-names = "acpu0_aux";
|
||||
};
|
||||
|
||||
acc1: clock-controller@2098000 {
|
||||
compatible = "qcom,kpss-acc-v1";
|
||||
reg = <0x02098000 0x1000>, <0x02008000 0x1000>;
|
||||
+ clock-output-names = "acpu1_aux";
|
||||
};
|
||||
|
||||
l2cc: clock-controller@2011000 {
|
|
@ -0,0 +1,461 @@
|
|||
From dd77db4143290689d3a5e1ec61627233d0711b66 Mon Sep 17 00:00:00 2001
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Date: Fri, 30 May 2014 16:36:11 -0700
|
||||
Subject: [PATCH] FROMLIST: cpufreq: Add a cpufreq-krait based on cpufreq-cpu0
|
||||
|
||||
Krait processors have individual clocks for each CPU that can
|
||||
scale independently from one another. cpufreq-cpu0 is fairly
|
||||
close to this, but assumes that there is only one clock for all
|
||||
CPUs. Add a driver to support the Krait configuration.
|
||||
|
||||
TODO: Merge into cpufreq-cpu0? Or make generic?
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
drivers/cpufreq/Kconfig | 13 +++
|
||||
drivers/cpufreq/Makefile | 1 +
|
||||
drivers/cpufreq/cpufreq-krait.c | 190 ++++++++++++++++++++++++++++++++++++++++
|
||||
3 files changed, 204 insertions(+)
|
||||
create mode 100644 drivers/cpufreq/cpufreq-krait.c
|
||||
|
||||
--- a/drivers/cpufreq/Kconfig
|
||||
+++ b/drivers/cpufreq/Kconfig
|
||||
@@ -198,6 +198,19 @@ config CPUFREQ_DT
|
||||
|
||||
If in doubt, say N.
|
||||
|
||||
+config GENERIC_CPUFREQ_KRAIT
|
||||
+ tristate "Krait cpufreq driver"
|
||||
+ depends on HAVE_CLK && OF
|
||||
+ # if CPU_THERMAL is on and THERMAL=m, CPU0 cannot be =y:
|
||||
+ depends on !CPU_THERMAL || THERMAL
|
||||
+ select PM_OPP
|
||||
+ help
|
||||
+ This adds a generic cpufreq driver for CPU0 frequency management.
|
||||
+ It supports both uniprocessor (UP) and symmetric multiprocessor (SMP)
|
||||
+ systems which share clock and voltage across all CPUs.
|
||||
+
|
||||
+ If in doubt, say N.
|
||||
+
|
||||
if X86
|
||||
source "drivers/cpufreq/Kconfig.x86"
|
||||
endif
|
||||
--- a/drivers/cpufreq/Makefile
|
||||
+++ b/drivers/cpufreq/Makefile
|
||||
@@ -14,6 +14,7 @@ obj-$(CONFIG_CPU_FREQ_GOV_CONSERVATIVE)
|
||||
obj-$(CONFIG_CPU_FREQ_GOV_COMMON) += cpufreq_governor.o
|
||||
|
||||
obj-$(CONFIG_CPUFREQ_DT) += cpufreq-dt.o
|
||||
+obj-$(CONFIG_GENERIC_CPUFREQ_KRAIT) += cpufreq-krait.o
|
||||
|
||||
##################################################################################
|
||||
# x86 drivers.
|
||||
--- /dev/null
|
||||
+++ b/drivers/cpufreq/cpufreq-krait.c
|
||||
@@ -0,0 +1,390 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2012 Freescale Semiconductor, Inc.
|
||||
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
|
||||
+ *
|
||||
+ * The OPP code in function krait_set_target() is reused from
|
||||
+ * drivers/cpufreq/omap-cpufreq.c
|
||||
+ *
|
||||
+ * This program is free software; you can redistribute it and/or modify
|
||||
+ * it under the terms of the GNU General Public License version 2 as
|
||||
+ * published by the Free Software Foundation.
|
||||
+ */
|
||||
+
|
||||
+#include <linux/clk.h>
|
||||
+#include <linux/cpu.h>
|
||||
+#include <linux/cpu_cooling.h>
|
||||
+#include <linux/cpufreq.h>
|
||||
+#include <linux/cpumask.h>
|
||||
+#include <linux/err.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/pm_opp.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/regulator/consumer.h>
|
||||
+#include <linux/slab.h>
|
||||
+#include <linux/thermal.h>
|
||||
+
|
||||
+static unsigned int transition_latency;
|
||||
+static unsigned int voltage_tolerance; /* in percentage */
|
||||
+
|
||||
+static struct device *cpu_dev;
|
||||
+static DEFINE_PER_CPU(struct clk *, krait_cpu_clks);
|
||||
+static DEFINE_PER_CPU(struct regulator *, krait_supply_core);
|
||||
+static struct cpufreq_frequency_table *freq_table;
|
||||
+static struct thermal_cooling_device *cdev;
|
||||
+
|
||||
+struct cache_points {
|
||||
+ unsigned long cache_freq;
|
||||
+ unsigned int cache_volt;
|
||||
+ unsigned long cpu_freq;
|
||||
+};
|
||||
+
|
||||
+static struct regulator *krait_l2_reg;
|
||||
+static struct clk *krait_l2_clk;
|
||||
+static struct cache_points *krait_l2_points;
|
||||
+static int nr_krait_l2_points;
|
||||
+
|
||||
+static int krait_parse_cache_points(struct device *dev,
|
||||
+ struct device_node *of_node)
|
||||
+{
|
||||
+ const struct property *prop;
|
||||
+ const __be32 *val;
|
||||
+ int nr, i;
|
||||
+
|
||||
+ prop = of_find_property(of_node, "cache-points-kHz", NULL);
|
||||
+ if (!prop)
|
||||
+ return -ENODEV;
|
||||
+ if (!prop->value)
|
||||
+ return -ENODATA;
|
||||
+
|
||||
+ /*
|
||||
+ * Each OPP is a set of tuples consisting of frequency and
|
||||
+ * cpu-frequency like <freq-kHz volt-uV freq-kHz>.
|
||||
+ */
|
||||
+ nr = prop->length / sizeof(u32);
|
||||
+ if (nr % 3) {
|
||||
+ dev_err(dev, "%s: Invalid cache points\n", __func__);
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+ nr /= 3;
|
||||
+
|
||||
+ krait_l2_points = devm_kcalloc(dev, nr, sizeof(*krait_l2_points),
|
||||
+ GFP_KERNEL);
|
||||
+ if (!krait_l2_points)
|
||||
+ return -ENOMEM;
|
||||
+ nr_krait_l2_points = nr;
|
||||
+
|
||||
+ for (i = 0, val = prop->value; i < nr; i++) {
|
||||
+ unsigned long cache_freq = be32_to_cpup(val++) * 1000;
|
||||
+ unsigned int cache_volt = be32_to_cpup(val++);
|
||||
+ unsigned long cpu_freq = be32_to_cpup(val++) * 1000;
|
||||
+
|
||||
+ krait_l2_points[i].cache_freq = cache_freq;
|
||||
+ krait_l2_points[i].cache_volt = cache_volt;
|
||||
+ krait_l2_points[i].cpu_freq = cpu_freq;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int krait_set_target(struct cpufreq_policy *policy, unsigned int index)
|
||||
+{
|
||||
+ struct dev_pm_opp *opp;
|
||||
+ unsigned long volt = 0, volt_old = 0, tol = 0;
|
||||
+ unsigned long freq, max_cpu_freq = 0;
|
||||
+ unsigned int old_freq, new_freq;
|
||||
+ long freq_Hz, freq_exact;
|
||||
+ int ret, i;
|
||||
+ struct clk *cpu_clk;
|
||||
+ struct regulator *core;
|
||||
+ unsigned int cpu;
|
||||
+
|
||||
+ cpu_clk = per_cpu(krait_cpu_clks, policy->cpu);
|
||||
+
|
||||
+ freq_Hz = clk_round_rate(cpu_clk, freq_table[index].frequency * 1000);
|
||||
+ if (freq_Hz <= 0)
|
||||
+ freq_Hz = freq_table[index].frequency * 1000;
|
||||
+
|
||||
+ freq_exact = freq_Hz;
|
||||
+ new_freq = freq_Hz / 1000;
|
||||
+ old_freq = clk_get_rate(cpu_clk) / 1000;
|
||||
+
|
||||
+ core = per_cpu(krait_supply_core, policy->cpu);
|
||||
+
|
||||
+ rcu_read_lock();
|
||||
+ opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_Hz);
|
||||
+ if (IS_ERR(opp)) {
|
||||
+ rcu_read_unlock();
|
||||
+ pr_err("failed to find OPP for %ld\n", freq_Hz);
|
||||
+ return PTR_ERR(opp);
|
||||
+ }
|
||||
+ volt = dev_pm_opp_get_voltage(opp);
|
||||
+ rcu_read_unlock();
|
||||
+ tol = volt * voltage_tolerance / 100;
|
||||
+ volt_old = regulator_get_voltage(core);
|
||||
+
|
||||
+ pr_debug("%u MHz, %ld mV --> %u MHz, %ld mV\n",
|
||||
+ old_freq / 1000, volt_old ? volt_old / 1000 : -1,
|
||||
+ new_freq / 1000, volt ? volt / 1000 : -1);
|
||||
+
|
||||
+ /* scaling up? scale voltage before frequency */
|
||||
+ if (new_freq > old_freq) {
|
||||
+ ret = regulator_set_voltage_tol(core, volt, tol);
|
||||
+ if (ret) {
|
||||
+ pr_err("failed to scale voltage up: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ ret = clk_set_rate(cpu_clk, freq_exact);
|
||||
+ if (ret) {
|
||||
+ pr_err("failed to set clock rate: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ /* scaling down? scale voltage after frequency */
|
||||
+ if (new_freq < old_freq) {
|
||||
+ ret = regulator_set_voltage_tol(core, volt, tol);
|
||||
+ if (ret) {
|
||||
+ pr_err("failed to scale voltage down: %d\n", ret);
|
||||
+ clk_set_rate(cpu_clk, old_freq * 1000);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ for_each_possible_cpu(cpu) {
|
||||
+ freq = clk_get_rate(per_cpu(krait_cpu_clks, cpu));
|
||||
+ max_cpu_freq = max(max_cpu_freq, freq);
|
||||
+ }
|
||||
+
|
||||
+ for (i = 0; i < nr_krait_l2_points; i++) {
|
||||
+ if (max_cpu_freq >= krait_l2_points[i].cpu_freq) {
|
||||
+ if (krait_l2_reg) {
|
||||
+ ret = regulator_set_voltage_tol(krait_l2_reg,
|
||||
+ krait_l2_points[i].cache_volt,
|
||||
+ tol);
|
||||
+ if (ret) {
|
||||
+ pr_err("failed to scale l2 voltage: %d\n",
|
||||
+ ret);
|
||||
+ }
|
||||
+ }
|
||||
+ ret = clk_set_rate(krait_l2_clk,
|
||||
+ krait_l2_points[i].cache_freq);
|
||||
+ if (ret)
|
||||
+ pr_err("failed to scale l2 clk: %d\n", ret);
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ }
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int krait_cpufreq_init(struct cpufreq_policy *policy)
|
||||
+{
|
||||
+ int ret;
|
||||
+
|
||||
+ policy->clk = per_cpu(krait_cpu_clks, policy->cpu);
|
||||
+
|
||||
+ ret = cpufreq_table_validate_and_show(policy, freq_table);
|
||||
+ if (ret) {
|
||||
+ pr_err("%s: invalid frequency table: %d\n", __func__, ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ policy->cpuinfo.transition_latency = transition_latency;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct cpufreq_driver krait_cpufreq_driver = {
|
||||
+ .flags = CPUFREQ_STICKY,
|
||||
+ .verify = cpufreq_generic_frequency_table_verify,
|
||||
+ .target_index = krait_set_target,
|
||||
+ .get = cpufreq_generic_get,
|
||||
+ .init = krait_cpufreq_init,
|
||||
+ .name = "generic_krait",
|
||||
+ .attr = cpufreq_generic_attr,
|
||||
+};
|
||||
+
|
||||
+static int krait_cpufreq_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device_node *np, *cache;
|
||||
+ int ret, i;
|
||||
+ unsigned int cpu;
|
||||
+ struct device *dev;
|
||||
+ struct clk *clk;
|
||||
+ struct regulator *core;
|
||||
+ unsigned long freq_Hz, freq, max_cpu_freq;
|
||||
+ struct dev_pm_opp *opp;
|
||||
+ unsigned long volt, tol;
|
||||
+
|
||||
+ cpu_dev = get_cpu_device(0);
|
||||
+ if (!cpu_dev) {
|
||||
+ pr_err("failed to get krait device\n");
|
||||
+ return -ENODEV;
|
||||
+ }
|
||||
+
|
||||
+ np = of_node_get(cpu_dev->of_node);
|
||||
+ if (!np) {
|
||||
+ pr_err("failed to find krait node\n");
|
||||
+ return -ENOENT;
|
||||
+ }
|
||||
+
|
||||
+ ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
|
||||
+ if (ret) {
|
||||
+ pr_err("failed to init cpufreq table: %d\n", ret);
|
||||
+ goto out_put_node;
|
||||
+ }
|
||||
+
|
||||
+ of_property_read_u32(np, "voltage-tolerance", &voltage_tolerance);
|
||||
+
|
||||
+ if (of_property_read_u32(np, "clock-latency", &transition_latency))
|
||||
+ transition_latency = CPUFREQ_ETERNAL;
|
||||
+
|
||||
+ cache = of_find_next_cache_node(np);
|
||||
+ if (cache) {
|
||||
+ struct device_node *vdd;
|
||||
+
|
||||
+ vdd = of_parse_phandle(cache, "vdd_dig-supply", 0);
|
||||
+ if (vdd) {
|
||||
+ krait_l2_reg = regulator_get(NULL, vdd->name);
|
||||
+ if (IS_ERR(krait_l2_reg)) {
|
||||
+ pr_warn("failed to get l2 vdd_dig supply\n");
|
||||
+ krait_l2_reg = NULL;
|
||||
+ }
|
||||
+ of_node_put(vdd);
|
||||
+ }
|
||||
+
|
||||
+ krait_l2_clk = of_clk_get(cache, 0);
|
||||
+ if (!IS_ERR(krait_l2_clk)) {
|
||||
+ ret = krait_parse_cache_points(&pdev->dev, cache);
|
||||
+ if (ret)
|
||||
+ clk_put(krait_l2_clk);
|
||||
+ }
|
||||
+ if (IS_ERR(krait_l2_clk) || ret)
|
||||
+ krait_l2_clk = NULL;
|
||||
+ }
|
||||
+
|
||||
+ for_each_possible_cpu(cpu) {
|
||||
+ dev = get_cpu_device(cpu);
|
||||
+ if (!dev) {
|
||||
+ pr_err("failed to get krait device\n");
|
||||
+ ret = -ENOENT;
|
||||
+ goto out_free_table;
|
||||
+ }
|
||||
+ per_cpu(krait_cpu_clks, cpu) = clk = devm_clk_get(dev, NULL);
|
||||
+ if (IS_ERR(clk)) {
|
||||
+ ret = PTR_ERR(clk);
|
||||
+ goto out_free_table;
|
||||
+ }
|
||||
+ core = devm_regulator_get(dev, "core");
|
||||
+ if (IS_ERR(core)) {
|
||||
+ pr_debug("failed to get core regulator\n");
|
||||
+ ret = PTR_ERR(core);
|
||||
+ goto out_free_table;
|
||||
+ }
|
||||
+ per_cpu(krait_supply_core, cpu) = core;
|
||||
+
|
||||
+ freq_Hz = clk_get_rate(clk);
|
||||
+
|
||||
+ rcu_read_lock();
|
||||
+ opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_Hz);
|
||||
+ if (IS_ERR(opp)) {
|
||||
+ rcu_read_unlock();
|
||||
+ pr_err("failed to find OPP for %ld\n", freq_Hz);
|
||||
+ ret = PTR_ERR(opp);
|
||||
+ goto out_free_table;
|
||||
+ }
|
||||
+ volt = dev_pm_opp_get_voltage(opp);
|
||||
+ rcu_read_unlock();
|
||||
+
|
||||
+ tol = volt * voltage_tolerance / 100;
|
||||
+ ret = regulator_set_voltage_tol(core, volt, tol);
|
||||
+ if (ret) {
|
||||
+ pr_err("failed to scale voltage up: %d\n", ret);
|
||||
+ goto out_free_table;
|
||||
+ }
|
||||
+ ret = regulator_enable(core);
|
||||
+ if (ret) {
|
||||
+ pr_err("failed to enable regulator: %d\n", ret);
|
||||
+ goto out_free_table;
|
||||
+ }
|
||||
+ max_cpu_freq = max(max_cpu_freq, freq);
|
||||
+ }
|
||||
+
|
||||
+ for (i = 0; i < nr_krait_l2_points; i++) {
|
||||
+ if (max_cpu_freq >= krait_l2_points[i].cpu_freq) {
|
||||
+ if (krait_l2_reg) {
|
||||
+ ret = regulator_set_voltage_tol(krait_l2_reg,
|
||||
+ krait_l2_points[i].cache_volt,
|
||||
+ tol);
|
||||
+ if (ret)
|
||||
+ pr_err("failed to scale l2 voltage: %d\n",
|
||||
+ ret);
|
||||
+ ret = regulator_enable(krait_l2_reg);
|
||||
+ if (ret)
|
||||
+ pr_err("failed to enable l2 voltage: %d\n",
|
||||
+ ret);
|
||||
+ }
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ }
|
||||
+
|
||||
+ ret = cpufreq_register_driver(&krait_cpufreq_driver);
|
||||
+ if (ret) {
|
||||
+ pr_err("failed register driver: %d\n", ret);
|
||||
+ goto out_free_table;
|
||||
+ }
|
||||
+ of_node_put(np);
|
||||
+
|
||||
+ /*
|
||||
+ * For now, just loading the cooling device;
|
||||
+ * thermal DT code takes care of matching them.
|
||||
+ */
|
||||
+ for_each_possible_cpu(cpu) {
|
||||
+ dev = get_cpu_device(cpu);
|
||||
+ np = of_node_get(dev->of_node);
|
||||
+ if (of_find_property(np, "#cooling-cells", NULL)) {
|
||||
+ cdev = of_cpufreq_cooling_register(np, cpumask_of(cpu));
|
||||
+ if (IS_ERR(cdev))
|
||||
+ pr_err("running cpufreq without cooling device: %ld\n",
|
||||
+ PTR_ERR(cdev));
|
||||
+ }
|
||||
+ of_node_put(np);
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+
|
||||
+out_free_table:
|
||||
+ regulator_put(krait_l2_reg);
|
||||
+ clk_put(krait_l2_clk);
|
||||
+ dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
|
||||
+out_put_node:
|
||||
+ of_node_put(np);
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int krait_cpufreq_remove(struct platform_device *pdev)
|
||||
+{
|
||||
+ cpufreq_cooling_unregister(cdev);
|
||||
+ cpufreq_unregister_driver(&krait_cpufreq_driver);
|
||||
+ dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
|
||||
+ clk_put(krait_l2_clk);
|
||||
+ regulator_put(krait_l2_reg);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver krait_cpufreq_platdrv = {
|
||||
+ .driver = {
|
||||
+ .name = "cpufreq-krait",
|
||||
+ .owner = THIS_MODULE,
|
||||
+ },
|
||||
+ .probe = krait_cpufreq_probe,
|
||||
+ .remove = krait_cpufreq_remove,
|
||||
+};
|
||||
+module_platform_driver(krait_cpufreq_platdrv);
|
||||
+
|
||||
+MODULE_DESCRIPTION("Krait CPUfreq driver");
|
||||
+MODULE_LICENSE("GPL v2");
|
||||
--- a/drivers/cpufreq/qcom-cpufreq.c
|
||||
+++ b/drivers/cpufreq/qcom-cpufreq.c
|
||||
@@ -168,11 +168,8 @@ static int __init qcom_cpufreq_populate_
|
||||
|
||||
static int __init qcom_cpufreq_driver_init(void)
|
||||
{
|
||||
- struct cpufreq_dt_platform_data pdata = { .independent_clocks = true };
|
||||
struct platform_device_info devinfo = {
|
||||
- .name = "cpufreq-dt",
|
||||
- .data = &pdata,
|
||||
- .size_data = sizeof(pdata),
|
||||
+ .name = "cpufreq-krait",
|
||||
};
|
||||
struct device *cpu_dev;
|
||||
struct device_node *np;
|
|
@ -127,7 +127,7 @@
|
|||
|
||||
/ {
|
||||
model = "Qualcomm IPQ8064";
|
||||
@@ -479,5 +480,42 @@
|
||||
@@ -539,5 +540,42 @@
|
||||
|
||||
status = "disabled";
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue