linux/drivers/clk/qcom/clk-cpu-8996.c

539 lines
14 KiB
C
Raw Normal View History

clk: qcom: Add CPU clock driver for msm8996 Each of the CPU clusters (Power and Perf) on msm8996 are clocked via 2 PLLs, a primary and alternate. There are also 2 Mux'es, a primary and secondary all connected together as shown below +-------+ XO | | +------------------>0 | | | PLL/2 | SMUX +----+ +------->1 | | | | | | | +-------+ | +-------+ | +---->0 | | | | +---------------+ | +----------->1 | CPU clk |Primary PLL +----+ PLL_EARLY | | +------> | +------+-----------+ +------>2 PMUX | +---------------+ | | | | | +------+ | +-->3 | +--^+ ACD +-----+ | +-------+ +---------------+ +------+ | |Alt PLL | | | +---------------------------+ +---------------+ PLL_EARLY The primary PLL is what drives the CPU clk, except for times when we are reprogramming the PLL itself (for rate changes) when we temporarily switch to an alternate PLL. A subsequent patch adds support to switch between primary and alternate PLL during rate changes. The primary PLL operates on a single VCO range, between 600MHz and 3GHz. However the CPUs do support OPPs with frequencies between 300MHz and 600MHz. In order to support running the CPUs at those frequencies we end up having to lock the PLL at twice the rate and drive the CPU clk via the PLL/2 output and SMUX. So for frequencies above 600MHz we follow the following path Primary PLL --> PLL_EARLY --> PMUX(1) --> CPU clk and for frequencies between 300MHz and 600MHz we follow Primary PLL --> PLL/2 --> SMUX(1) --> PMUX(0) --> CPU clk ACD stands for Adaptive Clock Distribution and is used to detect voltage droops. Signed-off-by: Rajendra Nayak <rnayak@codeaurora.org> Rajendra Nayak: Initial RFC - https://lkml.org/lkml/2016/9/29/84 Signed-off-by: Ilia Lin <ilialin@codeaurora.org> Ilia Lin: - reworked clock registering - Added clock-tree diagram - non-builtin support - clock notifier on rate change - https://lkml.org/lkml/2018/5/24/123 Signed-off-by: Loic Poulain <loic.poulain@linaro.org> Loic Poulain: - fixed driver remove / clk deregistering - Removed useless memory barriers - devm usage when possible - Fixed Kconfig depends Link: https://lore.kernel.org/r/1593766185-16346-3-git-send-email-loic.poulain@linaro.org Signed-off-by: Stephen Boyd <sboyd@kernel.org>
2020-07-03 16:49:42 +08:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*/
/*
* Each of the CPU clusters (Power and Perf) on msm8996 are
* clocked via 2 PLLs, a primary and alternate. There are also
* 2 Mux'es, a primary and secondary all connected together
* as shown below
*
* +-------+
* XO | |
* +------------------>0 |
* | |
* PLL/2 | SMUX +----+
* +------->1 | |
* | | | |
* | +-------+ | +-------+
* | +---->0 |
* | | |
* +---------------+ | +----------->1 | CPU clk
* |Primary PLL +----+ PLL_EARLY | | +------>
* | +------+-----------+ +------>2 PMUX |
* +---------------+ | | | |
* | +------+ | +-->3 |
* +--^+ ACD +-----+ | +-------+
* +---------------+ +------+ |
* |Alt PLL | |
* | +---------------------------+
* +---------------+ PLL_EARLY
*
* The primary PLL is what drives the CPU clk, except for times
* when we are reprogramming the PLL itself (for rate changes) when
* we temporarily switch to an alternate PLL.
*
* The primary PLL operates on a single VCO range, between 600MHz
* and 3GHz. However the CPUs do support OPPs with frequencies
* between 300MHz and 600MHz. In order to support running the CPUs
* at those frequencies we end up having to lock the PLL at twice
* the rate and drive the CPU clk via the PLL/2 output and SMUX.
*
* So for frequencies above 600MHz we follow the following path
* Primary PLL --> PLL_EARLY --> PMUX(1) --> CPU clk
* and for frequencies between 300MHz and 600MHz we follow
* Primary PLL --> PLL/2 --> SMUX(1) --> PMUX(0) --> CPU clk
*
* ACD stands for Adaptive Clock Distribution and is used to
* detect voltage droops.
*/
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <soc/qcom/kryo-l2-accessors.h>
#include "clk-alpha-pll.h"
#include "clk-regmap.h"
enum _pmux_input {
DIV_2_INDEX = 0,
PLL_INDEX,
ACD_INDEX,
ALT_INDEX,
NUM_OF_PMUX_INPUTS
};
#define DIV_2_THRESHOLD 600000000
#define PWRCL_REG_OFFSET 0x0
#define PERFCL_REG_OFFSET 0x80000
#define MUX_OFFSET 0x40
#define ALT_PLL_OFFSET 0x100
#define SSSCTL_OFFSET 0x160
static const u8 prim_pll_regs[PLL_OFF_MAX_REGS] = {
[PLL_OFF_L_VAL] = 0x04,
[PLL_OFF_ALPHA_VAL] = 0x08,
[PLL_OFF_USER_CTL] = 0x10,
[PLL_OFF_CONFIG_CTL] = 0x18,
[PLL_OFF_CONFIG_CTL_U] = 0x1c,
[PLL_OFF_TEST_CTL] = 0x20,
[PLL_OFF_TEST_CTL_U] = 0x24,
[PLL_OFF_STATUS] = 0x28,
};
static const u8 alt_pll_regs[PLL_OFF_MAX_REGS] = {
[PLL_OFF_L_VAL] = 0x04,
[PLL_OFF_ALPHA_VAL] = 0x08,
[PLL_OFF_ALPHA_VAL_U] = 0x0c,
[PLL_OFF_USER_CTL] = 0x10,
[PLL_OFF_USER_CTL_U] = 0x14,
[PLL_OFF_CONFIG_CTL] = 0x18,
[PLL_OFF_TEST_CTL] = 0x20,
[PLL_OFF_TEST_CTL_U] = 0x24,
[PLL_OFF_STATUS] = 0x28,
};
/* PLLs */
static const struct alpha_pll_config hfpll_config = {
.l = 60,
.config_ctl_val = 0x200d4aa8,
.config_ctl_hi_val = 0x006,
.pre_div_mask = BIT(12),
.post_div_mask = 0x3 << 8,
.post_div_val = 0x1 << 8,
.main_output_mask = BIT(0),
.early_output_mask = BIT(3),
};
static struct clk_alpha_pll perfcl_pll = {
.offset = PERFCL_REG_OFFSET,
.regs = prim_pll_regs,
.flags = SUPPORTS_DYNAMIC_UPDATE | SUPPORTS_FSM_MODE,
.clkr.hw.init = &(struct clk_init_data){
.name = "perfcl_pll",
.parent_names = (const char *[]){ "xo" },
.num_parents = 1,
.ops = &clk_alpha_pll_huayra_ops,
},
};
static struct clk_alpha_pll pwrcl_pll = {
.offset = PWRCL_REG_OFFSET,
.regs = prim_pll_regs,
.flags = SUPPORTS_DYNAMIC_UPDATE | SUPPORTS_FSM_MODE,
.clkr.hw.init = &(struct clk_init_data){
.name = "pwrcl_pll",
.parent_names = (const char *[]){ "xo" },
.num_parents = 1,
.ops = &clk_alpha_pll_huayra_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 alpha_pll_config altpll_config = {
.l = 16,
.vco_val = 0x3 << 20,
.vco_mask = 0x3 << 20,
.config_ctl_val = 0x4001051b,
.post_div_mask = 0x3 << 8,
.post_div_val = 0x1 << 8,
.main_output_mask = BIT(0),
.early_output_mask = BIT(3),
};
static struct clk_alpha_pll perfcl_alt_pll = {
.offset = PERFCL_REG_OFFSET + ALT_PLL_OFFSET,
.regs = alt_pll_regs,
.vco_table = alt_pll_vco_modes,
.num_vco = ARRAY_SIZE(alt_pll_vco_modes),
.flags = SUPPORTS_OFFLINE_REQ | SUPPORTS_FSM_MODE,
.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 = PWRCL_REG_OFFSET + ALT_PLL_OFFSET,
.regs = alt_pll_regs,
.vco_table = alt_pll_vco_modes,
.num_vco = ARRAY_SIZE(alt_pll_vco_modes),
.flags = SUPPORTS_OFFLINE_REQ | SUPPORTS_FSM_MODE,
.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,
},
};
struct clk_cpu_8996_mux {
u32 reg;
u8 shift;
u8 width;
struct notifier_block nb;
struct clk_hw *pll;
struct clk_hw *pll_div_2;
struct clk_regmap clkr;
};
static int cpu_clk_notifier_cb(struct notifier_block *nb, unsigned long event,
void *data);
#define to_clk_cpu_8996_mux_nb(_nb) \
container_of(_nb, struct clk_cpu_8996_mux, nb)
static inline struct clk_cpu_8996_mux *to_clk_cpu_8996_mux_hw(struct clk_hw *hw)
{
return container_of(to_clk_regmap(hw), struct clk_cpu_8996_mux, clkr);
}
static u8 clk_cpu_8996_mux_get_parent(struct clk_hw *hw)
{
struct clk_regmap *clkr = to_clk_regmap(hw);
struct clk_cpu_8996_mux *cpuclk = to_clk_cpu_8996_mux_hw(hw);
u32 mask = GENMASK(cpuclk->width - 1, 0);
u32 val;
regmap_read(clkr->regmap, cpuclk->reg, &val);
val >>= cpuclk->shift;
return val & mask;
}
static int clk_cpu_8996_mux_set_parent(struct clk_hw *hw, u8 index)
{
struct clk_regmap *clkr = to_clk_regmap(hw);
struct clk_cpu_8996_mux *cpuclk = to_clk_cpu_8996_mux_hw(hw);
u32 mask = GENMASK(cpuclk->width + cpuclk->shift - 1, cpuclk->shift);
u32 val;
val = index;
val <<= cpuclk->shift;
return regmap_update_bits(clkr->regmap, cpuclk->reg, mask, val);
}
static int clk_cpu_8996_mux_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct clk_cpu_8996_mux *cpuclk = to_clk_cpu_8996_mux_hw(hw);
struct clk_hw *parent = cpuclk->pll;
if (cpuclk->pll_div_2 && req->rate < DIV_2_THRESHOLD) {
if (req->rate < (DIV_2_THRESHOLD / 2))
return -EINVAL;
parent = cpuclk->pll_div_2;
}
req->best_parent_rate = clk_hw_round_rate(parent, req->rate);
req->best_parent_hw = parent;
return 0;
}
static const struct clk_ops clk_cpu_8996_mux_ops = {
.set_parent = clk_cpu_8996_mux_set_parent,
.get_parent = clk_cpu_8996_mux_get_parent,
.determine_rate = clk_cpu_8996_mux_determine_rate,
};
static struct clk_cpu_8996_mux pwrcl_smux = {
.reg = PWRCL_REG_OFFSET + MUX_OFFSET,
.shift = 2,
.width = 2,
.clkr.hw.init = &(struct clk_init_data) {
.name = "pwrcl_smux",
.parent_names = (const char *[]){
"xo",
"pwrcl_pll_main",
},
.num_parents = 2,
.ops = &clk_cpu_8996_mux_ops,
.flags = CLK_SET_RATE_PARENT,
},
};
static struct clk_cpu_8996_mux perfcl_smux = {
.reg = PERFCL_REG_OFFSET + MUX_OFFSET,
.shift = 2,
.width = 2,
.clkr.hw.init = &(struct clk_init_data) {
.name = "perfcl_smux",
.parent_names = (const char *[]){
"xo",
"perfcl_pll_main",
},
.num_parents = 2,
.ops = &clk_cpu_8996_mux_ops,
.flags = CLK_SET_RATE_PARENT,
},
};
static struct clk_cpu_8996_mux pwrcl_pmux = {
.reg = PWRCL_REG_OFFSET + MUX_OFFSET,
.shift = 0,
.width = 2,
.pll = &pwrcl_pll.clkr.hw,
.pll_div_2 = &pwrcl_smux.clkr.hw,
.nb.notifier_call = cpu_clk_notifier_cb,
.clkr.hw.init = &(struct clk_init_data) {
.name = "pwrcl_pmux",
.parent_names = (const char *[]){
"pwrcl_smux",
"pwrcl_pll",
"pwrcl_pll_acd",
"pwrcl_alt_pll",
},
.num_parents = 4,
.ops = &clk_cpu_8996_mux_ops,
/* CPU clock is critical and should never be gated */
.flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
},
};
static struct clk_cpu_8996_mux perfcl_pmux = {
.reg = PERFCL_REG_OFFSET + MUX_OFFSET,
.shift = 0,
.width = 2,
.pll = &perfcl_pll.clkr.hw,
.pll_div_2 = &perfcl_smux.clkr.hw,
.nb.notifier_call = cpu_clk_notifier_cb,
.clkr.hw.init = &(struct clk_init_data) {
.name = "perfcl_pmux",
.parent_names = (const char *[]){
"perfcl_smux",
"perfcl_pll",
"perfcl_pll_acd",
"perfcl_alt_pll",
},
.num_parents = 4,
.ops = &clk_cpu_8996_mux_ops,
/* CPU clock is critical and should never be gated */
.flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
},
};
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 struct clk_regmap *cpu_msm8996_clks[] = {
clk: qcom: Add CPU clock driver for msm8996 Each of the CPU clusters (Power and Perf) on msm8996 are clocked via 2 PLLs, a primary and alternate. There are also 2 Mux'es, a primary and secondary all connected together as shown below +-------+ XO | | +------------------>0 | | | PLL/2 | SMUX +----+ +------->1 | | | | | | | +-------+ | +-------+ | +---->0 | | | | +---------------+ | +----------->1 | CPU clk |Primary PLL +----+ PLL_EARLY | | +------> | +------+-----------+ +------>2 PMUX | +---------------+ | | | | | +------+ | +-->3 | +--^+ ACD +-----+ | +-------+ +---------------+ +------+ | |Alt PLL | | | +---------------------------+ +---------------+ PLL_EARLY The primary PLL is what drives the CPU clk, except for times when we are reprogramming the PLL itself (for rate changes) when we temporarily switch to an alternate PLL. A subsequent patch adds support to switch between primary and alternate PLL during rate changes. The primary PLL operates on a single VCO range, between 600MHz and 3GHz. However the CPUs do support OPPs with frequencies between 300MHz and 600MHz. In order to support running the CPUs at those frequencies we end up having to lock the PLL at twice the rate and drive the CPU clk via the PLL/2 output and SMUX. So for frequencies above 600MHz we follow the following path Primary PLL --> PLL_EARLY --> PMUX(1) --> CPU clk and for frequencies between 300MHz and 600MHz we follow Primary PLL --> PLL/2 --> SMUX(1) --> PMUX(0) --> CPU clk ACD stands for Adaptive Clock Distribution and is used to detect voltage droops. Signed-off-by: Rajendra Nayak <rnayak@codeaurora.org> Rajendra Nayak: Initial RFC - https://lkml.org/lkml/2016/9/29/84 Signed-off-by: Ilia Lin <ilialin@codeaurora.org> Ilia Lin: - reworked clock registering - Added clock-tree diagram - non-builtin support - clock notifier on rate change - https://lkml.org/lkml/2018/5/24/123 Signed-off-by: Loic Poulain <loic.poulain@linaro.org> Loic Poulain: - fixed driver remove / clk deregistering - Removed useless memory barriers - devm usage when possible - Fixed Kconfig depends Link: https://lore.kernel.org/r/1593766185-16346-3-git-send-email-loic.poulain@linaro.org Signed-off-by: Stephen Boyd <sboyd@kernel.org>
2020-07-03 16:49:42 +08:00
&perfcl_pll.clkr,
&pwrcl_pll.clkr,
&perfcl_alt_pll.clkr,
&pwrcl_alt_pll.clkr,
&perfcl_smux.clkr,
&pwrcl_smux.clkr,
&perfcl_pmux.clkr,
&pwrcl_pmux.clkr,
};
static int qcom_cpu_clk_msm8996_register_clks(struct device *dev,
struct regmap *regmap)
{
int i, ret;
perfcl_smux.pll = clk_hw_register_fixed_factor(dev, "perfcl_pll_main",
"perfcl_pll",
CLK_SET_RATE_PARENT,
1, 2);
if (IS_ERR(perfcl_smux.pll)) {
dev_err(dev, "Failed to initialize perfcl_pll_main\n");
return PTR_ERR(perfcl_smux.pll);
}
pwrcl_smux.pll = clk_hw_register_fixed_factor(dev, "pwrcl_pll_main",
"pwrcl_pll",
CLK_SET_RATE_PARENT,
1, 2);
if (IS_ERR(pwrcl_smux.pll)) {
dev_err(dev, "Failed to initialize pwrcl_pll_main\n");
clk_hw_unregister(perfcl_smux.pll);
return PTR_ERR(pwrcl_smux.pll);
}
for (i = 0; i < ARRAY_SIZE(cpu_msm8996_clks); i++) {
ret = devm_clk_register_regmap(dev, cpu_msm8996_clks[i]);
if (ret) {
clk_hw_unregister(perfcl_smux.pll);
clk_hw_unregister(pwrcl_smux.pll);
return ret;
}
}
clk_alpha_pll_configure(&perfcl_pll, regmap, &hfpll_config);
clk_alpha_pll_configure(&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);
/* Enable alt PLLs */
clk_prepare_enable(pwrcl_alt_pll.clkr.hw.clk);
clk_prepare_enable(perfcl_alt_pll.clkr.hw.clk);
clk_notifier_register(pwrcl_pmux.clkr.hw.clk, &pwrcl_pmux.nb);
clk_notifier_register(perfcl_pmux.clkr.hw.clk, &perfcl_pmux.nb);
return ret;
}
static int qcom_cpu_clk_msm8996_unregister_clks(void)
{
int ret = 0;
ret = clk_notifier_unregister(pwrcl_pmux.clkr.hw.clk, &pwrcl_pmux.nb);
if (ret)
return ret;
ret = clk_notifier_unregister(perfcl_pmux.clkr.hw.clk, &perfcl_pmux.nb);
if (ret)
return ret;
clk_hw_unregister(perfcl_smux.pll);
clk_hw_unregister(pwrcl_smux.pll);
return 0;
}
#define CPU_AFINITY_MASK 0xFFF
#define PWRCL_CPU_REG_MASK 0x3
#define PERFCL_CPU_REG_MASK 0x103
#define L2ACDCR_REG 0x580ULL
#define L2ACDTD_REG 0x581ULL
#define L2ACDDVMRC_REG 0x584ULL
#define L2ACDSSCR_REG 0x589ULL
static DEFINE_SPINLOCK(qcom_clk_acd_lock);
static void __iomem *base;
static void qcom_cpu_clk_msm8996_acd_init(void __iomem *base)
{
u64 hwid;
unsigned long flags;
spin_lock_irqsave(&qcom_clk_acd_lock, flags);
hwid = read_cpuid_mpidr() & CPU_AFINITY_MASK;
kryo_l2_set_indirect_reg(L2ACDTD_REG, 0x00006a11);
kryo_l2_set_indirect_reg(L2ACDDVMRC_REG, 0x000e0f0f);
kryo_l2_set_indirect_reg(L2ACDSSCR_REG, 0x00000601);
if (PWRCL_CPU_REG_MASK == (hwid | PWRCL_CPU_REG_MASK)) {
writel(0xf, base + PWRCL_REG_OFFSET + SSSCTL_OFFSET);
kryo_l2_set_indirect_reg(L2ACDCR_REG, 0x002c5ffd);
}
if (PERFCL_CPU_REG_MASK == (hwid | PERFCL_CPU_REG_MASK)) {
kryo_l2_set_indirect_reg(L2ACDCR_REG, 0x002c5ffd);
writel(0xf, base + PERFCL_REG_OFFSET + SSSCTL_OFFSET);
}
spin_unlock_irqrestore(&qcom_clk_acd_lock, flags);
}
static int cpu_clk_notifier_cb(struct notifier_block *nb, unsigned long event,
void *data)
{
struct clk_cpu_8996_mux *cpuclk = to_clk_cpu_8996_mux_nb(nb);
struct clk_notifier_data *cnd = data;
int ret;
switch (event) {
case PRE_RATE_CHANGE:
ret = clk_cpu_8996_mux_set_parent(&cpuclk->clkr.hw, ALT_INDEX);
qcom_cpu_clk_msm8996_acd_init(base);
break;
case POST_RATE_CHANGE:
if (cnd->new_rate < DIV_2_THRESHOLD)
ret = clk_cpu_8996_mux_set_parent(&cpuclk->clkr.hw,
DIV_2_INDEX);
else
ret = clk_cpu_8996_mux_set_parent(&cpuclk->clkr.hw,
ACD_INDEX);
break;
default:
ret = 0;
break;
}
return notifier_from_errno(ret);
};
static int qcom_cpu_clk_msm8996_driver_probe(struct platform_device *pdev)
{
struct regmap *regmap;
struct clk_hw_onecell_data *data;
struct device *dev = &pdev->dev;
int ret;
data = devm_kzalloc(dev, struct_size(data, hws, 2), GFP_KERNEL);
if (!data)
return -ENOMEM;
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return PTR_ERR(base);
regmap = devm_regmap_init_mmio(dev, base, &cpu_msm8996_regmap_config);
if (IS_ERR(regmap))
return PTR_ERR(regmap);
ret = qcom_cpu_clk_msm8996_register_clks(dev, regmap);
if (ret)
return ret;
qcom_cpu_clk_msm8996_acd_init(base);
data->hws[0] = &pwrcl_pmux.clkr.hw;
data->hws[1] = &perfcl_pmux.clkr.hw;
data->num = 2;
return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, data);
}
static int qcom_cpu_clk_msm8996_driver_remove(struct platform_device *pdev)
{
return qcom_cpu_clk_msm8996_unregister_clks();
}
static const struct of_device_id qcom_cpu_clk_msm8996_match_table[] = {
{ .compatible = "qcom,msm8996-apcc" },
{}
};
MODULE_DEVICE_TABLE(of, qcom_cpu_clk_msm8996_match_table);
static struct platform_driver qcom_cpu_clk_msm8996_driver = {
.probe = qcom_cpu_clk_msm8996_driver_probe,
.remove = qcom_cpu_clk_msm8996_driver_remove,
.driver = {
.name = "qcom-msm8996-apcc",
.of_match_table = qcom_cpu_clk_msm8996_match_table,
},
};
module_platform_driver(qcom_cpu_clk_msm8996_driver);
MODULE_DESCRIPTION("QCOM MSM8996 CPU Clock Driver");
MODULE_LICENSE("GPL v2");