|  | // 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". | 
|  | (cpu.as_u32() == 0) | 
|  | .then(|| find_supply_name_exact(dev, "cpu0")) | 
|  | .flatten() | 
|  | .or_else(|| find_supply_name_exact(dev, "cpu")) | 
|  | .and_then(|name| kernel::kvec![name].ok()) | 
|  | } | 
|  |  | 
|  | /// 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::DEFAULT_TRANSITION_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", | 
|  | } |