Merge branches 'pm-em', 'pm-opp' and 'pm-devfreq'

Merge energy model management, OPP (operating performance points) and
devfreq updates for 6.18-rc1:

 - Prevent CPU capacity updates after registering a perf domain from
   failing on a first CPU that is not present (Christian Loehle)

 - Add support for the cases in which frequency alone is not sufficient
   to uniquely identify an OPP (Krishna Chaitanya Chundru)

 - Use to_result() for OPP error handling in Rust (Onur Özkan)

 - Add support for LPDDR5 on Rockhip RK3588 SoC to rockchip-dfi devfreq
   driver (Nicolas Frattaroli)

 - Fix an issue where DDR cycle counts on RK3588/RK3528 with LPDDR4(X)
   are reported as half by adding a cycle multiplier to the DFI driver
   in rockchip-dfi devfreq-event driver (Nicolas Frattaroli)

 - Fix missing error pointer dereference check of regulator instance in
   the mtk-cci devfreq driver probe and remove a redundant condition from
   an if () statement in that driver (Dan Carpenter, Liao Yuanhong)

* pm-em:
  PM: EM: Fix late boot with holes in CPU topology

* pm-opp:
  OPP: Add support to find OPP for a set of keys
  rust: opp: use to_result for error handling

* pm-devfreq:
  PM / devfreq: rockchip-dfi: add support for LPDDR5
  PM / devfreq: rockchip-dfi: double count on RK3588
  PM / devfreq: mtk-cci: avoid redundant conditions
  PM / devfreq: mtk-cci: Fix potential error pointer dereference in probe()
This commit is contained in:
Rafael J. Wysocki
2025-09-29 12:30:44 +02:00
8 changed files with 222 additions and 39 deletions

View File

@@ -34,15 +34,18 @@
/* DDRMON_CTRL */
#define DDRMON_CTRL 0x04
#define DDRMON_CTRL_LPDDR5 BIT(6)
#define DDRMON_CTRL_DDR4 BIT(5)
#define DDRMON_CTRL_LPDDR4 BIT(4)
#define DDRMON_CTRL_HARDWARE_EN BIT(3)
#define DDRMON_CTRL_LPDDR23 BIT(2)
#define DDRMON_CTRL_SOFTWARE_EN BIT(1)
#define DDRMON_CTRL_TIMER_CNT_EN BIT(0)
#define DDRMON_CTRL_DDR_TYPE_MASK (DDRMON_CTRL_DDR4 | \
#define DDRMON_CTRL_DDR_TYPE_MASK (DDRMON_CTRL_LPDDR5 | \
DDRMON_CTRL_DDR4 | \
DDRMON_CTRL_LPDDR4 | \
DDRMON_CTRL_LPDDR23)
#define DDRMON_CTRL_LP5_BANK_MODE_MASK GENMASK(8, 7)
#define DDRMON_CH0_WR_NUM 0x20
#define DDRMON_CH0_RD_NUM 0x24
@@ -116,12 +119,60 @@ struct rockchip_dfi {
int buswidth[DMC_MAX_CHANNELS];
int ddrmon_stride;
bool ddrmon_ctrl_single;
u32 lp5_bank_mode;
bool lp5_ckr; /* true if in 4:1 command-to-data clock ratio mode */
unsigned int count_multiplier; /* number of data clocks per count */
};
static int rockchip_dfi_ddrtype_to_ctrl(struct rockchip_dfi *dfi, u32 *ctrl,
u32 *mask)
{
u32 ddrmon_ver;
*mask = DDRMON_CTRL_DDR_TYPE_MASK;
switch (dfi->ddr_type) {
case ROCKCHIP_DDRTYPE_LPDDR2:
case ROCKCHIP_DDRTYPE_LPDDR3:
*ctrl = DDRMON_CTRL_LPDDR23;
break;
case ROCKCHIP_DDRTYPE_LPDDR4:
case ROCKCHIP_DDRTYPE_LPDDR4X:
*ctrl = DDRMON_CTRL_LPDDR4;
break;
case ROCKCHIP_DDRTYPE_LPDDR5:
ddrmon_ver = readl_relaxed(dfi->regs);
if (ddrmon_ver < 0x40) {
*ctrl = DDRMON_CTRL_LPDDR5 | dfi->lp5_bank_mode;
*mask |= DDRMON_CTRL_LP5_BANK_MODE_MASK;
break;
}
/*
* As it is unknown whether the unpleasant special case
* behaviour used by the vendor kernel is needed for any
* shipping hardware, ask users to report if they have
* some of that hardware.
*/
dev_err(&dfi->edev->dev,
"unsupported DDRMON version 0x%04X, please let linux-rockchip know!\n",
ddrmon_ver);
return -EOPNOTSUPP;
default:
dev_err(&dfi->edev->dev, "unsupported memory type 0x%X\n",
dfi->ddr_type);
return -EOPNOTSUPP;
}
return 0;
}
static int rockchip_dfi_enable(struct rockchip_dfi *dfi)
{
void __iomem *dfi_regs = dfi->regs;
int i, ret = 0;
u32 ctrl;
u32 ctrl_mask;
mutex_lock(&dfi->mutex);
@@ -135,8 +186,11 @@ static int rockchip_dfi_enable(struct rockchip_dfi *dfi)
goto out;
}
ret = rockchip_dfi_ddrtype_to_ctrl(dfi, &ctrl, &ctrl_mask);
if (ret)
goto out;
for (i = 0; i < dfi->max_channels; i++) {
u32 ctrl = 0;
if (!(dfi->channel_mask & BIT(i)))
continue;
@@ -146,21 +200,7 @@ static int rockchip_dfi_enable(struct rockchip_dfi *dfi)
DDRMON_CTRL_SOFTWARE_EN | DDRMON_CTRL_HARDWARE_EN),
dfi_regs + i * dfi->ddrmon_stride + DDRMON_CTRL);
/* set ddr type to dfi */
switch (dfi->ddr_type) {
case ROCKCHIP_DDRTYPE_LPDDR2:
case ROCKCHIP_DDRTYPE_LPDDR3:
ctrl = DDRMON_CTRL_LPDDR23;
break;
case ROCKCHIP_DDRTYPE_LPDDR4:
case ROCKCHIP_DDRTYPE_LPDDR4X:
ctrl = DDRMON_CTRL_LPDDR4;
break;
default:
break;
}
writel_relaxed(HIWORD_UPDATE(ctrl, DDRMON_CTRL_DDR_TYPE_MASK),
writel_relaxed(HIWORD_UPDATE(ctrl, ctrl_mask),
dfi_regs + i * dfi->ddrmon_stride + DDRMON_CTRL);
/* enable count, use software mode */
@@ -435,7 +475,7 @@ static u64 rockchip_ddr_perf_event_get_count(struct perf_event *event)
switch (event->attr.config) {
case PERF_EVENT_CYCLES:
count = total.c[0].clock_cycles;
count = total.c[0].clock_cycles * dfi->count_multiplier;
break;
case PERF_EVENT_READ_BYTES:
for (i = 0; i < dfi->max_channels; i++)
@@ -651,10 +691,14 @@ static int rockchip_ddr_perf_init(struct rockchip_dfi *dfi)
break;
case ROCKCHIP_DDRTYPE_LPDDR4:
case ROCKCHIP_DDRTYPE_LPDDR4X:
case ROCKCHIP_DDRTYPE_LPDDR5:
dfi->burst_len = 16;
break;
}
if (!dfi->count_multiplier)
dfi->count_multiplier = 1;
ret = perf_pmu_register(pmu, "rockchip_ddr", -1);
if (ret)
return ret;
@@ -726,7 +770,7 @@ static int rk3568_dfi_init(struct rockchip_dfi *dfi)
static int rk3588_dfi_init(struct rockchip_dfi *dfi)
{
struct regmap *regmap_pmu = dfi->regmap_pmu;
u32 reg2, reg3, reg4;
u32 reg2, reg3, reg4, reg6;
regmap_read(regmap_pmu, RK3588_PMUGRF_OS_REG2, &reg2);
regmap_read(regmap_pmu, RK3588_PMUGRF_OS_REG3, &reg3);
@@ -751,6 +795,15 @@ static int rk3588_dfi_init(struct rockchip_dfi *dfi)
dfi->max_channels = 4;
dfi->ddrmon_stride = 0x4000;
dfi->count_multiplier = 2;
if (dfi->ddr_type == ROCKCHIP_DDRTYPE_LPDDR5) {
regmap_read(regmap_pmu, RK3588_PMUGRF_OS_REG6, &reg6);
dfi->lp5_bank_mode = FIELD_GET(RK3588_PMUGRF_OS_REG6_LP5_BANK_MODE, reg6) << 7;
dfi->lp5_ckr = FIELD_GET(RK3588_PMUGRF_OS_REG6_LP5_CKR, reg6);
if (dfi->lp5_ckr)
dfi->count_multiplier *= 2;
}
return 0;
};

View File

@@ -86,7 +86,7 @@ static int mtk_ccifreq_set_voltage(struct mtk_ccifreq_drv *drv, int new_voltage)
soc_data->sram_max_volt);
return ret;
}
} else if (pre_voltage > new_voltage) {
} else {
voltage = max(new_voltage,
pre_vsram - soc_data->max_volt_shift);
ret = regulator_set_voltage(drv->proc_reg, voltage,
@@ -386,7 +386,8 @@ out_disable_cci_clk:
out_free_resources:
if (regulator_is_enabled(drv->proc_reg))
regulator_disable(drv->proc_reg);
if (drv->sram_reg && regulator_is_enabled(drv->sram_reg))
if (!IS_ERR_OR_NULL(drv->sram_reg) &&
regulator_is_enabled(drv->sram_reg))
regulator_disable(drv->sram_reg);
return ret;

View File

@@ -476,6 +476,16 @@ static unsigned long _read_bw(struct dev_pm_opp *opp, int index)
return opp->bandwidth[index].peak;
}
static unsigned long _read_opp_key(struct dev_pm_opp *opp, int index,
struct dev_pm_opp_key *key)
{
key->bw = opp->bandwidth ? opp->bandwidth[index].peak : 0;
key->freq = opp->rates[index];
key->level = opp->level;
return true;
}
/* Generic comparison helpers */
static bool _compare_exact(struct dev_pm_opp **opp, struct dev_pm_opp *temp_opp,
unsigned long opp_key, unsigned long key)
@@ -509,6 +519,22 @@ static bool _compare_floor(struct dev_pm_opp **opp, struct dev_pm_opp *temp_opp,
return false;
}
static bool _compare_opp_key_exact(struct dev_pm_opp **opp,
struct dev_pm_opp *temp_opp, struct dev_pm_opp_key *opp_key,
struct dev_pm_opp_key *key)
{
bool level_match = (key->level == OPP_LEVEL_UNSET || opp_key->level == key->level);
bool freq_match = (key->freq == 0 || opp_key->freq == key->freq);
bool bw_match = (key->bw == 0 || opp_key->bw == key->bw);
if (freq_match && level_match && bw_match) {
*opp = temp_opp;
return true;
}
return false;
}
/* Generic key finding helpers */
static struct dev_pm_opp *_opp_table_find_key(struct opp_table *opp_table,
unsigned long *key, int index, bool available,
@@ -541,6 +567,37 @@ static struct dev_pm_opp *_opp_table_find_key(struct opp_table *opp_table,
return opp;
}
static struct dev_pm_opp *_opp_table_find_opp_key(struct opp_table *opp_table,
struct dev_pm_opp_key *key, bool available,
unsigned long (*read)(struct dev_pm_opp *opp, int index,
struct dev_pm_opp_key *key),
bool (*compare)(struct dev_pm_opp **opp, struct dev_pm_opp *temp_opp,
struct dev_pm_opp_key *opp_key, struct dev_pm_opp_key *key),
bool (*assert)(struct opp_table *opp_table, unsigned int index))
{
struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE);
struct dev_pm_opp_key temp_key;
/* Assert that the requirement is met */
if (!assert(opp_table, 0))
return ERR_PTR(-EINVAL);
guard(mutex)(&opp_table->lock);
list_for_each_entry(temp_opp, &opp_table->opp_list, node) {
if (temp_opp->available == available) {
read(temp_opp, 0, &temp_key);
if (compare(&opp, temp_opp, &temp_key, key)) {
/* Increment the reference count of OPP */
dev_pm_opp_get(opp);
break;
}
}
}
return opp;
}
static struct dev_pm_opp *
_find_key(struct device *dev, unsigned long *key, int index, bool available,
unsigned long (*read)(struct dev_pm_opp *opp, int index),
@@ -632,6 +689,48 @@ struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev,
}
EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_exact);
/**
* dev_pm_opp_find_key_exact() - Search for an OPP with exact key set
* @dev: Device for which the OPP is being searched
* @key: OPP key set to match
* @available: true/false - match for available OPP
*
* Search for an exact match of the key set in the OPP table.
*
* Return: A matching opp on success, else ERR_PTR in case of error.
* Possible error values:
* EINVAL: for bad pointers
* ERANGE: no match found for search
* ENODEV: if device not found in list of registered devices
*
* Note: 'available' is a modifier for the search. If 'available' == true,
* then the match is for exact matching key and is available in the stored
* OPP table. If false, the match is for exact key which is not available.
*
* This provides a mechanism to enable an OPP which is not available currently
* or the opposite as well.
*
* The callers are required to call dev_pm_opp_put() for the returned OPP after
* use.
*/
struct dev_pm_opp *dev_pm_opp_find_key_exact(struct device *dev,
struct dev_pm_opp_key *key,
bool available)
{
struct opp_table *opp_table __free(put_opp_table) = _find_opp_table(dev);
if (IS_ERR(opp_table)) {
dev_err(dev, "%s: OPP table not found (%ld)\n", __func__,
PTR_ERR(opp_table));
return ERR_CAST(opp_table);
}
return _opp_table_find_opp_key(opp_table, key, available,
_read_opp_key, _compare_opp_key_exact,
assert_single_clk);
}
EXPORT_SYMBOL_GPL(dev_pm_opp_find_key_exact);
/**
* dev_pm_opp_find_freq_exact_indexed() - Search for an exact freq for the
* clock corresponding to the index

View File

@@ -98,6 +98,25 @@ struct dev_pm_opp_data {
unsigned long u_volt;
};
/**
* struct dev_pm_opp_key - Key used to identify OPP entries
* @freq: Frequency in Hz. Use 0 if frequency is not to be matched.
* @level: Performance level associated with the OPP entry.
* Use OPP_LEVEL_UNSET if level is not to be matched.
* @bw: Bandwidth associated with the OPP entry.
* Use 0 if bandwidth is not to be matched.
*
* This structure is used to uniquely identify an OPP entry based on
* frequency, performance level, and bandwidth. Each field can be
* selectively ignored during matching by setting it to its respective
* NOP value.
*/
struct dev_pm_opp_key {
unsigned long freq;
unsigned int level;
u32 bw;
};
#if defined(CONFIG_PM_OPP)
struct opp_table *dev_pm_opp_get_opp_table(struct device *dev);
@@ -131,6 +150,10 @@ struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev,
unsigned long freq,
bool available);
struct dev_pm_opp *dev_pm_opp_find_key_exact(struct device *dev,
struct dev_pm_opp_key *key,
bool available);
struct dev_pm_opp *
dev_pm_opp_find_freq_exact_indexed(struct device *dev, unsigned long freq,
u32 index, bool available);
@@ -289,6 +312,13 @@ static inline struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev,
return ERR_PTR(-EOPNOTSUPP);
}
static inline struct dev_pm_opp *dev_pm_opp_find_key_exact(struct device *dev,
struct dev_pm_opp_key *key,
bool available)
{
return ERR_PTR(-EOPNOTSUPP);
}
static inline struct dev_pm_opp *
dev_pm_opp_find_freq_exact_indexed(struct device *dev, unsigned long freq,
u32 index, bool available)

View File

@@ -12,7 +12,11 @@
#define RK3588_PMUGRF_OS_REG3_DRAMTYPE_INFO_V3 GENMASK(13, 12)
#define RK3588_PMUGRF_OS_REG3_SYSREG_VERSION GENMASK(31, 28)
#define RK3588_PMUGRF_OS_REG4 0x210
#define RK3588_PMUGRF_OS_REG5 0x214
#define RK3588_PMUGRF_OS_REG4 0x210
#define RK3588_PMUGRF_OS_REG5 0x214
#define RK3588_PMUGRF_OS_REG6 0x218
#define RK3588_PMUGRF_OS_REG6_LP5_BANK_MODE GENMASK(2, 1)
/* Whether the LPDDR5 is in 2:1 (= 0) or 4:1 (= 1) CKR a.k.a. DQS mode */
#define RK3588_PMUGRF_OS_REG6_LP5_CKR BIT(0)
#endif /* __SOC_RK3588_GRF_H */

View File

@@ -13,6 +13,7 @@ enum {
ROCKCHIP_DDRTYPE_LPDDR3 = 6,
ROCKCHIP_DDRTYPE_LPDDR4 = 7,
ROCKCHIP_DDRTYPE_LPDDR4X = 8,
ROCKCHIP_DDRTYPE_LPDDR5 = 9,
};
#endif /* __SOC_ROCKCHIP_GRF_H */

View File

@@ -799,7 +799,7 @@ void em_adjust_cpu_capacity(unsigned int cpu)
static void em_check_capacity_update(void)
{
cpumask_var_t cpu_done_mask;
int cpu;
int cpu, failed_cpus = 0;
if (!zalloc_cpumask_var(&cpu_done_mask, GFP_KERNEL)) {
pr_warn("no free memory\n");
@@ -817,10 +817,8 @@ static void em_check_capacity_update(void)
policy = cpufreq_cpu_get(cpu);
if (!policy) {
pr_debug("Accessing cpu%d policy failed\n", cpu);
schedule_delayed_work(&em_update_work,
msecs_to_jiffies(1000));
break;
failed_cpus++;
continue;
}
cpufreq_cpu_put(policy);
@@ -835,6 +833,9 @@ static void em_check_capacity_update(void)
em_adjust_new_capacity(cpu, dev, pd);
}
if (failed_cpus)
schedule_delayed_work(&em_update_work, msecs_to_jiffies(1000));
free_cpumask_var(cpu_done_mask);
}

View File

@@ -12,7 +12,7 @@ use crate::{
clk::Hertz,
cpumask::{Cpumask, CpumaskVar},
device::Device,
error::{code::*, from_err_ptr, from_result, to_result, Error, Result, VTABLE_DEFAULT_ERROR},
error::{code::*, from_err_ptr, from_result, to_result, Result, VTABLE_DEFAULT_ERROR},
ffi::c_ulong,
prelude::*,
str::CString,
@@ -501,11 +501,8 @@ impl<T: ConfigOps + Default> Config<T> {
// requirements. The OPP core guarantees not to access fields of [`Config`] after this call
// and so we don't need to save a copy of them for future use.
let ret = unsafe { bindings::dev_pm_opp_set_config(dev.as_raw(), &mut config) };
if ret < 0 {
Err(Error::from_errno(ret))
} else {
Ok(ConfigToken(ret))
}
to_result(ret).map(|()| ConfigToken(ret))
}
/// Config's clk callback.
@@ -714,11 +711,8 @@ impl Table {
// SAFETY: The requirements are satisfied by the existence of [`Device`] and its safety
// requirements.
let ret = unsafe { bindings::dev_pm_opp_get_opp_count(self.dev.as_raw()) };
if ret < 0 {
Err(Error::from_errno(ret))
} else {
Ok(ret as u32)
}
to_result(ret).map(|()| ret as u32)
}
/// Returns max clock latency (in nanoseconds) of the [`OPP`]s in the [`Table`].