summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-02-11 09:52:33 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2026-02-11 09:52:33 -0800
commitc371f62da7982ff4f19484a131db7a255538ad00 (patch)
tree55bea842ce28a9a5243bece26d213a9f4f687888
parente86dda7bde8801d32ffe7d1570fe173cab14d1ba (diff)
parent9321f9d27fbaf6c4f32772fc2620961a0c492135 (diff)
Merge tag 'pwm/for-7.0-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux
Pull pwm updates from Uwe Kleine-König: "There are a few patches adapting to changes in Rust land which seems to be the norm since there is a pwm driver written in Rust. Other than that just a few cleanups and a single fix for the tiehrpwm driver that came in too late for making it into v6.19. Thanks to Andy Shevchenko, Bartosz Golaszewski, Daniel Almeida and Michal Wilczynski for reviews in this cycle, and to Alice Ryhl, Ben Zong-You Xie, Gokul Praveen, Kari Argillander, Markus Probst, Raag Jadav, Shankari Anand, Tamir Duberstein and Vladimir Zapolskiy for code contributions" * tag 'pwm/for-7.0-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux: pwm: Remove redundant check in pwm_ops_check() pwm: tiehrpwm: Enable pwmchip's parent device before setting configuration pwm: Update MAINTAINER entry rust: pwm: Add __rust_helper to helpers rust: pwm: Simplify to_result call sites and unsafe blocks rust: pwm: Fix potential memory leak on init error dt-bindings: pwm: nxp,lpc32xx-pwm: Specify clocks property as mandatory pwm: th1520: Replace `kernel::c_str!` with C-Strings pwm: dwc: Use size macro pwm: Emit native configuration in /sys/kernel/debug/pwm rust: pwm: Add UnregisteredChip wrapper around Chip rust: pwm: Update ARef and AlwaysRefCounted imports to use sync::aref
-rw-r--r--Documentation/devicetree/bindings/pwm/nxp,lpc3220-pwm.yaml4
-rw-r--r--MAINTAINERS6
-rw-r--r--drivers/pwm/core.c30
-rw-r--r--drivers/pwm/pwm-dwc.c3
-rw-r--r--drivers/pwm/pwm-tiehrpwm.c6
-rw-r--r--drivers/pwm/pwm_th1520.rs5
-rw-r--r--rust/helpers/pwm.c6
-rw-r--r--rust/kernel/pwm.rs124
8 files changed, 103 insertions, 81 deletions
diff --git a/Documentation/devicetree/bindings/pwm/nxp,lpc3220-pwm.yaml b/Documentation/devicetree/bindings/pwm/nxp,lpc3220-pwm.yaml
index d8ebb0735c96..cdd83ac29caf 100644
--- a/Documentation/devicetree/bindings/pwm/nxp,lpc3220-pwm.yaml
+++ b/Documentation/devicetree/bindings/pwm/nxp,lpc3220-pwm.yaml
@@ -27,6 +27,7 @@ properties:
required:
- compatible
- reg
+ - clocks
- '#pwm-cells'
allOf:
@@ -36,9 +37,12 @@ unevaluatedProperties: false
examples:
- |
+ #include <dt-bindings/clock/lpc32xx-clock.h>
+
pwm@4005c000 {
compatible = "nxp,lpc3220-pwm";
reg = <0x4005c000 0x4>;
+ clocks = <&clk LPC32XX_CLK_PWM1>;
#pwm-cells = <3>;
};
diff --git a/MAINTAINERS b/MAINTAINERS
index 1198dd72073e..acf1e748575b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21151,16 +21151,14 @@ L: linux-pwm@vger.kernel.org
S: Maintained
Q: https://patchwork.ozlabs.org/project/linux-pwm/list/
T: git https://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux.git
-F: Documentation/devicetree/bindings/gpio/gpio-mvebu.yaml
F: Documentation/devicetree/bindings/pwm/
F: Documentation/driver-api/pwm.rst
-F: drivers/gpio/gpio-mvebu.c
F: drivers/pwm/
-F: drivers/video/backlight/pwm_bl.c
F: include/dt-bindings/pwm/
F: include/linux/pwm.h
-F: include/linux/pwm_backlight.h
K: pwm_(config|apply_might_sleep|apply_atomic|ops)
+K: (devm_)?pwmchip_(add|alloc|remove)
+K: pwm_(round|get|set)_waveform
PWM SUBSYSTEM BINDINGS [RUST]
M: Michal Wilczynski <m.wilczynski@samsung.com>
diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index ec8731515333..8da78b4b21b9 100644
--- a/drivers/pwm/core.c
+++ b/drivers/pwm/core.c
@@ -1699,8 +1699,7 @@ static bool pwm_ops_check(const struct pwm_chip *chip)
if (ops->write_waveform) {
if (!ops->round_waveform_tohw ||
- !ops->round_waveform_fromhw ||
- !ops->write_waveform)
+ !ops->round_waveform_fromhw)
return false;
if (PWM_WFHWSIZE < ops->sizeof_wfhw) {
@@ -2638,10 +2637,10 @@ static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s)
for (i = 0; i < chip->npwm; i++) {
struct pwm_device *pwm = &chip->pwms[i];
- struct pwm_state state, hwstate;
+ struct pwm_state state;
+ int err;
pwm_get_state(pwm, &state);
- pwm_get_state_hw(pwm, &hwstate);
seq_printf(s, " pwm-%-3d (%-20.20s):", i, pwm->label);
@@ -2657,9 +2656,26 @@ static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s)
seq_puts(s, ", usage_power");
seq_puts(s, "\n");
- seq_printf(s, " actual configuration: %3sabled, %llu/%llu ns, %s polarity",
- hwstate.enabled ? "en" : "dis", hwstate.duty_cycle, hwstate.period,
- hwstate.polarity ? "inverse" : "normal");
+ if (pwmchip_supports_waveform(chip)) {
+ struct pwm_waveform wf;
+
+ err = pwm_get_waveform_might_sleep(pwm, &wf);
+ if (!err)
+ seq_printf(s, " actual configuration: %lld/%lld [+%lld]",
+ wf.duty_length_ns, wf.period_length_ns, wf.duty_offset_ns);
+ else
+ seq_printf(s, " actual configuration: read out error: %pe\n", ERR_PTR(err));
+ } else {
+ struct pwm_state hwstate;
+
+ err = pwm_get_state_hw(pwm, &hwstate);
+ if (!err)
+ seq_printf(s, " actual configuration: %3sabled, %llu/%llu ns, %s polarity",
+ hwstate.enabled ? "en" : "dis", hwstate.duty_cycle, hwstate.period,
+ hwstate.polarity ? "inverse" : "normal");
+ else
+ seq_printf(s, " actual configuration: read out error: %pe", ERR_PTR(err));
+ }
seq_puts(s, "\n");
}
diff --git a/drivers/pwm/pwm-dwc.c b/drivers/pwm/pwm-dwc.c
index b6c16139ce4a..86b72db58741 100644
--- a/drivers/pwm/pwm-dwc.c
+++ b/drivers/pwm/pwm-dwc.c
@@ -22,13 +22,14 @@
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#include <linux/pwm.h>
+#include <linux/sizes.h>
#include "pwm-dwc.h"
/* Elkhart Lake */
static const struct dwc_pwm_info ehl_pwm_info = {
.nr = 2,
- .size = 0x1000,
+ .size = SZ_4K,
};
static int dwc_pwm_init_one(struct device *dev, struct dwc_pwm_drvdata *ddata, unsigned int idx)
diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
index 7a86cb090f76..2533c95b0ba9 100644
--- a/drivers/pwm/pwm-tiehrpwm.c
+++ b/drivers/pwm/pwm-tiehrpwm.c
@@ -237,8 +237,6 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
if (period_cycles < 1)
period_cycles = 1;
- pm_runtime_get_sync(pwmchip_parent(chip));
-
/* Update clock prescaler values */
ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_CLKDIV_MASK, tb_divval);
@@ -290,8 +288,6 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
if (!(duty_cycles > period_cycles))
ehrpwm_write(pc->mmio_base, cmp_reg, duty_cycles);
- pm_runtime_put_sync(pwmchip_parent(chip));
-
return 0;
}
@@ -378,6 +374,8 @@ static int ehrpwm_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
int err;
bool enabled = pwm->state.enabled;
+ guard(pm_runtime_active)(pwmchip_parent(chip));
+
if (state->polarity != pwm->state.polarity) {
if (enabled) {
ehrpwm_pwm_disable(chip, pwm);
diff --git a/drivers/pwm/pwm_th1520.rs b/drivers/pwm/pwm_th1520.rs
index e3b7e77356fc..21b4bdaf0607 100644
--- a/drivers/pwm/pwm_th1520.rs
+++ b/drivers/pwm/pwm_th1520.rs
@@ -22,7 +22,6 @@
use core::ops::Deref;
use kernel::{
- c_str,
clk::Clk,
device::{Bound, Core, Device},
devres,
@@ -327,7 +326,7 @@ kernel::of_device_table!(
OF_TABLE,
MODULE_OF_TABLE,
<Th1520PwmPlatformDriver as platform::Driver>::IdInfo,
- [(of::DeviceId::new(c_str!("thead,th1520-pwm")), ())]
+ [(of::DeviceId::new(c"thead,th1520-pwm"), ())]
);
impl platform::Driver for Th1520PwmPlatformDriver {
@@ -372,7 +371,7 @@ impl platform::Driver for Th1520PwmPlatformDriver {
}),
)?;
- pwm::Registration::register(dev, chip)?;
+ chip.register()?;
Ok(Th1520PwmPlatformDriver)
}
diff --git a/rust/helpers/pwm.c b/rust/helpers/pwm.c
index d75c58886368..eb24d2ea8e74 100644
--- a/rust/helpers/pwm.c
+++ b/rust/helpers/pwm.c
@@ -4,17 +4,17 @@
#include <linux/pwm.h>
-struct device *rust_helper_pwmchip_parent(const struct pwm_chip *chip)
+__rust_helper struct device *rust_helper_pwmchip_parent(const struct pwm_chip *chip)
{
return pwmchip_parent(chip);
}
-void *rust_helper_pwmchip_get_drvdata(struct pwm_chip *chip)
+__rust_helper void *rust_helper_pwmchip_get_drvdata(struct pwm_chip *chip)
{
return pwmchip_get_drvdata(chip);
}
-void rust_helper_pwmchip_set_drvdata(struct pwm_chip *chip, void *data)
+__rust_helper void rust_helper_pwmchip_set_drvdata(struct pwm_chip *chip, void *data)
{
pwmchip_set_drvdata(chip, data);
}
diff --git a/rust/kernel/pwm.rs b/rust/kernel/pwm.rs
index cb00f8a8765c..6c9d667009ef 100644
--- a/rust/kernel/pwm.rs
+++ b/rust/kernel/pwm.rs
@@ -13,9 +13,14 @@ use crate::{
devres,
error::{self, to_result},
prelude::*,
- types::{ARef, AlwaysRefCounted, Opaque}, //
+ sync::aref::{ARef, AlwaysRefCounted},
+ types::Opaque, //
+};
+use core::{
+ marker::PhantomData,
+ ops::Deref,
+ ptr::NonNull, //
};
-use core::{marker::PhantomData, ptr::NonNull};
/// Represents a PWM waveform configuration.
/// Mirrors struct [`struct pwm_waveform`](srctree/include/linux/pwm.h).
@@ -124,8 +129,7 @@ impl Device {
// SAFETY: `self.as_raw()` provides a valid `*mut pwm_device` pointer.
// `&c_wf` is a valid pointer to a `pwm_waveform` struct. The C function
// handles all necessary internal locking.
- let ret = unsafe { bindings::pwm_set_waveform_might_sleep(self.as_raw(), &c_wf, exact) };
- to_result(ret)
+ to_result(unsafe { bindings::pwm_set_waveform_might_sleep(self.as_raw(), &c_wf, exact) })
}
/// Queries the hardware for the configuration it would apply for a given
@@ -155,9 +159,7 @@ impl Device {
// SAFETY: `self.as_raw()` is a valid pointer. We provide a valid pointer
// to a stack-allocated `pwm_waveform` struct for the kernel to fill.
- let ret = unsafe { bindings::pwm_get_waveform_might_sleep(self.as_raw(), &mut c_wf) };
-
- to_result(ret)?;
+ to_result(unsafe { bindings::pwm_get_waveform_might_sleep(self.as_raw(), &mut c_wf) })?;
Ok(Waveform::from(c_wf))
}
@@ -173,7 +175,7 @@ pub struct RoundedWaveform<WfHw> {
}
/// Trait defining the operations for a PWM driver.
-pub trait PwmOps: 'static + Sized {
+pub trait PwmOps: 'static + Send + Sync + Sized {
/// The driver-specific hardware representation of a waveform.
///
/// This type must be [`Copy`], [`Default`], and fit within `PWM_WFHWSIZE`.
@@ -258,8 +260,8 @@ impl<T: PwmOps> Adapter<T> {
core::ptr::from_ref::<T::WfHw>(wfhw).cast::<u8>(),
wfhw_ptr.cast::<u8>(),
size,
- );
- }
+ )
+ };
Ok(())
}
@@ -279,8 +281,8 @@ impl<T: PwmOps> Adapter<T> {
wfhw_ptr.cast::<u8>(),
core::ptr::from_mut::<T::WfHw>(&mut wfhw).cast::<u8>(),
size,
- );
- }
+ )
+ };
Ok(wfhw)
}
@@ -306,9 +308,7 @@ impl<T: PwmOps> Adapter<T> {
// Now, call the original release function to free the `pwm_chip` itself.
// SAFETY: `dev` is the valid pointer passed into this callback, which is
// the expected argument for `pwmchip_release`.
- unsafe {
- bindings::pwmchip_release(dev);
- }
+ unsafe { bindings::pwmchip_release(dev) };
}
/// # Safety
@@ -408,9 +408,7 @@ impl<T: PwmOps> Adapter<T> {
match T::round_waveform_fromhw(chip, pwm, &wfhw, &mut rust_wf) {
Ok(()) => {
// SAFETY: `wf_ptr` is guaranteed valid by the C caller.
- unsafe {
- *wf_ptr = rust_wf.into();
- };
+ unsafe { *wf_ptr = rust_wf.into() };
0
}
Err(e) => e.to_errno(),
@@ -584,11 +582,12 @@ impl<T: PwmOps> Chip<T> {
///
/// Returns an [`ARef<Chip>`] managing the chip's lifetime via refcounting
/// on its embedded `struct device`.
- pub fn new(
- parent_dev: &device::Device,
+ #[allow(clippy::new_ret_no_self)]
+ pub fn new<'a>(
+ parent_dev: &'a device::Device<Bound>,
num_channels: u32,
data: impl pin_init::PinInit<T, Error>,
- ) -> Result<ARef<Self>> {
+ ) -> Result<UnregisteredChip<'a, T>> {
let sizeof_priv = core::mem::size_of::<T>();
// SAFETY: `pwmchip_alloc` allocates memory for the C struct and our private data.
let c_chip_ptr_raw =
@@ -601,19 +600,19 @@ impl<T: PwmOps> Chip<T> {
let drvdata_ptr = unsafe { bindings::pwmchip_get_drvdata(c_chip_ptr) };
// SAFETY: We construct the `T` object in-place in the allocated private memory.
- unsafe { data.__pinned_init(drvdata_ptr.cast())? };
+ unsafe { data.__pinned_init(drvdata_ptr.cast()) }.inspect_err(|_| {
+ // SAFETY: It is safe to call `pwmchip_put()` with a valid pointer obtained
+ // from `pwmchip_alloc()`. We will not use pointer after this.
+ unsafe { bindings::pwmchip_put(c_chip_ptr) }
+ })?;
// SAFETY: `c_chip_ptr` points to a valid chip.
- unsafe {
- (*c_chip_ptr).dev.release = Some(Adapter::<T>::release_callback);
- }
+ unsafe { (*c_chip_ptr).dev.release = Some(Adapter::<T>::release_callback) };
// SAFETY: `c_chip_ptr` points to a valid chip.
// The `Adapter`'s `VTABLE` has a 'static lifetime, so the pointer
// returned by `as_raw()` is always valid.
- unsafe {
- (*c_chip_ptr).ops = Adapter::<T>::VTABLE.as_raw();
- }
+ unsafe { (*c_chip_ptr).ops = Adapter::<T>::VTABLE.as_raw() };
// Cast the `*mut bindings::pwm_chip` to `*mut Chip`. This is valid because
// `Chip` is `repr(transparent)` over `Opaque<bindings::pwm_chip>`, and
@@ -623,7 +622,9 @@ impl<T: PwmOps> Chip<T> {
// SAFETY: `chip_ptr_as_self` points to a valid `Chip` (layout-compatible with
// `bindings::pwm_chip`) whose embedded device has refcount 1.
// `ARef::from_raw` takes this pointer and manages it via `AlwaysRefCounted`.
- Ok(unsafe { ARef::from_raw(NonNull::new_unchecked(chip_ptr_as_self)) })
+ let chip = unsafe { ARef::from_raw(NonNull::new_unchecked(chip_ptr_as_self)) };
+
+ Ok(UnregisteredChip { chip, parent_dev })
}
}
@@ -633,9 +634,7 @@ unsafe impl<T: PwmOps> AlwaysRefCounted for Chip<T> {
fn inc_ref(&self) {
// SAFETY: `self.0.get()` points to a valid `pwm_chip` because `self` exists.
// The embedded `dev` is valid. `get_device` increments its refcount.
- unsafe {
- bindings::get_device(&raw mut (*self.0.get()).dev);
- }
+ unsafe { bindings::get_device(&raw mut (*self.0.get()).dev) };
}
#[inline]
@@ -644,9 +643,7 @@ unsafe impl<T: PwmOps> AlwaysRefCounted for Chip<T> {
// SAFETY: `obj` is a valid pointer to a `Chip` (and thus `bindings::pwm_chip`)
// with a non-zero refcount. `put_device` handles decrement and final release.
- unsafe {
- bindings::put_device(&raw mut (*c_chip_ptr).dev);
- }
+ unsafe { bindings::put_device(&raw mut (*c_chip_ptr).dev) };
}
}
@@ -654,50 +651,61 @@ unsafe impl<T: PwmOps> AlwaysRefCounted for Chip<T> {
// structure's state is managed and synchronized by the kernel's device model
// and PWM core locking mechanisms. Therefore, it is safe to move the `Chip`
// wrapper (and the pointer it contains) across threads.
-unsafe impl<T: PwmOps + Send> Send for Chip<T> {}
+unsafe impl<T: PwmOps> Send for Chip<T> {}
// SAFETY: It is safe for multiple threads to have shared access (`&Chip`) because
// the `Chip` data is immutable from the Rust side without holding the appropriate
// kernel locks, which the C core is responsible for. Any interior mutability is
// handled and synchronized by the C kernel code.
-unsafe impl<T: PwmOps + Sync> Sync for Chip<T> {}
+unsafe impl<T: PwmOps> Sync for Chip<T> {}
-/// A resource guard that ensures `pwmchip_remove` is called on drop.
-///
-/// This struct is intended to be managed by the `devres` framework by transferring its ownership
-/// via [`devres::register`]. This ties the lifetime of the PWM chip registration
-/// to the lifetime of the underlying device.
-pub struct Registration<T: PwmOps> {
+/// A wrapper around `ARef<Chip<T>>` that ensures that `register` can only be called once.
+pub struct UnregisteredChip<'a, T: PwmOps> {
chip: ARef<Chip<T>>,
+ parent_dev: &'a device::Device<Bound>,
}
-impl<T: 'static + PwmOps + Send + Sync> Registration<T> {
+impl<T: PwmOps> UnregisteredChip<'_, T> {
/// Registers a PWM chip with the PWM subsystem.
///
/// Transfers its ownership to the `devres` framework, which ties its lifetime
/// to the parent device.
/// On unbind of the parent device, the `devres` entry will be dropped, automatically
/// calling `pwmchip_remove`. This function should be called from the driver's `probe`.
- pub fn register(dev: &device::Device<Bound>, chip: ARef<Chip<T>>) -> Result {
- let chip_parent = chip.device().parent().ok_or(EINVAL)?;
- if dev.as_raw() != chip_parent.as_raw() {
- return Err(EINVAL);
- }
-
- let c_chip_ptr = chip.as_raw();
+ pub fn register(self) -> Result<ARef<Chip<T>>> {
+ let c_chip_ptr = self.chip.as_raw();
// SAFETY: `c_chip_ptr` points to a valid chip with its ops initialized.
// `__pwmchip_add` is the C function to register the chip with the PWM core.
- unsafe {
- to_result(bindings::__pwmchip_add(c_chip_ptr, core::ptr::null_mut()))?;
- }
+ to_result(unsafe { bindings::__pwmchip_add(c_chip_ptr, core::ptr::null_mut()) })?;
+
+ let registration = Registration {
+ chip: ARef::clone(&self.chip),
+ };
+
+ devres::register(self.parent_dev, registration, GFP_KERNEL)?;
+
+ Ok(self.chip)
+ }
+}
- let registration = Registration { chip };
+impl<T: PwmOps> Deref for UnregisteredChip<'_, T> {
+ type Target = Chip<T>;
- devres::register(dev, registration, GFP_KERNEL)
+ fn deref(&self) -> &Self::Target {
+ &self.chip
}
}
+/// A resource guard that ensures `pwmchip_remove` is called on drop.
+///
+/// This struct is intended to be managed by the `devres` framework by transferring its ownership
+/// via [`devres::register`]. This ties the lifetime of the PWM chip registration
+/// to the lifetime of the underlying device.
+struct Registration<T: PwmOps> {
+ chip: ARef<Chip<T>>,
+}
+
impl<T: PwmOps> Drop for Registration<T> {
fn drop(&mut self) {
let chip_raw = self.chip.as_raw();
@@ -705,9 +713,7 @@ impl<T: PwmOps> Drop for Registration<T> {
// SAFETY: `chip_raw` points to a chip that was successfully registered.
// `bindings::pwmchip_remove` is the correct C function to unregister it.
// This `drop` implementation is called automatically by `devres` on driver unbind.
- unsafe {
- bindings::pwmchip_remove(chip_raw);
- }
+ unsafe { bindings::pwmchip_remove(chip_raw) };
}
}