From 4264ea29c610a5812885ce07bab7ab28ea11d3fc Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Thu, 30 Oct 2025 20:26:28 +0200 Subject: [PATCH 01/13] FROMGIT: PM / devfreq: Move governor.h to a public header location Some device drivers (and out-of-tree modules) might want to define device-specific device governors. Rather than restricting all of them to be a part of drivers/devfreq/ (which is not possible for out-of-tree drivers anyway) move governor.h to include/linux/devfreq-governor.h and update all drivers to use it. The devfreq_cpu_data is only used internally, by the passive governor, so it is moved to the driver source rather than being a part of the public interface. Acked-by: Jon Hunter Signed-off-by: Dmitry Baryshkov Reviewed-by: Bjorn Andersson Acked-by: MyungJoo Ham Signed-off-by: Chanwoo Choi Link: https://patchwork.kernel.org/project/linux-pm/patch/20251030-governor-public-v2-1-432a11a9975a@oss.qualcomm.com/ Signed-off-by: Xin Liu --- drivers/devfreq/devfreq.c | 2 +- drivers/devfreq/governor_passive.c | 27 ++++++++++++++- drivers/devfreq/governor_performance.c | 2 +- drivers/devfreq/governor_powersave.c | 2 +- drivers/devfreq/governor_simpleondemand.c | 2 +- drivers/devfreq/governor_userspace.c | 2 +- drivers/devfreq/hisi_uncore_freq.c | 3 +- drivers/devfreq/tegra30-devfreq.c | 3 +- .../linux/devfreq-governor.h | 33 +++---------------- 9 files changed, 37 insertions(+), 39 deletions(-) rename drivers/devfreq/governor.h => include/linux/devfreq-governor.h (80%) diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index 2e8d01d47f699..00979f2e0e276 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -28,7 +29,6 @@ #include #include #include -#include "governor.h" #define CREATE_TRACE_POINTS #include diff --git a/drivers/devfreq/governor_passive.c b/drivers/devfreq/governor_passive.c index 953cf9a1e9f7f..8cd6f9a59f642 100644 --- a/drivers/devfreq/governor_passive.c +++ b/drivers/devfreq/governor_passive.c @@ -14,8 +14,33 @@ #include #include #include +#include #include -#include "governor.h" + +/** + * struct devfreq_cpu_data - Hold the per-cpu data + * @node: list node + * @dev: reference to cpu device. + * @first_cpu: the cpumask of the first cpu of a policy. + * @opp_table: reference to cpu opp table. + * @cur_freq: the current frequency of the cpu. + * @min_freq: the min frequency of the cpu. + * @max_freq: the max frequency of the cpu. + * + * This structure stores the required cpu_data of a cpu. + * This is auto-populated by the governor. + */ +struct devfreq_cpu_data { + struct list_head node; + + struct device *dev; + unsigned int first_cpu; + + struct opp_table *opp_table; + unsigned int cur_freq; + unsigned int min_freq; + unsigned int max_freq; +}; static struct devfreq_cpu_data * get_parent_cpu_data(struct devfreq_passive_data *p_data, diff --git a/drivers/devfreq/governor_performance.c b/drivers/devfreq/governor_performance.c index 2e4e981446fa8..fdb22bf512cf1 100644 --- a/drivers/devfreq/governor_performance.c +++ b/drivers/devfreq/governor_performance.c @@ -7,8 +7,8 @@ */ #include +#include #include -#include "governor.h" static int devfreq_performance_func(struct devfreq *df, unsigned long *freq) diff --git a/drivers/devfreq/governor_powersave.c b/drivers/devfreq/governor_powersave.c index f059e88148046..ee2d6ec8a5122 100644 --- a/drivers/devfreq/governor_powersave.c +++ b/drivers/devfreq/governor_powersave.c @@ -7,8 +7,8 @@ */ #include +#include #include -#include "governor.h" static int devfreq_powersave_func(struct devfreq *df, unsigned long *freq) diff --git a/drivers/devfreq/governor_simpleondemand.c b/drivers/devfreq/governor_simpleondemand.c index c234357363675..9c69b96df5f97 100644 --- a/drivers/devfreq/governor_simpleondemand.c +++ b/drivers/devfreq/governor_simpleondemand.c @@ -9,8 +9,8 @@ #include #include #include +#include #include -#include "governor.h" /* Default constants for DevFreq-Simple-Ondemand (DFSO) */ #define DFSO_UPTHRESHOLD (90) diff --git a/drivers/devfreq/governor_userspace.c b/drivers/devfreq/governor_userspace.c index 175de0c0b50e0..395174f93960d 100644 --- a/drivers/devfreq/governor_userspace.c +++ b/drivers/devfreq/governor_userspace.c @@ -9,11 +9,11 @@ #include #include #include +#include #include #include #include #include -#include "governor.h" struct userspace_data { unsigned long user_frequency; diff --git a/drivers/devfreq/hisi_uncore_freq.c b/drivers/devfreq/hisi_uncore_freq.c index c1ed70fa0a400..4d00d813c8ac6 100644 --- a/drivers/devfreq/hisi_uncore_freq.c +++ b/drivers/devfreq/hisi_uncore_freq.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -26,8 +27,6 @@ #include #include -#include "governor.h" - struct hisi_uncore_pcc_data { u16 status; u16 resv; diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index 4a4f0106ab9dd..77cbb204087c9 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -21,8 +22,6 @@ #include -#include "governor.h" - #define ACTMON_GLB_STATUS 0x0 #define ACTMON_GLB_PERIOD_CTRL 0x4 diff --git a/drivers/devfreq/governor.h b/include/linux/devfreq-governor.h similarity index 80% rename from drivers/devfreq/governor.h rename to include/linux/devfreq-governor.h index 0adfebc0467a3..dfdd0160a29f3 100644 --- a/drivers/devfreq/governor.h +++ b/include/linux/devfreq-governor.h @@ -5,11 +5,11 @@ * Copyright (C) 2011 Samsung Electronics * MyungJoo Ham * - * This header is for devfreq governors in drivers/devfreq/ + * This header is for devfreq governors */ -#ifndef _GOVERNOR_H -#define _GOVERNOR_H +#ifndef __LINUX_DEVFREQ_DEVFREQ_H__ +#define __LINUX_DEVFREQ_DEVFREQ_H__ #include @@ -47,31 +47,6 @@ #define DEVFREQ_GOV_ATTR_POLLING_INTERVAL BIT(0) #define DEVFREQ_GOV_ATTR_TIMER BIT(1) -/** - * struct devfreq_cpu_data - Hold the per-cpu data - * @node: list node - * @dev: reference to cpu device. - * @first_cpu: the cpumask of the first cpu of a policy. - * @opp_table: reference to cpu opp table. - * @cur_freq: the current frequency of the cpu. - * @min_freq: the min frequency of the cpu. - * @max_freq: the max frequency of the cpu. - * - * This structure stores the required cpu_data of a cpu. - * This is auto-populated by the governor. - */ -struct devfreq_cpu_data { - struct list_head node; - - struct device *dev; - unsigned int first_cpu; - - struct opp_table *opp_table; - unsigned int cur_freq; - unsigned int min_freq; - unsigned int max_freq; -}; - /** * struct devfreq_governor - Devfreq policy governor * @node: list node - contains registered devfreq governors @@ -124,4 +99,4 @@ static inline int devfreq_update_stats(struct devfreq *df) return df->profile->get_dev_status(df->dev.parent, &df->last_status); } -#endif /* _GOVERNOR_H */ +#endif /* __LINUX_DEVFREQ_DEVFREQ_H__ */ From 58fc42ce38ff0ef85945f8301fc86be10523a11f Mon Sep 17 00:00:00 2001 From: Sibi Sankar Date: Thu, 7 May 2026 11:52:30 +0530 Subject: [PATCH 02/13] FROMLIST: firmware: arm_scmi: Add QCOM Generic Vendor Protocol documentation Add QCOM System Control Management Interface (SCMI) Generic Vendor Extensions Protocol documentation. Link: https://lore.kernel.org/lkml/20260507062237.78051-2-sibi.sankar@oss.qualcomm.com/ Signed-off-by: Sibi Sankar Signed-off-by: Xin Liu --- .../arm_scmi/vendors/qcom/qcom_generic.rst | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 drivers/firmware/arm_scmi/vendors/qcom/qcom_generic.rst 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. | ++------------------+-----------------------------------------------------------+ From 5de26089948a2ceb96ddd9266292a7e11694c77e Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Sat, 27 Dec 2025 16:41:31 +0000 Subject: [PATCH 03/13] FROMGIT: firmware: arm_scmi: Rework protocol version negotiation logic Protocol version negotiation can be used by an agent to request the server to downgrade the version effectively utilized by a specific protocol during the current session, if the latest version used by the server is newer than the latest version known to the client. In order for the negotiation process to be fully effective at preventing any possible version incompatibility, it must happen early on, well before the specific protocol initialization phase takes place. Delegate protocol version querying to the core SCMI stack and rework the protocol negotiation logic in order to execute the needed negotiation exchanges upfront, right before the initialization phase takes place. Signed-off-by: Cristian Marussi Link: https://lore.kernel.org/r/20251227164132.1311988-2-cristian.marussi@arm.com Signed-off-by: Sudeep Holla Signed-off-by: Xin Liu --- drivers/firmware/arm_scmi/driver.c | 93 ++++++++++++++++++++++----- drivers/firmware/arm_scmi/protocols.h | 4 ++ 2 files changed, 81 insertions(+), 16 deletions(-) diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index 5caa9191a8d1a..094cfcf51d289 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -2112,6 +2112,76 @@ static int scmi_protocol_version_negotiate(struct scmi_protocol_handle *ph) return ret; } +/** + * scmi_protocol_version_initialize - Initialize protocol version + * @dev: A device reference. + * @pi: A reference to the protocol instance being initialized + * + * At first retrieve the newest protocol version supported by the platform for + * this specific protoocol. + * + * Negotiation is attempted only when the platform advertised a protocol + * version newer than the most recent version known to this agent, since + * backward compatibility is NOT assured in general between versions. + * + * Failing to negotiate a fallback version or to query supported version at + * all will result in an attempt to use the newest version known to this agent + * even though compatibility is NOT assured. + * + * Versions are defined as: + * + * pi->version: the version supported by the platform as returned by the query. + * pi->proto->supported_version: the newest version supported by this agent + * for this protocol. + * pi->negotiated_version: The version successfully negotiated with the platform. + * ph->version: The final version effectively chosen for this session. + */ +static void scmi_protocol_version_initialize(struct device *dev, + struct scmi_protocol_instance *pi) +{ + struct scmi_protocol_handle *ph = &pi->ph; + int ret; + + /* + * Query and store platform supported protocol version: this is usually + * the newest version the platfom can support. + */ + ret = version_get(ph, &pi->version); + if (ret) { + dev_warn(dev, + "Failed to query supported version for protocol 0x%X.\n", + pi->proto->id); + goto best_effort; + } + + /* Need to negotiate at all ? */ + if (pi->version <= pi->proto->supported_version) { + ph->version = pi->version; + return; + } + + /* Attempt negotiation */ + ret = scmi_protocol_version_negotiate(ph); + if (!ret) { + ph->version = pi->negotiated_version; + dev_info(dev, + "Protocol 0x%X successfully negotiated version 0x%X\n", + pi->proto->id, ph->version); + return; + } + + dev_warn(dev, + "Detected UNSUPPORTED higher version 0x%X for protocol 0x%X.\n", + pi->version, pi->proto->id); + +best_effort: + /* Fallback to use newest version known to this agent */ + ph->version = pi->proto->supported_version; + dev_warn(dev, + "Trying version 0x%X. Backward compatibility is NOT assured.\n", + ph->version); +} + /** * scmi_alloc_init_protocol_instance - Allocate and initialize a protocol * instance descriptor. @@ -2157,6 +2227,13 @@ scmi_alloc_init_protocol_instance(struct scmi_info *info, pi->ph.set_priv = scmi_set_protocol_priv; pi->ph.get_priv = scmi_get_protocol_priv; refcount_set(&pi->users, 1); + + /* + * Initialize effectively used protocol version performing any + * possibly needed negotiations. + */ + scmi_protocol_version_initialize(handle->dev, pi); + /* proto->init is assured NON NULL by scmi_protocol_register */ ret = pi->proto->instance_init(&pi->ph); if (ret) @@ -2184,22 +2261,6 @@ scmi_alloc_init_protocol_instance(struct scmi_info *info, devres_close_group(handle->dev, pi->gid); dev_dbg(handle->dev, "Initialized protocol: 0x%X\n", pi->proto->id); - if (pi->version > proto->supported_version) { - ret = scmi_protocol_version_negotiate(&pi->ph); - if (!ret) { - dev_info(handle->dev, - "Protocol 0x%X successfully negotiated version 0x%X\n", - proto->id, pi->negotiated_version); - } else { - dev_warn(handle->dev, - "Detected UNSUPPORTED higher version 0x%X for protocol 0x%X.\n", - pi->version, pi->proto->id); - dev_warn(handle->dev, - "Trying version 0x%X. Backward compatibility is NOT assured.\n", - pi->proto->supported_version); - } - } - return pi; clean: diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h index d62c4469d1fd9..2766c2b822b56 100644 --- a/drivers/firmware/arm_scmi/protocols.h +++ b/drivers/firmware/arm_scmi/protocols.h @@ -159,6 +159,9 @@ struct scmi_proto_helpers_ops; * struct scmi_protocol_handle - Reference to an initialized protocol instance * * @dev: A reference to the associated SCMI instance device (handle->dev). + * @version: The protocol version currently effectively in use by this + * initialized instance of the protocol as determined at the end of + * any possibly needed negotiations performed by the core. * @xops: A reference to a struct holding refs to the core xfer operations that * can be used by the protocol implementation to generate SCMI messages. * @set_priv: A method to set protocol private data for this instance. @@ -177,6 +180,7 @@ struct scmi_proto_helpers_ops; */ struct scmi_protocol_handle { struct device *dev; + unsigned int version; const struct scmi_xfer_ops *xops; const struct scmi_proto_helpers_ops *hops; int (*set_priv)(const struct scmi_protocol_handle *ph, void *priv, From d6d63d84d9e673dc23dcddc9d5fb993fe238ae1b Mon Sep 17 00:00:00 2001 From: Sibi Sankar Date: Thu, 7 May 2026 11:52:31 +0530 Subject: [PATCH 04/13] FROMLIST: firmware: arm_scmi: vendors: Add QCOM SCMI Generic Extensions The QCOM SCMI Generic Extensions 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. Co-developed-by: Shivnandan Kumar Signed-off-by: Shivnandan Kumar Co-developed-by: Ramakrishna Gottimukkula Signed-off-by: Ramakrishna Gottimukkula Co-developed-by: Amir Vajid Signed-off-by: Amir Vajid Link: https://lore.kernel.org/lkml/20260507062237.78051-3-sibi.sankar@oss.qualcomm.com/ Signed-off-by: Sibi Sankar Signed-off-by: Xin Liu --- drivers/firmware/arm_scmi/Kconfig | 1 + drivers/firmware/arm_scmi/Makefile | 1 + .../firmware/arm_scmi/vendors/qcom/Kconfig | 15 ++ .../firmware/arm_scmi/vendors/qcom/Makefile | 2 + .../arm_scmi/vendors/qcom/qcom-generic-ext.c | 135 ++++++++++++++++++ include/linux/scmi_qcom_protocol.h | 37 +++++ 6 files changed, 191 insertions(+) create mode 100644 drivers/firmware/arm_scmi/vendors/qcom/Kconfig create mode 100644 drivers/firmware/arm_scmi/vendors/qcom/Makefile create mode 100644 drivers/firmware/arm_scmi/vendors/qcom/qcom-generic-ext.c create mode 100644 include/linux/scmi_qcom_protocol.h 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/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 */ From 236776ba55ca13255640caad2dc9c7d3ef9bf9a5 Mon Sep 17 00:00:00 2001 From: Sibi Sankar Date: Thu, 7 May 2026 11:52:32 +0530 Subject: [PATCH 05/13] FROMLIST: PM / devfreq: Add new target_freq attribute flag for governors Introduce the target_freq attribute flag as a pre-cursor to adding support for devfreq governors who do not explicitly allow for a method to get/set target frequency. Link: https://lore.kernel.org/lkml/20260507062237.78051-4-sibi.sankar@oss.qualcomm.com/ Signed-off-by: Sibi Sankar Signed-off-by: Xin Liu --- drivers/devfreq/devfreq.c | 4 ++++ drivers/devfreq/governor_passive.c | 1 + drivers/devfreq/governor_performance.c | 1 + drivers/devfreq/governor_powersave.c | 1 + drivers/devfreq/governor_simpleondemand.c | 1 + drivers/devfreq/governor_userspace.c | 1 + drivers/devfreq/hisi_uncore_freq.c | 1 + drivers/devfreq/tegra30-devfreq.c | 3 ++- include/linux/devfreq-governor.h | 3 +++ 9 files changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index 00979f2e0e276..361e05eec0798 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -1568,6 +1568,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 8cd6f9a59f642..19cdba56a73a0 100644 --- a/drivers/devfreq/governor_passive.c +++ b/drivers/devfreq/governor_passive.c @@ -449,6 +449,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_simpleondemand.c b/drivers/devfreq/governor_simpleondemand.c index 9c69b96df5f97..a6aa6c9916e76 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 395174f93960d..2cd403feb4bcc 100644 --- a/drivers/devfreq/governor_userspace.c +++ b/drivers/devfreq/governor_userspace.c @@ -136,6 +136,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/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index 77cbb204087c9..4cc284948fbc0 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -780,7 +780,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/include/linux/devfreq-governor.h b/include/linux/devfreq-governor.h index dfdd0160a29f3..2853f571dfdf7 100644 --- a/include/linux/devfreq-governor.h +++ b/include/linux/devfreq-governor.h @@ -43,9 +43,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 From dc3b3afe7963f0ba825e9972b69e397c6f2996f4 Mon Sep 17 00:00:00 2001 From: Sibi Sankar Date: Thu, 7 May 2026 11:52:33 +0530 Subject: [PATCH 06/13] FROMLIST: PM / devfreq: Add new track_remote flag for governors Add a new track_remote flag for devfreq governors as a pre-curor to adding the devfreq governors that is responsible for tracking frequency changes on remote devices. This new governor flag provides for a way track the remote device's frequency and keep the trans_stat data updated and skip the frequency update sequency for the device. Link: https://lore.kernel.org/lkml/20260507062237.78051-5-sibi.sankar@oss.qualcomm.com/ Signed-off-by: Sibi Sankar Signed-off-by: Xin Liu --- drivers/devfreq/devfreq.c | 5 +++++ include/linux/devfreq-governor.h | 1 + 2 files changed, 6 insertions(+) diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index 361e05eec0798..c408779b13560 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"); diff --git a/include/linux/devfreq-governor.h b/include/linux/devfreq-governor.h index 2853f571dfdf7..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 From 221cf86f73fba2522bf3117d629e6f6151da54f1 Mon Sep 17 00:00:00 2001 From: Sibi Sankar Date: Thu, 7 May 2026 11:52:34 +0530 Subject: [PATCH 07/13] FROMLIST: PM / devfreq: Add a governor for tracking remote device frequencies On SoCs, where the governor and the mechanism to control the frequency for devices like caches is hosted on the System Control Processor (SCP), there exists a need track the frequency changes in a reliable way and provide ways to tweaking parameters on the remote governor. To address this introduce the new remote devfreq governor that provides for a way to track the frequency changes on remote devices. It uses the newly introduced target_freq attribute flag and track_remote flag to achieve this. Link: https://lore.kernel.org/lkml/20260507062237.78051-6-sibi.sankar@oss.qualcomm.com/ Signed-off-by: Sibi Sankar Signed-off-by: Xin Liu --- drivers/devfreq/Kconfig | 8 ++++ drivers/devfreq/Makefile | 1 + drivers/devfreq/governor_remote.c | 80 +++++++++++++++++++++++++++++++ include/linux/devfreq.h | 1 + 4 files changed, 90 insertions(+) create mode 100644 drivers/devfreq/governor_remote.c diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index c999c4a1e5675..2caa875549141 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 diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile index 404179d79a9db..cde57c8cda764 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 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/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) From cc26a4d685828c8d8246e31e9419ff28d438616d Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 12 Nov 2025 11:28:46 +0100 Subject: [PATCH 08/13] FROMGIT: of: Add wrappers to match root node with OF device ID tables Several drivers duplicate same code for getting reference to the root node, matching it against 'struct of_device_id' table and getting out the match data from the table entry. There is a of_machine_compatible_match() wrapper but it takes array of strings, which is not suitable for many drivers since they want the driver data associated with each compatible. Add two wrappers, similar to existing of_device_get_match_data(): 1. of_machine_device_match() doing only matching against 'struct of_device_id' and returning bool. 2. of_machine_get_match_data() doing the matching and returning associated driver data for found compatible. Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Krzysztof Kozlowski Reviewed-by: Lukasz Luba Tested-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20251112-b4-of-match-matchine-data-v2-1-d46b72003fd6@linaro.org Signed-off-by: Rob Herring (Arm) Signed-off-by: Xin Liu --- drivers/of/base.c | 47 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/of.h | 13 +++++++++++++ 2 files changed, 60 insertions(+) diff --git a/drivers/of/base.c b/drivers/of/base.c index 3df51a122c846..ec06c45e8fe36 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -434,6 +434,53 @@ bool of_machine_compatible_match(const char *const *compats) } EXPORT_SYMBOL(of_machine_compatible_match); +/** + * of_machine_device_match - Test root of device tree against a of_device_id array + * @matches: NULL terminated array of of_device_id match structures to search in + * + * Returns true if the root node has any of the given compatible values in its + * compatible property. + */ +bool of_machine_device_match(const struct of_device_id *matches) +{ + struct device_node *root; + const struct of_device_id *match = NULL; + + root = of_find_node_by_path("/"); + if (root) { + match = of_match_node(matches, root); + of_node_put(root); + } + + return match != NULL; +} +EXPORT_SYMBOL(of_machine_device_match); + +/** + * of_machine_get_match_data - Tell if root of device tree has a matching of_match structure + * @matches: NULL terminated array of of_device_id match structures to search in + * + * Returns data associated with matched entry or NULL + */ +const void *of_machine_get_match_data(const struct of_device_id *matches) +{ + const struct of_device_id *match; + struct device_node *root; + + root = of_find_node_by_path("/"); + if (!root) + return NULL; + + match = of_match_node(matches, root); + of_node_put(root); + + if (!match) + return NULL; + + return match->data; +} +EXPORT_SYMBOL(of_machine_get_match_data); + static bool __of_device_is_status(const struct device_node *device, const char * const*strings) { diff --git a/include/linux/of.h b/include/linux/of.h index aab51acfbbfae..e84a28b6078c7 100644 --- a/include/linux/of.h +++ b/include/linux/of.h @@ -407,6 +407,8 @@ extern int of_alias_get_id(const struct device_node *np, const char *stem); extern int of_alias_get_highest_id(const char *stem); bool of_machine_compatible_match(const char *const *compats); +bool of_machine_device_match(const struct of_device_id *matches); +const void *of_machine_get_match_data(const struct of_device_id *matches); /** * of_machine_is_compatible - Test root of device tree for a given compatible value @@ -862,6 +864,17 @@ static inline bool of_machine_compatible_match(const char *const *compats) return false; } +static inline bool of_machine_device_match(const struct of_device_id *matches) +{ + return false; +} + +static inline const void * +of_machine_get_match_data(const struct of_device_id *matches) +{ + return NULL; +} + static inline bool of_console_check(const struct device_node *dn, const char *name, int index) { return false; From 26ad6a40e77f33a19fd8ee091a72159b2c8de150 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 2 Mar 2026 17:29:05 +0100 Subject: [PATCH 09/13] FROMGIT: of: Add of_machine_get_match() helper Currently, there are two helpers to match the root compatible value against an of_device_id array: - of_machine_device_match() returns true if a match is found, - of_machine_get_match_data() returns the match data if a match is found. However, there is no helper that returns the actual of_device_id structure corresponding to the match, leading to code duplication in various drivers. Fix this by reworking of_machine_device_match() to return the actual match structure, and renaming it to of_machine_get_match(). Retain the old of_machine_device_match() functionality using a cheap static inline wrapper around the new of_machine_get_match() helper. Signed-off-by: Geert Uytterhoeven Acked-by: Viresh Kumar Link: https://patch.msgid.link/14e1c03d443b1a5f210609ec3a1ebbaeab8fb3d9.1772468323.git.geert+renesas@glider.be Signed-off-by: Rob Herring (Arm) Signed-off-by: Xin Liu --- drivers/of/base.c | 11 +++++------ include/linux/of.h | 11 ++++++++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/drivers/of/base.c b/drivers/of/base.c index ec06c45e8fe36..c1eeba4970de9 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -435,13 +435,12 @@ bool of_machine_compatible_match(const char *const *compats) EXPORT_SYMBOL(of_machine_compatible_match); /** - * of_machine_device_match - Test root of device tree against a of_device_id array + * of_machine_get_match - Test root of device tree against an of_device_id array * @matches: NULL terminated array of of_device_id match structures to search in * - * Returns true if the root node has any of the given compatible values in its - * compatible property. + * Returns matched entry or NULL */ -bool of_machine_device_match(const struct of_device_id *matches) +const struct of_device_id *of_machine_get_match(const struct of_device_id *matches) { struct device_node *root; const struct of_device_id *match = NULL; @@ -452,9 +451,9 @@ bool of_machine_device_match(const struct of_device_id *matches) of_node_put(root); } - return match != NULL; + return match; } -EXPORT_SYMBOL(of_machine_device_match); +EXPORT_SYMBOL(of_machine_get_match); /** * of_machine_get_match_data - Tell if root of device tree has a matching of_match structure diff --git a/include/linux/of.h b/include/linux/of.h index e84a28b6078c7..f046b00e21ba3 100644 --- a/include/linux/of.h +++ b/include/linux/of.h @@ -407,7 +407,7 @@ extern int of_alias_get_id(const struct device_node *np, const char *stem); extern int of_alias_get_highest_id(const char *stem); bool of_machine_compatible_match(const char *const *compats); -bool of_machine_device_match(const struct of_device_id *matches); +const struct of_device_id *of_machine_get_match(const struct of_device_id *matches); const void *of_machine_get_match_data(const struct of_device_id *matches); /** @@ -864,9 +864,9 @@ static inline bool of_machine_compatible_match(const char *const *compats) return false; } -static inline bool of_machine_device_match(const struct of_device_id *matches) +static inline const struct of_device_id *of_machine_get_match(const struct of_device_id *matches) { - return false; + return NULL; } static inline const void * @@ -989,6 +989,11 @@ static inline int of_numa_init(void) } #endif +static inline bool of_machine_device_match(const struct of_device_id *matches) +{ + return of_machine_get_match(matches) != NULL; +} + static inline struct device_node *of_find_matching_node( struct device_node *from, const struct of_device_id *matches) From 92844f8b582abdd86dba546c13995118c5d92ea4 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 2 Mar 2026 17:29:06 +0100 Subject: [PATCH 10/13] FROMGIT: of: Convert to of_machine_get_match() Use the of_machine_get_match() helper instead of open-coding the same operation. Signed-off-by: Geert Uytterhoeven Acked-by: Viresh Kumar Link: https://patch.msgid.link/83ed49314b94dab7781e1d74236af72dd5c349c6.1772468323.git.geert+renesas@glider.be Signed-off-by: Rob Herring (Arm) Signed-off-by: Xin Liu --- drivers/of/base.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/drivers/of/base.c b/drivers/of/base.c index c1eeba4970de9..a8f0757287500 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -464,15 +464,8 @@ EXPORT_SYMBOL(of_machine_get_match); const void *of_machine_get_match_data(const struct of_device_id *matches) { const struct of_device_id *match; - struct device_node *root; - - root = of_find_node_by_path("/"); - if (!root) - return NULL; - - match = of_match_node(matches, root); - of_node_put(root); + match = of_machine_get_match(matches); if (!match) return NULL; From 2eb52d11f369520218d13b5384360d9ccd425202 Mon Sep 17 00:00:00 2001 From: Sibi Sankar Date: Thu, 7 May 2026 11:52:35 +0530 Subject: [PATCH 11/13] FROMLIST: PM / devfreq: Introduce the QCOM SCMI Memlat devfreq device On Qualcomm Glymur and Hamoa SoCs, the memlat governor and the mechanism to control the various caches and ram is hosted on the CPU Control Processor (CPUCP) and the method to tweak and start the governor is exposed through the QCOM SCMI Generic Extension Protocol. Introduce the devfreq scmi client driver that uses the memlat algorithm string hosted on QCOM SCMI Generic Extension Protocol to detect memory latency workloads and control frequency/level of the various memory buses (DDR/LLCC/DDR_QOS). The DDR/LLCC/DDR_QOS are modelled as devfreq devices, with the governor set to remote devfreq governor. This serves as a way to get a basic insight into the device operation through trans_stat and provides for ways to further tweak the parameters of the remote governor. Co-developed-by: Shivnandan Kumar Signed-off-by: Shivnandan Kumar Co-developed-by: Ramakrishna Gottimukkula Signed-off-by: Ramakrishna Gottimukkula Co-developed-by: Amir Vajid Signed-off-by: Amir Vajid Link: https://lore.kernel.org/lkml/20260507062237.78051-7-sibi.sankar@oss.qualcomm.com/ Signed-off-by: Sibi Sankar Signed-off-by: Xin Liu --- drivers/devfreq/Kconfig | 13 + drivers/devfreq/Makefile | 1 + drivers/devfreq/scmi-qcom-memlat-cfg.h | 473 +++++++++++++++++ drivers/devfreq/scmi-qcom-memlat-devfreq.c | 582 +++++++++++++++++++++ 4 files changed, 1069 insertions(+) create mode 100644 drivers/devfreq/scmi-qcom-memlat-cfg.h create mode 100644 drivers/devfreq/scmi-qcom-memlat-devfreq.c diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index 2caa875549141..98b5a50d31896 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig @@ -169,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 cde57c8cda764..b11f94e2f4855 100644 --- a/drivers/devfreq/Makefile +++ b/drivers/devfreq/Makefile @@ -17,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/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"); From 32a75e66d9ad5008314db267363a8593a5acd03d Mon Sep 17 00:00:00 2001 From: Sibi Sankar Date: Thu, 7 May 2026 11:52:37 +0530 Subject: [PATCH 12/13] FROMLIST: arm64: dts: qcom: hamoa: Enable LLCC/DDR/DDR_QOS dvfs On Qualcomm Hamoa SoCs, the memlat governor and the mechanism to control the LLCC and DDR/DDR_QOS is hosted on the CPU Control Processor (CPUCP). Enable the nodes required to get QCOM SCMI Generic Extension protocol to probe on Hamoa and Purwa SoCs. Link: https://lore.kernel.org/lkml/20260507062237.78051-9-sibi.sankar@oss.qualcomm.com/ Signed-off-by: Sibi Sankar Signed-off-by: Xin Liu --- arch/arm64/boot/dts/qcom/hamoa.dtsi | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/hamoa.dtsi b/arch/arm64/boot/dts/qcom/hamoa.dtsi index 309a16e9c5540..0f8e5b1a305df 100644 --- a/arch/arm64/boot/dts/qcom/hamoa.dtsi +++ b/arch/arm64/boot/dts/qcom/hamoa.dtsi @@ -361,6 +361,10 @@ reg = <0x13>; #power-domain-cells = <1>; }; + + scmi_vendor: protocol@80 { + reg = <0x80>; + }; }; }; From 959807b6356ea001f55fabf651789cf02dae1739 Mon Sep 17 00:00:00 2001 From: Sibi Sankar Date: Wed, 3 Jun 2026 16:24:38 +0800 Subject: [PATCH 13/13] QCLINUX: qcom.config: Enable QCOM SCMI memlat bus scaling Enable the QCOM SCMI Generic Vendor Extension protocol (=y), the remote devfreq governor (=y), and the QCOM SCMI memlat devfreq device driver (=m) for LLCC/DDR/DDR_QOS bus scaling on Glymur and Hamoa SoCs. SCMI_QCOM_MEMLAT_DEVFREQ follows QCOM_CPUCP_MBOX at =m since it depends on that mailbox transport. Link: https://lore.kernel.org/lkml/20260507062237.78051-1-sibi.sankar@oss.qualcomm.com/ Signed-off-by: Sibi Sankar Signed-off-by: Xin Liu --- arch/arm64/configs/qcom.config | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/configs/qcom.config b/arch/arm64/configs/qcom.config index 9bb3776684b6b..957945a4f3cc3 100644 --- a/arch/arm64/configs/qcom.config +++ b/arch/arm64/configs/qcom.config @@ -47,6 +47,7 @@ CONFIG_POWERCAP=y CONFIG_PROC_EVENTS=y CONFIG_QCA808X_PHY=m CONFIG_QCOM_QMI_COOLING=y +CONFIG_QCOM_SCMI_GENERIC_EXT=y CONFIG_REMOTEPROC_THERMAL=y CONFIG_SCHED_DEBUG=y CONFIG_SCHEDSTATS=y @@ -109,6 +110,9 @@ CONFIG_SCSI_AACRAID=m CONFIG_SCSI_SMARTPQI=m # Support performance profiling CONFIG_FTRACE=y +# QCOM SCMI memlat bus scaling +CONFIG_DEVFREQ_GOV_REMOTE=y +CONFIG_SCMI_QCOM_MEMLAT_DEVFREQ=m # Kubernetes support CONFIG_CFS_BANDWIDTH=y CONFIG_CGROUP_FAVOR_DYNMODS=y