File Index
Files and directories
Sign in
FILE

pwm_th1520.rs

File preview with syntax highlighting.

linux-6.19.14/drivers/pwm/pwm_th1520.rs RUST
  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
 23use core::ops::Deref;
 24use 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
 35const TH1520_MAX_PWM_NUM: u32 = 6;
 36
 37// Register offsets
 38const fn th1520_pwm_chn_base(n: u32) -> usize {
 39    (n * 0x20) as usize
 40}
 41
 42const fn th1520_pwm_ctrl(n: u32) -> usize {
 43    th1520_pwm_chn_base(n)
 44}
 45
 46const fn th1520_pwm_per(n: u32) -> usize {
 47    th1520_pwm_chn_base(n) + 0x08
 48}
 49
 50const fn th1520_pwm_fp(n: u32) -> usize {
 51    th1520_pwm_chn_base(n) + 0x0c
 52}
 53
 54// Control register bits
 55const TH1520_PWM_START: u32 = 1 << 0;
 56const TH1520_PWM_CFG_UPDATE: u32 = 1 << 2;
 57const TH1520_PWM_CONTINUOUS_MODE: u32 = 1 << 5;
 58const TH1520_PWM_FPOUT: u32 = 1 << 8;
 59
 60const TH1520_PWM_REG_SIZE: usize = 0xB0;
 61
 62fn 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
 71fn 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)]
 85struct 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)]
 94struct 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.
108unsafe 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.
113unsafe impl Sync for Th1520PwmDriverData {}
114
115impl pwm::PwmOps for Th1520PwmDriverData {
116    type WfHw = Th1520WfHw;
117
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
193    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
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
265    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]
318impl PinnedDrop for Th1520PwmDriverData {
319    fn drop(self: Pin<&mut Self>) {
320        self.clk.disable_unprepare();
321    }
322}
323
324struct Th1520PwmPlatformDriver;
325
326kernel::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
333impl platform::Driver for Th1520PwmPlatformDriver {
334    type IdInfo = ();
335    const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
336
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
381kernel::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}
~/ Read-only file preview.