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