xref: /linux/drivers/pwm/pwm_th1520.rs (revision c6e62d002b7f0613f02d8707c80f2a7bd66808a0)
1 // SPDX-License-Identifier: GPL-2.0
2 // Copyright (c) 2025 Samsung Electronics Co., Ltd.
3 // Author: Michal Wilczynski <m.wilczynski@samsung.com>
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 
23 use core::ops::Deref;
24 use kernel::{
25     clk::Clk,
26     device::{Bound, Core, Device},
27     devres,
28     io::{
29         mem::IoMem,
30         Io, //
31     },
32     of, platform,
33     prelude::*,
34     pwm, time,
35 };
36 
37 const TH1520_MAX_PWM_NUM: u32 = 6;
38 
39 // Register offsets
40 const fn th1520_pwm_chn_base(n: u32) -> usize {
41     (n * 0x20) as usize
42 }
43 
44 const fn th1520_pwm_ctrl(n: u32) -> usize {
45     th1520_pwm_chn_base(n)
46 }
47 
48 const fn th1520_pwm_per(n: u32) -> usize {
49     th1520_pwm_chn_base(n) + 0x08
50 }
51 
52 const fn th1520_pwm_fp(n: u32) -> usize {
53     th1520_pwm_chn_base(n) + 0x0c
54 }
55 
56 // Control register bits
57 const TH1520_PWM_START: u32 = 1 << 0;
58 const TH1520_PWM_CFG_UPDATE: u32 = 1 << 2;
59 const TH1520_PWM_CONTINUOUS_MODE: u32 = 1 << 5;
60 const TH1520_PWM_FPOUT: u32 = 1 << 8;
61 
62 const TH1520_PWM_REG_SIZE: usize = 0xB0;
63 
64 fn ns_to_cycles(ns: u64, rate_hz: u64) -> u64 {
65     const NSEC_PER_SEC_U64: u64 = time::NSEC_PER_SEC as u64;
66 
67     (match ns.checked_mul(rate_hz) {
68         Some(product) => product,
69         None => u64::MAX,
70     }) / NSEC_PER_SEC_U64
71 }
72 
73 fn cycles_to_ns(cycles: u64, rate_hz: u64) -> u64 {
74     const NSEC_PER_SEC_U64: u64 = time::NSEC_PER_SEC as u64;
75 
76     // TODO: Replace with a kernel helper like `mul_u64_u64_div_u64_roundup`
77     // once available in Rust.
78     let numerator = cycles
79         .saturating_mul(NSEC_PER_SEC_U64)
80         .saturating_add(rate_hz - 1);
81 
82     numerator / rate_hz
83 }
84 
85 /// Hardware-specific waveform representation for TH1520.
86 #[derive(Copy, Clone, Debug, Default)]
87 struct Th1520WfHw {
88     period_cycles: u32,
89     duty_cycles: u32,
90     ctrl_val: u32,
91     enabled: bool,
92 }
93 
94 /// The driver's private data struct. It holds all necessary devres managed resources.
95 #[pin_data(PinnedDrop)]
96 struct Th1520PwmDriverData {
97     #[pin]
98     iomem: devres::Devres<IoMem<TH1520_PWM_REG_SIZE>>,
99     clk: Clk,
100 }
101 
102 // This `unsafe` implementation is a temporary necessity because the underlying `kernel::clk::Clk`
103 // type does not yet expose `Send` and `Sync` implementations. This block should be removed
104 // as soon as the clock abstraction provides these guarantees directly.
105 // TODO: Remove those unsafe impl's when Clk will support them itself.
106 
107 // SAFETY: The `devres` framework requires the driver's private data to be `Send` and `Sync`.
108 // We can guarantee this because the PWM core synchronizes all callbacks, preventing concurrent
109 // access to the contained `iomem` and `clk` resources.
110 unsafe impl Send for Th1520PwmDriverData {}
111 
112 // SAFETY: The same reasoning applies as for `Send`. The PWM core's synchronization
113 // guarantees that it is safe for multiple threads to have shared access (`&self`)
114 // to the driver data during callbacks.
115 unsafe impl Sync for Th1520PwmDriverData {}
116 
117 impl pwm::PwmOps for Th1520PwmDriverData {
118     type WfHw = Th1520WfHw;
119 
120     fn round_waveform_tohw(
121         chip: &pwm::Chip<Self>,
122         _pwm: &pwm::Device,
123         wf: &pwm::Waveform,
124     ) -> Result<pwm::RoundedWaveform<Self::WfHw>> {
125         let data = chip.drvdata();
126         let mut status = 0;
127 
128         if wf.period_length_ns == 0 {
129             dev_dbg!(chip.device(), "Requested period is 0, disabling PWM.\n");
130 
131             return Ok(pwm::RoundedWaveform {
132                 status: 0,
133                 hardware_waveform: Th1520WfHw {
134                     enabled: false,
135                     ..Default::default()
136                 },
137             });
138         }
139 
140         let rate_hz = data.clk.rate().as_hz() as u64;
141 
142         let mut period_cycles = ns_to_cycles(wf.period_length_ns, rate_hz).min(u64::from(u32::MAX));
143 
144         if period_cycles == 0 {
145             dev_dbg!(
146                 chip.device(),
147                 "Requested period {} ns is too small for clock rate {} Hz, rounding up.\n",
148                 wf.period_length_ns,
149                 rate_hz
150             );
151 
152             period_cycles = 1;
153             status = 1;
154         }
155 
156         let mut duty_cycles = ns_to_cycles(wf.duty_length_ns, rate_hz).min(u64::from(u32::MAX));
157 
158         let mut ctrl_val = TH1520_PWM_CONTINUOUS_MODE;
159 
160         let is_inversed = wf.duty_length_ns > 0
161             && wf.duty_offset_ns > 0
162             && wf.duty_offset_ns >= wf.period_length_ns.saturating_sub(wf.duty_length_ns);
163         if is_inversed {
164             duty_cycles = period_cycles - duty_cycles;
165         } else {
166             ctrl_val |= TH1520_PWM_FPOUT;
167         }
168 
169         let wfhw = Th1520WfHw {
170             // The cast is safe because the value was clamped with `.min(u64::from(u32::MAX))`.
171             period_cycles: period_cycles as u32,
172             duty_cycles: duty_cycles as u32,
173             ctrl_val,
174             enabled: true,
175         };
176 
177         dev_dbg!(
178             chip.device(),
179             "Requested: {}/{} ns [+{} ns] -> HW: {}/{} cycles, ctrl 0x{:x}, rate {} Hz\n",
180             wf.duty_length_ns,
181             wf.period_length_ns,
182             wf.duty_offset_ns,
183             wfhw.duty_cycles,
184             wfhw.period_cycles,
185             wfhw.ctrl_val,
186             rate_hz
187         );
188 
189         Ok(pwm::RoundedWaveform {
190             status,
191             hardware_waveform: wfhw,
192         })
193     }
194 
195     fn round_waveform_fromhw(
196         chip: &pwm::Chip<Self>,
197         _pwm: &pwm::Device,
198         wfhw: &Self::WfHw,
199         wf: &mut pwm::Waveform,
200     ) -> Result {
201         let data = chip.drvdata();
202         let rate_hz = data.clk.rate().as_hz() as u64;
203 
204         if wfhw.period_cycles == 0 {
205             dev_dbg!(
206                 chip.device(),
207                 "HW state has zero period, reporting as disabled.\n"
208             );
209             *wf = pwm::Waveform::default();
210             return Ok(());
211         }
212 
213         wf.period_length_ns = cycles_to_ns(u64::from(wfhw.period_cycles), rate_hz);
214 
215         let duty_cycles = u64::from(wfhw.duty_cycles);
216 
217         if (wfhw.ctrl_val & TH1520_PWM_FPOUT) != 0 {
218             wf.duty_length_ns = cycles_to_ns(duty_cycles, rate_hz);
219             wf.duty_offset_ns = 0;
220         } else {
221             let period_cycles = u64::from(wfhw.period_cycles);
222             let original_duty_cycles = period_cycles.saturating_sub(duty_cycles);
223 
224             // For an inverted signal, `duty_length_ns` is the high time (period - low_time).
225             wf.duty_length_ns = cycles_to_ns(original_duty_cycles, rate_hz);
226             // The offset is the initial low time, which is what the hardware register provides.
227             wf.duty_offset_ns = cycles_to_ns(duty_cycles, rate_hz);
228         }
229 
230         Ok(())
231     }
232 
233     fn read_waveform(
234         chip: &pwm::Chip<Self>,
235         pwm: &pwm::Device,
236         parent_dev: &Device<Bound>,
237     ) -> Result<Self::WfHw> {
238         let data = chip.drvdata();
239         let hwpwm = pwm.hwpwm();
240         let iomem_accessor = data.iomem.access(parent_dev)?;
241         let iomap = iomem_accessor.deref();
242 
243         let ctrl = iomap.try_read32(th1520_pwm_ctrl(hwpwm))?;
244         let period_cycles = iomap.try_read32(th1520_pwm_per(hwpwm))?;
245         let duty_cycles = iomap.try_read32(th1520_pwm_fp(hwpwm))?;
246 
247         let wfhw = Th1520WfHw {
248             period_cycles,
249             duty_cycles,
250             ctrl_val: ctrl,
251             enabled: duty_cycles != 0,
252         };
253 
254         dev_dbg!(
255             chip.device(),
256             "PWM-{}: read_waveform: Read hw state - period: {}, duty: {}, ctrl: 0x{:x}, enabled: {}",
257             hwpwm,
258             wfhw.period_cycles,
259             wfhw.duty_cycles,
260             wfhw.ctrl_val,
261             wfhw.enabled
262         );
263 
264         Ok(wfhw)
265     }
266 
267     fn write_waveform(
268         chip: &pwm::Chip<Self>,
269         pwm: &pwm::Device,
270         wfhw: &Self::WfHw,
271         parent_dev: &Device<Bound>,
272     ) -> Result {
273         let data = chip.drvdata();
274         let hwpwm = pwm.hwpwm();
275         let iomem_accessor = data.iomem.access(parent_dev)?;
276         let iomap = iomem_accessor.deref();
277         let duty_cycles = iomap.try_read32(th1520_pwm_fp(hwpwm))?;
278         let was_enabled = duty_cycles != 0;
279 
280         if !wfhw.enabled {
281             dev_dbg!(chip.device(), "PWM-{}: Disabling channel.\n", hwpwm);
282             if was_enabled {
283                 iomap.try_write32(wfhw.ctrl_val, th1520_pwm_ctrl(hwpwm))?;
284                 iomap.try_write32(0, th1520_pwm_fp(hwpwm))?;
285                 iomap.try_write32(
286                     wfhw.ctrl_val | TH1520_PWM_CFG_UPDATE,
287                     th1520_pwm_ctrl(hwpwm),
288                 )?;
289             }
290             return Ok(());
291         }
292 
293         iomap.try_write32(wfhw.ctrl_val, th1520_pwm_ctrl(hwpwm))?;
294         iomap.try_write32(wfhw.period_cycles, th1520_pwm_per(hwpwm))?;
295         iomap.try_write32(wfhw.duty_cycles, th1520_pwm_fp(hwpwm))?;
296         iomap.try_write32(
297             wfhw.ctrl_val | TH1520_PWM_CFG_UPDATE,
298             th1520_pwm_ctrl(hwpwm),
299         )?;
300 
301         // The `TH1520_PWM_START` bit must be written in a separate, final transaction, and
302         // only when enabling the channel from a disabled state.
303         if !was_enabled {
304             iomap.try_write32(wfhw.ctrl_val | TH1520_PWM_START, th1520_pwm_ctrl(hwpwm))?;
305         }
306 
307         dev_dbg!(
308             chip.device(),
309             "PWM-{}: Wrote {}/{} cycles",
310             hwpwm,
311             wfhw.duty_cycles,
312             wfhw.period_cycles,
313         );
314 
315         Ok(())
316     }
317 }
318 
319 #[pinned_drop]
320 impl PinnedDrop for Th1520PwmDriverData {
321     fn drop(self: Pin<&mut Self>) {
322         self.clk.disable_unprepare();
323     }
324 }
325 
326 struct Th1520PwmPlatformDriver;
327 
328 kernel::of_device_table!(
329     OF_TABLE,
330     MODULE_OF_TABLE,
331     <Th1520PwmPlatformDriver as platform::Driver>::IdInfo,
332     [(of::DeviceId::new(c"thead,th1520-pwm"), ())]
333 );
334 
335 impl platform::Driver for Th1520PwmPlatformDriver {
336     type IdInfo = ();
337     const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
338 
339     fn probe(
340         pdev: &platform::Device<Core>,
341         _id_info: Option<&Self::IdInfo>,
342     ) -> impl PinInit<Self, Error> {
343         let dev = pdev.as_ref();
344         let request = pdev.io_request_by_index(0).ok_or(ENODEV)?;
345 
346         let clk = Clk::get(dev, None)?;
347 
348         clk.prepare_enable()?;
349 
350         // TODO: Get exclusive ownership of the clock to prevent rate changes.
351         // The Rust equivalent of `clk_rate_exclusive_get()` is not yet available.
352         // This should be updated once it is implemented.
353         let rate_hz = clk.rate().as_hz();
354         if rate_hz == 0 {
355             dev_err!(dev, "Clock rate is zero\n");
356             return Err(EINVAL);
357         }
358 
359         if rate_hz > time::NSEC_PER_SEC as usize {
360             dev_err!(
361                 dev,
362                 "Clock rate {} Hz is too high, not supported.\n",
363                 rate_hz
364             );
365             return Err(EINVAL);
366         }
367 
368         let chip = pwm::Chip::new(
369             dev,
370             TH1520_MAX_PWM_NUM,
371             try_pin_init!(Th1520PwmDriverData {
372                 iomem <- request.iomap_sized::<TH1520_PWM_REG_SIZE>(),
373                 clk <- clk,
374             }),
375         )?;
376 
377         chip.register()?;
378 
379         Ok(Th1520PwmPlatformDriver)
380     }
381 }
382 
383 kernel::module_pwm_platform_driver! {
384     type: Th1520PwmPlatformDriver,
385     name: "pwm-th1520",
386     authors: ["Michal Wilczynski <m.wilczynski@samsung.com>"],
387     description: "T-HEAD TH1520 PWM driver",
388     license: "GPL v2",
389 }
390