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