Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

pwm: Add Rust driver for T-HEAD TH1520 SoC

Introduce a PWM driver for the T-HEAD TH1520 SoC, written in Rust and
utilizing the safe PWM abstractions from the preceding commit.

The driver implements the pwm::PwmOps trait using the modern waveform
API (round_waveform_tohw, write_waveform, etc.) to support configuration
of period, duty cycle, and polarity for the TH1520's PWM channels.

Resource management is handled using idiomatic Rust patterns. The PWM
chip object is allocated via pwm::Chip::new and its registration with
the PWM core is managed by the pwm::Registration RAII guard. This
ensures pwmchip_remove is always called when the driver unbinds,
preventing resource leaks. Device managed resources are used for the
MMIO region, and the clock lifecycle is correctly managed in the
driver's private data Drop implementation.

The driver's core logic is written entirely in safe Rust, with no unsafe
blocks, except for the Send and Sync implementations for the driver
data, which are explained in the comments.

Reviewed-by: Elle Rhumsaa <elle@weathered-steel.dev>
Signed-off-by: Michal Wilczynski <m.wilczynski@samsung.com>
Link: https://patch.msgid.link/20251016-rust-next-pwm-working-fan-for-sending-v16-4-a5df2405d2bd@samsung.com
Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>

authored by

Michal Wilczynski and committed by
Uwe Kleine-König
e03724aa 51b4c0f9

+391
+1
MAINTAINERS
··· 22189 22189 F: drivers/pmdomain/thead/ 22190 22190 F: drivers/power/reset/th1520-aon-reboot.c 22191 22191 F: drivers/power/sequencing/pwrseq-thead-gpu.c 22192 + F: drivers/pwm/pwm_th1520.rs 22192 22193 F: drivers/reset/reset-th1520.c 22193 22194 F: include/dt-bindings/clock/thead,th1520-clk-ap.h 22194 22195 F: include/dt-bindings/power/thead,th1520-power.h
+11
drivers/pwm/Kconfig
··· 748 748 To compile this driver as a module, choose M here: the module 749 749 will be called pwm-tegra. 750 750 751 + config PWM_TH1520 752 + tristate "TH1520 PWM support" 753 + depends on RUST 754 + select RUST_PWM_ABSTRACTIONS 755 + help 756 + This option enables the driver for the PWM controller found on the 757 + T-HEAD TH1520 SoC. 758 + 759 + To compile this driver as a module, choose M here; the module 760 + will be called pwm-th1520. If you are unsure, say N. 761 + 751 762 config PWM_TIECAP 752 763 tristate "ECAP PWM support" 753 764 depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX || ARCH_KEYSTONE || ARCH_K3 || COMPILE_TEST
+1
drivers/pwm/Makefile
··· 68 68 obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o 69 69 obj-$(CONFIG_PWM_SUNPLUS) += pwm-sunplus.o 70 70 obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o 71 + obj-$(CONFIG_PWM_TH1520) += pwm_th1520.o 71 72 obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o 72 73 obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o 73 74 obj-$(CONFIG_PWM_TWL) += pwm-twl.o
+378
drivers/pwm/pwm_th1520.rs
··· 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 38 + const fn th1520_pwm_chn_base(n: u32) -> usize { 39 + (n * 0x20) as usize 40 + } 41 + 42 + const fn th1520_pwm_ctrl(n: u32) -> usize { 43 + th1520_pwm_chn_base(n) 44 + } 45 + 46 + const fn th1520_pwm_per(n: u32) -> usize { 47 + th1520_pwm_chn_base(n) + 0x08 48 + } 49 + 50 + 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 + 62 + 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 + 71 + 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 + 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: 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!(chip.device(), "HW state has zero period, reporting as disabled.\n"); 204 + *wf = pwm::Waveform::default(); 205 + return Ok(()); 206 + } 207 + 208 + wf.period_length_ns = cycles_to_ns(u64::from(wfhw.period_cycles), rate_hz); 209 + 210 + let duty_cycles = u64::from(wfhw.duty_cycles); 211 + 212 + if (wfhw.ctrl_val & TH1520_PWM_FPOUT) != 0 { 213 + wf.duty_length_ns = cycles_to_ns(duty_cycles, rate_hz); 214 + wf.duty_offset_ns = 0; 215 + } else { 216 + let period_cycles = u64::from(wfhw.period_cycles); 217 + let original_duty_cycles = period_cycles.saturating_sub(duty_cycles); 218 + 219 + // For an inverted signal, `duty_length_ns` is the high time (period - low_time). 220 + wf.duty_length_ns = cycles_to_ns(original_duty_cycles, rate_hz); 221 + // The offset is the initial low time, which is what the hardware register provides. 222 + wf.duty_offset_ns = cycles_to_ns(duty_cycles, rate_hz); 223 + } 224 + 225 + Ok(()) 226 + } 227 + 228 + fn read_waveform( 229 + chip: &pwm::Chip<Self>, 230 + pwm: &pwm::Device, 231 + parent_dev: &Device<Bound>, 232 + ) -> Result<Self::WfHw> { 233 + let data = chip.drvdata(); 234 + let hwpwm = pwm.hwpwm(); 235 + let iomem_accessor = data.iomem.access(parent_dev)?; 236 + let iomap = iomem_accessor.deref(); 237 + 238 + let ctrl = iomap.try_read32(th1520_pwm_ctrl(hwpwm))?; 239 + let period_cycles = iomap.try_read32(th1520_pwm_per(hwpwm))?; 240 + let duty_cycles = iomap.try_read32(th1520_pwm_fp(hwpwm))?; 241 + 242 + let wfhw = Th1520WfHw { 243 + period_cycles, 244 + duty_cycles, 245 + ctrl_val: ctrl, 246 + enabled: duty_cycles != 0, 247 + }; 248 + 249 + dev_dbg!( 250 + chip.device(), 251 + "PWM-{}: read_waveform: Read hw state - period: {}, duty: {}, ctrl: 0x{:x}, enabled: {}", 252 + hwpwm, 253 + wfhw.period_cycles, 254 + wfhw.duty_cycles, 255 + wfhw.ctrl_val, 256 + wfhw.enabled 257 + ); 258 + 259 + Ok(wfhw) 260 + } 261 + 262 + fn write_waveform( 263 + chip: &pwm::Chip<Self>, 264 + pwm: &pwm::Device, 265 + wfhw: &Self::WfHw, 266 + parent_dev: &Device<Bound>, 267 + ) -> Result { 268 + let data = chip.drvdata(); 269 + let hwpwm = pwm.hwpwm(); 270 + let iomem_accessor = data.iomem.access(parent_dev)?; 271 + let iomap = iomem_accessor.deref(); 272 + let duty_cycles = iomap.try_read32(th1520_pwm_fp(hwpwm))?; 273 + let was_enabled = duty_cycles != 0; 274 + 275 + if !wfhw.enabled { 276 + dev_dbg!(chip.device(), "PWM-{}: Disabling channel.\n", hwpwm); 277 + if was_enabled { 278 + iomap.try_write32(wfhw.ctrl_val, th1520_pwm_ctrl(hwpwm))?; 279 + iomap.try_write32(0, th1520_pwm_fp(hwpwm))?; 280 + iomap.try_write32(wfhw.ctrl_val | TH1520_PWM_CFG_UPDATE, th1520_pwm_ctrl(hwpwm))?; 281 + } 282 + return Ok(()); 283 + } 284 + 285 + iomap.try_write32(wfhw.ctrl_val, th1520_pwm_ctrl(hwpwm))?; 286 + iomap.try_write32(wfhw.period_cycles, th1520_pwm_per(hwpwm))?; 287 + iomap.try_write32(wfhw.duty_cycles, th1520_pwm_fp(hwpwm))?; 288 + iomap.try_write32(wfhw.ctrl_val | TH1520_PWM_CFG_UPDATE, th1520_pwm_ctrl(hwpwm))?; 289 + 290 + // The `TH1520_PWM_START` bit must be written in a separate, final transaction, and 291 + // only when enabling the channel from a disabled state. 292 + if !was_enabled { 293 + iomap.try_write32(wfhw.ctrl_val | TH1520_PWM_START, th1520_pwm_ctrl(hwpwm))?; 294 + } 295 + 296 + dev_dbg!( 297 + chip.device(), 298 + "PWM-{}: Wrote {}/{} cycles", 299 + hwpwm, 300 + wfhw.duty_cycles, 301 + wfhw.period_cycles, 302 + ); 303 + 304 + Ok(()) 305 + } 306 + } 307 + 308 + #[pinned_drop] 309 + impl PinnedDrop for Th1520PwmDriverData { 310 + fn drop(self: Pin<&mut Self>) { 311 + self.clk.disable_unprepare(); 312 + } 313 + } 314 + 315 + struct Th1520PwmPlatformDriver; 316 + 317 + kernel::of_device_table!( 318 + OF_TABLE, 319 + MODULE_OF_TABLE, 320 + <Th1520PwmPlatformDriver as platform::Driver>::IdInfo, 321 + [(of::DeviceId::new(c_str!("thead,th1520-pwm")), ())] 322 + ); 323 + 324 + impl platform::Driver for Th1520PwmPlatformDriver { 325 + type IdInfo = (); 326 + const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE); 327 + 328 + fn probe( 329 + pdev: &platform::Device<Core>, 330 + _id_info: Option<&Self::IdInfo>, 331 + ) -> Result<Pin<KBox<Self>>> { 332 + let dev = pdev.as_ref(); 333 + let request = pdev.io_request_by_index(0).ok_or(ENODEV)?; 334 + 335 + let clk = Clk::get(dev, None)?; 336 + 337 + clk.prepare_enable()?; 338 + 339 + // TODO: Get exclusive ownership of the clock to prevent rate changes. 340 + // The Rust equivalent of `clk_rate_exclusive_get()` is not yet available. 341 + // This should be updated once it is implemented. 342 + let rate_hz = clk.rate().as_hz(); 343 + if rate_hz == 0 { 344 + dev_err!(dev, "Clock rate is zero\n"); 345 + return Err(EINVAL); 346 + } 347 + 348 + if rate_hz > time::NSEC_PER_SEC as usize { 349 + dev_err!( 350 + dev, 351 + "Clock rate {} Hz is too high, not supported.\n", 352 + rate_hz 353 + ); 354 + return Err(EINVAL); 355 + } 356 + 357 + let chip = pwm::Chip::new( 358 + dev, 359 + TH1520_MAX_PWM_NUM, 360 + try_pin_init!(Th1520PwmDriverData { 361 + iomem <- request.iomap_sized::<TH1520_PWM_REG_SIZE>(), 362 + clk <- clk, 363 + }), 364 + )?; 365 + 366 + pwm::Registration::register(dev, chip)?; 367 + 368 + Ok(KBox::new(Th1520PwmPlatformDriver, GFP_KERNEL)?.into()) 369 + } 370 + } 371 + 372 + kernel::module_platform_driver! { 373 + type: Th1520PwmPlatformDriver, 374 + name: "pwm-th1520", 375 + authors: ["Michal Wilczynski <m.wilczynski@samsung.com>"], 376 + description: "T-HEAD TH1520 PWM driver", 377 + license: "GPL v2", 378 + }