mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2024-12-29 09:12:07 +00:00
ARM cpufreq updates for 6.13
- Add virtual cpufreq driver for guest kernels (David Dai). - Minor cleanup to various cpufreq drivers (Andy Shevchenko, Dhruva Gole, Jie Zhan, Jinjie Ruan, Shuosheng Huang, Sibi Sankar, and Yuan Can). - Revert "cpufreq: brcmstb-avs-cpufreq: Fix initial command check" (Colin Ian King). - Improve DT bindings for qcom-hw driver (Dmitry Baryshkov, Konrad Dybcio, and Nikunj Kela). -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEx73Crsp7f6M6scA70rkcPK6BEhwFAmc6wgAACgkQ0rkcPK6B Ehz2HA/9FvaDMmi4q1yt2CvkypN4XiaerUZBE+wBMgLDiGIOzx3X1YpiZwfH7LRR V+E7+63ZYH0bxfErG3M75x8YKvARB5WaiP+f+YYIFGnBiNdbG8WdooAy+gViE+AX Wiq4FNV2IqaQXceq1TCFMiwN8tn1gZO/axsWDiEUB+bTn11noJBkNa4I6TaGDH4X IwTVss5VBcP4fORmkTSnA/Epw6mtFIQfHPO3m5SbgBiB6NVK3+//ZAnHCYB23H34 X5f0BcTw0IxkHbSuASg8ZMgqHfnmduV8g8dAhhIMkh2Zci145nmcSdcBZk1G32NC ffYznTTwEVRGMQ9ku6j9FXrqUw0Bb8GEOKSNMoO0Tc+e0UmAP/xKYICH3lbLEfXo 3DWBDNu7wNjTpZ5OJwkFsRspCVZVBDi/bnBH+XN2ELzLIPiSMN2R+I480G5uNfMt iLy9Vzqpw7H6Z2OZiN9scUDZ/pgIC7T3ZjJLwy6XsBeWjM3/AwNPx1LYM5UNFl2P MEQs0EOXl1jxMEFG4jmr8iDT5dhgX0LM5zVq0kK/dQCtLtz9BqcVr3NV3bU4pLrx fldHqaxZXnCFqtXR3RHSodGdj1B3H+KhgeXki7qxtXAAzdydNFDZxA0Kxm7DHG0i onwB+b+J4TrEIwdAODZsjh4XYjKl/fLaIinHCkabQhOXUlQsgcg= =72Nq -----END PGP SIGNATURE----- Merge tag 'cpufreq-arm-updates-6.13' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/vireshk/pm Merge ARM cpufreq updates for 6.13 from Viresh Kumar: "- Add virtual cpufreq driver for guest kernels (David Dai). - Minor cleanup to various cpufreq drivers (Andy Shevchenko, Dhruva Gole, Jie Zhan, Jinjie Ruan, Shuosheng Huang, Sibi Sankar, and Yuan Can). - Revert "cpufreq: brcmstb-avs-cpufreq: Fix initial command check" (Colin Ian King). - Improve DT bindings for qcom-hw driver (Dmitry Baryshkov, Konrad Dybcio, and Nikunj Kela)." * tag 'cpufreq-arm-updates-6.13' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/vireshk/pm: arm64: dts: qcom: sc8180x: Add a SoC-specific compatible to cpufreq-hw dt-bindings: cpufreq: cpufreq-qcom-hw: Add SC8180X compatible cpufreq: sun50i: add a100 cpufreq support cpufreq: mediatek-hw: Fix wrong return value in mtk_cpufreq_get_cpu_power() cpufreq: CPPC: Fix wrong return value in cppc_get_cpu_power() cpufreq: CPPC: Fix wrong return value in cppc_get_cpu_cost() cpufreq: loongson3: Check for error code from devm_mutex_init() call cpufreq: scmi: Fix cleanup path when boost enablement fails cpufreq: CPPC: Fix possible null-ptr-deref for cppc_get_cpu_cost() cpufreq: CPPC: Fix possible null-ptr-deref for cpufreq_cpu_get_raw() Revert "cpufreq: brcmstb-avs-cpufreq: Fix initial command check" dt-bindings: cpufreq: cpufreq-qcom-hw: Add SAR2130P compatible cpufreq: add virtual-cpufreq driver dt-bindings: cpufreq: add virtual cpufreq device cpufreq: loongson2: Unregister platform_driver on failure cpufreq: ti-cpufreq: Remove revision offsets in AM62 family cpufreq: ti-cpufreq: Allow backward compatibility for efuse syscon cppc_cpufreq: Remove HiSilicon CPPC workaround cppc_cpufreq: Use desired perf if feedback ctrs are 0 or unchanged dt-bindings: cpufreq: qcom-hw: document support for SA8255p
This commit is contained in:
commit
baf4ae8038
@ -23,6 +23,7 @@ properties:
|
||||
- enum:
|
||||
- qcom,qcm2290-cpufreq-hw
|
||||
- qcom,sc7180-cpufreq-hw
|
||||
- qcom,sc8180x-cpufreq-hw
|
||||
- qcom,sdm670-cpufreq-hw
|
||||
- qcom,sdm845-cpufreq-hw
|
||||
- qcom,sm6115-cpufreq-hw
|
||||
@ -34,7 +35,9 @@ properties:
|
||||
items:
|
||||
- enum:
|
||||
- qcom,qdu1000-cpufreq-epss
|
||||
- qcom,sa8255p-cpufreq-epss
|
||||
- qcom,sa8775p-cpufreq-epss
|
||||
- qcom,sar2130p-cpufreq-epss
|
||||
- qcom,sc7280-cpufreq-epss
|
||||
- qcom,sc8280xp-cpufreq-epss
|
||||
- qcom,sdx75-cpufreq-epss
|
||||
@ -107,6 +110,7 @@ allOf:
|
||||
contains:
|
||||
enum:
|
||||
- qcom,qcm2290-cpufreq-hw
|
||||
- qcom,sar2130p-cpufreq-epss
|
||||
then:
|
||||
properties:
|
||||
reg:
|
||||
@ -130,7 +134,9 @@ allOf:
|
||||
contains:
|
||||
enum:
|
||||
- qcom,qdu1000-cpufreq-epss
|
||||
- qcom,sa8255p-cpufreq-epss
|
||||
- qcom,sc7180-cpufreq-hw
|
||||
- qcom,sc8180x-cpufreq-hw
|
||||
- qcom,sc8280xp-cpufreq-epss
|
||||
- qcom,sdm670-cpufreq-hw
|
||||
- qcom,sdm845-cpufreq-hw
|
||||
|
@ -0,0 +1,48 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/cpufreq/qemu,virtual-cpufreq.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Virtual CPUFreq
|
||||
|
||||
maintainers:
|
||||
- David Dai <davidai@google.com>
|
||||
- Saravana Kannan <saravanak@google.com>
|
||||
|
||||
description:
|
||||
Virtual CPUFreq is a virtualized driver in guest kernels that sends performance
|
||||
selection of its vCPUs as a hint to the host through MMIO regions. Each vCPU
|
||||
is associated with a performance domain which can be shared with other vCPUs.
|
||||
Each performance domain has its own set of registers for performance controls.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: qemu,virtual-cpufreq
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
description:
|
||||
Address and size of region containing performance controls for each of the
|
||||
performance domains. Regions for each performance domain is placed
|
||||
contiguously and contain registers for controlling DVFS(Dynamic Frequency
|
||||
and Voltage) characteristics. The size of the region is proportional to
|
||||
total number of performance domains.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
soc {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
cpufreq@1040000 {
|
||||
compatible = "qemu,virtual-cpufreq";
|
||||
reg = <0x1040000 0x2000>;
|
||||
};
|
||||
};
|
@ -3889,7 +3889,7 @@ lmh@18358800 {
|
||||
};
|
||||
|
||||
cpufreq_hw: cpufreq@18323000 {
|
||||
compatible = "qcom,cpufreq-hw";
|
||||
compatible = "qcom,sc8180x-cpufreq-hw", "qcom,cpufreq-hw";
|
||||
reg = <0 0x18323000 0 0x1400>, <0 0x18325800 0 0x1400>;
|
||||
reg-names = "freq-domain0", "freq-domain1";
|
||||
|
||||
|
@ -217,6 +217,20 @@ config CPUFREQ_DT
|
||||
|
||||
If in doubt, say N.
|
||||
|
||||
config CPUFREQ_VIRT
|
||||
tristate "Virtual cpufreq driver"
|
||||
depends on GENERIC_ARCH_TOPOLOGY
|
||||
help
|
||||
This adds a virtualized cpufreq driver for guest kernels that
|
||||
read/writes to a MMIO region for a virtualized cpufreq device to
|
||||
communicate with the host. It sends performance requests to the host
|
||||
which gets used as a hint to schedule vCPU threads and select CPU
|
||||
frequency. If a VM does not support a virtualized FIE such as AMUs,
|
||||
it updates the frequency scaling factor by polling host CPU frequency
|
||||
to enable accurate Per-Entity Load Tracking for tasks running in the guest.
|
||||
|
||||
If in doubt, say N.
|
||||
|
||||
config CPUFREQ_DT_PLATDEV
|
||||
tristate "Generic DT based cpufreq platdev driver"
|
||||
depends on OF
|
||||
|
@ -16,6 +16,7 @@ obj-$(CONFIG_CPU_FREQ_GOV_ATTR_SET) += cpufreq_governor_attr_set.o
|
||||
|
||||
obj-$(CONFIG_CPUFREQ_DT) += cpufreq-dt.o
|
||||
obj-$(CONFIG_CPUFREQ_DT_PLATDEV) += cpufreq-dt-platdev.o
|
||||
obj-$(CONFIG_CPUFREQ_VIRT) += virtual-cpufreq.o
|
||||
|
||||
# Traces
|
||||
CFLAGS_amd-pstate-trace.o := -I$(src)
|
||||
|
@ -474,8 +474,8 @@ static bool brcm_avs_is_firmware_loaded(struct private_data *priv)
|
||||
rc = brcm_avs_get_pmap(priv, NULL);
|
||||
magic = readl(priv->base + AVS_MBOX_MAGIC);
|
||||
|
||||
return (magic == AVS_FIRMWARE_MAGIC) && ((rc != -ENOTSUPP) ||
|
||||
(rc != -EINVAL));
|
||||
return (magic == AVS_FIRMWARE_MAGIC) && (rc != -ENOTSUPP) &&
|
||||
(rc != -EINVAL);
|
||||
}
|
||||
|
||||
static unsigned int brcm_avs_cpufreq_get(unsigned int cpu)
|
||||
|
@ -36,33 +36,15 @@ static LIST_HEAD(cpu_data_list);
|
||||
|
||||
static bool boost_supported;
|
||||
|
||||
struct cppc_workaround_oem_info {
|
||||
char oem_id[ACPI_OEM_ID_SIZE + 1];
|
||||
char oem_table_id[ACPI_OEM_TABLE_ID_SIZE + 1];
|
||||
u32 oem_revision;
|
||||
};
|
||||
|
||||
static struct cppc_workaround_oem_info wa_info[] = {
|
||||
{
|
||||
.oem_id = "HISI ",
|
||||
.oem_table_id = "HIP07 ",
|
||||
.oem_revision = 0,
|
||||
}, {
|
||||
.oem_id = "HISI ",
|
||||
.oem_table_id = "HIP08 ",
|
||||
.oem_revision = 0,
|
||||
}
|
||||
};
|
||||
|
||||
static struct cpufreq_driver cppc_cpufreq_driver;
|
||||
|
||||
#ifdef CONFIG_ACPI_CPPC_CPUFREQ_FIE
|
||||
static enum {
|
||||
FIE_UNSET = -1,
|
||||
FIE_ENABLED,
|
||||
FIE_DISABLED
|
||||
} fie_disabled = FIE_UNSET;
|
||||
|
||||
#ifdef CONFIG_ACPI_CPPC_CPUFREQ_FIE
|
||||
module_param(fie_disabled, int, 0444);
|
||||
MODULE_PARM_DESC(fie_disabled, "Disable Frequency Invariance Engine (FIE)");
|
||||
|
||||
@ -78,7 +60,6 @@ struct cppc_freq_invariance {
|
||||
static DEFINE_PER_CPU(struct cppc_freq_invariance, cppc_freq_inv);
|
||||
static struct kthread_worker *kworker_fie;
|
||||
|
||||
static unsigned int hisi_cppc_cpufreq_get_rate(unsigned int cpu);
|
||||
static int cppc_perf_from_fbctrs(struct cppc_cpudata *cpu_data,
|
||||
struct cppc_perf_fb_ctrs *fb_ctrs_t0,
|
||||
struct cppc_perf_fb_ctrs *fb_ctrs_t1);
|
||||
@ -118,6 +99,9 @@ static void cppc_scale_freq_workfn(struct kthread_work *work)
|
||||
|
||||
perf = cppc_perf_from_fbctrs(cpu_data, &cppc_fi->prev_perf_fb_ctrs,
|
||||
&fb_ctrs);
|
||||
if (!perf)
|
||||
return;
|
||||
|
||||
cppc_fi->prev_perf_fb_ctrs = fb_ctrs;
|
||||
|
||||
perf <<= SCHED_CAPACITY_SHIFT;
|
||||
@ -420,6 +404,9 @@ static int cppc_get_cpu_power(struct device *cpu_dev,
|
||||
struct cppc_cpudata *cpu_data;
|
||||
|
||||
policy = cpufreq_cpu_get_raw(cpu_dev->id);
|
||||
if (!policy)
|
||||
return -EINVAL;
|
||||
|
||||
cpu_data = policy->driver_data;
|
||||
perf_caps = &cpu_data->perf_caps;
|
||||
max_cap = arch_scale_cpu_capacity(cpu_dev->id);
|
||||
@ -487,6 +474,9 @@ static int cppc_get_cpu_cost(struct device *cpu_dev, unsigned long KHz,
|
||||
int step;
|
||||
|
||||
policy = cpufreq_cpu_get_raw(cpu_dev->id);
|
||||
if (!policy)
|
||||
return -EINVAL;
|
||||
|
||||
cpu_data = policy->driver_data;
|
||||
perf_caps = &cpu_data->perf_caps;
|
||||
max_cap = arch_scale_cpu_capacity(cpu_dev->id);
|
||||
@ -724,13 +714,31 @@ static int cppc_perf_from_fbctrs(struct cppc_cpudata *cpu_data,
|
||||
delta_delivered = get_delta(fb_ctrs_t1->delivered,
|
||||
fb_ctrs_t0->delivered);
|
||||
|
||||
/* Check to avoid divide-by zero and invalid delivered_perf */
|
||||
/*
|
||||
* Avoid divide-by zero and unchanged feedback counters.
|
||||
* Leave it for callers to handle.
|
||||
*/
|
||||
if (!delta_reference || !delta_delivered)
|
||||
return cpu_data->perf_ctrls.desired_perf;
|
||||
return 0;
|
||||
|
||||
return (reference_perf * delta_delivered) / delta_reference;
|
||||
}
|
||||
|
||||
static int cppc_get_perf_ctrs_sample(int cpu,
|
||||
struct cppc_perf_fb_ctrs *fb_ctrs_t0,
|
||||
struct cppc_perf_fb_ctrs *fb_ctrs_t1)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = cppc_get_perf_ctrs(cpu, fb_ctrs_t0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
udelay(2); /* 2usec delay between sampling */
|
||||
|
||||
return cppc_get_perf_ctrs(cpu, fb_ctrs_t1);
|
||||
}
|
||||
|
||||
static unsigned int cppc_cpufreq_get_rate(unsigned int cpu)
|
||||
{
|
||||
struct cppc_perf_fb_ctrs fb_ctrs_t0 = {0}, fb_ctrs_t1 = {0};
|
||||
@ -746,18 +754,32 @@ static unsigned int cppc_cpufreq_get_rate(unsigned int cpu)
|
||||
|
||||
cpufreq_cpu_put(policy);
|
||||
|
||||
ret = cppc_get_perf_ctrs(cpu, &fb_ctrs_t0);
|
||||
if (ret)
|
||||
return 0;
|
||||
|
||||
udelay(2); /* 2usec delay between sampling */
|
||||
|
||||
ret = cppc_get_perf_ctrs(cpu, &fb_ctrs_t1);
|
||||
if (ret)
|
||||
ret = cppc_get_perf_ctrs_sample(cpu, &fb_ctrs_t0, &fb_ctrs_t1);
|
||||
if (ret) {
|
||||
if (ret == -EFAULT)
|
||||
/* Any of the associated CPPC regs is 0. */
|
||||
goto out_invalid_counters;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
delivered_perf = cppc_perf_from_fbctrs(cpu_data, &fb_ctrs_t0,
|
||||
&fb_ctrs_t1);
|
||||
if (!delivered_perf)
|
||||
goto out_invalid_counters;
|
||||
|
||||
return cppc_perf_to_khz(&cpu_data->perf_caps, delivered_perf);
|
||||
|
||||
out_invalid_counters:
|
||||
/*
|
||||
* Feedback counters could be unchanged or 0 when a cpu enters a
|
||||
* low-power idle state, e.g. clock-gated or power-gated.
|
||||
* Use desired perf for reflecting frequency. Get the latest register
|
||||
* value first as some platforms may update the actual delivered perf
|
||||
* there; if failed, resort to the cached desired perf.
|
||||
*/
|
||||
if (cppc_get_desired_perf(cpu, &delivered_perf))
|
||||
delivered_perf = cpu_data->perf_ctrls.desired_perf;
|
||||
|
||||
return cppc_perf_to_khz(&cpu_data->perf_caps, delivered_perf);
|
||||
}
|
||||
@ -812,57 +834,6 @@ static struct cpufreq_driver cppc_cpufreq_driver = {
|
||||
.name = "cppc_cpufreq",
|
||||
};
|
||||
|
||||
/*
|
||||
* HISI platform does not support delivered performance counter and
|
||||
* reference performance counter. It can calculate the performance using the
|
||||
* platform specific mechanism. We reuse the desired performance register to
|
||||
* store the real performance calculated by the platform.
|
||||
*/
|
||||
static unsigned int hisi_cppc_cpufreq_get_rate(unsigned int cpu)
|
||||
{
|
||||
struct cpufreq_policy *policy = cpufreq_cpu_get(cpu);
|
||||
struct cppc_cpudata *cpu_data;
|
||||
u64 desired_perf;
|
||||
int ret;
|
||||
|
||||
if (!policy)
|
||||
return -ENODEV;
|
||||
|
||||
cpu_data = policy->driver_data;
|
||||
|
||||
cpufreq_cpu_put(policy);
|
||||
|
||||
ret = cppc_get_desired_perf(cpu, &desired_perf);
|
||||
if (ret < 0)
|
||||
return -EIO;
|
||||
|
||||
return cppc_perf_to_khz(&cpu_data->perf_caps, desired_perf);
|
||||
}
|
||||
|
||||
static void cppc_check_hisi_workaround(void)
|
||||
{
|
||||
struct acpi_table_header *tbl;
|
||||
acpi_status status = AE_OK;
|
||||
int i;
|
||||
|
||||
status = acpi_get_table(ACPI_SIG_PCCT, 0, &tbl);
|
||||
if (ACPI_FAILURE(status) || !tbl)
|
||||
return;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(wa_info); i++) {
|
||||
if (!memcmp(wa_info[i].oem_id, tbl->oem_id, ACPI_OEM_ID_SIZE) &&
|
||||
!memcmp(wa_info[i].oem_table_id, tbl->oem_table_id, ACPI_OEM_TABLE_ID_SIZE) &&
|
||||
wa_info[i].oem_revision == tbl->oem_revision) {
|
||||
/* Overwrite the get() callback */
|
||||
cppc_cpufreq_driver.get = hisi_cppc_cpufreq_get_rate;
|
||||
fie_disabled = FIE_DISABLED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
acpi_put_table(tbl);
|
||||
}
|
||||
|
||||
static int __init cppc_cpufreq_init(void)
|
||||
{
|
||||
int ret;
|
||||
@ -870,7 +841,6 @@ static int __init cppc_cpufreq_init(void)
|
||||
if (!acpi_cpc_valid())
|
||||
return -ENODEV;
|
||||
|
||||
cppc_check_hisi_workaround();
|
||||
cppc_freq_invariance_init();
|
||||
populate_efficiency_class();
|
||||
|
||||
|
@ -103,6 +103,7 @@ static const struct of_device_id allowlist[] __initconst = {
|
||||
* platforms using "operating-points-v2" property.
|
||||
*/
|
||||
static const struct of_device_id blocklist[] __initconst = {
|
||||
{ .compatible = "allwinner,sun50i-a100" },
|
||||
{ .compatible = "allwinner,sun50i-h6", },
|
||||
{ .compatible = "allwinner,sun50i-h616", },
|
||||
{ .compatible = "allwinner,sun50i-h618", },
|
||||
|
@ -148,7 +148,9 @@ static int __init cpufreq_init(void)
|
||||
|
||||
ret = cpufreq_register_driver(&loongson2_cpufreq_driver);
|
||||
|
||||
if (!ret && !nowait) {
|
||||
if (ret) {
|
||||
platform_driver_unregister(&platform_driver);
|
||||
} else if (!nowait) {
|
||||
saved_cpu_wait = cpu_wait;
|
||||
cpu_wait = loongson2_cpu_wait;
|
||||
}
|
||||
|
@ -346,8 +346,11 @@ static int loongson3_cpufreq_probe(struct platform_device *pdev)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < MAX_PACKAGES; i++)
|
||||
devm_mutex_init(&pdev->dev, &cpufreq_mutex[i]);
|
||||
for (i = 0; i < MAX_PACKAGES; i++) {
|
||||
ret = devm_mutex_init(&pdev->dev, &cpufreq_mutex[i]);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = do_service_request(0, 0, CMD_GET_VERSION, 0, 0);
|
||||
if (ret <= 0)
|
||||
|
@ -62,7 +62,7 @@ mtk_cpufreq_get_cpu_power(struct device *cpu_dev, unsigned long *uW,
|
||||
|
||||
policy = cpufreq_cpu_get_raw(cpu_dev->id);
|
||||
if (!policy)
|
||||
return 0;
|
||||
return -EINVAL;
|
||||
|
||||
data = policy->driver_data;
|
||||
|
||||
|
@ -287,7 +287,7 @@ static int scmi_cpufreq_init(struct cpufreq_policy *policy)
|
||||
ret = cpufreq_enable_boost_support();
|
||||
if (ret) {
|
||||
dev_warn(cpu_dev, "failed to enable boost: %d\n", ret);
|
||||
goto out_free_opp;
|
||||
goto out_free_table;
|
||||
} else {
|
||||
scmi_cpufreq_hw_attr[1] = &cpufreq_freq_attr_scaling_boost_freqs;
|
||||
scmi_cpufreq_driver.boost_enabled = true;
|
||||
@ -296,6 +296,8 @@ static int scmi_cpufreq_init(struct cpufreq_policy *policy)
|
||||
|
||||
return 0;
|
||||
|
||||
out_free_table:
|
||||
dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
|
||||
out_free_opp:
|
||||
dev_pm_opp_remove_all_dynamic(cpu_dev);
|
||||
|
||||
|
@ -22,6 +22,9 @@
|
||||
#define NVMEM_MASK 0x7
|
||||
#define NVMEM_SHIFT 5
|
||||
|
||||
#define SUN50I_A100_NVMEM_MASK 0xf
|
||||
#define SUN50I_A100_NVMEM_SHIFT 12
|
||||
|
||||
static struct platform_device *cpufreq_dt_pdev, *sun50i_cpufreq_pdev;
|
||||
|
||||
struct sunxi_cpufreq_data {
|
||||
@ -45,6 +48,23 @@ static u32 sun50i_h6_efuse_xlate(u32 speedbin)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 sun50i_a100_efuse_xlate(u32 speedbin)
|
||||
{
|
||||
u32 efuse_value;
|
||||
|
||||
efuse_value = (speedbin >> SUN50I_A100_NVMEM_SHIFT) &
|
||||
SUN50I_A100_NVMEM_MASK;
|
||||
|
||||
switch (efuse_value) {
|
||||
case 0b100:
|
||||
return 2;
|
||||
case 0b010:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int get_soc_id_revision(void)
|
||||
{
|
||||
#ifdef CONFIG_HAVE_ARM_SMCCC_DISCOVERY
|
||||
@ -108,6 +128,10 @@ static struct sunxi_cpufreq_data sun50i_h6_cpufreq_data = {
|
||||
.efuse_xlate = sun50i_h6_efuse_xlate,
|
||||
};
|
||||
|
||||
static struct sunxi_cpufreq_data sun50i_a100_cpufreq_data = {
|
||||
.efuse_xlate = sun50i_a100_efuse_xlate,
|
||||
};
|
||||
|
||||
static struct sunxi_cpufreq_data sun50i_h616_cpufreq_data = {
|
||||
.efuse_xlate = sun50i_h616_efuse_xlate,
|
||||
};
|
||||
@ -116,6 +140,9 @@ static const struct of_device_id cpu_opp_match_list[] = {
|
||||
{ .compatible = "allwinner,sun50i-h6-operating-points",
|
||||
.data = &sun50i_h6_cpufreq_data,
|
||||
},
|
||||
{ .compatible = "allwinner,sun50i-a100-operating-points",
|
||||
.data = &sun50i_a100_cpufreq_data,
|
||||
},
|
||||
{ .compatible = "allwinner,sun50i-h616-operating-points",
|
||||
.data = &sun50i_h616_cpufreq_data,
|
||||
},
|
||||
@ -291,6 +318,7 @@ static struct platform_driver sun50i_cpufreq_driver = {
|
||||
|
||||
static const struct of_device_id sun50i_cpufreq_match_list[] = {
|
||||
{ .compatible = "allwinner,sun50i-h6" },
|
||||
{ .compatible = "allwinner,sun50i-a100" },
|
||||
{ .compatible = "allwinner,sun50i-h616" },
|
||||
{ .compatible = "allwinner,sun50i-h618" },
|
||||
{ .compatible = "allwinner,sun50i-h700" },
|
||||
|
@ -93,6 +93,8 @@ struct ti_cpufreq_soc_data {
|
||||
bool multi_regulator;
|
||||
/* Backward compatibility hack: Might have missing syscon */
|
||||
#define TI_QUIRK_SYSCON_MAY_BE_MISSING 0x1
|
||||
/* Backward compatibility hack: new syscon size is 1 register wide */
|
||||
#define TI_QUIRK_SYSCON_IS_SINGLE_REG 0x2
|
||||
u8 quirks;
|
||||
};
|
||||
|
||||
@ -316,8 +318,8 @@ static struct ti_cpufreq_soc_data am625_soc_data = {
|
||||
.efuse_offset = 0x0018,
|
||||
.efuse_mask = 0x07c0,
|
||||
.efuse_shift = 0x6,
|
||||
.rev_offset = 0x0014,
|
||||
.multi_regulator = false,
|
||||
.quirks = TI_QUIRK_SYSCON_IS_SINGLE_REG,
|
||||
};
|
||||
|
||||
static struct ti_cpufreq_soc_data am62a7_soc_data = {
|
||||
@ -325,7 +327,6 @@ static struct ti_cpufreq_soc_data am62a7_soc_data = {
|
||||
.efuse_offset = 0x0,
|
||||
.efuse_mask = 0x07c0,
|
||||
.efuse_shift = 0x6,
|
||||
.rev_offset = 0x0014,
|
||||
.multi_regulator = false,
|
||||
};
|
||||
|
||||
@ -334,7 +335,6 @@ static struct ti_cpufreq_soc_data am62p5_soc_data = {
|
||||
.efuse_offset = 0x0,
|
||||
.efuse_mask = 0x07c0,
|
||||
.efuse_shift = 0x6,
|
||||
.rev_offset = 0x0014,
|
||||
.multi_regulator = false,
|
||||
};
|
||||
|
||||
@ -354,6 +354,10 @@ static int ti_cpufreq_get_efuse(struct ti_cpufreq_data *opp_data,
|
||||
|
||||
ret = regmap_read(opp_data->syscon, opp_data->soc_data->efuse_offset,
|
||||
&efuse);
|
||||
|
||||
if (opp_data->soc_data->quirks & TI_QUIRK_SYSCON_IS_SINGLE_REG && ret == -EIO)
|
||||
ret = regmap_read(opp_data->syscon, 0x0, &efuse);
|
||||
|
||||
if (opp_data->soc_data->quirks & TI_QUIRK_SYSCON_MAY_BE_MISSING && ret == -EIO) {
|
||||
/* not a syscon register! */
|
||||
void __iomem *regs = ioremap(OMAP3_SYSCON_BASE +
|
||||
|
333
drivers/cpufreq/virtual-cpufreq.c
Normal file
333
drivers/cpufreq/virtual-cpufreq.c
Normal file
@ -0,0 +1,333 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2024 Google LLC
|
||||
*/
|
||||
|
||||
#include <linux/arch_topology.h>
|
||||
#include <linux/cpufreq.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/*
|
||||
* CPU0..CPUn
|
||||
* +-------------+-------------------------------+--------+-------+
|
||||
* | Register | Description | Offset | Len |
|
||||
* +-------------+-------------------------------+--------+-------+
|
||||
* | cur_perf | read this register to get | 0x0 | 0x4 |
|
||||
* | | the current perf (integer val | | |
|
||||
* | | representing perf relative to | | |
|
||||
* | | max performance) | | |
|
||||
* | | that vCPU is running at | | |
|
||||
* +-------------+-------------------------------+--------+-------+
|
||||
* | set_perf | write to this register to set | 0x4 | 0x4 |
|
||||
* | | perf value of the vCPU | | |
|
||||
* +-------------+-------------------------------+--------+-------+
|
||||
* | perftbl_len | number of entries in perf | 0x8 | 0x4 |
|
||||
* | | table. A single entry in the | | |
|
||||
* | | perf table denotes no table | | |
|
||||
* | | and the entry contains | | |
|
||||
* | | the maximum perf value | | |
|
||||
* | | that this vCPU supports. | | |
|
||||
* | | The guest can request any | | |
|
||||
* | | value between 1 and max perf | | |
|
||||
* | | when perftbls are not used. | | |
|
||||
* +---------------------------------------------+--------+-------+
|
||||
* | perftbl_sel | write to this register to | 0xc | 0x4 |
|
||||
* | | select perf table entry to | | |
|
||||
* | | read from | | |
|
||||
* +---------------------------------------------+--------+-------+
|
||||
* | perftbl_rd | read this register to get | 0x10 | 0x4 |
|
||||
* | | perf value of the selected | | |
|
||||
* | | entry based on perftbl_sel | | |
|
||||
* +---------------------------------------------+--------+-------+
|
||||
* | perf_domain | performance domain number | 0x14 | 0x4 |
|
||||
* | | that this vCPU belongs to. | | |
|
||||
* | | vCPUs sharing the same perf | | |
|
||||
* | | domain number are part of the | | |
|
||||
* | | same performance domain. | | |
|
||||
* +-------------+-------------------------------+--------+-------+
|
||||
*/
|
||||
|
||||
#define REG_CUR_PERF_STATE_OFFSET 0x0
|
||||
#define REG_SET_PERF_STATE_OFFSET 0x4
|
||||
#define REG_PERFTBL_LEN_OFFSET 0x8
|
||||
#define REG_PERFTBL_SEL_OFFSET 0xc
|
||||
#define REG_PERFTBL_RD_OFFSET 0x10
|
||||
#define REG_PERF_DOMAIN_OFFSET 0x14
|
||||
#define PER_CPU_OFFSET 0x1000
|
||||
|
||||
#define PERFTBL_MAX_ENTRIES 64U
|
||||
|
||||
static void __iomem *base;
|
||||
static DEFINE_PER_CPU(u32, perftbl_num_entries);
|
||||
|
||||
static void virt_scale_freq_tick(void)
|
||||
{
|
||||
int cpu = smp_processor_id();
|
||||
u32 max_freq = (u32)cpufreq_get_hw_max_freq(cpu);
|
||||
u64 cur_freq;
|
||||
unsigned long scale;
|
||||
|
||||
cur_freq = (u64)readl_relaxed(base + cpu * PER_CPU_OFFSET
|
||||
+ REG_CUR_PERF_STATE_OFFSET);
|
||||
|
||||
cur_freq <<= SCHED_CAPACITY_SHIFT;
|
||||
scale = (unsigned long)div_u64(cur_freq, max_freq);
|
||||
scale = min(scale, SCHED_CAPACITY_SCALE);
|
||||
|
||||
this_cpu_write(arch_freq_scale, scale);
|
||||
}
|
||||
|
||||
static struct scale_freq_data virt_sfd = {
|
||||
.source = SCALE_FREQ_SOURCE_VIRT,
|
||||
.set_freq_scale = virt_scale_freq_tick,
|
||||
};
|
||||
|
||||
static unsigned int virt_cpufreq_set_perf(struct cpufreq_policy *policy,
|
||||
unsigned int target_freq)
|
||||
{
|
||||
writel_relaxed(target_freq,
|
||||
base + policy->cpu * PER_CPU_OFFSET + REG_SET_PERF_STATE_OFFSET);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int virt_cpufreq_fast_switch(struct cpufreq_policy *policy,
|
||||
unsigned int target_freq)
|
||||
{
|
||||
virt_cpufreq_set_perf(policy, target_freq);
|
||||
return target_freq;
|
||||
}
|
||||
|
||||
static u32 virt_cpufreq_get_perftbl_entry(int cpu, u32 idx)
|
||||
{
|
||||
writel_relaxed(idx, base + cpu * PER_CPU_OFFSET +
|
||||
REG_PERFTBL_SEL_OFFSET);
|
||||
return readl_relaxed(base + cpu * PER_CPU_OFFSET +
|
||||
REG_PERFTBL_RD_OFFSET);
|
||||
}
|
||||
|
||||
static int virt_cpufreq_target(struct cpufreq_policy *policy,
|
||||
unsigned int target_freq,
|
||||
unsigned int relation)
|
||||
{
|
||||
struct cpufreq_freqs freqs;
|
||||
int ret = 0;
|
||||
|
||||
freqs.old = policy->cur;
|
||||
freqs.new = target_freq;
|
||||
|
||||
cpufreq_freq_transition_begin(policy, &freqs);
|
||||
ret = virt_cpufreq_set_perf(policy, target_freq);
|
||||
cpufreq_freq_transition_end(policy, &freqs, ret != 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int virt_cpufreq_get_sharing_cpus(struct cpufreq_policy *policy)
|
||||
{
|
||||
u32 cur_perf_domain, perf_domain;
|
||||
struct device *cpu_dev;
|
||||
int cpu;
|
||||
|
||||
cur_perf_domain = readl_relaxed(base + policy->cpu *
|
||||
PER_CPU_OFFSET + REG_PERF_DOMAIN_OFFSET);
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
cpu_dev = get_cpu_device(cpu);
|
||||
if (!cpu_dev)
|
||||
continue;
|
||||
|
||||
perf_domain = readl_relaxed(base + cpu *
|
||||
PER_CPU_OFFSET + REG_PERF_DOMAIN_OFFSET);
|
||||
|
||||
if (perf_domain == cur_perf_domain)
|
||||
cpumask_set_cpu(cpu, policy->cpus);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int virt_cpufreq_get_freq_info(struct cpufreq_policy *policy)
|
||||
{
|
||||
struct cpufreq_frequency_table *table;
|
||||
u32 num_perftbl_entries, idx;
|
||||
|
||||
num_perftbl_entries = per_cpu(perftbl_num_entries, policy->cpu);
|
||||
|
||||
if (num_perftbl_entries == 1) {
|
||||
policy->cpuinfo.min_freq = 1;
|
||||
policy->cpuinfo.max_freq = virt_cpufreq_get_perftbl_entry(policy->cpu, 0);
|
||||
|
||||
policy->min = policy->cpuinfo.min_freq;
|
||||
policy->max = policy->cpuinfo.max_freq;
|
||||
|
||||
policy->cur = policy->max;
|
||||
return 0;
|
||||
}
|
||||
|
||||
table = kcalloc(num_perftbl_entries + 1, sizeof(*table), GFP_KERNEL);
|
||||
if (!table)
|
||||
return -ENOMEM;
|
||||
|
||||
for (idx = 0; idx < num_perftbl_entries; idx++)
|
||||
table[idx].frequency = virt_cpufreq_get_perftbl_entry(policy->cpu, idx);
|
||||
|
||||
table[idx].frequency = CPUFREQ_TABLE_END;
|
||||
policy->freq_table = table;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int virt_cpufreq_cpu_init(struct cpufreq_policy *policy)
|
||||
{
|
||||
struct device *cpu_dev;
|
||||
int ret;
|
||||
|
||||
cpu_dev = get_cpu_device(policy->cpu);
|
||||
if (!cpu_dev)
|
||||
return -ENODEV;
|
||||
|
||||
ret = virt_cpufreq_get_freq_info(policy);
|
||||
if (ret) {
|
||||
dev_warn(cpu_dev, "failed to get cpufreq info\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = virt_cpufreq_get_sharing_cpus(policy);
|
||||
if (ret) {
|
||||
dev_warn(cpu_dev, "failed to get sharing cpumask\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* To simplify and improve latency of handling frequency requests on
|
||||
* the host side, this ensures that the vCPU thread triggering the MMIO
|
||||
* abort is the same thread whose performance constraints (Ex. uclamp
|
||||
* settings) need to be updated. This simplifies the VMM (Virtual
|
||||
* Machine Manager) having to find the correct vCPU thread and/or
|
||||
* facing permission issues when configuring other threads.
|
||||
*/
|
||||
policy->dvfs_possible_from_any_cpu = false;
|
||||
policy->fast_switch_possible = true;
|
||||
|
||||
/*
|
||||
* Using the default SCALE_FREQ_SOURCE_CPUFREQ is insufficient since
|
||||
* the actual physical CPU frequency may not match requested frequency
|
||||
* from the vCPU thread due to frequency update latencies or other
|
||||
* inputs to the physical CPU frequency selection. This additional FIE
|
||||
* source allows for more accurate freq_scale updates and only takes
|
||||
* effect if another FIE source such as AMUs have not been registered.
|
||||
*/
|
||||
topology_set_scale_freq_source(&virt_sfd, policy->cpus);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void virt_cpufreq_cpu_exit(struct cpufreq_policy *policy)
|
||||
{
|
||||
topology_clear_scale_freq_source(SCALE_FREQ_SOURCE_VIRT, policy->related_cpus);
|
||||
kfree(policy->freq_table);
|
||||
}
|
||||
|
||||
static int virt_cpufreq_online(struct cpufreq_policy *policy)
|
||||
{
|
||||
/* Nothing to restore. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int virt_cpufreq_offline(struct cpufreq_policy *policy)
|
||||
{
|
||||
/* Dummy offline() to avoid exit() being called and freeing resources. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int virt_cpufreq_verify_policy(struct cpufreq_policy_data *policy)
|
||||
{
|
||||
if (policy->freq_table)
|
||||
return cpufreq_frequency_table_verify(policy, policy->freq_table);
|
||||
|
||||
cpufreq_verify_within_cpu_limits(policy);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct cpufreq_driver cpufreq_virt_driver = {
|
||||
.name = "virt-cpufreq",
|
||||
.init = virt_cpufreq_cpu_init,
|
||||
.exit = virt_cpufreq_cpu_exit,
|
||||
.online = virt_cpufreq_online,
|
||||
.offline = virt_cpufreq_offline,
|
||||
.verify = virt_cpufreq_verify_policy,
|
||||
.target = virt_cpufreq_target,
|
||||
.fast_switch = virt_cpufreq_fast_switch,
|
||||
.attr = cpufreq_generic_attr,
|
||||
};
|
||||
|
||||
static int virt_cpufreq_driver_probe(struct platform_device *pdev)
|
||||
{
|
||||
u32 num_perftbl_entries;
|
||||
int ret, cpu;
|
||||
|
||||
base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
num_perftbl_entries = readl_relaxed(base + cpu * PER_CPU_OFFSET +
|
||||
REG_PERFTBL_LEN_OFFSET);
|
||||
|
||||
if (!num_perftbl_entries || num_perftbl_entries > PERFTBL_MAX_ENTRIES)
|
||||
return -ENODEV;
|
||||
|
||||
per_cpu(perftbl_num_entries, cpu) = num_perftbl_entries;
|
||||
}
|
||||
|
||||
ret = cpufreq_register_driver(&cpufreq_virt_driver);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Virtual CPUFreq driver failed to register: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_dbg(&pdev->dev, "Virtual CPUFreq driver initialized\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void virt_cpufreq_driver_remove(struct platform_device *pdev)
|
||||
{
|
||||
cpufreq_unregister_driver(&cpufreq_virt_driver);
|
||||
}
|
||||
|
||||
static const struct of_device_id virt_cpufreq_match[] = {
|
||||
{ .compatible = "qemu,virtual-cpufreq", .data = NULL},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, virt_cpufreq_match);
|
||||
|
||||
static struct platform_driver virt_cpufreq_driver = {
|
||||
.probe = virt_cpufreq_driver_probe,
|
||||
.remove = virt_cpufreq_driver_remove,
|
||||
.driver = {
|
||||
.name = "virt-cpufreq",
|
||||
.of_match_table = virt_cpufreq_match,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init virt_cpufreq_init(void)
|
||||
{
|
||||
return platform_driver_register(&virt_cpufreq_driver);
|
||||
}
|
||||
postcore_initcall(virt_cpufreq_init);
|
||||
|
||||
static void __exit virt_cpufreq_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&virt_cpufreq_driver);
|
||||
}
|
||||
module_exit(virt_cpufreq_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Virtual cpufreq driver");
|
||||
MODULE_LICENSE("GPL");
|
@ -45,6 +45,7 @@ enum scale_freq_source {
|
||||
SCALE_FREQ_SOURCE_CPUFREQ = 0,
|
||||
SCALE_FREQ_SOURCE_ARCH,
|
||||
SCALE_FREQ_SOURCE_CPPC,
|
||||
SCALE_FREQ_SOURCE_VIRT,
|
||||
};
|
||||
|
||||
struct scale_freq_data {
|
||||
|
Loading…
Reference in New Issue
Block a user