mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-26 06:04:14 +08:00
Merge branch 'clk-qcom' into clk-next
- Enable CPU clks on Qualcomm MSM8996 SoCs * clk-qcom: clk: qcom: Add CPU clock driver for msm8996 dt-bindings: clk: qcom: Add bindings for CPU clock for msm8996 soc: qcom: Separate kryo l2 accessors from PMU driver clk: qcom: Fix return value check in apss_ipq6018_probe()
This commit is contained in:
commit
d39fc26556
@ -0,0 +1,56 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/clock/qcom,kryocc.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Qualcomm clock controller for MSM8996 CPUs
|
||||
|
||||
maintainers:
|
||||
- Loic Poulain <loic.poulain@linaro.org>
|
||||
|
||||
description: |
|
||||
Qualcomm CPU clock controller for MSM8996 CPUs, clock 0 is for Power cluster
|
||||
and clock 1 is for Perf cluster.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- qcom,msm8996-apcc
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
'#clock-cells':
|
||||
const: 1
|
||||
|
||||
clocks:
|
||||
items:
|
||||
- description: Primary PLL clock for power cluster (little)
|
||||
- description: Primary PLL clock for perf cluster (big)
|
||||
- description: Alternate PLL clock for power cluster (little)
|
||||
- description: Alternate PLL clock for perf cluster (big)
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: pwrcl_pll
|
||||
- const: perfcl_pll
|
||||
- const: pwrcl_alt_pll
|
||||
- const: perfcl_alt_pll
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- '#clock-cells'
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
# Example for msm8996
|
||||
- |
|
||||
kryocc: clock-controller@6400000 {
|
||||
compatible = "qcom,msm8996-apcc";
|
||||
reg = <0x6400000 0x90000>;
|
||||
#clock-cells = <1>;
|
||||
};
|
||||
...
|
@ -37,6 +37,15 @@ config QCOM_CLK_APCS_MSM8916
|
||||
Say Y if you want to support CPU frequency scaling on devices
|
||||
such as msm8916.
|
||||
|
||||
config QCOM_CLK_APCC_MSM8996
|
||||
tristate "MSM8996 CPU Clock Controller"
|
||||
select QCOM_KRYO_L2_ACCESSORS
|
||||
depends on ARM64
|
||||
help
|
||||
Support for the CPU clock controller on msm8996 devices.
|
||||
Say Y if you want to support CPU clock scaling using CPUfreq
|
||||
drivers for dyanmic power management.
|
||||
|
||||
config QCOM_CLK_RPM
|
||||
tristate "RPM based Clock Controller"
|
||||
depends on MFD_QCOM_RPM
|
||||
|
@ -44,6 +44,7 @@ obj-$(CONFIG_MSM_MMCC_8996) += mmcc-msm8996.o
|
||||
obj-$(CONFIG_MSM_MMCC_8998) += mmcc-msm8998.o
|
||||
obj-$(CONFIG_QCOM_A53PLL) += a53-pll.o
|
||||
obj-$(CONFIG_QCOM_CLK_APCS_MSM8916) += apcs-msm8916.o
|
||||
obj-$(CONFIG_QCOM_CLK_APCC_MSM8996) += clk-cpu-8996.o
|
||||
obj-$(CONFIG_QCOM_CLK_RPM) += clk-rpm.o
|
||||
obj-$(CONFIG_QCOM_CLK_RPMH) += clk-rpmh.o
|
||||
obj-$(CONFIG_QCOM_CLK_SMD_RPM) += clk-smd-rpm.o
|
||||
|
@ -87,8 +87,8 @@ static int apss_ipq6018_probe(struct platform_device *pdev)
|
||||
struct regmap *regmap;
|
||||
|
||||
regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
||||
if (IS_ERR(regmap))
|
||||
return PTR_ERR(regmap);
|
||||
if (!regmap)
|
||||
return -ENODEV;
|
||||
|
||||
return qcom_cc_really_probe(pdev, &apss_ipq6018_desc, regmap);
|
||||
}
|
||||
|
@ -47,6 +47,12 @@ struct pll_vco {
|
||||
u32 val;
|
||||
};
|
||||
|
||||
#define VCO(a, b, c) { \
|
||||
.val = a,\
|
||||
.min_freq = b,\
|
||||
.max_freq = c,\
|
||||
}
|
||||
|
||||
/**
|
||||
* struct clk_alpha_pll - phase locked loop (PLL)
|
||||
* @offset: base address of registers
|
||||
|
538
drivers/clk/qcom/clk-cpu-8996.c
Normal file
538
drivers/clk/qcom/clk-cpu-8996.c
Normal file
@ -0,0 +1,538 @@
|
||||
// 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,
|
||||
};
|
||||
|
||||
struct clk_regmap *cpu_msm8996_clks[] = {
|
||||
&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");
|
@ -82,6 +82,7 @@ config FSL_IMX8_DDR_PMU
|
||||
config QCOM_L2_PMU
|
||||
bool "Qualcomm Technologies L2-cache PMU"
|
||||
depends on ARCH_QCOM && ARM64 && ACPI
|
||||
select QCOM_KRYO_L2_ACCESSORS
|
||||
help
|
||||
Provides support for the L2 cache performance monitor unit (PMU)
|
||||
in Qualcomm Technologies processors.
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <asm/barrier.h>
|
||||
#include <asm/local64.h>
|
||||
#include <asm/sysreg.h>
|
||||
#include <soc/qcom/kryo-l2-accessors.h>
|
||||
|
||||
#define MAX_L2_CTRS 9
|
||||
|
||||
@ -79,8 +80,6 @@
|
||||
#define L2_COUNTER_RELOAD BIT_ULL(31)
|
||||
#define L2_CYCLE_COUNTER_RELOAD BIT_ULL(63)
|
||||
|
||||
#define L2CPUSRSELR_EL1 sys_reg(3, 3, 15, 0, 6)
|
||||
#define L2CPUSRDR_EL1 sys_reg(3, 3, 15, 0, 7)
|
||||
|
||||
#define reg_idx(reg, i) (((i) * IA_L2_REG_OFFSET) + reg##_BASE)
|
||||
|
||||
@ -99,48 +98,7 @@
|
||||
#define L2_EVENT_STREX 0x421
|
||||
#define L2_EVENT_CLREX 0x422
|
||||
|
||||
static DEFINE_RAW_SPINLOCK(l2_access_lock);
|
||||
|
||||
/**
|
||||
* set_l2_indirect_reg: write value to an L2 register
|
||||
* @reg: Address of L2 register.
|
||||
* @value: Value to be written to register.
|
||||
*
|
||||
* Use architecturally required barriers for ordering between system register
|
||||
* accesses
|
||||
*/
|
||||
static void set_l2_indirect_reg(u64 reg, u64 val)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
raw_spin_lock_irqsave(&l2_access_lock, flags);
|
||||
write_sysreg_s(reg, L2CPUSRSELR_EL1);
|
||||
isb();
|
||||
write_sysreg_s(val, L2CPUSRDR_EL1);
|
||||
isb();
|
||||
raw_spin_unlock_irqrestore(&l2_access_lock, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* get_l2_indirect_reg: read an L2 register value
|
||||
* @reg: Address of L2 register.
|
||||
*
|
||||
* Use architecturally required barriers for ordering between system register
|
||||
* accesses
|
||||
*/
|
||||
static u64 get_l2_indirect_reg(u64 reg)
|
||||
{
|
||||
u64 val;
|
||||
unsigned long flags;
|
||||
|
||||
raw_spin_lock_irqsave(&l2_access_lock, flags);
|
||||
write_sysreg_s(reg, L2CPUSRSELR_EL1);
|
||||
isb();
|
||||
val = read_sysreg_s(L2CPUSRDR_EL1);
|
||||
raw_spin_unlock_irqrestore(&l2_access_lock, flags);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
struct cluster_pmu;
|
||||
|
||||
@ -211,28 +169,28 @@ static inline struct cluster_pmu *get_cluster_pmu(
|
||||
static void cluster_pmu_reset(void)
|
||||
{
|
||||
/* Reset all counters */
|
||||
set_l2_indirect_reg(L2PMCR, L2PMCR_RESET_ALL);
|
||||
set_l2_indirect_reg(L2PMCNTENCLR, l2_counter_present_mask);
|
||||
set_l2_indirect_reg(L2PMINTENCLR, l2_counter_present_mask);
|
||||
set_l2_indirect_reg(L2PMOVSCLR, l2_counter_present_mask);
|
||||
kryo_l2_set_indirect_reg(L2PMCR, L2PMCR_RESET_ALL);
|
||||
kryo_l2_set_indirect_reg(L2PMCNTENCLR, l2_counter_present_mask);
|
||||
kryo_l2_set_indirect_reg(L2PMINTENCLR, l2_counter_present_mask);
|
||||
kryo_l2_set_indirect_reg(L2PMOVSCLR, l2_counter_present_mask);
|
||||
}
|
||||
|
||||
static inline void cluster_pmu_enable(void)
|
||||
{
|
||||
set_l2_indirect_reg(L2PMCR, L2PMCR_COUNTERS_ENABLE);
|
||||
kryo_l2_set_indirect_reg(L2PMCR, L2PMCR_COUNTERS_ENABLE);
|
||||
}
|
||||
|
||||
static inline void cluster_pmu_disable(void)
|
||||
{
|
||||
set_l2_indirect_reg(L2PMCR, L2PMCR_COUNTERS_DISABLE);
|
||||
kryo_l2_set_indirect_reg(L2PMCR, L2PMCR_COUNTERS_DISABLE);
|
||||
}
|
||||
|
||||
static inline void cluster_pmu_counter_set_value(u32 idx, u64 value)
|
||||
{
|
||||
if (idx == l2_cycle_ctr_idx)
|
||||
set_l2_indirect_reg(L2PMCCNTR, value);
|
||||
kryo_l2_set_indirect_reg(L2PMCCNTR, value);
|
||||
else
|
||||
set_l2_indirect_reg(reg_idx(IA_L2PMXEVCNTR, idx), value);
|
||||
kryo_l2_set_indirect_reg(reg_idx(IA_L2PMXEVCNTR, idx), value);
|
||||
}
|
||||
|
||||
static inline u64 cluster_pmu_counter_get_value(u32 idx)
|
||||
@ -240,46 +198,46 @@ static inline u64 cluster_pmu_counter_get_value(u32 idx)
|
||||
u64 value;
|
||||
|
||||
if (idx == l2_cycle_ctr_idx)
|
||||
value = get_l2_indirect_reg(L2PMCCNTR);
|
||||
value = kryo_l2_get_indirect_reg(L2PMCCNTR);
|
||||
else
|
||||
value = get_l2_indirect_reg(reg_idx(IA_L2PMXEVCNTR, idx));
|
||||
value = kryo_l2_get_indirect_reg(reg_idx(IA_L2PMXEVCNTR, idx));
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static inline void cluster_pmu_counter_enable(u32 idx)
|
||||
{
|
||||
set_l2_indirect_reg(L2PMCNTENSET, idx_to_reg_bit(idx));
|
||||
kryo_l2_set_indirect_reg(L2PMCNTENSET, idx_to_reg_bit(idx));
|
||||
}
|
||||
|
||||
static inline void cluster_pmu_counter_disable(u32 idx)
|
||||
{
|
||||
set_l2_indirect_reg(L2PMCNTENCLR, idx_to_reg_bit(idx));
|
||||
kryo_l2_set_indirect_reg(L2PMCNTENCLR, idx_to_reg_bit(idx));
|
||||
}
|
||||
|
||||
static inline void cluster_pmu_counter_enable_interrupt(u32 idx)
|
||||
{
|
||||
set_l2_indirect_reg(L2PMINTENSET, idx_to_reg_bit(idx));
|
||||
kryo_l2_set_indirect_reg(L2PMINTENSET, idx_to_reg_bit(idx));
|
||||
}
|
||||
|
||||
static inline void cluster_pmu_counter_disable_interrupt(u32 idx)
|
||||
{
|
||||
set_l2_indirect_reg(L2PMINTENCLR, idx_to_reg_bit(idx));
|
||||
kryo_l2_set_indirect_reg(L2PMINTENCLR, idx_to_reg_bit(idx));
|
||||
}
|
||||
|
||||
static inline void cluster_pmu_set_evccntcr(u32 val)
|
||||
{
|
||||
set_l2_indirect_reg(L2PMCCNTCR, val);
|
||||
kryo_l2_set_indirect_reg(L2PMCCNTCR, val);
|
||||
}
|
||||
|
||||
static inline void cluster_pmu_set_evcntcr(u32 ctr, u32 val)
|
||||
{
|
||||
set_l2_indirect_reg(reg_idx(IA_L2PMXEVCNTCR, ctr), val);
|
||||
kryo_l2_set_indirect_reg(reg_idx(IA_L2PMXEVCNTCR, ctr), val);
|
||||
}
|
||||
|
||||
static inline void cluster_pmu_set_evtyper(u32 ctr, u32 val)
|
||||
{
|
||||
set_l2_indirect_reg(reg_idx(IA_L2PMXEVTYPER, ctr), val);
|
||||
kryo_l2_set_indirect_reg(reg_idx(IA_L2PMXEVTYPER, ctr), val);
|
||||
}
|
||||
|
||||
static void cluster_pmu_set_resr(struct cluster_pmu *cluster,
|
||||
@ -295,11 +253,11 @@ static void cluster_pmu_set_resr(struct cluster_pmu *cluster,
|
||||
|
||||
spin_lock_irqsave(&cluster->pmu_lock, flags);
|
||||
|
||||
resr_val = get_l2_indirect_reg(L2PMRESR);
|
||||
resr_val = kryo_l2_get_indirect_reg(L2PMRESR);
|
||||
resr_val &= ~(L2PMRESR_GROUP_MASK << shift);
|
||||
resr_val |= field;
|
||||
resr_val |= L2PMRESR_EN;
|
||||
set_l2_indirect_reg(L2PMRESR, resr_val);
|
||||
kryo_l2_set_indirect_reg(L2PMRESR, resr_val);
|
||||
|
||||
spin_unlock_irqrestore(&cluster->pmu_lock, flags);
|
||||
}
|
||||
@ -315,14 +273,14 @@ static inline void cluster_pmu_set_evfilter_sys_mode(u32 ctr)
|
||||
L2PMXEVFILTER_ORGFILTER_IDINDEP |
|
||||
L2PMXEVFILTER_ORGFILTER_ALL;
|
||||
|
||||
set_l2_indirect_reg(reg_idx(IA_L2PMXEVFILTER, ctr), val);
|
||||
kryo_l2_set_indirect_reg(reg_idx(IA_L2PMXEVFILTER, ctr), val);
|
||||
}
|
||||
|
||||
static inline u32 cluster_pmu_getreset_ovsr(void)
|
||||
{
|
||||
u32 result = get_l2_indirect_reg(L2PMOVSSET);
|
||||
u32 result = kryo_l2_get_indirect_reg(L2PMOVSSET);
|
||||
|
||||
set_l2_indirect_reg(L2PMOVSCLR, result);
|
||||
kryo_l2_set_indirect_reg(L2PMOVSCLR, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -767,7 +725,7 @@ static int get_num_counters(void)
|
||||
{
|
||||
int val;
|
||||
|
||||
val = get_l2_indirect_reg(L2PMCR);
|
||||
val = kryo_l2_get_indirect_reg(L2PMCR);
|
||||
|
||||
/*
|
||||
* Read number of counters from L2PMCR and add 1
|
||||
|
@ -53,6 +53,10 @@ config QCOM_LLCC
|
||||
SDM845. This provides interfaces to clients that use the LLCC.
|
||||
Say yes here to enable LLCC slice driver.
|
||||
|
||||
config QCOM_KRYO_L2_ACCESSORS
|
||||
bool
|
||||
depends on ARCH_QCOM && ARM64 || COMPILE_TEST
|
||||
|
||||
config QCOM_MDT_LOADER
|
||||
tristate
|
||||
select QCOM_SCM
|
||||
|
@ -24,3 +24,4 @@ obj-$(CONFIG_QCOM_APR) += apr.o
|
||||
obj-$(CONFIG_QCOM_LLCC) += llcc-qcom.o
|
||||
obj-$(CONFIG_QCOM_RPMHPD) += rpmhpd.o
|
||||
obj-$(CONFIG_QCOM_RPMPD) += rpmpd.o
|
||||
obj-$(CONFIG_QCOM_KRYO_L2_ACCESSORS) += kryo-l2-accessors.o
|
||||
|
57
drivers/soc/qcom/kryo-l2-accessors.c
Normal file
57
drivers/soc/qcom/kryo-l2-accessors.c
Normal file
@ -0,0 +1,57 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2018, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/spinlock.h>
|
||||
#include <asm/barrier.h>
|
||||
#include <asm/sysreg.h>
|
||||
#include <soc/qcom/kryo-l2-accessors.h>
|
||||
|
||||
#define L2CPUSRSELR_EL1 sys_reg(3, 3, 15, 0, 6)
|
||||
#define L2CPUSRDR_EL1 sys_reg(3, 3, 15, 0, 7)
|
||||
|
||||
static DEFINE_RAW_SPINLOCK(l2_access_lock);
|
||||
|
||||
/**
|
||||
* kryo_l2_set_indirect_reg() - write value to an L2 register
|
||||
* @reg: Address of L2 register.
|
||||
* @value: Value to be written to register.
|
||||
*
|
||||
* Use architecturally required barriers for ordering between system register
|
||||
* accesses, and system registers with respect to device memory
|
||||
*/
|
||||
void kryo_l2_set_indirect_reg(u64 reg, u64 val)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
raw_spin_lock_irqsave(&l2_access_lock, flags);
|
||||
write_sysreg_s(reg, L2CPUSRSELR_EL1);
|
||||
isb();
|
||||
write_sysreg_s(val, L2CPUSRDR_EL1);
|
||||
isb();
|
||||
raw_spin_unlock_irqrestore(&l2_access_lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL(kryo_l2_set_indirect_reg);
|
||||
|
||||
/**
|
||||
* kryo_l2_get_indirect_reg() - read an L2 register value
|
||||
* @reg: Address of L2 register.
|
||||
*
|
||||
* Use architecturally required barriers for ordering between system register
|
||||
* accesses, and system registers with respect to device memory
|
||||
*/
|
||||
u64 kryo_l2_get_indirect_reg(u64 reg)
|
||||
{
|
||||
u64 val;
|
||||
unsigned long flags;
|
||||
|
||||
raw_spin_lock_irqsave(&l2_access_lock, flags);
|
||||
write_sysreg_s(reg, L2CPUSRSELR_EL1);
|
||||
isb();
|
||||
val = read_sysreg_s(L2CPUSRDR_EL1);
|
||||
raw_spin_unlock_irqrestore(&l2_access_lock, flags);
|
||||
|
||||
return val;
|
||||
}
|
||||
EXPORT_SYMBOL(kryo_l2_get_indirect_reg);
|
12
include/soc/qcom/kryo-l2-accessors.h
Normal file
12
include/soc/qcom/kryo-l2-accessors.h
Normal file
@ -0,0 +1,12 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (c) 2018, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __SOC_ARCH_QCOM_KRYO_L2_ACCESSORS_H
|
||||
#define __SOC_ARCH_QCOM_KRYO_L2_ACCESSORS_H
|
||||
|
||||
void kryo_l2_set_indirect_reg(u64 reg, u64 val);
|
||||
u64 kryo_l2_get_indirect_reg(u64 reg);
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user