aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSrinivas Kandagatla <srinivas.kandagatla@linaro.org>2016-07-20 11:54:48 +0100
committerSrinivas Kandagatla <srinivas.kandagatla@linaro.org>2016-07-20 11:54:48 +0100
commit6707e66167e610ab7eabfb9e0889bea16683cd05 (patch)
tree32db3ba6ac37185f52f25fda7c0932d4aed903ac
parentfb5b86830431ae4b99075048e536c06fea738192 (diff)
parent8b2e35a1e9448a58c0b2a4d8453bc498446f5d20 (diff)
Merge branch 'tracking-qcomlt-cpu-clk-8996' into integration-linux-qcomlt
-rw-r--r--drivers/clk/clk.c10
-rw-r--r--drivers/clk/qcom/Makefile1
-rw-r--r--drivers/clk/qcom/clk-alpha-pll.c133
-rw-r--r--drivers/clk/qcom/clk-alpha-pll.h11
-rw-r--r--drivers/clk/qcom/clk-cpu-8996.c566
-rw-r--r--drivers/clk/qcom/clk-pll.c96
-rw-r--r--drivers/clk/qcom/clk-pll.h13
-rw-r--r--include/linux/clk-provider.h6
8 files changed, 816 insertions, 20 deletions
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index 5a9c2c870dcf9..2a97ab647cd25 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -288,6 +288,12 @@ const char *clk_hw_get_name(const struct clk_hw *hw)
}
EXPORT_SYMBOL_GPL(clk_hw_get_name);
+struct clk *clk_hw_get_clk(const struct clk_hw *hw)
+{
+ return hw->clk;
+}
+EXPORT_SYMBOL_GPL(clk_hw_get_clk);
+
struct clk_hw *__clk_get_hw(struct clk *clk)
{
return !clk ? NULL : clk->core->hw;
@@ -363,7 +369,7 @@ static struct clk_core *clk_core_get_parent_by_index(struct clk_core *core,
}
struct clk_hw *
-clk_hw_get_parent_by_index(const struct clk_hw *hw, unsigned int index)
+clk_hw_get_parent_by_index(const struct clk_hw *hw, u8 index)
{
struct clk_core *parent;
@@ -849,7 +855,7 @@ int __clk_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
}
EXPORT_SYMBOL_GPL(__clk_determine_rate);
-unsigned long clk_hw_round_rate(struct clk_hw *hw, unsigned long rate)
+long clk_hw_round_rate(struct clk_hw *hw, unsigned long rate)
{
int ret;
struct clk_rate_request req;
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index ade0d729a222c..1f7936db00297 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -13,6 +13,7 @@ clk-qcom-y += clk-regmap-mux-div.o
clk-qcom-$(CONFIG_KRAIT_CLOCKS) += clk-krait.o
clk-qcom-y += clk-hfpll.o
clk-qcom-y += reset.o
+clk-qcom-y += clk-cpu-8996.o
clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o
obj-$(CONFIG_APQ_GCC_8084) += gcc-apq8084.o
diff --git a/drivers/clk/qcom/clk-alpha-pll.c b/drivers/clk/qcom/clk-alpha-pll.c
index e6a03eaf7a934..1e950a9f50e57 100644
--- a/drivers/clk/qcom/clk-alpha-pll.c
+++ b/drivers/clk/qcom/clk-alpha-pll.c
@@ -62,9 +62,10 @@
#define to_clk_alpha_pll_postdiv(_hw) container_of(to_clk_regmap(_hw), \
struct clk_alpha_pll_postdiv, clkr)
-static int wait_for_pll(struct clk_alpha_pll *pll)
+static int wait_for_pll(struct clk_alpha_pll *pll, u32 mask, bool inverse,
+ const char *action)
{
- u32 val, mask, off;
+ u32 val, off;
int count;
int ret;
const char *name = clk_hw_get_name(&pll->clkr.hw);
@@ -74,26 +75,125 @@ static int wait_for_pll(struct clk_alpha_pll *pll)
if (ret)
return ret;
- if (val & PLL_VOTE_FSM_ENA)
- mask = PLL_ACTIVE_FLAG;
- else
- mask = PLL_LOCK_DET;
-
- /* Wait for pll to enable. */
for (count = 100; count > 0; count--) {
ret = regmap_read(pll->clkr.regmap, off + PLL_MODE, &val);
if (ret)
return ret;
- if ((val & mask) == mask)
+ if (inverse && (val & mask))
+ return 0;
+ else if ((val & mask) == mask)
return 0;
udelay(1);
}
- WARN(1, "%s didn't enable after voting for it!\n", name);
+ WARN(1, "%s failed to %s!\n", name, action);
return -ETIMEDOUT;
}
+static int wait_for_pll_enable(struct clk_alpha_pll *pll, u32 mask)
+{
+ return wait_for_pll(pll, mask, 0, "enable");
+}
+
+static int wait_for_pll_disable(struct clk_alpha_pll *pll, u32 mask)
+{
+ return wait_for_pll(pll, mask, 1, "disable");
+}
+
+static int wait_for_pll_offline(struct clk_alpha_pll *pll, u32 mask)
+{
+ return wait_for_pll(pll, mask, 0, "offline");
+}
+
+
+/* alpha pll with hwfsm support */
+
+#define PLL_OFFLINE_REQ BIT(7)
+#define PLL_FSM_ENA BIT(20)
+#define PLL_OFFLINE_ACK BIT(28)
+#define PLL_ACTIVE_FLAG BIT(30)
+
+void clk_alpha_pll_configure(struct clk_alpha_pll *pll, struct regmap *regmap,
+ const struct pll_config *config)
+{
+ u32 val, mask;
+
+ regmap_write(regmap, pll->offset + PLL_CONFIG_CTL,
+ config->config_ctl_val);
+
+ val = config->main_output_mask;
+ val |= config->aux_output_mask;
+ val |= config->aux2_output_mask;
+ val |= config->early_output_mask;
+ val |= config->post_div_val;
+
+ mask = config->main_output_mask;
+ mask |= config->aux_output_mask;
+ mask |= config->aux2_output_mask;
+ mask |= config->early_output_mask;
+ mask |= config->post_div_mask;
+
+ regmap_update_bits(regmap, pll->offset + PLL_USER_CTL, mask, val);
+
+ return;
+}
+
+static int clk_alpha_pll_hwfsm_enable(struct clk_hw *hw)
+{
+ int ret;
+ struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
+ u32 val, off;
+
+ off = pll->offset;
+ ret = regmap_read(pll->clkr.regmap, off + PLL_MODE, &val);
+ if (ret)
+ return ret;
+ /* Enable HW FSM mode, clear OFFLINE request */
+ val |= PLL_FSM_ENA;
+ val &= ~PLL_OFFLINE_REQ;
+ ret = regmap_write(pll->clkr.regmap, off + PLL_MODE, val);
+ if (ret)
+ return ret;
+
+ /* Make sure enable request goes through before waiting for update */
+ mb();
+
+ ret = wait_for_pll_enable(pll, PLL_ACTIVE_FLAG);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void clk_alpha_pll_hwfsm_disable(struct clk_hw *hw)
+{
+ int ret;
+ struct clk_alpha_pll *pll = to_clk_alpha_pll(hw);
+ u32 val, off;
+
+ off = pll->offset;
+ ret = regmap_read(pll->clkr.regmap, off + PLL_MODE, &val);
+ if (ret)
+ return;
+ /* Request PLL_OFFLINE and wait for ack */
+ ret = regmap_update_bits(pll->clkr.regmap, off + PLL_MODE,
+ PLL_OFFLINE_REQ, PLL_OFFLINE_REQ);
+ if (ret)
+ return;
+ ret = wait_for_pll_offline(pll, PLL_OFFLINE_ACK);
+ if (ret)
+ return;
+
+ /* Disable hwfsm */
+ ret = regmap_update_bits(pll->clkr.regmap, off + PLL_MODE,
+ PLL_FSM_ENA, 0);
+ if (ret)
+ return;
+ wait_for_pll_disable(pll, PLL_ACTIVE_FLAG);
+ return;
+}
+
static int clk_alpha_pll_enable(struct clk_hw *hw)
{
int ret;
@@ -112,7 +212,7 @@ static int clk_alpha_pll_enable(struct clk_hw *hw)
ret = clk_enable_regmap(hw);
if (ret)
return ret;
- return wait_for_pll(pll);
+ return wait_for_pll_enable(pll, PLL_ACTIVE_FLAG);
}
/* Skip if already enabled */
@@ -136,7 +236,7 @@ static int clk_alpha_pll_enable(struct clk_hw *hw)
if (ret)
return ret;
- ret = wait_for_pll(pll);
+ ret = wait_for_pll_enable(pll, PLL_LOCK_DET);
if (ret)
return ret;
@@ -300,6 +400,15 @@ const struct clk_ops clk_alpha_pll_ops = {
};
EXPORT_SYMBOL_GPL(clk_alpha_pll_ops);
+const struct clk_ops clk_alpha_pll_hwfsm_ops = {
+ .enable = clk_alpha_pll_hwfsm_enable,
+ .disable = clk_alpha_pll_hwfsm_disable,
+ .recalc_rate = clk_alpha_pll_recalc_rate,
+ .round_rate = clk_alpha_pll_round_rate,
+ .set_rate = clk_alpha_pll_set_rate,
+};
+EXPORT_SYMBOL_GPL(clk_alpha_pll_hwfsm_ops);
+
static unsigned long
clk_alpha_pll_postdiv_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
{
diff --git a/drivers/clk/qcom/clk-alpha-pll.h b/drivers/clk/qcom/clk-alpha-pll.h
index 90ce2016e1a0e..b157ddf22f1a6 100644
--- a/drivers/clk/qcom/clk-alpha-pll.h
+++ b/drivers/clk/qcom/clk-alpha-pll.h
@@ -16,6 +16,7 @@
#include <linux/clk-provider.h>
#include "clk-regmap.h"
+#include "clk-pll.h"
struct pll_vco {
unsigned long min_freq;
@@ -36,6 +37,12 @@ struct clk_alpha_pll {
size_t num_vco;
struct clk_regmap clkr;
+ u32 config_ctl_val;
+#define PLLOUT_MAIN BIT(0)
+#define PLLOUT_AUX BIT(1)
+#define PLLOUT_AUX2 BIT(2)
+#define PLLOUT_EARLY BIT(3)
+ u32 pllout_flags;
};
/**
@@ -52,6 +59,10 @@ struct clk_alpha_pll_postdiv {
};
extern const struct clk_ops clk_alpha_pll_ops;
+extern const struct clk_ops clk_alpha_pll_hwfsm_ops;
extern const struct clk_ops clk_alpha_pll_postdiv_ops;
+void clk_alpha_pll_configure(struct clk_alpha_pll *pll, struct regmap *regmap,
+ const struct pll_config *config);
+
#endif
diff --git a/drivers/clk/qcom/clk-cpu-8996.c b/drivers/clk/qcom/clk-cpu-8996.c
new file mode 100644
index 0000000000000..cc25b5e2516ee
--- /dev/null
+++ b/drivers/clk/qcom/clk-cpu-8996.c
@@ -0,0 +1,566 @@
+/*
+ * Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/clk.h>
+#include <linux/cpu.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/pm_opp.h>
+#include <linux/pm_qos.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+
+#include <asm/cputype.h>
+
+#include "clk-alpha-pll.h"
+#include "clk-regmap-mux.h"
+#include "clk-regmap.h"
+
+#define VCO(a, b, c) { \
+ .val = a,\
+ .min_freq = b,\
+ .max_freq = c,\
+}
+
+static const struct pll_config hfpll_config = {
+ .l = 60,
+ .config_ctl_val = 0x200D4828,
+ .config_ctl_hi_val = 0x006,
+ .pre_div_mask = BIT(12),
+ .post_div_mask = 0x3 << 8,
+ .main_output_mask = BIT(0),
+ .early_output_mask = BIT(3),
+};
+
+static struct clk_pll perfcl_pll = {
+ .l_reg = 0x80004,
+ .alpha_reg = 0x80008,
+ .config_reg = 0x80010,
+ .config_ctl_reg = 0x80018,
+ .mode_reg = 0x80000,
+ .status_reg = 0x80000,
+ .status_bit = 31,
+ .min_rate = 600000000,
+ .max_rate = 3000000000,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "perfcl_pll",
+ .parent_names = (const char *[]){ "xo" },
+ .num_parents = 1,
+ .ops = &clk_pll_hwfsm_ops,
+ },
+};
+
+static struct clk_pll pwrcl_pll = {
+ .l_reg = 0x0004,
+ .alpha_reg = 0x0008,
+ .config_reg = 0x0010,
+ .config_ctl_reg = 0x0018,
+ .mode_reg = 0x0000,
+ .status_reg = 0x0000,
+ .status_bit = 31,
+ .min_rate = 600000000,
+ .max_rate = 3000000000,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "pwrcl_pll",
+ .parent_names = (const char *[]){ "xo" },
+ .num_parents = 1,
+ .ops = &clk_pll_hwfsm_ops,
+ },
+};
+
+static const struct pll_config cbfpll_config = {
+ .l = 32,
+ .config_ctl_val = 0x200D4828,
+ .config_ctl_hi_val = 0x006,
+ .pre_div_mask = BIT(12),
+ .post_div_mask = 0x3 << 8,
+ .main_output_mask = BIT(0),
+ .early_output_mask = BIT(3),
+};
+
+static struct clk_pll cbf_pll = {
+ .l_reg = 0x0008,
+ .alpha_reg = 0x0010,
+ .config_reg = 0x0018,
+ .config_ctl_reg = 0x0020,
+ .mode_reg = 0x0000,
+ .status_reg = 0x0000,
+ .status_bit = 31,
+ .min_rate = 600000000,
+ .max_rate = 3000000000,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "cbf_pll",
+ .parent_names = (const char *[]){ "xo" },
+ .num_parents = 1,
+ .ops = &clk_pll_hwfsm_ops,
+ },
+};
+
+static const struct pll_vco alt_pll_vco_modes[] = {
+ VCO(3, 250000000, 500000000),
+ VCO(2, 500000000, 750000000),
+ VCO(1, 750000000, 1000000000),
+ VCO(0, 1000000000, 2150400000),
+};
+
+static const struct pll_config altpll_config = {
+ .config_ctl_val = 0x4001051B,
+ .post_div_mask = 0x3 << 8,
+ .post_div_val = 0x1,
+ .main_output_mask = BIT(0),
+ .early_output_mask = BIT(3),
+};
+
+static struct clk_alpha_pll perfcl_alt_pll = {
+ .offset = 0x80100,
+ .vco_table = alt_pll_vco_modes,
+ .num_vco = ARRAY_SIZE(alt_pll_vco_modes),
+ .clkr.hw.init = &(struct clk_init_data) {
+ .name = "perfcl_alt_pll",
+ .parent_names = (const char *[]){ "xo" },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_hwfsm_ops,
+ },
+};
+
+static struct clk_alpha_pll pwrcl_alt_pll = {
+ .offset = 0x100,
+ .vco_table = alt_pll_vco_modes,
+ .num_vco = ARRAY_SIZE(alt_pll_vco_modes),
+ .clkr.hw.init = &(struct clk_init_data) {
+ .name = "pwrcl_alt_pll",
+ .parent_names = (const char *[]){ "xo" },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_hwfsm_ops,
+ },
+};
+
+static struct clk_regmap_mux pwrcl_pmux = {
+ .reg = 0x40,
+ .shift = 0,
+ .width = 2,
+ .clkr.hw.init = &(struct clk_init_data) {
+ .name = "pwrcl_pmux",
+ .parent_names = (const char *[]){
+ "pwrcl_smux",
+ "pwrcl_pll",
+ "xo",
+ "pwrcl_alt_pll",
+ },
+ .num_parents = 4,
+ .ops = &clk_regmap_mux_closest_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_regmap_mux pwrcl_smux = {
+ .reg = 0x40,
+ .shift = 2,
+ .width = 2,
+ .clkr.hw.init = &(struct clk_init_data) {
+ .name = "pwrcl_smux",
+ .parent_names = (const char *[]){
+ "xo",
+ "pwrcl_pll_main",
+ "xo",
+ "sys_apcsaux_clk",
+ },
+ .num_parents = 4,
+ .ops = &clk_regmap_mux_closest_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_regmap_mux perfcl_pmux = {
+ .reg = 0x80040,
+ .shift = 0,
+ .width = 2,
+ .clkr.hw.init = &(struct clk_init_data) {
+ .name = "perfcl_pmux",
+ .parent_names = (const char *[]){
+ "perfcl_smux",
+ "perfcl_pll",
+ "xo",
+ "perfcl_alt_pll",
+ },
+ .num_parents = 4,
+ .ops = &clk_regmap_mux_closest_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_regmap_mux perfcl_smux = {
+ .reg = 0x80040,
+ .shift = 2,
+ .width = 2,
+ .clkr.hw.init = &(struct clk_init_data) {
+ .name = "perfcl_smux",
+ .parent_names = (const char *[]){
+ "xo",
+ "perfcl_pll_main",
+ "xo",
+ "sys_apcsaux_clk",
+ },
+ .num_parents = 4,
+ .ops = &clk_regmap_mux_closest_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_regmap_mux cbf_pmux = {
+ .reg = 0x18,
+ .shift = 0,
+ .width = 2,
+ .clkr.hw.init = &(struct clk_init_data) {
+ .name = "cbf_pmux",
+ .parent_names = (const char *[]){
+ "xo",
+ "cbf_pll",
+ "cbf_pll_main",
+ "sys_apcsaux_clk",
+ },
+ .num_parents = 4,
+ .ops = &clk_regmap_mux_closest_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+struct clk_cpu_8996 {
+ struct clk_hw *alt_pll;
+ unsigned long *alt_pll_freqs;
+ int n_alt_pll_freqs;
+ unsigned long alt_pll_thresh;
+ struct clk_hw *pll;
+ struct clk_hw *pll_post_div;
+ unsigned long post_div_thresh;
+ struct clk_regmap clkr;
+};
+
+static unsigned long alt_pll_perfcl_freqs[] = {
+ 307200000,
+ 556800000,
+};
+
+static inline struct clk_cpu_8996 *to_clk_cpu_8996(struct clk_hw *hw)
+{
+ return container_of(to_clk_regmap(hw), struct clk_cpu_8996, clkr);
+}
+
+static int clk_cpu_8996_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long prate)
+{
+ struct clk_cpu_8996 *cpuclk = to_clk_cpu_8996(hw);
+ unsigned long n_alt_pll_freqs = cpuclk->n_alt_pll_freqs;
+ unsigned long alt_pll_rate, alt_pll_prev_rate;
+ struct clk *alt_pll, *pll, *parent, *orig_pll = NULL;
+ struct smux;
+ int ret;
+
+ if (rate < cpuclk->post_div_thresh)
+ pll = clk_hw_get_clk(cpuclk->pll_post_div);
+ else
+ pll = clk_hw_get_clk(cpuclk->pll);
+
+ parent = clk_hw_get_clk(clk_hw_get_parent(hw));
+ alt_pll = clk_hw_get_clk(cpuclk->alt_pll);
+
+ /* Check if the alt pll freq should be changed */
+ if (cpuclk->alt_pll_thresh && (n_alt_pll_freqs == 2)) {
+ alt_pll_prev_rate = clk_get_rate(alt_pll);
+ alt_pll_rate = cpuclk->alt_pll_freqs[0];
+ if (rate > cpuclk->alt_pll_thresh)
+ alt_pll_rate = cpuclk->alt_pll_freqs[1];
+ ret = clk_set_rate(alt_pll, alt_pll_rate);
+ if (ret)
+ return ret;
+ }
+
+ /* Switch parent to alt pll */
+ if (cpuclk->alt_pll) {
+ orig_pll = clk_get_parent(parent);
+ ret = clk_set_parent(parent, alt_pll);
+ if (ret)
+ return ret;
+ }
+
+ /* Set the PLL to new rate */
+ ret = clk_set_rate(pll, rate);
+ if (ret)
+ goto error;
+
+ /* Switch back to primary pll */
+ if (cpuclk->alt_pll) {
+ ret = clk_set_parent(parent, pll);
+ if (ret)
+ goto error;
+ }
+ return 0;
+
+error:
+ if (cpuclk->alt_pll)
+ clk_set_parent(parent, orig_pll);
+
+ return ret;
+}
+
+static unsigned long clk_cpu_8996_recalc_rate(struct clk_hw *hw,
+ unsigned long prate)
+{
+ return clk_hw_get_rate(clk_hw_get_parent(hw));
+}
+
+static long clk_cpu_8996_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ return clk_hw_round_rate(clk_hw_get_parent(hw), rate);
+}
+
+static struct clk_ops clk_cpu_8996_ops = {
+ .set_rate = clk_cpu_8996_set_rate,
+ .recalc_rate = clk_cpu_8996_recalc_rate,
+ .round_rate = clk_cpu_8996_round_rate,
+};
+
+static struct clk_cpu_8996 pwrcl_clk = {
+ .alt_pll = &pwrcl_alt_pll.clkr.hw,
+ .pll = &pwrcl_pll.clkr.hw,
+ .pll_post_div = &pwrcl_smux.clkr.hw,
+ .post_div_thresh = 600000000,
+ .clkr.hw.init = &(struct clk_init_data) {
+ .name = "pwrcl_clk",
+ .parent_names = (const char *[]){ "pwrcl_pmux" },
+ .num_parents = 1,
+ .ops = &clk_cpu_8996_ops,
+ },
+};
+
+static struct clk_cpu_8996 perfcl_clk = {
+ .alt_pll = &perfcl_alt_pll.clkr.hw,
+ .alt_pll_freqs = alt_pll_perfcl_freqs,
+ .alt_pll_thresh = 1190400000,
+ .n_alt_pll_freqs = ARRAY_SIZE(alt_pll_perfcl_freqs),
+ .pll = &perfcl_pll.clkr.hw,
+ .pll_post_div = &perfcl_smux.clkr.hw,
+ .post_div_thresh = 600000000,
+ .clkr.hw.init = &(struct clk_init_data) {
+ .name = "perfcl_clk",
+ .parent_names = (const char *[]){ "perfcl_pmux" },
+ .num_parents = 1,
+ .ops = &clk_cpu_8996_ops,
+ },
+};
+
+static struct clk_cpu_8996 cbfcl_clk = {
+ .pll = &cbf_pll.clkr.hw,
+ .post_div_thresh = 600000000,
+ .clkr.hw.init = &(struct clk_init_data) {
+ .name = "cbf_clk",
+ .parent_names = (const char *[]){ "cbf_pmux" },
+ .num_parents = 1,
+ .ops = &clk_cpu_8996_ops,
+ },
+};
+
+static const struct regmap_config cpu_msm8996_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x80210,
+ .fast_io = true,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+};
+
+static const struct of_device_id match_table[] = {
+ { .compatible = "qcom,cpu-clk-msm8996" },
+ {}
+};
+
+#define cluster_clk_register(dev, clk, clkr) { \
+ clk = devm_clk_register_regmap(dev, clkr); \
+ if (IS_ERR(clk)) \
+ return PTR_ERR(clk); }
+
+#define cbf_clk_register(dev, clk, hw) { \
+ clk = devm_clk_register(dev, hw); \
+ if (IS_ERR(clk)) \
+ return PTR_ERR(clk); }
+
+#define cpu_clk_register_fixed(dev, clk, name, pname, flags, m, n) { \
+ clk = clk_register_fixed_factor(dev, name, pname, flags, m, n); \
+ if (IS_ERR(clk)) \
+ return PTR_ERR(clk); }
+
+#define cpu_set_rate(dev, clk, rate) { \
+ if (clk_set_rate(clk, rate)) \
+ dev_err(dev, "Failed to set " #clk " to " #rate "\n"); }
+
+#define cpu_prepare_enable(dev, clk) { \
+ if (clk_prepare_enable(clk)) \
+ dev_err(dev, "Failed to enable " #clk "\n"); }
+
+#define cpu_set_parent(dev, clk, parent) { \
+ if (clk_set_parent(clk, parent)) \
+ dev_err(dev, "Failed to set parent for " #clk "\n"); }
+
+struct clk *sys_apcsaux, *pwr_clk, *perf_clk, *cbf_clk;
+
+static int register_cpu_clocks(struct device *dev, struct regmap *regmap)
+{
+ /* clocks */
+ struct clk *perf_alt_pll, *pwr_alt_pll, *perf_pll, *pwr_pll;
+ struct clk *perf_pmux, *perf_smux, *pwr_pmux, *pwr_smux;
+ struct clk *perf_pll_main, *pwr_pll_main;
+
+ /* Initialise the PLLs */
+ clk_pll_configure_variable_rate(&perfcl_pll, regmap, &hfpll_config);
+ clk_pll_configure_variable_rate(&pwrcl_pll, regmap, &hfpll_config);
+ clk_alpha_pll_configure(&perfcl_alt_pll, regmap, &altpll_config);
+ clk_alpha_pll_configure(&pwrcl_alt_pll, regmap, &altpll_config);
+
+ /* PLLs */
+ cluster_clk_register(dev, perf_pll, &perfcl_pll.clkr);
+ cluster_clk_register(dev, pwr_pll, &pwrcl_pll.clkr);
+ cluster_clk_register(dev, perf_alt_pll, &perfcl_alt_pll.clkr);
+ cluster_clk_register(dev, pwr_alt_pll, &pwrcl_alt_pll.clkr);
+
+ /* MUXs */
+ cluster_clk_register(dev, perf_pmux, &perfcl_pmux.clkr);
+ cluster_clk_register(dev, perf_smux, &perfcl_smux.clkr);
+ cluster_clk_register(dev, pwr_pmux, &pwrcl_pmux.clkr);
+ cluster_clk_register(dev, pwr_smux, &pwrcl_smux.clkr);
+
+ /* Fixed factor CLKs */
+ cpu_clk_register_fixed(dev, perf_pll_main, "perfcl_pll_main",
+ "perfcl_pll", CLK_SET_RATE_PARENT, 1, 2);
+ cpu_clk_register_fixed(dev, pwr_pll_main, "pwrcl_pll_main",
+ "pwrcl_pll", CLK_SET_RATE_PARENT, 1, 2);
+
+ /* Init alt pll to boot frequency */
+ cpu_set_rate(dev, perf_alt_pll, 307200000);
+ cpu_set_rate(dev, pwr_alt_pll, 307200000);
+
+ /* Enable all PLLs and alt PLLs */
+ cpu_prepare_enable(dev, perf_pll);
+ cpu_prepare_enable(dev, pwr_pll);
+ cpu_prepare_enable(dev, perf_alt_pll);
+ cpu_prepare_enable(dev, pwr_alt_pll);
+
+ /* Init MUXes with default parents */
+ cpu_set_parent(dev, perf_pmux, perf_pll);
+ cpu_set_parent(dev, pwr_pmux, pwr_pll);
+ cpu_set_parent(dev, perf_smux, perf_pll_main);
+ cpu_set_parent(dev, pwr_smux, pwr_pll_main);
+
+ /* Register CPU clocks */
+ cluster_clk_register(dev, perf_clk, &perfcl_clk.clkr);
+ cluster_clk_register(dev, pwr_clk, &pwrcl_clk.clkr);
+
+ return 0;
+}
+
+static int register_cbf_clocks(struct device *dev, struct regmap *regmap)
+{
+ struct clk *cbf_pll_clk, *cbf_pmux_clk, *cbf_pll_main_clk;
+
+ cbf_pll.clkr.regmap = regmap;
+ cbf_pmux.clkr.regmap = regmap;
+ cbfcl_clk.clkr.regmap = regmap;
+
+ clk_pll_configure_variable_rate(&cbf_pll, regmap, &cbfpll_config);
+
+ cbf_clk_register(dev, cbf_pll_clk, &cbf_pll.clkr.hw);
+ cbf_clk_register(dev, cbf_pmux_clk, &cbf_pmux.clkr.hw);
+
+ cpu_clk_register_fixed(dev, cbf_pll_main_clk, "cbf_pll_main", "cbf_pll",
+ CLK_SET_RATE_PARENT, 1, 2);
+
+ cpu_prepare_enable(dev, cbf_pll_clk);
+ cpu_set_parent(dev, cbf_pmux_clk, cbf_pll_clk);
+
+ cbfcl_clk.alt_pll = __clk_get_hw(sys_apcsaux);
+ cbfcl_clk.pll_post_div = __clk_get_hw(cbf_pll_main_clk);
+
+ cbf_clk_register(dev, cbf_clk, &cbfcl_clk.clkr.hw);
+
+ return 0;
+}
+
+static int qcom_cpu_clk_msm8996_driver_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ void __iomem *base;
+ struct regmap *regmap_cpu, *regmap_cbf;
+ struct clk_onecell_data *data;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->clks = devm_kcalloc(dev, 3, sizeof(struct clk *), GFP_KERNEL);
+ if (!data->clks)
+ return -ENOMEM;
+
+ cpu_clk_register_fixed(dev, sys_apcsaux, "sys_apcsaux_clk",
+ "gpll0_early", 0, 1, 1);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ regmap_cpu = devm_regmap_init_mmio(dev, base,
+ &cpu_msm8996_regmap_config);
+ if (IS_ERR(regmap_cpu))
+ return PTR_ERR(regmap_cpu);
+
+ ret = register_cpu_clocks(dev, regmap_cpu);
+ if (ret)
+ return ret;
+
+ regmap_cbf = syscon_regmap_lookup_by_phandle(dev->of_node, "qcom,cbf");
+ if (IS_ERR(regmap_cbf))
+ return PTR_ERR(regmap_cbf);
+
+ ret = register_cbf_clocks(dev, regmap_cbf);
+ if (ret)
+ return ret;
+
+ data->clks[0] = pwr_clk;
+ data->clks[1] = perf_clk;
+ data->clks[2] = cbf_clk;
+
+ return of_clk_add_provider(dev->of_node, of_clk_src_onecell_get, data);
+}
+
+static struct platform_driver qcom_cpu_clk_msm8996_driver = {
+ .probe = qcom_cpu_clk_msm8996_driver_probe,
+ .driver = {
+ .name = "qcom-cpu-clk-msm8996",
+ .of_match_table = match_table,
+ .owner = THIS_MODULE,
+ },
+};
+
+builtin_platform_driver(qcom_cpu_clk_msm8996_driver);
+
+MODULE_DESCRIPTION("CPU clock driver for msm8996");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/clk/qcom/clk-pll.c b/drivers/clk/qcom/clk-pll.c
index 5b940d629045f..4026b3c09e35a 100644
--- a/drivers/clk/qcom/clk-pll.c
+++ b/drivers/clk/qcom/clk-pll.c
@@ -255,8 +255,13 @@ static void clk_pll_configure(struct clk_pll *pll, struct regmap *regmap,
u32 mask;
regmap_write(regmap, pll->l_reg, config->l);
- regmap_write(regmap, pll->m_reg, config->m);
- regmap_write(regmap, pll->n_reg, config->n);
+
+ if (pll->alpha_reg) {
+ regmap_write(regmap, pll->alpha_reg, config->alpha);
+ } else {
+ regmap_write(regmap, pll->m_reg, config->m);
+ regmap_write(regmap, pll->n_reg, config->n);
+ }
val = config->vco_val;
val |= config->pre_div_val;
@@ -264,6 +269,7 @@ static void clk_pll_configure(struct clk_pll *pll, struct regmap *regmap,
val |= config->mn_ena_mask;
val |= config->main_output_mask;
val |= config->aux_output_mask;
+ val |= config->early_output_mask;
mask = config->vco_mask;
mask |= config->pre_div_mask;
@@ -271,9 +277,24 @@ static void clk_pll_configure(struct clk_pll *pll, struct regmap *regmap,
mask |= config->mn_ena_mask;
mask |= config->main_output_mask;
mask |= config->aux_output_mask;
+ mask |= config->early_output_mask;
regmap_update_bits(regmap, pll->config_reg, mask, val);
+
+}
+
+void clk_pll_configure_variable_rate(struct clk_pll *pll, struct regmap *regmap,
+ const struct pll_config *config)
+{
+ clk_pll_configure(pll, regmap, config);
+ regmap_write(regmap, pll->config_ctl_reg, config->config_ctl_val);
+ regmap_write(regmap, pll->config_ctl_reg + 4,
+ config->config_ctl_hi_val);
+
+ /* Enable FSM mode with bias count as 0x6 */
+ regmap_write(regmap, pll->mode_reg, 0x00118000);
}
+EXPORT_SYMBOL_GPL(clk_pll_configure_variable_rate);
void clk_pll_configure_sr(struct clk_pll *pll, struct regmap *regmap,
const struct pll_config *config, bool fsm_mode)
@@ -367,3 +388,74 @@ const struct clk_ops clk_pll_sr2_ops = {
.determine_rate = clk_pll_determine_rate,
};
EXPORT_SYMBOL_GPL(clk_pll_sr2_ops);
+
+static int clk_pll_hwfsm_enable(struct clk_hw *hw)
+{
+ struct clk_pll *pll = to_clk_pll(hw);
+
+ /* Wait for 50us explicitly to avoid transient locks */
+ udelay(50);
+ return wait_for_pll(pll);
+};
+
+static void clk_pll_hwfsm_disable(struct clk_hw *hw)
+{
+ /* 8 reference clock cycle delay mandated by the HPG */
+ udelay(1);
+};
+
+static unsigned long
+clk_pll_hwfsm_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+ u32 l_val;
+ int ret;
+
+ struct clk_pll *pll = to_clk_pll(hw);
+
+ ret = regmap_read(pll->clkr.regmap, pll->l_reg, &l_val);
+ if (ret)
+ return ret;
+
+ return l_val * parent_rate;
+};
+
+static int
+clk_pll_hwfsm_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
+{
+ struct clk_pll *pll = to_clk_pll(hw);
+ const struct pll_freq_tbl *f;
+
+ f = find_freq(pll->freq_tbl, req->rate);
+ if (!f)
+ req->rate = DIV_ROUND_UP(req->rate, req->best_parent_rate)
+ * req->best_parent_rate;
+ else
+ req->rate = f->freq;
+
+ return 0;
+}
+
+static int
+clk_pll_hwfsm_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long prate)
+{
+ u32 l_val;
+ struct clk_pll *pll = to_clk_pll(hw);
+
+ if ((rate < pll->min_rate) || ( rate > pll->max_rate) || !prate)
+ return -EINVAL;
+
+ l_val = rate / prate;
+ regmap_write(pll->clkr.regmap, pll->l_reg, l_val);
+
+ return 0;
+}
+
+const struct clk_ops clk_pll_hwfsm_ops = {
+ .enable = clk_pll_hwfsm_enable,
+ .disable = clk_pll_hwfsm_disable,
+ .set_rate = clk_pll_hwfsm_set_rate,
+ .recalc_rate = clk_pll_hwfsm_recalc_rate,
+ .determine_rate = clk_pll_hwfsm_determine_rate,
+};
+EXPORT_SYMBOL_GPL(clk_pll_hwfsm_ops);
diff --git a/drivers/clk/qcom/clk-pll.h b/drivers/clk/qcom/clk-pll.h
index ffd0c63bddbc4..333d576bfe817 100644
--- a/drivers/clk/qcom/clk-pll.h
+++ b/drivers/clk/qcom/clk-pll.h
@@ -48,12 +48,16 @@ struct clk_pll {
u32 l_reg;
u32 m_reg;
u32 n_reg;
+ u32 alpha_reg;
u32 config_reg;
u32 mode_reg;
u32 status_reg;
+ u32 config_ctl_reg;
u8 status_bit;
u8 post_div_width;
u8 post_div_shift;
+ unsigned long min_rate;
+ unsigned long max_rate;
const struct pll_freq_tbl *freq_tbl;
@@ -63,6 +67,7 @@ struct clk_pll {
extern const struct clk_ops clk_pll_ops;
extern const struct clk_ops clk_pll_vote_ops;
extern const struct clk_ops clk_pll_sr2_ops;
+extern const struct clk_ops clk_pll_hwfsm_ops;
#define to_clk_pll(_hw) container_of(to_clk_regmap(_hw), struct clk_pll, clkr)
@@ -70,6 +75,7 @@ struct pll_config {
u16 l;
u32 m;
u32 n;
+ u32 alpha;
u32 vco_val;
u32 vco_mask;
u32 pre_div_val;
@@ -79,11 +85,16 @@ struct pll_config {
u32 mn_ena_mask;
u32 main_output_mask;
u32 aux_output_mask;
+ u32 aux2_output_mask;
+ u32 early_output_mask;
+ u32 config_ctl_val;
+ u32 config_ctl_hi_val;
};
void clk_pll_configure_sr(struct clk_pll *pll, struct regmap *regmap,
const struct pll_config *config, bool fsm_mode);
void clk_pll_configure_sr_hpm_lp(struct clk_pll *pll, struct regmap *regmap,
const struct pll_config *config, bool fsm_mode);
-
+void clk_pll_configure_variable_rate(struct clk_pll *pll, struct regmap *regmap,
+ const struct pll_config *config);
#endif
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index 69f57250fe109..979840cef103e 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -733,11 +733,11 @@ void devm_clk_hw_unregister(struct device *dev, struct clk_hw *hw);
/* helper functions */
const char *__clk_get_name(const struct clk *clk);
const char *clk_hw_get_name(const struct clk_hw *hw);
+struct clk *clk_hw_get_clk(const struct clk_hw *hw);
struct clk_hw *__clk_get_hw(struct clk *clk);
unsigned int clk_hw_get_num_parents(const struct clk_hw *hw);
struct clk_hw *clk_hw_get_parent(const struct clk_hw *hw);
-struct clk_hw *clk_hw_get_parent_by_index(const struct clk_hw *hw,
- unsigned int index);
+struct clk_hw *clk_hw_get_parent_by_index(const struct clk_hw *hw, u8 index);
unsigned int __clk_get_enable_count(struct clk *clk);
unsigned long clk_hw_get_rate(const struct clk_hw *hw);
unsigned long __clk_get_flags(struct clk *clk);
@@ -764,7 +764,7 @@ static inline void __clk_hw_set_clk(struct clk_hw *dst, struct clk_hw *src)
/*
* FIXME clock api without lock protection
*/
-unsigned long clk_hw_round_rate(struct clk_hw *hw, unsigned long rate);
+long clk_hw_round_rate(struct clk_hw *hw, unsigned long rate);
struct of_device_id;