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