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