* [PATCH v5 02/12] firmware: arm_scmi: Refactor powercap domain layout
From: Philip Radford @ 2026-04-28 9:09 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi,
Philip Radford
In-Reply-To: <20260428090922.346069-1-philip.radford@arm.com>
From: Cristian Marussi <cristian.marussi@arm.com>
SCMIv4.0 introduces the idea of an optional Concurrent Power Limit (CPL)
for each powercap domain, where CPL0 coincides with the one and only
per-domain constraint limit that was available in pre-v4.0 SCMI Powercap.
Refactor the powercap domain descriptors and powercap operations to allow
future v4.0 extensions to cope with multiple CPLs.
While at that generalize the powercap protocol API to drop PAI references
in favour of a more generic avg_ivl naming, since from v4.0 the number and
types of averaging intervals will change in a non-backward compatible way,
so let's bury these changes within the protocol layer.
Last but not least, make the necessary changes to the ARM SCMI Powwercap
driver in order to support all of these new capabilities.
No functional change.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
[Philip: Adjusted domain_id comparitor in scmi_powercap_pai_get]
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/firmware/arm_scmi/powercap.c | 182 +++++++++++++++++----------
drivers/powercap/arm_scmi_powercap.c | 50 ++++----
include/linux/scmi_protocol.h | 74 +++++++----
3 files changed, 188 insertions(+), 118 deletions(-)
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index 22aff71c75e9..47aa6dde4a52 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -2,7 +2,7 @@
/*
* System Control and Management Interface (SCMI) Powercap Protocol
*
- * Copyright (C) 2022 ARM Ltd.
+ * Copyright (C) 2022-2026 ARM Ltd.
*/
#define pr_fmt(fmt) "SCMI Notifications POWERCAP - " fmt
@@ -20,6 +20,8 @@
/* Updated only after ALL the mandatory features for that version are merged */
#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x20000
+#define CPL0 0
+
enum scmi_powercap_protocol_cmd {
POWERCAP_DOMAIN_ATTRIBUTES = 0x3,
POWERCAP_CAP_GET = 0x4,
@@ -192,27 +194,26 @@ scmi_powercap_validate(unsigned int min_val, unsigned int max_val,
static int
scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
- struct powercap_info *pinfo, u32 domain)
+ struct powercap_info *pinfo,
+ struct scmi_powercap_info *dom_info)
{
int ret;
u32 flags;
struct scmi_xfer *t;
- struct scmi_powercap_info *dom_info = pinfo->powercaps + domain;
struct scmi_msg_resp_powercap_domain_attributes *resp;
ret = ph->xops->xfer_get_init(ph, POWERCAP_DOMAIN_ATTRIBUTES,
- sizeof(domain), sizeof(*resp), &t);
+ sizeof(dom_info->id), sizeof(*resp), &t);
if (ret)
return ret;
- put_unaligned_le32(domain, t->tx.buf);
+ put_unaligned_le32(dom_info->id, t->tx.buf);
resp = t->rx.buf;
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
flags = le32_to_cpu(resp->attributes);
- dom_info->id = domain;
if (pinfo->notify_cap_cmd)
dom_info->notify_powercap_cap_change =
SUPPORTS_POWERCAP_CAP_CHANGE_NOTIFY(flags);
@@ -221,12 +222,9 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
SUPPORTS_POWERCAP_MEASUREMENTS_CHANGE_NOTIFY(flags);
dom_info->async_powercap_cap_set =
SUPPORTS_ASYNC_POWERCAP_CAP_SET(flags);
- dom_info->powercap_cap_config =
- SUPPORTS_POWERCAP_CAP_CONFIGURATION(flags);
+
dom_info->powercap_monitoring =
SUPPORTS_POWERCAP_MONITORING(flags);
- dom_info->powercap_pai_config =
- SUPPORTS_POWERCAP_PAI_CONFIGURATION(flags);
dom_info->powercap_scale_mw =
SUPPORTS_POWER_UNITS_MW(flags);
dom_info->powercap_scale_uw =
@@ -236,13 +234,29 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
strscpy(dom_info->name, resp->name, SCMI_SHORT_NAME_MAX_SIZE);
- dom_info->min_pai = le32_to_cpu(resp->min_pai);
- dom_info->max_pai = le32_to_cpu(resp->max_pai);
- dom_info->pai_step = le32_to_cpu(resp->pai_step);
- ret = scmi_powercap_validate(dom_info->min_pai,
- dom_info->max_pai,
- dom_info->pai_step,
- dom_info->powercap_pai_config);
+ dom_info->sustainable_power =
+ le32_to_cpu(resp->sustainable_power);
+ dom_info->accuracy = le32_to_cpu(resp->accuracy);
+
+ dom_info->parent_id = le32_to_cpu(resp->parent_id);
+ if (dom_info->parent_id != SCMI_POWERCAP_ROOT_ZONE_ID &&
+ (dom_info->parent_id >= pinfo->num_domains ||
+ dom_info->parent_id == dom_info->id)) {
+ dev_err(ph->dev,
+ "Platform reported inconsistent parent ID for domain %d - %s\n",
+ dom_info->id, dom_info->name);
+ ret = -ENODEV;
+ }
+
+ dom_info->cpli[0].avg_ivl_config =
+ SUPPORTS_POWERCAP_PAI_CONFIGURATION(flags);
+ dom_info->cpli[0].min_avg_ivl = le32_to_cpu(resp->min_pai);
+ dom_info->cpli[0].max_avg_ivl = le32_to_cpu(resp->max_pai);
+ dom_info->cpli[0].avg_ivl_step = le32_to_cpu(resp->pai_step);
+ ret = scmi_powercap_validate(dom_info->cpli[0].min_avg_ivl,
+ dom_info->cpli[0].max_avg_ivl,
+ dom_info->cpli[0].avg_ivl_step,
+ dom_info->cpli[0].avg_ivl_config);
if (ret) {
dev_err(ph->dev,
"Platform reported inconsistent PAI config for domain %d - %s\n",
@@ -250,13 +264,15 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
goto clean;
}
- dom_info->min_power_cap = le32_to_cpu(resp->min_power_cap);
- dom_info->max_power_cap = le32_to_cpu(resp->max_power_cap);
- dom_info->power_cap_step = le32_to_cpu(resp->power_cap_step);
- ret = scmi_powercap_validate(dom_info->min_power_cap,
- dom_info->max_power_cap,
- dom_info->power_cap_step,
- dom_info->powercap_cap_config);
+ dom_info->cpli[0].cap_config =
+ SUPPORTS_POWERCAP_CAP_CONFIGURATION(flags);
+ dom_info->cpli[0].min_power_cap = le32_to_cpu(resp->min_power_cap);
+ dom_info->cpli[0].max_power_cap = le32_to_cpu(resp->max_power_cap);
+ dom_info->cpli[0].power_cap_step = le32_to_cpu(resp->power_cap_step);
+ ret = scmi_powercap_validate(dom_info->cpli[0].min_power_cap,
+ dom_info->cpli[0].max_power_cap,
+ dom_info->cpli[0].power_cap_step,
+ dom_info->cpli[0].cap_config);
if (ret) {
dev_err(ph->dev,
"Platform reported inconsistent CAP config for domain %d - %s\n",
@@ -264,19 +280,9 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
goto clean;
}
- dom_info->sustainable_power =
- le32_to_cpu(resp->sustainable_power);
- dom_info->accuracy = le32_to_cpu(resp->accuracy);
-
- dom_info->parent_id = le32_to_cpu(resp->parent_id);
- if (dom_info->parent_id != SCMI_POWERCAP_ROOT_ZONE_ID &&
- (dom_info->parent_id >= pinfo->num_domains ||
- dom_info->parent_id == dom_info->id)) {
- dev_err(ph->dev,
- "Platform reported inconsistent parent ID for domain %d - %s\n",
- dom_info->id, dom_info->name);
- ret = -ENODEV;
- }
+ /* Just using same short name */
+ strscpy(dom_info->cpli[0].name, dom_info->name,
+ SCMI_SHORT_NAME_MAX_SIZE);
}
clean:
@@ -288,12 +294,30 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
*/
if (!ret && SUPPORTS_EXTENDED_NAMES(flags))
ph->hops->extended_name_get(ph, POWERCAP_DOMAIN_NAME_GET,
- domain, NULL, dom_info->name,
+ dom_info->id, NULL, dom_info->name,
SCMI_MAX_STR_SIZE);
return ret;
}
+static int
+scmi_powercap_domain_initialize(const struct scmi_protocol_handle *ph,
+ struct powercap_info *pinfo, u32 domain)
+{
+ struct scmi_powercap_info *dom_info = pinfo->powercaps + domain;
+
+ dom_info->num_cpli = 1;
+ dom_info->cpli = devm_kcalloc(ph->dev, dom_info->num_cpli,
+ sizeof(*dom_info->cpli), GFP_KERNEL);
+ if (!dom_info->cpli)
+ return -ENOMEM;
+
+ dom_info->id = domain;
+ dom_info->cpli[0].id = CPL0;
+
+ return scmi_powercap_domain_attributes_get(ph, pinfo, dom_info);
+}
+
static int scmi_powercap_num_domains_get(const struct scmi_protocol_handle *ph)
{
struct powercap_info *pi = ph->get_priv(ph);
@@ -335,10 +359,11 @@ static int scmi_powercap_xfer_cap_get(const struct scmi_protocol_handle *ph,
static int __scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
const struct scmi_powercap_info *dom,
- u32 *power_cap)
+ u32 cpl_id, u32 *power_cap)
{
- if (dom->fc_info && dom->fc_info[POWERCAP_FC_CAP].get_addr) {
- *power_cap = ioread32(dom->fc_info[POWERCAP_FC_CAP].get_addr);
+ if (dom->cpli[cpl_id].fc_info &&
+ dom->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].get_addr) {
+ *power_cap = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].get_addr);
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_CAP_GET,
dom->id, *power_cap, 0);
return 0;
@@ -348,7 +373,7 @@ static int __scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
}
static int scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 *power_cap)
+ u32 domain_id, u32 cpl_id, u32 *power_cap)
{
const struct scmi_powercap_info *dom;
@@ -359,12 +384,13 @@ static int scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
if (!dom)
return -EINVAL;
- return __scmi_powercap_cap_get(ph, dom, power_cap);
+ return __scmi_powercap_cap_get(ph, dom, cpl_id, power_cap);
}
static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
const struct scmi_powercap_info *pc,
- u32 power_cap, bool ignore_dresp)
+ u32 cpl_id, u32 power_cap,
+ bool ignore_dresp)
{
int ret;
struct scmi_xfer *t;
@@ -406,21 +432,23 @@ static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
struct powercap_info *pi, u32 domain_id,
- u32 power_cap, bool ignore_dresp)
+ u32 cpl_id, u32 power_cap, bool ignore_dresp)
{
int ret = -EINVAL;
const struct scmi_powercap_info *pc;
pc = scmi_powercap_dom_info_get(ph, domain_id);
- if (!pc || !pc->powercap_cap_config)
+ if (!pc || !pc->cpli[cpl_id].cap_config)
return ret;
if (power_cap &&
- (power_cap < pc->min_power_cap || power_cap > pc->max_power_cap))
+ (power_cap < pc->cpli[cpl_id].min_power_cap ||
+ power_cap > pc->cpli[cpl_id].max_power_cap))
return ret;
- if (pc->fc_info && pc->fc_info[POWERCAP_FC_CAP].set_addr) {
- struct scmi_fc_info *fci = &pc->fc_info[POWERCAP_FC_CAP];
+ if (pc->cpli[cpl_id].fc_info &&
+ pc->cpli[cpl_id].fc_info[POWERCAP_FC_CAP].set_addr) {
+ struct scmi_fc_info *fci = &pc->cpli[cpl_id].fc_info[POWERCAP_FC_CAP];
iowrite32(power_cap, fci->set_addr);
ph->hops->fastchannel_db_ring(fci->set_db);
@@ -428,7 +456,7 @@ static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
domain_id, power_cap, 0);
ret = 0;
} else {
- ret = scmi_powercap_xfer_cap_set(ph, pc, power_cap,
+ ret = scmi_powercap_xfer_cap_set(ph, pc, cpl_id, power_cap,
ignore_dresp);
}
@@ -440,7 +468,7 @@ static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
}
static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 power_cap,
+ u32 domain_id, u32 cpl_id, u32 power_cap,
bool ignore_dresp)
{
struct powercap_info *pi = ph->get_priv(ph);
@@ -459,7 +487,7 @@ static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
return 0;
}
- return __scmi_powercap_cap_set(ph, pi, domain_id,
+ return __scmi_powercap_cap_set(ph, pi, domain_id, cpl_id,
power_cap, ignore_dresp);
}
@@ -485,7 +513,7 @@ static int scmi_powercap_xfer_pai_get(const struct scmi_protocol_handle *ph,
}
static int scmi_powercap_pai_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 *pai)
+ u32 domain_id, u32 cpl_id, u32 *pai)
{
struct scmi_powercap_info *dom;
struct powercap_info *pi = ph->get_priv(ph);
@@ -494,8 +522,11 @@ static int scmi_powercap_pai_get(const struct scmi_protocol_handle *ph,
return -EINVAL;
dom = pi->powercaps + domain_id;
- if (dom->fc_info && dom->fc_info[POWERCAP_FC_PAI].get_addr) {
- *pai = ioread32(dom->fc_info[POWERCAP_FC_PAI].get_addr);
+ if (cpl_id >= dom->num_cpli)
+ return -EINVAL;
+
+ if (dom->cpli[cpl_id].fc_info && dom->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].get_addr) {
+ *pai = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].get_addr);
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_PAI_GET,
domain_id, *pai, 0);
return 0;
@@ -504,6 +535,12 @@ static int scmi_powercap_pai_get(const struct scmi_protocol_handle *ph,
return scmi_powercap_xfer_pai_get(ph, domain_id, pai);
}
+static int scmi_powercap_avg_interval_get(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *val)
+{
+ return scmi_powercap_pai_get(ph, domain_id, cpl_id, val);
+}
+
static int scmi_powercap_xfer_pai_set(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 pai)
{
@@ -528,17 +565,18 @@ static int scmi_powercap_xfer_pai_set(const struct scmi_protocol_handle *ph,
}
static int scmi_powercap_pai_set(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 pai)
+ u32 domain_id, u32 cpl_id, u32 pai)
{
const struct scmi_powercap_info *pc;
pc = scmi_powercap_dom_info_get(ph, domain_id);
- if (!pc || !pc->powercap_pai_config || !pai ||
- pai < pc->min_pai || pai > pc->max_pai)
+ if (!pc || cpl_id >= pc->num_cpli || !pc->cpli[cpl_id].avg_ivl_config ||
+ !pai || pai < pc->cpli[cpl_id].min_avg_ivl ||
+ pai > pc->cpli[cpl_id].max_avg_ivl)
return -EINVAL;
- if (pc->fc_info && pc->fc_info[POWERCAP_FC_PAI].set_addr) {
- struct scmi_fc_info *fci = &pc->fc_info[POWERCAP_FC_PAI];
+ if (pc->cpli[cpl_id].fc_info && pc->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].set_addr) {
+ struct scmi_fc_info *fci = &pc->cpli[cpl_id].fc_info[POWERCAP_FC_PAI];
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_PAI_SET,
domain_id, pai, 0);
@@ -550,6 +588,12 @@ static int scmi_powercap_pai_set(const struct scmi_protocol_handle *ph,
return scmi_powercap_xfer_pai_set(ph, domain_id, pai);
}
+static int scmi_powercap_avg_interval_set(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 val)
+{
+ return scmi_powercap_pai_set(ph, domain_id, cpl_id, val);
+}
+
static int scmi_powercap_measurements_get(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 *average_power,
u32 *pai)
@@ -645,11 +689,11 @@ static int scmi_powercap_cap_enable_set(const struct scmi_protocol_handle *ph,
if (!pi->states[domain_id].last_pcap)
return -EINVAL;
- ret = __scmi_powercap_cap_set(ph, pi, domain_id,
+ ret = __scmi_powercap_cap_set(ph, pi, domain_id, CPL0,
pi->states[domain_id].last_pcap,
true);
} else {
- ret = __scmi_powercap_cap_set(ph, pi, domain_id, 0, true);
+ ret = __scmi_powercap_cap_set(ph, pi, domain_id, CPL0, 0, true);
}
if (ret)
@@ -660,7 +704,7 @@ static int scmi_powercap_cap_enable_set(const struct scmi_protocol_handle *ph,
* server could have ignored a disable request and kept enforcing some
* powercap limit requested by other agents.
*/
- ret = scmi_powercap_cap_get(ph, domain_id, &power_cap);
+ ret = scmi_powercap_cap_get(ph, domain_id, CPL0, &power_cap);
if (!ret)
pi->states[domain_id].enabled = !!power_cap;
@@ -682,7 +726,7 @@ static int scmi_powercap_cap_enable_get(const struct scmi_protocol_handle *ph,
* Report always real platform state; platform could have ignored
* a previous disable request. Default true on any error.
*/
- ret = scmi_powercap_cap_get(ph, domain_id, &power_cap);
+ ret = scmi_powercap_cap_get(ph, domain_id, CPL0, &power_cap);
if (!ret)
*enable = !!power_cap;
@@ -699,8 +743,8 @@ static const struct scmi_powercap_proto_ops powercap_proto_ops = {
.cap_set = scmi_powercap_cap_set,
.cap_enable_set = scmi_powercap_cap_enable_set,
.cap_enable_get = scmi_powercap_cap_enable_get,
- .pai_get = scmi_powercap_pai_get,
- .pai_set = scmi_powercap_pai_set,
+ .avg_interval_get = scmi_powercap_avg_interval_get,
+ .avg_interval_set = scmi_powercap_avg_interval_set,
.measurements_get = scmi_powercap_measurements_get,
.measurements_threshold_set = scmi_powercap_measurements_threshold_set,
.measurements_threshold_get = scmi_powercap_measurements_threshold_get,
@@ -991,18 +1035,18 @@ scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph)
* formed and correlated by sane parent-child relationship (if any).
*/
for (domain = 0; domain < pinfo->num_domains; domain++) {
- ret = scmi_powercap_domain_attributes_get(ph, pinfo, domain);
+ ret = scmi_powercap_domain_initialize(ph, pinfo, domain);
if (ret)
return ret;
if (pinfo->powercaps[domain].fastchannels)
scmi_powercap_domain_init_fc(ph, domain,
- &pinfo->powercaps[domain].fc_info);
+ &pinfo->powercaps[domain].cpli[CPL0].fc_info);
/* Grab initial state when disable is supported. */
if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2) {
ret = __scmi_powercap_cap_get(ph,
- &pinfo->powercaps[domain],
+ &pinfo->powercaps[domain], CPL0,
&pinfo->states[domain].last_pcap);
if (ret)
return ret;
diff --git a/drivers/powercap/arm_scmi_powercap.c b/drivers/powercap/arm_scmi_powercap.c
index ab66e9a3b1e2..be3007206a74 100644
--- a/drivers/powercap/arm_scmi_powercap.c
+++ b/drivers/powercap/arm_scmi_powercap.c
@@ -97,7 +97,7 @@ static const struct powercap_zone_ops zone_ops = {
};
static void scmi_powercap_normalize_cap(const struct scmi_powercap_zone *spz,
- u64 power_limit_uw, u32 *norm)
+ u64 power_limit_uw, int cid, u32 *norm)
{
bool scale_mw = spz->info->powercap_scale_mw;
u64 val;
@@ -108,9 +108,9 @@ static void scmi_powercap_normalize_cap(const struct scmi_powercap_zone *spz,
* the range [min_power_cap, max_power_cap] whose bounds are assured to
* be two unsigned 32bits quantities.
*/
- *norm = clamp_t(u32, val, spz->info->min_power_cap,
- spz->info->max_power_cap);
- *norm = rounddown(*norm, spz->info->power_cap_step);
+ *norm = clamp_t(u32, val, spz->info->cpli[cid].min_power_cap,
+ spz->info->cpli[cid].max_power_cap);
+ *norm = rounddown(*norm, spz->info->cpli[cid].power_cap_step);
val = (scale_mw) ? *norm * 1000 : *norm;
if (power_limit_uw != val)
@@ -125,12 +125,12 @@ static int scmi_powercap_set_power_limit_uw(struct powercap_zone *pz, int cid,
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
u32 norm_power;
- if (!spz->info->powercap_cap_config)
+ if (!spz->info->cpli[cid].cap_config)
return -EINVAL;
- scmi_powercap_normalize_cap(spz, power_uw, &norm_power);
+ scmi_powercap_normalize_cap(spz, power_uw, cid, &norm_power);
- return powercap_ops->cap_set(spz->ph, spz->info->id, norm_power, false);
+ return powercap_ops->cap_set(spz->ph, spz->info->id, cid, norm_power, false);
}
static int scmi_powercap_get_power_limit_uw(struct powercap_zone *pz, int cid,
@@ -140,7 +140,7 @@ static int scmi_powercap_get_power_limit_uw(struct powercap_zone *pz, int cid,
u32 power;
int ret;
- ret = powercap_ops->cap_get(spz->ph, spz->info->id, &power);
+ ret = powercap_ops->cap_get(spz->ph, spz->info->id, cid, &power);
if (ret)
return ret;
@@ -152,19 +152,20 @@ static int scmi_powercap_get_power_limit_uw(struct powercap_zone *pz, int cid,
}
static void scmi_powercap_normalize_time(const struct scmi_powercap_zone *spz,
- u64 time_us, u32 *norm)
+ u64 time_us, int cid, u32 *norm)
{
/*
* This cast is lossless since here @time_us is certain to be within the
- * range [min_pai, max_pai] whose bounds are assured to be two unsigned
- * 32bits quantities.
+ * range [min_avg_ivl, max_avg_ivl] whose bounds are assured to be two
+ * unsigned 32bits quantities.
*/
- *norm = clamp_t(u32, time_us, spz->info->min_pai, spz->info->max_pai);
- *norm = rounddown(*norm, spz->info->pai_step);
+ *norm = clamp_t(u32, time_us, spz->info->cpli[cid].min_avg_ivl,
+ spz->info->cpli[cid].max_avg_ivl);
+ *norm = rounddown(*norm, spz->info->cpli[cid].avg_ivl_step);
if (time_us != *norm)
dev_dbg(spz->dev,
- "Normalized %s:PAI - requested:%llu - normalized:%u\n",
+ "Normalized %s:AVG_IVL - requested:%llu - normalized:%u\n",
spz->info->name, time_us, *norm);
}
@@ -174,12 +175,13 @@ static int scmi_powercap_set_time_window_us(struct powercap_zone *pz, int cid,
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
u32 norm_pai;
- if (!spz->info->powercap_pai_config)
+ if (!spz->info->cpli[cid].avg_ivl_config)
return -EINVAL;
- scmi_powercap_normalize_time(spz, time_window_us, &norm_pai);
+ scmi_powercap_normalize_time(spz, time_window_us, cid, &norm_pai);
- return powercap_ops->pai_set(spz->ph, spz->info->id, norm_pai);
+ return powercap_ops->avg_interval_set(spz->ph, spz->info->id,
+ cid, norm_pai);
}
static int scmi_powercap_get_time_window_us(struct powercap_zone *pz, int cid,
@@ -189,7 +191,7 @@ static int scmi_powercap_get_time_window_us(struct powercap_zone *pz, int cid,
int ret;
u32 pai;
- ret = powercap_ops->pai_get(spz->ph, spz->info->id, &pai);
+ ret = powercap_ops->avg_interval_get(spz->ph, spz->info->id, cid, &pai);
if (ret)
return ret;
@@ -203,7 +205,7 @@ static int scmi_powercap_get_max_power_uw(struct powercap_zone *pz, int cid,
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
- *max_power_uw = spz->info->max_power_cap;
+ *max_power_uw = spz->info->cpli[cid].max_power_cap;
if (spz->info->powercap_scale_mw)
*max_power_uw *= 1000;
@@ -215,7 +217,7 @@ static int scmi_powercap_get_min_power_uw(struct powercap_zone *pz, int cid,
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
- *min_power_uw = spz->info->min_power_cap;
+ *min_power_uw = spz->info->cpli[cid].min_power_cap;
if (spz->info->powercap_scale_mw)
*min_power_uw *= 1000;
@@ -227,7 +229,7 @@ static int scmi_powercap_get_max_time_window_us(struct powercap_zone *pz,
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
- *time_window_us = spz->info->max_pai;
+ *time_window_us = spz->info->cpli[cid].max_avg_ivl;
return 0;
}
@@ -237,14 +239,16 @@ static int scmi_powercap_get_min_time_window_us(struct powercap_zone *pz,
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
- *time_window_us = (u64)spz->info->min_pai;
+ *time_window_us = (u64)spz->info->cpli[cid].min_avg_ivl;
return 0;
}
static const char *scmi_powercap_get_name(struct powercap_zone *pz, int cid)
{
- return "SCMI power-cap";
+ struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
+
+ return spz->info->cpli[cid].name;
}
static const struct powercap_zone_constraint_ops constraint_ops = {
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index aafaac1496b0..9918fb30100c 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -2,7 +2,7 @@
/*
* SCMI Message Protocol driver header
*
- * Copyright (C) 2018-2021 ARM Ltd.
+ * Copyright (C) 2018-2026 ARM Ltd.
*/
#ifndef _LINUX_SCMI_PROTOCOL_H
@@ -609,6 +609,35 @@ struct scmi_voltage_proto_ops {
s32 *volt_uV);
};
+/**
+ * struct scmi_powercap_cpl_info - Describe one CPL - Concurrent Powercap Limit
+ *
+ * @id: CPL ID as advertised by the platform.
+ * @cap_config: CAP configuration support for this CPL.
+ * @min_power_cap: Minimum configurable CAP.
+ * @max_power_cap: Maximum configurable CAP.
+ * @power_cap_step: Step size between two consecutive CAP values.
+ * @avg_ivl_config: Powercap averaging interval configuration support.
+ * @min_avg_ivl: Minimum configurable powercap averaging interval.
+ * @max_avg_ivl: Maximum configurable powercap averaging interval.
+ * @avg_ivl_step: Step size between two consecutive averaging intervals.
+ * @name: name assigned to the Powercap Domain by platform.
+ * @fc_info: Reference to the FastChannels descriptors supported by this CPL
+ */
+struct scmi_powercap_cpl_info {
+ unsigned int id;
+ bool cap_config;
+ unsigned int min_power_cap;
+ unsigned int max_power_cap;
+ unsigned int power_cap_step;
+ bool avg_ivl_config;
+ unsigned int min_avg_ivl;
+ unsigned int max_avg_ivl;
+ unsigned int avg_ivl_step;
+ char name[SCMI_SHORT_NAME_MAX_SIZE];
+ struct scmi_fc_info *fc_info;
+};
+
/**
* struct scmi_powercap_info - Describe one available Powercap domain
*
@@ -617,21 +646,15 @@ struct scmi_voltage_proto_ops {
* @notify_powercap_measurement_change: MEASUREMENTS change notifications
* support.
* @async_powercap_cap_set: Asynchronous CAP set support.
- * @powercap_cap_config: CAP configuration support.
* @powercap_monitoring: Monitoring (measurements) support.
- * @powercap_pai_config: PAI configuration support.
* @powercap_scale_mw: Domain reports power data in milliwatt units.
* @powercap_scale_uw: Domain reports power data in microwatt units.
* Note that, when both @powercap_scale_mw and
* @powercap_scale_uw are set to false, the domain
* reports power data on an abstract linear scale.
+ * @extended_names: Support for long names.
+ * @fastchannels: Support for at least one fastchannel,
* @name: name assigned to the Powercap Domain by platform.
- * @min_pai: Minimum configurable PAI.
- * @max_pai: Maximum configurable PAI.
- * @pai_step: Step size between two consecutive PAI values.
- * @min_power_cap: Minimum configurable CAP.
- * @max_power_cap: Maximum configurable CAP.
- * @power_cap_step: Step size between two consecutive CAP values.
* @sustainable_power: Maximum sustainable power consumption for this domain
* under normal conditions.
* @accuracy: The accuracy with which the power is measured and reported in
@@ -639,30 +662,25 @@ struct scmi_voltage_proto_ops {
* @parent_id: Identifier of the containing parent power capping domain, or the
* value 0xFFFFFFFF if this powercap domain is a root domain not
* contained in any other domain.
+ * @num_cpli: Number of discovered CPLs.
+ * @cpli: Reference to an array holding descriptors to all the discovered CPLs.
*/
struct scmi_powercap_info {
unsigned int id;
bool notify_powercap_cap_change;
bool notify_powercap_measurement_change;
bool async_powercap_cap_set;
- bool powercap_cap_config;
bool powercap_monitoring;
- bool powercap_pai_config;
bool powercap_scale_mw;
bool powercap_scale_uw;
bool fastchannels;
char name[SCMI_MAX_STR_SIZE];
- unsigned int min_pai;
- unsigned int max_pai;
- unsigned int pai_step;
- unsigned int min_power_cap;
- unsigned int max_power_cap;
- unsigned int power_cap_step;
unsigned int sustainable_power;
unsigned int accuracy;
#define SCMI_POWERCAP_ROOT_ZONE_ID 0xFFFFFFFFUL
unsigned int parent_id;
- struct scmi_fc_info *fc_info;
+ unsigned int num_cpli;
+ struct scmi_powercap_cpl_info *cpli;
};
/**
@@ -691,8 +709,12 @@ struct scmi_powercap_info {
* on the system: for this reason @cap_get and @cap_enable_get
* will always report the final platform view of the powercaps.
* @cap_enable_get: get the current CAP enable status for the specified domain.
- * @pai_get: get the current PAI value for the specified domain.
- * @pai_set: set the PAI value for the specified domain to the provided value.
+ * @avg_interval_get: get the current averaging interval value for the specified
+ * domain. This will get the PAI or CAI depending on the used
+ * protocol version.
+ * @avg_interval_set: set the current averaging interval value for the specified
+ * domain. This will set the PAI or CAI depending on the used
+ * protocol version.
* @measurements_get: retrieve the current average power measurements for the
* specified domain and the related PAI upon which is
* calculated.
@@ -716,17 +738,17 @@ struct scmi_powercap_proto_ops {
const struct scmi_powercap_info __must_check *(*info_get)
(const struct scmi_protocol_handle *ph, u32 domain_id);
int (*cap_get)(const struct scmi_protocol_handle *ph, u32 domain_id,
- u32 *power_cap);
+ u32 cpl_id, u32 *power_cap);
int (*cap_set)(const struct scmi_protocol_handle *ph, u32 domain_id,
- u32 power_cap, bool ignore_dresp);
+ u32 cpl_id, u32 power_cap, bool ignore_dresp);
int (*cap_enable_set)(const struct scmi_protocol_handle *ph,
u32 domain_id, bool enable);
int (*cap_enable_get)(const struct scmi_protocol_handle *ph,
u32 domain_id, bool *enable);
- int (*pai_get)(const struct scmi_protocol_handle *ph, u32 domain_id,
- u32 *pai);
- int (*pai_set)(const struct scmi_protocol_handle *ph, u32 domain_id,
- u32 pai);
+ int (*avg_interval_get)(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *val);
+ int (*avg_interval_set)(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 val);
int (*measurements_get)(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 *average_power, u32 *pai);
int (*measurements_threshold_set)(const struct scmi_protocol_handle *ph,
--
2.47.3
^ permalink raw reply related
* [PATCH v5 04/12] firmware: arm_scmi: Add SCMIv4.0 Powercap FCs support
From: Philip Radford @ 2026-04-28 9:09 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi,
Philip Radford
In-Reply-To: <20260428090922.346069-1-philip.radford@arm.com>
From: Cristian Marussi <cristian.marussi@arm.com>
Add support for new SCMIv4.0 Powercap Fastchannels.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
[Philip: removed reference to old versioning logic]
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
V2->V4
- moved fastchannel inits outside of loop
- renamed arguments for consistency
---
drivers/firmware/arm_scmi/powercap.c | 331 ++++++++++++++++++---------
1 file changed, 229 insertions(+), 102 deletions(-)
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index 3291bde78367..db5bc4f38ea4 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -27,19 +27,25 @@ enum scmi_powercap_protocol_cmd {
POWERCAP_CAP_GET = 0x4,
POWERCAP_CAP_SET = 0x5,
POWERCAP_PAI_GET = 0x6,
+ POWERCAP_MAI_GET = POWERCAP_PAI_GET,
POWERCAP_PAI_SET = 0x7,
+ POWERCAP_MAI_SET = POWERCAP_PAI_SET,
POWERCAP_DOMAIN_NAME_GET = 0x8,
POWERCAP_MEASUREMENTS_GET = 0x9,
POWERCAP_CAP_NOTIFY = 0xa,
POWERCAP_MEASUREMENTS_NOTIFY = 0xb,
POWERCAP_DESCRIBE_FASTCHANNEL = 0xc,
POWERCAP_CPC_ATTRIBUTES = 0xd,
+ POWERCAP_CAI_GET = 0xe,
+ POWERCAP_CAI_SET = 0xf,
};
enum {
POWERCAP_FC_CAP,
- POWERCAP_FC_PAI,
- POWERCAP_FC_MAX,
+ POWERCAP_FC_XAI,
+ POWERCAP_FC_MAI,
+ POWERCAP_FC_MEASUREMENT,
+ POWERCAP_FC_MAX
};
struct scmi_msg_resp_powercap_domain_attributes {
@@ -91,12 +97,12 @@ struct scmi_msg_resp_powercap_domain_attributes_v3 {
__le32 cai_step;
};
-struct scmi_msg_powercap_get_v3 {
+struct scmi_msg_powercap_cap_or_cai_get_v3 {
__le32 domain_id;
__le32 cpli;
};
-struct scmi_msg_powercap_set_cap_or_pai {
+struct scmi_msg_powercap_cap_or_pai_set {
__le32 domain_id;
__le32 flags;
#define CAP_SET_ASYNC BIT(1)
@@ -104,13 +110,20 @@ struct scmi_msg_powercap_set_cap_or_pai {
__le32 value;
};
-struct scmi_msg_powercap_set_cap_v3 {
+struct scmi_msg_powercap_cap_set_v3 {
__le32 domain_id;
__le32 cpli;
__le32 flags;
__le32 power_cap;
};
+struct scmi_msg_powercap_cai_set {
+ __le32 domain_id;
+ __le32 flags;
+ __le32 cai;
+ __le32 cpli;
+};
+
struct scmi_msg_resp_powercap_cap_set_complete {
__le32 domain_id;
__le32 power_cap;
@@ -201,6 +214,10 @@ struct powercap_info {
int (*xfer_cap_set)(const struct scmi_protocol_handle *ph,
const struct scmi_powercap_info *pc,
u32 cpl_id, u32 power_cap, bool ignore_dresp);
+ int (*xfer_avg_ivl_get)(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *ivl);
+ int (*xfer_avg_ivl_set)(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 ivl);
};
static enum scmi_powercap_protocol_cmd evt_2_cmd[] = {
@@ -501,17 +518,6 @@ scmi_powercap_domain_attributes_get(const struct scmi_protocol_handle *ph,
return ret;
}
-static int
-scmi_powercap_domain_initialize(const struct scmi_protocol_handle *ph,
- struct powercap_info *pinfo, u32 domain)
-{
- struct scmi_powercap_info *dom_info = pinfo->powercaps + domain;
-
- dom_info->id = domain;
-
- return scmi_powercap_domain_attributes_get(ph, pinfo, dom_info);
-}
-
static int scmi_powercap_num_domains_get(const struct scmi_protocol_handle *ph)
{
struct powercap_info *pi = ph->get_priv(ph);
@@ -558,7 +564,7 @@ static int scmi_powercap_xfer_cap_get_v3(const struct scmi_protocol_handle *ph,
{
int ret;
struct scmi_xfer *t;
- struct scmi_msg_powercap_get_v3 *msg;
+ struct scmi_msg_powercap_cap_or_cai_get_v3 *msg;
ret = ph->xops->xfer_get_init(ph, POWERCAP_CAP_GET, sizeof(*msg),
sizeof(u32), &t);
@@ -617,7 +623,7 @@ static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
{
int ret;
struct scmi_xfer *t;
- struct scmi_msg_powercap_set_cap_or_pai *msg;
+ struct scmi_msg_powercap_cap_or_pai_set *msg;
ret = ph->xops->xfer_get_init(ph, POWERCAP_CAP_SET,
sizeof(*msg), 0, &t);
@@ -660,7 +666,7 @@ static int scmi_powercap_xfer_cap_set_v3(const struct scmi_protocol_handle *ph,
{
int ret;
struct scmi_xfer *t;
- struct scmi_msg_powercap_set_cap_v3 *msg;
+ struct scmi_msg_powercap_cap_set_v3 *msg;
ret = ph->xops->xfer_get_init(ph, POWERCAP_CAP_SET,
sizeof(*msg), 0, &t);
@@ -759,8 +765,9 @@ static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
power_cap, ignore_dresp);
}
-static int scmi_powercap_xfer_pai_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 *pai)
+static int
+scmi_powercap_xfer_avg_interval_get(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *ivl)
{
int ret;
struct scmi_xfer *t;
@@ -773,58 +780,105 @@ static int scmi_powercap_xfer_pai_get(const struct scmi_protocol_handle *ph,
put_unaligned_le32(domain_id, t->tx.buf);
ret = ph->xops->do_xfer(ph, t);
if (!ret)
- *pai = get_unaligned_le32(t->rx.buf);
+ *ivl = get_unaligned_le32(t->rx.buf);
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static int
+scmi_powercap_xfer_avg_interval_get_v3(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *ivl)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_powercap_cap_or_cai_get_v3 *msg;
+
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_CAI_GET, sizeof(*msg),
+ sizeof(u32), &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->domain_id = cpu_to_le32(domain_id);
+ msg->cpli = cpu_to_le32(cpl_id);
+
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret)
+ *ivl = get_unaligned_le32(t->rx.buf);
ph->xops->xfer_put(ph, t);
return ret;
}
-static int scmi_powercap_pai_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 cpl_id, u32 *pai)
+static int scmi_powercap_avg_interval_get(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 *val)
{
struct scmi_powercap_info *dom;
struct powercap_info *pi = ph->get_priv(ph);
- if (!pai || domain_id >= pi->num_domains)
+ if (!val || domain_id >= pi->num_domains)
return -EINVAL;
dom = pi->powercaps + domain_id;
if (cpl_id >= dom->num_cpli)
return -EINVAL;
- if (dom->cpli[cpl_id].fc_info && dom->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].get_addr) {
- *pai = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].get_addr);
- trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_PAI_GET,
- domain_id, *pai, 0);
+ if (dom->cpli[cpl_id].fc_info &&
+ dom->cpli[cpl_id].fc_info[POWERCAP_FC_XAI].get_addr) {
+ int trace_cmd = (PROTOCOL_REV_MAJOR(ph->version) < 0x3) ?
+ POWERCAP_PAI_GET : POWERCAP_CAI_GET;
+
+ *val = ioread32(dom->cpli[cpl_id].fc_info[POWERCAP_FC_XAI].get_addr);
+ trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, trace_cmd, domain_id, *val, 0);
return 0;
}
- return scmi_powercap_xfer_pai_get(ph, domain_id, pai);
+ return pi->xfer_avg_ivl_get(ph, domain_id, cpl_id, val);
}
-static int scmi_powercap_avg_interval_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 cpl_id, u32 *val)
+static int
+scmi_powercap_xfer_avg_interval_set(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 ivl)
{
- return scmi_powercap_pai_get(ph, domain_id, cpl_id, val);
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_powercap_cap_or_pai_set *msg;
+
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_PAI_SET, sizeof(*msg), 0, &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->domain_id = cpu_to_le32(domain_id);
+ msg->flags = cpu_to_le32(0);
+ msg->value = cpu_to_le32(ivl);
+
+ ret = ph->xops->do_xfer(ph, t);
+
+ ph->xops->xfer_put(ph, t);
+ return ret;
}
-static int scmi_powercap_xfer_pai_set(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 pai)
+static int
+scmi_powercap_xfer_avg_interval_set_v3(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 ivl)
{
int ret;
struct scmi_xfer *t;
- struct scmi_msg_powercap_set_cap_or_pai *msg;
+ struct scmi_msg_powercap_cai_set *msg;
- ret = ph->xops->xfer_get_init(ph, POWERCAP_PAI_SET,
- sizeof(*msg), 0, &t);
+ ret = ph->xops->xfer_get_init(ph, POWERCAP_CAI_SET, sizeof(*msg), 0, &t);
if (ret)
return ret;
msg = t->tx.buf;
msg->domain_id = cpu_to_le32(domain_id);
msg->flags = cpu_to_le32(0);
- msg->value = cpu_to_le32(pai);
+ msg->cai = cpu_to_le32(ivl);
+ msg->cpli = cpu_to_le32(cpl_id);
ret = ph->xops->do_xfer(ph, t);
@@ -832,48 +886,42 @@ static int scmi_powercap_xfer_pai_set(const struct scmi_protocol_handle *ph,
return ret;
}
-static int scmi_powercap_pai_set(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 cpl_id, u32 pai)
+static int scmi_powercap_avg_interval_set(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 cpl_id, u32 ivl)
{
const struct scmi_powercap_info *pc;
+ struct powercap_info *pi = ph->get_priv(ph);
pc = scmi_powercap_dom_info_get(ph, domain_id);
if (!pc || cpl_id >= pc->num_cpli || !pc->cpli[cpl_id].avg_ivl_config ||
- !pai || pai < pc->cpli[cpl_id].min_avg_ivl ||
- pai > pc->cpli[cpl_id].max_avg_ivl)
+ !ivl || ivl < pc->cpli[cpl_id].min_avg_ivl ||
+ ivl > pc->cpli[cpl_id].max_avg_ivl)
return -EINVAL;
- if (pc->cpli[cpl_id].fc_info && pc->cpli[cpl_id].fc_info[POWERCAP_FC_PAI].set_addr) {
- struct scmi_fc_info *fci = &pc->cpli[cpl_id].fc_info[POWERCAP_FC_PAI];
+ /* Note that fc_info descriptors for any unsupported FC will be NULL */
+ if (pc->cpli[cpl_id].fc_info &&
+ pc->cpli[cpl_id].fc_info[POWERCAP_FC_XAI].set_addr) {
+ int trace_cmd = (PROTOCOL_REV_MAJOR(ph->version) < 0x3) ?
+ POWERCAP_PAI_SET : POWERCAP_CAI_SET;
+ struct scmi_fc_info *fci = &pc->cpli[cpl_id].fc_info[POWERCAP_FC_XAI];
- trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_PAI_SET,
- domain_id, pai, 0);
- iowrite32(pai, fci->set_addr);
+ trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, trace_cmd, domain_id, ivl, 0);
+ iowrite32(ivl, fci->set_addr);
ph->hops->fastchannel_db_ring(fci->set_db);
return 0;
}
- return scmi_powercap_xfer_pai_set(ph, domain_id, pai);
-}
-
-static int scmi_powercap_avg_interval_set(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 cpl_id, u32 val)
-{
- return scmi_powercap_pai_set(ph, domain_id, cpl_id, val);
+ return pi->xfer_avg_ivl_set(ph, domain_id, cpl_id, ivl);
}
-static int scmi_powercap_measurements_get(const struct scmi_protocol_handle *ph,
- u32 domain_id, u32 *average_power,
- u32 *pai)
+static int
+scmi_powercap_xfer_measurements_get(const struct scmi_protocol_handle *ph,
+ const struct scmi_powercap_info *pc,
+ u32 *avg_power, u32 *avg_ivl)
{
int ret;
struct scmi_xfer *t;
struct scmi_msg_resp_powercap_meas_get *resp;
- const struct scmi_powercap_info *pc;
-
- pc = scmi_powercap_dom_info_get(ph, domain_id);
- if (!pc || !pc->powercap_monitoring || !pai || !average_power)
- return -EINVAL;
ret = ph->xops->xfer_get_init(ph, POWERCAP_MEASUREMENTS_GET,
sizeof(u32), sizeof(*resp), &t);
@@ -881,17 +929,42 @@ static int scmi_powercap_measurements_get(const struct scmi_protocol_handle *ph,
return ret;
resp = t->rx.buf;
- put_unaligned_le32(domain_id, t->tx.buf);
+ put_unaligned_le32(pc->id, t->tx.buf);
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
- *average_power = le32_to_cpu(resp->power);
- *pai = le32_to_cpu(resp->pai);
+ *avg_power = le32_to_cpu(resp->power);
+ *avg_ivl = le32_to_cpu(resp->pai);
}
ph->xops->xfer_put(ph, t);
return ret;
}
+static int scmi_powercap_measurements_get(const struct scmi_protocol_handle *ph,
+ u32 domain_id, u32 *avg_power,
+ u32 *avg_ivl)
+{
+ const struct scmi_powercap_info *pc;
+ struct scmi_fc_info *fci;
+
+ pc = scmi_powercap_dom_info_get(ph, domain_id);
+ if (!pc || !pc->powercap_monitoring || !avg_ivl || !avg_power)
+ return -EINVAL;
+
+ /* Note that fc_info descriptors for any unsupported FC will be NULL */
+ fci = pc->cpli[CPL0].fc_info;
+ if (fci && fci[POWERCAP_FC_MEASUREMENT].get_addr) {
+ *avg_power = ioread32(fci[POWERCAP_FC_MEASUREMENT].get_addr);
+ /* See SCMIv4.0 3.10.2 - Payload is 32bit ONLY avg_power */
+ *avg_ivl = 0;
+ trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_MEASUREMENTS_GET,
+ pc->id, *avg_power, *avg_ivl);
+ return 0;
+ }
+
+ return scmi_powercap_xfer_measurements_get(ph, pc, avg_power, avg_ivl);
+}
+
static int
scmi_powercap_measurements_threshold_get(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 *power_thresh_low,
@@ -1019,37 +1092,85 @@ static const struct scmi_powercap_proto_ops powercap_proto_ops = {
};
static void scmi_powercap_domain_init_fc(const struct scmi_protocol_handle *ph,
- u32 domain, struct scmi_fc_info **p_fc)
+ struct scmi_powercap_info *dom_info)
{
- struct scmi_fc_info *fc;
-
- fc = devm_kcalloc(ph->dev, POWERCAP_FC_MAX, sizeof(*fc), GFP_KERNEL);
- if (!fc)
- return;
-
- ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_CAP_SET, 4, domain, NULL,
- &fc[POWERCAP_FC_CAP].set_addr,
- &fc[POWERCAP_FC_CAP].set_db,
- &fc[POWERCAP_FC_CAP].rate_limit);
-
- ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_CAP_GET, 4, domain, NULL,
- &fc[POWERCAP_FC_CAP].get_addr, NULL,
- &fc[POWERCAP_FC_CAP].rate_limit);
-
- ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_PAI_SET, 4, domain, NULL,
- &fc[POWERCAP_FC_PAI].set_addr,
- &fc[POWERCAP_FC_PAI].set_db,
- &fc[POWERCAP_FC_PAI].rate_limit);
-
- ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_PAI_GET, 4, domain, NULL,
- &fc[POWERCAP_FC_PAI].get_addr, NULL,
- &fc[POWERCAP_FC_PAI].rate_limit);
-
- *p_fc = fc;
+ struct scmi_fc_info *fc_cpl0;
+
+ for (int id = 0; id < dom_info->num_cpli; id++) {
+ struct scmi_fc_info *fc;
+ u32 *cpl_id, zero_cpl_id = 0;
+
+ fc = devm_kcalloc(ph->dev, POWERCAP_FC_MAX, sizeof(*fc), GFP_KERNEL);
+ if (!fc)
+ return;
+
+ /* NOTE THAT when num_cpli == 1 the arg *cpl_id is 0 */
+ cpl_id = (PROTOCOL_REV_MAJOR(ph->version) >= 0x3) ? &id : NULL;
+
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_CAP_SET, 4, dom_info->id,
+ cpl_id,
+ &fc[POWERCAP_FC_CAP].set_addr,
+ &fc[POWERCAP_FC_CAP].set_db,
+ &fc[POWERCAP_FC_CAP].rate_limit);
+
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_CAP_GET, 4, dom_info->id,
+ cpl_id,
+ &fc[POWERCAP_FC_CAP].get_addr, NULL,
+ &fc[POWERCAP_FC_CAP].rate_limit);
+
+ if (PROTOCOL_REV_MAJOR(ph->version) >= 0x3) {
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_CAI_SET, 4,
+ dom_info->id, cpl_id,
+ &fc[POWERCAP_FC_XAI].set_addr,
+ &fc[POWERCAP_FC_XAI].set_db,
+ &fc[POWERCAP_FC_XAI].rate_limit);
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_CAI_GET, 4,
+ dom_info->id, cpl_id,
+ &fc[POWERCAP_FC_XAI].get_addr, NULL,
+ &fc[POWERCAP_FC_XAI].rate_limit);
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_MAI_SET, 4,
+ dom_info->id, &zero_cpl_id,
+ &fc[POWERCAP_FC_MAI].set_addr,
+ &fc[POWERCAP_FC_MAI].set_db,
+ &fc[POWERCAP_FC_MAI].rate_limit);
+
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_MAI_GET, 4,
+ dom_info->id, &zero_cpl_id,
+ &fc[POWERCAP_FC_MAI].get_addr, NULL,
+ &fc[POWERCAP_FC_MAI].rate_limit);
+
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_MEASUREMENTS_GET, 4,
+ dom_info->id, &zero_cpl_id,
+ &fc[POWERCAP_FC_MEASUREMENT].get_addr, NULL,
+ &fc[POWERCAP_FC_MEASUREMENT].rate_limit);
+ }
+
+ dom_info->cpli[id].fc_info = fc;
+ }
+
+ if (PROTOCOL_REV_MAJOR(ph->version) < 0x3) {
+ fc_cpl0 = dom_info->cpli[CPL0].fc_info;
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_PAI_SET, 4,
+ dom_info->id, NULL,
+ &fc_cpl0[POWERCAP_FC_XAI].set_addr,
+ &fc_cpl0[POWERCAP_FC_XAI].set_db,
+ &fc_cpl0[POWERCAP_FC_XAI].rate_limit);
+
+ ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
+ POWERCAP_PAI_GET, 4,
+ dom_info->id, NULL,
+ &fc_cpl0[POWERCAP_FC_XAI].get_addr, NULL,
+ &fc_cpl0[POWERCAP_FC_XAI].rate_limit);
+ }
+
}
static int scmi_powercap_notify(const struct scmi_protocol_handle *ph,
@@ -1286,9 +1407,14 @@ scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph)
if (PROTOCOL_REV_MAJOR(ph->version) < 0x3) {
pinfo->xfer_cap_get = scmi_powercap_xfer_cap_get;
pinfo->xfer_cap_set = scmi_powercap_xfer_cap_set;
+ pinfo->xfer_avg_ivl_get = scmi_powercap_xfer_avg_interval_get;
+ pinfo->xfer_avg_ivl_set = scmi_powercap_xfer_avg_interval_set;
+
} else {
pinfo->xfer_cap_get = scmi_powercap_xfer_cap_get_v3;
pinfo->xfer_cap_set = scmi_powercap_xfer_cap_set_v3;
+ pinfo->xfer_avg_ivl_get = scmi_powercap_xfer_avg_interval_get_v3;
+ pinfo->xfer_avg_ivl_set = scmi_powercap_xfer_avg_interval_set_v3;
}
ret = scmi_powercap_attributes_get(ph, pinfo);
@@ -1313,18 +1439,19 @@ scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph)
* formed and correlated by sane parent-child relationship (if any).
*/
for (domain = 0; domain < pinfo->num_domains; domain++) {
- ret = scmi_powercap_domain_initialize(ph, pinfo, domain);
+ struct scmi_powercap_info *dom_info = pinfo->powercaps + domain;
+
+ dom_info->id = domain;
+ ret = scmi_powercap_domain_attributes_get(ph, pinfo, dom_info);
if (ret)
return ret;
- if (pinfo->powercaps[domain].fastchannels)
- scmi_powercap_domain_init_fc(ph, domain,
- &pinfo->powercaps[domain].cpli[CPL0].fc_info);
+ if (dom_info->fastchannels)
+ scmi_powercap_domain_init_fc(ph, dom_info);
/* Grab initial state when disable is supported. */
if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2) {
- ret = __scmi_powercap_cap_get(ph,
- &pinfo->powercaps[domain], CPL0,
+ ret = __scmi_powercap_cap_get(ph, dom_info, CPL0,
&pinfo->states[domain].last_pcap);
if (ret)
return ret;
--
2.47.3
^ permalink raw reply related
* [PATCH v5 01/12] firmware: arm_scmi: Add an optional custom parameter to fastchannel helpers
From: Philip Radford @ 2026-04-28 9:09 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi,
Philip Radford
In-Reply-To: <20260428090922.346069-1-philip.radford@arm.com>
From: Cristian Marussi <cristian.marussi@arm.com>
Starting from SCMIv4.0 the protocols DESCRIBE_FASTCHANNEL commands allow
to specify one additional per-protocol custom field in the outgoing message
request in order to, optionally, further narrow down the scope of the
fastchannel discovery request; the related message-reply format is instead
unchanged.
Add an optional custom protocol parameter to the common fastchannel helper
so as to enable the caller to choose the kind of message to send based on
the detected protocol version.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
Signed-off-by: Philip Radford <philip.radford@arm.com>
---
drivers/firmware/arm_scmi/driver.c | 12 ++++++++++--
drivers/firmware/arm_scmi/perf.c | 8 ++++----
drivers/firmware/arm_scmi/powercap.c | 8 ++++----
drivers/firmware/arm_scmi/protocols.h | 2 +-
4 files changed, 19 insertions(+), 11 deletions(-)
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index f167194f7cf6..adff81a3d233 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -1872,6 +1872,11 @@ static int scmi_iterator_run(void *iter)
struct scmi_msg_get_fc_info {
__le32 domain;
__le32 message_id;
+ __le32 custom;
+#define MSG_FC_INFO_SZ_EXTENDED \
+ (sizeof(struct scmi_msg_get_fc_info))
+#define MSG_FC_INFO_SZ \
+ (sizeof(struct scmi_msg_get_fc_info) - sizeof(__le32))
};
struct scmi_msg_resp_desc_fc {
@@ -1900,7 +1905,7 @@ struct scmi_msg_resp_desc_fc {
static void
scmi_common_fastchannel_init(const struct scmi_protocol_handle *ph,
u8 describe_id, u32 message_id, u32 valid_size,
- u32 domain, void __iomem **p_addr,
+ u32 domain, u32 *custom, void __iomem **p_addr,
struct scmi_fc_db_info **p_db, u32 *rate_limit)
{
int ret;
@@ -1931,13 +1936,16 @@ scmi_common_fastchannel_init(const struct scmi_protocol_handle *ph,
}
ret = ph->xops->xfer_get_init(ph, describe_id,
- sizeof(*info), sizeof(*resp), &t);
+ custom ? MSG_FC_INFO_SZ_EXTENDED :
+ MSG_FC_INFO_SZ, sizeof(*resp), &t);
if (ret)
goto err_out;
info = t->tx.buf;
info->domain = cpu_to_le32(domain);
info->message_id = cpu_to_le32(message_id);
+ if (custom)
+ info->custom = cpu_to_le32(*custom);
/*
* Bail out on error leaving fc_info addresses zeroed; this includes
diff --git a/drivers/firmware/arm_scmi/perf.c b/drivers/firmware/arm_scmi/perf.c
index 4583d02bee1c..7f283f457e02 100644
--- a/drivers/firmware/arm_scmi/perf.c
+++ b/drivers/firmware/arm_scmi/perf.c
@@ -835,25 +835,25 @@ static void scmi_perf_domain_init_fc(const struct scmi_protocol_handle *ph,
return;
ph->hops->fastchannel_init(ph, PERF_DESCRIBE_FASTCHANNEL,
- PERF_LEVEL_GET, 4, dom->id,
+ PERF_LEVEL_GET, 4, dom->id, NULL,
&fc[PERF_FC_LEVEL].get_addr, NULL,
&fc[PERF_FC_LEVEL].rate_limit);
ph->hops->fastchannel_init(ph, PERF_DESCRIBE_FASTCHANNEL,
- PERF_LIMITS_GET, 8, dom->id,
+ PERF_LIMITS_GET, 8, dom->id, NULL,
&fc[PERF_FC_LIMIT].get_addr, NULL,
&fc[PERF_FC_LIMIT].rate_limit);
if (dom->info.set_perf)
ph->hops->fastchannel_init(ph, PERF_DESCRIBE_FASTCHANNEL,
- PERF_LEVEL_SET, 4, dom->id,
+ PERF_LEVEL_SET, 4, dom->id, NULL,
&fc[PERF_FC_LEVEL].set_addr,
&fc[PERF_FC_LEVEL].set_db,
&fc[PERF_FC_LEVEL].rate_limit);
if (dom->set_limits)
ph->hops->fastchannel_init(ph, PERF_DESCRIBE_FASTCHANNEL,
- PERF_LIMITS_SET, 8, dom->id,
+ PERF_LIMITS_SET, 8, dom->id, NULL,
&fc[PERF_FC_LIMIT].set_addr,
&fc[PERF_FC_LIMIT].set_db,
&fc[PERF_FC_LIMIT].rate_limit);
diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c
index ab9733f4458b..22aff71c75e9 100644
--- a/drivers/firmware/arm_scmi/powercap.c
+++ b/drivers/firmware/arm_scmi/powercap.c
@@ -716,24 +716,24 @@ static void scmi_powercap_domain_init_fc(const struct scmi_protocol_handle *ph,
return;
ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_CAP_SET, 4, domain,
+ POWERCAP_CAP_SET, 4, domain, NULL,
&fc[POWERCAP_FC_CAP].set_addr,
&fc[POWERCAP_FC_CAP].set_db,
&fc[POWERCAP_FC_CAP].rate_limit);
ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_CAP_GET, 4, domain,
+ POWERCAP_CAP_GET, 4, domain, NULL,
&fc[POWERCAP_FC_CAP].get_addr, NULL,
&fc[POWERCAP_FC_CAP].rate_limit);
ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_PAI_SET, 4, domain,
+ POWERCAP_PAI_SET, 4, domain, NULL,
&fc[POWERCAP_FC_PAI].set_addr,
&fc[POWERCAP_FC_PAI].set_db,
&fc[POWERCAP_FC_PAI].rate_limit);
ph->hops->fastchannel_init(ph, POWERCAP_DESCRIBE_FASTCHANNEL,
- POWERCAP_PAI_GET, 4, domain,
+ POWERCAP_PAI_GET, 4, domain, NULL,
&fc[POWERCAP_FC_PAI].get_addr, NULL,
&fc[POWERCAP_FC_PAI].rate_limit);
diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h
index f51245aca259..e618d3141c95 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -280,7 +280,7 @@ struct scmi_proto_helpers_ops {
u32 message_id, u32 *attributes);
void (*fastchannel_init)(const struct scmi_protocol_handle *ph,
u8 describe_id, u32 message_id,
- u32 valid_size, u32 domain,
+ u32 valid_size, u32 domain, u32 *custom,
void __iomem **p_addr,
struct scmi_fc_db_info **p_db,
u32 *rate_limit);
--
2.47.3
^ permalink raw reply related
* [PATCH v5 00/12] Add support for SCMIv4.0 Powercap Extensions
From: Philip Radford @ 2026-04-28 9:09 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-pm
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Philip Radford
Hi all,
I will be taking over this series from Cristian and in doing so I have
addressed a couple of issues raised by the first version and added five
additional patches since Cristian's original series:
[6/12] exposes the measurement averaging interval (MAI) value when the
agent has registered to receive power measurement change notifications
for the power capping domain.
[9/12] adds MAI get and set support for the powercap protocol.
[10/12] introduces a synthetic root zone to act as a common parent for all
top-level domains.
[11/12] adds get_power_uw to synthetic root zone, summing the per-zone
power of immediate child zones.
[12/12] adds enable/disable functionality to synthetic root zone to
enable/disable immediate children.
The original series was based on v6.17-rc1 whereas this version has been
based on v7.1-rc1.
The rest of Cristian's series is explained below;
SCMIv4.0 [1] introduces some new features and commands into the Powercap
protocol. In a nutshell, such protocol changes add support for:
- setting multiple powercap limit/interval constraints for each SCMI
powercap domain which supports multiple Concurrent Power Limit
- enabling more Powercap commands to use Fastchannels mechanism
- adding multiple constraints support to the existing notifications
After a bit of needed updates in the SCMI core this series adds:
- support for the idea of optional multiple Concurrent Power Limit (CPLs)
- support for the new FCs
- support for extended notifications
- enable usage of such multiple constraint in the ARM SCMI Powercap driver
Note that the public SCMIv4.0 spec at [1] is currently still BETA0, so
this series could anyway need some minor rework along the way and
definitely will need to wait for a final public release before being
possibly merged.
Tested on a single and multi-instance scenario on an emulated setup
implementing the new protocol extensions.
Based on v7.1-rc1.
Thanks,
Phil
---
V4->V5
- Added enable/disable functionality to synthetic node
- Rebased on v7.1-rc1
V3->V4
- Rebased on v7.0-rc4
- Added sythentic parent node and functionality
- Moved fastchannel inits outside of loop
- Renamed arguments for consistency
V2->V3
- Added powercap MAI get/set support
V1->V2
- Rebased on sudeep/for-next/scmi/updates
- Amended Copyright to include 2026
- Added patch to extend powercap report to include MAI
- Removed creation of pi powercap_info struct due to legacy code change
- Amended references to pi->version and similar, which were based on
legacy code
- Wrapped two variables in le32_to_cpu() to appease Sparse warnings
- Amended comparing operator value in response to feedback
Cristian Marussi (7):
firmware: arm_scmi: Add an optional custom parameter to fastchannel
helpers
firmware: arm_scmi: Refactor powercap domain layout
firmware: arm_scmi: Add SCMIv4.0 Powercap basic support
firmware: arm_scmi: Add SCMIv4.0 Powercap FCs support
firmware: arm_scmi: Add SCMIV4.0 Powercap notifications support
include: trace: Add new parameter to trace_scmi_fc_call
powercap: arm_scmi: Enable multiple constraints support
Philip Radford (5):
firmware: arm_scmi: Extend powercap report to include MAI
firmware: arm_scmi: add Powercap MAI get/set support
powercap: arm_scmi: Create synthetic parent node for multi-instance
powercap: arm_scmi: Add get_power_uw to synthetic node
powercap: arm_scmi: Synthetic zone enable/disable
drivers/firmware/arm_scmi/driver.c | 12 +-
drivers/firmware/arm_scmi/perf.c | 16 +-
drivers/firmware/arm_scmi/powercap.c | 984 ++++++++++++++++++++------
drivers/firmware/arm_scmi/protocols.h | 2 +-
drivers/powercap/arm_scmi_powercap.c | 227 +++++-
include/linux/scmi_protocol.h | 87 ++-
include/trace/events/scmi.h | 12 +-
7 files changed, 1067 insertions(+), 273 deletions(-)
--
2.47.3
^ permalink raw reply
* [PATCH 5/5] remoteproc: imx: use device node name as processor name
From: Jiafei Pan @ 2026-04-28 9:08 UTC (permalink / raw)
To: andersson, mathieu.poirier, peng.fan, Frank.Li, s.hauer, kernel,
festevam, imx, linux-arm-kernel, linux-kernel
Cc: Zhiqiang.Hou, mingkai.hu, Jiafei Pan
In-Reply-To: <20260428090819.17761-1-Jiafei.Pan@nxp.com>
As currently there are maybe multiple remote processors, so change from
using fixed name to using device node name as remote processor name in
order to make them can be distinguished by through of name in sys
filesystem.
Signed-off-by: Jiafei Pan <Jiafei.Pan@nxp.com>
---
drivers/remoteproc/imx_rproc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/remoteproc/imx_rproc.c b/drivers/remoteproc/imx_rproc.c
index e8d239bef5c9..17026e390e88 100644
--- a/drivers/remoteproc/imx_rproc.c
+++ b/drivers/remoteproc/imx_rproc.c
@@ -1357,7 +1357,7 @@ static int imx_rproc_probe(struct platform_device *pdev)
int ret;
/* set some other name then imx */
- rproc = devm_rproc_alloc(dev, "imx-rproc", &imx_rproc_ops,
+ rproc = devm_rproc_alloc(dev, np->name, &imx_rproc_ops,
NULL, sizeof(*priv));
if (!rproc)
return -ENOMEM;
--
2.43.0
^ permalink raw reply related
* [PATCH 4/5] arm64: dts: imx93: Cortex-A Core remoteproc device node
From: Jiafei Pan @ 2026-04-28 9:08 UTC (permalink / raw)
To: andersson, mathieu.poirier, peng.fan, Frank.Li, s.hauer, kernel,
festevam, imx, linux-arm-kernel, linux-kernel
Cc: Zhiqiang.Hou, mingkai.hu, Jiafei Pan
In-Reply-To: <20260428090819.17761-1-Jiafei.Pan@nxp.com>
Create device tree for i.MX93 14x14 evk and 11x11 evk, add Cortex-A Core
remoteproc device nodes in these device tree.
Signed-off-by: Jiafei Pan <Jiafei.Pan@nxp.com>
Signed-off-by: Hou Zhiqiang <Zhiqiang.Hou@nxp.com>
---
.../imx93-11x11-evk-multicore-rtos.dts | 39 +++++++++++++++++++
.../imx93-14x14-evk-multicore-rtos.dts | 39 +++++++++++++++++++
.../boot/dts/freescale/imx93-rproc-ca55.dtsi | 14 +++++++
3 files changed, 92 insertions(+)
create mode 100644 arch/arm64/boot/dts/freescale/imx93-11x11-evk-multicore-rtos.dts
create mode 100644 arch/arm64/boot/dts/freescale/imx93-14x14-evk-multicore-rtos.dts
create mode 100644 arch/arm64/boot/dts/freescale/imx93-rproc-ca55.dtsi
diff --git a/arch/arm64/boot/dts/freescale/imx93-11x11-evk-multicore-rtos.dts b/arch/arm64/boot/dts/freescale/imx93-11x11-evk-multicore-rtos.dts
new file mode 100644
index 000000000000..9fb2b94b83b4
--- /dev/null
+++ b/arch/arm64/boot/dts/freescale/imx93-11x11-evk-multicore-rtos.dts
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Copyright 2023-2026 NXP
+ */
+
+/dts-v1/;
+#include "imx93-11x11-evk.dts"
+#include "imx93-rproc-ca55.dtsi"
+
+/ {
+ reserved-memory {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+
+ /*
+ * Reserve up to 16MB for one possible RTOS instances running on
+ * one Cortex-A Cores when booting Linux on at least one Cortex-A Core.
+ */
+ rtos_ca55_reserved: rtos-ca55@d0000000 {
+ no-map;
+ reg = <0 0xd0000000 0x0 0x1000000>;
+ };
+
+ /* Reserve 16MB for FreeRTOS on M33 */
+ m33_reserved: m33@a5000000 {
+ no-map;
+ reg = <0 0xa5000000 0 0x1000000>;
+ };
+ };
+};
+
+&lpuart2 {
+ status = "disabled";
+};
+
+&clk {
+ init-on-array = <IMX93_CLK_LPUART2_GATE>;
+};
diff --git a/arch/arm64/boot/dts/freescale/imx93-14x14-evk-multicore-rtos.dts b/arch/arm64/boot/dts/freescale/imx93-14x14-evk-multicore-rtos.dts
new file mode 100644
index 000000000000..b2481bf19b4a
--- /dev/null
+++ b/arch/arm64/boot/dts/freescale/imx93-14x14-evk-multicore-rtos.dts
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Copyright 2024-2026 NXP
+ */
+
+/dts-v1/;
+#include "imx93-14x14-evk.dts"
+#include "imx93-rproc-ca55.dtsi"
+
+/ {
+ reserved-memory {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+
+ /*
+ * Reserve up to 16MB for one possible RTOS instances running on
+ * one Cortex-A Cores when booting Linux on at least one Cortex-A Core.
+ */
+ rtos_ca55_reserved: rtos-ca55@d0000000 {
+ no-map;
+ reg = <0 0xd0000000 0x0 0x1000000>;
+ };
+
+ /* Reserve 16MB for FreeRTOS on M33 */
+ m33_reserved: m33@a5000000 {
+ no-map;
+ reg = <0 0xa5000000 0 0x1000000>;
+ };
+ };
+};
+
+&lpuart2 {
+ status = "disabled";
+};
+
+&clk {
+ init-on-array = <IMX93_CLK_LPUART2_GATE>;
+};
diff --git a/arch/arm64/boot/dts/freescale/imx93-rproc-ca55.dtsi b/arch/arm64/boot/dts/freescale/imx93-rproc-ca55.dtsi
new file mode 100644
index 000000000000..9d9a60404d2b
--- /dev/null
+++ b/arch/arm64/boot/dts/freescale/imx93-rproc-ca55.dtsi
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Copyright 2024-2026 NXP
+ */
+
+/ {
+ ca55_1: remoteproc-ca55-1 {
+ compatible = "fsl,imx-rproc-psci";
+ /* bitmask:0b10, assign A55 Core 1 */
+ fsl,cpus-bits = <0x2>;
+ memory-region = <&rtos_ca55_reserved>;
+ };
+};
+
--
2.43.0
^ permalink raw reply related
* [PATCH 2/5] drivers: remoteproc: imx: add support for Cortex-A Core
From: Jiafei Pan @ 2026-04-28 9:08 UTC (permalink / raw)
To: andersson, mathieu.poirier, peng.fan, Frank.Li, s.hauer, kernel,
festevam, imx, linux-arm-kernel, linux-kernel
Cc: Zhiqiang.Hou, mingkai.hu, Jiafei Pan
In-Reply-To: <20260428090819.17761-1-Jiafei.Pan@nxp.com>
Add Cortex-A Core remoteproc support, it use PSCI and SIP SMC call
to manage Cortex-A Core to be on or off.
Signed-off-by: Jiafei Pan <Jiafei.Pan@nxp.com>
---
drivers/remoteproc/imx_rproc.c | 134 +++++++++++++++++++++++++++++++++
drivers/remoteproc/imx_rproc.h | 2 +
2 files changed, 136 insertions(+)
diff --git a/drivers/remoteproc/imx_rproc.c b/drivers/remoteproc/imx_rproc.c
index 0dd80e688b0e..8a3de27c96b7 100644
--- a/drivers/remoteproc/imx_rproc.c
+++ b/drivers/remoteproc/imx_rproc.c
@@ -6,6 +6,7 @@
#include <dt-bindings/firmware/imx/rsrc.h>
#include <linux/arm-smccc.h>
#include <linux/clk.h>
+#include <linux/cpu.h>
#include <linux/err.h>
#include <linux/firmware/imx/sci.h>
#include <linux/firmware/imx/sm.h>
@@ -20,12 +21,17 @@
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
+#include <linux/psci.h>
#include <linux/reboot.h>
#include <linux/regmap.h>
#include <linux/remoteproc.h>
#include <linux/scmi_imx_protocol.h>
#include <linux/workqueue.h>
+#include <uapi/linux/psci.h>
+
+#include <asm/smp_plat.h>
+
#include "imx_rproc.h"
#include "remoteproc_internal.h"
@@ -72,6 +78,8 @@
#define IMX_SIP_RPROC_STARTED 0x01
#define IMX_SIP_RPROC_STOP 0x02
+#define IMX_SIP_CPU_OFF 0xC2000012
+
#define IMX_SC_IRQ_GROUP_REBOOTED 5
/**
@@ -131,6 +139,9 @@ struct imx_rproc {
* BIT 0: IMX_RPROC_FLAGS_SM_LMM_CTRL(RPROC LM is under Linux control )
*/
u32 flags;
+ /* used by Cortex-A Core remoteproc to manage all CPU Cores */
+ cpumask_t cpus;
+ cpumask_t offlined_cpus;
};
static const struct imx_rproc_att imx_rproc_att_imx95_m7[] = {
@@ -381,6 +392,45 @@ static int imx_rproc_sm_lmm_start(struct rproc *rproc)
return 0;
}
+static int imx_rproc_psci_start(struct rproc *rproc)
+{
+ struct imx_rproc *priv = rproc->priv;
+ struct device *dev = priv->dev;
+ unsigned int cpu;
+ int ret;
+
+ if (cpumask_empty(&priv->cpus)) {
+ dev_err(dev, "No CPU Core assigned!\n");
+ return -ENODEV;
+ }
+
+ for_each_cpu(cpu, &priv->cpus) {
+ if (cpu_online(cpu)) {
+ ret = remove_cpu(cpu);
+ if (ret)
+ goto err;
+ cpumask_set_cpu(cpu, &priv->offlined_cpus);
+ }
+ }
+
+ cpu = cpumask_first(&priv->cpus);
+ ret = psci_ops.cpu_on(cpu_logical_map(cpu), rproc->bootaddr);
+ if (ret) {
+ dev_err(dev, "Boot failed on CPU Core %d\n", cpu);
+ goto err;
+ }
+
+ return 0;
+
+err:
+ for_each_cpu(cpu, &priv->cpus) {
+ if (!cpu_online(cpu) && add_cpu(cpu) == 0)
+ cpumask_clear_cpu(cpu, &priv->offlined_cpus);
+ }
+
+ return ret;
+}
+
static int imx_rproc_start(struct rproc *rproc)
{
struct imx_rproc *priv = rproc->priv;
@@ -456,6 +506,50 @@ static int imx_rproc_sm_lmm_stop(struct rproc *rproc)
return scmi_imx_lmm_operation(dcfg->lmid, SCMI_IMX_LMM_SHUTDOWN, 0);
}
+static int imx_rproc_psci_stop(struct rproc *rproc)
+{
+ struct imx_rproc *priv = rproc->priv;
+ struct device *dev = priv->dev;
+ struct arm_smccc_res res;
+ unsigned int cpu;
+ unsigned long start, end;
+ int err;
+
+ for_each_cpu(cpu, &priv->cpus) {
+ /* Check CPU status */
+ err = psci_ops.affinity_info(cpu_logical_map(cpu), 0);
+ if (err == PSCI_0_2_AFFINITY_LEVEL_OFF)
+ continue;
+
+ /* Bring CPU to be off */
+ arm_smccc_smc(IMX_SIP_CPU_OFF, cpu, 0,
+ 0, 0, 0, 0, 0, &res);
+ start = jiffies;
+ end = start + msecs_to_jiffies(100);
+ do {
+ err = psci_ops.affinity_info(cpu_logical_map(cpu), 0);
+ if (err == PSCI_0_2_AFFINITY_LEVEL_OFF) {
+ pr_info("CPU%d is killed (polled %d ms)\n", cpu,
+ jiffies_to_msecs(jiffies - start));
+ break;
+ }
+
+ usleep_range(100, 1000);
+ } while (time_before(jiffies, end));
+ }
+
+ /* Return back freed CPU Core to Linux kernel */
+ for_each_cpu(cpu, &priv->cpus) {
+ if (cpumask_test_cpu(cpu, &priv->offlined_cpus)) {
+ if (add_cpu(cpu) != 0)
+ dev_err(dev, "Failed to bring CPU %d back to be online", cpu);
+ cpumask_clear_cpu(cpu, &priv->offlined_cpus);
+ }
+ }
+
+ return 0;
+}
+
static int imx_rproc_stop(struct rproc *rproc)
{
struct imx_rproc *priv = rproc->priv;
@@ -480,6 +574,12 @@ static int imx_rproc_da_to_sys(struct imx_rproc *priv, u64 da,
const struct imx_rproc_dcfg *dcfg = priv->dcfg;
int i;
+ /* No need to translate for Cortex-A Core */
+ if (dcfg->flags & IMX_RPROC_NO_ADDR_TRANS) {
+ *sys = da;
+ return 0;
+ }
+
/* parse address translation table */
for (i = 0; i < dcfg->att_size; i++) {
const struct imx_rproc_att *att = &dcfg->att[i];
@@ -1184,6 +1284,13 @@ static int imx_rproc_sm_detect_mode(struct rproc *rproc)
return imx_rproc_sm_lmm_check(rproc, started);
}
+static int imx_rproc_psci_detect_mode(struct rproc *rproc)
+{
+ rproc->state = RPROC_OFFLINE;
+
+ return 0;
+}
+
static int imx_rproc_detect_mode(struct imx_rproc *priv)
{
/*
@@ -1228,6 +1335,8 @@ static int imx_rproc_probe(struct platform_device *pdev)
struct imx_rproc *priv;
struct rproc *rproc;
const struct imx_rproc_dcfg *dcfg;
+ unsigned int cpus;
+ unsigned long cpus_bits;
int ret;
/* set some other name then imx */
@@ -1274,6 +1383,17 @@ static int imx_rproc_probe(struct platform_device *pdev)
if (ret)
return dev_err_probe(dev, ret, "failed on imx_rproc_addr_init\n");
+ ret = of_property_read_u32(dev->of_node, "fsl,cpus-bits", &cpus);
+ if (ret) {
+ cpumask_clear(&priv->cpus);
+ } else {
+ cpus_bits = cpus;
+ bitmap_copy(cpumask_bits(&priv->cpus), &cpus_bits,
+ min((unsigned int)nr_cpumask_bits,
+ (unsigned int)sizeof(unsigned long)));
+ rproc->auto_boot = false;
+ }
+
ret = imx_rproc_detect_mode(priv);
if (ret)
return dev_err_probe(dev, ret, "failed on detect mode\n");
@@ -1372,6 +1492,12 @@ static const struct imx_rproc_plat_ops imx_rproc_ops_sm_cpu = {
.stop = imx_rproc_sm_cpu_stop,
};
+static const struct imx_rproc_plat_ops imx_rproc_ops_psci = {
+ .start = imx_rproc_psci_start,
+ .stop = imx_rproc_psci_stop,
+ .detect_mode = imx_rproc_psci_detect_mode,
+};
+
static const struct imx_rproc_dcfg imx_rproc_cfg_imx8mn_mmio = {
.src_reg = IMX7D_SRC_SCR,
.src_mask = IMX7D_M4_RST_MASK,
@@ -1464,6 +1590,13 @@ static const struct imx_rproc_dcfg imx_rproc_cfg_imx95_m7 = {
.lmid = 1, /* Use 1 as Logical Machine ID where M7 resides */
};
+static const struct imx_rproc_dcfg imx_rproc_cfg_psci = {
+ .att = NULL,
+ .att_size = 0,
+ .ops = &imx_rproc_ops_psci,
+ .flags = IMX_RPROC_NO_ADDR_TRANS,
+};
+
static const struct of_device_id imx_rproc_of_match[] = {
{ .compatible = "fsl,imx7ulp-cm4", .data = &imx_rproc_cfg_imx7ulp },
{ .compatible = "fsl,imx7d-cm4", .data = &imx_rproc_cfg_imx7d },
@@ -1479,6 +1612,7 @@ static const struct of_device_id imx_rproc_of_match[] = {
{ .compatible = "fsl,imx8ulp-cm33", .data = &imx_rproc_cfg_imx8ulp },
{ .compatible = "fsl,imx93-cm33", .data = &imx_rproc_cfg_imx93 },
{ .compatible = "fsl,imx95-cm7", .data = &imx_rproc_cfg_imx95_m7 },
+ { .compatible = "fsl,imx-rproc-psci", .data = &imx_rproc_cfg_psci },
{},
};
MODULE_DEVICE_TABLE(of, imx_rproc_of_match);
diff --git a/drivers/remoteproc/imx_rproc.h b/drivers/remoteproc/imx_rproc.h
index d37e6f90548c..be6709971042 100644
--- a/drivers/remoteproc/imx_rproc.h
+++ b/drivers/remoteproc/imx_rproc.h
@@ -18,6 +18,8 @@ struct imx_rproc_att {
/* dcfg flags */
#define IMX_RPROC_NEED_SYSTEM_OFF BIT(0)
#define IMX_RPROC_NEED_CLKS BIT(1)
+/* No need address translation */
+#define IMX_RPROC_NO_ADDR_TRANS BIT(2)
struct imx_rproc_plat_ops {
int (*start)(struct rproc *rproc);
--
2.43.0
^ permalink raw reply related
* [PATCH 3/5] drivers: remoteproc: imx: add autoboot support for A-core remoteproc
From: Jiafei Pan @ 2026-04-28 9:08 UTC (permalink / raw)
To: andersson, mathieu.poirier, peng.fan, Frank.Li, s.hauer, kernel,
festevam, imx, linux-arm-kernel, linux-kernel
Cc: Zhiqiang.Hou, mingkai.hu
In-Reply-To: <20260428090819.17761-1-Jiafei.Pan@nxp.com>
From: Hou Zhiqiang <Zhiqiang.Hou@nxp.com>
Add autoboot support for Cortex-A Core remoteproc.
Signed-off-by: Hou Zhiqiang <Zhiqiang.Hou@nxp.com>
---
drivers/remoteproc/imx_rproc.c | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/drivers/remoteproc/imx_rproc.c b/drivers/remoteproc/imx_rproc.c
index 8a3de27c96b7..e8d239bef5c9 100644
--- a/drivers/remoteproc/imx_rproc.c
+++ b/drivers/remoteproc/imx_rproc.c
@@ -1286,7 +1286,24 @@ static int imx_rproc_sm_detect_mode(struct rproc *rproc)
static int imx_rproc_psci_detect_mode(struct rproc *rproc)
{
- rproc->state = RPROC_OFFLINE;
+ struct imx_rproc *priv = rproc->priv;
+ unsigned int cpu;
+ int cpu_aff;
+
+ rproc->state = RPROC_DETACHED;
+ for_each_cpu(cpu, &priv->cpus) {
+ cpu_aff = psci_ops.affinity_info(cpu_logical_map(cpu), 0);
+ if (cpu_aff == PSCI_0_2_AFFINITY_LEVEL_OFF) {
+ rproc->state = RPROC_OFFLINE;
+ break;
+ }
+
+ /* in psci on state but running Linux */
+ if (cpu_online(cpu)) {
+ rproc->state = RPROC_OFFLINE;
+ break;
+ }
+ }
return 0;
}
@@ -1391,7 +1408,6 @@ static int imx_rproc_probe(struct platform_device *pdev)
bitmap_copy(cpumask_bits(&priv->cpus), &cpus_bits,
min((unsigned int)nr_cpumask_bits,
(unsigned int)sizeof(unsigned long)));
- rproc->auto_boot = false;
}
ret = imx_rproc_detect_mode(priv);
--
2.43.0
^ permalink raw reply related
* [PATCH 1/5] dt-bindings: remoteproc: add imx-rproc-psci binding
From: Jiafei Pan @ 2026-04-28 9:08 UTC (permalink / raw)
To: andersson, mathieu.poirier, peng.fan, Frank.Li, s.hauer, kernel,
festevam, imx, linux-arm-kernel, linux-kernel
Cc: Zhiqiang.Hou, mingkai.hu, Jiafei Pan
In-Reply-To: <20260428090819.17761-1-Jiafei.Pan@nxp.com>
Add compatible string "fsl,imx-rproc-psci" for i.MX Cortex-A Core's
remoteproc support.
Signed-off-by: Jiafei Pan <Jiafei.Pan@nxp.com>
---
.../remoteproc/fsl,imx-rproc-psci.yaml | 64 +++++++++++++++++++
1 file changed, 64 insertions(+)
create mode 100644 Documentation/devicetree/bindings/remoteproc/fsl,imx-rproc-psci.yaml
diff --git a/Documentation/devicetree/bindings/remoteproc/fsl,imx-rproc-psci.yaml b/Documentation/devicetree/bindings/remoteproc/fsl,imx-rproc-psci.yaml
new file mode 100644
index 000000000000..cdab9566e588
--- /dev/null
+++ b/Documentation/devicetree/bindings/remoteproc/fsl,imx-rproc-psci.yaml
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/remoteproc/fsl,imx-rproc-psci.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NXP i.MX Cortex-A Core Remote Processor via PSCI
+
+maintainers:
+ - Jiafei Pan <Jiafei.Pan@nxp.com>
+
+description:
+ This binding provides support for managing Cortex-A cores as remote
+ processors on i.MX platforms using the PSCI (Power State Coordination
+ Interface) for CPU power management operations. This allows single
+ Cortex-A core or multiple Cortex-A cores to be controlled by Linux as
+ a remote processor, enabling them to run RTOS or bare-metal applications.
+
+properties:
+ compatible:
+ const: fsl,imx-rproc-psci
+
+ fsl,cpus-bits:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Bitmask indicating which CPU cores are assigned to this remote
+ processor instance. Each bit represents a CPU core, where bit N
+ corresponds to CPU N. For example, 0x2 (0b10) assigns CPU core 1,
+ while 0x6 (0b110) assigns CPU cores 1 and 2.
+
+ memory-region:
+ maxItems: 1
+ description:
+ Phandle to a reserved memory region to be used for the remote
+ processor's code and data.
+
+required:
+ - compatible
+ - fsl,cpus-bits
+ - memory-region
+
+additionalProperties: false
+
+examples:
+ - |
+ / {
+ reserved-memory {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+
+ rtos_ca55_reserved: rtos-ca55@d0000000 {
+ reg = <0 0xd0000000 0 0x10000000>;
+ no-map;
+ };
+ };
+
+ remoteproc-ca55-1 {
+ compatible = "fsl,imx-rproc-psci";
+ /* bitmask: 0b10, assign A55 Core 1 */
+ fsl,cpus-bits = <0x2>;
+ memory-region = <&rtos_ca55_reserved>;
+ };
+ };
--
2.43.0
^ permalink raw reply related
* [PATCH 0/5] remoteproc: add Cortex-A Core remoteproc support on i.MX platforms
From: Jiafei Pan @ 2026-04-28 9:08 UTC (permalink / raw)
To: andersson, mathieu.poirier, peng.fan, Frank.Li, s.hauer, kernel,
festevam, imx, linux-arm-kernel, linux-kernel
Cc: Zhiqiang.Hou, mingkai.hu, Jiafei Pan
This patch series is to add remoteproc support on Cortex-A Core of i.MX platforms:
1. Adding dts binding for Cortex-A Core remoteproc
2. Enable Cortex-A Core remoteproc support in remoteproc driver
3. Adding dts example on imx93 platforms.
4. Updating remoteproc driver to use prcoessor name as device node in sys filesystem
Hou Zhiqiang (1):
drivers: remoteproc: imx: add autoboot support for A-core remoteproc
Jiafei Pan (4):
dt-bindings: remoteproc: add imx-rproc-psci binding
drivers: remoteproc: imx: add support for Cortex-A Core
arm64: dts: imx93: Cortex-A Core remoteproc device node
remoteproc: imx: use device node name as processor name
.../remoteproc/fsl,imx-rproc-psci.yaml | 64 ++++++++
.../imx93-11x11-evk-multicore-rtos.dts | 39 +++++
.../imx93-14x14-evk-multicore-rtos.dts | 39 +++++
.../boot/dts/freescale/imx93-rproc-ca55.dtsi | 14 ++
drivers/remoteproc/imx_rproc.c | 152 +++++++++++++++++-
drivers/remoteproc/imx_rproc.h | 2 +
6 files changed, 309 insertions(+), 1 deletion(-)
create mode 100644 Documentation/devicetree/bindings/remoteproc/fsl,imx-rproc-psci.yaml
create mode 100644 arch/arm64/boot/dts/freescale/imx93-11x11-evk-multicore-rtos.dts
create mode 100644 arch/arm64/boot/dts/freescale/imx93-14x14-evk-multicore-rtos.dts
create mode 100644 arch/arm64/boot/dts/freescale/imx93-rproc-ca55.dtsi
--
2.43.0
^ permalink raw reply
* Re: [QUESTION] Is the ARM SMMU v3 implementation designed to always ignore SSID when SSID_VALID == 0?
From: Jean-Philippe Brucker @ 2026-04-28 9:05 UTC (permalink / raw)
To: Joonwon Kang
Cc: will, robin.murphy, joro, jgg, baolu.lu, linux-arm-kernel, iommu,
linux-kernel, stimim, cychu, hhchung
In-Reply-To: <20260428073859.1502047-1-joonwonkang@google.com>
Hi,
On Tue, Apr 28, 2026 at 07:38:59AM +0000, Joonwon Kang wrote:
> Hi team,
>
> According to the ARM SMMU v3 spec, I believe that SSID should always be
> ignored when SSID_VALID == 0 and the current ARM SMMU v3 module
> implementation in the kernel seems to comply with this without exception.
> For example, when handling an event from SMMU, the implementation checks
> SSID_VALID(SSV) first and ignores SSID accordingly. If there is any
> exception to this rule, I believe it is a bug.
Indeed
> Is it true for all the current and future cases? In other words, is it
> **mandatory** that the ARM SMMU v3 implementation ignores SSID when
> SSID_VALID == 0? or there might be some cases where the implementation
> needs to refer to SSID even when SSID_VALID == 0?
>
> Asking this question since our HW may not be able to clear SSID when
> SSID_VALID == 0 and so there might be some garbage value in SSID at some
> point of time(the HW will have a correct SSID when SSID_VALID == 1,
> though). If the ARM SMMU v3 implementation is to refer to that garbage
> value for any reason, the result would be devastating.
At least according to the architecture, SubstreamID is ignored when SSV=0.
The SMMU is allowed to propagate the garbage:
7.3 Event record
* SSV: The SubstreamID validity flag
- 0: No SubstreamID was provided with the transaction and the SubstreamID field is UNKNOWN.
But the driver will ignore it.
Same for PRI queue but in that case the page request wouldn't have a PASID
TLP prefix.
Thanks,
Jean
^ permalink raw reply
* [PATCH v5 20/29] media: rockchip: rga: change offset to dma_addresses
From: Sven Püschel @ 2026-04-28 9:00 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, Sven Püschel,
Nicolas Dufresne
In-Reply-To: <20260428-spu-rga3-v5-0-eb7f5d019d86@pengutronix.de>
Change the offset to dma_addresses, as the current naming is misleading.
The offset naming comes from the fact that it references the offset in
the mapped iommu address space. But from the hardware point of view this
is an address, as also pointed out by the register naming
(e.g. RGA_DST_Y_RGB_BASE_ADDR). Therefore also change the type to
dma_addr_t, as with an external iommu driver this would also be the
correct type.
This change is a preparation for the RGA3 support, which uses an external
iommu and therefore just gets an dma_addr_t for each buffer. The field
renaming allows to reuse the existing fields of rga_vb_buffer to store
these values.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/platform/rockchip/rga/rga-buf.c | 12 +--
drivers/media/platform/rockchip/rga/rga-hw.c | 105 +++++++++++++-------------
drivers/media/platform/rockchip/rga/rga.h | 12 +--
3 files changed, 64 insertions(+), 65 deletions(-)
diff --git a/drivers/media/platform/rockchip/rga/rga-buf.c b/drivers/media/platform/rockchip/rga/rga-buf.c
index ffc6162b2e681..bc349d0a46365 100644
--- a/drivers/media/platform/rockchip/rga/rga-buf.c
+++ b/drivers/media/platform/rockchip/rga/rga-buf.c
@@ -121,7 +121,7 @@ static int rga_buf_prepare(struct vb2_buffer *vb)
size_t curr_desc = 0;
int i;
const struct v4l2_format_info *info;
- unsigned int offsets[VIDEO_MAX_PLANES];
+ unsigned int dma_addrs[VIDEO_MAX_PLANES];
if (IS_ERR(f))
return PTR_ERR(f);
@@ -145,18 +145,18 @@ static int rga_buf_prepare(struct vb2_buffer *vb)
"Failed to map video buffer to RGA\n");
return n_desc;
}
- offsets[i] = curr_desc << PAGE_SHIFT;
+ dma_addrs[i] = curr_desc << PAGE_SHIFT;
curr_desc += n_desc;
}
/* Fill the remaining planes */
info = v4l2_format_info(f->fmt->fourcc);
for (i = info->mem_planes; i < info->comp_planes; i++)
- offsets[i] = get_plane_offset(f, info, i);
+ dma_addrs[i] = dma_addrs[0] + get_plane_offset(f, info, i);
- rbuf->offset.y_off = offsets[0];
- rbuf->offset.u_off = offsets[1];
- rbuf->offset.v_off = offsets[2];
+ rbuf->dma_addrs.y_addr = dma_addrs[0];
+ rbuf->dma_addrs.u_addr = dma_addrs[1];
+ rbuf->dma_addrs.v_addr = dma_addrs[2];
return 0;
}
diff --git a/drivers/media/platform/rockchip/rga/rga-hw.c b/drivers/media/platform/rockchip/rga/rga-hw.c
index 931b4c4074a29..8f60482f538c5 100644
--- a/drivers/media/platform/rockchip/rga/rga-hw.c
+++ b/drivers/media/platform/rockchip/rga/rga-hw.c
@@ -16,11 +16,11 @@ enum e_rga_start_pos {
RB = 3,
};
-struct rga_corners_addr_offset {
- struct rga_addr_offset left_top;
- struct rga_addr_offset right_top;
- struct rga_addr_offset left_bottom;
- struct rga_addr_offset right_bottom;
+struct rga_corners_addrs {
+ struct rga_addrs left_top;
+ struct rga_addrs right_top;
+ struct rga_addrs left_bottom;
+ struct rga_addrs right_bottom;
};
static unsigned int rga_get_scaling(unsigned int src, unsigned int dst)
@@ -36,20 +36,20 @@ static unsigned int rga_get_scaling(unsigned int src, unsigned int dst)
return (src > dst) ? ((dst << 16) / src) : ((src << 16) / dst);
}
-static struct rga_corners_addr_offset
-rga_get_addr_offset(struct rga_frame *frm, struct rga_addr_offset *offset,
- unsigned int x, unsigned int y, unsigned int w, unsigned int h)
+static struct rga_corners_addrs
+rga_get_corner_addrs(struct rga_frame *frm, struct rga_addrs *addrs,
+ unsigned int x, unsigned int y, unsigned int w, unsigned int h)
{
- struct rga_corners_addr_offset offsets;
- struct rga_addr_offset *lt, *lb, *rt, *rb;
+ struct rga_corners_addrs corner_addrs;
+ struct rga_addrs *lt, *lb, *rt, *rb;
const struct v4l2_format_info *format_info;
unsigned int x_div = 0,
y_div = 0, uv_stride = 0, pixel_width = 0;
- lt = &offsets.left_top;
- lb = &offsets.left_bottom;
- rt = &offsets.right_top;
- rb = &offsets.right_bottom;
+ lt = &corner_addrs.left_top;
+ lb = &corner_addrs.left_bottom;
+ rt = &corner_addrs.right_top;
+ rb = &corner_addrs.right_bottom;
format_info = v4l2_format_info(frm->pix.pixelformat);
/* x_div is only used for the u/v planes.
@@ -64,29 +64,28 @@ rga_get_addr_offset(struct rga_frame *frm, struct rga_addr_offset *offset,
uv_stride = frm->stride / x_div;
pixel_width = frm->stride / frm->pix.width;
- lt->y_off = offset->y_off + y * frm->stride + x * pixel_width;
- lt->u_off = offset->u_off + (y / y_div) * uv_stride + x / x_div;
- lt->v_off = offset->v_off + (y / y_div) * uv_stride + x / x_div;
+ lt->y_addr = addrs->y_addr + y * frm->stride + x * pixel_width;
+ lt->u_addr = addrs->u_addr + (y / y_div) * uv_stride + x / x_div;
+ lt->v_addr = addrs->v_addr + (y / y_div) * uv_stride + x / x_div;
- lb->y_off = lt->y_off + (h - 1) * frm->stride;
- lb->u_off = lt->u_off + (h / y_div - 1) * uv_stride;
- lb->v_off = lt->v_off + (h / y_div - 1) * uv_stride;
+ lb->y_addr = lt->y_addr + (h - 1) * frm->stride;
+ lb->u_addr = lt->u_addr + (h / y_div - 1) * uv_stride;
+ lb->v_addr = lt->v_addr + (h / y_div - 1) * uv_stride;
- rt->y_off = lt->y_off + (w - 1) * pixel_width;
- rt->u_off = lt->u_off + w / x_div - 1;
- rt->v_off = lt->v_off + w / x_div - 1;
+ rt->y_addr = lt->y_addr + (w - 1) * pixel_width;
+ rt->u_addr = lt->u_addr + w / x_div - 1;
+ rt->v_addr = lt->v_addr + w / x_div - 1;
- rb->y_off = lb->y_off + (w - 1) * pixel_width;
- rb->u_off = lb->u_off + w / x_div - 1;
- rb->v_off = lb->v_off + w / x_div - 1;
+ rb->y_addr = lb->y_addr + (w - 1) * pixel_width;
+ rb->u_addr = lb->u_addr + w / x_div - 1;
+ rb->v_addr = lb->v_addr + w / x_div - 1;
- return offsets;
+ return corner_addrs;
}
-static struct rga_addr_offset *rga_lookup_draw_pos(struct
- rga_corners_addr_offset
- * offsets, u32 rotate_mode,
- u32 mirr_mode)
+static struct rga_addrs *rga_lookup_draw_pos(struct rga_corners_addrs *corner_addrs,
+ u32 rotate_mode,
+ u32 mirr_mode)
{
static enum e_rga_start_pos rot_mir_point_matrix[4][4] = {
{
@@ -103,18 +102,18 @@ static struct rga_addr_offset *rga_lookup_draw_pos(struct
},
};
- if (!offsets)
+ if (!corner_addrs)
return NULL;
switch (rot_mir_point_matrix[rotate_mode][mirr_mode]) {
case LT:
- return &offsets->left_top;
+ return &corner_addrs->left_top;
case LB:
- return &offsets->left_bottom;
+ return &corner_addrs->left_bottom;
case RT:
- return &offsets->right_top;
+ return &corner_addrs->right_top;
case RB:
- return &offsets->right_bottom;
+ return &corner_addrs->right_bottom;
}
return NULL;
@@ -326,9 +325,9 @@ static void rga_cmd_set_format_scale_info(struct rga_ctx *ctx)
}
static void rga_cmd_set_src_info(struct rga_ctx *ctx,
- struct rga_addr_offset *offset)
+ struct rga_addrs *addrs)
{
- struct rga_corners_addr_offset src_offsets;
+ struct rga_corners_addrs src_corner_addrs;
u32 *dest = ctx->cmdbuf_virt;
unsigned int src_h, src_w, src_x, src_y;
@@ -340,22 +339,22 @@ static void rga_cmd_set_src_info(struct rga_ctx *ctx,
/*
* Calculate the source framebuffer base address with offset pixel.
*/
- src_offsets = rga_get_addr_offset(&ctx->in, offset,
- src_x, src_y, src_w, src_h);
+ src_corner_addrs = rga_get_corner_addrs(&ctx->in, addrs,
+ src_x, src_y, src_w, src_h);
dest[(RGA_SRC_Y_RGB_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] =
- src_offsets.left_top.y_off;
+ src_corner_addrs.left_top.y_addr;
dest[(RGA_SRC_CB_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] =
- src_offsets.left_top.u_off;
+ src_corner_addrs.left_top.u_addr;
dest[(RGA_SRC_CR_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] =
- src_offsets.left_top.v_off;
+ src_corner_addrs.left_top.v_addr;
}
static void rga_cmd_set_dst_info(struct rga_ctx *ctx,
- struct rga_addr_offset *offset)
+ struct rga_addrs *addrs)
{
- struct rga_addr_offset *dst_offset;
- struct rga_corners_addr_offset offsets;
+ struct rga_addrs *dst_addrs;
+ struct rga_corners_addrs corner_addrs;
u32 *dest = ctx->cmdbuf_virt;
unsigned int dst_h, dst_w, dst_x, dst_y;
unsigned int mir_mode = 0;
@@ -389,15 +388,15 @@ static void rga_cmd_set_dst_info(struct rga_ctx *ctx,
/*
* Configure the dest framebuffer base address with pixel offset.
*/
- offsets = rga_get_addr_offset(&ctx->out, offset, dst_x, dst_y, dst_w, dst_h);
- dst_offset = rga_lookup_draw_pos(&offsets, rot_mode, mir_mode);
+ corner_addrs = rga_get_corner_addrs(&ctx->out, addrs, dst_x, dst_y, dst_w, dst_h);
+ dst_addrs = rga_lookup_draw_pos(&corner_addrs, rot_mode, mir_mode);
dest[(RGA_DST_Y_RGB_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] =
- dst_offset->y_off;
+ dst_addrs->y_addr;
dest[(RGA_DST_CB_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] =
- dst_offset->u_off;
+ dst_addrs->u_addr;
dest[(RGA_DST_CR_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] =
- dst_offset->v_off;
+ dst_addrs->v_addr;
}
static void rga_cmd_set_mode(struct rga_ctx *ctx)
@@ -436,8 +435,8 @@ static void rga_cmd_set(struct rga_ctx *ctx,
rga_cmd_set_dst_addr(ctx, dst->dma_desc_pa);
- rga_cmd_set_src_info(ctx, &src->offset);
- rga_cmd_set_dst_info(ctx, &dst->offset);
+ rga_cmd_set_src_info(ctx, &src->dma_addrs);
+ rga_cmd_set_dst_info(ctx, &dst->dma_addrs);
rga_cmd_set_flip_rotate_info(ctx);
rga_write(rga, RGA_CMD_BASE, ctx->cmdbuf_phy);
diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h
index dc6f90b843c32..025b1df594e9a 100644
--- a/drivers/media/platform/rockchip/rga/rga.h
+++ b/drivers/media/platform/rockchip/rga/rga.h
@@ -96,10 +96,10 @@ struct rockchip_rga {
const struct rga_hw *hw;
};
-struct rga_addr_offset {
- unsigned int y_off;
- unsigned int u_off;
- unsigned int v_off;
+struct rga_addrs {
+ dma_addr_t y_addr;
+ dma_addr_t u_addr;
+ dma_addr_t v_addr;
};
struct rga_vb_buffer {
@@ -111,8 +111,8 @@ struct rga_vb_buffer {
dma_addr_t dma_desc_pa;
size_t n_desc;
- /* Plane offsets of this buffer into the mapping */
- struct rga_addr_offset offset;
+ /* Plane DMA addresses after the MMU mapping of the buffer */
+ struct rga_addrs dma_addrs;
};
static inline struct rga_vb_buffer *vb_to_rga(struct vb2_v4l2_buffer *vb)
--
2.54.0
^ permalink raw reply related
* [PATCH v5 04/29] media: v4l2-common: add has_alpha to v4l2_format_info
From: Sven Püschel @ 2026-04-28 9:00 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, Sven Püschel,
Nicolas Dufresne
In-Reply-To: <20260428-spu-rga3-v5-0-eb7f5d019d86@pengutronix.de>
Add a has_alpha value to the v4l2_format_info struct to indicate if the
format contains an alpha component. This information can currently not
be queried in a generic way, but might be useful for potential drivers
to properly setup alpha blending to copy or set the alpha value.
The implementation is based on the drm_format_info implementation.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/v4l2-core/v4l2-common.c | 32 ++++++++++++++++----------------
include/media/v4l2-common.h | 2 ++
2 files changed, 18 insertions(+), 16 deletions(-)
diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c
index 58a4b372cf5be..2b5ccedeb6841 100644
--- a/drivers/media/v4l2-core/v4l2-common.c
+++ b/drivers/media/v4l2-core/v4l2-common.c
@@ -248,26 +248,26 @@ const struct v4l2_format_info *v4l2_format_info(u32 format)
/* RGB formats (1 or 2 bytes per pixel) */
{ .format = V4L2_PIX_FMT_RGB332, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGB444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_ARGB444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_ARGB444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_XRGB444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_RGBA444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_RGBA444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_RGBX444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_ABGR444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_ABGR444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_XBGR444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_BGRA444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_BGRA444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_BGRX444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGB555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_ARGB555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_ARGB555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_XRGB555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_RGBA555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_RGBA555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_RGBX555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_ABGR555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_ABGR555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_XBGR555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_BGRA555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_BGRA555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_BGRX555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGB565, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGB555X, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_ARGB555X, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_ARGB555X, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_XRGB555X, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGB565X, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
@@ -276,24 +276,24 @@ const struct v4l2_format_info *v4l2_format_info(u32 format)
{ .format = V4L2_PIX_FMT_BGR24, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGB24, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_BGR32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_ABGR32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_ABGR32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_XBGR32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_BGRA32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_BGRA32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_BGRX32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGB32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_RGBA32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_RGBA32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_RGBX32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_ARGB32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_ARGB32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_XRGB32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGBX1010102, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_RGBA1010102, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_ARGB2101010, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_RGBA1010102, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
+ { .format = V4L2_PIX_FMT_ARGB2101010, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
/* RGB formats (6 or 8 bytes per pixel) */
{ .format = V4L2_PIX_FMT_BGR48_12, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_BGR48, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGB48, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_ABGR64_12, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 8, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_ABGR64_12, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 8, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
/* HSV formats */
{ .format = V4L2_PIX_FMT_HSV24, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
diff --git a/include/media/v4l2-common.h b/include/media/v4l2-common.h
index f8b1faced79c8..401d8506c24b5 100644
--- a/include/media/v4l2-common.h
+++ b/include/media/v4l2-common.h
@@ -520,6 +520,7 @@ enum v4l2_pixel_encoding {
* @vdiv: Vertical chroma subsampling factor
* @block_w: Per-plane macroblock pixel width (optional)
* @block_h: Per-plane macroblock pixel height (optional)
+ * @has_alpha: Does the format embeds an alpha component?
*/
struct v4l2_format_info {
u32 format;
@@ -532,6 +533,7 @@ struct v4l2_format_info {
u8 vdiv;
u8 block_w[4];
u8 block_h[4];
+ bool has_alpha;
};
static inline bool v4l2_is_format_rgb(const struct v4l2_format_info *f)
--
2.54.0
^ permalink raw reply related
* [PATCH v5 25/29] media: rockchip: rga: move rga_fmt to rga-hw.h
From: Sven Püschel @ 2026-04-28 9:01 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, Sven Püschel,
Nicolas Dufresne
In-Reply-To: <20260428-spu-rga3-v5-0-eb7f5d019d86@pengutronix.de>
Move rga_fmt to rga-hw in preparation of the RGA3 addition, as the struct
contains many RGA2 specific values. They are used to write the correct
register values quickly based on the chosen format. Therefore the
pointer to the rga_fmt struct is kept but changed to an opaque void
pointer outside of the rga-hw.h.
To enumerate and set the correct formats, two helper functions need to
be exposed in the rga_hw struct:
enum_format just get's the vidioc_enum_fmt format and it's return value
is also returned from vidioc_enum_fmt. This is a simple pass-through,
as the implementation is very simple.
adjust_and_map_format is a simple abstraction around the previous
rga_find_format. But unlike rga_find_format, it always returns a valid
format. Therefore the passed format value is also a pointer to update
it in case the values are not supported by the hardware.
Due to the RGA3 supporting different formats on the capture and output
side, an additional parameter is_capture has been added to support
this use-case. The additional ctx parameter is also added to allow the
RGA3 to limit the colorimetry only if an RGB<->YCrCb transformation
happens.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/platform/rockchip/rga/rga-buf.c | 2 +-
drivers/media/platform/rockchip/rga/rga-hw.c | 49 +++++++++++++++++++++------
drivers/media/platform/rockchip/rga/rga-hw.h | 8 +++++
drivers/media/platform/rockchip/rga/rga.c | 39 +++++++--------------
drivers/media/platform/rockchip/rga/rga.h | 14 +++-----
5 files changed, 66 insertions(+), 46 deletions(-)
diff --git a/drivers/media/platform/rockchip/rga/rga-buf.c b/drivers/media/platform/rockchip/rga/rga-buf.c
index c0cc885ba58a8..8632b18cd3fc6 100644
--- a/drivers/media/platform/rockchip/rga/rga-buf.c
+++ b/drivers/media/platform/rockchip/rga/rga-buf.c
@@ -162,7 +162,7 @@ static int rga_buf_prepare(struct vb2_buffer *vb)
}
/* Fill the remaining planes */
- info = v4l2_format_info(f->fmt->fourcc);
+ info = v4l2_format_info(f->pix.pixelformat);
for (i = info->mem_planes; i < info->comp_planes; i++)
dma_addrs[i] = dma_addrs[0] + get_plane_offset(f, info, i);
diff --git a/drivers/media/platform/rockchip/rga/rga-hw.c b/drivers/media/platform/rockchip/rga/rga-hw.c
index e75674b183cdf..d4282fc5d92ba 100644
--- a/drivers/media/platform/rockchip/rga/rga-hw.c
+++ b/drivers/media/platform/rockchip/rga/rga-hw.c
@@ -202,6 +202,8 @@ static void rga_cmd_set_format_scale_info(struct rga_ctx *ctx)
union rga_dst_vir_info dst_vir_info;
union rga_dst_act_info dst_act_info;
u32 in_stride, out_stride;
+ struct rga_fmt *in_fmt = ctx->in.fmt;
+ struct rga_fmt *out_fmt = ctx->out.fmt;
src_h = ctx->in.crop.height;
src_w = ctx->in.crop.width;
@@ -217,18 +219,18 @@ static void rga_cmd_set_format_scale_info(struct rga_ctx *ctx)
dst_vir_info.val = dest[(RGA_DST_VIR_INFO - RGA_MODE_BASE_REG) >> 2];
dst_act_info.val = dest[(RGA_DST_ACT_INFO - RGA_MODE_BASE_REG) >> 2];
- src_info.data.format = ctx->in.fmt->hw_format;
- src_info.data.swap = ctx->in.fmt->color_swap;
- dst_info.data.format = ctx->out.fmt->hw_format;
- dst_info.data.swap = ctx->out.fmt->color_swap;
+ src_info.data.format = in_fmt->hw_format;
+ src_info.data.swap = in_fmt->color_swap;
+ dst_info.data.format = out_fmt->hw_format;
+ dst_info.data.swap = out_fmt->color_swap;
/*
* CSC mode must only be set when the colorspace families differ between
* input and output. It must remain unset (zeroed) if both are the same.
*/
- if (RGA_COLOR_FMT_IS_YUV(ctx->in.fmt->hw_format) &&
- RGA_COLOR_FMT_IS_RGB(ctx->out.fmt->hw_format)) {
+ if (RGA_COLOR_FMT_IS_YUV(in_fmt->hw_format) &&
+ RGA_COLOR_FMT_IS_RGB(out_fmt->hw_format)) {
switch (ctx->in.pix.colorspace) {
case V4L2_COLORSPACE_REC709:
src_info.data.csc_mode = RGA_SRC_CSC_MODE_BT709_R0;
@@ -239,8 +241,8 @@ static void rga_cmd_set_format_scale_info(struct rga_ctx *ctx)
}
}
- if (RGA_COLOR_FMT_IS_RGB(ctx->in.fmt->hw_format) &&
- RGA_COLOR_FMT_IS_YUV(ctx->out.fmt->hw_format)) {
+ if (RGA_COLOR_FMT_IS_RGB(in_fmt->hw_format) &&
+ RGA_COLOR_FMT_IS_YUV(out_fmt->hw_format)) {
switch (ctx->out.pix.colorspace) {
case V4L2_COLORSPACE_REC709:
dst_info.data.csc_mode = RGA_SRC_CSC_MODE_BT709_R0;
@@ -574,11 +576,36 @@ static struct rga_fmt formats[] = {
},
};
+static void *rga_adjust_and_map_format(struct rga_ctx *ctx,
+ struct v4l2_pix_format_mplane *format,
+ bool is_output)
+{
+ unsigned int i;
+
+ if (!format)
+ return &formats[0];
+
+ for (i = 0; i < ARRAY_SIZE(formats); i++) {
+ if (formats[i].fourcc == format->pixelformat)
+ return &formats[i];
+ }
+
+ format->pixelformat = formats[0].fourcc;
+ return &formats[0];
+}
+
+static int rga_enum_format(struct v4l2_fmtdesc *f)
+{
+ if (f->index >= ARRAY_SIZE(formats))
+ return -EINVAL;
+
+ f->pixelformat = formats[f->index].fourcc;
+ return 0;
+}
+
const struct rga_hw rga2_hw = {
.card_type = "rga2",
.has_internal_iommu = true,
- .formats = formats,
- .num_formats = ARRAY_SIZE(formats),
.cmdbuf_size = RGA_CMDBUF_SIZE,
.min_width = MIN_WIDTH,
.max_width = MAX_WIDTH,
@@ -591,4 +618,6 @@ const struct rga_hw rga2_hw = {
.start = rga_hw_start,
.handle_irq = rga_handle_irq,
.get_version = rga_get_version,
+ .adjust_and_map_format = rga_adjust_and_map_format,
+ .enum_format = rga_enum_format,
};
diff --git a/drivers/media/platform/rockchip/rga/rga-hw.h b/drivers/media/platform/rockchip/rga/rga-hw.h
index 805ec23e5e3f4..14ffa5ebd453b 100644
--- a/drivers/media/platform/rockchip/rga/rga-hw.h
+++ b/drivers/media/platform/rockchip/rga/rga-hw.h
@@ -6,6 +6,8 @@
#ifndef __RGA_HW_H__
#define __RGA_HW_H__
+#include <linux/types.h>
+
#define RGA_CMDBUF_SIZE 0x80
/* Hardware limits */
@@ -431,4 +433,10 @@ union rga_pat_con {
} data;
};
+struct rga_fmt {
+ u32 fourcc;
+ u8 color_swap;
+ u8 hw_format;
+};
+
#endif
diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c
index 6433fdaa5b339..364c1dac3f81c 100644
--- a/drivers/media/platform/rockchip/rga/rga.c
+++ b/drivers/media/platform/rockchip/rga/rga.c
@@ -185,17 +185,6 @@ static int rga_setup_ctrls(struct rga_ctx *ctx)
return 0;
}
-static struct rga_fmt *rga_fmt_find(struct rockchip_rga *rga, u32 pixelformat)
-{
- unsigned int i;
-
- for (i = 0; i < rga->hw->num_formats; i++) {
- if (rga->hw->formats[i].fourcc == pixelformat)
- return &rga->hw->formats[i];
- }
- return NULL;
-}
-
struct rga_frame *rga_get_frame(struct rga_ctx *ctx, enum v4l2_buf_type type)
{
if (V4L2_TYPE_IS_OUTPUT(type))
@@ -217,7 +206,6 @@ static int rga_open(struct file *file)
.crop.top = 0,
.crop.width = def_width,
.crop.height = def_height,
- .fmt = &rga->hw->formats[0],
};
ctx = kzalloc_obj(*ctx);
@@ -238,9 +226,12 @@ static int rga_open(struct file *file)
ctx->in = def_frame;
ctx->out = def_frame;
- v4l2_fill_pixfmt_mp_aligned(&ctx->in.pix, ctx->in.fmt->fourcc,
+ ctx->in.fmt = rga->hw->adjust_and_map_format(ctx, &ctx->in.pix, true);
+ v4l2_fill_pixfmt_mp_aligned(&ctx->in.pix, ctx->in.pix.pixelformat,
def_width, def_height, rga->hw->stride_alignment);
- v4l2_fill_pixfmt_mp_aligned(&ctx->out.pix, ctx->out.fmt->fourcc,
+ ctx->out.fmt =
+ rga->hw->adjust_and_map_format(ctx, &ctx->out.pix, false);
+ v4l2_fill_pixfmt_mp_aligned(&ctx->out.pix, ctx->out.pix.pixelformat,
def_width, def_height, rga->hw->stride_alignment);
if (mutex_lock_interruptible(&rga->mutex)) {
@@ -322,13 +313,11 @@ vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap)
static int vidioc_enum_fmt(struct file *file, void *priv, struct v4l2_fmtdesc *f)
{
struct rockchip_rga *rga = video_drvdata(file);
- struct rga_fmt *fmt;
-
- if (f->index >= rga->hw->num_formats)
- return -EINVAL;
+ int ret;
- fmt = &rga->hw->formats[f->index];
- f->pixelformat = fmt->fourcc;
+ ret = rga->hw->enum_format(f);
+ if (ret != 0)
+ return ret;
if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
@@ -363,7 +352,6 @@ static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f)
struct v4l2_pix_format_mplane *pix_fmt = &f->fmt.pix_mp;
struct rga_ctx *ctx = file_to_rga_ctx(file);
const struct rga_hw *hw = ctx->rga->hw;
- struct rga_fmt *fmt;
struct v4l2_frmsize_stepwise frmsize = {
.min_width = hw->min_width,
.max_width = hw->max_width,
@@ -394,9 +382,7 @@ static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f)
pix_fmt->xfer_func = frm->pix.xfer_func;
}
- fmt = rga_fmt_find(ctx->rga, pix_fmt->pixelformat);
- if (!fmt)
- fmt = &hw->formats[0];
+ hw->adjust_and_map_format(ctx, pix_fmt, V4L2_TYPE_IS_OUTPUT(f->type));
v4l2_apply_frmsize_constraints(&pix_fmt->width, &pix_fmt->height, &frmsize);
v4l2_fill_pixfmt_mp_aligned(pix_fmt, pix_fmt->pixelformat,
@@ -456,7 +442,8 @@ static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
frm = rga_get_frame(ctx, f->type);
if (IS_ERR(frm))
return PTR_ERR(frm);
- frm->fmt = rga_fmt_find(rga, pix_fmt->pixelformat);
+ frm->fmt = rga->hw->adjust_and_map_format(ctx, pix_fmt,
+ V4L2_TYPE_IS_OUTPUT(f->type));
/*
* Copy colorimetry from output to capture as required by the
@@ -480,7 +467,7 @@ static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
v4l2_dbg(debug, 1, &rga->v4l2_dev,
"[%s] fmt - %p4cc %dx%d (stride %d)\n",
V4L2_TYPE_IS_OUTPUT(f->type) ? "OUTPUT" : "CAPTURE",
- &frm->fmt->fourcc, pix_fmt->width, pix_fmt->height,
+ &pix_fmt->pixelformat, pix_fmt->width, pix_fmt->height,
pix_fmt->plane_fmt[0].bytesperline);
for (i = 0; i < pix_fmt->num_planes; i++) {
diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h
index d203b7dae2b03..d98e57c6d7b57 100644
--- a/drivers/media/platform/rockchip/rga/rga.h
+++ b/drivers/media/platform/rockchip/rga/rga.h
@@ -17,18 +17,12 @@
#define DEFAULT_WIDTH 100
#define DEFAULT_HEIGHT 100
-struct rga_fmt {
- u32 fourcc;
- u8 color_swap;
- u8 hw_format;
-};
-
struct rga_frame {
/* Crop */
struct v4l2_rect crop;
/* Image format */
- struct rga_fmt *fmt;
+ void *fmt;
struct v4l2_pix_format_mplane pix;
};
@@ -142,8 +136,6 @@ static inline void rga_mod(struct rockchip_rga *rga, u32 reg, u32 val, u32 mask)
struct rga_hw {
const char *card_type;
bool has_internal_iommu;
- struct rga_fmt *formats;
- u32 num_formats;
size_t cmdbuf_size;
u32 min_width, min_height;
u32 max_width, max_height;
@@ -155,6 +147,10 @@ struct rga_hw {
struct rga_vb_buffer *src, struct rga_vb_buffer *dst);
bool (*handle_irq)(struct rockchip_rga *rga);
void (*get_version)(struct rockchip_rga *rga);
+ void *(*adjust_and_map_format)(struct rga_ctx *ctx,
+ struct v4l2_pix_format_mplane *format,
+ bool is_output);
+ int (*enum_format)(struct v4l2_fmtdesc *f);
};
static inline bool rga_has_internal_iommu(const struct rockchip_rga *rga)
--
2.54.0
^ permalink raw reply related
* [PATCH v5 28/29] media: rockchip: rga: add rga3 support
From: Sven Püschel @ 2026-04-28 9:01 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, Sven Püschel,
Nicolas Dufresne
In-Reply-To: <20260428-spu-rga3-v5-0-eb7f5d019d86@pengutronix.de>
Add support for the RGA3 unit contained in the RK3588.
Only a basic feature set consisting of scaling and color conversion is
implemented. Currently unimplemented features include:
- Advanced formats like 10bit YUV, FBCE mode and Tile8x8 mode
- Background color (V4L2_CID_BG_COLOR)
- Configurable alpha value (V4L2_CID_ALPHA_COMPONENT)
- Image flipping (V4L2_CID_HFLIP and V4L2_CID_VFLIP)
- Image rotation (V4L2_CID_ROTATE)
- Image cropping/composing (VIDIOC_S_SELECTION)
- Only very basic output cropping for 1088 -> 1080 cases is implemented
The register address defines were copied from the
vendor Rockchip kernel sources and slightly adjusted to not start at 0
again for the cmd registers.
During testing it has been noted that the scaling of the hardware is
slightly incorrect. A test conversion of 128x128 RGBA to 256x256 RGBA
causes a slightly larger scaling. The scaling is suddle, as it seems
that the image is scaled to a 2px larger version and then cropped to
it's final size. Trying to use the RGA2 scaling factor calculation
didn't work. As the calculation matches the vendor kernel driver, no
further research has been utilized to check if there may be some kind of
better scaling factor calculation.
Furthermore comparing the RGA3 conversion with the GStreamer
videoconvertscale element, the chroma-site is different. A quick testing
didn't reveal a chroma-site that creates the same image with the
GStreamer Element. Also when converting from YUV to RGB the RGB values
differ by 1 or 2. This doesn't seem to be a colorspace conversion issue
but rather a slightly different precision on the calculation.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
Changes in v5:
- Fixed cmdbuf size to not be a quarter of the actual size
---
drivers/media/platform/rockchip/rga/Makefile | 2 +-
drivers/media/platform/rockchip/rga/rga.c | 4 +
drivers/media/platform/rockchip/rga/rga.h | 1 +
drivers/media/platform/rockchip/rga/rga3-hw.c | 507 ++++++++++++++++++++++++++
drivers/media/platform/rockchip/rga/rga3-hw.h | 192 ++++++++++
5 files changed, 705 insertions(+), 1 deletion(-)
diff --git a/drivers/media/platform/rockchip/rga/Makefile b/drivers/media/platform/rockchip/rga/Makefile
index 1bbecdc3d8df2..7326a548f3dc7 100644
--- a/drivers/media/platform/rockchip/rga/Makefile
+++ b/drivers/media/platform/rockchip/rga/Makefile
@@ -1,4 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
-rockchip-rga-objs := rga.o rga-hw.o rga-buf.o
+rockchip-rga-objs := rga.o rga-hw.o rga3-hw.o rga-buf.o
obj-$(CONFIG_VIDEO_ROCKCHIP_RGA) += rockchip-rga.o
diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c
index fd92ecf408e0c..5007f2a025b1d 100644
--- a/drivers/media/platform/rockchip/rga/rga.c
+++ b/drivers/media/platform/rockchip/rga/rga.c
@@ -910,6 +910,10 @@ static const struct of_device_id rockchip_rga_match[] = {
.compatible = "rockchip,rk3399-rga",
.data = &rga2_hw,
},
+ {
+ .compatible = "rockchip,rk3588-rga3",
+ .data = &rga3_hw,
+ },
{},
};
diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h
index 849b96392b780..bb225549db86e 100644
--- a/drivers/media/platform/rockchip/rga/rga.h
+++ b/drivers/media/platform/rockchip/rga/rga.h
@@ -166,5 +166,6 @@ static inline bool rga_has_internal_iommu(const struct rockchip_rga *rga)
}
extern const struct rga_hw rga2_hw;
+extern const struct rga_hw rga3_hw;
#endif
diff --git a/drivers/media/platform/rockchip/rga/rga3-hw.c b/drivers/media/platform/rockchip/rga/rga3-hw.c
new file mode 100644
index 0000000000000..09c617019d552
--- /dev/null
+++ b/drivers/media/platform/rockchip/rga/rga3-hw.c
@@ -0,0 +1,507 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025-2026 Pengutronix e.K.
+ * Author: Sven Püschel <s.pueschel@pengutronix.de>
+ */
+
+#include <linux/pm_runtime.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/printk.h>
+
+#include <media/v4l2-common.h>
+
+#include "rga3-hw.h"
+#include "rga.h"
+
+static unsigned int rga3_get_scaling(unsigned int src, unsigned int dst)
+{
+ /*
+ * RGA3 scaling factor calculation as described in chapter 5.4.7 Resize
+ * of the TRM Part 2. The resulting scaling factor is a 16-bit value
+ * and therefore normalized with 2^16.
+ *
+ * While the TRM also mentions (dst-1)/(src-1) for the up-scaling case,
+ * it didn't work as the value always exceeds 16 bit. Flipping the
+ * factors results in a correct up-scaling. This is possible as the
+ * RGA3 has the RGA3_WIN_SCALE_XXX_UP bit to determine if it does
+ * an up or downscale.
+ *
+ * The scaling factor can potentially cause a slightly larger scaling
+ * (e.g. 1/2px larger scale and then cropped to the destination size).
+ * This can be seen when scaling 128x128px RGBA to 256x256px RGBA.
+ * The RGA2 scaling factor calculation (without the various +/-1
+ * doesn't work for the RGA3. It's assumed that this is an hardware
+ * accuracy limitation, as the vendor kernel driver uses the same
+ * scaling factor calculation.
+ *
+ * With a scaling factor of 1.0 the calculation technically also
+ * overflows 16 bit. This isn't relevant, as in this case the
+ * RGA3_WIN_SCALE_XXX_BYPASS bit completely skips the scaling operation.
+ */
+ if (dst > src) {
+ if (((src - 1) << 16) % (dst - 1) == 0)
+ return ((src - 1) << 16) / (dst - 1) - 1;
+ else
+ return ((src - 1) << 16) / (dst - 1);
+ } else {
+ return ((dst - 1) << 16) / (src - 1) + 1;
+ }
+}
+
+/*
+ * Check if the given format can be captured, as the RGA3 doesn't support all
+ * input formats also on it's output.
+ */
+static bool rga3_can_capture(const struct rga3_fmt *fmt)
+{
+ return fmt->hw_format <= RGA3_COLOR_FMT_LAST_OUTPUT;
+}
+
+/*
+ * Map the transformations to the RGA3 command buffer.
+ * Currently this is just the scaling settings and a fixed alpha value.
+ */
+static void rga3_cmd_set_trans_info(struct rga_ctx *ctx)
+{
+ u32 *cmd = ctx->cmdbuf_virt;
+ unsigned int src_h, src_w, dst_h, dst_w;
+ unsigned int reg;
+ u16 hor_scl_fac, ver_scl_fac;
+ const struct rga3_fmt *in = ctx->in.fmt;
+
+ /* Support basic input cropping to support 1088px inputs */
+ src_h = ctx->in.crop.height;
+ src_w = ctx->in.crop.width;
+ dst_h = ctx->out.pix.height;
+ dst_w = ctx->out.pix.width;
+
+ reg = RGA3_WIN0_RD_CTRL - RGA3_FIRST_CMD_REG;
+ cmd[reg >> 2] |= FIELD_PREP(RGA3_WIN_SCALE_HOR_UP, dst_w > src_w)
+ | FIELD_PREP(RGA3_WIN_SCALE_HOR_BYPASS, dst_w == src_w)
+ | FIELD_PREP(RGA3_WIN_SCALE_VER_UP, dst_h > src_h)
+ | FIELD_PREP(RGA3_WIN_SCALE_VER_BYPASS, dst_h == src_h);
+
+ hor_scl_fac = rga3_get_scaling(src_w, dst_w);
+ ver_scl_fac = rga3_get_scaling(src_h, dst_h);
+ reg = RGA3_WIN0_SCL_FAC - RGA3_FIRST_CMD_REG;
+ cmd[reg >> 2] = FIELD_PREP(RGA3_SCALE_HOR_FAC, hor_scl_fac)
+ | FIELD_PREP(RGA3_SCALE_VER_FAC, ver_scl_fac);
+
+ if (v4l2_format_info(in->fourcc)->has_alpha) {
+ /* copy alpha from input */
+ reg = RGA3_OVLP_TOP_ALPHA - RGA3_FIRST_CMD_REG;
+ cmd[reg >> 2] = FIELD_PREP(RGA3_ALPHA_SELECT_MODE, 1)
+ | FIELD_PREP(RGA3_ALPHA_BLEND_MODE, 1);
+ reg = RGA3_OVLP_BOT_ALPHA - RGA3_FIRST_CMD_REG;
+ cmd[reg >> 2] = FIELD_PREP(RGA3_ALPHA_SELECT_MODE, 1)
+ | FIELD_PREP(RGA3_ALPHA_BLEND_MODE, 1);
+ } else {
+ /* just use a 255 alpha value */
+ reg = RGA3_OVLP_TOP_CTRL - RGA3_FIRST_CMD_REG;
+ cmd[reg >> 2] = FIELD_PREP(RGA3_OVLP_GLOBAL_ALPHA, 0xff)
+ | FIELD_PREP(RGA3_OVLP_COLOR_MODE, 1);
+ reg = RGA3_OVLP_BOT_CTRL - RGA3_FIRST_CMD_REG;
+ cmd[reg >> 2] = FIELD_PREP(RGA3_OVLP_GLOBAL_ALPHA, 0xff)
+ | FIELD_PREP(RGA3_OVLP_COLOR_MODE, 1);
+ }
+}
+
+static void rga3_cmd_set_win0_addr(struct rga_ctx *ctx,
+ const struct rga_addrs *addrs)
+{
+ u32 *cmd = ctx->cmdbuf_virt;
+ unsigned int reg;
+
+ reg = RGA3_WIN0_Y_BASE - RGA3_FIRST_CMD_REG;
+ cmd[reg >> 2] = addrs->y_addr;
+ reg = RGA3_WIN0_U_BASE - RGA3_FIRST_CMD_REG;
+ cmd[reg >> 2] = addrs->u_addr;
+}
+
+static void rga3_cmd_set_wr_addr(struct rga_ctx *ctx,
+ const struct rga_addrs *addrs)
+{
+ u32 *cmd = ctx->cmdbuf_virt;
+ unsigned int reg;
+
+ reg = RGA3_WR_Y_BASE - RGA3_FIRST_CMD_REG;
+ cmd[reg >> 2] = addrs->y_addr;
+ reg = RGA3_WR_U_BASE - RGA3_FIRST_CMD_REG;
+ cmd[reg >> 2] = addrs->u_addr;
+}
+
+/* Map the input pixel format to win0 of the comamnd buffer. */
+static void rga3_cmd_set_win0_format(struct rga_ctx *ctx)
+{
+ u32 *cmd = ctx->cmdbuf_virt;
+ const struct rga3_fmt *in = ctx->in.fmt;
+ const struct rga3_fmt *out = ctx->out.fmt;
+ const struct v4l2_format_info *in_fmt, *out_fmt;
+ unsigned int act_h, act_w, src_h, src_w;
+ bool r2y, y2r;
+ u8 rd_format;
+ const struct v4l2_pix_format_mplane *csc_pix;
+ u8 csc_mode;
+ unsigned int reg;
+
+ act_h = ctx->in.pix.height;
+ act_w = ctx->in.pix.width;
+ /* Support basic input cropping to support 1088px inputs */
+ src_h = ctx->in.crop.height;
+ src_w = ctx->in.crop.width;
+
+ in_fmt = v4l2_format_info(in->fourcc);
+ out_fmt = v4l2_format_info(out->fourcc);
+ r2y = v4l2_is_format_rgb(in_fmt) && v4l2_is_format_yuv(out_fmt);
+ y2r = v4l2_is_format_yuv(in_fmt) && v4l2_is_format_rgb(out_fmt);
+
+ /* The Hardware only supports formats with 1/2 planes */
+ if (in_fmt->comp_planes == 2)
+ rd_format = RGA3_RDWR_FORMAT_SEMI_PLANAR;
+ else
+ rd_format = RGA3_RDWR_FORMAT_INTERLEAVED;
+
+ /* set pixel format and CSC */
+ csc_pix = r2y ? &ctx->out.pix : &ctx->in.pix;
+ switch (csc_pix->ycbcr_enc) {
+ case V4L2_YCBCR_ENC_BT2020:
+ csc_mode = RGA3_WIN_CSC_MODE_BT2020_L;
+ break;
+ case V4L2_YCBCR_ENC_709:
+ csc_mode = RGA3_WIN_CSC_MODE_BT709_L;
+ break;
+ default: /* should be fixed to BT601 in adjust_and_map_format */
+ if (csc_pix->quantization == V4L2_QUANTIZATION_LIM_RANGE)
+ csc_mode = RGA3_WIN_CSC_MODE_BT601_L;
+ else
+ csc_mode = RGA3_WIN_CSC_MODE_BT601_F;
+ break;
+ }
+
+ reg = RGA3_WIN0_RD_CTRL - RGA3_FIRST_CMD_REG;
+ cmd[reg >> 2] |= FIELD_PREP(RGA3_WIN_ENABLE, 1)
+ | FIELD_PREP(RGA3_WIN_PIC_FORMAT, in->hw_format)
+ | FIELD_PREP(RGA3_WIN_YC_SWAP, in->yc_swap)
+ | FIELD_PREP(RGA3_WIN_RBUV_SWAP, in->rbuv_swap)
+ | FIELD_PREP(RGA3_WIN_RD_FORMAT, rd_format)
+ | FIELD_PREP(RGA3_WIN_R2Y, r2y)
+ | FIELD_PREP(RGA3_WIN_Y2R, y2r)
+ | FIELD_PREP(RGA3_WIN_CSC_MODE, csc_mode);
+
+ /* set stride */
+ reg = RGA3_WIN0_VIR_STRIDE - RGA3_FIRST_CMD_REG;
+ /* stride needs to be in words */
+ cmd[reg >> 2] = ctx->in.pix.plane_fmt[0].bytesperline >> 2;
+ reg = RGA3_WIN0_UV_VIR_STRIDE - RGA3_FIRST_CMD_REG;
+ /* The Hardware only supports formats with 1/2 planes */
+ if (ctx->in.pix.num_planes == 2)
+ cmd[reg >> 2] = ctx->in.pix.plane_fmt[1].bytesperline >> 2;
+ else
+ cmd[reg >> 2] = ctx->in.pix.plane_fmt[0].bytesperline >> 2;
+
+ /* set size */
+ reg = RGA3_WIN0_ACT_SIZE - RGA3_FIRST_CMD_REG;
+ cmd[reg >> 2] = FIELD_PREP(RGA3_WIDTH, act_w)
+ | FIELD_PREP(RGA3_HEIGHT, act_h);
+ reg = RGA3_WIN0_SRC_SIZE - RGA3_FIRST_CMD_REG;
+ cmd[reg >> 2] = FIELD_PREP(RGA3_WIDTH, src_w)
+ | FIELD_PREP(RGA3_HEIGHT, src_h);
+}
+
+/* Map the output pixel format to the command buffer */
+static void rga3_cmd_set_wr_format(struct rga_ctx *ctx)
+{
+ u32 *cmd = ctx->cmdbuf_virt;
+ const struct rga3_fmt *out = ctx->out.fmt;
+ const struct v4l2_format_info *out_fmt;
+ unsigned int dst_h, dst_w;
+ u8 wr_format;
+ unsigned int reg;
+
+ dst_h = ctx->out.pix.height;
+ dst_w = ctx->out.pix.width;
+
+ out_fmt = v4l2_format_info(out->fourcc);
+
+ /* The Hardware only supports formats with 1/2 planes */
+ if (out_fmt->comp_planes == 2)
+ wr_format = RGA3_RDWR_FORMAT_SEMI_PLANAR;
+ else
+ wr_format = RGA3_RDWR_FORMAT_INTERLEAVED;
+
+ /* set pixel format */
+ reg = RGA3_WR_CTRL - RGA3_FIRST_CMD_REG;
+ cmd[reg >> 2] = FIELD_PREP(RGA3_WR_PIC_FORMAT, out->hw_format)
+ | FIELD_PREP(RGA3_WR_YC_SWAP, out->yc_swap)
+ | FIELD_PREP(RGA3_WR_RBUV_SWAP, out->rbuv_swap)
+ | FIELD_PREP(RGA3_WR_FORMAT, wr_format)
+ /* Use the max value to avoid limiting the write speed */
+ | FIELD_PREP(RGA3_WR_SW_OUTSTANDING_MAX, 63);
+
+ /* set stride */
+ reg = RGA3_WR_VIR_STRIDE - RGA3_FIRST_CMD_REG;
+ /* stride needs to be in words */
+ cmd[reg >> 2] = ctx->out.pix.plane_fmt[0].bytesperline >> 2;
+ reg = RGA3_WR_PL_VIR_STRIDE - RGA3_FIRST_CMD_REG;
+ /* The Hardware only supports formats with 1/2 planes */
+ if (ctx->out.pix.num_planes == 2)
+ cmd[reg >> 2] = ctx->out.pix.plane_fmt[1].bytesperline >> 2;
+ else
+ cmd[reg >> 2] = ctx->out.pix.plane_fmt[0].bytesperline >> 2;
+
+ /* Set size.
+ * As two inputs are not supported, we don't use win1.
+ * Therefore only set the size for win0.
+ */
+ reg = RGA3_WIN0_DST_SIZE - RGA3_FIRST_CMD_REG;
+ cmd[reg >> 2] = FIELD_PREP(RGA3_WIDTH, dst_w)
+ | FIELD_PREP(RGA3_HEIGHT, dst_h);
+}
+
+static void rga3_hw_setup_cmdbuf(struct rga_ctx *ctx)
+{
+ memset(ctx->cmdbuf_virt, 0, RGA3_CMDBUF_SIZE);
+
+ rga3_cmd_set_win0_format(ctx);
+ rga3_cmd_set_trans_info(ctx);
+ rga3_cmd_set_wr_format(ctx);
+}
+
+static void rga3_hw_start(struct rockchip_rga *rga,
+ struct rga_vb_buffer *src, struct rga_vb_buffer *dst)
+{
+ struct rga_ctx *ctx = rga->curr;
+
+ rga3_cmd_set_win0_addr(ctx, &src->dma_addrs);
+ rga3_cmd_set_wr_addr(ctx, &dst->dma_addrs);
+
+ rga_write(rga, RGA3_CMD_ADDR, ctx->cmdbuf_phy);
+
+ /* sync CMD buf for RGA */
+ dma_sync_single_for_device(rga->dev, ctx->cmdbuf_phy,
+ PAGE_SIZE, DMA_BIDIRECTIONAL);
+
+ /* set to master mode and start the conversion */
+ rga_write(rga, RGA3_SYS_CTRL,
+ FIELD_PREP(RGA3_CMD_MODE, RGA3_CMD_MODE_MASTER));
+ rga_write(rga, RGA3_INT_EN, FIELD_PREP(RGA3_INT_FRM_DONE, 1));
+ rga_write(rga, RGA3_CMD_CTRL,
+ FIELD_PREP(RGA3_CMD_LINE_START_PULSE, 1));
+}
+
+static bool rga3_handle_irq(struct rockchip_rga *rga)
+{
+ u32 intr;
+
+ intr = rga_read(rga, RGA3_INT_RAW);
+ /* clear all interrupts */
+ rga_write(rga, RGA3_INT_CLR, intr);
+
+ return FIELD_GET(RGA3_INT_FRM_DONE, intr);
+}
+
+static void rga3_get_version(struct rockchip_rga *rga)
+{
+ u32 version = rga_read(rga, RGA3_VERSION_NUM);
+
+ rga->version.major = FIELD_GET(RGA3_VERSION_NUM_MAJOR, version);
+ rga->version.minor = FIELD_GET(RGA3_VERSION_NUM_MINOR, version);
+}
+
+static struct rga3_fmt rga3_formats[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_RGB24,
+ .hw_format = RGA3_COLOR_FMT_BGR888,
+ .rbuv_swap = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_BGR24,
+ .hw_format = RGA3_COLOR_FMT_BGR888,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_ABGR32,
+ .hw_format = RGA3_COLOR_FMT_BGRA8888,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_RGBA32,
+ .hw_format = RGA3_COLOR_FMT_BGRA8888,
+ .rbuv_swap = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_XBGR32,
+ .hw_format = RGA3_COLOR_FMT_BGRA8888,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_RGBX32,
+ .hw_format = RGA3_COLOR_FMT_BGRA8888,
+ .rbuv_swap = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_RGB565,
+ .hw_format = RGA3_COLOR_FMT_BGR565,
+ .rbuv_swap = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV12M,
+ .hw_format = RGA3_COLOR_FMT_YUV420,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV12,
+ .hw_format = RGA3_COLOR_FMT_YUV420,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV21M,
+ .hw_format = RGA3_COLOR_FMT_YUV420,
+ .rbuv_swap = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV21,
+ .hw_format = RGA3_COLOR_FMT_YUV420,
+ .rbuv_swap = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV16M,
+ .hw_format = RGA3_COLOR_FMT_YUV422,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV16,
+ .hw_format = RGA3_COLOR_FMT_YUV422,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV61M,
+ .hw_format = RGA3_COLOR_FMT_YUV422,
+ .rbuv_swap = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV61,
+ .hw_format = RGA3_COLOR_FMT_YUV422,
+ .rbuv_swap = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .hw_format = RGA3_COLOR_FMT_YUV422,
+ .yc_swap = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_YVYU,
+ .hw_format = RGA3_COLOR_FMT_YUV422,
+ .yc_swap = 1,
+ .rbuv_swap = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_UYVY,
+ .hw_format = RGA3_COLOR_FMT_YUV422,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_VYUY,
+ .hw_format = RGA3_COLOR_FMT_YUV422,
+ .rbuv_swap = 1,
+ },
+ /* Input only formats last to keep rga3_enum_format simple */
+ {
+ .fourcc = V4L2_PIX_FMT_ARGB32,
+ .hw_format = RGA3_COLOR_FMT_ABGR8888,
+ .rbuv_swap = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_BGRA32,
+ .hw_format = RGA3_COLOR_FMT_ABGR8888,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_XRGB32,
+ .hw_format = RGA3_COLOR_FMT_ABGR8888,
+ .rbuv_swap = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_BGRX32,
+ .hw_format = RGA3_COLOR_FMT_ABGR8888,
+ },
+};
+
+static int rga3_enum_format(struct v4l2_fmtdesc *f)
+{
+ struct rga3_fmt *fmt;
+
+ if (f->index >= ARRAY_SIZE(rga3_formats))
+ return -EINVAL;
+
+ fmt = &rga3_formats[f->index];
+ if (V4L2_TYPE_IS_CAPTURE(f->type) && !rga3_can_capture(fmt))
+ return -EINVAL;
+
+ f->pixelformat = fmt->fourcc;
+ return 0;
+}
+
+static void *rga3_adjust_and_map_format(struct rga_ctx *ctx,
+ struct v4l2_pix_format_mplane *format,
+ bool is_output)
+{
+ unsigned int i;
+ const struct v4l2_format_info *format_info;
+ const struct v4l2_pix_format_mplane *other_format;
+ const struct v4l2_format_info *other_format_info;
+
+ if (!format)
+ return &rga3_formats[0];
+
+ format_info = v4l2_format_info(format->pixelformat);
+ other_format = is_output ? &ctx->in.pix : &ctx->out.pix;
+ other_format_info = v4l2_format_info(other_format->pixelformat);
+
+ if ((v4l2_is_format_rgb(format_info) &&
+ v4l2_is_format_yuv(other_format_info)) ||
+ (v4l2_is_format_yuv(format_info) &&
+ v4l2_is_format_rgb(other_format_info))) {
+ /*
+ * The RGA3 only supports BT601, BT709 and BT2020 RGB<->YUV conversions
+ * Additionally BT709 and BT2020 only support limited range YUV.
+ */
+ switch (format->ycbcr_enc) {
+ case V4L2_YCBCR_ENC_601:
+ /* supports full and limited range */
+ break;
+ case V4L2_YCBCR_ENC_709:
+ case V4L2_YCBCR_ENC_BT2020:
+ format->quantization = V4L2_QUANTIZATION_LIM_RANGE;
+ break;
+ default:
+ format->ycbcr_enc = V4L2_YCBCR_ENC_601;
+ format->quantization = V4L2_QUANTIZATION_FULL_RANGE;
+ break;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(rga3_formats); i++) {
+ if (!is_output && !rga3_can_capture(&rga3_formats[i]))
+ continue;
+
+ if (rga3_formats[i].fourcc == format->pixelformat)
+ return &rga3_formats[i];
+ }
+
+ format->pixelformat = rga3_formats[0].fourcc;
+ return &rga3_formats[0];
+}
+
+const struct rga_hw rga3_hw = {
+ .card_type = "rga3",
+ .has_internal_iommu = false,
+ .cmdbuf_size = RGA3_CMDBUF_SIZE,
+ .min_width = RGA3_MIN_WIDTH,
+ .min_height = RGA3_MIN_HEIGHT,
+ /* use output size, as it's a bit smaller than the input size */
+ .max_width = RGA3_MAX_OUTPUT_WIDTH,
+ .max_height = RGA3_MAX_OUTPUT_HEIGHT,
+ .max_scaling_factor = RGA3_MAX_SCALING_FACTOR,
+ .stride_alignment = 16,
+ .features = 0,
+
+ .setup_cmdbuf = rga3_hw_setup_cmdbuf,
+ .start = rga3_hw_start,
+ .handle_irq = rga3_handle_irq,
+ .get_version = rga3_get_version,
+ .enum_format = rga3_enum_format,
+ .adjust_and_map_format = rga3_adjust_and_map_format,
+};
diff --git a/drivers/media/platform/rockchip/rga/rga3-hw.h b/drivers/media/platform/rockchip/rga/rga3-hw.h
new file mode 100644
index 0000000000000..85fd8ae257ecd
--- /dev/null
+++ b/drivers/media/platform/rockchip/rga/rga3-hw.h
@@ -0,0 +1,192 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) Pengutronix e.K.
+ * Author: Sven Püschel <s.pueschel@pengutronix.de>
+ */
+#ifndef __RGA3_HW_H__
+#define __RGA3_HW_H__
+
+#include <linux/bits.h>
+#include <linux/types.h>
+
+#define RGA3_CMDBUF_SIZE 0xb8
+
+#define RGA3_MIN_WIDTH 128
+#define RGA3_MIN_HEIGHT 128
+#define RGA3_MAX_INPUT_WIDTH (8192 - 16)
+#define RGA3_MAX_INPUT_HEIGHT (8192 - 16)
+#define RGA3_MAX_OUTPUT_WIDTH (8192 - 64)
+#define RGA3_MAX_OUTPUT_HEIGHT (8192 - 64)
+#define RGA3_MAX_SCALING_FACTOR 8
+#define RGA3_RESET_TIMEOUT 1000
+
+/* Registers address */
+/* sys reg */
+#define RGA3_SYS_CTRL 0x000
+#define RGA3_CMD_CTRL 0x004
+#define RGA3_CMD_ADDR 0x008
+#define RGA3_MI_GROUP_CTRL 0x00c
+#define RGA3_ARQOS_CTRL 0x010
+#define RGA3_VERSION_NUM 0x018
+#define RGA3_VERSION_TIM 0x01c
+#define RGA3_INT_EN 0x020
+#define RGA3_INT_RAW 0x024
+#define RGA3_INT_MSK 0x028
+#define RGA3_INT_CLR 0x02c
+#define RGA3_RO_SRST 0x030
+#define RGA3_STATUS0 0x034
+#define RGA3_SCAN_CNT 0x038
+#define RGA3_CMD_STATE 0x040
+
+/* cmd reg */
+#define RGA3_WIN0_RD_CTRL 0x100
+#define RGA3_FIRST_CMD_REG RGA3_WIN0_RD_CTRL
+#define RGA3_WIN0_Y_BASE 0x110
+#define RGA3_WIN0_U_BASE 0x114
+#define RGA3_WIN0_V_BASE 0x118
+#define RGA3_WIN0_VIR_STRIDE 0x11c
+#define RGA3_WIN0_FBC_OFF 0x120
+#define RGA3_WIN0_SRC_SIZE 0x124
+#define RGA3_WIN0_ACT_OFF 0x128
+#define RGA3_WIN0_ACT_SIZE 0x12c
+#define RGA3_WIN0_DST_SIZE 0x130
+#define RGA3_WIN0_SCL_FAC 0x134
+#define RGA3_WIN0_UV_VIR_STRIDE 0x138
+#define RGA3_WIN1_RD_CTRL 0x140
+#define RGA3_WIN1_Y_BASE 0x150
+#define RGA3_WIN1_U_BASE 0x154
+#define RGA3_WIN1_V_BASE 0x158
+#define RGA3_WIN1_VIR_STRIDE 0x15c
+#define RGA3_WIN1_FBC_OFF 0x160
+#define RGA3_WIN1_SRC_SIZE 0x164
+#define RGA3_WIN1_ACT_OFF 0x168
+#define RGA3_WIN1_ACT_SIZE 0x16c
+#define RGA3_WIN1_DST_SIZE 0x170
+#define RGA3_WIN1_SCL_FAC 0x174
+#define RGA3_WIN1_UV_VIR_STRIDE 0x178
+#define RGA3_OVLP_CTRL 0x180
+#define RGA3_OVLP_OFF 0x184
+#define RGA3_OVLP_TOP_KEY_MIN 0x188
+#define RGA3_OVLP_TOP_KEY_MAX 0x18c
+#define RGA3_OVLP_TOP_CTRL 0x190
+#define RGA3_OVLP_BOT_CTRL 0x194
+#define RGA3_OVLP_TOP_ALPHA 0x198
+#define RGA3_OVLP_BOT_ALPHA 0x19c
+#define RGA3_WR_CTRL 0x1a0
+#define RGA3_WR_FBCE_CTRL 0x1a4
+#define RGA3_WR_VIR_STRIDE 0x1a8
+#define RGA3_WR_PL_VIR_STRIDE 0x1ac
+#define RGA3_WR_Y_BASE 0x1b0
+#define RGA3_WR_U_BASE 0x1b4
+#define RGA3_WR_V_BASE 0x1b8
+
+/* Registers value */
+#define RGA3_COLOR_FMT_YUV420 0x0
+#define RGA3_COLOR_FMT_YUV422 0x1
+#define RGA3_COLOR_FMT_YUV420_10B 0x2
+#define RGA3_COLOR_FMT_YUV422_10B 0x3
+/*
+ * Use memory ordering names
+ * instead of the datasheet naming RGB formats in big endian order
+ */
+#define RGA3_COLOR_FMT_BGR565 0x4
+#define RGA3_COLOR_FMT_BGR888 0x5
+#define RGA3_COLOR_FMT_FIRST_HAS_ALPHA RGA3_COLOR_FMT_BGRA8888
+#define RGA3_COLOR_FMT_BGRA8888 0x6
+#define RGA3_COLOR_FMT_LAST_OUTPUT RGA3_COLOR_FMT_BGRA8888
+/* the following are only supported as inputs */
+#define RGA3_COLOR_FMT_ABGR8888 0x7
+/*
+ * the following seem to be unnecessary,
+ * as they can be achieved with RB swaps
+ */
+#define RGA3_COLOR_FMT_RGBA8888 0x8
+#define RGA3_COLOR_FMT_ARGB8888 0x9
+
+#define RGA3_RDWR_FORMAT_SEMI_PLANAR 0x1
+#define RGA3_RDWR_FORMAT_INTERLEAVED 0x2
+
+#define RGA3_CMD_MODE_MASTER 0x1
+
+#define RGA3_WIN_CSC_MODE_BT601_L 0x0
+#define RGA3_WIN_CSC_MODE_BT709_L 0x1
+#define RGA3_WIN_CSC_MODE_BT601_F 0x2
+#define RGA3_WIN_CSC_MODE_BT2020_L 0x3
+
+/* RGA masks */
+/* SYS_CTRL */
+#define RGA3_CCLK_SRESET BIT(4)
+#define RGA3_ACLK_SRESET BIT(3)
+#define RGA3_CMD_MODE BIT(1)
+
+/* CMD_CTRL */
+#define RGA3_CMD_LINE_START_PULSE BIT(0)
+
+/* VERSION_NUM */
+#define RGA3_VERSION_NUM_MAJOR GENMASK(31, 28)
+#define RGA3_VERSION_NUM_MINOR GENMASK(27, 20)
+
+/* INT_* */
+#define RGA3_INT_FRM_DONE BIT(0)
+#define RGA3_INT_DMA_READ_BUS_ERR BIT(2)
+#define RGA3_INT_WIN0_FBC_DEC_ERR BIT(5)
+#define RGA3_INT_WIN0_HOR_ERR BIT(6)
+#define RGA3_INT_WIN0_VER_ERR BIT(7)
+#define RGA3_INT_WR_VER_ERR BIT(13)
+#define RGA3_INT_WR_HOR_ERR BIT(14)
+#define RGA3_INT_WR_BUS_ERR BIT(15)
+#define RGA3_INT_WIN0_IN_FIFO_WR_ERR BIT(16)
+#define RGA3_INT_WIN0_IN_FIFO_RD_ERR BIT(17)
+#define RGA3_INT_WIN0_HOR_FIFO_WR_ERR BIT(18)
+#define RGA3_INT_WIN0_HOR_FIFO_RD_ERR BIT(19)
+#define RGA3_INT_WIN0_VER_FIFO_WR_ERR BIT(20)
+#define RGA3_INT_WIN0_VER_FIFO_RD_ERR BIT(21)
+
+/* RO_SRST */
+#define RGA3_RO_SRST_DONE GENMASK(5, 0)
+
+/* *_SIZE */
+#define RGA3_HEIGHT GENMASK(28, 16)
+#define RGA3_WIDTH GENMASK(12, 0)
+
+/* SCL_FAC */
+#define RGA3_SCALE_VER_FAC GENMASK(31, 16)
+#define RGA3_SCALE_HOR_FAC GENMASK(15, 0)
+
+/* WINx_CTRL */
+#define RGA3_WIN_CSC_MODE GENMASK(27, 26)
+#define RGA3_WIN_R2Y BIT(25)
+#define RGA3_WIN_Y2R BIT(24)
+#define RGA3_WIN_SCALE_VER_UP BIT(23)
+#define RGA3_WIN_SCALE_VER_BYPASS BIT(22)
+#define RGA3_WIN_SCALE_HOR_UP BIT(21)
+#define RGA3_WIN_SCALE_HOR_BYPASS BIT(20)
+#define RGA3_WIN_YC_SWAP BIT(13)
+#define RGA3_WIN_RBUV_SWAP BIT(12)
+#define RGA3_WIN_RD_FORMAT GENMASK(9, 8)
+#define RGA3_WIN_PIC_FORMAT GENMASK(7, 4)
+#define RGA3_WIN_ENABLE BIT(0)
+
+/* COLOR_CTRL */
+#define RGA3_OVLP_GLOBAL_ALPHA GENMASK(23, 16)
+#define RGA3_OVLP_COLOR_MODE BIT(0)
+
+/* ALPHA_CTRL */
+#define RGA3_ALPHA_SELECT_MODE BIT(4)
+#define RGA3_ALPHA_BLEND_MODE GENMASK(3, 2)
+
+/* WR_CTRL */
+#define RGA3_WR_YC_SWAP BIT(20)
+#define RGA3_WR_SW_OUTSTANDING_MAX GENMASK(18, 13)
+#define RGA3_WR_RBUV_SWAP BIT(12)
+#define RGA3_WR_FORMAT GENMASK(9, 8)
+#define RGA3_WR_PIC_FORMAT GENMASK(7, 4)
+
+struct rga3_fmt {
+ u32 fourcc;
+ u8 hw_format;
+ bool rbuv_swap;
+ bool yc_swap;
+};
+
+#endif
--
2.54.0
^ permalink raw reply related
* [PATCH v5 11/29] media: rockchip: rga: move hw specific parts to a dedicated struct
From: Sven Püschel @ 2026-04-28 9:00 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, Sven Püschel,
Nicolas Dufresne
In-Reply-To: <20260428-spu-rga3-v5-0-eb7f5d019d86@pengutronix.de>
In preparation for the RGA3 unit, move RGA2 specific parts from rga.c
to rga-hw.c and create a struct to reference the RGA2 specific functions
and formats. This also allows to remove the rga-hw.h reference from the
include list of the rga driver.
Also document the command finish interrupt with a dedicated define.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/platform/rockchip/rga/rga-hw.c | 166 ++++++++++++++++++++-
drivers/media/platform/rockchip/rga/rga-hw.h | 5 +-
drivers/media/platform/rockchip/rga/rga.c | 211 +++++----------------------
drivers/media/platform/rockchip/rga/rga.h | 23 ++-
4 files changed, 227 insertions(+), 178 deletions(-)
diff --git a/drivers/media/platform/rockchip/rga/rga-hw.c b/drivers/media/platform/rockchip/rga/rga-hw.c
index ec6c17504ca15..40498796507e0 100644
--- a/drivers/media/platform/rockchip/rga/rga-hw.c
+++ b/drivers/media/platform/rockchip/rga/rga-hw.c
@@ -437,8 +437,8 @@ static void rga_cmd_set(struct rga_ctx *ctx,
PAGE_SIZE, DMA_BIDIRECTIONAL);
}
-void rga_hw_start(struct rockchip_rga *rga,
- struct rga_vb_buffer *src, struct rga_vb_buffer *dst)
+static void rga_hw_start(struct rockchip_rga *rga,
+ struct rga_vb_buffer *src, struct rga_vb_buffer *dst)
{
struct rga_ctx *ctx = rga->curr;
@@ -452,3 +452,165 @@ void rga_hw_start(struct rockchip_rga *rga,
rga_write(rga, RGA_CMD_CTRL, 0x1);
}
+
+static bool rga_handle_irq(struct rockchip_rga *rga)
+{
+ int intr;
+
+ intr = rga_read(rga, RGA_INT) & 0xf;
+
+ rga_mod(rga, RGA_INT, intr << 4, 0xf << 4);
+
+ return intr & RGA_INT_COMMAND_FINISHED;
+}
+
+static void rga_get_version(struct rockchip_rga *rga)
+{
+ rga->version.major = (rga_read(rga, RGA_VERSION_INFO) >> 24) & 0xFF;
+ rga->version.minor = (rga_read(rga, RGA_VERSION_INFO) >> 20) & 0x0F;
+}
+
+static struct rga_fmt formats[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_ARGB32,
+ .color_swap = RGA_COLOR_ALPHA_SWAP,
+ .hw_format = RGA_COLOR_FMT_ABGR8888,
+ .depth = 32,
+ .y_div = 1,
+ .x_div = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_ABGR32,
+ .color_swap = RGA_COLOR_RB_SWAP,
+ .hw_format = RGA_COLOR_FMT_ABGR8888,
+ .depth = 32,
+ .y_div = 1,
+ .x_div = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_XBGR32,
+ .color_swap = RGA_COLOR_RB_SWAP,
+ .hw_format = RGA_COLOR_FMT_XBGR8888,
+ .depth = 32,
+ .y_div = 1,
+ .x_div = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_RGB24,
+ .color_swap = RGA_COLOR_NONE_SWAP,
+ .hw_format = RGA_COLOR_FMT_RGB888,
+ .depth = 24,
+ .y_div = 1,
+ .x_div = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_BGR24,
+ .color_swap = RGA_COLOR_RB_SWAP,
+ .hw_format = RGA_COLOR_FMT_RGB888,
+ .depth = 24,
+ .y_div = 1,
+ .x_div = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_ARGB444,
+ .color_swap = RGA_COLOR_RB_SWAP,
+ .hw_format = RGA_COLOR_FMT_ABGR4444,
+ .depth = 16,
+ .y_div = 1,
+ .x_div = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_ARGB555,
+ .color_swap = RGA_COLOR_RB_SWAP,
+ .hw_format = RGA_COLOR_FMT_ABGR1555,
+ .depth = 16,
+ .y_div = 1,
+ .x_div = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_RGB565,
+ .color_swap = RGA_COLOR_RB_SWAP,
+ .hw_format = RGA_COLOR_FMT_BGR565,
+ .depth = 16,
+ .y_div = 1,
+ .x_div = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV21,
+ .color_swap = RGA_COLOR_UV_SWAP,
+ .hw_format = RGA_COLOR_FMT_YUV420SP,
+ .depth = 12,
+ .y_div = 2,
+ .x_div = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV61,
+ .color_swap = RGA_COLOR_UV_SWAP,
+ .hw_format = RGA_COLOR_FMT_YUV422SP,
+ .depth = 16,
+ .y_div = 1,
+ .x_div = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV12,
+ .color_swap = RGA_COLOR_NONE_SWAP,
+ .hw_format = RGA_COLOR_FMT_YUV420SP,
+ .depth = 12,
+ .y_div = 2,
+ .x_div = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV12M,
+ .color_swap = RGA_COLOR_NONE_SWAP,
+ .hw_format = RGA_COLOR_FMT_YUV420SP,
+ .depth = 12,
+ .y_div = 2,
+ .x_div = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV16,
+ .color_swap = RGA_COLOR_NONE_SWAP,
+ .hw_format = RGA_COLOR_FMT_YUV422SP,
+ .depth = 16,
+ .y_div = 1,
+ .x_div = 1,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_YUV420,
+ .color_swap = RGA_COLOR_NONE_SWAP,
+ .hw_format = RGA_COLOR_FMT_YUV420P,
+ .depth = 12,
+ .y_div = 2,
+ .x_div = 2,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_YUV422P,
+ .color_swap = RGA_COLOR_NONE_SWAP,
+ .hw_format = RGA_COLOR_FMT_YUV422P,
+ .depth = 16,
+ .y_div = 1,
+ .x_div = 2,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_YVU420,
+ .color_swap = RGA_COLOR_UV_SWAP,
+ .hw_format = RGA_COLOR_FMT_YUV420P,
+ .depth = 12,
+ .y_div = 2,
+ .x_div = 2,
+ },
+};
+
+const struct rga_hw rga2_hw = {
+ .formats = formats,
+ .num_formats = ARRAY_SIZE(formats),
+ .cmdbuf_size = RGA_CMDBUF_SIZE,
+ .min_width = MIN_WIDTH,
+ .max_width = MAX_WIDTH,
+ .min_height = MIN_HEIGHT,
+ .max_height = MAX_HEIGHT,
+
+ .start = rga_hw_start,
+ .handle_irq = rga_handle_irq,
+ .get_version = rga_get_version,
+};
diff --git a/drivers/media/platform/rockchip/rga/rga-hw.h b/drivers/media/platform/rockchip/rga/rga-hw.h
index 2b8537a5fd0d7..c2e34be751939 100644
--- a/drivers/media/platform/rockchip/rga/rga-hw.h
+++ b/drivers/media/platform/rockchip/rga/rga-hw.h
@@ -15,9 +15,6 @@
#define MIN_WIDTH 34
#define MIN_HEIGHT 34
-#define DEFAULT_WIDTH 100
-#define DEFAULT_HEIGHT 100
-
#define RGA_TIMEOUT 500
/* Registers address */
@@ -178,6 +175,8 @@
#define RGA_ALPHA_COLOR_NORMAL 0
#define RGA_ALPHA_COLOR_MULTIPLY_CAL 1
+#define RGA_INT_COMMAND_FINISHED 4
+
/* Registers union */
union rga_mode_ctrl {
unsigned int val;
diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c
index 8c34f73d69764..f599c992829dd 100644
--- a/drivers/media/platform/rockchip/rga/rga.c
+++ b/drivers/media/platform/rockchip/rga/rga.c
@@ -25,7 +25,6 @@
#include <media/videobuf2-dma-sg.h>
#include <media/videobuf2-v4l2.h>
-#include "rga-hw.h"
#include "rga.h"
static int debug;
@@ -47,7 +46,7 @@ static void device_run(void *prv)
dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
- rga_hw_start(rga, vb_to_rga(src), vb_to_rga(dst));
+ rga->hw->start(rga, vb_to_rga(src), vb_to_rga(dst));
spin_unlock_irqrestore(&rga->ctrl_lock, flags);
}
@@ -55,13 +54,8 @@ static void device_run(void *prv)
static irqreturn_t rga_isr(int irq, void *prv)
{
struct rockchip_rga *rga = prv;
- int intr;
- intr = rga_read(rga, RGA_INT) & 0xf;
-
- rga_mod(rga, RGA_INT, intr << 4, 0xf << 4);
-
- if (intr & 0x04) {
+ if (rga->hw->handle_irq(rga)) {
struct vb2_v4l2_buffer *src, *dst;
struct rga_ctx *ctx = rga->curr;
@@ -184,158 +178,17 @@ static int rga_setup_ctrls(struct rga_ctx *ctx)
return 0;
}
-static struct rga_fmt formats[] = {
- {
- .fourcc = V4L2_PIX_FMT_ARGB32,
- .color_swap = RGA_COLOR_ALPHA_SWAP,
- .hw_format = RGA_COLOR_FMT_ABGR8888,
- .depth = 32,
- .y_div = 1,
- .x_div = 1,
- },
- {
- .fourcc = V4L2_PIX_FMT_ABGR32,
- .color_swap = RGA_COLOR_RB_SWAP,
- .hw_format = RGA_COLOR_FMT_ABGR8888,
- .depth = 32,
- .y_div = 1,
- .x_div = 1,
- },
- {
- .fourcc = V4L2_PIX_FMT_XBGR32,
- .color_swap = RGA_COLOR_RB_SWAP,
- .hw_format = RGA_COLOR_FMT_XBGR8888,
- .depth = 32,
- .y_div = 1,
- .x_div = 1,
- },
- {
- .fourcc = V4L2_PIX_FMT_RGB24,
- .color_swap = RGA_COLOR_NONE_SWAP,
- .hw_format = RGA_COLOR_FMT_RGB888,
- .depth = 24,
- .y_div = 1,
- .x_div = 1,
- },
- {
- .fourcc = V4L2_PIX_FMT_BGR24,
- .color_swap = RGA_COLOR_RB_SWAP,
- .hw_format = RGA_COLOR_FMT_RGB888,
- .depth = 24,
- .y_div = 1,
- .x_div = 1,
- },
- {
- .fourcc = V4L2_PIX_FMT_ARGB444,
- .color_swap = RGA_COLOR_RB_SWAP,
- .hw_format = RGA_COLOR_FMT_ABGR4444,
- .depth = 16,
- .y_div = 1,
- .x_div = 1,
- },
- {
- .fourcc = V4L2_PIX_FMT_ARGB555,
- .color_swap = RGA_COLOR_RB_SWAP,
- .hw_format = RGA_COLOR_FMT_ABGR1555,
- .depth = 16,
- .y_div = 1,
- .x_div = 1,
- },
- {
- .fourcc = V4L2_PIX_FMT_RGB565,
- .color_swap = RGA_COLOR_RB_SWAP,
- .hw_format = RGA_COLOR_FMT_BGR565,
- .depth = 16,
- .y_div = 1,
- .x_div = 1,
- },
- {
- .fourcc = V4L2_PIX_FMT_NV21,
- .color_swap = RGA_COLOR_UV_SWAP,
- .hw_format = RGA_COLOR_FMT_YUV420SP,
- .depth = 12,
- .y_div = 2,
- .x_div = 1,
- },
- {
- .fourcc = V4L2_PIX_FMT_NV61,
- .color_swap = RGA_COLOR_UV_SWAP,
- .hw_format = RGA_COLOR_FMT_YUV422SP,
- .depth = 16,
- .y_div = 1,
- .x_div = 1,
- },
- {
- .fourcc = V4L2_PIX_FMT_NV12,
- .color_swap = RGA_COLOR_NONE_SWAP,
- .hw_format = RGA_COLOR_FMT_YUV420SP,
- .depth = 12,
- .y_div = 2,
- .x_div = 1,
- },
- {
- .fourcc = V4L2_PIX_FMT_NV12M,
- .color_swap = RGA_COLOR_NONE_SWAP,
- .hw_format = RGA_COLOR_FMT_YUV420SP,
- .depth = 12,
- .y_div = 2,
- .x_div = 1,
- },
- {
- .fourcc = V4L2_PIX_FMT_NV16,
- .color_swap = RGA_COLOR_NONE_SWAP,
- .hw_format = RGA_COLOR_FMT_YUV422SP,
- .depth = 16,
- .y_div = 1,
- .x_div = 1,
- },
- {
- .fourcc = V4L2_PIX_FMT_YUV420,
- .color_swap = RGA_COLOR_NONE_SWAP,
- .hw_format = RGA_COLOR_FMT_YUV420P,
- .depth = 12,
- .y_div = 2,
- .x_div = 2,
- },
- {
- .fourcc = V4L2_PIX_FMT_YUV422P,
- .color_swap = RGA_COLOR_NONE_SWAP,
- .hw_format = RGA_COLOR_FMT_YUV422P,
- .depth = 16,
- .y_div = 1,
- .x_div = 2,
- },
- {
- .fourcc = V4L2_PIX_FMT_YVU420,
- .color_swap = RGA_COLOR_UV_SWAP,
- .hw_format = RGA_COLOR_FMT_YUV420P,
- .depth = 12,
- .y_div = 2,
- .x_div = 2,
- },
-};
-
-#define NUM_FORMATS ARRAY_SIZE(formats)
-
-static struct rga_fmt *rga_fmt_find(u32 pixelformat)
+static struct rga_fmt *rga_fmt_find(struct rockchip_rga *rga, u32 pixelformat)
{
unsigned int i;
- for (i = 0; i < NUM_FORMATS; i++) {
- if (formats[i].fourcc == pixelformat)
- return &formats[i];
+ for (i = 0; i < rga->hw->num_formats; i++) {
+ if (rga->hw->formats[i].fourcc == pixelformat)
+ return &rga->hw->formats[i];
}
return NULL;
}
-static struct rga_frame def_frame = {
- .crop.left = 0,
- .crop.top = 0,
- .crop.width = DEFAULT_WIDTH,
- .crop.height = DEFAULT_HEIGHT,
- .fmt = &formats[0],
-};
-
struct rga_frame *rga_get_frame(struct rga_ctx *ctx, enum v4l2_buf_type type)
{
if (V4L2_TYPE_IS_OUTPUT(type))
@@ -350,6 +203,18 @@ static int rga_open(struct file *file)
struct rockchip_rga *rga = video_drvdata(file);
struct rga_ctx *ctx = NULL;
int ret = 0;
+ u32 def_width = clamp(DEFAULT_WIDTH, rga->hw->min_width, rga->hw->max_width);
+ u32 def_height = clamp(DEFAULT_HEIGHT, rga->hw->min_height, rga->hw->max_height);
+ struct rga_frame def_frame = {
+ .crop.left = 0,
+ .crop.top = 0,
+ .crop.width = def_width,
+ .crop.height = def_height,
+ .fmt = &rga->hw->formats[0],
+ };
+
+ def_frame.stride = (def_width * def_frame.fmt->depth) >> 3;
+ def_frame.size = def_frame.stride * def_height;
ctx = kzalloc_obj(*ctx);
if (!ctx)
@@ -360,9 +225,9 @@ static int rga_open(struct file *file)
ctx->out = def_frame;
v4l2_fill_pixfmt_mp(&ctx->in.pix,
- ctx->in.fmt->fourcc, DEFAULT_WIDTH, DEFAULT_HEIGHT);
+ ctx->in.fmt->fourcc, def_width, def_height);
v4l2_fill_pixfmt_mp(&ctx->out.pix,
- ctx->out.fmt->fourcc, DEFAULT_WIDTH, DEFAULT_HEIGHT);
+ ctx->out.fmt->fourcc, def_width, def_height);
if (mutex_lock_interruptible(&rga->mutex)) {
kfree(ctx);
@@ -429,12 +294,13 @@ vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap)
static int vidioc_enum_fmt(struct file *file, void *priv, struct v4l2_fmtdesc *f)
{
+ struct rockchip_rga *rga = video_drvdata(file);
struct rga_fmt *fmt;
- if (f->index >= NUM_FORMATS)
+ if (f->index >= rga->hw->num_formats)
return -EINVAL;
- fmt = &formats[f->index];
+ fmt = &rga->hw->formats[f->index];
f->pixelformat = fmt->fourcc;
if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
@@ -469,6 +335,7 @@ static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f)
{
struct v4l2_pix_format_mplane *pix_fmt = &f->fmt.pix_mp;
struct rga_ctx *ctx = file_to_rga_ctx(file);
+ const struct rga_hw *hw = ctx->rga->hw;
struct rga_fmt *fmt;
if (V4L2_TYPE_IS_CAPTURE(f->type)) {
@@ -487,14 +354,14 @@ static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f)
pix_fmt->xfer_func = frm->pix.xfer_func;
}
- fmt = rga_fmt_find(pix_fmt->pixelformat);
+ fmt = rga_fmt_find(ctx->rga, pix_fmt->pixelformat);
if (!fmt)
- fmt = &formats[0];
+ fmt = &hw->formats[0];
pix_fmt->width = clamp(pix_fmt->width,
- (u32)MIN_WIDTH, (u32)MAX_WIDTH);
+ hw->min_width, hw->max_width);
pix_fmt->height = clamp(pix_fmt->height,
- (u32)MIN_HEIGHT, (u32)MAX_HEIGHT);
+ hw->min_height, hw->max_height);
v4l2_fill_pixfmt_mp(pix_fmt, fmt->fourcc, pix_fmt->width, pix_fmt->height);
pix_fmt->field = V4L2_FIELD_NONE;
@@ -529,7 +396,7 @@ static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
frm->size = 0;
for (i = 0; i < pix_fmt->num_planes; i++)
frm->size += pix_fmt->plane_fmt[i].sizeimage;
- frm->fmt = rga_fmt_find(pix_fmt->pixelformat);
+ frm->fmt = rga_fmt_find(rga, pix_fmt->pixelformat);
frm->stride = pix_fmt->plane_fmt[0].bytesperline;
/*
@@ -660,7 +527,7 @@ static int vidioc_s_selection(struct file *file, void *priv,
if (s->r.left + s->r.width > f->pix.width ||
s->r.top + s->r.height > f->pix.height ||
- s->r.width < MIN_WIDTH || s->r.height < MIN_HEIGHT) {
+ s->r.width < rga->hw->min_width || s->r.height < rga->hw->min_height) {
v4l2_dbg(debug, 1, &rga->v4l2_dev, "unsupported crop value.\n");
return -EINVAL;
}
@@ -770,6 +637,10 @@ static int rga_probe(struct platform_device *pdev)
if (!rga)
return -ENOMEM;
+ rga->hw = of_device_get_match_data(&pdev->dev);
+ if (!rga->hw)
+ return dev_err_probe(&pdev->dev, -ENODEV, "failed to get match data\n");
+
rga->dev = &pdev->dev;
spin_lock_init(&rga->ctrl_lock);
mutex_init(&rga->mutex);
@@ -833,8 +704,7 @@ static int rga_probe(struct platform_device *pdev)
if (ret < 0)
goto rel_m2m;
- rga->version.major = (rga_read(rga, RGA_VERSION_INFO) >> 24) & 0xFF;
- rga->version.minor = (rga_read(rga, RGA_VERSION_INFO) >> 20) & 0x0F;
+ rga->hw->get_version(rga);
v4l2_info(&rga->v4l2_dev, "HW Version: 0x%02x.%02x\n",
rga->version.major, rga->version.minor);
@@ -842,7 +712,7 @@ static int rga_probe(struct platform_device *pdev)
pm_runtime_put(rga->dev);
/* Create CMD buffer */
- rga->cmdbuf_virt = dma_alloc_attrs(rga->dev, RGA_CMDBUF_SIZE,
+ rga->cmdbuf_virt = dma_alloc_attrs(rga->dev, rga->hw->cmdbuf_size,
&rga->cmdbuf_phy, GFP_KERNEL,
DMA_ATTR_WRITE_COMBINE);
if (!rga->cmdbuf_virt) {
@@ -850,9 +720,6 @@ static int rga_probe(struct platform_device *pdev)
goto rel_m2m;
}
- def_frame.stride = (DEFAULT_WIDTH * def_frame.fmt->depth) >> 3;
- def_frame.size = def_frame.stride * DEFAULT_HEIGHT;
-
ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1);
if (ret) {
v4l2_err(&rga->v4l2_dev, "Failed to register video device\n");
@@ -865,7 +732,7 @@ static int rga_probe(struct platform_device *pdev)
return 0;
free_dma:
- dma_free_attrs(rga->dev, RGA_CMDBUF_SIZE, rga->cmdbuf_virt,
+ dma_free_attrs(rga->dev, rga->hw->cmdbuf_size, rga->cmdbuf_virt,
rga->cmdbuf_phy, DMA_ATTR_WRITE_COMBINE);
rel_m2m:
v4l2_m2m_release(rga->m2m_dev);
@@ -883,7 +750,7 @@ static void rga_remove(struct platform_device *pdev)
{
struct rockchip_rga *rga = platform_get_drvdata(pdev);
- dma_free_attrs(rga->dev, RGA_CMDBUF_SIZE, rga->cmdbuf_virt,
+ dma_free_attrs(rga->dev, rga->hw->cmdbuf_size, rga->cmdbuf_virt,
rga->cmdbuf_phy, DMA_ATTR_WRITE_COMBINE);
v4l2_info(&rga->v4l2_dev, "Removing\n");
@@ -919,9 +786,11 @@ static const struct dev_pm_ops rga_pm = {
static const struct of_device_id rockchip_rga_match[] = {
{
.compatible = "rockchip,rk3288-rga",
+ .data = &rga2_hw,
},
{
.compatible = "rockchip,rk3399-rga",
+ .data = &rga2_hw,
},
{},
};
diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h
index c4a3905a48f0d..640e510285341 100644
--- a/drivers/media/platform/rockchip/rga/rga.h
+++ b/drivers/media/platform/rockchip/rga/rga.h
@@ -14,6 +14,9 @@
#define RGA_NAME "rockchip-rga"
+#define DEFAULT_WIDTH 100
+#define DEFAULT_HEIGHT 100
+
struct rga_fmt {
u32 fourcc;
int depth;
@@ -68,6 +71,8 @@ static inline struct rga_ctx *file_to_rga_ctx(struct file *filp)
return container_of(file_to_v4l2_fh(filp), struct rga_ctx, fh);
}
+struct rga_hw;
+
struct rockchip_rga {
struct v4l2_device v4l2_dev;
struct v4l2_m2m_dev *m2m_dev;
@@ -88,6 +93,8 @@ struct rockchip_rga {
struct rga_ctx *curr;
dma_addr_t cmdbuf_phy;
void *cmdbuf_virt;
+
+ const struct rga_hw *hw;
};
struct rga_addr_offset {
@@ -138,7 +145,19 @@ static inline void rga_mod(struct rockchip_rga *rga, u32 reg, u32 val, u32 mask)
rga_write(rga, reg, temp);
};
-void rga_hw_start(struct rockchip_rga *rga,
- struct rga_vb_buffer *src, struct rga_vb_buffer *dst);
+struct rga_hw {
+ struct rga_fmt *formats;
+ u32 num_formats;
+ size_t cmdbuf_size;
+ u32 min_width, min_height;
+ u32 max_width, max_height;
+
+ void (*start)(struct rockchip_rga *rga,
+ struct rga_vb_buffer *src, struct rga_vb_buffer *dst);
+ bool (*handle_irq)(struct rockchip_rga *rga);
+ void (*get_version)(struct rockchip_rga *rga);
+};
+
+extern const struct rga_hw rga2_hw;
#endif
--
2.54.0
^ permalink raw reply related
* [PATCH v5 23/29] media: rockchip: rga: remove size from rga_frame
From: Sven Püschel @ 2026-04-28 9:00 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, Sven Püschel,
Nicolas Dufresne
In-Reply-To: <20260428-spu-rga3-v5-0-eb7f5d019d86@pengutronix.de>
The size member is only used for the mmu page table mapping.
Therefore avoid storing the value and instead only calculate it
in place. This also avoids the calculation entirely when an external
iommu is used.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/platform/rockchip/rga/rga-buf.c | 6 +++++-
drivers/media/platform/rockchip/rga/rga.c | 8 ++------
drivers/media/platform/rockchip/rga/rga.h | 1 -
3 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/drivers/media/platform/rockchip/rga/rga-buf.c b/drivers/media/platform/rockchip/rga/rga-buf.c
index 4e82ca1a5e8d9..c0cc885ba58a8 100644
--- a/drivers/media/platform/rockchip/rga/rga-buf.c
+++ b/drivers/media/platform/rockchip/rga/rga-buf.c
@@ -79,6 +79,8 @@ static int rga_buf_init(struct vb2_buffer *vb)
struct rockchip_rga *rga = ctx->rga;
struct rga_frame *f = rga_get_frame(ctx, vb->vb2_queue->type);
size_t n_desc = 0;
+ u32 size = 0;
+ u8 i;
if (IS_ERR(f))
return PTR_ERR(f);
@@ -86,7 +88,9 @@ static int rga_buf_init(struct vb2_buffer *vb)
if (!rga_has_internal_iommu(rga))
return 0;
- n_desc = DIV_ROUND_UP(f->size, PAGE_SIZE);
+ for (i = 0; i < f->pix.num_planes; i++)
+ size += f->pix.plane_fmt[i].sizeimage;
+ n_desc = DIV_ROUND_UP(size, PAGE_SIZE);
rbuf->n_desc = n_desc;
rbuf->dma_desc = dma_alloc_coherent(rga->dev,
diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c
index eb7b211a99e66..2695bc5077a03 100644
--- a/drivers/media/platform/rockchip/rga/rga.c
+++ b/drivers/media/platform/rockchip/rga/rga.c
@@ -221,7 +221,6 @@ static int rga_open(struct file *file)
};
def_frame.stride = (def_width * def_frame.fmt->depth) >> 3;
- def_frame.size = def_frame.stride * def_height;
ctx = kzalloc_obj(*ctx);
if (!ctx)
@@ -459,9 +458,6 @@ static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
frm = rga_get_frame(ctx, f->type);
if (IS_ERR(frm))
return PTR_ERR(frm);
- frm->size = 0;
- for (i = 0; i < pix_fmt->num_planes; i++)
- frm->size += pix_fmt->plane_fmt[i].sizeimage;
frm->fmt = rga_fmt_find(rga, pix_fmt->pixelformat);
frm->stride = pix_fmt->plane_fmt[0].bytesperline;
@@ -485,10 +481,10 @@ static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
frm->pix = *pix_fmt;
v4l2_dbg(debug, 1, &rga->v4l2_dev,
- "[%s] fmt - %p4cc %dx%d (stride %d, sizeimage %d)\n",
+ "[%s] fmt - %p4cc %dx%d (stride %d)\n",
V4L2_TYPE_IS_OUTPUT(f->type) ? "OUTPUT" : "CAPTURE",
&frm->fmt->fourcc, pix_fmt->width, pix_fmt->height,
- frm->stride, frm->size);
+ frm->stride);
for (i = 0; i < pix_fmt->num_planes; i++) {
v4l2_dbg(debug, 1, &rga->v4l2_dev,
diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h
index 95fa7fd1c509a..2838fc7785f72 100644
--- a/drivers/media/platform/rockchip/rga/rga.h
+++ b/drivers/media/platform/rockchip/rga/rga.h
@@ -34,7 +34,6 @@ struct rga_frame {
/* Variables that can calculated once and reused */
u32 stride;
- u32 size;
};
struct rga_dma_desc {
--
2.54.0
^ permalink raw reply related
* [PATCH v5 29/29] arm64: dts: rockchip: add rga3 dt nodes
From: Sven Püschel @ 2026-04-28 9:01 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, Sven Püschel
In-Reply-To: <20260428-spu-rga3-v5-0-eb7f5d019d86@pengutronix.de>
Add devicetree nodes for the RGA3 (Raster Graphics Acceleration 3)
peripheral in the RK3588.
The existing rga node refers to the RGA2-Enhanced peripheral. The RK3588
contains one RGA2-Enhanced core and two RGA3 cores. Both feature a similar
functionality of scaling, cropping and rotating of up to two input
images into one output image. Key differences of the RGA3 are:
- supports 10bit YUV output formats
- supports 8x8 tiles and FBCD as inputs and outputs
- supports BT2020 color space conversion
- max output resolution of (8192-64)x(8192-64)
- MMU can map up to 32G DDR RAM
- fully planar formats (3 planes) are not supported
- max scale up/down factor of 8 (RGA2 allows up to 16)
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 44 +++++++++++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
index 4fb8888c281c8..c8ac419f51cf9 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
@@ -1273,6 +1273,50 @@ rga: rga@fdb80000 {
power-domains = <&power RK3588_PD_VDPU>;
};
+ rga3_core0: rga@fdb60000 {
+ compatible = "rockchip,rk3588-rga3";
+ reg = <0x0 0xfdb60000 0x0 0x200>;
+ interrupts = <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH 0>;
+ clocks = <&cru ACLK_RGA3_0>, <&cru HCLK_RGA3_0>, <&cru CLK_RGA3_0_CORE>;
+ clock-names = "aclk", "hclk", "sclk";
+ resets = <&cru SRST_RGA3_0_CORE>, <&cru SRST_A_RGA3_0>, <&cru SRST_H_RGA3_0>;
+ reset-names = "core", "axi", "ahb";
+ power-domains = <&power RK3588_PD_RGA30>;
+ iommus = <&rga3_0_mmu>;
+ };
+
+ rga3_0_mmu: iommu@fdb60f00 {
+ compatible = "rockchip,rk3588-iommu", "rockchip,rk3568-iommu";
+ reg = <0x0 0xfdb60f00 0x0 0x100>;
+ interrupts = <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH 0>;
+ clocks = <&cru ACLK_RGA3_0>, <&cru HCLK_RGA3_0>;
+ clock-names = "aclk", "iface";
+ #iommu-cells = <0>;
+ power-domains = <&power RK3588_PD_RGA30>;
+ };
+
+ rga3_core1: rga@fdb70000 {
+ compatible = "rockchip,rk3588-rga3";
+ reg = <0x0 0xfdb70000 0x0 0x200>;
+ interrupts = <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH 0>;
+ clocks = <&cru ACLK_RGA3_1>, <&cru HCLK_RGA3_1>, <&cru CLK_RGA3_1_CORE>;
+ clock-names = "aclk", "hclk", "sclk";
+ resets = <&cru SRST_RGA3_1_CORE>, <&cru SRST_A_RGA3_1>, <&cru SRST_H_RGA3_1>;
+ reset-names = "core", "axi", "ahb";
+ power-domains = <&power RK3588_PD_RGA31>;
+ iommus = <&rga3_1_mmu>;
+ };
+
+ rga3_1_mmu: iommu@fdb70f00 {
+ compatible = "rockchip,rk3588-iommu", "rockchip,rk3568-iommu";
+ reg = <0x0 0xfdb70f00 0x0 0x100>;
+ interrupts = <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH 0>;
+ clocks = <&cru ACLK_RGA3_1>, <&cru HCLK_RGA3_1>;
+ clock-names = "aclk", "iface";
+ #iommu-cells = <0>;
+ power-domains = <&power RK3588_PD_RGA31>;
+ };
+
vepu121_0: video-codec@fdba0000 {
compatible = "rockchip,rk3588-vepu121";
reg = <0x0 0xfdba0000 0x0 0x800>;
--
2.54.0
^ permalink raw reply related
* [PATCH v5 14/29] media: rockchip: rga: move cmdbuf to rga_ctx
From: Sven Püschel @ 2026-04-28 9:00 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, Sven Püschel,
Nicolas Dufresne
In-Reply-To: <20260428-spu-rga3-v5-0-eb7f5d019d86@pengutronix.de>
Move the command buffer to the rga_ctx struct in preparation to reuse
an already prepared command buffer. This allows to split the command
buffer setup in a further commit to setup a template for the command
buffer at streamon and only update the buffer addresses in device_run
and trigger the command stream. No sync point is added, as one command
buffer should only be used for one conversion at a time.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/platform/rockchip/rga/rga-hw.c | 28 +++++++---------
drivers/media/platform/rockchip/rga/rga.c | 48 ++++++++++++++++------------
drivers/media/platform/rockchip/rga/rga.h | 5 +--
3 files changed, 41 insertions(+), 40 deletions(-)
diff --git a/drivers/media/platform/rockchip/rga/rga-hw.c b/drivers/media/platform/rockchip/rga/rga-hw.c
index 17f7a67c0b4bb..9881c14f908d5 100644
--- a/drivers/media/platform/rockchip/rga/rga-hw.c
+++ b/drivers/media/platform/rockchip/rga/rga-hw.c
@@ -122,8 +122,7 @@ static struct rga_addr_offset *rga_lookup_draw_pos(struct
static void rga_cmd_set_src_addr(struct rga_ctx *ctx, dma_addr_t dma_addr)
{
- struct rockchip_rga *rga = ctx->rga;
- u32 *dest = rga->cmdbuf_virt;
+ u32 *dest = ctx->cmdbuf_virt;
unsigned int reg;
reg = RGA_MMU_SRC_BASE - RGA_MODE_BASE_REG;
@@ -135,8 +134,7 @@ static void rga_cmd_set_src_addr(struct rga_ctx *ctx, dma_addr_t dma_addr)
static void rga_cmd_set_src1_addr(struct rga_ctx *ctx, dma_addr_t dma_addr)
{
- struct rockchip_rga *rga = ctx->rga;
- u32 *dest = rga->cmdbuf_virt;
+ u32 *dest = ctx->cmdbuf_virt;
unsigned int reg;
reg = RGA_MMU_SRC1_BASE - RGA_MODE_BASE_REG;
@@ -148,8 +146,7 @@ static void rga_cmd_set_src1_addr(struct rga_ctx *ctx, dma_addr_t dma_addr)
static void rga_cmd_set_dst_addr(struct rga_ctx *ctx, dma_addr_t dma_addr)
{
- struct rockchip_rga *rga = ctx->rga;
- u32 *dest = rga->cmdbuf_virt;
+ u32 *dest = ctx->cmdbuf_virt;
unsigned int reg;
reg = RGA_MMU_DST_BASE - RGA_MODE_BASE_REG;
@@ -162,7 +159,7 @@ static void rga_cmd_set_dst_addr(struct rga_ctx *ctx, dma_addr_t dma_addr)
static void rga_cmd_set_trans_info(struct rga_ctx *ctx)
{
struct rockchip_rga *rga = ctx->rga;
- u32 *dest = rga->cmdbuf_virt;
+ u32 *dest = ctx->cmdbuf_virt;
unsigned int scale_dst_w, scale_dst_h;
unsigned int src_h, src_w, dst_h, dst_w;
union rga_src_info src_info;
@@ -322,8 +319,7 @@ static void rga_cmd_set_src_info(struct rga_ctx *ctx,
struct rga_addr_offset *offset)
{
struct rga_corners_addr_offset src_offsets;
- struct rockchip_rga *rga = ctx->rga;
- u32 *dest = rga->cmdbuf_virt;
+ u32 *dest = ctx->cmdbuf_virt;
unsigned int src_h, src_w, src_x, src_y;
src_h = ctx->in.crop.height;
@@ -350,8 +346,7 @@ static void rga_cmd_set_dst_info(struct rga_ctx *ctx,
{
struct rga_addr_offset *dst_offset;
struct rga_corners_addr_offset offsets;
- struct rockchip_rga *rga = ctx->rga;
- u32 *dest = rga->cmdbuf_virt;
+ u32 *dest = ctx->cmdbuf_virt;
unsigned int dst_h, dst_w, dst_x, dst_y;
unsigned int mir_mode = 0;
unsigned int rot_mode = 0;
@@ -397,8 +392,7 @@ static void rga_cmd_set_dst_info(struct rga_ctx *ctx,
static void rga_cmd_set_mode(struct rga_ctx *ctx)
{
- struct rockchip_rga *rga = ctx->rga;
- u32 *dest = rga->cmdbuf_virt;
+ u32 *dest = ctx->cmdbuf_virt;
union rga_mode_ctrl mode;
union rga_alpha_ctrl0 alpha_ctrl0;
union rga_alpha_ctrl1 alpha_ctrl1;
@@ -423,7 +417,7 @@ static void rga_cmd_set(struct rga_ctx *ctx,
{
struct rockchip_rga *rga = ctx->rga;
- memset(rga->cmdbuf_virt, 0, RGA_CMDBUF_SIZE);
+ memset(ctx->cmdbuf_virt, 0, RGA_CMDBUF_SIZE);
rga_cmd_set_src_addr(ctx, src->dma_desc_pa);
/*
@@ -439,11 +433,11 @@ static void rga_cmd_set(struct rga_ctx *ctx,
rga_cmd_set_dst_info(ctx, &dst->offset);
rga_cmd_set_trans_info(ctx);
- rga_write(rga, RGA_CMD_BASE, rga->cmdbuf_phy);
+ rga_write(rga, RGA_CMD_BASE, ctx->cmdbuf_phy);
/* sync CMD buf for RGA */
- dma_sync_single_for_device(rga->dev, rga->cmdbuf_phy,
- PAGE_SIZE, DMA_BIDIRECTIONAL);
+ dma_sync_single_for_device(rga->dev, ctx->cmdbuf_phy,
+ PAGE_SIZE, DMA_BIDIRECTIONAL);
}
static void rga_hw_start(struct rockchip_rga *rga,
diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c
index 77b8c7ab74274..bf6bbcbfc869b 100644
--- a/drivers/media/platform/rockchip/rga/rga.c
+++ b/drivers/media/platform/rockchip/rga/rga.c
@@ -219,6 +219,16 @@ static int rga_open(struct file *file)
ctx = kzalloc_obj(*ctx);
if (!ctx)
return -ENOMEM;
+
+ /* Create CMD buffer */
+ ctx->cmdbuf_virt = dma_alloc_attrs(rga->dev, rga->hw->cmdbuf_size,
+ &ctx->cmdbuf_phy, GFP_KERNEL,
+ DMA_ATTR_WRITE_COMBINE);
+ if (!ctx->cmdbuf_virt) {
+ ret = -ENOMEM;
+ goto rel_ctx;
+ }
+
ctx->rga = rga;
/* Set default formats */
ctx->in = def_frame;
@@ -230,15 +240,13 @@ static int rga_open(struct file *file)
ctx->out.fmt->fourcc, def_width, def_height);
if (mutex_lock_interruptible(&rga->mutex)) {
- kfree(ctx);
- return -ERESTARTSYS;
+ ret = -ERESTARTSYS;
+ goto rel_cmdbuf;
}
ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(rga->m2m_dev, ctx, &queue_init);
if (IS_ERR(ctx->fh.m2m_ctx)) {
ret = PTR_ERR(ctx->fh.m2m_ctx);
- mutex_unlock(&rga->mutex);
- kfree(ctx);
- return ret;
+ goto unlock_mutex;
}
v4l2_fh_init(&ctx->fh, video_devdata(file));
v4l2_fh_add(&ctx->fh, file);
@@ -252,6 +260,15 @@ static int rga_open(struct file *file)
mutex_unlock(&rga->mutex);
return 0;
+
+unlock_mutex:
+ mutex_unlock(&rga->mutex);
+rel_cmdbuf:
+ dma_free_attrs(rga->dev, rga->hw->cmdbuf_size, ctx->cmdbuf_virt,
+ ctx->cmdbuf_phy, DMA_ATTR_WRITE_COMBINE);
+rel_ctx:
+ kfree(ctx);
+ return ret;
}
static int rga_release(struct file *file)
@@ -266,6 +283,10 @@ static int rga_release(struct file *file)
v4l2_ctrl_handler_free(&ctx->ctrl_handler);
v4l2_fh_del(&ctx->fh, file);
v4l2_fh_exit(&ctx->fh);
+
+ dma_free_attrs(rga->dev, rga->hw->cmdbuf_size, ctx->cmdbuf_virt,
+ ctx->cmdbuf_phy, DMA_ATTR_WRITE_COMBINE);
+
kfree(ctx);
mutex_unlock(&rga->mutex);
@@ -720,19 +741,10 @@ static int rga_probe(struct platform_device *pdev)
pm_runtime_put(rga->dev);
- /* Create CMD buffer */
- rga->cmdbuf_virt = dma_alloc_attrs(rga->dev, rga->hw->cmdbuf_size,
- &rga->cmdbuf_phy, GFP_KERNEL,
- DMA_ATTR_WRITE_COMBINE);
- if (!rga->cmdbuf_virt) {
- ret = -ENOMEM;
- goto rel_m2m;
- }
-
ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1);
if (ret) {
v4l2_err(&rga->v4l2_dev, "Failed to register video device\n");
- goto free_dma;
+ goto rel_m2m;
}
v4l2_info(&rga->v4l2_dev, "Registered %s as /dev/%s\n",
@@ -740,9 +752,6 @@ static int rga_probe(struct platform_device *pdev)
return 0;
-free_dma:
- dma_free_attrs(rga->dev, rga->hw->cmdbuf_size, rga->cmdbuf_virt,
- rga->cmdbuf_phy, DMA_ATTR_WRITE_COMBINE);
rel_m2m:
v4l2_m2m_release(rga->m2m_dev);
rel_vdev:
@@ -759,9 +768,6 @@ static void rga_remove(struct platform_device *pdev)
{
struct rockchip_rga *rga = platform_get_drvdata(pdev);
- dma_free_attrs(rga->dev, rga->hw->cmdbuf_size, rga->cmdbuf_virt,
- rga->cmdbuf_phy, DMA_ATTR_WRITE_COMBINE);
-
v4l2_info(&rga->v4l2_dev, "Removing\n");
v4l2_m2m_release(rga->m2m_dev);
diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h
index 27b3c9b4f220c..04aeb7b429523 100644
--- a/drivers/media/platform/rockchip/rga/rga.h
+++ b/drivers/media/platform/rockchip/rga/rga.h
@@ -53,6 +53,9 @@ struct rga_ctx {
struct rga_frame out;
struct v4l2_ctrl_handler ctrl_handler;
+ void *cmdbuf_virt;
+ dma_addr_t cmdbuf_phy;
+
int osequence;
int csequence;
@@ -89,8 +92,6 @@ struct rockchip_rga {
spinlock_t ctrl_lock;
struct rga_ctx *curr;
- dma_addr_t cmdbuf_phy;
- void *cmdbuf_virt;
const struct rga_hw *hw;
};
--
2.54.0
^ permalink raw reply related
* [PATCH v5 05/29] media: v4l2-common: add v4l2_fill_pixfmt_mp_aligned helper
From: Sven Püschel @ 2026-04-28 9:00 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, Sven Püschel
In-Reply-To: <20260428-spu-rga3-v5-0-eb7f5d019d86@pengutronix.de>
Add a v4l2_fill_pixfmt_mp_aligned helper which allows the user to
specify a custom stride alignment in bytes. This is necessary for
hardware like the Rockchip RGA3, which requires the stride value to be
aligned to a 16 bytes boundary.
The code makes some assumptions about the v4l2 format to simplify the
calculation. They currently hold for all known v4l2 formats.
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/v4l2-core/v4l2-common.c | 51 ++++++++++++++++++++++++++---------
include/media/v4l2-common.h | 4 +++
2 files changed, 43 insertions(+), 12 deletions(-)
diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c
index 2b5ccedeb6841..d64d7e76c834d 100644
--- a/drivers/media/v4l2-core/v4l2-common.c
+++ b/drivers/media/v4l2-core/v4l2-common.c
@@ -431,14 +431,28 @@ static inline unsigned int v4l2_format_block_height(const struct v4l2_format_inf
}
static inline unsigned int v4l2_format_plane_stride(const struct v4l2_format_info *info, int plane,
- unsigned int width)
+ unsigned int width, u8 byte_alignment)
{
unsigned int hdiv = plane ? info->hdiv : 1;
unsigned int aligned_width =
ALIGN(width, v4l2_format_block_width(info, plane));
- return DIV_ROUND_UP(aligned_width, hdiv) *
- info->bpp[plane] / info->bpp_div[plane];
+ /*
+ * Formats with a single memory plane derive the stride of the
+ * other planes from the y stride. Due to chroma subsampling this
+ * may cause the other plane strides to break the byte_alignment.
+ * Increase the alignment if necessary to prevent this.
+ *
+ * It assumes the following format properties:
+ * - bpp_div[0] == bpp_div[1]
+ * - The multiplication factor doesn't differ between the non y planes
+ */
+ if (info->mem_planes == 1 && info->comp_planes > 1 && plane == 0)
+ byte_alignment *= info->hdiv * info->bpp[0] / info->bpp[1];
+
+ return ALIGN(DIV_ROUND_UP(aligned_width, hdiv) * info->bpp[plane] /
+ info->bpp_div[plane],
+ byte_alignment);
}
static inline unsigned int v4l2_format_plane_height(const struct v4l2_format_info *info, int plane,
@@ -452,9 +466,10 @@ static inline unsigned int v4l2_format_plane_height(const struct v4l2_format_inf
}
static inline unsigned int v4l2_format_plane_size(const struct v4l2_format_info *info, int plane,
- unsigned int width, unsigned int height)
+ unsigned int width, unsigned int height,
+ u8 stride_alignment)
{
- return v4l2_format_plane_stride(info, plane, width) *
+ return v4l2_format_plane_stride(info, plane, width, stride_alignment) *
v4l2_format_plane_height(info, plane, height);
}
@@ -475,8 +490,9 @@ void v4l2_apply_frmsize_constraints(u32 *width, u32 *height,
}
EXPORT_SYMBOL_GPL(v4l2_apply_frmsize_constraints);
-int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt,
- u32 pixelformat, u32 width, u32 height)
+int v4l2_fill_pixfmt_mp_aligned(struct v4l2_pix_format_mplane *pixfmt,
+ u32 pixelformat, u32 width, u32 height,
+ u8 stride_alignment)
{
const struct v4l2_format_info *info;
struct v4l2_plane_pix_format *plane;
@@ -493,23 +509,34 @@ int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt,
if (info->mem_planes == 1) {
plane = &pixfmt->plane_fmt[0];
- plane->bytesperline = v4l2_format_plane_stride(info, 0, width);
+ plane->bytesperline = v4l2_format_plane_stride(info, 0, width,
+ stride_alignment);
plane->sizeimage = 0;
for (i = 0; i < info->comp_planes; i++)
plane->sizeimage +=
- v4l2_format_plane_size(info, i, width, height);
+ v4l2_format_plane_size(info, i, width, height,
+ stride_alignment);
} else {
for (i = 0; i < info->comp_planes; i++) {
plane = &pixfmt->plane_fmt[i];
plane->bytesperline =
- v4l2_format_plane_stride(info, i, width);
+ v4l2_format_plane_stride(info, i, width,
+ stride_alignment);
plane->sizeimage = plane->bytesperline *
v4l2_format_plane_height(info, i, height);
}
}
return 0;
}
+EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt_mp_aligned);
+
+int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt,
+ u32 pixelformat, u32 width, u32 height)
+{
+ return v4l2_fill_pixfmt_mp_aligned(pixfmt, pixelformat,
+ width, height, 1);
+}
EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt_mp);
int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat,
@@ -529,12 +556,12 @@ int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat,
pixfmt->width = width;
pixfmt->height = height;
pixfmt->pixelformat = pixelformat;
- pixfmt->bytesperline = v4l2_format_plane_stride(info, 0, width);
+ pixfmt->bytesperline = v4l2_format_plane_stride(info, 0, width, 1);
pixfmt->sizeimage = 0;
for (i = 0; i < info->comp_planes; i++)
pixfmt->sizeimage +=
- v4l2_format_plane_size(info, i, width, height);
+ v4l2_format_plane_size(info, i, width, height, 1);
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt);
diff --git a/include/media/v4l2-common.h b/include/media/v4l2-common.h
index 401d8506c24b5..edd416178c333 100644
--- a/include/media/v4l2-common.h
+++ b/include/media/v4l2-common.h
@@ -558,6 +558,10 @@ int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat,
u32 width, u32 height);
int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt, u32 pixelformat,
u32 width, u32 height);
+/* @stride_alignment is a power of 2 value in bytes */
+int v4l2_fill_pixfmt_mp_aligned(struct v4l2_pix_format_mplane *pixfmt,
+ u32 pixelformat, u32 width, u32 height,
+ u8 stride_alignment);
/**
* v4l2_get_link_freq - Get link rate from transmitter
--
2.54.0
^ permalink raw reply related
* [PATCH v5 16/29] media: rockchip: rga: split flip and rotate into separate function
From: Sven Püschel @ 2026-04-28 9:00 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, Sven Püschel
In-Reply-To: <20260428-spu-rga3-v5-0-eb7f5d019d86@pengutronix.de>
Split the flip and rotate command configuration into a separate
function in preparation of filling the command stream at streamon.
As the userspace can change the flipping and rotation controls while
streaming, we have to update them with each new frame to prevent the
user being unable to change them while streaming.
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/platform/rockchip/rga/rga-hw.c | 57 +++++++++++++++++-----------
1 file changed, 34 insertions(+), 23 deletions(-)
diff --git a/drivers/media/platform/rockchip/rga/rga-hw.c b/drivers/media/platform/rockchip/rga/rga-hw.c
index dac3cb6aa17d3..6c1956b04f6ba 100644
--- a/drivers/media/platform/rockchip/rga/rga-hw.c
+++ b/drivers/media/platform/rockchip/rga/rga-hw.c
@@ -156,7 +156,38 @@ static void rga_cmd_set_dst_addr(struct rga_ctx *ctx, dma_addr_t dma_addr)
dest[reg >> 2] |= 0x7 << 8;
}
-static void rga_cmd_set_trans_info(struct rga_ctx *ctx)
+static void rga_cmd_set_flip_rotate_info(struct rga_ctx *ctx)
+{
+ u32 *dest = ctx->cmdbuf_virt;
+ union rga_src_info src_info;
+
+ src_info.val = dest[(RGA_SRC_INFO - RGA_MODE_BASE_REG) >> 2];
+
+ if (ctx->vflip)
+ src_info.data.mir_mode |= RGA_SRC_MIRR_MODE_X;
+
+ if (ctx->hflip)
+ src_info.data.mir_mode |= RGA_SRC_MIRR_MODE_Y;
+
+ switch (ctx->rotate) {
+ case 90:
+ src_info.data.rot_mode = RGA_SRC_ROT_MODE_90_DEGREE;
+ break;
+ case 180:
+ src_info.data.rot_mode = RGA_SRC_ROT_MODE_180_DEGREE;
+ break;
+ case 270:
+ src_info.data.rot_mode = RGA_SRC_ROT_MODE_270_DEGREE;
+ break;
+ default:
+ src_info.data.rot_mode = RGA_SRC_ROT_MODE_0_DEGREE;
+ break;
+ }
+
+ dest[(RGA_SRC_INFO - RGA_MODE_BASE_REG) >> 2] = src_info.val;
+}
+
+static void rga_cmd_set_format_scale_info(struct rga_ctx *ctx)
{
struct rockchip_rga *rga = ctx->rga;
u32 *dest = ctx->cmdbuf_virt;
@@ -219,27 +250,6 @@ static void rga_cmd_set_trans_info(struct rga_ctx *ctx)
}
}
- if (ctx->vflip)
- src_info.data.mir_mode |= RGA_SRC_MIRR_MODE_X;
-
- if (ctx->hflip)
- src_info.data.mir_mode |= RGA_SRC_MIRR_MODE_Y;
-
- switch (ctx->rotate) {
- case 90:
- src_info.data.rot_mode = RGA_SRC_ROT_MODE_90_DEGREE;
- break;
- case 180:
- src_info.data.rot_mode = RGA_SRC_ROT_MODE_180_DEGREE;
- break;
- case 270:
- src_info.data.rot_mode = RGA_SRC_ROT_MODE_270_DEGREE;
- break;
- default:
- src_info.data.rot_mode = RGA_SRC_ROT_MODE_0_DEGREE;
- break;
- }
-
/*
* Calculate the up/down scaling mode/factor.
*
@@ -431,7 +441,8 @@ static void rga_cmd_set(struct rga_ctx *ctx,
rga_cmd_set_src_info(ctx, &src->offset);
rga_cmd_set_dst_info(ctx, &dst->offset);
- rga_cmd_set_trans_info(ctx);
+ rga_cmd_set_format_scale_info(ctx);
+ rga_cmd_set_flip_rotate_info(ctx);
rga_write(rga, RGA_CMD_BASE, ctx->cmdbuf_phy);
--
2.54.0
^ permalink raw reply related
* [PATCH v5 02/29] media: v4l2-common: sort RGB formats in v4l2_format_info
From: Sven Püschel @ 2026-04-28 9:00 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, Sven Püschel,
Nicolas Dufresne
In-Reply-To: <20260428-spu-rga3-v5-0-eb7f5d019d86@pengutronix.de>
Sort the RGB formats in v4l2_format_info to match the format definitions
in include/uapi/linux/videodev2.h . Also introduce the same sections to
partition the list of formats and align the format info in each section.
The alignment of the 1 or 2 bytes RGB formats contains an additional
space in preparation of adding the missing formats to the list, as for
V4L2_PIX_FMT_ARGB555X an additional space is necessary.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/v4l2-core/v4l2-common.c | 54 +++++++++++++++++++----------------
1 file changed, 30 insertions(+), 24 deletions(-)
diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c
index 554c591e11133..49c1ec08e2eb3 100644
--- a/drivers/media/v4l2-core/v4l2-common.c
+++ b/drivers/media/v4l2-core/v4l2-common.c
@@ -245,33 +245,39 @@ EXPORT_SYMBOL_GPL(v4l2_s_parm_cap);
const struct v4l2_format_info *v4l2_format_info(u32 format)
{
static const struct v4l2_format_info formats[] = {
- /* RGB formats */
- { .format = V4L2_PIX_FMT_BGR24, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_RGB24, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_HSV24, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_BGR32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_XBGR32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_BGRX32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_RGB32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_XRGB32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_RGBX32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_HSV32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_ARGB32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_RGBA32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_ABGR32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_BGRA32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_RGB565, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_RGB565X, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_RGB555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_BGR666, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_BGR48_12, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_BGR48, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_RGB48, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_ABGR64_12, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 8, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
- { .format = V4L2_PIX_FMT_RGBA1010102, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ /* RGB formats (1 or 2 bytes per pixel) */
+ { .format = V4L2_PIX_FMT_RGB555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_RGB565, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_RGB565X, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+
+ /* RGB formats (3 or 4 bytes per pixel) */
+ { .format = V4L2_PIX_FMT_BGR666, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_BGR24, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_RGB24, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_BGR32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_ABGR32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_XBGR32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_BGRA32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_BGRX32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_RGB32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_RGBA32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_RGBX32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_ARGB32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_XRGB32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGBX1010102, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_RGBA1010102, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_ARGB2101010, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ /* RGB formats (6 or 8 bytes per pixel) */
+ { .format = V4L2_PIX_FMT_BGR48_12, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_BGR48, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_RGB48, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_ABGR64_12, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 8, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+
+ /* HSV formats */
+ { .format = V4L2_PIX_FMT_HSV24, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+ { .format = V4L2_PIX_FMT_HSV32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+
/* YUV packed formats */
{ .format = V4L2_PIX_FMT_YUYV, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_YVYU, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
--
2.54.0
^ permalink raw reply related
* [PATCH v5 24/29] media: rockchip: rga: remove stride from rga_frame
From: Sven Püschel @ 2026-04-28 9:00 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, Sven Püschel,
Nicolas Dufresne
In-Reply-To: <20260428-spu-rga3-v5-0-eb7f5d019d86@pengutronix.de>
Remove the stride variable from rga_frame. Despite the comment it
didn't involve any calculation and is just a copy of the
plane_fmt[0].bytesperline value. Therefore avoid this struct member
and use the bytesperline value directly in the places where it is
required.
Also drop the dependency on the depth format member, which was only
used to calculate the stride of the default format. This is already done
by the v4l2_fill_pixfmt_mp_aligned helper and used as stride in try_fmt.
Therefore using it's value also for the default format stride is just
more consistent.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/platform/rockchip/rga/rga-hw.c | 36 ++++++++++------------------
drivers/media/platform/rockchip/rga/rga.c | 5 +---
drivers/media/platform/rockchip/rga/rga.h | 4 ----
3 files changed, 13 insertions(+), 32 deletions(-)
diff --git a/drivers/media/platform/rockchip/rga/rga-hw.c b/drivers/media/platform/rockchip/rga/rga-hw.c
index ea4d2b0832d82..e75674b183cdf 100644
--- a/drivers/media/platform/rockchip/rga/rga-hw.c
+++ b/drivers/media/platform/rockchip/rga/rga-hw.c
@@ -44,7 +44,7 @@ rga_get_corner_addrs(struct rga_frame *frm, struct rga_addrs *addrs,
struct rga_addrs *lt, *lb, *rt, *rb;
const struct v4l2_format_info *format_info;
unsigned int x_div = 0,
- y_div = 0, uv_stride = 0, pixel_width = 0;
+ y_div = 0, y_stride = 0, uv_stride = 0, pixel_width = 0;
lt = &corner_addrs.left_top;
lb = &corner_addrs.left_bottom;
@@ -61,14 +61,15 @@ rga_get_corner_addrs(struct rga_frame *frm, struct rga_addrs *addrs,
else
x_div = 1;
y_div = format_info->vdiv;
- uv_stride = frm->stride / x_div;
- pixel_width = frm->stride / frm->pix.width;
+ y_stride = frm->pix.plane_fmt[0].bytesperline;
+ uv_stride = y_stride / x_div;
+ pixel_width = y_stride / frm->pix.width;
- lt->y_addr = addrs->y_addr + y * frm->stride + x * pixel_width;
+ lt->y_addr = addrs->y_addr + y * y_stride + x * pixel_width;
lt->u_addr = addrs->u_addr + (y / y_div) * uv_stride + x / x_div;
lt->v_addr = addrs->v_addr + (y / y_div) * uv_stride + x / x_div;
- lb->y_addr = lt->y_addr + (h - 1) * frm->stride;
+ lb->y_addr = lt->y_addr + (h - 1) * y_stride;
lb->u_addr = lt->u_addr + (h / y_div - 1) * uv_stride;
lb->v_addr = lt->v_addr + (h / y_div - 1) * uv_stride;
@@ -200,6 +201,7 @@ static void rga_cmd_set_format_scale_info(struct rga_ctx *ctx)
union rga_src_act_info src_act_info;
union rga_dst_vir_info dst_vir_info;
union rga_dst_act_info dst_act_info;
+ u32 in_stride, out_stride;
src_h = ctx->in.crop.height;
src_w = ctx->in.crop.width;
@@ -301,13 +303,15 @@ static void rga_cmd_set_format_scale_info(struct rga_ctx *ctx)
* Calculate the framebuffer virtual strides and active size,
* note that the step of vir_stride / vir_width is 4 byte words
*/
- src_vir_info.data.vir_stride = ctx->in.stride >> 2;
- src_vir_info.data.vir_width = ctx->in.stride >> 2;
+ in_stride = ctx->in.pix.plane_fmt[0].bytesperline;
+ src_vir_info.data.vir_stride = in_stride >> 2;
+ src_vir_info.data.vir_width = in_stride >> 2;
src_act_info.data.act_height = src_h - 1;
src_act_info.data.act_width = src_w - 1;
- dst_vir_info.data.vir_stride = ctx->out.stride >> 2;
+ out_stride = ctx->out.pix.plane_fmt[0].bytesperline;
+ dst_vir_info.data.vir_stride = out_stride >> 2;
dst_act_info.data.act_height = dst_h - 1;
dst_act_info.data.act_width = dst_w - 1;
@@ -492,97 +496,81 @@ static struct rga_fmt formats[] = {
.fourcc = V4L2_PIX_FMT_ARGB32,
.color_swap = RGA_COLOR_ALPHA_SWAP,
.hw_format = RGA_COLOR_FMT_ABGR8888,
- .depth = 32,
},
{
.fourcc = V4L2_PIX_FMT_ABGR32,
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_ABGR8888,
- .depth = 32,
},
{
.fourcc = V4L2_PIX_FMT_XBGR32,
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_XBGR8888,
- .depth = 32,
},
{
.fourcc = V4L2_PIX_FMT_RGB24,
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_RGB888,
- .depth = 24,
},
{
.fourcc = V4L2_PIX_FMT_BGR24,
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_RGB888,
- .depth = 24,
},
{
.fourcc = V4L2_PIX_FMT_ARGB444,
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_ABGR4444,
- .depth = 16,
},
{
.fourcc = V4L2_PIX_FMT_ARGB555,
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_ABGR1555,
- .depth = 16,
},
{
.fourcc = V4L2_PIX_FMT_RGB565,
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_BGR565,
- .depth = 16,
},
{
.fourcc = V4L2_PIX_FMT_NV21,
.color_swap = RGA_COLOR_UV_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420SP,
- .depth = 12,
},
{
.fourcc = V4L2_PIX_FMT_NV61,
.color_swap = RGA_COLOR_UV_SWAP,
.hw_format = RGA_COLOR_FMT_YUV422SP,
- .depth = 16,
},
{
.fourcc = V4L2_PIX_FMT_NV12,
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420SP,
- .depth = 12,
},
{
.fourcc = V4L2_PIX_FMT_NV12M,
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420SP,
- .depth = 12,
},
{
.fourcc = V4L2_PIX_FMT_NV16,
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV422SP,
- .depth = 16,
},
{
.fourcc = V4L2_PIX_FMT_YUV420,
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420P,
- .depth = 12,
},
{
.fourcc = V4L2_PIX_FMT_YUV422P,
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV422P,
- .depth = 16,
},
{
.fourcc = V4L2_PIX_FMT_YVU420,
.color_swap = RGA_COLOR_UV_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420P,
- .depth = 12,
},
};
diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c
index 2695bc5077a03..6433fdaa5b339 100644
--- a/drivers/media/platform/rockchip/rga/rga.c
+++ b/drivers/media/platform/rockchip/rga/rga.c
@@ -220,8 +220,6 @@ static int rga_open(struct file *file)
.fmt = &rga->hw->formats[0],
};
- def_frame.stride = (def_width * def_frame.fmt->depth) >> 3;
-
ctx = kzalloc_obj(*ctx);
if (!ctx)
return -ENOMEM;
@@ -459,7 +457,6 @@ static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
if (IS_ERR(frm))
return PTR_ERR(frm);
frm->fmt = rga_fmt_find(rga, pix_fmt->pixelformat);
- frm->stride = pix_fmt->plane_fmt[0].bytesperline;
/*
* Copy colorimetry from output to capture as required by the
@@ -484,7 +481,7 @@ static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
"[%s] fmt - %p4cc %dx%d (stride %d)\n",
V4L2_TYPE_IS_OUTPUT(f->type) ? "OUTPUT" : "CAPTURE",
&frm->fmt->fourcc, pix_fmt->width, pix_fmt->height,
- frm->stride);
+ pix_fmt->plane_fmt[0].bytesperline);
for (i = 0; i < pix_fmt->num_planes; i++) {
v4l2_dbg(debug, 1, &rga->v4l2_dev,
diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h
index 2838fc7785f72..d203b7dae2b03 100644
--- a/drivers/media/platform/rockchip/rga/rga.h
+++ b/drivers/media/platform/rockchip/rga/rga.h
@@ -19,7 +19,6 @@
struct rga_fmt {
u32 fourcc;
- int depth;
u8 color_swap;
u8 hw_format;
};
@@ -31,9 +30,6 @@ struct rga_frame {
/* Image format */
struct rga_fmt *fmt;
struct v4l2_pix_format_mplane pix;
-
- /* Variables that can calculated once and reused */
- u32 stride;
};
struct rga_dma_desc {
--
2.54.0
^ permalink raw reply related
* [PATCH v5 27/29] media: rockchip: rga: disable multi-core support
From: Sven Püschel @ 2026-04-28 9:01 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, Sven Püschel,
Nicolas Dufresne
In-Reply-To: <20260428-spu-rga3-v5-0-eb7f5d019d86@pengutronix.de>
Disable multi-core support in preparation of the RGA3 addition. The
RK3588 SoC features two equal RGA3 cores. This allows scheduling of the
work between both cores, which is not yet implemented. Until it is
implemented avoid exposing both cores as independent video devices to
prevent an ABI breakage when multi-core support is added.
This patch is copied from the Hantro driver patch to disable multi core
support by Sebastian Reichel. See
commit ccdeb8d57f7f ("media: hantro: Disable multicore support")
Link: https://lore.kernel.org/all/20240618183816.77597-4-sebastian.reichel@collabora.com/
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/platform/rockchip/rga/rga.c | 47 +++++++++++++++++++++++++++++++
1 file changed, 47 insertions(+)
diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c
index b0c88ad647e96..fd92ecf408e0c 100644
--- a/drivers/media/platform/rockchip/rga/rga.c
+++ b/drivers/media/platform/rockchip/rga/rga.c
@@ -708,6 +708,49 @@ static int rga_parse_dt(struct rockchip_rga *rga)
return 0;
}
+/*
+ * Some SoCs, like RK3588 have multiple identical RGA3 cores, but the
+ * kernel is currently missing support for multi-core handling. Exposing
+ * separate devices for each core to userspace is bad, since that does
+ * not allow scheduling tasks properly (and creates ABI). With this workaround
+ * the driver will only probe for the first core and early exit for the other
+ * cores. Once the driver gains multi-core support, the same technique
+ * for detecting the main core can be used to cluster all cores together.
+ */
+static int rga_disable_multicore(struct device *dev)
+{
+ struct device_node *node = NULL;
+ const char *compatible;
+ bool is_main_core;
+ int ret;
+
+ /* Intentionally ignores the fallback strings */
+ ret = of_property_read_string(dev->of_node, "compatible", &compatible);
+ if (ret)
+ return ret;
+
+ /* The first compatible and available node found is considered the main core */
+ do {
+ node = of_find_compatible_node(node, NULL, compatible);
+ if (of_device_is_available(node))
+ break;
+ } while (node);
+
+ if (!node)
+ return -EINVAL;
+
+ is_main_core = (dev->of_node == node);
+
+ of_node_put(node);
+
+ if (!is_main_core) {
+ dev_info(dev, "missing multi-core support, ignoring this instance\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
static int rga_probe(struct platform_device *pdev)
{
struct rockchip_rga *rga;
@@ -718,6 +761,10 @@ static int rga_probe(struct platform_device *pdev)
if (!pdev->dev.of_node)
return -ENODEV;
+ ret = rga_disable_multicore(&pdev->dev);
+ if (ret)
+ return ret;
+
rga = devm_kzalloc(&pdev->dev, sizeof(*rga), GFP_KERNEL);
if (!rga)
return -ENOMEM;
--
2.54.0
^ permalink raw reply related
* [PATCH v5 26/29] media: rockchip: rga: add feature flags
From: Sven Püschel @ 2026-04-28 9:01 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, Sven Püschel,
Nicolas Dufresne
In-Reply-To: <20260428-spu-rga3-v5-0-eb7f5d019d86@pengutronix.de>
In preparation to the RGA3 addition add feature flags, which can limit
the exposed feature set of the video device, like rotating or selection
support. This is necessary as the RGA3 doesn't initially implement the
full feature set currently exposed by the driver.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/platform/rockchip/rga/rga-hw.c | 4 ++++
drivers/media/platform/rockchip/rga/rga.c | 23 +++++++++++++++--------
drivers/media/platform/rockchip/rga/rga.h | 7 +++++++
3 files changed, 26 insertions(+), 8 deletions(-)
diff --git a/drivers/media/platform/rockchip/rga/rga-hw.c b/drivers/media/platform/rockchip/rga/rga-hw.c
index d4282fc5d92ba..2a14d18baf532 100644
--- a/drivers/media/platform/rockchip/rga/rga-hw.c
+++ b/drivers/media/platform/rockchip/rga/rga-hw.c
@@ -613,6 +613,10 @@ const struct rga_hw rga2_hw = {
.max_height = MAX_HEIGHT,
.max_scaling_factor = MAX_SCALING_FACTOR,
.stride_alignment = 4,
+ .features = RGA_FEATURE_FLIP
+ | RGA_FEATURE_ROTATE
+ | RGA_FEATURE_BG_COLOR
+ | RGA_FEATURE_SELECTION,
.setup_cmdbuf = rga_hw_setup_cmdbuf,
.start = rga_hw_start,
diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c
index 364c1dac3f81c..b0c88ad647e96 100644
--- a/drivers/media/platform/rockchip/rga/rga.c
+++ b/drivers/media/platform/rockchip/rga/rga.c
@@ -162,17 +162,21 @@ static int rga_setup_ctrls(struct rga_ctx *ctx)
v4l2_ctrl_handler_init(&ctx->ctrl_handler, 4);
- v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops,
- V4L2_CID_HFLIP, 0, 1, 1, 0);
+ if (rga->hw->features & RGA_FEATURE_FLIP) {
+ v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops,
+ V4L2_CID_HFLIP, 0, 1, 1, 0);
- v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops,
- V4L2_CID_VFLIP, 0, 1, 1, 0);
+ v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops,
+ V4L2_CID_VFLIP, 0, 1, 1, 0);
+ }
- v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops,
- V4L2_CID_ROTATE, 0, 270, 90, 0);
+ if (rga->hw->features & RGA_FEATURE_ROTATE)
+ v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops,
+ V4L2_CID_ROTATE, 0, 270, 90, 0);
- v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops,
- V4L2_CID_BG_COLOR, 0, 0xffffffff, 1, 0);
+ if (rga->hw->features & RGA_FEATURE_BG_COLOR)
+ v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops,
+ V4L2_CID_BG_COLOR, 0, 0xffffffff, 1, 0);
if (ctx->ctrl_handler.error) {
int err = ctx->ctrl_handler.error;
@@ -536,6 +540,9 @@ static int vidioc_s_selection(struct file *file, void *priv,
struct rga_frame *f;
int ret = 0;
+ if (!(rga->hw->features & RGA_FEATURE_SELECTION))
+ return -EINVAL;
+
f = rga_get_frame(ctx, s->type);
if (IS_ERR(f))
return PTR_ERR(f);
diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h
index d98e57c6d7b57..849b96392b780 100644
--- a/drivers/media/platform/rockchip/rga/rga.h
+++ b/drivers/media/platform/rockchip/rga/rga.h
@@ -6,6 +6,7 @@
#ifndef __RGA_H__
#define __RGA_H__
+#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <media/videobuf2-v4l2.h>
@@ -133,6 +134,11 @@ static inline void rga_mod(struct rockchip_rga *rga, u32 reg, u32 val, u32 mask)
rga_write(rga, reg, temp);
};
+#define RGA_FEATURE_FLIP BIT(0)
+#define RGA_FEATURE_ROTATE BIT(1)
+#define RGA_FEATURE_BG_COLOR BIT(2)
+#define RGA_FEATURE_SELECTION BIT(3)
+
struct rga_hw {
const char *card_type;
bool has_internal_iommu;
@@ -141,6 +147,7 @@ struct rga_hw {
u32 max_width, max_height;
u8 max_scaling_factor;
u8 stride_alignment;
+ u8 features;
void (*setup_cmdbuf)(struct rga_ctx *ctx);
void (*start)(struct rockchip_rga *rga,
--
2.54.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox