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