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:
Stephen Boyd
2020-07-10 17:14:55 -07:00
12 changed files with 711 additions and 68 deletions

View File

@@ -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>;
};
...

View File

@@ -37,6 +37,15 @@ config QCOM_CLK_APCS_MSM8916
Say Y if you want to support CPU frequency scaling on devices Say Y if you want to support CPU frequency scaling on devices
such as msm8916. 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 config QCOM_CLK_RPM
tristate "RPM based Clock Controller" tristate "RPM based Clock Controller"
depends on MFD_QCOM_RPM depends on MFD_QCOM_RPM

View File

@@ -44,6 +44,7 @@ obj-$(CONFIG_MSM_MMCC_8996) += mmcc-msm8996.o
obj-$(CONFIG_MSM_MMCC_8998) += mmcc-msm8998.o obj-$(CONFIG_MSM_MMCC_8998) += mmcc-msm8998.o
obj-$(CONFIG_QCOM_A53PLL) += a53-pll.o obj-$(CONFIG_QCOM_A53PLL) += a53-pll.o
obj-$(CONFIG_QCOM_CLK_APCS_MSM8916) += apcs-msm8916.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_RPM) += clk-rpm.o
obj-$(CONFIG_QCOM_CLK_RPMH) += clk-rpmh.o obj-$(CONFIG_QCOM_CLK_RPMH) += clk-rpmh.o
obj-$(CONFIG_QCOM_CLK_SMD_RPM) += clk-smd-rpm.o obj-$(CONFIG_QCOM_CLK_SMD_RPM) += clk-smd-rpm.o

View File

@@ -87,8 +87,8 @@ static int apss_ipq6018_probe(struct platform_device *pdev)
struct regmap *regmap; struct regmap *regmap;
regmap = dev_get_regmap(pdev->dev.parent, NULL); regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (IS_ERR(regmap)) if (!regmap)
return PTR_ERR(regmap); return -ENODEV;
return qcom_cc_really_probe(pdev, &apss_ipq6018_desc, regmap); return qcom_cc_really_probe(pdev, &apss_ipq6018_desc, regmap);
} }

View File

@@ -47,6 +47,12 @@ struct pll_vco {
u32 val; u32 val;
}; };
#define VCO(a, b, c) { \
.val = a,\
.min_freq = b,\
.max_freq = c,\
}
/** /**
* struct clk_alpha_pll - phase locked loop (PLL) * struct clk_alpha_pll - phase locked loop (PLL)
* @offset: base address of registers * @offset: base address of registers

View 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");

View File

@@ -82,6 +82,7 @@ config FSL_IMX8_DDR_PMU
config QCOM_L2_PMU config QCOM_L2_PMU
bool "Qualcomm Technologies L2-cache PMU" bool "Qualcomm Technologies L2-cache PMU"
depends on ARCH_QCOM && ARM64 && ACPI depends on ARCH_QCOM && ARM64 && ACPI
select QCOM_KRYO_L2_ACCESSORS
help help
Provides support for the L2 cache performance monitor unit (PMU) Provides support for the L2 cache performance monitor unit (PMU)
in Qualcomm Technologies processors. in Qualcomm Technologies processors.

View File

@@ -23,6 +23,7 @@
#include <asm/barrier.h> #include <asm/barrier.h>
#include <asm/local64.h> #include <asm/local64.h>
#include <asm/sysreg.h> #include <asm/sysreg.h>
#include <soc/qcom/kryo-l2-accessors.h>
#define MAX_L2_CTRS 9 #define MAX_L2_CTRS 9
@@ -79,8 +80,6 @@
#define L2_COUNTER_RELOAD BIT_ULL(31) #define L2_COUNTER_RELOAD BIT_ULL(31)
#define L2_CYCLE_COUNTER_RELOAD BIT_ULL(63) #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) #define reg_idx(reg, i) (((i) * IA_L2_REG_OFFSET) + reg##_BASE)
@@ -99,48 +98,7 @@
#define L2_EVENT_STREX 0x421 #define L2_EVENT_STREX 0x421
#define L2_EVENT_CLREX 0x422 #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; struct cluster_pmu;
@@ -211,28 +169,28 @@ static inline struct cluster_pmu *get_cluster_pmu(
static void cluster_pmu_reset(void) static void cluster_pmu_reset(void)
{ {
/* Reset all counters */ /* Reset all counters */
set_l2_indirect_reg(L2PMCR, L2PMCR_RESET_ALL); kryo_l2_set_indirect_reg(L2PMCR, L2PMCR_RESET_ALL);
set_l2_indirect_reg(L2PMCNTENCLR, l2_counter_present_mask); kryo_l2_set_indirect_reg(L2PMCNTENCLR, l2_counter_present_mask);
set_l2_indirect_reg(L2PMINTENCLR, l2_counter_present_mask); kryo_l2_set_indirect_reg(L2PMINTENCLR, l2_counter_present_mask);
set_l2_indirect_reg(L2PMOVSCLR, l2_counter_present_mask); kryo_l2_set_indirect_reg(L2PMOVSCLR, l2_counter_present_mask);
} }
static inline void cluster_pmu_enable(void) 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) 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) static inline void cluster_pmu_counter_set_value(u32 idx, u64 value)
{ {
if (idx == l2_cycle_ctr_idx) if (idx == l2_cycle_ctr_idx)
set_l2_indirect_reg(L2PMCCNTR, value); kryo_l2_set_indirect_reg(L2PMCCNTR, value);
else 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) 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; u64 value;
if (idx == l2_cycle_ctr_idx) if (idx == l2_cycle_ctr_idx)
value = get_l2_indirect_reg(L2PMCCNTR); value = kryo_l2_get_indirect_reg(L2PMCCNTR);
else else
value = get_l2_indirect_reg(reg_idx(IA_L2PMXEVCNTR, idx)); value = kryo_l2_get_indirect_reg(reg_idx(IA_L2PMXEVCNTR, idx));
return value; return value;
} }
static inline void cluster_pmu_counter_enable(u32 idx) 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) 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) 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) 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) 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) 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) 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, 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); 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 &= ~(L2PMRESR_GROUP_MASK << shift);
resr_val |= field; resr_val |= field;
resr_val |= L2PMRESR_EN; 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); 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_IDINDEP |
L2PMXEVFILTER_ORGFILTER_ALL; 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) 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; return result;
} }
@@ -767,7 +725,7 @@ static int get_num_counters(void)
{ {
int val; int val;
val = get_l2_indirect_reg(L2PMCR); val = kryo_l2_get_indirect_reg(L2PMCR);
/* /*
* Read number of counters from L2PMCR and add 1 * Read number of counters from L2PMCR and add 1

View File

@@ -53,6 +53,10 @@ config QCOM_LLCC
SDM845. This provides interfaces to clients that use the LLCC. SDM845. This provides interfaces to clients that use the LLCC.
Say yes here to enable LLCC slice driver. 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 config QCOM_MDT_LOADER
tristate tristate
select QCOM_SCM select QCOM_SCM

View File

@@ -24,3 +24,4 @@ obj-$(CONFIG_QCOM_APR) += apr.o
obj-$(CONFIG_QCOM_LLCC) += llcc-qcom.o obj-$(CONFIG_QCOM_LLCC) += llcc-qcom.o
obj-$(CONFIG_QCOM_RPMHPD) += rpmhpd.o obj-$(CONFIG_QCOM_RPMHPD) += rpmhpd.o
obj-$(CONFIG_QCOM_RPMPD) += rpmpd.o obj-$(CONFIG_QCOM_RPMPD) += rpmpd.o
obj-$(CONFIG_QCOM_KRYO_L2_ACCESSORS) += kryo-l2-accessors.o

View 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);

View 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