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