summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArnd Bergmann <arnd@arndb.de>2026-05-30 01:04:45 +0300
committerArnd Bergmann <arnd@arndb.de>2026-05-30 01:04:46 +0300
commit1499b84142cc1c85df780f26fe9b94d8fffd87d8 (patch)
tree69d4daa3c1cc5447c7d8a234d85c4d533995e9db
parentb6a6fae6c542e0e71bbabca653bb97699d2f3b33 (diff)
parent524abd2fa6907ebe2762342be339afcc5b227dc4 (diff)
downloadlinux-1499b84142cc1c85df780f26fe9b94d8fffd87d8.tar.xz
Merge tag 'scmi-updates-7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux into soc/drivers
Arm SCMI updates for v7.2 1. Improve SCMI clock handling with a protocol-level determine_rate operation, simplified per-clock rate properties, dynamic rate allocation, bounded iterator support, lazy full-rate discovery, and hardened parent/rate enumeration. 2. Fix several SCMI bounds and payload validation issues, including clock rate discovery OOB handling, power domain name lookup, Powercap domain state access, BASE_ERROR_EVENT and SENSOR_UPDATE payload sizing, and sensor config width handling. 3. Rework SCMI transport probing for virtio and OP-TEE using per-instance transport handles and a generic transport supplier, removing the need to register SCMI core drivers from transport probe paths. 4. Add i.MX SCMI MISC reset reason support and print i.MX95 boot/shutdown reasons via the System Manager interface. 5. Clean up SCMI core internals, including base-info naming, quirk parsing and table iteration, and list iteration. 6. Fix SCPI clock provider removal so child clock providers are unregistered using the same DT nodes used at registration time. * tag 'scmi-updates-7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux: (31 commits) firmware: arm_scmi: optee: Rework transport probe sequence firmware: arm_scmi: virtio: Rework transport probe sequence firmware: arm_scmi: Add a generic transport supplier firmware: arm_scmi: Add transport instance handles firmware: arm_scmi: Fix OOB in scmi_power_name_get() firmware: arm_scmi: Validate Powercap domains before state access firmware: arm_scmi: Validate SENSOR_UPDATE payload size firmware: arm_scmi: Validate BASE_ERROR_EVENT payload size firmware: arm_scmi: Read sensor config as 32-bit value clk: scpi: Unregister child clock providers on remove firmware: arm_scmi: Introduce all_rates_get clock operation firmware: arm_scmi: Fix OOB in scmi_clock_describe_rates_get_lazy() firmware: arm_scmi: Use bound iterators to minimize discovered rates firmware: arm_scmi: Use proper iter_response_bound_cleanup() name firmware: arm_scmi: Fix bound iterators returning too many items firmware: arm_scmi: Add bound iterators support firmware: arm_scmi: Refactor iterators internal allocation firmware: arm_scmi: Harden clock parents discovery firmware: arm_scmi: Make clock rates allocation dynamic firmware: arm_scmi: Drop unused clock rate interfaces ... Signed-off-by: Arnd Bergmann <arnd@arndb.de>
-rw-r--r--drivers/clk/clk-scmi.c48
-rw-r--r--drivers/clk/clk-scpi.c2
-rw-r--r--drivers/firmware/arm_scmi/base.c25
-rw-r--r--drivers/firmware/arm_scmi/clock.c301
-rw-r--r--drivers/firmware/arm_scmi/common.h165
-rw-r--r--drivers/firmware/arm_scmi/driver.c98
-rw-r--r--drivers/firmware/arm_scmi/power.c6
-rw-r--r--drivers/firmware/arm_scmi/powercap.c18
-rw-r--r--drivers/firmware/arm_scmi/protocols.h13
-rw-r--r--drivers/firmware/arm_scmi/quirks.c17
-rw-r--r--drivers/firmware/arm_scmi/sensors.c12
-rw-r--r--drivers/firmware/arm_scmi/transports/optee.c46
-rw-r--r--drivers/firmware/arm_scmi/transports/virtio.c52
-rw-r--r--drivers/firmware/arm_scmi/vendors/imx/imx-sm-misc.c86
-rw-r--r--drivers/firmware/imx/sm-misc.c73
-rw-r--r--include/linux/scmi_imx_protocol.h14
-rw-r--r--include/linux/scmi_protocol.h36
17 files changed, 802 insertions, 210 deletions
diff --git a/drivers/clk/clk-scmi.c b/drivers/clk/clk-scmi.c
index 6b286ea6f121..7c562559ad8b 100644
--- a/drivers/clk/clk-scmi.c
+++ b/drivers/clk/clk-scmi.c
@@ -12,7 +12,6 @@
#include <linux/of.h>
#include <linux/module.h>
#include <linux/scmi_protocol.h>
-#include <asm/div64.h>
#define NOT_ATOMIC false
#define ATOMIC true
@@ -57,35 +56,17 @@ static unsigned long scmi_clk_recalc_rate(struct clk_hw *hw,
static int scmi_clk_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
- u64 fmin, fmax, ftmp;
+ int ret;
struct scmi_clk *clk = to_scmi_clk(hw);
/*
- * We can't figure out what rate it will be, so just return the
- * rate back to the caller. scmi_clk_recalc_rate() will be called
- * after the rate is set and we'll know what rate the clock is
+ * If we could not get a better rate scmi_clk_recalc_rate() will be
+ * called after the rate is set and we'll know what rate the clock is
* running at then.
*/
- if (clk->info->rate_discrete)
- return 0;
-
- fmin = clk->info->range.min_rate;
- fmax = clk->info->range.max_rate;
- if (req->rate <= fmin) {
- req->rate = fmin;
-
- return 0;
- } else if (req->rate >= fmax) {
- req->rate = fmax;
-
- return 0;
- }
-
- ftmp = req->rate - fmin;
- ftmp += clk->info->range.step_size - 1; /* to round up */
- do_div(ftmp, clk->info->range.step_size);
-
- req->rate = ftmp * clk->info->range.step_size + fmin;
+ ret = scmi_proto_clk_ops->determine_rate(clk->ph, clk->id, &req->rate);
+ if (ret)
+ return ret;
return 0;
}
@@ -221,7 +202,6 @@ static int scmi_clk_ops_init(struct device *dev, struct scmi_clk *sclk,
const struct clk_ops *scmi_ops)
{
int ret;
- unsigned long min_rate, max_rate;
struct clk_init_data init = {
.flags = CLK_GET_RATE_NOCACHE,
@@ -236,20 +216,8 @@ static int scmi_clk_ops_init(struct device *dev, struct scmi_clk *sclk,
if (ret)
return ret;
- if (sclk->info->rate_discrete) {
- int num_rates = sclk->info->list.num_rates;
-
- if (num_rates <= 0)
- return -EINVAL;
-
- min_rate = sclk->info->list.rates[0];
- max_rate = sclk->info->list.rates[num_rates - 1];
- } else {
- min_rate = sclk->info->range.min_rate;
- max_rate = sclk->info->range.max_rate;
- }
-
- clk_hw_set_rate_range(&sclk->hw, min_rate, max_rate);
+ clk_hw_set_rate_range(&sclk->hw, sclk->info->min_rate,
+ sclk->info->max_rate);
return ret;
}
diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c
index 7806569cd0d5..24cee7c9fda6 100644
--- a/drivers/clk/clk-scpi.c
+++ b/drivers/clk/clk-scpi.c
@@ -258,7 +258,7 @@ static void scpi_clocks_remove(struct platform_device *pdev)
}
for_each_available_child_of_node(np, child)
- of_clk_del_provider(np);
+ of_clk_del_provider(child);
}
static int scpi_clocks_probe(struct platform_device *pdev)
diff --git a/drivers/firmware/arm_scmi/base.c b/drivers/firmware/arm_scmi/base.c
index cd1331c2fc40..a9cea09355ff 100644
--- a/drivers/firmware/arm_scmi/base.c
+++ b/drivers/firmware/arm_scmi/base.c
@@ -69,7 +69,7 @@ static int scmi_base_attributes_get(const struct scmi_protocol_handle *ph)
int ret;
struct scmi_xfer *t;
struct scmi_msg_resp_base_attributes *attr_info;
- struct scmi_revision_info *rev = ph->get_priv(ph);
+ struct scmi_base_info *rev = ph->get_priv(ph);
ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES,
0, sizeof(*attr_info), &t);
@@ -103,7 +103,7 @@ scmi_base_vendor_id_get(const struct scmi_protocol_handle *ph, bool sub_vendor)
int ret, size;
char *vendor_id;
struct scmi_xfer *t;
- struct scmi_revision_info *rev = ph->get_priv(ph);
+ struct scmi_base_info *rev = ph->get_priv(ph);
if (sub_vendor) {
cmd = BASE_DISCOVER_SUB_VENDOR;
@@ -143,7 +143,7 @@ scmi_base_implementation_version_get(const struct scmi_protocol_handle *ph)
int ret;
__le32 *impl_ver;
struct scmi_xfer *t;
- struct scmi_revision_info *rev = ph->get_priv(ph);
+ struct scmi_base_info *rev = ph->get_priv(ph);
ret = ph->xops->xfer_get_init(ph, BASE_DISCOVER_IMPLEMENT_VERSION,
0, sizeof(*impl_ver), &t);
@@ -180,7 +180,7 @@ scmi_base_implementation_list_get(const struct scmi_protocol_handle *ph,
__le32 *num_skip, *num_ret;
u32 tot_num_ret = 0, loop_num_ret;
struct device *dev = ph->dev;
- struct scmi_revision_info *rev = ph->get_priv(ph);
+ struct scmi_base_info *rev = ph->get_priv(ph);
ret = ph->xops->xfer_get_init(ph, BASE_DISCOVER_LIST_PROTOCOLS,
sizeof(*num_skip), 0, &t);
@@ -325,6 +325,8 @@ static void *scmi_base_fill_custom_report(const struct scmi_protocol_handle *ph,
void *report, u32 *src_id)
{
int i;
+ u32 error_status;
+ size_t expected_sz;
const struct scmi_base_error_notify_payld *p = payld;
struct scmi_base_error_report *r = report;
@@ -338,10 +340,19 @@ static void *scmi_base_fill_custom_report(const struct scmi_protocol_handle *ph,
if (evt_id != SCMI_EVENT_BASE_ERROR_EVENT || sizeof(*p) < payld_sz)
return NULL;
+ expected_sz = offsetof(typeof(*p), msg_reports);
+ if (payld_sz < expected_sz)
+ return NULL;
+
r->timestamp = timestamp;
r->agent_id = le32_to_cpu(p->agent_id);
- r->fatal = IS_FATAL_ERROR(le32_to_cpu(p->error_status));
- r->cmd_count = ERROR_CMD_COUNT(le32_to_cpu(p->error_status));
+ error_status = le32_to_cpu(p->error_status);
+ r->fatal = IS_FATAL_ERROR(error_status);
+ r->cmd_count = ERROR_CMD_COUNT(error_status);
+ expected_sz += r->cmd_count * sizeof(p->msg_reports[0]);
+ if (payld_sz < expected_sz)
+ return NULL;
+
for (i = 0; i < r->cmd_count; i++)
r->reports[i] = le64_to_cpu(p->msg_reports[i]);
*src_id = 0;
@@ -377,7 +388,7 @@ static int scmi_base_protocol_init(const struct scmi_protocol_handle *ph)
u8 *prot_imp;
char name[SCMI_SHORT_NAME_MAX_SIZE];
struct device *dev = ph->dev;
- struct scmi_revision_info *rev = scmi_revision_area_get(ph);
+ struct scmi_base_info *rev = scmi_revision_area_get(ph);
rev->major_ver = PROTOCOL_REV_MAJOR(ph->version);
rev->minor_ver = PROTOCOL_REV_MINOR(ph->version);
diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c
index ab36871650a1..42e666a628c7 100644
--- a/drivers/firmware/arm_scmi/clock.c
+++ b/drivers/firmware/arm_scmi/clock.c
@@ -5,6 +5,7 @@
* Copyright (C) 2018-2022 ARM Ltd.
*/
+#include <linux/math64.h>
#include <linux/module.h>
#include <linux/limits.h>
#include <linux/sort.h>
@@ -156,13 +157,26 @@ struct scmi_clock_rate_notify_payld {
__le32 rate_high;
};
+struct scmi_clock_desc {
+ u32 id;
+ unsigned int tot_rates;
+ struct scmi_clock_rates r;
+#define RATE_MIN 0
+#define RATE_MAX 1
+#define RATE_STEP 2
+ struct scmi_clock_info info;
+};
+
+#define to_desc(p) (container_of(p, struct scmi_clock_desc, info))
+
struct clock_info {
int num_clocks;
int max_async_req;
bool notify_rate_changed_cmd;
bool notify_rate_change_requested_cmd;
atomic_t cur_async_req;
- struct scmi_clock_info *clk;
+ struct scmi_clock_desc *clkds;
+#define CLOCK_INFO(c, i) (&(((c)->clkds + (i))->info))
int (*clock_config_set)(const struct scmi_protocol_handle *ph,
u32 clk_id, enum clk_state state,
enum scmi_clock_oem_config oem_type,
@@ -184,7 +198,7 @@ scmi_clock_domain_lookup(struct clock_info *ci, u32 clk_id)
if (clk_id >= ci->num_clocks)
return ERR_PTR(-EINVAL);
- return ci->clk + clk_id;
+ return CLOCK_INFO(ci, clk_id);
}
static int
@@ -225,8 +239,7 @@ scmi_clock_protocol_attributes_get(const struct scmi_protocol_handle *ph,
struct scmi_clk_ipriv {
struct device *dev;
- u32 clk_id;
- struct scmi_clock_info *clk;
+ struct scmi_clock_desc *clkd;
};
static void iter_clk_possible_parents_prepare_message(void *message, unsigned int desc_index,
@@ -235,7 +248,7 @@ static void iter_clk_possible_parents_prepare_message(void *message, unsigned in
struct scmi_msg_clock_possible_parents *msg = message;
const struct scmi_clk_ipriv *p = priv;
- msg->id = cpu_to_le32(p->clk_id);
+ msg->id = cpu_to_le32(p->clkd->id);
/* Set the number of OPPs to be skipped/already read */
msg->skip_parents = cpu_to_le32(desc_index);
}
@@ -245,7 +258,6 @@ static int iter_clk_possible_parents_update_state(struct scmi_iterator_state *st
{
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);
@@ -257,14 +269,15 @@ static int iter_clk_possible_parents_update_state(struct scmi_iterator_state *st
* 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;
+ int num_parents = st->num_returned + st->num_remaining;
+
+ p->clkd->info.parents = devm_kcalloc(p->dev, num_parents,
+ sizeof(*p->clkd->info.parents),
+ GFP_KERNEL);
+ if (!p->clkd->info.parents)
return -ENOMEM;
- }
+
+ /* max_resources is used by the iterators to control bounds */
st->max_resources = st->num_returned + st->num_remaining;
}
@@ -279,29 +292,29 @@ static int iter_clk_possible_parents_process_response(const struct scmi_protocol
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];
+ p->clkd->info.parents[st->desc_index + st->loop_idx] =
+ le32_to_cpu(r->possible_parents[st->loop_idx]);
- *parent = le32_to_cpu(r->possible_parents[st->loop_idx]);
+ /* Count only effectively discovered parents */
+ p->clkd->info.num_parents++;
return 0;
}
-static int scmi_clock_possible_parents(const struct scmi_protocol_handle *ph, u32 clk_id,
- struct scmi_clock_info *clk)
+static int scmi_clock_possible_parents(const struct scmi_protocol_handle *ph,
+ u32 clk_id, struct clock_info *cinfo)
{
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_clock_desc *clkd = &cinfo->clkds[clk_id];
struct scmi_clk_ipriv ppriv = {
- .clk_id = clk_id,
- .clk = clk,
+ .clkd = clkd,
.dev = ph->dev,
};
void *iter;
- int ret;
iter = ph->hops->iter_response_init(ph, &ops, 0,
CLOCK_POSSIBLE_PARENTS_GET,
@@ -310,9 +323,7 @@ static int scmi_clock_possible_parents(const struct scmi_protocol_handle *ph, u3
if (IS_ERR(iter))
return PTR_ERR(iter);
- ret = ph->hops->iter_response_run(iter);
-
- return ret;
+ return ph->hops->iter_response_run(iter);
}
static int
@@ -351,7 +362,7 @@ static int scmi_clock_attributes_get(const struct scmi_protocol_handle *ph,
u32 attributes;
struct scmi_xfer *t;
struct scmi_msg_resp_clock_attributes *attr;
- struct scmi_clock_info *clk = cinfo->clk + clk_id;
+ struct scmi_clock_info *clk = CLOCK_INFO(cinfo, clk_id);
ret = ph->xops->xfer_get_init(ph, CLOCK_ATTRIBUTES,
sizeof(clk_id), sizeof(*attr), &t);
@@ -393,7 +404,7 @@ static int scmi_clock_attributes_get(const struct scmi_protocol_handle *ph,
clk->rate_change_requested_notifications = true;
if (PROTOCOL_REV_MAJOR(ph->version) >= 0x3) {
if (SUPPORTS_PARENT_CLOCK(attributes))
- scmi_clock_possible_parents(ph, clk_id, clk);
+ scmi_clock_possible_parents(ph, clk_id, cinfo);
if (SUPPORTS_GET_PERMISSIONS(attributes))
scmi_clock_get_permissions(ph, clk_id, clk);
if (SUPPORTS_EXTENDED_CONFIG(attributes))
@@ -423,7 +434,7 @@ static void iter_clk_describe_prepare_message(void *message,
struct scmi_msg_clock_describe_rates *msg = message;
const struct scmi_clk_ipriv *p = priv;
- msg->id = cpu_to_le32(p->clk_id);
+ msg->id = cpu_to_le32(p->clkd->id);
/* Set the number of rates to be skipped/already read */
msg->rate_index = cpu_to_le32(desc_index);
}
@@ -456,20 +467,33 @@ iter_clk_describe_update_state(struct scmi_iterator_state *st,
flags = le32_to_cpu(r->num_rates_flags);
st->num_remaining = NUM_REMAINING(flags);
st->num_returned = NUM_RETURNED(flags);
- p->clk->rate_discrete = RATE_DISCRETE(flags);
+ p->clkd->r.rate_discrete = RATE_DISCRETE(flags);
/* Warn about out of spec replies ... */
- if (!p->clk->rate_discrete &&
+ if (!p->clkd->r.rate_discrete &&
(st->num_returned != 3 || st->num_remaining != 0)) {
dev_warn(p->dev,
"Out-of-spec CLOCK_DESCRIBE_RATES reply for %s - returned:%d remaining:%d rx_len:%zd\n",
- p->clk->name, st->num_returned, st->num_remaining,
+ p->clkd->info.name, st->num_returned, st->num_remaining,
st->rx_len);
SCMI_QUIRK(clock_rates_triplet_out_of_spec,
QUIRK_OUT_OF_SPEC_TRIPLET);
}
+ if (!st->max_resources) {
+ unsigned int tot_rates = st->num_returned + st->num_remaining;
+
+ p->clkd->r.rates = devm_kcalloc(p->dev, tot_rates,
+ sizeof(*p->clkd->r.rates), GFP_KERNEL);
+ if (!p->clkd->r.rates)
+ return -ENOMEM;
+
+ /* max_resources is used by the iterators to control bounds */
+ p->clkd->tot_rates = tot_rates;
+ st->max_resources = tot_rates;
+ }
+
return 0;
}
@@ -478,38 +502,20 @@ iter_clk_describe_process_response(const struct scmi_protocol_handle *ph,
const void *response,
struct scmi_iterator_state *st, void *priv)
{
- int ret = 0;
struct scmi_clk_ipriv *p = priv;
const struct scmi_msg_resp_clock_describe_rates *r = response;
- if (!p->clk->rate_discrete) {
- switch (st->desc_index + st->loop_idx) {
- case 0:
- p->clk->range.min_rate = RATE_TO_U64(r->rate[0]);
- break;
- case 1:
- p->clk->range.max_rate = RATE_TO_U64(r->rate[1]);
- break;
- case 2:
- p->clk->range.step_size = RATE_TO_U64(r->rate[2]);
- break;
- default:
- ret = -EINVAL;
- break;
- }
- } else {
- u64 *rate = &p->clk->list.rates[st->desc_index + st->loop_idx];
+ p->clkd->r.rates[p->clkd->r.num_rates] = RATE_TO_U64(r->rate[st->loop_idx]);
- *rate = RATE_TO_U64(r->rate[st->loop_idx]);
- p->clk->list.num_rates++;
- }
+ /* Count only effectively discovered rates */
+ p->clkd->r.num_rates++;
- return ret;
+ return 0;
}
static int
-scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id,
- struct scmi_clock_info *clk)
+scmi_clock_describe_rates_get_full(const struct scmi_protocol_handle *ph,
+ struct scmi_clock_desc *clkd)
{
int ret;
void *iter;
@@ -519,12 +525,16 @@ scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id,
.process_response = iter_clk_describe_process_response,
};
struct scmi_clk_ipriv cpriv = {
- .clk_id = clk_id,
- .clk = clk,
+ .clkd = clkd,
.dev = ph->dev,
};
- iter = ph->hops->iter_response_init(ph, &ops, SCMI_MAX_NUM_RATES,
+ /*
+ * Using tot_rates as max_resources parameter here so as to trigger
+ * the dynamic allocation only when strictly needed: when trying a
+ * full enumeration after a lazy one tot_rates will be non-zero.
+ */
+ iter = ph->hops->iter_response_init(ph, &ops, clkd->tot_rates,
CLOCK_DESCRIBE_RATES,
sizeof(struct scmi_msg_clock_describe_rates),
&cpriv);
@@ -535,19 +545,100 @@ scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id,
if (ret)
return ret;
- if (!clk->rate_discrete) {
- dev_dbg(ph->dev, "Min %llu Max %llu Step %llu Hz\n",
- clk->range.min_rate, clk->range.max_rate,
- clk->range.step_size);
- } else if (clk->list.num_rates) {
- sort(clk->list.rates, clk->list.num_rates,
- sizeof(clk->list.rates[0]), rate_cmp_func, NULL);
+ /* empty set ? */
+ if (!clkd->r.num_rates)
+ return 0;
+
+ if (clkd->r.rate_discrete && PROTOCOL_REV_MAJOR(ph->version) == 0x1)
+ sort(clkd->r.rates, clkd->r.num_rates,
+ sizeof(clkd->r.rates[0]), rate_cmp_func, NULL);
+
+ return 0;
+}
+
+static int
+scmi_clock_describe_rates_get_lazy(const struct scmi_protocol_handle *ph,
+ struct scmi_clock_desc *clkd)
+{
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_clk_describe_prepare_message,
+ .update_state = iter_clk_describe_update_state,
+ .process_response = iter_clk_describe_process_response,
+ };
+ struct scmi_clk_ipriv cpriv = {
+ .clkd = clkd,
+ .dev = ph->dev,
+ };
+ unsigned int first, last;
+ void *iter;
+ int ret;
+
+ iter = ph->hops->iter_response_init(ph, &ops, 0, CLOCK_DESCRIBE_RATES,
+ sizeof(struct scmi_msg_clock_describe_rates),
+ &cpriv);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ /* Try to grab a triplet, so that in case is NON-discrete we are done */
+ first = 0;
+ last = 2;
+ ret = ph->hops->iter_response_run_bound(iter, &first, &last);
+ if (ret)
+ goto out;
+
+ /*
+ * If discrete and we don't already have it, grab the last value, which
+ * should be the max
+ */
+ if (clkd->r.rate_discrete && clkd->tot_rates > clkd->r.num_rates) {
+ first = clkd->tot_rates - 1;
+ last = clkd->tot_rates - 1;
+ ret = ph->hops->iter_response_run_bound(iter, &first, &last);
}
+out:
+ ph->hops->iter_response_bound_cleanup(iter);
+
return ret;
}
static int
+scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph,
+ u32 clk_id, struct clock_info *cinfo)
+{
+ struct scmi_clock_desc *clkd = &cinfo->clkds[clk_id];
+ int ret;
+
+ /*
+ * Since only after SCMI Clock v1.0 the returned rates are guaranteed to
+ * be discovered in ascending order, lazy enumeration cannot be use for
+ * SCMI Clock v1.0 protocol.
+ */
+ if (PROTOCOL_REV_MAJOR(ph->version) > 0x1)
+ ret = scmi_clock_describe_rates_get_lazy(ph, clkd);
+ else
+ ret = scmi_clock_describe_rates_get_full(ph, clkd);
+
+ if (ret)
+ return ret;
+
+ clkd->info.min_rate = clkd->r.rates[RATE_MIN];
+ if (!clkd->r.rate_discrete) {
+ clkd->info.max_rate = clkd->r.rates[RATE_MAX];
+ dev_dbg(ph->dev, "Min %llu Max %llu Step %llu Hz\n",
+ clkd->r.rates[RATE_MIN], clkd->r.rates[RATE_MAX],
+ clkd->r.rates[RATE_STEP]);
+ } else {
+ clkd->info.max_rate = clkd->r.rates[clkd->r.num_rates - 1];
+ dev_dbg(ph->dev, "Clock:%s Num_Rates:%u -> Min %llu Max %llu\n",
+ clkd->info.name, clkd->tot_rates,
+ clkd->info.min_rate, clkd->info.max_rate);
+ }
+
+ return 0;
+}
+
+static int
scmi_clock_rate_get(const struct scmi_protocol_handle *ph,
u32 clk_id, u64 *value)
{
@@ -624,6 +715,75 @@ static int scmi_clock_rate_set(const struct scmi_protocol_handle *ph,
return ret;
}
+static int scmi_clock_determine_rate(const struct scmi_protocol_handle *ph,
+ u32 clk_id, unsigned long *rate)
+{
+ u64 fmin, fmax, ftmp;
+ struct scmi_clock_info *clk;
+ struct scmi_clock_desc *clkd;
+ struct clock_info *ci = ph->get_priv(ph);
+
+ if (!rate)
+ return -EINVAL;
+
+ clk = scmi_clock_domain_lookup(ci, clk_id);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ clkd = to_desc(clk);
+
+ /*
+ * If we can't figure out what rate it will be, so just return the
+ * rate back to the caller.
+ */
+ if (clkd->r.rate_discrete)
+ return 0;
+
+ fmin = clk->min_rate;
+ fmax = clk->max_rate;
+ if (*rate <= fmin) {
+ *rate = fmin;
+ return 0;
+ } else if (*rate >= fmax) {
+ *rate = fmax;
+ return 0;
+ }
+
+ ftmp = *rate - fmin;
+ ftmp += clkd->r.rates[RATE_STEP] - 1; /* to round up */
+ ftmp = div64_ul(ftmp, clkd->r.rates[RATE_STEP]);
+
+ *rate = ftmp * clkd->r.rates[RATE_STEP] + fmin;
+
+ return 0;
+}
+
+static const struct scmi_clock_rates *
+scmi_clock_all_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id)
+{
+ struct clock_info *ci = ph->get_priv(ph);
+ struct scmi_clock_desc *clkd;
+ struct scmi_clock_info *clk;
+
+ clk = scmi_clock_domain_lookup(ci, clk_id);
+ if (IS_ERR(clk) || !clk->name[0])
+ return NULL;
+
+ clkd = to_desc(clk);
+ /* Needs full enumeration ? */
+ if (clkd->r.rate_discrete && clkd->tot_rates != clkd->r.num_rates) {
+ int ret;
+
+ /* rates[] is already allocated BUT we need to re-enumerate */
+ clkd->r.num_rates = 0;
+ ret = scmi_clock_describe_rates_get_full(ph, clkd);
+ if (ret)
+ return NULL;
+ }
+
+ return &clkd->r;
+}
+
static int
scmi_clock_config_set(const struct scmi_protocol_handle *ph, u32 clk_id,
enum clk_state state,
@@ -936,6 +1096,8 @@ static const struct scmi_clk_proto_ops clk_proto_ops = {
.info_get = scmi_clock_info_get,
.rate_get = scmi_clock_rate_get,
.rate_set = scmi_clock_rate_set,
+ .determine_rate = scmi_clock_determine_rate,
+ .all_rates_get = scmi_clock_all_rates_get,
.enable = scmi_clock_enable,
.disable = scmi_clock_disable,
.state_get = scmi_clock_state_get,
@@ -1080,17 +1242,16 @@ static int scmi_clock_protocol_init(const struct scmi_protocol_handle *ph)
if (ret)
return ret;
- cinfo->clk = devm_kcalloc(ph->dev, cinfo->num_clocks,
- sizeof(*cinfo->clk), GFP_KERNEL);
- if (!cinfo->clk)
+ cinfo->clkds = devm_kcalloc(ph->dev, cinfo->num_clocks,
+ sizeof(*cinfo->clkds), GFP_KERNEL);
+ if (!cinfo->clkds)
return -ENOMEM;
for (clkid = 0; clkid < cinfo->num_clocks; clkid++) {
- struct scmi_clock_info *clk = cinfo->clk + clkid;
-
+ cinfo->clkds[clkid].id = clkid;
ret = scmi_clock_attributes_get(ph, clkid, cinfo);
if (!ret)
- scmi_clock_describe_rates_get(ph, clkid, clk);
+ scmi_clock_describe_rates_get(ph, clkid, cinfo);
}
if (PROTOCOL_REV_MAJOR(ph->version) >= 0x3) {
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index 7c9617d080a0..b9723c105fc1 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -17,6 +17,7 @@
#include <linux/hashtable.h>
#include <linux/list.h>
#include <linux/module.h>
+#include <linux/property.h>
#include <linux/refcount.h>
#include <linux/scmi_protocol.h>
#include <linux/spinlock.h>
@@ -138,7 +139,7 @@ static inline void unpack_scmi_header(u32 msg_hdr, struct scmi_msg_hdr *hdr)
xfer_; \
})
-struct scmi_revision_info *
+struct scmi_base_info *
scmi_revision_area_get(const struct scmi_protocol_handle *ph);
void scmi_setup_protocol_implemented(const struct scmi_protocol_handle *ph,
u8 *prot_imp);
@@ -463,6 +464,28 @@ struct scmi_transport_core_operations {
};
/**
+ * struct scmi_transport_handle - Transport instance handle
+ * @supplier_get: A helper to retrieve the device descriptor, identifying the
+ * transport driver serving this SCMI instance, which will be
+ * used as a supplier for the core SCMI driver: returning an
+ * error here causes the probe sequence to be interrupted and
+ * return that same error code, so that each transport can decide
+ * which policy to implement by choosing an appropriate error.
+ * @supplier_put: A helper to signal that the specified transport supplier is
+ * no more being used and it is made available again.
+ *
+ * Note that these helpers are needed and provided only by those transports
+ * whose initialization relies on some other subsystem and whose relations to
+ * the core SCMI driver is not tracked by firmware descriptions.
+ */
+struct scmi_transport_handle {
+ struct device __must_check *(*supplier_get)
+ (const struct scmi_transport_handle *th);
+ int (*supplier_put)(const struct scmi_transport_handle *th,
+ struct device *dev);
+};
+
+/**
* struct scmi_transport - A structure representing a configured transport
*
* @supplier: Device representing the transport and acting as a supplier for
@@ -470,35 +493,163 @@ struct scmi_transport_core_operations {
* @desc: Transport descriptor
* @core_ops: A pointer to a pointer used by the core SCMI stack to make the
* core transport operations accessible to the transports.
+ * @th: An optional pointer to the transport handle
*/
struct scmi_transport {
struct device *supplier;
struct scmi_desc desc;
struct scmi_transport_core_operations **core_ops;
+ const struct scmi_transport_handle *th;
+};
+
+/**
+ * struct scmi_transport_supplier - Transport descriptor
+ * @mtx: A mutex to protect @available
+ * @available: A reference to an initialized transport device, when available.
+ * This reference is implicitly used to track the status of the
+ * supplier and it can cycle through the following 3 states:
+ * 1. NOT_READY - PTR_ERR(-EPROBE_DEFER): no supplier available;
+ * this is the transport initial state.
+ * 2. AVAILABLE - <supplier_dev>: a transport supplier has been
+ * initialized and it is available, ready to use.
+ * 3. BUSY _ PTR_ERR(-EBUSY): transport supplier is currently in use.
+ * @th: An embedded transport handle object that embeds the helpers
+ * implementing the above mentioned logic
+ *
+ * Note that this transport driver enforces single instance probing.
+ */
+struct scmi_transport_supplier {
+ /* Protect @available */
+ struct mutex mtx;
+ struct device *available;
+ const struct scmi_transport_handle th;
};
+#define to_sup(t) container_of(t, struct scmi_transport_supplier, th)
+
+/**
+ * scmi_transport_supplier_put - A helper to dispose of a supplier
+ * @th: A reference to the transport handle to use
+ * @supplier: A reference to the device supplier to manage, cannot be NULL
+ * or ERR_PTR.
+ *
+ * Note that putting a supplier will have different effect based on the
+ * current state of scmi_transport_supplier.available:
+ * - NOT_READY/BUSY: @supplier will be set as the new available device: this
+ * can be used to made available a supplier OR stop using one.
+ * - AVAILABLE: if the @supplier we are disposing of matches the currently
+ * available one, roll back to NOT_READY state.
+ * Any other attempt to override an available supplier with a
+ * new one is rejected, effectively enforcing one single supplier.
+ *
+ * Return: 0 on Success, errno otherwise.
+ */
+static inline int
+scmi_transport_supplier_put(const struct scmi_transport_handle *th,
+ struct device *supplier)
+{
+ struct scmi_transport_supplier *sup = to_sup(th);
+
+ /* Nothing to do when the provided supplier was never real */
+ if (IS_ERR_OR_NULL(supplier))
+ return 0;
+
+ guard(mutex)(&sup->mtx);
+ switch (PTR_ERR_OR_ZERO(sup->available)) {
+ case -EPROBE_DEFER:
+ case -EBUSY:
+ sup->available = supplier;
+ break;
+ case 0:
+ /* Putting a supplier when in the AVAILABLE state causes a
+ * transition back to the NOT_READY state, BUT only if the
+ * supplier we are disposing of was exactly the device that was
+ * previously made readily available.
+ */
+ if (supplier != sup->available)
+ return -EINVAL;
+ sup->available = ERR_PTR(-EPROBE_DEFER);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * scmi_transport_supplier_get - A helper to get hold of a supplier
+ * @th: A reference to the transport handle to use
+ *
+ * Note that, trying to get a supplier device can return:
+ * - a ready to use supplier device, (subsequently made unavailable)
+ * - PTR_ERR(-EPROBE_DEFER): no supplier is available
+ * - PTR_ERR(-BUSY): supplier was already taken by a previous get
+ *
+ * This allows the probe to defer and wait when a possible device can
+ * be reasonably expected to appear.
+ *
+ * Return: a usable supplier device on Success or PTR_ERR on Failure.
+ */
+static inline struct device *
+scmi_transport_supplier_get(const struct scmi_transport_handle *th)
+{
+ struct scmi_transport_supplier *sup = to_sup(th);
+ struct device *supplier;
+
+ guard(mutex)(&sup->mtx);
+ supplier = sup->available;
+ if (!IS_ERR(sup->available))
+ sup->available = ERR_PTR(-EBUSY);
+
+ return supplier;
+}
+
+#define DEFINE_SCMI_TRANSPORT_SUPPLIER(__supplier) \
+struct scmi_transport_supplier __supplier = { \
+ .mtx = __MUTEX_INITIALIZER(__supplier.mtx), \
+ .available = INIT_ERR_PTR(-EPROBE_DEFER), \
+ .th.supplier_get = scmi_transport_supplier_get, \
+ .th.supplier_put = scmi_transport_supplier_put, \
+}
+
#define DEFINE_SCMI_TRANSPORT_DRIVER(__tag, __drv, __desc, __match, __core_ops)\
static void __tag##_dev_free(void *data) \
{ \
struct platform_device *spdev = data; \
+ struct scmi_transport *strans; \
+ \
+ strans = dev_get_platdata(&spdev->dev); \
+ if (strans && strans->th) \
+ strans->th->supplier_put(strans->th, strans->supplier); \
\
platform_device_unregister(spdev); \
} \
\
static int __tag##_probe(struct platform_device *pdev) \
{ \
- struct device *dev = &pdev->dev; \
+ struct device *dev = &pdev->dev, *supplier; \
struct platform_device *spdev; \
struct scmi_transport strans; \
int ret; \
\
+ supplier = dev; \
+ strans.th = device_get_match_data(dev); \
+ if (strans.th) { \
+ supplier = strans.th->supplier_get(strans.th); \
+ if (IS_ERR(supplier)) \
+ return PTR_ERR(supplier); \
+ } \
+ \
spdev = platform_device_alloc("arm-scmi", PLATFORM_DEVID_AUTO); \
- if (!spdev) \
- return -ENOMEM; \
+ if (!spdev) { \
+ ret = -ENOMEM; \
+ goto err_mem; \
+ } \
\
device_set_of_node_from_dev(&spdev->dev, dev); \
\
- strans.supplier = dev; \
+ strans.supplier = supplier; \
memcpy(&strans.desc, &(__desc), sizeof(strans.desc)); \
strans.core_ops = &(__core_ops); \
\
@@ -515,6 +666,10 @@ static int __tag##_probe(struct platform_device *pdev) \
\
err: \
platform_device_put(spdev); \
+err_mem: \
+ if (strans.th) \
+ strans.th->supplier_put(strans.th, supplier); \
+ \
return ret; \
} \
\
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index f167194f7cf6..3e0d975ec94c 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -17,6 +17,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/bitmap.h>
+#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/export.h>
@@ -133,7 +134,7 @@ struct scmi_protocol_instance {
* usage.
* @protocols_mtx: A mutex to protect protocols instances initialization.
* @protocols_imp: List of protocols implemented, currently maximum of
- * scmi_revision_info.num_protocols elements allocated by the
+ * scmi_base_info.num_protocols elements allocated by the
* base protocol
* @active_protocols: IDR storing device_nodes for protocols actually defined
* in the DT and confirmed as implemented by fw.
@@ -151,7 +152,7 @@ struct scmi_info {
int id;
struct device *dev;
const struct scmi_desc *desc;
- struct scmi_revision_info version;
+ struct scmi_base_info version;
struct scmi_handle handle;
struct scmi_xfers_info tx_minfo;
struct scmi_xfers_info rx_minfo;
@@ -265,7 +266,7 @@ scmi_vendor_protocol_lookup(int protocol_id, char *vendor_id,
}
static const struct scmi_protocol *
-scmi_vendor_protocol_get(int protocol_id, struct scmi_revision_info *version)
+scmi_vendor_protocol_get(int protocol_id, struct scmi_base_info *version)
{
const struct scmi_protocol *proto;
@@ -303,7 +304,7 @@ scmi_vendor_protocol_get(int protocol_id, struct scmi_revision_info *version)
}
static const struct scmi_protocol *
-scmi_protocol_get(int protocol_id, struct scmi_revision_info *version)
+scmi_protocol_get(int protocol_id, struct scmi_base_info *version)
{
const struct scmi_protocol *proto = NULL;
@@ -1789,86 +1790,115 @@ static void *scmi_iterator_init(const struct scmi_protocol_handle *ph,
size_t tx_size, void *priv)
{
int ret;
- struct scmi_iterator *i;
- i = devm_kzalloc(ph->dev, sizeof(*i), GFP_KERNEL);
+ struct scmi_iterator *i __free(kfree) = kzalloc(sizeof(*i), GFP_KERNEL);
if (!i)
return ERR_PTR(-ENOMEM);
+ if (!ops || !ph)
+ return ERR_PTR(-EINVAL);
+
i->ph = ph;
i->ops = ops;
i->priv = priv;
ret = ph->xops->xfer_get_init(ph, msg_id, tx_size, 0, &i->t);
- if (ret) {
- devm_kfree(ph->dev, i);
+ if (ret)
return ERR_PTR(ret);
- }
i->state.max_resources = max_resources;
i->msg = i->t->tx.buf;
i->resp = i->t->rx.buf;
- return i;
+ return no_free_ptr(i);
}
-static int scmi_iterator_run(void *iter)
+static int __scmi_iterator_run(void *iter, unsigned int *start, unsigned int *end)
{
- int ret = -EINVAL;
+ int ret;
struct scmi_iterator_ops *iops;
const struct scmi_protocol_handle *ph;
struct scmi_iterator_state *st;
- struct scmi_iterator *i = iter;
+ struct scmi_iterator *i;
+ unsigned int n;
- if (!i || !i->ops || !i->ph)
- return ret;
+ if (!iter)
+ return -EINVAL;
+ i = iter;
iops = i->ops;
ph = i->ph;
st = &i->state;
+ /* Reinitialize state for next run */
+ st->num_returned = 0;
+ st->num_remaining = 0;
+ st->desc_index = start ? *start : 0;
+
do {
iops->prepare_message(i->msg, st->desc_index, i->priv);
ret = ph->xops->do_xfer(ph, i->t);
if (ret)
- break;
+ return ret;
st->rx_len = i->t->rx.len;
ret = iops->update_state(st, i->resp, i->priv);
if (ret)
- break;
+ return ret;
if (st->num_returned > st->max_resources - st->desc_index) {
dev_err(ph->dev,
"No. of resources can't exceed %d\n",
st->max_resources);
- ret = -EINVAL;
- break;
+ return -EINVAL;
}
- for (st->loop_idx = 0; st->loop_idx < st->num_returned;
- st->loop_idx++) {
+ if (end)
+ n = min(st->num_returned, *end - st->desc_index + 1);
+ else
+ n = st->num_returned;
+ for (st->loop_idx = 0; st->loop_idx < n; st->loop_idx++) {
ret = iops->process_response(ph, i->resp, st, i->priv);
if (ret)
- goto out;
+ return ret;
}
- st->desc_index += st->num_returned;
+ st->desc_index += n;
ph->xops->reset_rx_to_maxsz(ph, i->t);
/*
* check for both returned and remaining to avoid infinite
* loop due to buggy firmware
*/
- } while (st->num_returned && st->num_remaining);
+ } while (st->num_returned && st->num_remaining &&
+ (!end || (st->desc_index <= min(*end, st->max_resources - 1))));
-out:
- /* Finalize and destroy iterator */
- ph->xops->xfer_put(ph, i->t);
- devm_kfree(ph->dev, i);
+ return 0;
+}
+
+static void scmi_iterator_bound_cleanup(void *iter)
+{
+ struct scmi_iterator *i = iter;
+
+ i->ph->xops->xfer_put(i->ph, i->t);
+ kfree(i);
+}
+
+static int scmi_iterator_run(void *iter)
+{
+ int ret;
+
+ ret = __scmi_iterator_run(iter, NULL, NULL);
+ scmi_iterator_bound_cleanup(iter);
return ret;
}
+static int scmi_iterator_run_bound(void *iter, unsigned int *start,
+ unsigned int *end)
+{
+ return __scmi_iterator_run(iter, start, end);
+}
+
struct scmi_msg_get_fc_info {
__le32 domain;
__le32 message_id;
@@ -2047,6 +2077,8 @@ static const struct scmi_proto_helpers_ops helpers_ops = {
.get_max_msg_size = scmi_common_get_max_msg_size,
.iter_response_init = scmi_iterator_init,
.iter_response_run = scmi_iterator_run,
+ .iter_response_run_bound = scmi_iterator_run_bound,
+ .iter_response_bound_cleanup = scmi_iterator_bound_cleanup,
.protocol_msg_check = scmi_protocol_msg_check,
.fastchannel_init = scmi_common_fastchannel_init,
.fastchannel_db_ring = scmi_common_fastchannel_db_ring,
@@ -2063,7 +2095,7 @@ static const struct scmi_proto_helpers_ops helpers_ops = {
* Return: A reference to the version memory area associated to the SCMI
* instance underlying this protocol handle.
*/
-struct scmi_revision_info *
+struct scmi_base_info *
scmi_revision_area_get(const struct scmi_protocol_handle *ph)
{
const struct scmi_protocol_instance *pi = ph_to_pi(ph);
@@ -2376,7 +2408,7 @@ scmi_is_protocol_implemented(const struct scmi_handle *handle, u8 prot_id)
{
int i;
struct scmi_info *info = handle_to_scmi_info(handle);
- struct scmi_revision_info *rev = handle->version;
+ struct scmi_base_info *rev = handle->version;
if (!info->protocols_imp)
return false;
@@ -2552,13 +2584,11 @@ static bool scmi_is_transport_atomic(const struct scmi_handle *handle,
*/
static struct scmi_handle *scmi_handle_get(struct device *dev)
{
- struct list_head *p;
struct scmi_info *info;
struct scmi_handle *handle = NULL;
mutex_lock(&scmi_list_mutex);
- list_for_each(p, &scmi_list) {
- info = list_entry(p, struct scmi_info, node);
+ list_for_each_entry(info, &scmi_list, node) {
if (dev->parent == info->dev) {
info->users++;
handle = &info->handle;
@@ -3171,7 +3201,7 @@ static const struct scmi_desc *scmi_transport_setup(struct device *dev)
static void scmi_enable_matching_quirks(struct scmi_info *info)
{
- struct scmi_revision_info *rev = &info->version;
+ struct scmi_base_info *rev = &info->version;
dev_dbg(info->dev, "Looking for quirks matching: %s/%s/0x%08X\n",
rev->vendor_id, rev->sub_vendor_id, rev->impl_ver);
diff --git a/drivers/firmware/arm_scmi/power.c b/drivers/firmware/arm_scmi/power.c
index bb5062ab8280..28ef63a4ecc2 100644
--- a/drivers/firmware/arm_scmi/power.c
+++ b/drivers/firmware/arm_scmi/power.c
@@ -204,8 +204,12 @@ scmi_power_name_get(const struct scmi_protocol_handle *ph,
u32 domain)
{
struct scmi_power_info *pi = ph->get_priv(ph);
- struct power_dom_info *dom = pi->dom_info + domain;
+ struct power_dom_info *dom;
+
+ if (domain >= pi->num_domains)
+ return "unknown";
+ dom = pi->dom_info + domain;
return dom->name;
}
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index ab9733f4458b..27e3c805e927 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -453,10 +453,14 @@ static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
return -EINVAL;
/* Just log the last set request if acting on a disabled domain */
- if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2 &&
- !pi->states[domain_id].enabled) {
- pi->states[domain_id].last_pcap = power_cap;
- return 0;
+ if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2) {
+ if (!scmi_powercap_dom_info_get(ph, domain_id))
+ return -EINVAL;
+
+ if (!pi->states[domain_id].enabled) {
+ pi->states[domain_id].last_pcap = power_cap;
+ return 0;
+ }
}
return __scmi_powercap_cap_set(ph, pi, domain_id,
@@ -637,6 +641,9 @@ static int scmi_powercap_cap_enable_set(const struct scmi_protocol_handle *ph,
if (PROTOCOL_REV_MAJOR(ph->version) < 0x2)
return -EINVAL;
+ if (!scmi_powercap_dom_info_get(ph, domain_id))
+ return -EINVAL;
+
if (enable == pi->states[domain_id].enabled)
return 0;
@@ -678,6 +685,9 @@ static int scmi_powercap_cap_enable_get(const struct scmi_protocol_handle *ph,
if (PROTOCOL_REV_MAJOR(ph->version) < 0x2)
return 0;
+ if (!scmi_powercap_dom_info_get(ph, domain_id))
+ return -EINVAL;
+
/*
* Report always real platform state; platform could have ignored
* a previous disable request. Default true on any error.
diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h
index f51245aca259..15ad5162e37a 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -259,7 +259,15 @@ struct scmi_fc_info {
* multi-part responses using the custom operations
* provided in @ops.
* @iter_response_run: A common helper to trigger the run of a previously
- * initialized iterator.
+ * initialized iterator. Note that unbound iterators are
+ * automatically cleaned up.
+ * @iter_response_run_bound: A common helper to trigger the run of a previously
+ * initialized iterator, but only within the
+ * specified, optional, @start and @end resource
+ * indexes. Note that these bound-iterators need
+ * explicit cleanup via @iter_response_bound_cleanup.
+ * @iter_response_bound_cleanup: A common helper to finally release the iterator
+ * for bound iterators.
* @protocol_msg_check: A common helper to check is a specific protocol message
* is supported.
* @fastchannel_init: A common helper used to initialize FC descriptors by
@@ -276,6 +284,9 @@ struct scmi_proto_helpers_ops {
unsigned int max_resources, u8 msg_id,
size_t tx_size, void *priv);
int (*iter_response_run)(void *iter);
+ int (*iter_response_run_bound)(void *iter,
+ unsigned int *start, unsigned int *end);
+ void (*iter_response_bound_cleanup)(void *iter);
int (*protocol_msg_check)(const struct scmi_protocol_handle *ph,
u32 message_id, u32 *attributes);
void (*fastchannel_init)(const struct scmi_protocol_handle *ph,
diff --git a/drivers/firmware/arm_scmi/quirks.c b/drivers/firmware/arm_scmi/quirks.c
index 03848283c2a0..9ed5ce4feded 100644
--- a/drivers/firmware/arm_scmi/quirks.c
+++ b/drivers/firmware/arm_scmi/quirks.c
@@ -219,9 +219,9 @@ static unsigned int scmi_quirk_signature(const char *vend, const char *sub_vend)
static int scmi_quirk_range_parse(struct scmi_quirk *quirk)
{
const char *last, *first __free(kfree) = NULL;
+ int ret = 0;
size_t len;
char *sep;
- int ret;
quirk->start_range = 0;
quirk->end_range = 0xFFFFFFFF;
@@ -238,16 +238,15 @@ static int scmi_quirk_range_parse(struct scmi_quirk *quirk)
if (sep)
*sep = '\0';
- if (sep == first) /* -X */
- ret = kstrtouint(first + 1, 0, &quirk->end_range);
- else /* X OR X- OR X-y */
+ if (sep != first) /* X OR X- OR X-y */ {
ret = kstrtouint(first, 0, &quirk->start_range);
- if (ret)
- return ret;
+ if (ret)
+ return ret;
+ }
if (!sep)
quirk->end_range = quirk->start_range;
- else if (sep != last) /* x-Y */
+ else if (sep != last) /* -X OR x-Y */
ret = kstrtouint(sep + 1, 0, &quirk->end_range);
if (quirk->start_range > quirk->end_range)
@@ -259,10 +258,8 @@ static int scmi_quirk_range_parse(struct scmi_quirk *quirk)
void scmi_quirks_initialize(void)
{
struct scmi_quirk *quirk;
- int i;
- for (i = 0, quirk = scmi_quirks_table[0]; quirk;
- i++, quirk = scmi_quirks_table[i]) {
+ for (unsigned int i = 0; (quirk = scmi_quirks_table[i]); i++) {
int ret;
ret = scmi_quirk_range_parse(quirk);
diff --git a/drivers/firmware/arm_scmi/sensors.c b/drivers/firmware/arm_scmi/sensors.c
index 882d55f987d2..b14bb1146356 100644
--- a/drivers/firmware/arm_scmi/sensors.c
+++ b/drivers/firmware/arm_scmi/sensors.c
@@ -793,7 +793,7 @@ static int scmi_sensor_config_get(const struct scmi_protocol_handle *ph,
if (!ret) {
struct scmi_sensor_info *s = si->sensors + sensor_id;
- *sensor_config = get_unaligned_le64(t->rx.buf);
+ *sensor_config = get_unaligned_le32(t->rx.buf);
s->sensor_config = *sensor_config;
}
@@ -1072,12 +1072,15 @@ scmi_sensor_fill_custom_report(const struct scmi_protocol_handle *ph,
case SCMI_EVENT_SENSOR_UPDATE:
{
int i;
+ size_t expected_sz;
struct scmi_sensor_info *s;
const struct scmi_sensor_update_notify_payld *p = payld;
struct scmi_sensor_update_report *r = report;
struct sensors_info *sinfo = ph->get_priv(ph);
- /* payld_sz is variable for this event */
+ if (payld_sz < sizeof(*p))
+ break;
+
r->sensor_id = le32_to_cpu(p->sensor_id);
if (r->sensor_id >= sinfo->num_sensors)
break;
@@ -1091,6 +1094,11 @@ scmi_sensor_fill_custom_report(const struct scmi_protocol_handle *ph,
* readings defined for this sensor or 1 for scalar sensors.
*/
r->readings_count = s->num_axis ?: 1;
+ expected_sz = sizeof(*p) + r->readings_count *
+ sizeof(p->readings[0]);
+ if (payld_sz < expected_sz)
+ break;
+
for (i = 0; i < r->readings_count; i++)
scmi_parse_sensor_readings(&r->readings[i],
&p->readings[i]);
diff --git a/drivers/firmware/arm_scmi/transports/optee.c b/drivers/firmware/arm_scmi/transports/optee.c
index 07ae18d5279d..dbe32e141748 100644
--- a/drivers/firmware/arm_scmi/transports/optee.c
+++ b/drivers/firmware/arm_scmi/transports/optee.c
@@ -154,6 +154,8 @@ static struct scmi_transport_core_operations *core;
/* There can be only 1 SCMI service in OP-TEE we connect to */
static struct scmi_optee_agent *scmi_optee_private;
+static DEFINE_SCMI_TRANSPORT_SUPPLIER(scmi_optee_supplier);
+
/* Open a session toward SCMI OP-TEE service with REE_KERNEL identity */
static int open_session(struct scmi_optee_agent *agent, u32 *tee_session)
{
@@ -522,7 +524,7 @@ static struct scmi_desc scmi_optee_desc = {
};
static const struct of_device_id scmi_of_match[] = {
- { .compatible = "linaro,scmi-optee" },
+ { .compatible = "linaro,scmi-optee", .data = &scmi_optee_supplier.th},
{ /* Sentinel */ },
};
@@ -561,18 +563,20 @@ static int scmi_optee_service_probe(struct tee_client_device *scmi_pta)
if (ret)
goto err;
- /* Ensure agent resources are all visible before scmi_optee_private is */
+ /* Ensure initialized scmi_optee_private is visible */
smp_mb();
scmi_optee_private = agent;
- ret = platform_driver_register(&scmi_optee_driver);
- if (ret) {
- scmi_optee_private = NULL;
- goto err;
- }
+ ret = scmi_transport_supplier_put(&scmi_optee_supplier.th, agent->dev);
+ if (ret)
+ goto err_put;
return 0;
+err_put:
+ /* Ensure cleared reference is visible before resources are released */
+ smp_store_mb(scmi_optee_private, NULL);
+
err:
tee_client_close_context(tee_ctx);
@@ -586,13 +590,12 @@ static void scmi_optee_service_remove(struct tee_client_device *scmi_pta)
if (!scmi_optee_private)
return;
- platform_driver_unregister(&scmi_optee_driver);
-
if (!list_empty(&scmi_optee_private->channel_list))
return;
/* Ensure cleared reference is visible before resources are released */
smp_store_mb(scmi_optee_private, NULL);
+ scmi_transport_supplier_put(&scmi_optee_supplier.th, agent->dev);
tee_client_close_context(agent->tee_ctx);
}
@@ -616,7 +619,30 @@ static struct tee_client_driver scmi_optee_service_driver = {
},
};
-module_tee_client_driver(scmi_optee_service_driver);
+static int __init scmi_transport_optee_init(void)
+{
+ int ret;
+
+ ret = tee_client_driver_register(&scmi_optee_service_driver);
+ if (ret)
+ return ret;
+
+ ret = platform_driver_register(&scmi_optee_driver);
+ if (ret) {
+ tee_client_driver_unregister(&scmi_optee_service_driver);
+ return ret;
+ }
+
+ return ret;
+}
+module_init(scmi_transport_optee_init);
+
+static void __exit scmi_transport_optee_exit(void)
+{
+ platform_driver_unregister(&scmi_optee_driver);
+ tee_client_driver_unregister(&scmi_optee_service_driver);
+}
+module_exit(scmi_transport_optee_exit);
MODULE_AUTHOR("Etienne Carriere <etienne.carriere@foss.st.com>");
MODULE_DESCRIPTION("SCMI OPTEE Transport driver");
diff --git a/drivers/firmware/arm_scmi/transports/virtio.c b/drivers/firmware/arm_scmi/transports/virtio.c
index 326c4a93e44b..3282d8271839 100644
--- a/drivers/firmware/arm_scmi/transports/virtio.c
+++ b/drivers/firmware/arm_scmi/transports/virtio.c
@@ -4,7 +4,7 @@
* (SCMI).
*
* Copyright (C) 2020-2022 OpenSynergy.
- * Copyright (C) 2021-2024 ARM Ltd.
+ * Copyright (C) 2021-2026 ARM Ltd.
*/
/**
@@ -116,6 +116,8 @@ static struct scmi_transport_core_operations *core;
/* Only one SCMI VirtIO device can possibly exist */
static struct virtio_device *scmi_vdev;
+static DEFINE_SCMI_TRANSPORT_SUPPLIER(scmi_virtio_supplier);
+
static void scmi_vio_channel_ready(struct scmi_vio_channel *vioch,
struct scmi_chan_info *cinfo)
{
@@ -394,6 +396,10 @@ static bool virtio_chan_available(struct device_node *of_node, int idx)
return false;
}
+ dev_dbg(&scmi_vdev->dev, "%s Channel %sAVAILABLE on SCMI Virtio device.\n",
+ idx == VIRTIO_SCMI_VQ_TX ? "TX" : "RX",
+ (vioch && !vioch->cinfo) ? "" : "NOT ");
+
return vioch && !vioch->cinfo;
}
@@ -410,7 +416,7 @@ static int virtio_chan_setup(struct scmi_chan_info *cinfo, struct device *dev,
int i;
if (!scmi_vdev)
- return -EPROBE_DEFER;
+ return -EINVAL;
vioch = &((struct scmi_vio_channel *)scmi_vdev->priv)[index];
@@ -460,6 +466,9 @@ static int virtio_chan_setup(struct scmi_chan_info *cinfo, struct device *dev,
scmi_vio_channel_ready(vioch, cinfo);
+ dev_dbg(&scmi_vdev->dev, "%s Channel SETUP on SCMI Virtio device.\n",
+ tx ? "TX" : "RX");
+
return 0;
}
@@ -801,7 +810,7 @@ static struct scmi_desc scmi_virtio_desc = {
};
static const struct of_device_id scmi_of_match[] = {
- { .compatible = "arm,scmi-virtio" },
+ { .compatible = "arm,scmi-virtio", .data = &scmi_virtio_supplier.th},
{ /* Sentinel */ },
};
@@ -864,33 +873,33 @@ static int scmi_vio_probe(struct virtio_device *vdev)
sz = MSG_TOKEN_MAX;
}
channels[i].max_msg = sz;
+ dev_dbg(dev, "VQ%d initialized with max_msg: %d\n", i, sz);
}
vdev->priv = channels;
-
/* Ensure initialized scmi_vdev is visible */
smp_store_mb(scmi_vdev, vdev);
/* Set device ready */
virtio_device_ready(vdev);
- ret = platform_driver_register(&scmi_virtio_driver);
+ ret = scmi_transport_supplier_put(&scmi_virtio_supplier.th, &vdev->dev);
if (ret) {
+ virtio_reset_device(vdev);
vdev->priv = NULL;
vdev->config->del_vqs(vdev);
/* Ensure NULLified scmi_vdev is visible */
smp_store_mb(scmi_vdev, NULL);
-
return ret;
}
+ dev_dbg(dev, "Probed and initialized SCMI Virtio device.\n");
+
return 0;
}
static void scmi_vio_remove(struct virtio_device *vdev)
{
- platform_driver_unregister(&scmi_virtio_driver);
-
/*
* Once we get here, virtio_chan_free() will have already been called by
* the SCMI core for any existing channel and, as a consequence, all the
@@ -900,8 +909,10 @@ static void scmi_vio_remove(struct virtio_device *vdev)
*/
virtio_reset_device(vdev);
vdev->config->del_vqs(vdev);
+
/* Ensure scmi_vdev is visible as NULL */
smp_store_mb(scmi_vdev, NULL);
+ scmi_transport_supplier_put(&scmi_virtio_supplier.th, &vdev->dev);
}
static int scmi_vio_validate(struct virtio_device *vdev)
@@ -936,7 +947,30 @@ static struct virtio_driver virtio_scmi_driver = {
.validate = scmi_vio_validate,
};
-module_virtio_driver(virtio_scmi_driver);
+static int __init scmi_transport_virtio_init(void)
+{
+ int ret;
+
+ ret = register_virtio_driver(&virtio_scmi_driver);
+ if (ret)
+ return ret;
+
+ ret = platform_driver_register(&scmi_virtio_driver);
+ if (ret) {
+ unregister_virtio_driver(&virtio_scmi_driver);
+ return ret;
+ }
+
+ return ret;
+}
+module_init(scmi_transport_virtio_init);
+
+static void __exit scmi_transport_virtio_exit(void)
+{
+ platform_driver_unregister(&scmi_virtio_driver);
+ unregister_virtio_driver(&virtio_scmi_driver);
+}
+module_exit(scmi_transport_virtio_exit);
MODULE_AUTHOR("Igor Skalkin <igor.skalkin@opensynergy.com>");
MODULE_AUTHOR("Peter Hilber <peter.hilber@opensynergy.com>");
diff --git a/drivers/firmware/arm_scmi/vendors/imx/imx-sm-misc.c b/drivers/firmware/arm_scmi/vendors/imx/imx-sm-misc.c
index 0ada753367ef..637973fb45e6 100644
--- a/drivers/firmware/arm_scmi/vendors/imx/imx-sm-misc.c
+++ b/drivers/firmware/arm_scmi/vendors/imx/imx-sm-misc.c
@@ -27,6 +27,7 @@ enum scmi_imx_misc_protocol_cmd {
SCMI_IMX_MISC_CTRL_GET = 0x4,
SCMI_IMX_MISC_DISCOVER_BUILD_INFO = 0x6,
SCMI_IMX_MISC_CTRL_NOTIFY = 0x8,
+ SCMI_IMX_MISC_RESET_REASON_GET = 0xA,
SCMI_IMX_MISC_CFG_INFO_GET = 0xC,
SCMI_IMX_MISC_SYSLOG_GET = 0xD,
SCMI_IMX_MISC_BOARD_INFO = 0xE,
@@ -89,6 +90,37 @@ struct scmi_imx_misc_cfg_info_out {
u8 cfgname[MISC_MAX_CFGNAME];
};
+struct scmi_imx_misc_reset_reason_in {
+#define MISC_REASON_FLAG_SYSTEM BIT(0)
+ __le32 flags;
+};
+
+struct scmi_imx_misc_reset_reason_out {
+ /* Boot reason flags */
+#define MISC_BOOT_FLAG_VLD BIT(31)
+#define MISC_BOOT_FLAG_ORG_VLD BIT(28)
+#define MISC_BOOT_FLAG_ORIGIN GENMASK(27, 24)
+#define MISC_BOOT_FLAG_O_SHIFT 24
+#define MISC_BOOT_FLAG_ERR_VLD BIT(23)
+#define MISC_BOOT_FLAG_ERR_ID GENMASK(22, 8)
+#define MISC_BOOT_FLAG_E_SHIFT 8
+#define MISC_BOOT_FLAG_REASON GENMASK(7, 0)
+ __le32 b_flags;
+ /* Shutdown reason flags */
+#define MISC_SHUTDOWN_FLAG_VLD BIT(31)
+#define MISC_SHUTDOWN_FLAG_EXT_LEN GENMASK(30, 29)
+#define MISC_SHUTDOWN_FLAG_ORG_VLD BIT(28)
+#define MISC_SHUTDOWN_FLAG_ORIGIN GENMASK(27, 24)
+#define MISC_SHUTDOWN_FLAG_O_SHIFT 24
+#define MISC_SHUTDOWN_FLAG_ERR_VLD BIT(23)
+#define MISC_SHUTDOWN_FLAG_ERR_ID GENMASK(22, 8)
+#define MISC_SHUTDOWN_FLAG_E_SHIFT 8
+#define MISC_SHUTDOWN_FLAG_REASON GENMASK(7, 0)
+ __le32 s_flags;
+ /* Array of extended info words */
+ __le32 extinfo[MISC_EXT_INFO_LEN_MAX];
+};
+
struct scmi_imx_misc_syslog_in {
__le32 flags;
__le32 index;
@@ -452,11 +484,65 @@ static int scmi_imx_misc_syslog_get(const struct scmi_protocol_handle *ph, u16 *
return ph->hops->iter_response_run(iter);
}
+static int scmi_imx_misc_reset_reason(const struct scmi_protocol_handle *ph, bool system,
+ struct scmi_imx_misc_reset_reason *boot_r,
+ struct scmi_imx_misc_reset_reason *shut_r,
+ u32 *extinfo)
+{
+ struct scmi_imx_misc_reset_reason_in *in;
+ struct scmi_imx_misc_reset_reason_out *out;
+ struct scmi_xfer *t;
+ int ret;
+
+ ret = ph->xops->xfer_get_init(ph, SCMI_IMX_MISC_RESET_REASON_GET, sizeof(*in),
+ sizeof(*out), &t);
+ if (ret)
+ return ret;
+
+ in = t->tx.buf;
+ if (system)
+ in->flags = le32_encode_bits(1, MISC_REASON_FLAG_SYSTEM);
+ else
+ in->flags = cpu_to_le32(0);
+
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ out = t->rx.buf;
+ if (boot_r) {
+ boot_r->valid = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_VLD);
+ boot_r->orig_valid = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_ORG_VLD);
+ boot_r->err_valid = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_ERR_VLD);
+ boot_r->reason = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_REASON);
+ boot_r->origin = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_ORIGIN);
+ boot_r->errid = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_ERR_ID);
+ }
+
+ if (shut_r) {
+ shut_r->valid = le32_get_bits(out->s_flags, MISC_SHUTDOWN_FLAG_VLD);
+ shut_r->orig_valid = le32_get_bits(out->s_flags,
+ MISC_SHUTDOWN_FLAG_ORG_VLD);
+ shut_r->err_valid = le32_get_bits(out->s_flags,
+ MISC_SHUTDOWN_FLAG_ERR_VLD);
+ shut_r->reason = le32_get_bits(out->s_flags, MISC_SHUTDOWN_FLAG_REASON);
+ shut_r->origin = le32_get_bits(out->s_flags, MISC_SHUTDOWN_FLAG_ORIGIN);
+ shut_r->errid = le32_get_bits(out->s_flags, MISC_SHUTDOWN_FLAG_ERR_ID);
+ }
+
+ if (extinfo)
+ memcpy_from_le32(extinfo, out->extinfo, MISC_EXT_INFO_LEN_MAX);
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
static const struct scmi_imx_misc_proto_ops scmi_imx_misc_proto_ops = {
.misc_ctrl_set = scmi_imx_misc_ctrl_set,
.misc_ctrl_get = scmi_imx_misc_ctrl_get,
.misc_ctrl_req_notify = scmi_imx_misc_ctrl_notify,
.misc_syslog = scmi_imx_misc_syslog_get,
+ .misc_reset_reason = scmi_imx_misc_reset_reason,
};
static int scmi_imx_misc_protocol_init(const struct scmi_protocol_handle *ph)
diff --git a/drivers/firmware/imx/sm-misc.c b/drivers/firmware/imx/sm-misc.c
index 0a8ada329c9d..16b5ff833d21 100644
--- a/drivers/firmware/imx/sm-misc.c
+++ b/drivers/firmware/imx/sm-misc.c
@@ -18,6 +18,29 @@ static const struct scmi_imx_misc_proto_ops *imx_misc_ctrl_ops;
static struct scmi_protocol_handle *ph;
struct notifier_block scmi_imx_misc_ctrl_nb;
+static const char * const rst_imx95[] = {
+ "cm33_lockup", "cm33_swreq", "cm7_lockup", "cm7_swreq", "fccu",
+ "jtag_sw", "ele", "tempsense", "wdog1", "wdog2", "wdog3", "wdog4",
+ "wdog5", "jtag", "cm33_exc", "bbm", "sw", "sm_err", "fusa_sreco",
+ "pmic", "unused", "unused", "unused", "unused", "unused", "unused",
+ "unused", "unused", "unused", "unused", "unused", "por",
+};
+
+static const char * const rst_imx94[] = {
+ "cm33_lockup", "cm33_swreq", "cm70_lockup", "cm70_swreq", "fccu",
+ "jtag_sw", "ele", "tempsense", "wdog1", "wdog2", "wdog3", "wdog4",
+ "wdog5", "jtag", "wdog6", "wdog7", "wdog8", "wo_netc", "cm33s_lockup",
+ "cm33s_swreq", "cm71_lockup", "cm71_swreq", "cm33_exc", "bbm", "sw",
+ "sm_err", "fusa_sreco", "pmic", "unused", "unused", "unused", "por",
+};
+
+static const struct of_device_id allowlist[] = {
+ { .compatible = "fsl,imx952", .data = rst_imx95 },
+ { .compatible = "fsl,imx95", .data = rst_imx95 },
+ { .compatible = "fsl,imx94", .data = rst_imx94 },
+ { /* Sentinel */ }
+};
+
int scmi_imx_misc_ctrl_set(u32 id, u32 val)
{
if (!ph)
@@ -75,6 +98,54 @@ static void scmi_imx_misc_put(void *p)
debugfs_remove((struct dentry *)p);
}
+static int scmi_imx_misc_get_reason(struct scmi_device *sdev)
+{
+ struct scmi_imx_misc_reset_reason boot, shutdown;
+ const char **rst;
+ bool system = true;
+ int ret;
+
+ if (!of_machine_device_match(allowlist))
+ return 0;
+
+ rst = (const char **)of_machine_get_match_data(allowlist);
+
+ ret = imx_misc_ctrl_ops->misc_reset_reason(ph, system, &boot, &shutdown, NULL);
+ if (!ret) {
+ if (boot.valid)
+ dev_info(&sdev->dev, "%s Boot reason: %s, origin: %d, errid: %d\n",
+ system ? "SYS" : "LM", rst[boot.reason],
+ boot.orig_valid ? boot.origin : -1,
+ boot.err_valid ? boot.errid : -1);
+ if (shutdown.valid)
+ dev_info(&sdev->dev, "%s shutdown reason: %s, origin: %d, errid: %d\n",
+ system ? "SYS" : "LM", rst[shutdown.reason],
+ shutdown.orig_valid ? shutdown.origin : -1,
+ shutdown.err_valid ? shutdown.errid : -1);
+ } else {
+ dev_err(&sdev->dev, "Failed to get system reset reason: %d\n", ret);
+ }
+
+ system = false;
+ ret = imx_misc_ctrl_ops->misc_reset_reason(ph, system, &boot, &shutdown, NULL);
+ if (!ret) {
+ if (boot.valid)
+ dev_info(&sdev->dev, "%s Boot reason: %s, origin: %d, errid: %d\n",
+ system ? "SYS" : "LM", rst[boot.reason],
+ boot.orig_valid ? boot.origin : -1,
+ boot.err_valid ? boot.errid : -1);
+ if (shutdown.valid)
+ dev_info(&sdev->dev, "%s shutdown reason: %s, origin: %d, errid: %d\n",
+ system ? "SYS" : "LM", rst[shutdown.reason],
+ shutdown.orig_valid ? shutdown.origin : -1,
+ shutdown.err_valid ? shutdown.errid : -1);
+ } else {
+ dev_err(&sdev->dev, "Failed to get lm reset reason: %d\n", ret);
+ }
+
+ return 0;
+}
+
static int scmi_imx_misc_ctrl_probe(struct scmi_device *sdev)
{
const struct scmi_handle *handle = sdev->handle;
@@ -133,6 +204,8 @@ static int scmi_imx_misc_ctrl_probe(struct scmi_device *sdev)
scmi_imx_dentry = debugfs_create_dir("scmi_imx", NULL);
debugfs_create_file("syslog", 0444, scmi_imx_dentry, &sdev->dev, &syslog_fops);
+ scmi_imx_misc_get_reason(sdev);
+
return devm_add_action_or_reset(&sdev->dev, scmi_imx_misc_put, scmi_imx_dentry);
}
diff --git a/include/linux/scmi_imx_protocol.h b/include/linux/scmi_imx_protocol.h
index 2407d7693b6b..ab867463c08c 100644
--- a/include/linux/scmi_imx_protocol.h
+++ b/include/linux/scmi_imx_protocol.h
@@ -52,6 +52,17 @@ struct scmi_imx_misc_ctrl_notify_report {
unsigned int flags;
};
+
+#define MISC_EXT_INFO_LEN_MAX 4
+struct scmi_imx_misc_reset_reason {
+ bool valid:1;
+ bool orig_valid:1;
+ bool err_valid:1;
+ u32 reason;
+ u32 origin;
+ u32 errid;
+};
+
struct scmi_imx_misc_proto_ops {
int (*misc_ctrl_set)(const struct scmi_protocol_handle *ph, u32 id,
u32 num, u32 *val);
@@ -61,6 +72,9 @@ struct scmi_imx_misc_proto_ops {
u32 ctrl_id, u32 evt_id, u32 flags);
int (*misc_syslog)(const struct scmi_protocol_handle *ph, u16 *size,
void *array);
+ int (*misc_reset_reason)(const struct scmi_protocol_handle *ph,
+ bool system, struct scmi_imx_misc_reset_reason *boot_r,
+ struct scmi_imx_misc_reset_reason *shut_r, u32 *extinfo);
};
/* See LMM_ATTRIBUTES in imx95.rst */
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index aafaac1496b0..5ab73b1ab9aa 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -15,10 +15,9 @@
#define SCMI_MAX_STR_SIZE 64
#define SCMI_SHORT_NAME_MAX_SIZE 16
-#define SCMI_MAX_NUM_RATES 16
/**
- * struct scmi_revision_info - version information structure
+ * struct scmi_base_info - version information structure
*
* @major_ver: Major ABI version. Change here implies risk of backward
* compatibility break.
@@ -31,7 +30,7 @@
* @vendor_id: A vendor identifier(Null terminated ASCII string)
* @sub_vendor_id: A sub-vendor identifier(Null terminated ASCII string)
*/
-struct scmi_revision_info {
+struct scmi_base_info {
u16 major_ver;
u16 minor_ver;
u8 num_protocols;
@@ -41,27 +40,23 @@ struct scmi_revision_info {
char sub_vendor_id[SCMI_SHORT_NAME_MAX_SIZE];
};
+struct scmi_clock_rates {
+ bool rate_discrete;
+ unsigned int num_rates;
+ u64 *rates;
+};
+
struct scmi_clock_info {
char name[SCMI_MAX_STR_SIZE];
unsigned int enable_latency;
- bool rate_discrete;
bool rate_changed_notifications;
bool rate_change_requested_notifications;
bool state_ctrl_forbidden;
bool rate_ctrl_forbidden;
bool parent_ctrl_forbidden;
bool extended_config;
- union {
- struct {
- int num_rates;
- u64 rates[SCMI_MAX_NUM_RATES];
- } list;
- struct {
- u64 min_rate;
- u64 max_rate;
- u64 step_size;
- } range;
- };
+ u64 min_rate;
+ u64 max_rate;
int num_parents;
u32 *parents;
};
@@ -91,6 +86,11 @@ enum scmi_clock_oem_config {
* @info_get: get the information of the specified clock
* @rate_get: request the current clock rate of a clock
* @rate_set: set the clock rate of a clock
+ * @determine_rate: determine the effective rate that can be supported by a
+ * clock calculating the closest allowed rate.
+ * Note that @rate is an input/output parameter used both to
+ * describe the requested rate and report the closest match
+ * @all_rates_get: get the list of all available rates for the specified clock.
* @enable: enables the specified clock
* @disable: disables the specified clock
* @state_get: get the status of the specified clock
@@ -108,6 +108,10 @@ struct scmi_clk_proto_ops {
u64 *rate);
int (*rate_set)(const struct scmi_protocol_handle *ph, u32 clk_id,
u64 rate);
+ int (*determine_rate)(const struct scmi_protocol_handle *ph, u32 clk_id,
+ unsigned long *rate);
+ const struct scmi_clock_rates __must_check *(*all_rates_get)
+ (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,
@@ -901,7 +905,7 @@ struct scmi_notify_ops {
*/
struct scmi_handle {
struct device *dev;
- struct scmi_revision_info *version;
+ struct scmi_base_info *version;
int __must_check (*devm_protocol_acquire)(struct scmi_device *sdev,
u8 proto);