1 // SPDX-License-Identifier: GPL-2.0 2 3 //! Rust based implementation of the cpufreq-dt driver. 4 5 use kernel::{ 6 c_str, 7 clk::Clk, 8 cpu, cpufreq, 9 cpumask::CpumaskVar, 10 device::{Core, Device}, 11 error::code::*, 12 macros::vtable, 13 module_platform_driver, of, opp, platform, 14 prelude::*, 15 str::CString, 16 sync::Arc, 17 }; 18 19 /// Finds exact supply name from the OF node. 20 fn find_supply_name_exact(dev: &Device, name: &str) -> Option<CString> { 21 let prop_name = CString::try_from_fmt(fmt!("{name}-supply")).ok()?; 22 dev.fwnode()? 23 .property_present(&prop_name) 24 .then(|| CString::try_from_fmt(fmt!("{name}")).ok()) 25 .flatten() 26 } 27 28 /// Finds supply name for the CPU from DT. 29 fn find_supply_names(dev: &Device, cpu: cpu::CpuId) -> Option<KVec<CString>> { 30 // Try "cpu0" for older DTs, fallback to "cpu". 31 (cpu.as_u32() == 0) 32 .then(|| find_supply_name_exact(dev, "cpu0")) 33 .flatten() 34 .or_else(|| find_supply_name_exact(dev, "cpu")) 35 .and_then(|name| kernel::kvec![name].ok()) 36 } 37 38 /// Represents the cpufreq dt device. 39 struct CPUFreqDTDevice { 40 opp_table: opp::Table, 41 freq_table: opp::FreqTable, 42 _mask: CpumaskVar, 43 _token: Option<opp::ConfigToken>, 44 _clk: Clk, 45 } 46 47 #[derive(Default)] 48 struct CPUFreqDTDriver; 49 50 #[vtable] 51 impl opp::ConfigOps for CPUFreqDTDriver {} 52 53 #[vtable] 54 impl cpufreq::Driver for CPUFreqDTDriver { 55 const NAME: &'static CStr = c_str!("cpufreq-dt"); 56 const FLAGS: u16 = cpufreq::flags::NEED_INITIAL_FREQ_CHECK | cpufreq::flags::IS_COOLING_DEV; 57 const BOOST_ENABLED: bool = true; 58 59 type PData = Arc<CPUFreqDTDevice>; 60 61 fn init(policy: &mut cpufreq::Policy) -> Result<Self::PData> { 62 let cpu = policy.cpu(); 63 // SAFETY: The CPU device is only used during init; it won't get hot-unplugged. The cpufreq 64 // core registers with CPU notifiers and the cpufreq core/driver won't use the CPU device, 65 // once the CPU is hot-unplugged. 66 let dev = unsafe { cpu::from_cpu(cpu)? }; 67 let mut mask = CpumaskVar::new_zero(GFP_KERNEL)?; 68 69 mask.set(cpu); 70 71 let token = find_supply_names(dev, cpu) 72 .map(|names| { 73 opp::Config::<Self>::new() 74 .set_regulator_names(names)? 75 .set(dev) 76 }) 77 .transpose()?; 78 79 // Get OPP-sharing information from "operating-points-v2" bindings. 80 let fallback = match opp::Table::of_sharing_cpus(dev, &mut mask) { 81 Ok(()) => false, 82 Err(e) if e == ENOENT => { 83 // "operating-points-v2" not supported. If the platform hasn't 84 // set sharing CPUs, fallback to all CPUs share the `Policy` 85 // for backward compatibility. 86 opp::Table::sharing_cpus(dev, &mut mask).is_err() 87 } 88 Err(e) => return Err(e), 89 }; 90 91 // Initialize OPP tables for all policy cpus. 92 // 93 // For platforms not using "operating-points-v2" bindings, we do this 94 // before updating policy cpus. Otherwise, we will end up creating 95 // duplicate OPPs for the CPUs. 96 // 97 // OPPs might be populated at runtime, don't fail for error here unless 98 // it is -EPROBE_DEFER. 99 let mut opp_table = match opp::Table::from_of_cpumask(dev, &mut mask) { 100 Ok(table) => table, 101 Err(e) => { 102 if e == EPROBE_DEFER { 103 return Err(e); 104 } 105 106 // The table is added dynamically ? 107 opp::Table::from_dev(dev)? 108 } 109 }; 110 111 // The OPP table must be initialized, statically or dynamically, by this point. 112 opp_table.opp_count()?; 113 114 // Set sharing cpus for fallback scenario. 115 if fallback { 116 mask.setall(); 117 opp_table.set_sharing_cpus(&mut mask)?; 118 } 119 120 let mut transition_latency = opp_table.max_transition_latency_ns() as u32; 121 if transition_latency == 0 { 122 transition_latency = cpufreq::DEFAULT_TRANSITION_LATENCY_NS; 123 } 124 125 policy 126 .set_dvfs_possible_from_any_cpu(true) 127 .set_suspend_freq(opp_table.suspend_freq()) 128 .set_transition_latency_ns(transition_latency); 129 130 let freq_table = opp_table.cpufreq_table()?; 131 // SAFETY: The `freq_table` is not dropped while it is getting used by the C code. 132 unsafe { policy.set_freq_table(&freq_table) }; 133 134 // SAFETY: The returned `clk` is not dropped while it is getting used by the C code. 135 let clk = unsafe { policy.set_clk(dev, None)? }; 136 137 mask.copy(policy.cpus()); 138 139 Ok(Arc::new( 140 CPUFreqDTDevice { 141 opp_table, 142 freq_table, 143 _mask: mask, 144 _token: token, 145 _clk: clk, 146 }, 147 GFP_KERNEL, 148 )?) 149 } 150 151 fn exit(_policy: &mut cpufreq::Policy, _data: Option<Self::PData>) -> Result { 152 Ok(()) 153 } 154 155 fn online(_policy: &mut cpufreq::Policy) -> Result { 156 // We did light-weight tear down earlier, nothing to do here. 157 Ok(()) 158 } 159 160 fn offline(_policy: &mut cpufreq::Policy) -> Result { 161 // Preserve policy->data and don't free resources on light-weight 162 // tear down. 163 Ok(()) 164 } 165 166 fn suspend(policy: &mut cpufreq::Policy) -> Result { 167 policy.generic_suspend() 168 } 169 170 fn verify(data: &mut cpufreq::PolicyData) -> Result { 171 data.generic_verify() 172 } 173 174 fn target_index(policy: &mut cpufreq::Policy, index: cpufreq::TableIndex) -> Result { 175 let Some(data) = policy.data::<Self::PData>() else { 176 return Err(ENOENT); 177 }; 178 179 let freq = data.freq_table.freq(index)?; 180 data.opp_table.set_rate(freq) 181 } 182 183 fn get(policy: &mut cpufreq::Policy) -> Result<u32> { 184 policy.generic_get() 185 } 186 187 fn set_boost(_policy: &mut cpufreq::Policy, _state: i32) -> Result { 188 Ok(()) 189 } 190 191 fn register_em(policy: &mut cpufreq::Policy) { 192 policy.register_em_opp() 193 } 194 } 195 196 kernel::of_device_table!( 197 OF_TABLE, 198 MODULE_OF_TABLE, 199 <CPUFreqDTDriver as platform::Driver>::IdInfo, 200 [(of::DeviceId::new(c_str!("operating-points-v2")), ())] 201 ); 202 203 impl platform::Driver for CPUFreqDTDriver { 204 type IdInfo = (); 205 const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE); 206 207 fn probe( 208 pdev: &platform::Device<Core>, 209 _id_info: Option<&Self::IdInfo>, 210 ) -> Result<Pin<KBox<Self>>> { 211 cpufreq::Registration::<CPUFreqDTDriver>::new_foreign_owned(pdev.as_ref())?; 212 Ok(KBox::new(Self {}, GFP_KERNEL)?.into()) 213 } 214 } 215 216 module_platform_driver! { 217 type: CPUFreqDTDriver, 218 name: "cpufreq-dt", 219 authors: ["Viresh Kumar <viresh.kumar@linaro.org>"], 220 description: "Generic CPUFreq DT driver", 221 license: "GPL v2", 222 } 223