diff options
author | Srinivas Kandagatla <srinivas.kandagatla@linaro.org> | 2016-07-20 11:54:48 +0100 |
---|---|---|
committer | Srinivas Kandagatla <srinivas.kandagatla@linaro.org> | 2016-07-20 11:54:48 +0100 |
commit | 6707e66167e610ab7eabfb9e0889bea16683cd05 (patch) | |
tree | 32db3ba6ac37185f52f25fda7c0932d4aed903ac | |
parent | fb5b86830431ae4b99075048e536c06fea738192 (diff) | |
parent | 8b2e35a1e9448a58c0b2a4d8453bc498446f5d20 (diff) |
Merge branch 'tracking-qcomlt-cpu-clk-8996' into integration-linux-qcomlt
-rw-r--r-- | drivers/clk/clk.c | 10 | ||||
-rw-r--r-- | drivers/clk/qcom/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/qcom/clk-alpha-pll.c | 133 | ||||
-rw-r--r-- | drivers/clk/qcom/clk-alpha-pll.h | 11 | ||||
-rw-r--r-- | drivers/clk/qcom/clk-cpu-8996.c | 566 | ||||
-rw-r--r-- | drivers/clk/qcom/clk-pll.c | 96 | ||||
-rw-r--r-- | drivers/clk/qcom/clk-pll.h | 13 | ||||
-rw-r--r-- | include/linux/clk-provider.h | 6 |
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; |