mirror of
https://github.com/torvalds/linux.git
synced 2026-01-25 07:47:50 +00:00
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:
@@ -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, ®2);
|
||||
regmap_read(regmap_pmu, RK3588_PMUGRF_OS_REG3, ®3);
|
||||
@@ -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, ®6);
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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`].
|
||||
|
||||
Reference in New Issue
Block a user