Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0
2
3//! Rust based implementation of the cpufreq-dt driver.
4
5use 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.
19fn 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.
28fn 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.
38struct 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)]
47struct CPUFreqDTDriver;
48
49#[vtable]
50impl opp::ConfigOps for CPUFreqDTDriver {}
51
52#[vtable]
53impl 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
195kernel::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
202impl 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
215module_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}