diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index c999c4a1e5675..98b5a50d31896 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig @@ -56,6 +56,14 @@ config DEVFREQ_GOV_POWERSAVE the DEVFREQ framework returns the lowest frequency available at any time. +config DEVFREQ_GOV_REMOTE + tristate "Remote" + help + A simple governor to track the frequency of devices whose + dvfs control lies outside the kernel. This governor acts + as an observer and provides for ways to track frequency and + set/get information related to the remote dvfs device. + config DEVFREQ_GOV_USERSPACE tristate "Userspace" help @@ -161,6 +169,19 @@ config ARM_SUN8I_A33_MBUS_DEVFREQ This adds the DEVFREQ driver for the MBUS controller in some Allwinner sun8i (A33 through H3) and sun50i (A64 and H5) SoCs. +config SCMI_QCOM_MEMLAT_DEVFREQ + tristate "Qualcomm Technologies Inc. SCMI client driver" + depends on QCOM_SCMI_GENERIC_EXT || COMPILE_TEST + select DEVFREQ_GOV_REMOTE + help + This driver uses the MEMLAT (memory latency) algorithm string + hosted on QCOM SCMI Vendor Protocol to detect memory latency + workloads and control frequency/level of the various memory + buses (DDR/LLCC/DDR_QOS). + + This driver defines/documents the parameter IDs used while configuring + the memory buses. + source "drivers/devfreq/event/Kconfig" endif # PM_DEVFREQ diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile index 404179d79a9db..b11f94e2f4855 100644 --- a/drivers/devfreq/Makefile +++ b/drivers/devfreq/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_PM_DEVFREQ_EVENT) += devfreq-event.o obj-$(CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND) += governor_simpleondemand.o obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o +obj-$(CONFIG_DEVFREQ_GOV_REMOTE) += governor_remote.o obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o obj-$(CONFIG_DEVFREQ_GOV_PASSIVE) += governor_passive.o @@ -16,6 +17,7 @@ obj-$(CONFIG_ARM_MEDIATEK_CCI_DEVFREQ) += mtk-cci-devfreq.o obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ) += rk3399_dmc.o obj-$(CONFIG_ARM_SUN8I_A33_MBUS_DEVFREQ) += sun8i-a33-mbus.o obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra30-devfreq.o +obj-$(CONFIG_SCMI_QCOM_MEMLAT_DEVFREQ) += scmi-qcom-memlat-devfreq.o # DEVFREQ Event Drivers obj-$(CONFIG_PM_DEVFREQ_EVENT) += event/ diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index 82dd9a43dc621..f52b3166e9a6c 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -348,6 +348,10 @@ static int devfreq_set_target(struct devfreq *devfreq, unsigned long new_freq, unsigned long cur_freq; int err = 0; + /* Frequency already updated, update trans-stat info */ + if (IS_SUPPORTED_FLAG(devfreq->governor->flags, TRACK_REMOTE)) + goto update_status; + if (devfreq->profile->get_cur_freq) devfreq->profile->get_cur_freq(devfreq->dev.parent, &cur_freq); else @@ -375,6 +379,7 @@ static int devfreq_set_target(struct devfreq *devfreq, unsigned long new_freq, freqs.new = new_freq; devfreq_notify_transition(devfreq, &freqs, DEVFREQ_POSTCHANGE); +update_status: if (devfreq_update_status(devfreq, new_freq)) dev_warn(&devfreq->dev, "Couldn't update frequency transition information.\n"); @@ -1562,6 +1567,10 @@ static ssize_t target_freq_show(struct device *dev, { struct devfreq *df = to_devfreq(dev); + if (!df->profile || !df->governor || + !IS_SUPPORTED_ATTR(df->governor->attrs, TARGET_FREQ)) + return -EINVAL; + return sprintf(buf, "%lu\n", df->previous_freq); } static DEVICE_ATTR_RO(target_freq); diff --git a/drivers/devfreq/governor_passive.c b/drivers/devfreq/governor_passive.c index d7feecd900f1d..b75e4bbee4b13 100644 --- a/drivers/devfreq/governor_passive.c +++ b/drivers/devfreq/governor_passive.c @@ -448,6 +448,7 @@ static int devfreq_passive_event_handler(struct devfreq *devfreq, static struct devfreq_governor devfreq_passive = { .name = DEVFREQ_GOV_PASSIVE, + .attrs = DEVFREQ_GOV_ATTR_TARGET_FREQ, .flags = DEVFREQ_GOV_FLAG_IMMUTABLE, .get_target_freq = devfreq_passive_get_target_freq, .event_handler = devfreq_passive_event_handler, diff --git a/drivers/devfreq/governor_performance.c b/drivers/devfreq/governor_performance.c index fdb22bf512cf1..b9ec587f582cc 100644 --- a/drivers/devfreq/governor_performance.c +++ b/drivers/devfreq/governor_performance.c @@ -37,6 +37,7 @@ static int devfreq_performance_handler(struct devfreq *devfreq, static struct devfreq_governor devfreq_performance = { .name = DEVFREQ_GOV_PERFORMANCE, + .attrs = DEVFREQ_GOV_ATTR_TARGET_FREQ, .get_target_freq = devfreq_performance_func, .event_handler = devfreq_performance_handler, }; diff --git a/drivers/devfreq/governor_powersave.c b/drivers/devfreq/governor_powersave.c index ee2d6ec8a5122..69eab1d0a7fcc 100644 --- a/drivers/devfreq/governor_powersave.c +++ b/drivers/devfreq/governor_powersave.c @@ -37,6 +37,7 @@ static int devfreq_powersave_handler(struct devfreq *devfreq, static struct devfreq_governor devfreq_powersave = { .name = DEVFREQ_GOV_POWERSAVE, + .attrs = DEVFREQ_GOV_ATTR_TARGET_FREQ, .get_target_freq = devfreq_powersave_func, .event_handler = devfreq_powersave_handler, }; diff --git a/drivers/devfreq/governor_remote.c b/drivers/devfreq/governor_remote.c new file mode 100644 index 0000000000000..6bff3cdaf1e50 --- /dev/null +++ b/drivers/devfreq/governor_remote.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include + +static int devfreq_remote_track_func(struct devfreq *devfreq, unsigned long *freq) +{ + unsigned long cur_freq = 0; + + if (devfreq->profile->get_cur_freq) + devfreq->profile->get_cur_freq(devfreq->dev.parent, &cur_freq); + + *freq = cur_freq; + + return 0; +} + +static int devfreq_remote_track_handler(struct devfreq *devfreq, unsigned int event, void *data) +{ + switch (event) { + case DEVFREQ_GOV_START: + devfreq_monitor_start(devfreq); + break; + + case DEVFREQ_GOV_STOP: + devfreq_monitor_stop(devfreq); + break; + + case DEVFREQ_GOV_UPDATE_INTERVAL: + devfreq_update_interval(devfreq, (unsigned int *)data); + break; + + case DEVFREQ_GOV_SUSPEND: + devfreq_monitor_suspend(devfreq); + break; + + case DEVFREQ_GOV_RESUME: + devfreq_monitor_resume(devfreq); + break; + + default: + break; + } + + return 0; +} + +static struct devfreq_governor devfreq_remote_track = { + .name = DEVFREQ_GOV_REMOTE, + .attrs = DEVFREQ_GOV_ATTR_POLLING_INTERVAL + | DEVFREQ_GOV_ATTR_TIMER, + .flags = DEVFREQ_GOV_FLAG_IMMUTABLE + | DEVFREQ_GOV_FLAG_TRACK_REMOTE, + .get_target_freq = devfreq_remote_track_func, + .event_handler = devfreq_remote_track_handler, +}; + +static int __init devfreq_remote_track_init(void) +{ + return devfreq_add_governor(&devfreq_remote_track); +} +subsys_initcall(devfreq_remote_track_init); + +static void __exit devfreq_remote_track_exit(void) +{ + int ret; + + ret = devfreq_remove_governor(&devfreq_remote_track); + if (ret) + pr_err("%s: failed remove governor %d\n", __func__, ret); +} +module_exit(devfreq_remote_track_exit); + +MODULE_DESCRIPTION("DEVFREQ Remote Tracking governor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/devfreq/governor_simpleondemand.c b/drivers/devfreq/governor_simpleondemand.c index ac9c5e9e51a49..65ff9d912ef9c 100644 --- a/drivers/devfreq/governor_simpleondemand.c +++ b/drivers/devfreq/governor_simpleondemand.c @@ -118,6 +118,7 @@ static int devfreq_simple_ondemand_handler(struct devfreq *devfreq, static struct devfreq_governor devfreq_simple_ondemand = { .name = DEVFREQ_GOV_SIMPLE_ONDEMAND, .attrs = DEVFREQ_GOV_ATTR_POLLING_INTERVAL + | DEVFREQ_GOV_ATTR_TARGET_FREQ | DEVFREQ_GOV_ATTR_TIMER, .get_target_freq = devfreq_simple_ondemand_func, .event_handler = devfreq_simple_ondemand_handler, diff --git a/drivers/devfreq/governor_userspace.c b/drivers/devfreq/governor_userspace.c index 3906ebedbae84..d1b765a7b8e56 100644 --- a/drivers/devfreq/governor_userspace.c +++ b/drivers/devfreq/governor_userspace.c @@ -135,6 +135,7 @@ static int devfreq_userspace_handler(struct devfreq *devfreq, static struct devfreq_governor devfreq_userspace = { .name = DEVFREQ_GOV_USERSPACE, + .attrs = DEVFREQ_GOV_ATTR_TARGET_FREQ, .get_target_freq = devfreq_userspace_func, .event_handler = devfreq_userspace_handler, }; diff --git a/drivers/devfreq/hisi_uncore_freq.c b/drivers/devfreq/hisi_uncore_freq.c index 4d00d813c8ac6..0800116e33343 100644 --- a/drivers/devfreq/hisi_uncore_freq.c +++ b/drivers/devfreq/hisi_uncore_freq.c @@ -399,6 +399,7 @@ static struct devfreq_governor hisi_platform_governor = { * Set interrupt_driven to skip the devfreq monitor mechanism, though * this governor is not interrupt-driven. */ + .attrs = DEVFREQ_GOV_ATTR_TARGET_FREQ, .flags = DEVFREQ_GOV_FLAG_IRQ_DRIVEN, .get_target_freq = hisi_platform_gov_func, .event_handler = hisi_platform_gov_handler, diff --git a/drivers/devfreq/scmi-qcom-memlat-cfg.h b/drivers/devfreq/scmi-qcom-memlat-cfg.h new file mode 100644 index 0000000000000..e56899489db45 --- /dev/null +++ b/drivers/devfreq/scmi-qcom-memlat-cfg.h @@ -0,0 +1,473 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __DRIVERS_DEVFREQ_SCMI_QCOM_MEMLAT_CONFIG_H__ +#define __DRIVERS_DEVFREQ_SCMI_QCOM_MEMLAT_CONFIG_H__ + +/* + * Memlat Effective Frequency Calculation Method + * CPUCP_EFFECTIVE_FREQ_METHOD_0 - Uses CPU Cycles and CONST Cycles to calculate + * CPUCP_EFFECTIVE_FREQ_METHOD_1 - Uses CPU Cycles and time period + */ +#define CPUCP_EFFECTIVE_FREQ_CALC_METHOD_0 0 +#define CPUCP_EFFECTIVE_FREQ_CALC_METHOD_1 1 + +#define EV_CPU_CYCLES 0 +#define EV_CNT_CYCLES 1 +#define EV_INST_RETIRED 2 +#define EV_STALL_BACKEND_MEM 3 +#define EV_L2_D_RFILL 5 +#define INVALID_IDX 0xff + +#define MEMLAT_ALGO_STR 0x4D454D4C4154 /* MEMLAT */ + +struct scmi_qcom_map_table { + unsigned int cpu_freq; + unsigned int mem_freq; +}; + +struct scmi_qcom_opp_data { + unsigned long freq; + unsigned int level; +}; + +struct scmi_qcom_memory_range { + unsigned int min_freq; + unsigned int max_freq; +}; + +struct scmi_qcom_monitor_cfg { + const struct scmi_qcom_map_table *table; + const char *name; + u32 be_stall_floor; + u32 cpu_mask; + u32 ipm_ceil; + int table_len; +}; + +struct scmi_qcom_memory_cfg { + const struct scmi_qcom_monitor_cfg *monitor_cfg; + const struct scmi_qcom_opp_data *mem_table; + struct scmi_qcom_memory_range memory_range; + const char *name; + u32 memory_type; + int monitor_cnt; + int num_opps; +}; + +struct scmi_qcom_memlat_cfg_data { + const struct scmi_qcom_memory_cfg *memory_cfg; + u32 cpucp_freq_method; + u32 cpucp_sample_ms; + int memory_cnt; +}; + +static const struct scmi_qcom_opp_data glymur_llcc_table[] = { + { .freq = 315000000 }, + { .freq = 479000000 }, + { .freq = 545000000 }, + { .freq = 725000000 }, + { .freq = 840000000 }, + { .freq = 959000000 }, + { .freq = 1090000000 }, + { .freq = 1211000000 }, +}; + +static const struct scmi_qcom_opp_data hamoa_llcc_table[] = { + { .freq = 300000000 }, + { .freq = 466000000 }, + { .freq = 600000000 }, + { .freq = 806000000 }, + { .freq = 933000000 }, + { .freq = 1066000000 }, +}; + +static const struct scmi_qcom_opp_data glymur_ddr_table[] = { + { .freq = 200000000 }, + { .freq = 547000000 }, + { .freq = 1353000000 }, + { .freq = 1555000000 }, + { .freq = 1708000000 }, + { .freq = 2092000000 }, + { .freq = 2736000000 }, + { .freq = 3187000000 }, + { .freq = 3686000000 }, + { .freq = 4224000000 }, + { .freq = 4761000000 }, +}; + +static const struct scmi_qcom_opp_data hamoa_ddr_table[] = { + { .freq = 200000000 }, + { .freq = 547000000 }, + { .freq = 768000000 }, + { .freq = 1555000000 }, + { .freq = 1708000000 }, + { .freq = 2092000000 }, + { .freq = 2736000000 }, + { .freq = 3187000000 }, + { .freq = 3686000000 }, + { .freq = 4224000000 }, +}; + +static const struct scmi_qcom_opp_data glymur_ddr_qos_table[] = { + { .freq = 1, .level = 0 }, + { .freq = 100, .level = 1 }, +}; + +static const struct scmi_qcom_memory_cfg glymur_memory_cfg[] = { + { + .memory_type = 0, + .name = "ddr", + .mem_table = glymur_ddr_table, + .num_opps = ARRAY_SIZE(glymur_ddr_table), + .monitor_cnt = 4, + .memory_range = { .min_freq = 547000, .max_freq = 4761000}, + .monitor_cfg = (const struct scmi_qcom_monitor_cfg[]) { + { + .name = "mon_0", + .cpu_mask = 0x3f, + .ipm_ceil = 60000000, + .be_stall_floor = 1, + .table_len = 8, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 960, .mem_freq = 547000 }, + { .cpu_freq = 1133, .mem_freq = 1353000 }, + { .cpu_freq = 1594, .mem_freq = 1555000 }, + { .cpu_freq = 1920, .mem_freq = 1708000 }, + { .cpu_freq = 2228, .mem_freq = 2736000 }, + { .cpu_freq = 2362, .mem_freq = 3187000 }, + { .cpu_freq = 2650, .mem_freq = 3686000 }, + { .cpu_freq = 2938, .mem_freq = 4761000 }, + } + }, + { + .name = "mon_1", + .cpu_mask = 0xfc0, + .ipm_ceil = 60000000, + .be_stall_floor = 1, + .table_len = 8, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 356, .mem_freq = 547000 }, + { .cpu_freq = 1018, .mem_freq = 1353000 }, + { .cpu_freq = 1536, .mem_freq = 1555000 }, + { .cpu_freq = 1748, .mem_freq = 1708800 }, + { .cpu_freq = 2324, .mem_freq = 2736000 }, + { .cpu_freq = 2496, .mem_freq = 3187000 }, + { .cpu_freq = 2900, .mem_freq = 3686000 }, + { .cpu_freq = 3514, .mem_freq = 4761000 }, + } + }, + { + .name = "mon_2", + .cpu_mask = 0x3f000, + .ipm_ceil = 60000000, + .be_stall_floor = 1, + .table_len = 8, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 356, .mem_freq = 547000 }, + { .cpu_freq = 1018, .mem_freq = 1353000 }, + { .cpu_freq = 1536, .mem_freq = 1555000 }, + { .cpu_freq = 1748, .mem_freq = 1708800 }, + { .cpu_freq = 2324, .mem_freq = 2736000 }, + { .cpu_freq = 2496, .mem_freq = 3187000 }, + { .cpu_freq = 2900, .mem_freq = 3686000 }, + { .cpu_freq = 3514, .mem_freq = 4761000 }, + } + }, + { + .name = "mon_3", + .cpu_mask = 0x3ffff, + .table_len = 4, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 2823, .mem_freq = 547000 }, + { .cpu_freq = 3034, .mem_freq = 1555000 }, + { .cpu_freq = 3226, .mem_freq = 1708000 }, + { .cpu_freq = 5012, .mem_freq = 2092000 }, + } + }, + }, + }, + { + .memory_type = 1, + .name = "llcc", + .mem_table = glymur_llcc_table, + .num_opps = ARRAY_SIZE(glymur_llcc_table), + .monitor_cnt = 3, + .memory_range = { .min_freq = 315000, .max_freq = 1211000}, + .monitor_cfg = (const struct scmi_qcom_monitor_cfg[]) { + { + .name = "mon_0", + .cpu_mask = 0x3f, + .ipm_ceil = 60000000, + .be_stall_floor = 1, + .table_len = 7, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 960, .mem_freq = 315000 }, + { .cpu_freq = 1113, .mem_freq = 479000 }, + { .cpu_freq = 1594, .mem_freq = 545000 }, + { .cpu_freq = 1920, .mem_freq = 725000 }, + { .cpu_freq = 2362, .mem_freq = 840000 }, + { .cpu_freq = 2650, .mem_freq = 959000 }, + { .cpu_freq = 2938, .mem_freq = 1211000 }, + } + }, + { + .name = "mon_1", + .cpu_mask = 0xfc0, + .ipm_ceil = 60000000, + .be_stall_floor = 1, + .table_len = 7, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 356, .mem_freq = 315000 }, + { .cpu_freq = 1018, .mem_freq = 479000 }, + { .cpu_freq = 1536, .mem_freq = 545000 }, + { .cpu_freq = 1748, .mem_freq = 725000 }, + { .cpu_freq = 2496, .mem_freq = 840000 }, + { .cpu_freq = 2900, .mem_freq = 959000 }, + { .cpu_freq = 3514, .mem_freq = 1211000 }, + } + }, + { + .name = "mon_2", + .cpu_mask = 0x3f000, + .ipm_ceil = 60000000, + .be_stall_floor = 1, + .table_len = 7, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 356, .mem_freq = 315000 }, + { .cpu_freq = 1018, .mem_freq = 479000 }, + { .cpu_freq = 1536, .mem_freq = 545000 }, + { .cpu_freq = 1748, .mem_freq = 725000 }, + { .cpu_freq = 2496, .mem_freq = 840000 }, + { .cpu_freq = 2900, .mem_freq = 959000 }, + { .cpu_freq = 3514, .mem_freq = 1211000 }, + } + }, + }, + }, + { + .memory_type = 2, + .name = "ddr-qos", + .monitor_cnt = 3, + .mem_table = glymur_ddr_qos_table, + .num_opps = ARRAY_SIZE(glymur_ddr_qos_table), + .memory_range = { .min_freq = 0, .max_freq = 1}, + .monitor_cfg = (const struct scmi_qcom_monitor_cfg[]) { + { + .name = "mon_0", + .cpu_mask = 0x3f, + .ipm_ceil = 80000000, + .be_stall_floor = 1, + .table_len = 2, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 2362, .mem_freq = 0 }, + { .cpu_freq = 2938, .mem_freq = 1 }, + } + }, + { + .name = "mon_1", + .cpu_mask = 0xfc0, + .ipm_ceil = 80000000, + .be_stall_floor = 1, + .table_len = 2, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 2496, .mem_freq = 0 }, + { .cpu_freq = 3514, .mem_freq = 1 }, + } + }, + { + .name = "mon_2", + .cpu_mask = 0x3f000, + .ipm_ceil = 80000000, + .be_stall_floor = 1, + .table_len = 2, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 2496, .mem_freq = 0 }, + { .cpu_freq = 3514, .mem_freq = 1 }, + } + }, + }, + }, +}; + +static const struct scmi_qcom_memory_cfg hamoa_memory_cfg[] = { + { + .memory_type = 0, + .name = "ddr", + .mem_table = hamoa_ddr_table, + .num_opps = ARRAY_SIZE(hamoa_ddr_table), + .monitor_cnt = 4, + .memory_range = { .min_freq = 200000, .max_freq = 4224000}, + .monitor_cfg = (const struct scmi_qcom_monitor_cfg[]) { + { + .name = "mon_0", + .cpu_mask = 0xf, + .ipm_ceil = 20000000, + .be_stall_floor = 1, + .table_len = 6, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 999, .mem_freq = 547000 }, + { .cpu_freq = 1440, .mem_freq = 768000 }, + { .cpu_freq = 1671, .mem_freq = 1555000 }, + { .cpu_freq = 2189, .mem_freq = 2092000 }, + { .cpu_freq = 2516, .mem_freq = 3187000 }, + { .cpu_freq = 3860, .mem_freq = 4224000 }, + } + }, + { + .name = "mon_1", + .cpu_mask = 0xf0, + .ipm_ceil = 20000000, + .be_stall_floor = 1, + .table_len = 6, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 999, .mem_freq = 547000 }, + { .cpu_freq = 1440, .mem_freq = 768000 }, + { .cpu_freq = 1671, .mem_freq = 1555000 }, + { .cpu_freq = 2189, .mem_freq = 2092000 }, + { .cpu_freq = 2516, .mem_freq = 3187000 }, + { .cpu_freq = 3860, .mem_freq = 4224000 }, + } + }, + { + .name = "mon_2", + .cpu_mask = 0xf00, + .ipm_ceil = 20000000, + .be_stall_floor = 1, + .table_len = 6, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 999, .mem_freq = 547000 }, + { .cpu_freq = 1440, .mem_freq = 768000 }, + { .cpu_freq = 1671, .mem_freq = 1555000 }, + { .cpu_freq = 2189, .mem_freq = 2092000 }, + { .cpu_freq = 2516, .mem_freq = 3187000 }, + { .cpu_freq = 3860, .mem_freq = 4224000 }, + } + }, + { + .name = "mon_3", + .cpu_mask = 0xfff, + .table_len = 4, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 1440, .mem_freq = 547000 }, + { .cpu_freq = 2189, .mem_freq = 768000 }, + { .cpu_freq = 2516, .mem_freq = 1555000 }, + { .cpu_freq = 3860, .mem_freq = 2092000 }, + } + }, + }, + }, + { + .memory_type = 1, + .name = "llcc", + .mem_table = hamoa_llcc_table, + .num_opps = ARRAY_SIZE(hamoa_llcc_table), + .monitor_cnt = 3, + .memory_range = { .min_freq = 300000, .max_freq = 1067000}, + .monitor_cfg = (const struct scmi_qcom_monitor_cfg[]) { + { + .name = "mon_0", + .cpu_mask = 0xf, + .ipm_ceil = 20000000, + .be_stall_floor = 1, + .table_len = 6, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 999, .mem_freq = 300000 }, + { .cpu_freq = 1440, .mem_freq = 466000 }, + { .cpu_freq = 1671, .mem_freq = 600000 }, + { .cpu_freq = 2189, .mem_freq = 806000 }, + { .cpu_freq = 2516, .mem_freq = 933000 }, + { .cpu_freq = 3860, .mem_freq = 1066000 }, + } + }, + { + .name = "mon_1", + .cpu_mask = 0xf0, + .ipm_ceil = 20000000, + .be_stall_floor = 1, + .table_len = 6, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 999, .mem_freq = 300000 }, + { .cpu_freq = 1440, .mem_freq = 466000 }, + { .cpu_freq = 1671, .mem_freq = 600000 }, + { .cpu_freq = 2189, .mem_freq = 806000 }, + { .cpu_freq = 2516, .mem_freq = 933000 }, + { .cpu_freq = 3860, .mem_freq = 1066000 }, + } + }, + { + .name = "mon_2", + .cpu_mask = 0xf00, + .ipm_ceil = 20000000, + .be_stall_floor = 1, + .table_len = 6, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 999, .mem_freq = 300000 }, + { .cpu_freq = 1440, .mem_freq = 466000 }, + { .cpu_freq = 1671, .mem_freq = 600000 }, + { .cpu_freq = 2189, .mem_freq = 806000 }, + { .cpu_freq = 2516, .mem_freq = 933000 }, + { .cpu_freq = 3860, .mem_freq = 1066000 }, + } + }, + }, + }, + { + .memory_type = 2, + .name = "ddr-qos", + .monitor_cnt = 3, + .mem_table = glymur_ddr_qos_table, + .num_opps = ARRAY_SIZE(glymur_ddr_qos_table), + .memory_range = { .min_freq = 0, .max_freq = 1}, + .monitor_cfg = (const struct scmi_qcom_monitor_cfg[]) { + { + .name = "mon_0", + .cpu_mask = 0xf, + .ipm_ceil = 20000000, + .be_stall_floor = 1, + .table_len = 2, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 2189, .mem_freq = 0 }, + { .cpu_freq = 3860, .mem_freq = 1 }, + } + }, + { + .name = "mon_1", + .cpu_mask = 0xf0, + .ipm_ceil = 20000000, + .be_stall_floor = 1, + .table_len = 2, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 2189, .mem_freq = 0 }, + { .cpu_freq = 3860, .mem_freq = 1 }, + } + }, + { + .name = "mon_2", + .cpu_mask = 0xf00, + .ipm_ceil = 20000000, + .be_stall_floor = 1, + .table_len = 2, + .table = (const struct scmi_qcom_map_table[]) { + { .cpu_freq = 2189, .mem_freq = 0 }, + { .cpu_freq = 3860, .mem_freq = 1 }, + } + }, + }, + }, +}; + +static const struct scmi_qcom_memlat_cfg_data glymur_memlat_data = { + .memory_cfg = glymur_memory_cfg, + .cpucp_freq_method = CPUCP_EFFECTIVE_FREQ_CALC_METHOD_1, + .cpucp_sample_ms = 4, + .memory_cnt = ARRAY_SIZE(glymur_memory_cfg), +}; + +static const struct scmi_qcom_memlat_cfg_data hamoa_memlat_data = { + .memory_cfg = hamoa_memory_cfg, + .cpucp_freq_method = CPUCP_EFFECTIVE_FREQ_CALC_METHOD_1, + .cpucp_sample_ms = 4, + .memory_cnt = ARRAY_SIZE(hamoa_memory_cfg), +}; + +#endif diff --git a/drivers/devfreq/scmi-qcom-memlat-devfreq.c b/drivers/devfreq/scmi-qcom-memlat-devfreq.c new file mode 100644 index 0000000000000..c75bfb16bb2b9 --- /dev/null +++ b/drivers/devfreq/scmi-qcom-memlat-devfreq.c @@ -0,0 +1,582 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_MEMORY_TYPES 4 +#define MAX_MONITOR_CNT 5 +#define MAX_NAME_LEN 20 +#define MAX_MAP_ENTRIES 10 + +#include "scmi-qcom-memlat-cfg.h" + +/** + * enum scmi_memlat_protocol_cmd - parameter_ids supported by the "MEMLAT" algo_str hosted + * by the Qualcomm Generic Vendor Protocol on the SCMI controller. + * + * MEMLAT (Memory Latency) monitors the counters to detect memory latency bound workloads + * and scales the frequency/levels of the memory buses accordingly. + * + * @MEMLAT_SET_MEM_GROUP: initializes the frequency/level scaling functions for the memory bus. + * @MEMLAT_SET_MONITOR: configures the monitor to work on a specific memory bus. + * @MEMLAT_SET_COMMON_EV_MAP: set up common counters used to monitor the cpu frequency. + * @MEMLAT_SET_GRP_EV_MAP: set up any specific counters used to monitor the memory bus. + * @MEMLAT_IPM_CEIL: set the IPM (Instruction Per Misses) ceiling per monitor. + * @MEMLAT_SAMPLE_MS: set the sampling period for all the monitors. + * @MEMLAT_MON_FREQ_MAP: setup the cpufreq to memfreq map. + * @MEMLAT_SET_MIN_FREQ: set the max frequency of the memory bus. + * @MEMLAT_SET_MAX_FREQ: set the min frequency of the memory bus. + * @MEMLAT_START_TIMER: start all the monitors with the requested sampling period. + * @MEMLAT_STOP_TIMER: stop all the running monitors. + * @MEMLAT_SET_EFFECTIVE_FREQ_METHOD: set the method used to determine cpu frequency. + */ +enum scmi_memlat_protocol_cmd { + MEMLAT_SET_MEM_GROUP = 16, + MEMLAT_SET_MONITOR, + MEMLAT_SET_COMMON_EV_MAP, + MEMLAT_SET_GRP_EV_MAP, + MEMLAT_IPM_CEIL = 23, + MEMLAT_BE_STALL_FLOOR = 25, + MEMLAT_SAMPLE_MS = 31, + MEMLAT_MON_FREQ_MAP, + MEMLAT_SET_MIN_FREQ, + MEMLAT_SET_MAX_FREQ, + MEMLAT_GET_CUR_FREQ, + MEMLAT_START_TIMER = 36, + MEMLAT_STOP_TIMER, + MEMLAT_SET_EFFECTIVE_FREQ_METHOD = 39, +}; + +struct cpucp_map_table { + u16 v1; + u16 v2; +}; + +struct map_param_msg { + u32 hw_type; + u32 mon_idx; + u32 nr_rows; + struct cpucp_map_table tbl[MAX_MAP_ENTRIES]; +} __packed; + +struct node_msg { + u32 cpumask; + u32 hw_type; + u32 mon_type; + u32 mon_idx; + char mon_name[MAX_NAME_LEN]; +}; + +struct scalar_param_msg { + u32 hw_type; + u32 mon_idx; + u32 val; +}; + +enum common_ev_idx { + INST_IDX, + CYC_IDX, + CONST_CYC_IDX, + FE_STALL_IDX, + BE_STALL_IDX, + NUM_COMMON_EVS +}; + +enum grp_ev_idx { + MISS_IDX, + WB_IDX, + ACC_IDX, + NUM_GRP_EVS +}; + +struct ev_map_msg { + u32 num_evs; + u32 hw_type; + u32 cid[NUM_COMMON_EVS]; +}; + +struct scmi_qcom_memlat_map { + unsigned int cpufreq_mhz; + unsigned int memfreq_khz; +}; + +struct scmi_qcom_monitor_info { + struct scmi_qcom_memlat_map *freq_map; + char name[MAX_NAME_LEN]; + u32 mon_idx; + u32 mon_type; + u32 ipm_ceil; + u32 be_stall_floor; + u32 mask; + u32 freq_map_len; +}; + +struct scmi_qcom_memory_info { + struct scmi_qcom_monitor_info *monitor[MAX_MONITOR_CNT]; + u32 hw_type; + int monitor_cnt; + u32 min_freq; + u32 max_freq; + struct devfreq_dev_profile profile; + struct devfreq *devfreq; + struct platform_device *pdev; + struct scmi_protocol_handle *ph; + const struct qcom_generic_ext_ops *ops; +}; + +struct scmi_qcom_memlat_info { + struct scmi_protocol_handle *ph; + const struct qcom_generic_ext_ops *ops; + struct scmi_qcom_memory_info *memory[MAX_MEMORY_TYPES]; + u32 cpucp_freq_method; + u32 cpucp_sample_ms; + int memory_cnt; +}; + +static int configure_cpucp_common_events(struct scmi_qcom_memlat_info *info) +{ + const struct qcom_generic_ext_ops *ops = info->ops; + u8 ev_map[NUM_COMMON_EVS]; + struct ev_map_msg msg; + + memset(ev_map, 0xFF, NUM_COMMON_EVS); + + msg.num_evs = NUM_COMMON_EVS; + msg.cid[INST_IDX] = EV_INST_RETIRED; + msg.cid[CYC_IDX] = EV_CPU_CYCLES; + msg.cid[CONST_CYC_IDX] = EV_CNT_CYCLES; + msg.cid[FE_STALL_IDX] = INVALID_IDX; + msg.cid[BE_STALL_IDX] = EV_STALL_BACKEND_MEM; + + return ops->set_param(info->ph, &msg, sizeof(msg), MEMLAT_ALGO_STR, + MEMLAT_SET_COMMON_EV_MAP); +} + +static int configure_cpucp_grp(struct device *dev, struct scmi_qcom_memlat_info *info, + int memory_index) +{ + struct scmi_qcom_memory_info *memory = info->memory[memory_index]; + const struct qcom_generic_ext_ops *ops = info->ops; + struct ev_map_msg ev_msg; + u8 ev_map[NUM_GRP_EVS]; + struct node_msg msg; + int ret; + + msg.cpumask = 0; + msg.hw_type = memory->hw_type; + msg.mon_type = 0; + msg.mon_idx = 0; + ret = ops->set_param(info->ph, &msg, sizeof(msg), MEMLAT_ALGO_STR, MEMLAT_SET_MEM_GROUP); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to configure mem type %d\n", + memory->hw_type); + + memset(ev_map, 0xFF, NUM_GRP_EVS); + ev_msg.num_evs = NUM_GRP_EVS; + ev_msg.hw_type = memory->hw_type; + ev_msg.cid[MISS_IDX] = EV_L2_D_RFILL; + ev_msg.cid[WB_IDX] = INVALID_IDX; + ev_msg.cid[ACC_IDX] = INVALID_IDX; + ret = ops->set_param(info->ph, &ev_msg, sizeof(ev_msg), MEMLAT_ALGO_STR, + MEMLAT_SET_GRP_EV_MAP); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to configure event map for mem type %d\n", + memory->hw_type); + + return ret; +} + +static int configure_cpucp_mon(struct device *dev, struct scmi_qcom_memlat_info *info, + int memory_index, int monitor_index) +{ + const struct qcom_generic_ext_ops *ops = info->ops; + struct scmi_qcom_memory_info *memory = info->memory[memory_index]; + struct scmi_qcom_monitor_info *monitor = memory->monitor[monitor_index]; + struct scalar_param_msg scalar_msg; + struct map_param_msg map_msg; + struct node_msg msg; + int ret; + int i; + + msg.cpumask = monitor->mask; + msg.hw_type = memory->hw_type; + msg.mon_type = monitor->mon_type; + msg.mon_idx = monitor->mon_idx; + strscpy(msg.mon_name, monitor->name, sizeof(msg.mon_name)); + ret = ops->set_param(info->ph, &msg, sizeof(msg), MEMLAT_ALGO_STR, MEMLAT_SET_MONITOR); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to configure monitor %s\n", + monitor->name); + + scalar_msg.hw_type = memory->hw_type; + scalar_msg.mon_idx = monitor->mon_idx; + scalar_msg.val = monitor->ipm_ceil; + ret = ops->set_param(info->ph, &scalar_msg, sizeof(scalar_msg), MEMLAT_ALGO_STR, + MEMLAT_IPM_CEIL); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to set ipm ceil for %s\n", + monitor->name); + + scalar_msg.hw_type = memory->hw_type; + scalar_msg.mon_idx = monitor->mon_idx; + scalar_msg.val = monitor->be_stall_floor; + ret = ops->set_param(info->ph, &scalar_msg, sizeof(scalar_msg), MEMLAT_ALGO_STR, + MEMLAT_BE_STALL_FLOOR); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to set ipm ceil for %s\n", monitor->name); + + map_msg.hw_type = memory->hw_type; + map_msg.mon_idx = monitor->mon_idx; + map_msg.nr_rows = monitor->freq_map_len; + for (i = 0; i < monitor->freq_map_len; i++) { + map_msg.tbl[i].v1 = monitor->freq_map[i].cpufreq_mhz; + + if (monitor->freq_map[i].memfreq_khz > 1) + map_msg.tbl[i].v2 = monitor->freq_map[i].memfreq_khz / 1000; + else + map_msg.tbl[i].v2 = monitor->freq_map[i].memfreq_khz; + } + ret = ops->set_param(info->ph, &map_msg, sizeof(map_msg), MEMLAT_ALGO_STR, + MEMLAT_MON_FREQ_MAP); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to configure freq_map for %s\n", + monitor->name); + + scalar_msg.hw_type = memory->hw_type; + scalar_msg.mon_idx = monitor->mon_idx; + scalar_msg.val = memory->min_freq; + ret = ops->set_param(info->ph, &scalar_msg, sizeof(scalar_msg), MEMLAT_ALGO_STR, + MEMLAT_SET_MIN_FREQ); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to set min_freq for %s\n", + monitor->name); + + scalar_msg.hw_type = memory->hw_type; + scalar_msg.mon_idx = monitor->mon_idx; + scalar_msg.val = memory->max_freq; + ret = ops->set_param(info->ph, &scalar_msg, sizeof(scalar_msg), MEMLAT_ALGO_STR, + MEMLAT_SET_MAX_FREQ); + if (ret < 0) + dev_err_probe(dev, ret, "failed to set max_freq for %s\n", monitor->name); + + return ret; +} + +static int scmi_qcom_devfreq_get_cur_freq(struct device *dev, unsigned long *freq) +{ + struct scmi_qcom_memory_info *memory = dev_get_drvdata(dev); + const struct qcom_generic_ext_ops *ops; + struct scalar_param_msg scalar_msg; + int ret; + + ops = memory->ops; + + scalar_msg.hw_type = memory->hw_type; + scalar_msg.mon_idx = 0; + u32 cur_freq; + + ret = ops->get_param(memory->ph, &scalar_msg, sizeof(scalar_msg), MEMLAT_ALGO_STR, + MEMLAT_GET_CUR_FREQ, sizeof(cur_freq)); + if (ret < 0) { + pr_err("failed to get mon current frequency\n"); + return ret; + } + + memcpy(&cur_freq, (void *)&scalar_msg, sizeof(cur_freq)); + + if (memory->hw_type == 2) + *freq = le32_to_cpu(cur_freq) ? 100 : 1; + else + *freq = le32_to_cpu(cur_freq) * 1000UL; + + return 0; +} + +static int scmi_qcom_memlat_configure_events(struct scmi_device *sdev, + struct scmi_qcom_memlat_info *info) +{ + const struct qcom_generic_ext_ops *ops = info->ops; + struct scmi_protocol_handle *ph = info->ph; + int i, j, ret; + + /* Configure common events ids */ + ret = configure_cpucp_common_events(info); + if (ret < 0) + return dev_err_probe(&sdev->dev, ret, "failed to configure common events\n"); + + for (i = 0; i < info->memory_cnt; i++) { + /* Configure per group parameters */ + ret = configure_cpucp_grp(&sdev->dev, info, i); + if (ret < 0) + return ret; + + for (j = 0; j < info->memory[i]->monitor_cnt; j++) { + /* Configure per monitor parameters */ + ret = configure_cpucp_mon(&sdev->dev, info, i, j); + if (ret < 0) + return ret; + } + } + + /* Set loop sampling time */ + ret = ops->set_param(ph, &info->cpucp_sample_ms, sizeof(info->cpucp_sample_ms), + MEMLAT_ALGO_STR, MEMLAT_SAMPLE_MS); + if (ret < 0) + return dev_err_probe(&sdev->dev, ret, "failed to set sample_ms\n"); + + /* Set the effective cpu frequency calculation method */ + ret = ops->set_param(ph, &info->cpucp_freq_method, sizeof(info->cpucp_freq_method), + MEMLAT_ALGO_STR, MEMLAT_SET_EFFECTIVE_FREQ_METHOD); + if (ret < 0) + return dev_err_probe(&sdev->dev, ret, + "failed to set effective frequency calc method\n"); + + /* Start sampling and voting timer */ + ret = ops->start_activity(ph, NULL, 0, MEMLAT_ALGO_STR, MEMLAT_START_TIMER); + if (ret < 0) + dev_err_probe(&sdev->dev, ret, "failed to start memory group timer\n"); + + for (i = 0; i < info->memory_cnt; i++) { + struct scmi_qcom_memory_info *memory = info->memory[i]; + struct platform_device *pdev = memory->pdev; + struct devfreq_dev_profile *profile = &memory->profile; + + profile->polling_ms = info->cpucp_sample_ms; + profile->get_cur_freq = scmi_qcom_devfreq_get_cur_freq; + profile->initial_freq = memory->min_freq > 1 ? + (memory->min_freq * 1000UL) : memory->min_freq; + + memory->ops = info->ops; + memory->ph = info->ph; + + platform_set_drvdata(pdev, memory); + + memory->devfreq = devm_devfreq_add_device(&pdev->dev, profile, + DEVFREQ_GOV_REMOTE, NULL); + if (IS_ERR(memory->devfreq)) { + dev_err(&sdev->dev, "failed to add devfreq device\n"); + /* Start sampling and voting timer */ + ret = ops->start_activity(ph, NULL, 0, MEMLAT_ALGO_STR, MEMLAT_STOP_TIMER); + if (ret < 0) + dev_err_probe(&sdev->dev, ret, + "failed to stop memory group timer\n"); + return -EINVAL; + } + } + + return 0; +} + +static struct scmi_qcom_memlat_map *scmi_qcom_parse_memlat_map(struct device *dev, + const struct scmi_qcom_monitor_cfg *mon_cfg) +{ + struct scmi_qcom_memlat_map *map_table; + const struct scmi_qcom_map_table *table; + + map_table = devm_kzalloc(dev, MAX_MAP_ENTRIES * sizeof(struct scmi_qcom_memlat_map), + GFP_KERNEL); + if (!map_table) + return ERR_PTR(-ENOMEM); + + for (int i = 0; i < mon_cfg->table_len; i++) { + table = &mon_cfg->table[i]; + + map_table[i].cpufreq_mhz = table->cpu_freq; + map_table[i].memfreq_khz = table->mem_freq; + } + + return map_table; +} + +static const struct of_device_id scmi_qcom_memlat_configs[] __maybe_unused = { + { .compatible = "qcom,glymur", .data = &glymur_memlat_data}, + { .compatible = "qcom,mahua", .data = &glymur_memlat_data}, + { .compatible = "qcom,x1e80100", .data = &hamoa_memlat_data}, + { .compatible = "qcom,x1p42100", .data = &hamoa_memlat_data}, + { } +}; + +static int scmi_qcom_memlat_parse_cfg(struct scmi_device *sdev, struct scmi_qcom_memlat_info *info) +{ + const struct scmi_qcom_memlat_cfg_data *cfg_data; + struct scmi_qcom_monitor_info *monitor; + struct scmi_qcom_memory_info *memory; + int ret, i, j; + + cfg_data = of_machine_get_match_data(scmi_qcom_memlat_configs); + if (!cfg_data) { + return dev_err_probe(&sdev->dev, PTR_ERR(cfg_data), + "Couldn't find config data for this platform\n"); + } + + for (i = 0; i < cfg_data->memory_cnt; i++) { + const struct scmi_qcom_memory_cfg *memory_cfg = &cfg_data->memory_cfg[i]; + struct platform_device_info pdevinfo = { 0 }; + + pdevinfo.parent = &sdev->dev; + pdevinfo.name = memory_cfg->name; + pdevinfo.id = PLATFORM_DEVID_NONE; + + memory = devm_kzalloc(&sdev->dev, sizeof(*memory), GFP_KERNEL); + if (!memory) { + ret = -ENOMEM; + goto out; + } + + memory->ops = info->ops; + memory->ph = info->ph; + memory->hw_type = memory_cfg->memory_type; + memory->monitor_cnt = memory_cfg->monitor_cnt; + memory->min_freq = memory_cfg->memory_range.min_freq; + memory->max_freq = memory_cfg->memory_range.max_freq; + + memory->pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(memory->pdev)) { + dev_err_probe(&sdev->dev, PTR_ERR(memory->pdev), + "failed to register platform device\n"); + ret = PTR_ERR(memory->pdev); + goto out; + } + + info->memory[i] = memory; + + for (j = 0; j < memory_cfg->num_opps; j++) { + const struct scmi_qcom_opp_data *table = &memory_cfg->mem_table[j]; + struct platform_device *pdev = memory->pdev; + struct dev_pm_opp_data data; + + data.freq = table->freq; + data.level = table->level; + + ret = dev_pm_opp_add_dynamic(&pdev->dev, &data); + if (ret) { + dev_err_probe(&sdev->dev, ret, "failed to add OPP\n"); + dev_pm_opp_remove_all_dynamic(&pdev->dev); + goto out; + } + } + + for (j = 0; j < memory_cfg->monitor_cnt; j++) { + const struct scmi_qcom_monitor_cfg *monitor_cfg = &memory_cfg->monitor_cfg[j]; + + monitor = devm_kzalloc(&sdev->dev, sizeof(*monitor), GFP_KERNEL); + if (!monitor) + return -ENOMEM; + + monitor->ipm_ceil = monitor_cfg->ipm_ceil; + monitor->mon_type = monitor->ipm_ceil ? 0 : 1; + monitor->be_stall_floor = monitor_cfg->be_stall_floor; + monitor->mask = monitor_cfg->cpu_mask; + monitor->freq_map_len = monitor_cfg->table_len; + + monitor->freq_map = scmi_qcom_parse_memlat_map(&sdev->dev, monitor_cfg); + if (IS_ERR(monitor->freq_map)) { + dev_err_probe(&sdev->dev, PTR_ERR(monitor->freq_map), + "failed to populate cpufreq-memfreq map\n"); + ret = -EINVAL; + goto out; + } + + strscpy(monitor->name, monitor_cfg->name, sizeof(monitor->name)); + monitor->mon_idx = j; + memory->monitor[j] = monitor; + } + } + + info->cpucp_freq_method = cfg_data->cpucp_freq_method; + info->cpucp_sample_ms = cfg_data->cpucp_sample_ms; + info->memory_cnt = cfg_data->memory_cnt; + + return 0; + +out: + for (i = 0; i < cfg_data->memory_cnt; i++) { + if (IS_ERR_OR_NULL(info->memory[i])) + break; + + memory = info->memory[i]; + if (!IS_ERR(memory->pdev)) + platform_device_unregister(memory->pdev); + } + + return ret; +} + +static int scmi_qcom_devfreq_memlat_probe(struct scmi_device *sdev) +{ + const struct scmi_handle *handle = sdev->handle; + const struct qcom_generic_ext_ops *ops; + struct scmi_qcom_memlat_info *info; + struct scmi_protocol_handle *ph; + int ret; + + if (!handle) + return -ENODEV; + + info = devm_kzalloc(&sdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_QCOM_GENERIC, &ph); + if (IS_ERR(ops)) + return PTR_ERR(ops); + + info->ops = ops; + info->ph = ph; + + ret = scmi_qcom_memlat_parse_cfg(sdev, info); + if (ret) + return ret; + + ret = scmi_qcom_memlat_configure_events(sdev, info); + if (ret) + return ret; + + dev_set_drvdata(&sdev->dev, info); + + return ret; +} + +static void scmi_qcom_devfreq_memlat_remove(struct scmi_device *sdev) +{ + struct scmi_qcom_memlat_info *info = dev_get_drvdata(&sdev->dev); + + for (int i = 0; i < info->memory_cnt; i++) { + struct scmi_qcom_memory_info *memory = info->memory[i]; + + if (!IS_ERR(memory->pdev)) + platform_device_unregister(memory->pdev); + } +} + +static const struct scmi_device_id scmi_id_table[] = { + { SCMI_PROTOCOL_QCOM_GENERIC, "qcom-generic-ext" }, + { }, +}; +MODULE_DEVICE_TABLE(scmi, scmi_id_table); + +static struct scmi_driver scmi_qcom_devfreq_memlat_driver = { + .name = "scmi-qcom-devfreq-memlat", + .probe = scmi_qcom_devfreq_memlat_probe, + .remove = scmi_qcom_devfreq_memlat_remove, + .id_table = scmi_id_table, +}; +module_scmi_driver(scmi_qcom_devfreq_memlat_driver); + +MODULE_AUTHOR("Sibi Sankar "); +MODULE_DESCRIPTION("SCMI QCOM DEVFREQ MEMLAT driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index 401aac6a9f07e..fcb278c4a74c9 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -776,7 +776,8 @@ static int tegra_governor_event_handler(struct devfreq *devfreq, static struct devfreq_governor tegra_devfreq_governor = { .name = "tegra_actmon", - .attrs = DEVFREQ_GOV_ATTR_POLLING_INTERVAL, + .attrs = DEVFREQ_GOV_ATTR_POLLING_INTERVAL + | DEVFREQ_GOV_ATTR_TARGET_FREQ, .flags = DEVFREQ_GOV_FLAG_IMMUTABLE | DEVFREQ_GOV_FLAG_IRQ_DRIVEN, .get_target_freq = tegra_governor_get_target, diff --git a/drivers/firmware/arm_scmi/Kconfig b/drivers/firmware/arm_scmi/Kconfig index e3fb36825978e..a52f4d1b8b2c8 100644 --- a/drivers/firmware/arm_scmi/Kconfig +++ b/drivers/firmware/arm_scmi/Kconfig @@ -84,6 +84,7 @@ config ARM_SCMI_QUIRKS source "drivers/firmware/arm_scmi/transports/Kconfig" source "drivers/firmware/arm_scmi/vendors/imx/Kconfig" +source "drivers/firmware/arm_scmi/vendors/qcom/Kconfig" endif #ARM_SCMI_PROTOCOL diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile index 780cd62b2f78a..5a0e003c2477a 100644 --- a/drivers/firmware/arm_scmi/Makefile +++ b/drivers/firmware/arm_scmi/Makefile @@ -13,6 +13,7 @@ scmi-module-objs := $(scmi-driver-y) $(scmi-protocols-y) $(scmi-transport-y) obj-$(CONFIG_ARM_SCMI_PROTOCOL) += transports/ obj-$(CONFIG_ARM_SCMI_PROTOCOL) += vendors/imx/ +obj-$(CONFIG_ARM_SCMI_PROTOCOL) += vendors/qcom/ obj-$(CONFIG_ARM_SCMI_PROTOCOL) += scmi-core.o obj-$(CONFIG_ARM_SCMI_PROTOCOL) += scmi-module.o diff --git a/drivers/firmware/arm_scmi/vendors/qcom/Kconfig b/drivers/firmware/arm_scmi/vendors/qcom/Kconfig new file mode 100644 index 0000000000000..5dd9e8a6b75f0 --- /dev/null +++ b/drivers/firmware/arm_scmi/vendors/qcom/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "ARM SCMI QCOM Vendor Protocols" + +config QCOM_SCMI_GENERIC_EXT + tristate "Qualcomm Technologies, Inc. Qcom SCMI vendor Protocol" + depends on ARM_SCMI_PROTOCOL || COMPILE_TEST + help + The QCOM SCMI vendor protocol provides a generic way of exposing + a number of Qualcomm SoC specific features (like memory bus scaling) + through a mixture of pre-determined algorithm strings and param_id + pairs hosted on the SCMI controller. + + This driver defines/documents the message ID's used for this + communication and also exposes the operations used by the clients. +endmenu diff --git a/drivers/firmware/arm_scmi/vendors/qcom/Makefile b/drivers/firmware/arm_scmi/vendors/qcom/Makefile new file mode 100644 index 0000000000000..6b98fabbebb86 --- /dev/null +++ b/drivers/firmware/arm_scmi/vendors/qcom/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_QCOM_SCMI_GENERIC_EXT) += qcom-generic-ext.o diff --git a/drivers/firmware/arm_scmi/vendors/qcom/qcom-generic-ext.c b/drivers/firmware/arm_scmi/vendors/qcom/qcom-generic-ext.c new file mode 100644 index 0000000000000..4f9eba8ff4bd4 --- /dev/null +++ b/drivers/firmware/arm_scmi/vendors/qcom/qcom-generic-ext.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include + +#include "../../common.h" + +/** + * enum qcom_generic_ext_protocol_cmd - vendor specific commands supported by SCMI Qualcomm + * generic vendor protocol. + * + * This protocol is intended as a generic way of exposing a number of Qualcomm SoC + * specific features through a mixture of pre-determined algorithm string and param_id + * pairs hosted on the SCMI controller. + * + * The QCOM SCMI Vendor Protocol has the protocol id as 0x80 and vendor id set to + * Qualcomm and the supported version is set to 0x10000. The PROTOCOL_VERSION command + * returns version 1.0. + * + * @QCOM_SCMI_SET_PARAM: is used to set the parameter of a specific algo_str hosted on + * QCOM SCMI Vendor Protocol. The tx len depends on the algo_str used. + * @QCOM_SCMI_GET_PARAM: is used to get parameter information of a specific algo_str + * hosted on QCOM SCMI Vendor Protocol. The tx and rx len depends + * on the algo_str used. + * @QCOM_SCMI_START_ACTIVITY: is used to start the activity performed by the algo_str. + * @QCOM_SCMI_STOP_ACTIVITY: is used to stop a pre-existing activity performed by the algo_str. + */ +enum qcom_generic_ext_protocol_cmd { + QCOM_SCMI_SET_PARAM = 0x10, + QCOM_SCMI_GET_PARAM = 0x11, + QCOM_SCMI_START_ACTIVITY = 0x12, + QCOM_SCMI_STOP_ACTIVITY = 0x13, +}; + +/** + * struct qcom_scmi_msg - represents the various parameters to be populated + * for using the QCOM SCMI Vendor Protocol + * + * @ext_id: reserved, must be zero + * @algo_low: lower 32 bits of the algo_str + * @algo_high: upper 32 bits of the algo_str + * @param_id: serves as token message id to the specific algo_str + * @buf: serves as the payload to the specified param_id and algo_str pair + */ +struct qcom_scmi_msg { + __le32 ext_id; + __le32 algo_low; + __le32 algo_high; + __le32 param_id; + __le32 buf[]; +}; + +static int qcom_scmi_common_xfer(const struct scmi_protocol_handle *ph, + enum qcom_generic_ext_protocol_cmd cmd_id, void *buf, + size_t buf_len, u64 algo_str, u32 param_id, size_t rx_size) +{ + struct scmi_xfer *t; + struct qcom_scmi_msg *msg; + int ret; + + ret = ph->xops->xfer_get_init(ph, cmd_id, buf_len + sizeof(*msg), rx_size, &t); + if (ret) + return ret; + + msg = t->tx.buf; + msg->algo_low = cpu_to_le32(lower_32_bits(algo_str)); + msg->algo_high = cpu_to_le32(upper_32_bits(algo_str)); + msg->param_id = cpu_to_le32(param_id); + memcpy(msg->buf, buf, buf_len); + + ret = ph->xops->do_xfer(ph, t); + if (!ret && rx_size) + memcpy(buf, t->rx.buf, t->rx.len); + ph->xops->xfer_put(ph, t); + + return ret; +} + +static int qcom_scmi_set_param(const struct scmi_protocol_handle *ph, void *buf, size_t buf_len, + u64 algo_str, u32 param_id) +{ + return qcom_scmi_common_xfer(ph, QCOM_SCMI_SET_PARAM, buf, buf_len, algo_str, + param_id, 0); +} + +static int qcom_scmi_get_param(const struct scmi_protocol_handle *ph, void *buf, size_t buf_len, + u64 algo_str, u32 param_id, size_t rx_size) +{ + return qcom_scmi_common_xfer(ph, QCOM_SCMI_GET_PARAM, buf, buf_len, algo_str, + param_id, rx_size); +} + +static int qcom_scmi_start_activity(const struct scmi_protocol_handle *ph, void *buf, + size_t buf_len, u64 algo_str, u32 param_id) +{ + return qcom_scmi_common_xfer(ph, QCOM_SCMI_START_ACTIVITY, buf, buf_len, algo_str, + param_id, 0); +} + +static int qcom_scmi_stop_activity(const struct scmi_protocol_handle *ph, void *buf, + size_t buf_len, u64 algo_str, u32 param_id) +{ + return qcom_scmi_common_xfer(ph, QCOM_SCMI_STOP_ACTIVITY, buf, buf_len, algo_str, + param_id, 0); +} + +static struct qcom_generic_ext_ops qcom_proto_ops = { + .set_param = qcom_scmi_set_param, + .get_param = qcom_scmi_get_param, + .start_activity = qcom_scmi_start_activity, + .stop_activity = qcom_scmi_stop_activity, +}; + +static int qcom_generic_ext_protocol_init(const struct scmi_protocol_handle *ph) +{ + dev_dbg(ph->dev, "QCOM Generic Vendor Version %d.%d\n", + PROTOCOL_REV_MAJOR(ph->version), PROTOCOL_REV_MINOR(ph->version)); + + return 0; +} + +static const struct scmi_protocol qcom_generic_ext = { + .id = SCMI_PROTOCOL_QCOM_GENERIC, + .owner = THIS_MODULE, + .instance_init = &qcom_generic_ext_protocol_init, + .ops = &qcom_proto_ops, + .vendor_id = "Qualcomm", + .supported_version = 0x10000, +}; +module_scmi_protocol(qcom_generic_ext); + +MODULE_DESCRIPTION("QCOM SCMI Generic Vendor protocol"); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/arm_scmi/vendors/qcom/qcom_generic.rst b/drivers/firmware/arm_scmi/vendors/qcom/qcom_generic.rst new file mode 100644 index 0000000000000..141bc932e30ff --- /dev/null +++ b/drivers/firmware/arm_scmi/vendors/qcom/qcom_generic.rst @@ -0,0 +1,211 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: + +=============================================================================== +QCOM System Control and Management Interface(SCMI) Vendor Protocols Extension +=============================================================================== + +:Copyright: |copy| 2024, Qualcomm Innovation Center, Inc. All rights reserved. + +:Author: Sibi Sankar + +SCMI_GENERIC: System Control and Management Interface QCOM Generic Vendor Protocol +================================================================================== + +This protocol is intended as a generic way of exposing a number of Qualcomm +SoC specific features through a mixture of pre-determined algorithm string and +param_id pairs hosted on the SCMI controller. It implements an interface compliant +with the Arm SCMI Specification with additional vendor specific commands as +detailed below. + +Commands: +_________ + +PROTOCOL_VERSION +~~~~~~~~~~~~~~~~ + +message_id: 0x0 +protocol_id: 0x80 + ++---------------+--------------------------------------------------------------+ +|Return values | ++---------------+--------------------------------------------------------------+ +|Name |Description | ++---------------+--------------------------------------------------------------+ +|int32 status |See ARM SCMI Specification for status code definitions. | ++---------------+--------------------------------------------------------------+ +|uint32 version |For this revision of the specification, this value must be | +| |0x10000. | ++---------------+--------------------------------------------------------------+ + +PROTOCOL_ATTRIBUTES +~~~~~~~~~~~~~~~~~~~ + +message_id: 0x1 +protocol_id: 0x80 + ++---------------+--------------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |See ARM SCMI Specification for status code definitions. | ++------------------+-----------------------------------------------------------+ +|uint32 attributes |Bits[31:16] Reserved, must be to 0. | +| |Bits[15:8] Number of agents in the system | +| |Bits[7:0] Number of vendor protocols in the system | ++------------------+-----------------------------------------------------------+ + +PROTOCOL_MESSAGE_ATTRIBUTES +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +message_id: 0x2 +protocol_id: 0x80 + ++---------------+--------------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |See ARM SCMI Specification for status code definitions. | ++------------------+-----------------------------------------------------------+ +|uint32 attributes |For all message id's the parameter has a value of 0. | ++------------------+-----------------------------------------------------------+ + +QCOM_SCMI_SET_PARAM +~~~~~~~~~~~~~~~~~~~ + +message_id: 0x10 +protocol_id: 0x80 + ++------------------+-----------------------------------------------------------+ +|Parameters | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|uint32 ext_id |Reserved, must be zero. | ++------------------+-----------------------------------------------------------+ +|uint32 algo_low |Lower 32-bit value of the algorithm string. | ++------------------+-----------------------------------------------------------+ +|uint32 algo_high |Upper 32-bit value of the algorithm string. | ++------------------+-----------------------------------------------------------+ +|uint32 param_id |Serves as the token message id for the algorithm string | +| |and is used to set various parameters supported by it. | ++------------------+-----------------------------------------------------------+ +|uint32 buf[] |Serves as the payload for the specified param_id and | +| |algorithm string pair. | ++------------------+-----------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |SUCCESS: if the param_id and buf[] is parsed successfully | +| |by the chosen algorithm string. | +| |NOT_SUPPORTED: if the algorithm string does not have any | +| |matches. | +| |INVALID_PARAMETERS: if the param_id and the buf[] passed | +| |is rejected by the algorithm string. | ++------------------+-----------------------------------------------------------+ + +QCOM_SCMI_GET_PARAM +~~~~~~~~~~~~~~~~~~~ + +message_id: 0x11 +protocol_id: 0x80 + ++------------------+-----------------------------------------------------------+ +|Parameters | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|uint32 ext_id |Reserved, must be zero. | ++------------------+-----------------------------------------------------------+ +|uint32 algo_low |Lower 32-bit value of the algorithm string. | ++------------------+-----------------------------------------------------------+ +|uint32 algo_high |Upper 32-bit value of the algorithm string. | ++------------------+-----------------------------------------------------------+ +|uint32 param_id |Serves as the token message id for the algorithm string. | ++------------------+-----------------------------------------------------------+ +|uint32 buf[] |Serves as the payload and store of value for the specified | +| |param_id and algorithm string pair. | ++------------------+-----------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |SUCCESS: if the param_id and buf[] is parsed successfully | +| |by the chosen algorithm string and the result is copied | +| |into buf[]. | +| |NOT_SUPPORTED: if the algorithm string does not have any | +| |matches. | +| |INVALID_PARAMETERS: if the param_id and the buf[] passed | +| |is rejected by the algorithm string. | ++------------------+-----------------------------------------------------------+ + +QCOM_SCMI_START_ACTIVITY +~~~~~~~~~~~~~~~~~~~~~~~~ + +message_id: 0x12 +protocol_id: 0x80 + ++------------------+-----------------------------------------------------------+ +|Parameters | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|uint32 ext_id |Reserved, must be zero. | ++------------------+-----------------------------------------------------------+ +|uint32 algo_low |Lower 32-bit value of the algorithm string. | ++------------------+-----------------------------------------------------------+ +|uint32 algo_high |Upper 32-bit value of the algorithm string. | ++------------------+-----------------------------------------------------------+ +|uint32 param_id |Serves as the token message id for the algorithm string | +| |and is generally used to start the activity performed by | +| |the algorithm string. | ++------------------+-----------------------------------------------------------+ +|uint32 buf[] |Serves as the payload for the specified param_id and | +| |algorithm string pair. | ++------------------+-----------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |SUCCESS: if the activity performed by the algorithm string | +| |starts successfully. | +| |NOT_SUPPORTED: if the algorithm string does not have any. | +| |matches or if the activity is already running. | ++------------------+-----------------------------------------------------------+ + +QCOM_SCMI_STOP_ACTIVITY +~~~~~~~~~~~~~~~~~~~~~~~ + +message_id: 0x13 +protocol_id: 0x80 + ++------------------+-----------------------------------------------------------+ +|Parameters | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|uint32 ext_id |Reserved, must be zero. | ++------------------+-----------------------------------------------------------+ +|uint32 algo_low |Lower 32-bit value of the algorithm string. | ++------------------+-----------------------------------------------------------+ +|uint32 algo_high |Upper 32-bit value of the algorithm string. | ++------------------+-----------------------------------------------------------+ +|uint32 param_id |Serves as the token message id for the algorithm string | +| |and is generally used to stop the activity performed by | +| |the algorithm string. | ++------------------+-----------------------------------------------------------+ +|uint32 buf[] |Serves as the payload for the specified param_id and | +| |algorithm string pair. | ++------------------+-----------------------------------------------------------+ +|Return values | ++------------------+-----------------------------------------------------------+ +|Name |Description | ++------------------+-----------------------------------------------------------+ +|int32 status |SUCCESS: if the activity performed by the algorithm string | +| |stops successfully. | +| |NOT_SUPPORTED: if the algorithm string does not have any | +| |matches or if the activity isn't running. | ++------------------+-----------------------------------------------------------+ diff --git a/include/linux/devfreq-governor.h b/include/linux/devfreq-governor.h index dfdd0160a29f3..83aa82c4a9b65 100644 --- a/include/linux/devfreq-governor.h +++ b/include/linux/devfreq-governor.h @@ -36,6 +36,7 @@ */ #define DEVFREQ_GOV_FLAG_IMMUTABLE BIT(0) #define DEVFREQ_GOV_FLAG_IRQ_DRIVEN BIT(1) +#define DEVFREQ_GOV_FLAG_TRACK_REMOTE BIT(2) /* * Definition of governor attribute flags except for common sysfs attributes @@ -43,9 +44,12 @@ * : Indicate polling_interval sysfs attribute * - DEVFREQ_GOV_ATTR_TIMER * : Indicate timer sysfs attribute + * - DEVFREQ_GOV_ATTR_TARGET_FREQ + * : Indicate the target freq sysfs attribute */ #define DEVFREQ_GOV_ATTR_POLLING_INTERVAL BIT(0) #define DEVFREQ_GOV_ATTR_TIMER BIT(1) +#define DEVFREQ_GOV_ATTR_TARGET_FREQ BIT(2) /** * struct devfreq_governor - Devfreq policy governor diff --git a/include/linux/devfreq.h b/include/linux/devfreq.h index dc1075dc34460..4d50cf2309504 100644 --- a/include/linux/devfreq.h +++ b/include/linux/devfreq.h @@ -21,6 +21,7 @@ #define DEVFREQ_GOV_POWERSAVE "powersave" #define DEVFREQ_GOV_USERSPACE "userspace" #define DEVFREQ_GOV_PASSIVE "passive" +#define DEVFREQ_GOV_REMOTE "remote" /* DEVFREQ notifier interface */ #define DEVFREQ_TRANSITION_NOTIFIER (0) diff --git a/include/linux/scmi_qcom_protocol.h b/include/linux/scmi_qcom_protocol.h new file mode 100644 index 0000000000000..465b2522ca29d --- /dev/null +++ b/include/linux/scmi_qcom_protocol.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * SCMI Message Protocol driver QCOM extension header + * + * Copyright (c) 2024, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _LINUX_SCMI_QCOM_PROTOCOL_H +#define _LINUX_SCMI_QCOM_PROTOCOL_H + +#include + +#define SCMI_PROTOCOL_QCOM_GENERIC 0x80 + +struct scmi_protocol_handle; + +/** + * struct qcom_generic_ext_ops - represents the various operations provided + * by QCOM Generic Vendor Protocol + * + * @set_param: set parameter specified by param_id and algo_str pair. + * @get_param: retrieve parameter specified by param_id and algo_str pair. + * @start_activity: initiate a specific activity defined by algo_str. + * @stop_activity: halt previously initiated activity defined by algo_str. + */ +struct qcom_generic_ext_ops { + int (*set_param)(const struct scmi_protocol_handle *ph, void *buf, size_t buf_len, + u64 algo_str, u32 param_id); + int (*get_param)(const struct scmi_protocol_handle *ph, void *buf, size_t buf_len, + u64 algo_str, u32 param_id, size_t rx_size); + int (*start_activity)(const struct scmi_protocol_handle *ph, void *buf, size_t buf_len, + u64 algo_str, u32 param_id); + int (*stop_activity)(const struct scmi_protocol_handle *ph, void *buf, size_t buf_len, + u64 algo_str, u32 param_id); +}; + +#endif /* _LINUX_SCMI_QCOM_PROTOCOL_H */