projects/linux·blob master · refs

367 lines · 10.7 KiB

1// SPDX-License-Identifier: GPL-2.0
2// Copyright (c) 2025 Samsung Electronics Co., Ltd.
3// Author: Michal Wilczynski <[email protected]>
4
5//! Rust T-HEAD TH1520 PWM driver
6//!
7//! Limitations:
8//! - The period and duty cycle are controlled by 32-bit hardware registers,
9//! limiting the maximum resolution.
10//! - The driver supports continuous output mode only; one-shot mode is not
11//! implemented.
12//! - The controller hardware provides up to 6 PWM channels.
13//! - Reconfiguration is glitch free - new period and duty cycle values are
14//! latched and take effect at the start of the next period.
15//! - Polarity is handled via a simple hardware inversion bit; arbitrary
16//! duty cycle offsets are not supported.
17//! - Disabling a channel is achieved by configuring its duty cycle to zero to
18//! produce a static low output. Clearing the `start` does not reliably
19//! force the static inactive level defined by the `INACTOUT` bit. Hence
20//! this method is not used in this driver.
21//!
22
23use kernel::{
24 clk::Clk,
25 device::{
26 Bound,
27 Core,
28 Device, //
29 },
30 io::{
31 mem::DevresIoMem,
32 Io, //
33 },
34 of,
35 platform,
36 prelude::*,
37 pwm,
38 time, //
39};
40
41const TH1520_MAX_PWM_NUM: u32 = 6;
42
43// Register offsets
44const fn th1520_pwm_chn_base(n: u32) -> usize {
45 (n * 0x20) as usize
46}
47
48const fn th1520_pwm_ctrl(n: u32) -> usize {
49 th1520_pwm_chn_base(n)
50}
51
52const fn th1520_pwm_per(n: u32) -> usize {
53 th1520_pwm_chn_base(n) + 0x08
54}
55
56const fn th1520_pwm_fp(n: u32) -> usize {
57 th1520_pwm_chn_base(n) + 0x0c
58}
59
60// Control register bits
61const TH1520_PWM_START: u32 = 1 << 0;
62const TH1520_PWM_CFG_UPDATE: u32 = 1 << 2;
63const TH1520_PWM_CONTINUOUS_MODE: u32 = 1 << 5;
64const TH1520_PWM_FPOUT: u32 = 1 << 8;
65
66const TH1520_PWM_REG_SIZE: usize = 0xB0;
67
68fn ns_to_cycles(ns: u64, rate_hz: u64) -> u64 {
69 const NSEC_PER_SEC_U64: u64 = time::NSEC_PER_SEC as u64;
70
71 ns.saturating_mul(rate_hz) / NSEC_PER_SEC_U64
72}
73
74fn cycles_to_ns(cycles: u32, rate_hz: u64) -> u64 {
75 const NSEC_PER_SEC_U64: u64 = time::NSEC_PER_SEC as u64;
76
77 (u64::from(cycles) * NSEC_PER_SEC_U64).div_ceil(rate_hz)
78}
79
80/// Hardware-specific waveform representation for TH1520.
81#[derive(Copy, Clone, Debug, Default)]
82struct Th1520WfHw {
83 period_cycles: u32,
84 duty_cycles: u32,
85 ctrl_val: u32,
86 enabled: bool,
87}
88
89/// The driver's private data struct. It holds all necessary devres managed resources.
90#[pin_data(PinnedDrop)]
91struct Th1520PwmDriverData {
92 #[pin]
93 iomem: DevresIoMem<TH1520_PWM_REG_SIZE>,
94 clk: Clk,
95}
96
97impl pwm::PwmOps for Th1520PwmDriverData {
98 type WfHw = Th1520WfHw;
99
100 fn round_waveform_tohw(
101 chip: &pwm::Chip<Self>,
102 _pwm: &pwm::Device,
103 wf: &pwm::Waveform,
104 ) -> Result<pwm::RoundedWaveform<Self::WfHw>> {
105 let data = chip.drvdata();
106 let mut status = 0;
107
108 if wf.period_length_ns == 0 {
109 dev_dbg!(chip.device(), "Requested period is 0, disabling PWM.\n");
110
111 return Ok(pwm::RoundedWaveform {
112 status: 0,
113 hardware_waveform: Th1520WfHw {
114 enabled: false,
115 ..Default::default()
116 },
117 });
118 }
119
120 let rate_hz = data.clk.rate().as_hz() as u64;
121
122 let mut period_cycles = ns_to_cycles(wf.period_length_ns, rate_hz).min(u64::from(u32::MAX));
123
124 if period_cycles == 0 {
125 dev_dbg!(
126 chip.device(),
127 "Requested period {} ns is too small for clock rate {} Hz, rounding up.\n",
128 wf.period_length_ns,
129 rate_hz
130 );
131
132 period_cycles = 1;
133 status = 1;
134 }
135
136 let mut duty_cycles = ns_to_cycles(wf.duty_length_ns, rate_hz).min(u64::from(u32::MAX));
137
138 let mut ctrl_val = TH1520_PWM_CONTINUOUS_MODE;
139
140 let is_inversed = wf.duty_length_ns > 0
141 && wf.duty_offset_ns > 0
142 && wf.duty_offset_ns >= wf.period_length_ns.saturating_sub(wf.duty_length_ns);
143 if is_inversed {
144 duty_cycles = period_cycles - duty_cycles;
145 } else {
146 ctrl_val |= TH1520_PWM_FPOUT;
147 }
148
149 let wfhw = Th1520WfHw {
150 // The cast is safe because the value was clamped with `.min(u64::from(u32::MAX))`.
151 period_cycles: period_cycles as u32,
152 duty_cycles: duty_cycles as u32,
153 ctrl_val,
154 enabled: true,
155 };
156
157 dev_dbg!(
158 chip.device(),
159 "Requested: {}/{} ns [+{} ns] -> HW: {}/{} cycles, ctrl 0x{:x}, rate {} Hz\n",
160 wf.duty_length_ns,
161 wf.period_length_ns,
162 wf.duty_offset_ns,
163 wfhw.duty_cycles,
164 wfhw.period_cycles,
165 wfhw.ctrl_val,
166 rate_hz
167 );
168
169 Ok(pwm::RoundedWaveform {
170 status,
171 hardware_waveform: wfhw,
172 })
173 }
174
175 fn round_waveform_fromhw(
176 chip: &pwm::Chip<Self>,
177 _pwm: &pwm::Device,
178 wfhw: &Self::WfHw,
179 wf: &mut pwm::Waveform,
180 ) -> Result {
181 let data = chip.drvdata();
182 let rate_hz = data.clk.rate().as_hz() as u64;
183
184 if wfhw.period_cycles == 0 {
185 dev_dbg!(
186 chip.device(),
187 "HW state has zero period, reporting as disabled.\n"
188 );
189 *wf = pwm::Waveform::default();
190 return Ok(());
191 }
192
193 wf.period_length_ns = cycles_to_ns(wfhw.period_cycles, rate_hz);
194
195 let duty_cycles = wfhw.duty_cycles;
196
197 if (wfhw.ctrl_val & TH1520_PWM_FPOUT) != 0 {
198 wf.duty_length_ns = cycles_to_ns(duty_cycles, rate_hz);
199 wf.duty_offset_ns = 0;
200 } else {
201 let period_cycles = wfhw.period_cycles;
202 let original_duty_cycles = period_cycles.saturating_sub(duty_cycles);
203
204 // For an inverted signal, `duty_length_ns` is the high time (period - low_time).
205 wf.duty_length_ns = cycles_to_ns(original_duty_cycles, rate_hz);
206 // The offset is the initial low time, which is what the hardware register provides.
207 wf.duty_offset_ns = cycles_to_ns(duty_cycles, rate_hz);
208 }
209
210 Ok(())
211 }
212
213 fn read_waveform(
214 chip: &pwm::Chip<Self>,
215 pwm: &pwm::Device,
216 parent_dev: &Device<Bound>,
217 ) -> Result<Self::WfHw> {
218 let data = chip.drvdata();
219 let hwpwm = pwm.hwpwm();
220 let iomap = data.iomem.access(parent_dev)?;
221
222 let ctrl = iomap.try_read32(th1520_pwm_ctrl(hwpwm))?;
223 let period_cycles = iomap.try_read32(th1520_pwm_per(hwpwm))?;
224 let duty_cycles = iomap.try_read32(th1520_pwm_fp(hwpwm))?;
225
226 let wfhw = Th1520WfHw {
227 period_cycles,
228 duty_cycles,
229 ctrl_val: ctrl,
230 enabled: duty_cycles != 0,
231 };
232
233 dev_dbg!(
234 chip.device(),
235 "PWM-{}: read_waveform: Read hw state - period: {}, duty: {}, ctrl: 0x{:x}, enabled: {}",
236 hwpwm,
237 wfhw.period_cycles,
238 wfhw.duty_cycles,
239 wfhw.ctrl_val,
240 wfhw.enabled
241 );
242
243 Ok(wfhw)
244 }
245
246 fn write_waveform(
247 chip: &pwm::Chip<Self>,
248 pwm: &pwm::Device,
249 wfhw: &Self::WfHw,
250 parent_dev: &Device<Bound>,
251 ) -> Result {
252 let data = chip.drvdata();
253 let hwpwm = pwm.hwpwm();
254 let iomap = data.iomem.access(parent_dev)?;
255 let duty_cycles = iomap.try_read32(th1520_pwm_fp(hwpwm))?;
256 let was_enabled = duty_cycles != 0;
257
258 if !wfhw.enabled {
259 dev_dbg!(chip.device(), "PWM-{}: Disabling channel.\n", hwpwm);
260 if was_enabled {
261 iomap.try_write32(wfhw.ctrl_val, th1520_pwm_ctrl(hwpwm))?;
262 iomap.try_write32(0, th1520_pwm_fp(hwpwm))?;
263 iomap.try_write32(
264 wfhw.ctrl_val | TH1520_PWM_CFG_UPDATE,
265 th1520_pwm_ctrl(hwpwm),
266 )?;
267 }
268 return Ok(());
269 }
270
271 iomap.try_write32(wfhw.ctrl_val, th1520_pwm_ctrl(hwpwm))?;
272 iomap.try_write32(wfhw.period_cycles, th1520_pwm_per(hwpwm))?;
273 iomap.try_write32(wfhw.duty_cycles, th1520_pwm_fp(hwpwm))?;
274 iomap.try_write32(
275 wfhw.ctrl_val | TH1520_PWM_CFG_UPDATE,
276 th1520_pwm_ctrl(hwpwm),
277 )?;
278
279 // The `TH1520_PWM_START` bit must be written in a separate, final transaction, and
280 // only when enabling the channel from a disabled state.
281 if !was_enabled {
282 iomap.try_write32(wfhw.ctrl_val | TH1520_PWM_START, th1520_pwm_ctrl(hwpwm))?;
283 }
284
285 dev_dbg!(
286 chip.device(),
287 "PWM-{}: Wrote {}/{} cycles",
288 hwpwm,
289 wfhw.duty_cycles,
290 wfhw.period_cycles,
291 );
292
293 Ok(())
294 }
295}
296
297#[pinned_drop]
298impl PinnedDrop for Th1520PwmDriverData {
299 fn drop(self: Pin<&mut Self>) {
300 self.clk.disable_unprepare();
301 }
302}
303
304struct Th1520PwmPlatformDriver;
305
306kernel::of_device_table!(
307 OF_TABLE,
308 <Th1520PwmPlatformDriver as platform::Driver>::IdInfo,
309 [(of::DeviceId::new(c"thead,th1520-pwm"), ())]
310);
311
312impl platform::Driver for Th1520PwmPlatformDriver {
313 type IdInfo = ();
314 type Data<'bound> = Self;
315 const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
316
317 fn probe<'bound>(
318 pdev: &'bound platform::Device<Core<'_>>,
319 _id_info: Option<&'bound Self::IdInfo>,
320 ) -> impl PinInit<Self, Error> + 'bound {
321 let dev = pdev.as_ref();
322 let request = pdev.io_request_by_index(0).ok_or(ENODEV)?;
323
324 let clk = Clk::get(dev, None)?;
325
326 clk.prepare_enable()?;
327
328 // TODO: Get exclusive ownership of the clock to prevent rate changes.
329 // The Rust equivalent of `clk_rate_exclusive_get()` is not yet available.
330 // This should be updated once it is implemented.
331 let rate_hz = clk.rate().as_hz();
332 if rate_hz == 0 {
333 dev_err!(dev, "Clock rate is zero\n");
334 return Err(EINVAL);
335 }
336
337 if rate_hz > time::NSEC_PER_SEC as usize {
338 dev_err!(
339 dev,
340 "Clock rate {} Hz is too high, not supported.\n",
341 rate_hz
342 );
343 return Err(EINVAL);
344 }
345
346 let chip = pwm::Chip::new(
347 dev,
348 TH1520_MAX_PWM_NUM,
349 try_pin_init!(Th1520PwmDriverData {
350 iomem <- request.iomap_sized::<TH1520_PWM_REG_SIZE>()?.into_devres(),
351 clk <- clk,
352 }),
353 )?;
354
355 chip.register()?;
356
357 Ok(Th1520PwmPlatformDriver)
358 }
359}
360
361kernel::module_pwm_platform_driver! {
362 type: Th1520PwmPlatformDriver,
363 name: "pwm-th1520",
364 authors: ["Michal Wilczynski <[email protected]>"],
365 description: "T-HEAD TH1520 PWM driver",
366 license: "GPL v2",
367}