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