xref: /linux/drivers/cpufreq/rcpufreq_dt.rs (revision abdf766d149c51fb256118f73be947d7a82f702e)
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.
find_supply_name_exact(dev: &Device, name: &str) -> Option<CString>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.
find_supply_names(dev: &Device, cpu: cpu::CpuId) -> Option<KVec<CString>>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 
init(policy: &mut cpufreq::Policy) -> Result<Self::PData>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 
exit(_policy: &mut cpufreq::Policy, _data: Option<Self::PData>) -> Result151     fn exit(_policy: &mut cpufreq::Policy, _data: Option<Self::PData>) -> Result {
152         Ok(())
153     }
154 
online(_policy: &mut cpufreq::Policy) -> Result155     fn online(_policy: &mut cpufreq::Policy) -> Result {
156         // We did light-weight tear down earlier, nothing to do here.
157         Ok(())
158     }
159 
offline(_policy: &mut cpufreq::Policy) -> Result160     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 
suspend(policy: &mut cpufreq::Policy) -> Result166     fn suspend(policy: &mut cpufreq::Policy) -> Result {
167         policy.generic_suspend()
168     }
169 
verify(data: &mut cpufreq::PolicyData) -> Result170     fn verify(data: &mut cpufreq::PolicyData) -> Result {
171         data.generic_verify()
172     }
173 
target_index(policy: &mut cpufreq::Policy, index: cpufreq::TableIndex) -> Result174     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 
get(policy: &mut cpufreq::Policy) -> Result<u32>183     fn get(policy: &mut cpufreq::Policy) -> Result<u32> {
184         policy.generic_get()
185     }
186 
set_boost(_policy: &mut cpufreq::Policy, _state: i32) -> Result187     fn set_boost(_policy: &mut cpufreq::Policy, _state: i32) -> Result {
188         Ok(())
189     }
190 
register_em(policy: &mut cpufreq::Policy)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 
probe( pdev: &platform::Device<Core>, _id_info: Option<&Self::IdInfo>, ) -> Result<Pin<KBox<Self>>>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