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