| // SPDX-License-Identifier: GPL-2.0 | |
| //! Rust based implementation of the cpufreq-dt driver. | |
| use kernel::{ | |
| 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"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, | |
| <CPUFreqDTDriver as platform::Driver>::IdInfo, | |
| [(of::DeviceId::new(c"operating-points-v2"), ())] | |
| ); | |
| impl platform::Driver for CPUFreqDTDriver { | |
| type IdInfo = (); | |
| type Data<'bound> = Self; | |
| const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE); | |
| fn probe<'bound>( | |
| pdev: &'bound platform::Device<Core<'_>>, | |
| _id_info: Option<&'bound Self::IdInfo>, | |
| ) -> impl PinInit<Self, Error> + 'bound { | |
| cpufreq::Registration::<CPUFreqDTDriver>::new_foreign_owned(pdev.as_ref())?; | |
| Ok(Self {}) | |
| } | |
| } | |
| module_platform_driver! { | |
| type: CPUFreqDTDriver, | |
| name: "cpufreq-dt", | |
| authors: ["Viresh Kumar <viresh.kumar@linaro.org>"], | |
| description: "Generic CPUFreq DT driver", | |
| license: "GPL v2", | |
| alias: ["platform:cpufreq-dt"], | |
| } |