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