summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/arm/cpus.yaml4
-rw-r--r--Documentation/devicetree/bindings/firmware/arm,scmi.yaml15
-rw-r--r--Documentation/devicetree/bindings/power/power-domain.yaml17
-rw-r--r--MAINTAINERS1
-rw-r--r--drivers/base/power/common.c21
-rw-r--r--drivers/base/power/domain.c44
-rw-r--r--drivers/clk/clk-scmi.c96
-rw-r--r--drivers/cpufreq/scmi-cpufreq.c52
-rw-r--r--drivers/firmware/arm_scmi/Kconfig12
-rw-r--r--drivers/firmware/arm_scmi/Makefile1
-rw-r--r--drivers/firmware/arm_scmi/clock.c402
-rw-r--r--drivers/firmware/arm_scmi/driver.c1
-rw-r--r--drivers/firmware/arm_scmi/perf.c112
-rw-r--r--drivers/firmware/arm_scmi/powercap.c4
-rw-r--r--drivers/firmware/arm_scmi/smc.c35
-rw-r--r--drivers/firmware/arm_scpi.c13
-rw-r--r--drivers/opp/core.c60
-rw-r--r--drivers/opp/of.c10
-rw-r--r--drivers/opp/opp.h2
-rw-r--r--drivers/pmdomain/Makefile1
-rw-r--r--drivers/pmdomain/arm/Makefile4
-rw-r--r--drivers/pmdomain/arm/scmi_perf_domain.c184
-rw-r--r--drivers/pmdomain/arm/scmi_pm_domain.c (renamed from drivers/firmware/arm_scmi/scmi_pm_domain.c)0
-rw-r--r--include/linux/pm.h2
-rw-r--r--include/linux/pm_domain.h11
-rw-r--r--include/linux/pm_opp.h31
-rw-r--r--include/linux/scmi_protocol.h43
27 files changed, 994 insertions, 184 deletions
diff --git a/Documentation/devicetree/bindings/arm/cpus.yaml b/Documentation/devicetree/bindings/arm/cpus.yaml
index 0d17cbad9778..ad7fee0245fa 100644
--- a/Documentation/devicetree/bindings/arm/cpus.yaml
+++ b/Documentation/devicetree/bindings/arm/cpus.yaml
@@ -308,7 +308,9 @@ properties:
power-domains property.
For PSCI based platforms, the name corresponding to the index of the PSCI
- PM domain provider, must be "psci".
+ PM domain provider, must be "psci". For SCMI based platforms, the name
+ corresponding to the index of an SCMI performance domain provider, must be
+ "perf".
qcom,saw:
$ref: /schemas/types.yaml#/definitions/phandle
diff --git a/Documentation/devicetree/bindings/firmware/arm,scmi.yaml b/Documentation/devicetree/bindings/firmware/arm,scmi.yaml
index b138f3d23df8..4591523b51a0 100644
--- a/Documentation/devicetree/bindings/firmware/arm,scmi.yaml
+++ b/Documentation/devicetree/bindings/firmware/arm,scmi.yaml
@@ -38,6 +38,9 @@ properties:
with shmem address(4KB-page, offset) as parameters
items:
- const: arm,scmi-smc-param
+ - description: SCMI compliant firmware with Qualcomm SMC/HVC transport
+ items:
+ - const: qcom,scmi-smc
- description: SCMI compliant firmware with SCMI Virtio transport.
The virtio transport only supports a single device.
items:
@@ -149,8 +152,15 @@ properties:
'#clock-cells':
const: 1
- required:
- - '#clock-cells'
+ '#power-domain-cells':
+ const: 1
+
+ oneOf:
+ - required:
+ - '#clock-cells'
+
+ - required:
+ - '#power-domain-cells'
protocol@14:
$ref: '#/$defs/protocol-node'
@@ -306,6 +316,7 @@ else:
enum:
- arm,scmi-smc
- arm,scmi-smc-param
+ - qcom,scmi-smc
then:
required:
- arm,smc-id
diff --git a/Documentation/devicetree/bindings/power/power-domain.yaml b/Documentation/devicetree/bindings/power/power-domain.yaml
index d1235e562041..8fdb529d560b 100644
--- a/Documentation/devicetree/bindings/power/power-domain.yaml
+++ b/Documentation/devicetree/bindings/power/power-domain.yaml
@@ -13,8 +13,9 @@ maintainers:
description: |+
System on chip designs are often divided into multiple PM domains that can be
- used for power gating of selected IP blocks for power saving by reduced leakage
- current.
+ used for power gating of selected IP blocks for power saving by reduced
+ leakage current. Moreover, in some cases the similar PM domains may also be
+ capable of scaling performance for a group of IP blocks.
This device tree binding can be used to bind PM domain consumer devices with
their PM domains provided by PM domain providers. A PM domain provider can be
@@ -25,7 +26,7 @@ description: |+
properties:
$nodename:
- pattern: "^(power-controller|power-domain)([@-].*)?$"
+ pattern: "^(power-controller|power-domain|performance-domain)([@-].*)?$"
domain-idle-states:
$ref: /schemas/types.yaml#/definitions/phandle-array
@@ -44,11 +45,11 @@ properties:
operating-points-v2:
description:
- Phandles to the OPP tables of power domains provided by a power domain
- provider. If the provider provides a single power domain only or all
- the power domains provided by the provider have identical OPP tables,
- then this shall contain a single phandle. Refer to ../opp/opp-v2-base.yaml
- for more information.
+ Phandles to the OPP tables of power domains that are capable of scaling
+ performance, provided by a power domain provider. If the provider provides
+ a single power domain only or all the power domains provided by the
+ provider have identical OPP tables, then this shall contain a single
+ phandle. Refer to ../opp/opp-v2-base.yaml for more information.
"#power-domain-cells":
description:
diff --git a/MAINTAINERS b/MAINTAINERS
index 35977b269d5e..6dcf2926109a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20904,6 +20904,7 @@ F: drivers/clk/clk-sc[mp]i.c
F: drivers/cpufreq/sc[mp]i-cpufreq.c
F: drivers/firmware/arm_scmi/
F: drivers/firmware/arm_scpi.c
+F: drivers/pmdomain/arm/
F: drivers/powercap/arm_scmi_powercap.c
F: drivers/regulator/scmi-regulator.c
F: drivers/reset/reset-scmi.c
diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c
index 72115917e0bd..44ec20918a4d 100644
--- a/drivers/base/power/common.c
+++ b/drivers/base/power/common.c
@@ -228,3 +228,24 @@ void dev_pm_domain_set(struct device *dev, struct dev_pm_domain *pd)
device_pm_check_callbacks(dev);
}
EXPORT_SYMBOL_GPL(dev_pm_domain_set);
+
+/**
+ * dev_pm_domain_set_performance_state - Request a new performance state.
+ * @dev: The device to make the request for.
+ * @state: Target performance state for the device.
+ *
+ * This function should be called when a new performance state needs to be
+ * requested for a device that is attached to a PM domain. Note that, the
+ * support for performance scaling for PM domains is optional.
+ *
+ * Returns 0 on success and when performance scaling isn't supported, negative
+ * error code on failure.
+ */
+int dev_pm_domain_set_performance_state(struct device *dev, unsigned int state)
+{
+ if (dev->pm_domain && dev->pm_domain->set_performance_state)
+ return dev->pm_domain->set_performance_state(dev, state);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dev_pm_domain_set_performance_state);
diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c
index 5cb2023581d4..da1777e39eaa 100644
--- a/drivers/base/power/domain.c
+++ b/drivers/base/power/domain.c
@@ -130,6 +130,7 @@ static const struct genpd_lock_ops genpd_spin_ops = {
#define genpd_is_active_wakeup(genpd) (genpd->flags & GENPD_FLAG_ACTIVE_WAKEUP)
#define genpd_is_cpu_domain(genpd) (genpd->flags & GENPD_FLAG_CPU_DOMAIN)
#define genpd_is_rpm_always_on(genpd) (genpd->flags & GENPD_FLAG_RPM_ALWAYS_ON)
+#define genpd_is_opp_table_fw(genpd) (genpd->flags & GENPD_FLAG_OPP_TABLE_FW)
static inline bool irq_safe_dev_in_sleep_domain(struct device *dev,
const struct generic_pm_domain *genpd)
@@ -419,6 +420,25 @@ static void genpd_restore_performance_state(struct device *dev,
genpd_set_performance_state(dev, state);
}
+static int genpd_dev_pm_set_performance_state(struct device *dev,
+ unsigned int state)
+{
+ struct generic_pm_domain *genpd = dev_to_genpd(dev);
+ int ret = 0;
+
+ genpd_lock(genpd);
+ if (pm_runtime_suspended(dev)) {
+ dev_gpd_data(dev)->rpm_pstate = state;
+ } else {
+ ret = genpd_set_performance_state(dev, state);
+ if (!ret)
+ dev_gpd_data(dev)->rpm_pstate = 0;
+ }
+ genpd_unlock(genpd);
+
+ return ret;
+}
+
/**
* dev_pm_genpd_set_performance_state- Set performance state of device's power
* domain.
@@ -437,7 +457,6 @@ static void genpd_restore_performance_state(struct device *dev,
int dev_pm_genpd_set_performance_state(struct device *dev, unsigned int state)
{
struct generic_pm_domain *genpd;
- int ret = 0;
genpd = dev_to_genpd_safe(dev);
if (!genpd)
@@ -447,17 +466,7 @@ int dev_pm_genpd_set_performance_state(struct device *dev, unsigned int state)
!dev->power.subsys_data->domain_data))
return -EINVAL;
- genpd_lock(genpd);
- if (pm_runtime_suspended(dev)) {
- dev_gpd_data(dev)->rpm_pstate = state;
- } else {
- ret = genpd_set_performance_state(dev, state);
- if (!ret)
- dev_gpd_data(dev)->rpm_pstate = 0;
- }
- genpd_unlock(genpd);
-
- return ret;
+ return genpd_dev_pm_set_performance_state(dev, state);
}
EXPORT_SYMBOL_GPL(dev_pm_genpd_set_performance_state);
@@ -2079,6 +2088,7 @@ int pm_genpd_init(struct generic_pm_domain *genpd,
genpd->domain.ops.restore_noirq = genpd_restore_noirq;
genpd->domain.ops.complete = genpd_complete;
genpd->domain.start = genpd_dev_pm_start;
+ genpd->domain.set_performance_state = genpd_dev_pm_set_performance_state;
if (genpd->flags & GENPD_FLAG_PM_CLK) {
genpd->dev_ops.stop = pm_clk_suspend;
@@ -2328,7 +2338,7 @@ int of_genpd_add_provider_simple(struct device_node *np,
genpd->dev.of_node = np;
/* Parse genpd OPP table */
- if (genpd->set_performance_state) {
+ if (!genpd_is_opp_table_fw(genpd) && genpd->set_performance_state) {
ret = dev_pm_opp_of_add_table(&genpd->dev);
if (ret)
return dev_err_probe(&genpd->dev, ret, "Failed to add OPP table\n");
@@ -2343,7 +2353,7 @@ int of_genpd_add_provider_simple(struct device_node *np,
ret = genpd_add_provider(np, genpd_xlate_simple, genpd);
if (ret) {
- if (genpd->set_performance_state) {
+ if (!genpd_is_opp_table_fw(genpd) && genpd->set_performance_state) {
dev_pm_opp_put_opp_table(genpd->opp_table);
dev_pm_opp_of_remove_table(&genpd->dev);
}
@@ -2387,7 +2397,7 @@ int of_genpd_add_provider_onecell(struct device_node *np,
genpd->dev.of_node = np;
/* Parse genpd OPP table */
- if (genpd->set_performance_state) {
+ if (!genpd_is_opp_table_fw(genpd) && genpd->set_performance_state) {
ret = dev_pm_opp_of_add_table_indexed(&genpd->dev, i);
if (ret) {
dev_err_probe(&genpd->dev, ret,
@@ -2423,7 +2433,7 @@ error:
genpd->provider = NULL;
genpd->has_provider = false;
- if (genpd->set_performance_state) {
+ if (!genpd_is_opp_table_fw(genpd) && genpd->set_performance_state) {
dev_pm_opp_put_opp_table(genpd->opp_table);
dev_pm_opp_of_remove_table(&genpd->dev);
}
@@ -2455,7 +2465,7 @@ void of_genpd_del_provider(struct device_node *np)
if (gpd->provider == &np->fwnode) {
gpd->has_provider = false;
- if (!gpd->set_performance_state)
+ if (genpd_is_opp_table_fw(gpd) || !gpd->set_performance_state)
continue;
dev_pm_opp_put_opp_table(gpd->opp_table);
diff --git a/drivers/clk/clk-scmi.c b/drivers/clk/clk-scmi.c
index 2c7a830ce308..8cbe24789c24 100644
--- a/drivers/clk/clk-scmi.c
+++ b/drivers/clk/clk-scmi.c
@@ -13,13 +13,18 @@
#include <linux/scmi_protocol.h>
#include <asm/div64.h>
+#define NOT_ATOMIC false
+#define ATOMIC true
+
static const struct scmi_clk_proto_ops *scmi_proto_clk_ops;
struct scmi_clk {
u32 id;
+ struct device *dev;
struct clk_hw hw;
const struct scmi_clock_info *info;
const struct scmi_protocol_handle *ph;
+ struct clk_parent_data *parent_data;
};
#define to_scmi_clk(clk) container_of(clk, struct scmi_clk, hw)
@@ -74,38 +79,89 @@ static int scmi_clk_set_rate(struct clk_hw *hw, unsigned long rate,
return scmi_proto_clk_ops->rate_set(clk->ph, clk->id, rate);
}
+static int scmi_clk_set_parent(struct clk_hw *hw, u8 parent_index)
+{
+ struct scmi_clk *clk = to_scmi_clk(hw);
+
+ return scmi_proto_clk_ops->parent_set(clk->ph, clk->id, parent_index);
+}
+
+static u8 scmi_clk_get_parent(struct clk_hw *hw)
+{
+ struct scmi_clk *clk = to_scmi_clk(hw);
+ u32 parent_id, p_idx;
+ int ret;
+
+ ret = scmi_proto_clk_ops->parent_get(clk->ph, clk->id, &parent_id);
+ if (ret)
+ return 0;
+
+ for (p_idx = 0; p_idx < clk->info->num_parents; p_idx++) {
+ if (clk->parent_data[p_idx].index == parent_id)
+ break;
+ }
+
+ if (p_idx == clk->info->num_parents)
+ return 0;
+
+ return p_idx;
+}
+
+static int scmi_clk_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
+{
+ /*
+ * Suppose all the requested rates are supported, and let firmware
+ * to handle the left work.
+ */
+ return 0;
+}
+
static int scmi_clk_enable(struct clk_hw *hw)
{
struct scmi_clk *clk = to_scmi_clk(hw);
- return scmi_proto_clk_ops->enable(clk->ph, clk->id);
+ return scmi_proto_clk_ops->enable(clk->ph, clk->id, NOT_ATOMIC);
}
static void scmi_clk_disable(struct clk_hw *hw)
{
struct scmi_clk *clk = to_scmi_clk(hw);
- scmi_proto_clk_ops->disable(clk->ph, clk->id);
+ scmi_proto_clk_ops->disable(clk->ph, clk->id, NOT_ATOMIC);
}
static int scmi_clk_atomic_enable(struct clk_hw *hw)
{
struct scmi_clk *clk = to_scmi_clk(hw);
- return scmi_proto_clk_ops->enable_atomic(clk->ph, clk->id);
+ return scmi_proto_clk_ops->enable(clk->ph, clk->id, ATOMIC);
}
static void scmi_clk_atomic_disable(struct clk_hw *hw)
{
struct scmi_clk *clk = to_scmi_clk(hw);
- scmi_proto_clk_ops->disable_atomic(clk->ph, clk->id);
+ scmi_proto_clk_ops->disable(clk->ph, clk->id, ATOMIC);
+}
+
+static int scmi_clk_atomic_is_enabled(struct clk_hw *hw)
+{
+ int ret;
+ bool enabled = false;
+ struct scmi_clk *clk = to_scmi_clk(hw);
+
+ ret = scmi_proto_clk_ops->state_get(clk->ph, clk->id, &enabled, ATOMIC);
+ if (ret)
+ dev_warn(clk->dev,
+ "Failed to get state for clock ID %d\n", clk->id);
+
+ return !!enabled;
}
/*
- * We can provide enable/disable atomic callbacks only if the underlying SCMI
- * transport for an SCMI instance is configured to handle SCMI commands in an
- * atomic manner.
+ * We can provide enable/disable/is_enabled atomic callbacks only if the
+ * underlying SCMI transport for an SCMI instance is configured to handle
+ * SCMI commands in an atomic manner.
*
* When no SCMI atomic transport support is available we instead provide only
* the prepare/unprepare API, as allowed by the clock framework when atomic
@@ -121,6 +177,9 @@ static const struct clk_ops scmi_clk_ops = {
.set_rate = scmi_clk_set_rate,
.prepare = scmi_clk_enable,
.unprepare = scmi_clk_disable,
+ .set_parent = scmi_clk_set_parent,
+ .get_parent = scmi_clk_get_parent,
+ .determine_rate = scmi_clk_determine_rate,
};
static const struct clk_ops scmi_atomic_clk_ops = {
@@ -129,6 +188,10 @@ static const struct clk_ops scmi_atomic_clk_ops = {
.set_rate = scmi_clk_set_rate,
.enable = scmi_clk_atomic_enable,
.disable = scmi_clk_atomic_disable,
+ .is_enabled = scmi_clk_atomic_is_enabled,
+ .set_parent = scmi_clk_set_parent,
+ .get_parent = scmi_clk_get_parent,
+ .determine_rate = scmi_clk_determine_rate,
};
static int scmi_clk_ops_init(struct device *dev, struct scmi_clk *sclk,
@@ -139,9 +202,10 @@ static int scmi_clk_ops_init(struct device *dev, struct scmi_clk *sclk,
struct clk_init_data init = {
.flags = CLK_GET_RATE_NOCACHE,
- .num_parents = 0,
+ .num_parents = sclk->info->num_parents,
.ops = scmi_ops,
.name = sclk->info->name,
+ .parent_data = sclk->parent_data,
};
sclk->hw.init = &init;
@@ -213,11 +277,13 @@ static int scmi_clocks_probe(struct scmi_device *sdev)
sclk->info = scmi_proto_clk_ops->info_get(ph, idx);
if (!sclk->info) {
dev_dbg(dev, "invalid clock info for idx %d\n", idx);
+ devm_kfree(dev, sclk);
continue;
}
sclk->id = idx;
sclk->ph = ph;
+ sclk->dev = dev;
/*
* Note that when transport is atomic but SCMI protocol did not
@@ -230,9 +296,23 @@ static int scmi_clocks_probe(struct scmi_device *sdev)
else
scmi_ops = &scmi_clk_ops;
+ /* Initialize clock parent data. */
+ if (sclk->info->num_parents > 0) {
+ sclk->parent_data = devm_kcalloc(dev, sclk->info->num_parents,
+ sizeof(*sclk->parent_data), GFP_KERNEL);
+ if (!sclk->parent_data)
+ return -ENOMEM;
+
+ for (int i = 0; i < sclk->info->num_parents; i++) {
+ sclk->parent_data[i].index = sclk->info->parents[i];
+ sclk->parent_data[i].hw = hws[sclk->info->parents[i]];
+ }
+ }
+
err = scmi_clk_ops_init(dev, sclk, scmi_ops);
if (err) {
dev_err(dev, "failed to register clock %d\n", idx);
+ devm_kfree(dev, sclk->parent_data);
devm_kfree(dev, sclk);
hws[idx] = NULL;
} else {
diff --git a/drivers/cpufreq/scmi-cpufreq.c b/drivers/cpufreq/scmi-cpufreq.c
index f34e6382a4c5..c8a7ccc42c16 100644
--- a/drivers/cpufreq/scmi-cpufreq.c
+++ b/drivers/cpufreq/scmi-cpufreq.c
@@ -70,16 +70,36 @@ static unsigned int scmi_cpufreq_fast_switch(struct cpufreq_policy *policy,
return 0;
}
+static int scmi_cpu_domain_id(struct device *cpu_dev)
+{
+ struct device_node *np = cpu_dev->of_node;
+ struct of_phandle_args domain_id;
+ int index;
+
+ if (of_parse_phandle_with_args(np, "clocks", "#clock-cells", 0,
+ &domain_id)) {
+ /* Find the corresponding index for power-domain "perf". */
+ index = of_property_match_string(np, "power-domain-names",
+ "perf");
+ if (index < 0)
+ return -EINVAL;
+
+ if (of_parse_phandle_with_args(np, "power-domains",
+ "#power-domain-cells", index,
+ &domain_id))
+ return -EINVAL;
+ }
+
+ return domain_id.args[0];
+}
+
static int
-scmi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask)
+scmi_get_sharing_cpus(struct device *cpu_dev, int domain,
+ struct cpumask *cpumask)
{
- int cpu, domain, tdomain;
+ int cpu, tdomain;
struct device *tcpu_dev;
- domain = perf_ops->device_domain_id(cpu_dev);
- if (domain < 0)
- return domain;
-
for_each_possible_cpu(cpu) {
if (cpu == cpu_dev->id)
continue;
@@ -88,7 +108,7 @@ scmi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask)
if (!tcpu_dev)
continue;
- tdomain = perf_ops->device_domain_id(tcpu_dev);
+ tdomain = scmi_cpu_domain_id(tcpu_dev);
if (tdomain == domain)
cpumask_set_cpu(cpu, cpumask);
}
@@ -104,7 +124,7 @@ scmi_get_cpu_power(struct device *cpu_dev, unsigned long *power,
unsigned long Hz;
int ret, domain;
- domain = perf_ops->device_domain_id(cpu_dev);
+ domain = scmi_cpu_domain_id(cpu_dev);
if (domain < 0)
return domain;
@@ -126,7 +146,7 @@ scmi_get_cpu_power(struct device *cpu_dev, unsigned long *power,
static int scmi_cpufreq_init(struct cpufreq_policy *policy)
{
- int ret, nr_opp;
+ int ret, nr_opp, domain;
unsigned int latency;
struct device *cpu_dev;
struct scmi_data *priv;
@@ -138,6 +158,10 @@ static int scmi_cpufreq_init(struct cpufreq_policy *policy)
return -ENODEV;
}
+ domain = scmi_cpu_domain_id(cpu_dev);
+ if (domain < 0)
+ return domain;
+
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
@@ -148,7 +172,7 @@ static int scmi_cpufreq_init(struct cpufreq_policy *policy)
}
/* Obtain CPUs that share SCMI performance controls */
- ret = scmi_get_sharing_cpus(cpu_dev, policy->cpus);
+ ret = scmi_get_sharing_cpus(cpu_dev, domain, policy->cpus);
if (ret) {
dev_warn(cpu_dev, "failed to get sharing cpumask\n");
goto out_free_cpumask;
@@ -176,7 +200,7 @@ static int scmi_cpufreq_init(struct cpufreq_policy *policy)
*/
nr_opp = dev_pm_opp_get_opp_count(cpu_dev);
if (nr_opp <= 0) {
- ret = perf_ops->device_opps_add(ph, cpu_dev);
+ ret = perf_ops->device_opps_add(ph, cpu_dev, domain);
if (ret) {
dev_warn(cpu_dev, "failed to add opps to the device\n");
goto out_free_cpumask;
@@ -209,7 +233,7 @@ static int scmi_cpufreq_init(struct cpufreq_policy *policy)
}
priv->cpu_dev = cpu_dev;
- priv->domain_id = perf_ops->device_domain_id(cpu_dev);
+ priv->domain_id = domain;
policy->driver_data = priv;
policy->freq_table = freq_table;
@@ -217,14 +241,14 @@ static int scmi_cpufreq_init(struct cpufreq_policy *policy)
/* SCMI allows DVFS request for any domain from any CPU */
policy->dvfs_possible_from_any_cpu = true;
- latency = perf_ops->transition_latency_get(ph, cpu_dev);
+ latency = perf_ops->transition_latency_get(ph, domain);
if (!latency)
latency = CPUFREQ_ETERNAL;
policy->cpuinfo.transition_latency = latency;
policy->fast_switch_possible =
- perf_ops->fast_switch_possible(ph, cpu_dev);
+ perf_ops->fast_switch_possible(ph, domain);
return 0;
diff --git a/drivers/firmware/arm_scmi/Kconfig b/drivers/firmware/arm_scmi/Kconfig
index ea0f5083ac47..706d1264d038 100644
--- a/drivers/firmware/arm_scmi/Kconfig
+++ b/drivers/firmware/arm_scmi/Kconfig
@@ -181,6 +181,18 @@ config ARM_SCMI_POWER_DOMAIN
will be called scmi_pm_domain. Note this may needed early in boot
before rootfs may be available.
+config ARM_SCMI_PERF_DOMAIN
+ tristate "SCMI performance domain driver"
+ depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)
+ default y
+ select PM_GENERIC_DOMAINS if PM
+ help
+ This enables support for the SCMI performance domains which can be
+ enabled or disabled via the SCP firmware.
+
+ This driver can also be built as a module. If so, the module will be
+ called scmi_perf_domain.
+
config ARM_SCMI_POWER_CONTROL
tristate "SCMI system power control driver"
depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
index b31d78fa66cc..a7bc4796519c 100644
--- a/drivers/firmware/arm_scmi/Makefile
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -16,7 +16,6 @@ scmi-module-objs := $(scmi-driver-y) $(scmi-protocols-y) $(scmi-transport-y)
obj-$(CONFIG_ARM_SCMI_PROTOCOL) += scmi-core.o
obj-$(CONFIG_ARM_SCMI_PROTOCOL) += scmi-module.o
-obj-$(CONFIG_ARM_SCMI_POWER_DOMAIN) += scmi_pm_domain.o
obj-$(CONFIG_ARM_SCMI_POWER_CONTROL) += scmi_power_control.o
ifeq ($(CONFIG_THUMB2_KERNEL)$(CONFIG_CC_IS_CLANG),yy)
diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c
index 96060bf90a24..42b81c181d68 100644
--- a/drivers/firmware/arm_scmi/clock.c
+++ b/drivers/firmware/arm_scmi/clock.c
@@ -21,6 +21,17 @@ enum scmi_clock_protocol_cmd {
CLOCK_NAME_GET = 0x8,
CLOCK_RATE_NOTIFY = 0x9,
CLOCK_RATE_CHANGE_REQUESTED_NOTIFY = 0xA,
+ CLOCK_CONFIG_GET = 0xB,
+ CLOCK_POSSIBLE_PARENTS_GET = 0xC,
+ CLOCK_PARENT_SET = 0xD,
+ CLOCK_PARENT_GET = 0xE,
+};
+
+enum clk_state {
+ CLK_STATE_DISABLE,
+ CLK_STATE_ENABLE,
+ CLK_STATE_RESERVED,
+ CLK_STATE_UNCHANGED,
};
struct scmi_msg_resp_clock_protocol_attributes {
@@ -31,17 +42,57 @@ struct scmi_msg_resp_clock_protocol_attributes {
struct scmi_msg_resp_clock_attributes {
__le32 attributes;
-#define CLOCK_ENABLE BIT(0)
#define SUPPORTS_RATE_CHANGED_NOTIF(x) ((x) & BIT(31))
#define SUPPORTS_RATE_CHANGE_REQUESTED_NOTIF(x) ((x) & BIT(30))
#define SUPPORTS_EXTENDED_NAMES(x) ((x) & BIT(29))
+#define SUPPORTS_PARENT_CLOCK(x) ((x) & BIT(28))
u8 name[SCMI_SHORT_NAME_MAX_SIZE];
__le32 clock_enable_latency;
};
-struct scmi_clock_set_config {
+struct scmi_msg_clock_possible_parents {
+ __le32 id;
+ __le32 skip_parents;
+};
+
+struct scmi_msg_resp_clock_possible_parents {
+ __le32 num_parent_flags;
+#define NUM_PARENTS_RETURNED(x) ((x) & 0xff)
+#define NUM_PARENTS_REMAINING(x) ((x) >> 24)
+ __le32 possible_parents[];
+};
+
+struct scmi_msg_clock_set_parent {
+ __le32 id;
+ __le32 parent_id;
+};
+
+struct scmi_msg_clock_config_set {
+ __le32 id;
+ __le32 attributes;
+};
+
+/* Valid only from SCMI clock v2.1 */
+struct scmi_msg_clock_config_set_v2 {
__le32 id;
__le32 attributes;
+#define NULL_OEM_TYPE 0
+#define REGMASK_OEM_TYPE_SET GENMASK(23, 16)
+#define REGMASK_CLK_STATE GENMASK(1, 0)
+ __le32 oem_config_val;
+};
+
+struct scmi_msg_clock_config_get {
+ __le32 id;
+ __le32 flags;
+#define REGMASK_OEM_TYPE_GET GENMASK(7, 0)
+};
+
+struct scmi_msg_resp_clock_config_get {
+ __le32 attributes;
+ __le32 config;
+#define IS_CLK_ENABLED(x) le32_get_bits((x), BIT(0))
+ __le32 oem_config_val;
};
struct scmi_msg_clock_describe_rates {
@@ -100,6 +151,12 @@ struct clock_info {
int max_async_req;
atomic_t cur_async_req;
struct scmi_clock_info *clk;
+ int (*clock_config_set)(const struct scmi_protocol_handle *ph,
+ u32 clk_id, enum clk_state state,
+ u8 oem_type, u32 oem_val, bool atomic);
+ int (*clock_config_get)(const struct scmi_protocol_handle *ph,
+ u32 clk_id, u8 oem_type, u32 *attributes,
+ bool *enabled, u32 *oem_val, bool atomic);
};
static enum scmi_clock_protocol_cmd evt_2_cmd[] = {
@@ -132,6 +189,98 @@ scmi_clock_protocol_attributes_get(const struct scmi_protocol_handle *ph,
return ret;
}
+struct scmi_clk_ipriv {
+ struct device *dev;
+ u32 clk_id;
+ struct scmi_clock_info *clk;
+};
+
+static void iter_clk_possible_parents_prepare_message(void *message, unsigned int desc_index,
+ const void *priv)
+{
+ struct scmi_msg_clock_possible_parents *msg = message;
+ const struct scmi_clk_ipriv *p = priv;
+
+ msg->id = cpu_to_le32(p->clk_id);
+ /* Set the number of OPPs to be skipped/already read */
+ msg->skip_parents = cpu_to_le32(desc_index);
+}
+
+static int iter_clk_possible_parents_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_clock_possible_parents *r = response;
+ struct scmi_clk_ipriv *p = priv;
+ struct device *dev = ((struct scmi_clk_ipriv *)p)->dev;
+ u32 flags;
+
+ flags = le32_to_cpu(r->num_parent_flags);
+ st->num_returned = NUM_PARENTS_RETURNED(flags);
+ st->num_remaining = NUM_PARENTS_REMAINING(flags);
+
+ /*
+ * num parents is not declared previously anywhere so we
+ * assume it's returned+remaining on first call.
+ */
+ if (!st->max_resources) {
+ p->clk->num_parents = st->num_returned + st->num_remaining;
+ p->clk->parents = devm_kcalloc(dev, p->clk->num_parents,
+ sizeof(*p->clk->parents),
+ GFP_KERNEL);
+ if (!p->clk->parents) {
+ p->clk->num_parents = 0;
+ return -ENOMEM;
+ }
+ st->max_resources = st->num_returned + st->num_remaining;
+ }
+
+ return 0;
+}
+
+static int iter_clk_possible_parents_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st,
+ void *priv)
+{
+ const struct scmi_msg_resp_clock_possible_parents *r = response;
+ struct scmi_clk_ipriv *p = priv;
+
+ u32 *parent = &p->clk->parents[st->desc_index + st->loop_idx];
+
+ *parent = le32_to_cpu(r->possible_parents[st->loop_idx]);
+
+ return 0;
+}
+
+static int scmi_clock_possible_parents(const struct scmi_protocol_handle *ph, u32 clk_id,
+ struct scmi_clock_info *clk)
+{
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_clk_possible_parents_prepare_message,
+ .update_state = iter_clk_possible_parents_update_state,
+ .process_response = iter_clk_possible_parents_process_response,
+ };
+
+ struct scmi_clk_ipriv ppriv = {
+ .clk_id = clk_id,
+ .clk = clk,
+ .dev = ph->dev,
+ };
+ void *iter;
+ int ret;
+
+ iter = ph->hops->iter_response_init(ph, &ops, 0,
+ CLOCK_POSSIBLE_PARENTS_GET,
+ sizeof(struct scmi_msg_clock_possible_parents),
+ &ppriv);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ ret = ph->hops->iter_response_run(iter);
+
+ return ret;
+}
+
static int scmi_clock_attributes_get(const struct scmi_protocol_handle *ph,
u32 clk_id, struct scmi_clock_info *clk,
u32 version)
@@ -176,6 +325,8 @@ static int scmi_clock_attributes_get(const struct scmi_protocol_handle *ph,
clk->rate_changed_notifications = true;
if (SUPPORTS_RATE_CHANGE_REQUESTED_NOTIF(attributes))
clk->rate_change_requested_notifications = true;
+ if (SUPPORTS_PARENT_CLOCK(attributes))
+ scmi_clock_possible_parents(ph, clk_id, clk);
}
return ret;
@@ -193,12 +344,6 @@ static int rate_cmp_func(const void *_r1, const void *_r2)
return 1;
}
-struct scmi_clk_ipriv {
- struct device *dev;
- u32 clk_id;
- struct scmi_clock_info *clk;
-};
-
static void iter_clk_describe_prepare_message(void *message,
const unsigned int desc_index,
const void *priv)
@@ -395,11 +540,105 @@ static int scmi_clock_rate_set(const struct scmi_protocol_handle *ph,
static int
scmi_clock_config_set(const struct scmi_protocol_handle *ph, u32 clk_id,
- u32 config, bool atomic)
+ enum clk_state state, u8 __unused0, u32 __unused1,
+ bool atomic)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_clock_config_set *cfg;
+
+ if (state >= CLK_STATE_RESERVED)
+ return -EINVAL;
+
+ ret = ph->xops->xfer_get_init(ph, CLOCK_CONFIG_SET,
+ sizeof(*cfg), 0, &t);
+ if (ret)
+ return ret;
+
+ t->hdr.poll_completion = atomic;
+
+ cfg = t->tx.buf;
+ cfg->id = cpu_to_le32(clk_id);
+ cfg->attributes = cpu_to_le32(state);
+
+ ret = ph->xops->do_xfer(ph, t);
+
+ ph->xops->xfer_put(ph, t);
+ return ret;
+}
+
+static int
+scmi_clock_set_parent(const struct scmi_protocol_handle *ph, u32 clk_id,
+ u32 parent_id)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_clock_set_parent *cfg;
+ struct clock_info *ci = ph->get_priv(ph);
+ struct scmi_clock_info *clk;
+
+ if (clk_id >= ci->num_clocks)
+ return -EINVAL;
+
+ clk = ci->clk + clk_id;
+
+ if (parent_id >= clk->num_parents)
+ return -EINVAL;
+
+ ret = ph->xops->xfer_get_init(ph, CLOCK_PARENT_SET,
+ sizeof(*cfg), 0, &t);
+ if (ret)
+ return ret;
+
+ t->hdr.poll_completion = false;
+
+ cfg = t->tx.buf;
+ cfg->id = cpu_to_le32(clk_id);
+ cfg->parent_id = cpu_to_le32(clk->parents[parent_id]);
+
+ ret = ph->xops->do_xfer(ph, t);
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static int
+scmi_clock_get_parent(const struct scmi_protocol_handle *ph, u32 clk_id,
+ u32 *parent_id)
{
int ret;
struct scmi_xfer *t;
- struct scmi_clock_set_config *cfg;
+
+ ret = ph->xops->xfer_get_init(ph, CLOCK_PARENT_GET,
+ sizeof(__le32), sizeof(u32), &t);
+ if (ret)
+ return ret;
+
+ put_unaligned_le32(clk_id, t->tx.buf);
+
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret)
+ *parent_id = get_unaligned_le32(t->rx.buf);
+
+ ph->xops->xfer_put(ph, t);
+ return ret;
+}
+
+/* For SCMI clock v2.1 and onwards */
+static int
+scmi_clock_config_set_v2(const struct scmi_protocol_handle *ph, u32 clk_id,
+ enum clk_state state, u8 oem_type, u32 oem_val,
+ bool atomic)
+{
+ int ret;
+ u32 attrs;
+ struct scmi_xfer *t;
+ struct scmi_msg_clock_config_set_v2 *cfg;
+
+ if (state == CLK_STATE_RESERVED ||
+ (!oem_type && state == CLK_STATE_UNCHANGED))
+ return -EINVAL;
ret = ph->xops->xfer_get_init(ph, CLOCK_CONFIG_SET,
sizeof(*cfg), 0, &t);
@@ -408,9 +647,16 @@ scmi_clock_config_set(const struct scmi_protocol_handle *ph, u32 clk_id,
t->hdr.poll_completion = atomic;
+ attrs = FIELD_PREP(REGMASK_OEM_TYPE_SET, oem_type) |
+ FIELD_PREP(REGMASK_CLK_STATE, state);
+
cfg = t->tx.buf;
cfg->id = cpu_to_le32(clk_id);
- cfg->attributes = cpu_to_le32(config);
+ cfg->attributes = cpu_to_le32(attrs);
+ /* Clear in any case */
+ cfg->oem_config_val = cpu_to_le32(0);
+ if (oem_type)
+ cfg->oem_config_val = cpu_to_le32(oem_val);
ret = ph->xops->do_xfer(ph, t);
@@ -418,26 +664,124 @@ scmi_clock_config_set(const struct scmi_protocol_handle *ph, u32 clk_id,
return ret;
}
-static int scmi_clock_enable(const struct scmi_protocol_handle *ph, u32 clk_id)
+static int scmi_clock_enable(const struct scmi_protocol_handle *ph, u32 clk_id,
+ bool atomic)
{
- return scmi_clock_config_set(ph, clk_id, CLOCK_ENABLE, false);
+ struct clock_info *ci = ph->get_priv(ph);
+
+ return ci->clock_config_set(ph, clk_id, CLK_STATE_ENABLE,
+ NULL_OEM_TYPE, 0, atomic);
}
-static int scmi_clock_disable(const struct scmi_protocol_handle *ph, u32 clk_id)
+static int scmi_clock_disable(const struct scmi_protocol_handle *ph, u32 clk_id,
+ bool atomic)
{
- return scmi_clock_config_set(ph, clk_id, 0, false);
+ struct clock_info *ci = ph->get_priv(ph);
+
+ return ci->clock_config_set(ph, clk_id, CLK_STATE_DISABLE,
+ NULL_OEM_TYPE, 0, atomic);
}
-static int scmi_clock_enable_atomic(const struct scmi_protocol_handle *ph,
- u32 clk_id)
+/* For SCMI clock v2.1 and onwards */
+static int
+scmi_clock_config_get_v2(const struct scmi_protocol_handle *ph, u32 clk_id,
+ u8 oem_type, u32 *attributes, bool *enabled,
+ u32 *oem_val, bool atomic)
{
- return scmi_clock_config_set(ph, clk_id, CLOCK_ENABLE, true);
+ int ret;
+ u32 flags;
+ struct scmi_xfer *t;
+ struct scmi_msg_clock_config_get *cfg;
+
+ ret = ph->xops->xfer_get_init(ph, CLOCK_CONFIG_GET,
+ sizeof(*cfg), 0, &t);
+ if (ret)
+ return ret;
+
+ t->hdr.poll_completion = atomic;
+
+ flags = FIELD_PREP(REGMASK_OEM_TYPE_GET, oem_type);
+
+ cfg = t->tx.buf;
+ cfg->id = cpu_to_le32(clk_id);
+ cfg->flags = cpu_to_le32(flags);
+
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ struct scmi_msg_resp_clock_config_get *resp = t->rx.buf;
+
+ if (attributes)
+ *attributes = le32_to_cpu(resp->attributes);
+
+ if (enabled)
+ *enabled = IS_CLK_ENABLED(resp->config);
+
+ if (oem_val && oem_type)
+ *oem_val = le32_to_cpu(resp->oem_config_val);
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static int
+scmi_clock_config_get(const struct scmi_protocol_handle *ph, u32 clk_id,
+ u8 oem_type, u32 *attributes, bool *enabled,
+ u32 *oem_val, bool atomic)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_clock_attributes *resp;
+
+ if (!enabled)
+ return -EINVAL;
+
+ ret = ph->xops->xfer_get_init(ph, CLOCK_ATTRIBUTES,
+ sizeof(clk_id), sizeof(*resp), &t);
+ if (ret)
+ return ret;
+
+ t->hdr.poll_completion = atomic;
+ put_unaligned_le32(clk_id, t->tx.buf);
+ resp = t->rx.buf;
+
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret)
+ *enabled = IS_CLK_ENABLED(resp->attributes);
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
}
-static int scmi_clock_disable_atomic(const struct scmi_protocol_handle *ph,
- u32 clk_id)
+static int scmi_clock_state_get(const struct scmi_protocol_handle *ph,
+ u32 clk_id, bool *enabled, bool atomic)
{
- return scmi_clock_config_set(ph, clk_id, 0, true);
+ struct clock_info *ci = ph->get_priv(ph);
+
+ return ci->clock_config_get(ph, clk_id, NULL_OEM_TYPE, NULL,
+ enabled, NULL, atomic);
+}
+
+static int scmi_clock_config_oem_set(const struct scmi_protocol_handle *ph,
+ u32 clk_id, u8 oem_type, u32 oem_val,
+ bool atomic)
+{
+ struct clock_info *ci = ph->get_priv(ph);
+
+ return ci->clock_config_set(ph, clk_id, CLK_STATE_UNCHANGED,
+ oem_type, oem_val, atomic);
+}
+
+static int scmi_clock_config_oem_get(const struct scmi_protocol_handle *ph,
+ u32 clk_id, u8 oem_type, u32 *oem_val,
+ u32 *attributes, bool atomic)
+{
+ struct clock_info *ci = ph->get_priv(ph);
+
+ return ci->clock_config_get(ph, clk_id, oem_type, attributes,
+ NULL, oem_val, atomic);
}
static int scmi_clock_count_get(const struct scmi_protocol_handle *ph)
@@ -470,8 +814,11 @@ static const struct scmi_clk_proto_ops clk_proto_ops = {
.rate_set = scmi_clock_rate_set,
.enable = scmi_clock_enable,
.disable = scmi_clock_disable,
- .enable_atomic = scmi_clock_enable_atomic,
- .disable_atomic = scmi_clock_disable_atomic,
+ .state_get = scmi_clock_state_get,
+ .config_oem_get = scmi_clock_config_oem_get,
+ .config_oem_set = scmi_clock_config_oem_set,
+ .parent_set = scmi_clock_set_parent,
+ .parent_get = scmi_clock_get_parent,
};
static int scmi_clk_rate_notify(const struct scmi_protocol_handle *ph,
@@ -604,6 +951,15 @@ static int scmi_clock_protocol_init(const struct scmi_protocol_handle *ph)
scmi_clock_describe_rates_get(ph, clkid, clk);
}
+ if (PROTOCOL_REV_MAJOR(version) >= 0x2 &&
+ PROTOCOL_REV_MINOR(version) >= 0x1) {
+ cinfo->clock_config_set = scmi_clock_config_set_v2;
+ cinfo->clock_config_get = scmi_clock_config_get_v2;
+ } else {
+ cinfo->clock_config_set = scmi_clock_config_set;
+ cinfo->clock_config_get = scmi_clock_config_get;
+ }
+
cinfo->version = version;
return ph->set_priv(ph, cinfo);
}
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 87383c05424b..09371f40d61f 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -2915,6 +2915,7 @@ static const struct of_device_id scmi_of_match[] = {
#ifdef CONFIG_ARM_SCMI_TRANSPORT_SMC
{ .compatible = "arm,scmi-smc", .data = &scmi_smc_desc},
{ .compatible = "arm,scmi-smc-param", .data = &scmi_smc_desc},
+ { .compatible = "qcom,scmi-smc", .data = &scmi_smc_desc},
#endif
#ifdef CONFIG_ARM_SCMI_TRANSPORT_VIRTIO
{ .compatible = "arm,scmi-virtio", .data = &scmi_virtio_desc},
diff --git a/drivers/firmware/arm_scmi/perf.c b/drivers/firmware/arm_scmi/perf.c
index 30dedd6ebfde..c2435be0ae1b 100644
--- a/drivers/firmware/arm_scmi/perf.c
+++ b/drivers/firmware/arm_scmi/perf.c
@@ -145,7 +145,6 @@ struct scmi_msg_resp_perf_describe_levels_v4 {
struct perf_dom_info {
u32 id;
bool set_limits;
- bool set_perf;
bool perf_limit_notify;
bool perf_level_notify;
bool perf_fastchannels;
@@ -154,7 +153,7 @@ struct perf_dom_info {
u32 sustained_freq_khz;
u32 sustained_perf_level;
u32 mult_factor;
- char name[SCMI_MAX_STR_SIZE];
+ struct scmi_perf_domain_info info;
struct scmi_opp opp[MAX_OPPS];
struct scmi_fc_info *fc_info;
struct xarray opps_by_idx;
@@ -257,7 +256,7 @@ scmi_perf_domain_attributes_get(const struct scmi_protocol_handle *ph,
flags = le32_to_cpu(attr->flags);
dom_info->set_limits = SUPPORTS_SET_LIMITS(flags);
- dom_info->set_perf = SUPPORTS_SET_PERF_LVL(flags);
+ dom_info->info.set_perf = SUPPORTS_SET_PERF_LVL(flags);
dom_info->perf_limit_notify = SUPPORTS_PERF_LIMIT_NOTIFY(flags);
dom_info->perf_level_notify = SUPPORTS_PERF_LEVEL_NOTIFY(flags);
dom_info->perf_fastchannels = SUPPORTS_PERF_FASTCHANNELS(flags);
@@ -276,7 +275,8 @@ scmi_perf_domain_attributes_get(const struct scmi_protocol_handle *ph,
dom_info->mult_factor =
(dom_info->sustained_freq_khz * 1000) /
dom_info->sustained_perf_level;
- strscpy(dom_info->name, attr->name, SCMI_SHORT_NAME_MAX_SIZE);
+ strscpy(dom_info->info.name, attr->name,
+ SCMI_SHORT_NAME_MAX_SIZE);
}
ph->xops->xfer_put(ph, t);
@@ -288,7 +288,7 @@ scmi_perf_domain_attributes_get(const struct scmi_protocol_handle *ph,
if (!ret && PROTOCOL_REV_MAJOR(version) >= 0x3 &&
SUPPORTS_EXTENDED_NAMES(flags))
ph->hops->extended_name_get(ph, PERF_DOMAIN_NAME_GET,
- dom_info->id, dom_info->name,
+ dom_info->id, dom_info->info.name,
SCMI_MAX_STR_SIZE);
if (dom_info->level_indexing_mode) {
@@ -423,6 +423,36 @@ scmi_perf_describe_levels_get(const struct scmi_protocol_handle *ph,
return ret;
}
+static int scmi_perf_num_domains_get(const struct scmi_protocol_handle *ph)
+{
+ struct scmi_perf_info *pi = ph->get_priv(ph);
+
+ return pi->num_domains;
+}
+
+static inline struct perf_dom_info *
+scmi_perf_domain_lookup(const struct scmi_protocol_handle *ph, u32 domain)
+{
+ struct scmi_perf_info *pi = ph->get_priv(ph);
+
+ if (domain >= pi->num_domains)
+ return ERR_PTR(-EINVAL);
+
+ return pi->dom_info + domain;
+}
+
+static const struct scmi_perf_domain_info *
+scmi_perf_info_get(const struct scmi_protocol_handle *ph, u32 domain)
+{
+ struct perf_dom_info *dom;
+
+ dom = scmi_perf_domain_lookup(ph, domain);
+ if (IS_ERR(dom))
+ return ERR_PTR(-EINVAL);
+
+ return &dom->info;
+}
+
static int scmi_perf_msg_limits_set(const struct scmi_protocol_handle *ph,
u32 domain, u32 max_perf, u32 min_perf)
{
@@ -446,17 +476,6 @@ static int scmi_perf_msg_limits_set(const struct scmi_protocol_handle *ph,
return ret;
}
-static inline struct perf_dom_info *
-scmi_perf_domain_lookup(const struct scmi_protocol_handle *ph, u32 domain)
-{
- struct scmi_perf_info *pi = ph->get_priv(ph);
-
- if (domain >= pi->num_domains)
- return ERR_PTR(-EINVAL);
-
- return pi->dom_info + domain;
-}
-
static int __scmi_perf_limits_set(const struct scmi_protocol_handle *ph,
struct perf_dom_info *dom, u32 max_perf,
u32 min_perf)
@@ -763,71 +782,46 @@ static void scmi_perf_domain_init_fc(const struct scmi_protocol_handle *ph,
*p_fc = fc;
}
-/* Device specific ops */
-static int scmi_dev_domain_id(struct device *dev)
-{
- struct of_phandle_args clkspec;
-
- if (of_parse_phandle_with_args(dev->of_node, "clocks", "#clock-cells",
- 0, &clkspec))
- return -EINVAL;
-
- return clkspec.args[0];
-}
-
static int scmi_dvfs_device_opps_add(const struct scmi_protocol_handle *ph,
- struct device *dev)
+ struct device *dev, u32 domain)
{
- int idx, ret, domain;
+ int idx, ret;
unsigned long freq;
- struct scmi_opp *opp;
+ struct dev_pm_opp_data data = {};
struct perf_dom_info *dom;
- domain = scmi_dev_domain_id(dev);
- if (domain < 0)
- return -EINVAL;
-
dom = scmi_perf_domain_lookup(ph, domain);
if (IS_ERR(dom))
return PTR_ERR(dom);
- for (opp = dom->opp, idx = 0; idx < dom->opp_count; idx++, opp++) {
+ for (idx = 0; idx < dom->opp_count; idx++) {
if (!dom->level_indexing_mode)
- freq = opp->perf * dom->mult_factor;
+ freq = dom->opp[idx].perf * dom->mult_factor;
else
- freq = opp->indicative_freq * 1000;
+ freq = dom->opp[idx].indicative_freq * 1000;
+
+ data.level = dom->opp[idx].perf;
+ data.freq = freq;
- ret = dev_pm_opp_add(dev, freq, 0);
+ ret = dev_pm_opp_add_dynamic(dev, &data);
if (ret) {
dev_warn(dev, "failed to add opp %luHz\n", freq);
-
- while (idx-- > 0) {
- if (!dom->level_indexing_mode)
- freq = (--opp)->perf * dom->mult_factor;
- else
- freq = (--opp)->indicative_freq * 1000;
- dev_pm_opp_remove(dev, freq);
- }
+ dev_pm_opp_remove_all_dynamic(dev);
return ret;
}
dev_dbg(dev, "[%d][%s]:: Registered OPP[%d] %lu\n",
- domain, dom->name, idx, freq);
+ domain, dom->info.name, idx, freq);
}
return 0;
}
static int
scmi_dvfs_transition_latency_get(const struct scmi_protocol_handle *ph,
- struct device *dev)
+ u32 domain)
{
- int domain;
struct perf_dom_info *dom;
- domain = scmi_dev_domain_id(dev);
- if (domain < 0)
- return -EINVAL;
-
dom = scmi_perf_domain_lookup(ph, domain);
if (IS_ERR(dom))
return PTR_ERR(dom);
@@ -923,15 +917,10 @@ static int scmi_dvfs_est_power_get(const struct scmi_protocol_handle *ph,
}
static bool scmi_fast_switch_possible(const struct scmi_protocol_handle *ph,
- struct device *dev)
+ u32 domain)
{
- int domain;
struct perf_dom_info *dom;
- domain = scmi_dev_domain_id(dev);
- if (domain < 0)
- return false;
-
dom = scmi_perf_domain_lookup(ph, domain);
if (IS_ERR(dom))
return false;
@@ -948,11 +937,12 @@ scmi_power_scale_get(const struct scmi_protocol_handle *ph)
}
static const struct scmi_perf_proto_ops perf_proto_ops = {
+ .num_domains_get = scmi_perf_num_domains_get,
+ .info_get = scmi_perf_info_get,
.limits_set = scmi_perf_limits_set,
.limits_get = scmi_perf_limits_get,
.level_set = scmi_perf_level_set,
.level_get = scmi_perf_level_get,
- .device_domain_id = scmi_dev_domain_id,
.transition_latency_get = scmi_dvfs_transition_latency_get,
.device_opps_add = scmi_dvfs_device_opps_add,
.freq_set = scmi_dvfs_freq_set,
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index 244929cb4f3e..cb5617443a14 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -360,8 +360,8 @@ static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
msg = t->tx.buf;
msg->domain = cpu_to_le32(pc->id);
msg->flags =
- cpu_to_le32(FIELD_PREP(CAP_SET_ASYNC, !!pc->async_powercap_cap_set) |
- FIELD_PREP(CAP_SET_IGNORE_DRESP, !!ignore_dresp));
+ cpu_to_le32(FIELD_PREP(CAP_SET_ASYNC, pc->async_powercap_cap_set) |
+ FIELD_PREP(CAP_SET_IGNORE_DRESP, ignore_dresp));
msg->value = cpu_to_le32(power_cap);
if (!pc->async_powercap_cap_set || ignore_dresp) {
diff --git a/drivers/firmware/arm_scmi/smc.c b/drivers/firmware/arm_scmi/smc.c
index c193516a254d..7611e9665038 100644
--- a/drivers/firmware/arm_scmi/smc.c
+++ b/drivers/firmware/arm_scmi/smc.c
@@ -15,6 +15,7 @@
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
+#include <linux/limits.h>
#include <linux/processor.h>
#include <linux/slab.h>
@@ -50,6 +51,8 @@
* @func_id: smc/hvc call function id
* @param_page: 4K page number of the shmem channel
* @param_offset: Offset within the 4K page of the shmem channel
+ * @cap_id: smc/hvc doorbell's capability id to be used on Qualcomm virtual
+ * platforms
*/
struct scmi_smc {
@@ -60,9 +63,10 @@ struct scmi_smc {
struct mutex shmem_lock;
#define INFLIGHT_NONE MSG_TOKEN_MAX
atomic_t inflight;
- u32 func_id;
- u32 param_page;
- u32 param_offset;
+ unsigned long func_id;
+ unsigned long param_page;
+ unsigned long param_offset;
+ unsigned long cap_id;
};
static irqreturn_t smc_msg_done_isr(int irq, void *data)
@@ -124,6 +128,7 @@ static int smc_chan_setup(struct scmi_chan_info *cinfo, struct device *dev,
bool tx)
{
struct device *cdev = cinfo->dev;
+ unsigned long cap_id = ULONG_MAX;
struct scmi_smc *scmi_info;
resource_size_t size;
struct resource res;
@@ -162,6 +167,18 @@ static int smc_chan_setup(struct scmi_chan_info *cinfo, struct device *dev,
if (ret < 0)
return ret;
+ if (of_device_is_compatible(dev->of_node, "qcom,scmi-smc")) {
+ void __iomem *ptr = (void __iomem *)scmi_info->shmem + size - 8;
+ /* The capability-id is kept in last 8 bytes of shmem.
+ * +-------+ <-- 0
+ * | shmem |
+ * +-------+ <-- size - 8
+ * | capId |
+ * +-------+ <-- size
+ */
+ memcpy_fromio(&cap_id, ptr, sizeof(cap_id));
+ }
+
if (of_device_is_compatible(dev->of_node, "arm,scmi-smc-param")) {
scmi_info->param_page = SHMEM_PAGE(res.start);
scmi_info->param_offset = SHMEM_OFFSET(res.start);
@@ -184,6 +201,7 @@ static int smc_chan_setup(struct scmi_chan_info *cinfo, struct device *dev,
}
scmi_info->func_id = func_id;
+ scmi_info->cap_id = cap_id;
scmi_info->cinfo = cinfo;
smc_channel_lock_init(scmi_info);
cinfo->transport_info = scmi_info;
@@ -211,8 +229,6 @@ static int smc_send_message(struct scmi_chan_info *cinfo,
{
struct scmi_smc *scmi_info = cinfo->transport_info;
struct arm_smccc_res res;
- unsigned long page = scmi_info->param_page;
- unsigned long offset = scmi_info->param_offset;
/*
* Channel will be released only once response has been
@@ -222,8 +238,13 @@ static int smc_send_message(struct scmi_chan_info *cinfo,
shmem_tx_prepare(scmi_info->shmem, xfer, cinfo);
- arm_smccc_1_1_invoke(scmi_info->func_id, page, offset, 0, 0, 0, 0, 0,
- &res);
+ if (scmi_info->cap_id != ULONG_MAX)
+ arm_smccc_1_1_invoke(scmi_info->func_id, scmi_info->cap_id, 0,
+ 0, 0, 0, 0, 0, &res);
+ else
+ arm_smccc_1_1_invoke(scmi_info->func_id, scmi_info->param_page,
+ scmi_info->param_offset, 0, 0, 0, 0, 0,
+ &res);
/* Only SMCCC_RET_NOT_SUPPORTED is valid error code */
if (res.a0) {
diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c
index 435d0e2658a4..3f123f592cb4 100644
--- a/drivers/firmware/arm_scpi.c
+++ b/drivers/firmware/arm_scpi.c
@@ -26,9 +26,12 @@
#include <linux/list.h>
#include <linux/mailbox_client.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
+#include <linux/platform_device.h>
#include <linux/printk.h>
+#include <linux/property.h>
#include <linux/pm_opp.h>
#include <linux/scpi_protocol.h>
#include <linux/slab.h>
@@ -894,11 +897,6 @@ static int scpi_alloc_xfer_list(struct device *dev, struct scpi_chan *ch)
return 0;
}
-static const struct of_device_id legacy_scpi_of_match[] = {
- {.compatible = "arm,scpi-pre-1.0"},
- {},
-};
-
static const struct of_device_id shmem_of_match[] __maybe_unused = {
{ .compatible = "amlogic,meson-gxbb-scp-shmem", },
{ .compatible = "amlogic,meson-axg-scp-shmem", },
@@ -919,8 +917,7 @@ static int scpi_probe(struct platform_device *pdev)
if (!scpi_drvinfo)
return -ENOMEM;
- if (of_match_device(legacy_scpi_of_match, &pdev->dev))
- scpi_drvinfo->is_legacy = true;
+ scpi_drvinfo->is_legacy = !!device_get_match_data(dev);
count = of_count_phandle_with_args(np, "mboxes", "#mbox-cells");
if (count < 0) {
@@ -1038,7 +1035,7 @@ static int scpi_probe(struct platform_device *pdev)
static const struct of_device_id scpi_of_match[] = {
{.compatible = "arm,scpi"},
- {.compatible = "arm,scpi-pre-1.0"},
+ {.compatible = "arm,scpi-pre-1.0", .data = (void *)1UL },
{},
};
diff --git a/drivers/opp/core.c b/drivers/opp/core.c
index 919cc53bc02e..8978e94c9ca1 100644
--- a/drivers/opp/core.c
+++ b/drivers/opp/core.c
@@ -1030,7 +1030,7 @@ static int _set_performance_state(struct device *dev, struct device *pd_dev,
if (!pd_dev)
return 0;
- ret = dev_pm_genpd_set_performance_state(pd_dev, pstate);
+ ret = dev_pm_domain_set_performance_state(pd_dev, pstate);
if (ret) {
dev_err(dev, "Failed to set performance state of %s: %d (%d)\n",
dev_name(pd_dev), pstate, ret);
@@ -1107,6 +1107,28 @@ void _update_set_required_opps(struct opp_table *opp_table)
opp_table->set_required_opps = _opp_set_required_opps_generic;
}
+static int _set_opp_level(struct device *dev, struct opp_table *opp_table,
+ struct dev_pm_opp *opp)
+{
+ unsigned int level = 0;
+ int ret = 0;
+
+ if (opp) {
+ if (!opp->level)
+ return 0;
+
+ level = opp->level;
+ }
+
+ /* Request a new performance state through the device's PM domain. */
+ ret = dev_pm_domain_set_performance_state(dev, level);
+ if (ret)
+ dev_err(dev, "Failed to set performance state %u (%d)\n", level,
+ ret);
+
+ return ret;
+}
+
static void _find_current_opp(struct device *dev, struct opp_table *opp_table)
{
struct dev_pm_opp *opp = ERR_PTR(-ENODEV);
@@ -1154,8 +1176,13 @@ static int _disable_opp_table(struct device *dev, struct opp_table *opp_table)
if (opp_table->regulators)
regulator_disable(opp_table->regulators[0]);
+ ret = _set_opp_level(dev, opp_table, NULL);
+ if (ret)
+ goto out;
+
ret = _set_required_opps(dev, opp_table, NULL, false);
+out:
opp_table->enabled = false;
return ret;
}
@@ -1198,6 +1225,10 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table,
return ret;
}
+ ret = _set_opp_level(dev, opp_table, opp);
+ if (ret)
+ return ret;
+
ret = _set_opp_bw(opp_table, opp, dev);
if (ret) {
dev_err(dev, "Failed to set bw: %d\n", ret);
@@ -1241,6 +1272,10 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table,
return ret;
}
+ ret = _set_opp_level(dev, opp_table, opp);
+ if (ret)
+ return ret;
+
ret = _set_required_opps(dev, opp_table, opp, false);
if (ret) {
dev_err(dev, "Failed to set required opps: %d\n", ret);
@@ -2002,8 +2037,7 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
* _opp_add_v1() - Allocate a OPP based on v1 bindings.
* @opp_table: OPP table
* @dev: device for which we do this operation
- * @freq: Frequency in Hz for this OPP
- * @u_volt: Voltage in uVolts for this OPP
+ * @data: The OPP data for the OPP to add
* @dynamic: Dynamically added OPPs.
*
* This function adds an opp definition to the opp table and returns status.
@@ -2021,10 +2055,10 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
* -ENOMEM Memory allocation failure
*/
int _opp_add_v1(struct opp_table *opp_table, struct device *dev,
- unsigned long freq, long u_volt, bool dynamic)
+ struct dev_pm_opp_data *data, bool dynamic)
{
struct dev_pm_opp *new_opp;
- unsigned long tol;
+ unsigned long tol, u_volt = data->u_volt;
int ret;
if (!assert_single_clk(opp_table))
@@ -2035,7 +2069,8 @@ int _opp_add_v1(struct opp_table *opp_table, struct device *dev,
return -ENOMEM;
/* populate the opp table */
- new_opp->rates[0] = freq;
+ new_opp->rates[0] = data->freq;
+ new_opp->level = data->level;
tol = u_volt * opp_table->voltage_tolerance_v1 / 100;
new_opp->supplies[0].u_volt = u_volt;
new_opp->supplies[0].u_volt_min = u_volt - tol;
@@ -2825,10 +2860,9 @@ unlock:
}
/**
- * dev_pm_opp_add() - Add an OPP table from a table definitions
- * @dev: device for which we do this operation
- * @freq: Frequency in Hz for this OPP
- * @u_volt: Voltage in uVolts for this OPP
+ * dev_pm_opp_add_dynamic() - Add an OPP table from a table definitions
+ * @dev: The device for which we do this operation
+ * @data: The OPP data for the OPP to add
*
* This function adds an opp definition to the opp table and returns status.
* The opp is made available by default and it can be controlled using
@@ -2841,7 +2875,7 @@ unlock:
* Duplicate OPPs (both freq and volt are same) and !opp->available
* -ENOMEM Memory allocation failure
*/
-int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt)
+int dev_pm_opp_add_dynamic(struct device *dev, struct dev_pm_opp_data *data)
{
struct opp_table *opp_table;
int ret;
@@ -2853,13 +2887,13 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt)
/* Fix regulator count for dynamic OPPs */
opp_table->regulator_count = 1;
- ret = _opp_add_v1(opp_table, dev, freq, u_volt, true);
+ ret = _opp_add_v1(opp_table, dev, data, true);
if (ret)
dev_pm_opp_put_opp_table(opp_table);
return ret;
}
-EXPORT_SYMBOL_GPL(dev_pm_opp_add);
+EXPORT_SYMBOL_GPL(dev_pm_opp_add_dynamic);
/**
* _opp_set_availability() - helper to set the availability of an opp
diff --git a/drivers/opp/of.c b/drivers/opp/of.c
index ada4963c7cfa..ade6d42cae46 100644
--- a/drivers/opp/of.c
+++ b/drivers/opp/of.c
@@ -1077,13 +1077,15 @@ static int _of_add_opp_table_v1(struct device *dev, struct opp_table *opp_table)
val = prop->value;
while (nr) {
- unsigned long freq = be32_to_cpup(val++) * 1000;
- unsigned long volt = be32_to_cpup(val++);
+ struct dev_pm_opp_data data = {
+ .freq = be32_to_cpup(val++) * 1000,
+ .u_volt = be32_to_cpup(val++),
+ };
- ret = _opp_add_v1(opp_table, dev, freq, volt, false);
+ ret = _opp_add_v1(opp_table, dev, &data, false);
if (ret) {
dev_err(dev, "%s: Failed to add OPP %ld (%d)\n",
- __func__, freq, ret);
+ __func__, data.freq, ret);
goto remove_static_opp;
}
nr -= 2;
diff --git a/drivers/opp/opp.h b/drivers/opp/opp.h
index 8a5ea38f3a3d..fefdf9845692 100644
--- a/drivers/opp/opp.h
+++ b/drivers/opp/opp.h
@@ -251,7 +251,7 @@ struct dev_pm_opp *_opp_allocate(struct opp_table *opp_table);
void _opp_free(struct dev_pm_opp *opp);
int _opp_compare_key(struct opp_table *opp_table, struct dev_pm_opp *opp1, struct dev_pm_opp *opp2);
int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table);
-int _opp_add_v1(struct opp_table *opp_table, struct device *dev, unsigned long freq, long u_volt, bool dynamic);
+int _opp_add_v1(struct opp_table *opp_table, struct device *dev, struct dev_pm_opp_data *data, bool dynamic);
void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask, int last_cpu);
struct opp_table *_add_opp_table_indexed(struct device *dev, int index, bool getclk);
void _put_opp_list_kref(struct opp_table *opp_table);
diff --git a/drivers/pmdomain/Makefile b/drivers/pmdomain/Makefile
index 666753676e5c..f0326b27b30b 100644
--- a/drivers/pmdomain/Makefile
+++ b/drivers/pmdomain/Makefile
@@ -2,6 +2,7 @@
obj-y += actions/
obj-y += amlogic/
obj-y += apple/
+obj-y += arm/
obj-y += bcm/
obj-y += imx/
obj-y += mediatek/
diff --git a/drivers/pmdomain/arm/Makefile b/drivers/pmdomain/arm/Makefile
new file mode 100644
index 000000000000..cfcb1f6cdd90
--- /dev/null
+++ b/drivers/pmdomain/arm/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_ARM_SCMI_PERF_DOMAIN) += scmi_perf_domain.o
+obj-$(CONFIG_ARM_SCMI_POWER_DOMAIN) += scmi_pm_domain.o
diff --git a/drivers/pmdomain/arm/scmi_perf_domain.c b/drivers/pmdomain/arm/scmi_perf_domain.c
new file mode 100644
index 000000000000..bc3f78abb6da
--- /dev/null
+++ b/drivers/pmdomain/arm/scmi_perf_domain.c
@@ -0,0 +1,184 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SCMI performance domain support.
+ *
+ * Copyright (C) 2023 Linaro Ltd.
+ */
+
+#include <linux/err.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_opp.h>
+#include <linux/scmi_protocol.h>
+#include <linux/slab.h>
+
+struct scmi_perf_domain {
+ struct generic_pm_domain genpd;
+ const struct scmi_perf_proto_ops *perf_ops;
+ const struct scmi_protocol_handle *ph;
+ const struct scmi_perf_domain_info *info;
+ u32 domain_id;
+};
+
+#define to_scmi_pd(pd) container_of(pd, struct scmi_perf_domain, genpd)
+
+static int
+scmi_pd_set_perf_state(struct generic_pm_domain *genpd, unsigned int state)
+{
+ struct scmi_perf_domain *pd = to_scmi_pd(genpd);
+ int ret;
+
+ if (!pd->info->set_perf)
+ return 0;
+
+ if (!state)
+ return -EINVAL;
+
+ ret = pd->perf_ops->level_set(pd->ph, pd->domain_id, state, true);
+ if (ret)
+ dev_warn(&genpd->dev, "Failed with %d when trying to set %d perf level",
+ ret, state);
+
+ return ret;
+}
+
+static int
+scmi_pd_attach_dev(struct generic_pm_domain *genpd, struct device *dev)
+{
+ struct scmi_perf_domain *pd = to_scmi_pd(genpd);
+ int ret;
+
+ /*
+ * Allow the device to be attached, but don't add the OPP table unless
+ * the performance level can be changed.
+ */
+ if (!pd->info->set_perf)
+ return 0;
+
+ ret = pd->perf_ops->device_opps_add(pd->ph, dev, pd->domain_id);
+ if (ret)
+ dev_warn(dev, "failed to add OPPs for the device\n");
+
+ return ret;
+}
+
+static void
+scmi_pd_detach_dev(struct generic_pm_domain *genpd, struct device *dev)
+{
+ struct scmi_perf_domain *pd = to_scmi_pd(genpd);
+
+ if (!pd->info->set_perf)
+ return;
+
+ dev_pm_opp_remove_all_dynamic(dev);
+}
+
+static int scmi_perf_domain_probe(struct scmi_device *sdev)
+{
+ struct device *dev = &sdev->dev;
+ const struct scmi_handle *handle = sdev->handle;
+ const struct scmi_perf_proto_ops *perf_ops;
+ struct scmi_protocol_handle *ph;
+ struct scmi_perf_domain *scmi_pd;
+ struct genpd_onecell_data *scmi_pd_data;
+ struct generic_pm_domain **domains;
+ int num_domains, i, ret = 0;
+
+ if (!handle)
+ return -ENODEV;
+
+ /* The OF node must specify us as a power-domain provider. */
+ if (!of_find_property(dev->of_node, "#power-domain-cells", NULL))
+ return 0;
+
+ perf_ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_PERF, &ph);
+ if (IS_ERR(perf_ops))
+ return PTR_ERR(perf_ops);
+
+ num_domains = perf_ops->num_domains_get(ph);
+ if (num_domains < 0) {
+ dev_warn(dev, "Failed with %d when getting num perf domains\n",
+ num_domains);
+ return num_domains;
+ } else if (!num_domains) {
+ return 0;
+ }
+
+ scmi_pd = devm_kcalloc(dev, num_domains, sizeof(*scmi_pd), GFP_KERNEL);
+ if (!scmi_pd)
+ return -ENOMEM;
+
+ scmi_pd_data = devm_kzalloc(dev, sizeof(*scmi_pd_data), GFP_KERNEL);
+ if (!scmi_pd_data)
+ return -ENOMEM;
+
+ domains = devm_kcalloc(dev, num_domains, sizeof(*domains), GFP_KERNEL);
+ if (!domains)
+ return -ENOMEM;
+
+ for (i = 0; i < num_domains; i++, scmi_pd++) {
+ scmi_pd->info = perf_ops->info_get(ph, i);
+
+ scmi_pd->domain_id = i;
+ scmi_pd->perf_ops = perf_ops;
+ scmi_pd->ph = ph;
+ scmi_pd->genpd.name = scmi_pd->info->name;
+ scmi_pd->genpd.flags = GENPD_FLAG_ALWAYS_ON |
+ GENPD_FLAG_OPP_TABLE_FW;
+ scmi_pd->genpd.set_performance_state = scmi_pd_set_perf_state;
+ scmi_pd->genpd.attach_dev = scmi_pd_attach_dev;
+ scmi_pd->genpd.detach_dev = scmi_pd_detach_dev;
+
+ ret = pm_genpd_init(&scmi_pd->genpd, NULL, false);
+ if (ret)
+ goto err;
+
+ domains[i] = &scmi_pd->genpd;
+ }
+
+ scmi_pd_data->domains = domains;
+ scmi_pd_data->num_domains = num_domains;
+
+ ret = of_genpd_add_provider_onecell(dev->of_node, scmi_pd_data);
+ if (ret)
+ goto err;
+
+ dev_set_drvdata(dev, scmi_pd_data);
+ dev_info(dev, "Initialized %d performance domains", num_domains);
+ return 0;
+err:
+ for (i--; i >= 0; i--)
+ pm_genpd_remove(domains[i]);
+ return ret;
+}
+
+static void scmi_perf_domain_remove(struct scmi_device *sdev)
+{
+ struct device *dev = &sdev->dev;
+ struct genpd_onecell_data *scmi_pd_data = dev_get_drvdata(dev);
+ int i;
+
+ of_genpd_del_provider(dev->of_node);
+
+ for (i = 0; i < scmi_pd_data->num_domains; i++)
+ pm_genpd_remove(scmi_pd_data->domains[i]);
+}
+
+static const struct scmi_device_id scmi_id_table[] = {
+ { SCMI_PROTOCOL_PERF, "perf" },
+ { },
+};
+MODULE_DEVICE_TABLE(scmi, scmi_id_table);
+
+static struct scmi_driver scmi_perf_domain_driver = {
+ .name = "scmi-perf-domain",
+ .probe = scmi_perf_domain_probe,
+ .remove = scmi_perf_domain_remove,
+ .id_table = scmi_id_table,
+};
+module_scmi_driver(scmi_perf_domain_driver);
+
+MODULE_AUTHOR("Ulf Hansson <ulf.hansson@linaro.org>");
+MODULE_DESCRIPTION("ARM SCMI perf domain driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/firmware/arm_scmi/scmi_pm_domain.c b/drivers/pmdomain/arm/scmi_pm_domain.c
index 0e05a79de82d..0e05a79de82d 100644
--- a/drivers/firmware/arm_scmi/scmi_pm_domain.c
+++ b/drivers/pmdomain/arm/scmi_pm_domain.c
diff --git a/include/linux/pm.h b/include/linux/pm.h
index 1400c37b29c7..4c9f571609c8 100644
--- a/include/linux/pm.h
+++ b/include/linux/pm.h
@@ -719,6 +719,7 @@ extern void dev_pm_put_subsys_data(struct device *dev);
* @activate: Called before executing probe routines for bus types and drivers.
* @sync: Called after successful driver probe.
* @dismiss: Called after unsuccessful driver probe and after driver removal.
+ * @set_performance_state: Called to request a new performance state.
*
* Power domains provide callbacks that are executed during system suspend,
* hibernation, system resume and during runtime PM transitions instead of
@@ -731,6 +732,7 @@ struct dev_pm_domain {
int (*activate)(struct device *dev);
void (*sync)(struct device *dev);
void (*dismiss)(struct device *dev);
+ int (*set_performance_state)(struct device *dev, unsigned int state);
};
/*
diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h
index f776fb93eaa0..34663d0d5c55 100644
--- a/include/linux/pm_domain.h
+++ b/include/linux/pm_domain.h
@@ -61,6 +61,10 @@
* GENPD_FLAG_MIN_RESIDENCY: Enable the genpd governor to consider its
* components' next wakeup when determining the
* optimal idle state.
+ *
+ * GENPD_FLAG_OPP_TABLE_FW: The genpd provider supports performance states,
+ * but its corresponding OPP tables are not
+ * described in DT, but are given directly by FW.
*/
#define GENPD_FLAG_PM_CLK (1U << 0)
#define GENPD_FLAG_IRQ_SAFE (1U << 1)
@@ -69,6 +73,7 @@
#define GENPD_FLAG_CPU_DOMAIN (1U << 4)
#define GENPD_FLAG_RPM_ALWAYS_ON (1U << 5)
#define GENPD_FLAG_MIN_RESIDENCY (1U << 6)
+#define GENPD_FLAG_OPP_TABLE_FW (1U << 7)
enum gpd_status {
GENPD_STATE_ON = 0, /* PM domain is on */
@@ -430,6 +435,7 @@ struct device *dev_pm_domain_attach_by_name(struct device *dev,
void dev_pm_domain_detach(struct device *dev, bool power_off);
int dev_pm_domain_start(struct device *dev);
void dev_pm_domain_set(struct device *dev, struct dev_pm_domain *pd);
+int dev_pm_domain_set_performance_state(struct device *dev, unsigned int state);
#else
static inline int dev_pm_domain_attach(struct device *dev, bool power_on)
{
@@ -452,6 +458,11 @@ static inline int dev_pm_domain_start(struct device *dev)
}
static inline void dev_pm_domain_set(struct device *dev,
struct dev_pm_domain *pd) {}
+static inline int dev_pm_domain_set_performance_state(struct device *dev,
+ unsigned int state)
+{
+ return 0;
+}
#endif
#endif /* _LINUX_PM_DOMAIN_H */
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
index 91f87d7e807c..9ad168f4cbf1 100644
--- a/include/linux/pm_opp.h
+++ b/include/linux/pm_opp.h
@@ -92,6 +92,18 @@ struct dev_pm_opp_config {
struct device ***virt_devs;
};
+/**
+ * struct dev_pm_opp_data - The data to use to initialize an OPP.
+ * @level: The performance level for the OPP.
+ * @freq: The clock rate in Hz for the OPP.
+ * @u_volt: The voltage in uV for the OPP.
+ */
+struct dev_pm_opp_data {
+ unsigned int level;
+ unsigned long freq;
+ unsigned long u_volt;
+};
+
#if defined(CONFIG_PM_OPP)
struct opp_table *dev_pm_opp_get_opp_table(struct device *dev);
@@ -152,8 +164,8 @@ struct dev_pm_opp *dev_pm_opp_find_bw_floor(struct device *dev,
void dev_pm_opp_put(struct dev_pm_opp *opp);
-int dev_pm_opp_add(struct device *dev, unsigned long freq,
- unsigned long u_volt);
+int dev_pm_opp_add_dynamic(struct device *dev, struct dev_pm_opp_data *opp);
+
void dev_pm_opp_remove(struct device *dev, unsigned long freq);
void dev_pm_opp_remove_all_dynamic(struct device *dev);
@@ -322,8 +334,8 @@ static inline struct dev_pm_opp *dev_pm_opp_find_bw_floor(struct device *dev,
static inline void dev_pm_opp_put(struct dev_pm_opp *opp) {}
-static inline int dev_pm_opp_add(struct device *dev, unsigned long freq,
- unsigned long u_volt)
+static inline int
+dev_pm_opp_add_dynamic(struct device *dev, struct dev_pm_opp_data *opp)
{
return -EOPNOTSUPP;
}
@@ -519,6 +531,17 @@ static inline int dev_pm_opp_of_find_icc_paths(struct device *dev, struct opp_ta
/* OPP Configuration helpers */
+static inline int dev_pm_opp_add(struct device *dev, unsigned long freq,
+ unsigned long u_volt)
+{
+ struct dev_pm_opp_data data = {
+ .freq = freq,
+ .u_volt = u_volt,
+ };
+
+ return dev_pm_opp_add_dynamic(dev, &data);
+}
+
/* Regulators helpers */
static inline int dev_pm_opp_set_regulators(struct device *dev,
const char * const names[])
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index e6fe4f73ffe6..f2f05fb42d28 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -58,6 +58,8 @@ struct scmi_clock_info {
u64 step_size;
} range;
};
+ int num_parents;
+ u32 *parents;
};
enum scmi_power_scale {
@@ -80,6 +82,11 @@ struct scmi_protocol_handle;
* @rate_set: set the clock rate of a clock
* @enable: enables the specified clock
* @disable: disables the specified clock
+ * @state_get: get the status of the specified clock
+ * @config_oem_get: get the value of an OEM specific clock config
+ * @config_oem_set: set the value of an OEM specific clock config
+ * @parent_get: get the parent id of a clk
+ * @parent_set: set the parent of a clock
*/
struct scmi_clk_proto_ops {
int (*count_get)(const struct scmi_protocol_handle *ph);
@@ -90,22 +97,36 @@ struct scmi_clk_proto_ops {
u64 *rate);
int (*rate_set)(const struct scmi_protocol_handle *ph, u32 clk_id,
u64 rate);
- int (*enable)(const struct scmi_protocol_handle *ph, u32 clk_id);
- int (*disable)(const struct scmi_protocol_handle *ph, u32 clk_id);
- int (*enable_atomic)(const struct scmi_protocol_handle *ph, u32 clk_id);
- int (*disable_atomic)(const struct scmi_protocol_handle *ph,
- u32 clk_id);
+ int (*enable)(const struct scmi_protocol_handle *ph, u32 clk_id,
+ bool atomic);
+ int (*disable)(const struct scmi_protocol_handle *ph, u32 clk_id,
+ bool atomic);
+ int (*state_get)(const struct scmi_protocol_handle *ph, u32 clk_id,
+ bool *enabled, bool atomic);
+ int (*config_oem_get)(const struct scmi_protocol_handle *ph, u32 clk_id,
+ u8 oem_type, u32 *oem_val, u32 *attributes,
+ bool atomic);
+ int (*config_oem_set)(const struct scmi_protocol_handle *ph, u32 clk_id,
+ u8 oem_type, u32 oem_val, bool atomic);
+ int (*parent_get)(const struct scmi_protocol_handle *ph, u32 clk_id, u32 *parent_id);
+ int (*parent_set)(const struct scmi_protocol_handle *ph, u32 clk_id, u32 parent_id);
+};
+
+struct scmi_perf_domain_info {
+ char name[SCMI_MAX_STR_SIZE];
+ bool set_perf;
};
/**
* struct scmi_perf_proto_ops - represents the various operations provided
* by SCMI Performance Protocol
*
+ * @num_domains_get: gets the number of supported performance domains
+ * @info_get: get the information of a performance domain
* @limits_set: sets limits on the performance level of a domain
* @limits_get: gets limits on the performance level of a domain
* @level_set: sets the performance level of a domain
* @level_get: gets the performance level of a domain
- * @device_domain_id: gets the scmi domain id for a given device
* @transition_latency_get: gets the DVFS transition latency for a given device
* @device_opps_add: adds all the OPPs for a given device
* @freq_set: sets the frequency for a given device using sustained frequency
@@ -120,6 +141,9 @@ struct scmi_clk_proto_ops {
* or in some other (abstract) scale
*/
struct scmi_perf_proto_ops {
+ int (*num_domains_get)(const struct scmi_protocol_handle *ph);
+ const struct scmi_perf_domain_info __must_check *(*info_get)
+ (const struct scmi_protocol_handle *ph, u32 domain);
int (*limits_set)(const struct scmi_protocol_handle *ph, u32 domain,
u32 max_perf, u32 min_perf);
int (*limits_get)(const struct scmi_protocol_handle *ph, u32 domain,
@@ -128,11 +152,10 @@ struct scmi_perf_proto_ops {
u32 level, bool poll);
int (*level_get)(const struct scmi_protocol_handle *ph, u32 domain,
u32 *level, bool poll);
- int (*device_domain_id)(struct device *dev);
int (*transition_latency_get)(const struct scmi_protocol_handle *ph,
- struct device *dev);
+ u32 domain);
int (*device_opps_add)(const struct scmi_protocol_handle *ph,
- struct device *dev);
+ struct device *dev, u32 domain);
int (*freq_set)(const struct scmi_protocol_handle *ph, u32 domain,
unsigned long rate, bool poll);
int (*freq_get)(const struct scmi_protocol_handle *ph, u32 domain,
@@ -140,7 +163,7 @@ struct scmi_perf_proto_ops {
int (*est_power_get)(const struct scmi_protocol_handle *ph, u32 domain,
unsigned long *rate, unsigned long *power);
bool (*fast_switch_possible)(const struct scmi_protocol_handle *ph,
- struct device *dev);
+ u32 domain);
enum scmi_power_scale (*power_scale_get)(const struct scmi_protocol_handle *ph);
};