xref: /linux/drivers/cpufreq/rcpufreq_dt.rs (revision 6f7e6393d1ce636bb7ec77a7fe7b77458fddf701)
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