blob: 7e1fbf9a091f74065f9e14e7e9b5195213642452 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
//! Rust based implementation of the cpufreq-dt driver.
use kernel::{
c_str,
clk::Clk,
cpu, cpufreq,
cpumask::CpumaskVar,
device::{Core, Device},
error::code::*,
macros::vtable,
module_platform_driver, of, opp, platform,
prelude::*,
str::CString,
sync::Arc,
};
/// Finds exact supply name from the OF node.
fn find_supply_name_exact(dev: &Device, name: &str) -> Option<CString> {
let prop_name = CString::try_from_fmt(fmt!("{name}-supply")).ok()?;
dev.fwnode()?
.property_present(&prop_name)
.then(|| CString::try_from_fmt(fmt!("{name}")).ok())
.flatten()
}
/// Finds supply name for the CPU from DT.
fn find_supply_names(dev: &Device, cpu: cpu::CpuId) -> Option<KVec<CString>> {
// Try "cpu0" for older DTs, fallback to "cpu".
let name = (cpu.as_u32() == 0)
.then(|| find_supply_name_exact(dev, "cpu0"))
.flatten()
.or_else(|| find_supply_name_exact(dev, "cpu"))?;
let mut list = KVec::with_capacity(1, GFP_KERNEL).ok()?;
list.push(name, GFP_KERNEL).ok()?;
Some(list)
}
/// Represents the cpufreq dt device.
struct CPUFreqDTDevice {
opp_table: opp::Table,
freq_table: opp::FreqTable,
_mask: CpumaskVar,
_token: Option<opp::ConfigToken>,
_clk: Clk,
}
#[derive(Default)]
struct CPUFreqDTDriver;
#[vtable]
impl opp::ConfigOps for CPUFreqDTDriver {}
#[vtable]
impl cpufreq::Driver for CPUFreqDTDriver {
const NAME: &'static CStr = c_str!("cpufreq-dt");
const FLAGS: u16 = cpufreq::flags::NEED_INITIAL_FREQ_CHECK | cpufreq::flags::IS_COOLING_DEV;
const BOOST_ENABLED: bool = true;
type PData = Arc<CPUFreqDTDevice>;
fn init(policy: &mut cpufreq::Policy) -> Result<Self::PData> {
let cpu = policy.cpu();
// SAFETY: The CPU device is only used during init; it won't get hot-unplugged. The cpufreq
// core registers with CPU notifiers and the cpufreq core/driver won't use the CPU device,
// once the CPU is hot-unplugged.
let dev = unsafe { cpu::from_cpu(cpu)? };
let mut mask = CpumaskVar::new_zero(GFP_KERNEL)?;
mask.set(cpu);
let token = find_supply_names(dev, cpu)
.map(|names| {
opp::Config::<Self>::new()
.set_regulator_names(names)?
.set(dev)
})
.transpose()?;
// Get OPP-sharing information from "operating-points-v2" bindings.
let fallback = match opp::Table::of_sharing_cpus(dev, &mut mask) {
Ok(()) => false,
Err(e) if e == ENOENT => {
// "operating-points-v2" not supported. If the platform hasn't
// set sharing CPUs, fallback to all CPUs share the `Policy`
// for backward compatibility.
opp::Table::sharing_cpus(dev, &mut mask).is_err()
}
Err(e) => return Err(e),
};
// Initialize OPP tables for all policy cpus.
//
// For platforms not using "operating-points-v2" bindings, we do this
// before updating policy cpus. Otherwise, we will end up creating
// duplicate OPPs for the CPUs.
//
// OPPs might be populated at runtime, don't fail for error here unless
// it is -EPROBE_DEFER.
let mut opp_table = match opp::Table::from_of_cpumask(dev, &mut mask) {
Ok(table) => table,
Err(e) => {
if e == EPROBE_DEFER {
return Err(e);
}
// The table is added dynamically ?
opp::Table::from_dev(dev)?
}
};
// The OPP table must be initialized, statically or dynamically, by this point.
opp_table.opp_count()?;
// Set sharing cpus for fallback scenario.
if fallback {
mask.setall();
opp_table.set_sharing_cpus(&mut mask)?;
}
let mut transition_latency = opp_table.max_transition_latency_ns() as u32;
if transition_latency == 0 {
transition_latency = cpufreq::ETERNAL_LATENCY_NS;
}
policy
.set_dvfs_possible_from_any_cpu(true)
.set_suspend_freq(opp_table.suspend_freq())
.set_transition_latency_ns(transition_latency);
let freq_table = opp_table.cpufreq_table()?;
// SAFETY: The `freq_table` is not dropped while it is getting used by the C code.
unsafe { policy.set_freq_table(&freq_table) };
// SAFETY: The returned `clk` is not dropped while it is getting used by the C code.
let clk = unsafe { policy.set_clk(dev, None)? };
mask.copy(policy.cpus());
Ok(Arc::new(
CPUFreqDTDevice {
opp_table,
freq_table,
_mask: mask,
_token: token,
_clk: clk,
},
GFP_KERNEL,
)?)
}
fn exit(_policy: &mut cpufreq::Policy, _data: Option<Self::PData>) -> Result {
Ok(())
}
fn online(_policy: &mut cpufreq::Policy) -> Result {
// We did light-weight tear down earlier, nothing to do here.
Ok(())
}
fn offline(_policy: &mut cpufreq::Policy) -> Result {
// Preserve policy->data and don't free resources on light-weight
// tear down.
Ok(())
}
fn suspend(policy: &mut cpufreq::Policy) -> Result {
policy.generic_suspend()
}
fn verify(data: &mut cpufreq::PolicyData) -> Result {
data.generic_verify()
}
fn target_index(policy: &mut cpufreq::Policy, index: cpufreq::TableIndex) -> Result {
let Some(data) = policy.data::<Self::PData>() else {
return Err(ENOENT);
};
let freq = data.freq_table.freq(index)?;
data.opp_table.set_rate(freq)
}
fn get(policy: &mut cpufreq::Policy) -> Result<u32> {
policy.generic_get()
}
fn set_boost(_policy: &mut cpufreq::Policy, _state: i32) -> Result {
Ok(())
}
fn register_em(policy: &mut cpufreq::Policy) {
policy.register_em_opp()
}
}
kernel::of_device_table!(
OF_TABLE,
MODULE_OF_TABLE,
<CPUFreqDTDriver as platform::Driver>::IdInfo,
[(of::DeviceId::new(c_str!("operating-points-v2")), ())]
);
impl platform::Driver for CPUFreqDTDriver {
type IdInfo = ();
const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
fn probe(
pdev: &platform::Device<Core>,
_id_info: Option<&Self::IdInfo>,
) -> Result<Pin<KBox<Self>>> {
cpufreq::Registration::<CPUFreqDTDriver>::new_foreign_owned(pdev.as_ref())?;
Ok(KBox::new(Self {}, GFP_KERNEL)?.into())
}
}
module_platform_driver! {
type: CPUFreqDTDriver,
name: "cpufreq-dt",
authors: ["Viresh Kumar <viresh.kumar@linaro.org>"],
description: "Generic CPUFreq DT driver",
license: "GPL v2",
}