* [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver
[not found] <CGME20250707094926eucas1p155bd967b6986c4a999776839b1aa1fc6@eucas1p1.samsung.com>
@ 2025-07-07 9:48 ` Michal Wilczynski
[not found] ` <CGME20250707094927eucas1p2be0302112d89f0ea39e4eeb21c771234@eucas1p2.samsung.com>
` (7 more replies)
0 siblings, 8 replies; 23+ messages in thread
From: Michal Wilczynski @ 2025-07-07 9:48 UTC (permalink / raw)
To: Uwe Kleine-König, Miguel Ojeda, Alex Gaynor, Boqun Feng,
Gary Guo, Björn Roy Baron, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Michal Wilczynski, Guo Ren,
Fu Wei, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti,
Marek Szyprowski, Benno Lossin, Michael Turquette, Drew Fustini,
Benno Lossin, Drew Fustini
Cc: linux-kernel, linux-pwm, rust-for-linux, linux-riscv, devicetree,
Krzysztof Kozlowski
This patch series introduces Rust support for the T-HEAD TH1520 PWM
controller and demonstrates its use for fan control on the Sipeed Lichee
Pi 4A board.
The primary goal of this patch series is to introduce a basic set of
Rust abstractions for the Linux PWM subsystem. As a first user and
practical demonstration of these abstractions, the series also provides
a functional PWM driver for the T-HEAD TH1520 SoC. This allows control
of its PWM channels and ultimately enables temperature controlled fan
support for the Lichee Pi 4A board. This work aims to explore the use of
Rust for PWM drivers and lay a foundation for potential future
Rust based PWM drivers.
The core of this series is a new rust/kernel/pwm.rs module that provides
abstractions for writing PWM chip provider drivers in Rust. This has
been significantly reworked from v1 based on extensive feedback. The key
features of the new abstraction layer include:
- Ownership and Lifetime Management: The pwm::Chip wrapper is managed
by ARef, correctly tying its lifetime to its embedded struct device
reference counter. Chip registration is handled by a pwm::Registration
RAII guard, which guarantees that pwmchip_add is always paired with
pwmchip_remove, preventing resource leaks.
- Modern and Safe API: The PwmOps trait is now based on the modern
waveform API (round_waveform_tohw, write_waveform, etc.) as recommended
by the subsystem maintainer. It is generic over a driver's
hardware specific data structure, moving all unsafe serialization logic
into the abstraction layer and allowing drivers to be written in 100%
safe Rust.
- Ergonomics: The API provides safe, idiomatic wrappers for other PWM
types (State, Args, Device, etc.) and uses standard kernel error
handling patterns.
The series is structured as follows:
- Expose static function pwmchip_release.
- Rust PWM Abstractions: The new safe abstraction layer.
- TH1520 PWM Driver: A new Rust driver for the TH1520 SoC, built on
top of the new abstractions.
- Device Tree Bindings & Nodes: The remaining patches add the necessary
DT bindings and nodes for the TH1520 PWM controller, and the PWM fan
configuration for the Lichee Pi 4A board.
Testing:
Tested on the TH1520 SoC. The fan works correctly. The duty/period
calculations are correct. Fan starts slow when the chip is not hot and
gradually increases the speed when PVT reports higher temperatures.
The patches are based on mainline, with some dependencies which are not
merged yet - platform Io support [1].
Reference repository with all the patches together can be found on
github [2].
[1] - https://lore.kernel.org/rust-for-linux/20250509-topics-tyr-platform_iomem-v8-0-e9f1725a40da@collabora.com/
[2] - https://github.com/mwilczy/linux/commits/rust-next-pwm-working-fan-for-sending-v15/
---
Changes in v10:
- Exported the C pwmchip_release function and called it from the custom
Rust release_callback to fix a memory leak of the pwm_chip struct.
- Removed the PwmOps::free callback, as it is not needed for idiomatic
Rust resource management.
- Removed the redundant is_null check for drvdata in the release handler,
as the Rust API guarantees a valid pointer is always provided.
- Link to v9: https://lore.kernel.org/r/20250706-rust-next-pwm-working-fan-for-sending-v9-0-42b5ac2101c7@samsung.com
Changes in v9:
- Encapsulated vtable setup in Chip::new(): The Chip::new() function is
now generic over the PwmOps implementation. This allows it to create and
assign the vtable internally, which simplifies the public API by
removing the ops_vtable parameter from Registration::register().
- Fixed memory leak with a release handler: A custom release_callback is
now assigned to the embedded struct device's release hook. This
guarantees that driver specific data is always freed when the chip is
destroyed, even if registration fails.
- The PwmOpsVTable is now defined as a const associated item to ensure
it has a 'static lifetime.
- Combined introductory commits: The Device, Chip, and PwmOps abstractions
are now introduced in a single commit. This was necessary to resolve the
circular dependencies between them and present a clean, compilable unit
for review.
- Link to v8: https://lore.kernel.org/r/20250704-rust-next-pwm-working-fan-for-sending-v8-0-951e5482c9fd@samsung.com
Changes in v8:
- Dropped already accepted commit, re-based on top of linux-next
- Reworked the Chip and PwmOps APIs to address the drvdata() type-safety
comment. Chip is now generic, and PwmOps uses an associated type
to provide compile-time guarantees.
- Added a parent device sanity check to Registration::register().
- Updated drvdata() to return the idiomatic T::Borrowed<'_>.
- added temporary unsafe blocks in the driver, as the current
abstraction for Clk is neiter Safe nor Sync. I think eventually
proper abstraction for Clk will be added as in a current state it's
not very useful.
- Link to v7: https://lore.kernel.org/r/20250702-rust-next-pwm-working-fan-for-sending-v7-0-67ef39ff1d29@samsung.com
Changes in v7:
- Made parent_device function private and moved casts to Device<Bound>
there as well.
- Link to v6: https://lore.kernel.org/r/20250701-rust-next-pwm-working-fan-for-sending-v6-0-2710932f6f6b@samsung.com
Changes in v6:
- Re-based on top of linux-next, dropped two already accepted commits.
- After re-basing the IoMem dependent patchset stopped working,
reworked it to use similar API like the PCI subsystem (I think it
will end up the same). Re-worked the driver for it as well.
- Remove the apply and get_state callbacks, and most of the State as
well, as the old way of implementing drivers should not be possible
in Rust. Left only enabled(), since it's useful for my driver.
- Removed the public set_drvdata() method from pwm::Chip
- Moved WFHWSIZE to the public include/linux/pwm.h header and renamed it
to PWM_WFHWSIZE, allowing bindgen to create safe FFI bindings.
- Corrected the ns_to_cycles integer calculation in the TH1520 driver to
handle overflow correctly.
- Updated the Kconfig entry for the TH1520 driver to select the Rust
abstractions for a better user experience.
- Link to v5: https://lore.kernel.org/r/20250623-rust-next-pwm-working-fan-for-sending-v5-0-0ca23747c23e@samsung.com
Changes in v5:
- Reworked `pwm::Chip` creation to take driver data directly, which
allowed making the `chip.drvdata()` accessor infallible
- added missing `pwm.c` file lost during the commit split (sorry !)
- Link to v4: https://lore.kernel.org/r/20250618-rust-next-pwm-working-fan-for-sending-v4-0-a6a28f2b6d8a@samsung.com
Changes in v4:
- Reworked the pwm::Registration API to use the devres framework,
addressing lifetime issue.
- Corrected the PwmOps trait and its callbacks to use immutable references
(&Chip, &Device) for improved safety.
- Applied various code style and naming cleanups based on feedback
- Link to v3: https://lore.kernel.org/r/20250617-rust-next-pwm-working-fan-for-sending-v3-0-1cca847c6f9f@samsung.com
Changes in v3:
- Addressed feedback from Uwe by making multiple changes to the TH1520
driver and the abstraction layer.
- Split the core PWM abstractions into three focused commits to ease
review per Benno request.
- Confirmed the driver now works correctly with CONFIG_PWM_DEBUG enabled
by implementing the full waveform API, which correctly reads the
hardware state.
- Refactored the Rust code to build cleanly with
CONFIG_RUST_BUILD_ASSERT_ALLOW=n, primarily by using the try_* family of
functions for IoMem access.
- Included several cosmetic changes and cleanups to the abstractions
per Miguel review.
- Link to v2: https://lore.kernel.org/r/20250610-rust-next-pwm-working-fan-for-sending-v2-0-753e2955f110@samsung.com
Changes in v2:
- Reworked the PWM abstraction layer based on extensive feedback.
- Replaced initial devm allocation with a proper ARef<Chip> lifetime model
using AlwaysRefCounted.
- Implemented a Registration RAII guard to ensure safe chip add/remove.
- Migrated the PwmOps trait from the legacy .apply callback to the modern
waveform API.
- Refactored the TH1520 driver to use the new, safer abstractions.
- Added a patch to mark essential bus clocks as CLK_IGNORE_UNUSED to fix
boot hangs when the PWM and thermal sensors are enabled.
- Link to v1: https://lore.kernel.org/r/20250524-rust-next-pwm-working-fan-for-sending-v1-0-bdd2d5094ff7@samsung.com
---
Michal Wilczynski (7):
pwm: Export `pwmchip_release` for external use
rust: pwm: Add Kconfig and basic data structures
rust: pwm: Add complete abstraction layer
pwm: Add Rust driver for T-HEAD TH1520 SoC
dt-bindings: pwm: thead: Add T-HEAD TH1520 PWM controller
riscv: dts: thead: Add PWM controller node
riscv: dts: thead: Add PWM fan and thermal control
.../devicetree/bindings/pwm/thead,th1520-pwm.yaml | 48 ++
MAINTAINERS | 8 +
arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts | 67 ++
arch/riscv/boot/dts/thead/th1520.dtsi | 7 +
drivers/pwm/Kconfig | 24 +
drivers/pwm/Makefile | 1 +
drivers/pwm/core.c | 2 +-
drivers/pwm/pwm_th1520.rs | 352 +++++++++
include/linux/pwm.h | 5 +
rust/bindings/bindings_helper.h | 1 +
rust/helpers/helpers.c | 1 +
rust/helpers/pwm.c | 20 +
rust/kernel/lib.rs | 2 +
rust/kernel/pwm.rs | 800 +++++++++++++++++++++
14 files changed, 1337 insertions(+), 1 deletion(-)
---
base-commit: 47753b5a1696283930a78aae79b29371f96f5bca
change-id: 20250524-rust-next-pwm-working-fan-for-sending-552ad2d1b193
Best regards,
--
Michal Wilczynski <m.wilczynski@samsung.com>
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v10 1/7] pwm: Export `pwmchip_release` for external use
[not found] ` <CGME20250707094927eucas1p2be0302112d89f0ea39e4eeb21c771234@eucas1p2.samsung.com>
@ 2025-07-07 9:48 ` Michal Wilczynski
0 siblings, 0 replies; 23+ messages in thread
From: Michal Wilczynski @ 2025-07-07 9:48 UTC (permalink / raw)
To: Uwe Kleine-König, Miguel Ojeda, Alex Gaynor, Boqun Feng,
Gary Guo, Björn Roy Baron, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Michal Wilczynski, Guo Ren,
Fu Wei, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti,
Marek Szyprowski, Benno Lossin, Michael Turquette, Drew Fustini,
Benno Lossin, Drew Fustini
Cc: linux-kernel, linux-pwm, rust-for-linux, linux-riscv, devicetree
The upcoming Rust abstraction layer for the PWM subsystem uses a custom
`dev->release` handler to safely manage the lifetime of its driver
data.
To prevent leaking the memory of the `struct pwm_chip` (allocated by
`pwmchip_alloc`), this custom handler must also call the original
`pwmchip_release` function to complete the cleanup.
Make `pwmchip_release` a global, exported function so that it can be
called from the Rust FFI bridge. This involves removing the `static`
keyword, adding a prototype to the public header, and exporting the
symbol.
Signed-off-by: Michal Wilczynski <m.wilczynski@samsung.com>
---
drivers/pwm/core.c | 2 +-
include/linux/pwm.h | 5 +++++
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index c1e8ab1a0945889d92dada003060b8b109f2a138..1d33438cd2cf0b5ca53dd22786ed5b5a9f5def8d 100644
--- a/drivers/pwm/core.c
+++ b/drivers/pwm/core.c
@@ -1600,7 +1600,7 @@ void pwmchip_put(struct pwm_chip *chip)
}
EXPORT_SYMBOL_GPL(pwmchip_put);
-static void pwmchip_release(struct device *pwmchip_dev)
+void pwmchip_release(struct device *pwmchip_dev)
{
struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev);
diff --git a/include/linux/pwm.h b/include/linux/pwm.h
index 8cafc483db53addf95591d1ac74287532c0fa0ee..8f0698c09e62b893d63fc258da3c34781183056f 100644
--- a/include/linux/pwm.h
+++ b/include/linux/pwm.h
@@ -478,6 +478,7 @@ static inline bool pwm_might_sleep(struct pwm_device *pwm)
/* PWM provider APIs */
void pwmchip_put(struct pwm_chip *chip);
+void pwmchip_release(struct device *dev);
struct pwm_chip *pwmchip_alloc(struct device *parent, unsigned int npwm, size_t sizeof_priv);
struct pwm_chip *devm_pwmchip_alloc(struct device *parent, unsigned int npwm, size_t sizeof_priv);
@@ -551,6 +552,10 @@ static inline void pwmchip_put(struct pwm_chip *chip)
{
}
+static inline void pwmchip_release(struct device *dev)
+{
+}
+
static inline struct pwm_chip *pwmchip_alloc(struct device *parent,
unsigned int npwm,
size_t sizeof_priv)
--
2.34.1
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v10 2/7] rust: pwm: Add Kconfig and basic data structures
[not found] ` <CGME20250707094929eucas1p2282257ae8e5351df04b497a6c8664ab2@eucas1p2.samsung.com>
@ 2025-07-07 9:48 ` Michal Wilczynski
0 siblings, 0 replies; 23+ messages in thread
From: Michal Wilczynski @ 2025-07-07 9:48 UTC (permalink / raw)
To: Uwe Kleine-König, Miguel Ojeda, Alex Gaynor, Boqun Feng,
Gary Guo, Björn Roy Baron, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Michal Wilczynski, Guo Ren,
Fu Wei, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti,
Marek Szyprowski, Benno Lossin, Michael Turquette, Drew Fustini,
Benno Lossin, Drew Fustini
Cc: linux-kernel, linux-pwm, rust-for-linux, linux-riscv, devicetree
Introduce the foundational support for PWM abstractions in Rust.
This commit adds the `RUST_PWM_ABSTRACTIONS` Kconfig option to enable
the feature, along with the necessary build-system support and C
helpers.
It also introduces the first set of safe wrappers for the PWM
subsystem, covering the basic data carrying C structs and enums:
- `Polarity`: A safe wrapper for `enum pwm_polarity`.
- `Waveform`: A wrapper for `struct pwm_waveform`.
- `Args`: A wrapper for `struct pwm_args`.
- `State`: A wrapper for `struct pwm_state`.
These types provide memory safe, idiomatic Rust representations of the
core PWM data structures and form the building blocks for the
abstractions that will follow.
Tested-by: Drew Fustini <fustini@kernel.org>
Signed-off-by: Michal Wilczynski <m.wilczynski@samsung.com>
---
MAINTAINERS | 6 ++
drivers/pwm/Kconfig | 13 ++++
rust/bindings/bindings_helper.h | 1 +
rust/helpers/helpers.c | 1 +
rust/helpers/pwm.c | 20 ++++++
rust/kernel/lib.rs | 2 +
rust/kernel/pwm.rs | 137 ++++++++++++++++++++++++++++++++++++++++
7 files changed, 180 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 92e9d8c7708ff4874efaa9727814ad7f1c9f6b0c..494de42ca8c36b30d80e14b03d3c9e0e054fb267 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20196,6 +20196,12 @@ F: include/linux/pwm.h
F: include/linux/pwm_backlight.h
K: pwm_(config|apply_might_sleep|apply_atomic|ops)
+PWM SUBSYSTEM BINDINGS [RUST]
+M: Michal Wilczynski <m.wilczynski@samsung.com>
+S: Maintained
+F: rust/helpers/pwm.c
+F: rust/kernel/pwm.rs
+
PXA GPIO DRIVER
M: Robert Jarzmik <robert.jarzmik@free.fr>
L: linux-gpio@vger.kernel.org
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 3ef1757502ebd92b30584cd10611311a0fbfc03b..c32655566d6ab9eff9d10f29e469f9aef89cecfa 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -799,4 +799,17 @@ config PWM_XILINX
To compile this driver as a module, choose M here: the module
will be called pwm-xilinx.
+ config RUST_PWM_ABSTRACTIONS
+ bool "Rust PWM abstractions support"
+ depends on RUST
+ depends on PWM=y
+ help
+ This option enables the safe Rust abstraction layer for the PWM
+ subsystem. It provides idiomatic wrappers and traits necessary for
+ writing PWM controller drivers in Rust.
+
+ The abstractions handle resource management (like memory and reference
+ counting) and provide safe interfaces to the underlying C core,
+ allowing driver logic to be written in safe Rust.
+
endif
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 5f795e60e889b9fc887013743c81b1cf92a52adb..6a6e1b1736b38f36c1dbdd875defb3b526372b67 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -66,6 +66,7 @@
#include <linux/pm_opp.h>
#include <linux/poll.h>
#include <linux/property.h>
+#include <linux/pwm.h>
#include <linux/refcount.h>
#include <linux/sched.h>
#include <linux/security.h>
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 1f800e78920145fc5149befb15579179dfb6e02e..c449d72fa8b19b2ab084be520466ab916c63cea7 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -35,6 +35,7 @@
#include "pid_namespace.c"
#include "poll.c"
#include "property.c"
+#include "pwm.c"
#include "rbtree.c"
#include "rcu.c"
#include "refcount.c"
diff --git a/rust/helpers/pwm.c b/rust/helpers/pwm.c
new file mode 100644
index 0000000000000000000000000000000000000000..d75c588863685d3990b525bb1b84aa4bc35ac397
--- /dev/null
+++ b/rust/helpers/pwm.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2025 Samsung Electronics Co., Ltd.
+// Author: Michal Wilczynski <m.wilczynski@samsung.com>
+
+#include <linux/pwm.h>
+
+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)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+void rust_helper_pwmchip_set_drvdata(struct pwm_chip *chip, void *data)
+{
+ pwmchip_set_drvdata(chip, data);
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 5bbf3627212f0a26d34be0d6c160a370abf1e996..9f7038d3d501982a843d6d86571d20f1213ba9ee 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -106,6 +106,8 @@
pub mod seq_file;
pub mod sizes;
mod static_assert;
+#[cfg(CONFIG_RUST_PWM_ABSTRACTIONS)]
+pub mod pwm;
#[doc(hidden)]
pub mod std_vendor;
pub mod str;
diff --git a/rust/kernel/pwm.rs b/rust/kernel/pwm.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3fad101406eac728d9b12083fad7abf7b7f89b25
--- /dev/null
+++ b/rust/kernel/pwm.rs
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2025 Samsung Electronics Co., Ltd.
+// Author: Michal Wilczynski <m.wilczynski@samsung.com>
+
+//! PWM subsystem abstractions.
+//!
+//! C header: [`include/linux/pwm.h`](srctree/include/linux/pwm.h).
+
+use crate::{
+ bindings,
+ prelude::*,
+ types::Opaque,
+};
+use core::convert::TryFrom;
+
+/// PWM polarity. Mirrors [`enum pwm_polarity`](srctree/include/linux/pwm.h).
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum Polarity {
+ /// Normal polarity (duty cycle defines the high period of the signal).
+ Normal,
+
+ /// Inversed polarity (duty cycle defines the low period of the signal).
+ Inversed,
+}
+
+impl TryFrom<bindings::pwm_polarity> for Polarity {
+ type Error = Error;
+
+ fn try_from(polarity: bindings::pwm_polarity) -> Result<Self, Error> {
+ match polarity {
+ bindings::pwm_polarity_PWM_POLARITY_NORMAL => Ok(Polarity::Normal),
+ bindings::pwm_polarity_PWM_POLARITY_INVERSED => Ok(Polarity::Inversed),
+ _ => Err(EINVAL),
+ }
+ }
+}
+
+impl From<Polarity> for bindings::pwm_polarity {
+ fn from(polarity: Polarity) -> Self {
+ match polarity {
+ Polarity::Normal => bindings::pwm_polarity_PWM_POLARITY_NORMAL,
+ Polarity::Inversed => bindings::pwm_polarity_PWM_POLARITY_INVERSED,
+ }
+ }
+}
+
+/// Represents a PWM waveform configuration.
+/// Mirrors struct [`struct pwm_waveform`](srctree/include/linux/pwm.h).
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+pub struct Waveform {
+ /// Total duration of one complete PWM cycle, in nanoseconds.
+ pub period_length_ns: u64,
+
+ /// Duty-cycle active time, in nanoseconds.
+ ///
+ /// For a typical normal polarity configuration (active-high) this is the
+ /// high time of the signal.
+ pub duty_length_ns: u64,
+
+ /// Duty-cycle start offset, in nanoseconds.
+ ///
+ /// Delay from the beginning of the period to the first active edge.
+ /// In most simple PWM setups this is `0`, so the duty cycle starts
+ /// immediately at each period’s start.
+ pub duty_offset_ns: u64,
+}
+
+impl From<bindings::pwm_waveform> for Waveform {
+ fn from(wf: bindings::pwm_waveform) -> Self {
+ Waveform {
+ period_length_ns: wf.period_length_ns,
+ duty_length_ns: wf.duty_length_ns,
+ duty_offset_ns: wf.duty_offset_ns,
+ }
+ }
+}
+
+impl From<Waveform> for bindings::pwm_waveform {
+ fn from(wf: Waveform) -> Self {
+ bindings::pwm_waveform {
+ period_length_ns: wf.period_length_ns,
+ duty_length_ns: wf.duty_length_ns,
+ duty_offset_ns: wf.duty_offset_ns,
+ }
+ }
+}
+
+/// Wrapper for board-dependent PWM arguments [`struct pwm_args`](srctree/include/linux/pwm.h).
+#[repr(transparent)]
+pub struct Args(Opaque<bindings::pwm_args>);
+
+impl Args {
+ /// Creates an `Args` wrapper from a C struct pointer.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that `c_args_ptr` is a valid, non-null pointer
+ /// to `bindings::pwm_args` and that the pointed-to data is valid
+ /// for the duration of this function call (as data is copied).
+ unsafe fn from_c_ptr(c_args_ptr: *const bindings::pwm_args) -> Self {
+ // SAFETY: Caller guarantees `c_args_ptr` is valid. We dereference it to copy.
+ Args(Opaque::new(unsafe { *c_args_ptr }))
+ }
+
+ /// Returns the period of the PWM signal in nanoseconds.
+ pub fn period(&self) -> u64 {
+ // SAFETY: `self.0.get()` returns a pointer to the `bindings::pwm_args`
+ // managed by the `Opaque` wrapper. This pointer is guaranteed to be
+ // valid and aligned for the lifetime of `self` because `Opaque` owns a copy.
+ unsafe { (*self.0.get()).period }
+ }
+
+ /// Returns the polarity of the PWM signal.
+ pub fn polarity(&self) -> Result<Polarity, Error> {
+ // SAFETY: `self.0.get()` returns a pointer to the `bindings::pwm_args`
+ // managed by the `Opaque` wrapper. This pointer is guaranteed to be
+ // valid and aligned for the lifetime of `self`.
+ let raw_polarity = unsafe { (*self.0.get()).polarity };
+ Polarity::try_from(raw_polarity)
+ }
+}
+
+/// Wrapper for PWM state [`struct pwm_state`](srctree/include/linux/pwm.h).
+#[repr(transparent)]
+pub struct State(bindings::pwm_state);
+
+impl State {
+ /// Creates a `State` wrapper by taking ownership of a C `pwm_state` value.
+ pub(crate) fn from_c(c_state: bindings::pwm_state) -> Self {
+ State(c_state)
+ }
+
+ /// Returns `true` if the PWM signal is enabled.
+ pub fn enabled(&self) -> bool {
+ self.0.enabled
+ }
+}
--
2.34.1
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v10 3/7] rust: pwm: Add complete abstraction layer
[not found] ` <CGME20250707094930eucas1p1932276804e345087eb05588cbd65a024@eucas1p1.samsung.com>
@ 2025-07-07 9:48 ` Michal Wilczynski
0 siblings, 0 replies; 23+ messages in thread
From: Michal Wilczynski @ 2025-07-07 9:48 UTC (permalink / raw)
To: Uwe Kleine-König, Miguel Ojeda, Alex Gaynor, Boqun Feng,
Gary Guo, Björn Roy Baron, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Michal Wilczynski, Guo Ren,
Fu Wei, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti,
Marek Szyprowski, Benno Lossin, Michael Turquette, Drew Fustini,
Benno Lossin, Drew Fustini
Cc: linux-kernel, linux-pwm, rust-for-linux, linux-riscv, devicetree
Introduce a comprehensive abstraction layer for the PWM subsystem to
enable writing drivers in Rust.
Because `Device`, `Chip`, and `PwmOps` all refer to each other, they
form a single, indivisible unit with circular dependencies. They are
introduced together in this single commit to create a complete,
compilable abstraction layer.
The main components are:
- Data Wrappers: Safe, idiomatic wrappers for core C types like
`pwm_device`, and `pwm_chip`.
- PwmOps Trait: An interface that drivers can implement to provide
their hardware-specific logic, mirroring the C `pwm_ops` interface.
- FFI VTable and Adapter: A bridge to connect the high-level PwmOps trait
to the C kernel's pwm_ops vtable.
- Allocation and Lifetime Management: A high-level `Chip::new()`
API to safely allocate a chip and a `Registration` guard that integrates
with `devres` to manage the chip's registration with the PWM core.
An `AlwaysRefCounted` implementation and a custom release handler
prevent memory leaks by managing the chip's lifetime and freeing
driver data correctly.
Reviewed-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Michal Wilczynski <m.wilczynski@samsung.com>
---
rust/kernel/pwm.rs | 667 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 665 insertions(+), 2 deletions(-)
diff --git a/rust/kernel/pwm.rs b/rust/kernel/pwm.rs
index 3fad101406eac728d9b12083fad7abf7b7f89b25..0e1896f70c2fb8ec6b40106c96018299fcf7157f 100644
--- a/rust/kernel/pwm.rs
+++ b/rust/kernel/pwm.rs
@@ -8,10 +8,14 @@
use crate::{
bindings,
+ container_of,
+ device::{self, Bound},
+ devres,
+ error::{self, to_result},
prelude::*,
- types::Opaque,
+ types::{ARef, AlwaysRefCounted, ForeignOwnable, Opaque},
};
-use core::convert::TryFrom;
+use core::{convert::TryFrom, marker::PhantomData, ptr::NonNull};
/// PWM polarity. Mirrors [`enum pwm_polarity`](srctree/include/linux/pwm.h).
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -135,3 +139,662 @@ pub fn enabled(&self) -> bool {
self.0.enabled
}
}
+
+/// Describes the outcome of a `round_waveform` operation.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum RoundingOutcome {
+ /// The requested waveform was achievable exactly or by rounding values down.
+ ExactOrRoundedDown,
+
+ /// The requested waveform could only be achieved by rounding up.
+ RoundedUp,
+}
+
+/// Wrapper for a PWM device [`struct pwm_device`](srctree/include/linux/pwm.h).
+#[repr(transparent)]
+pub struct Device(Opaque<bindings::pwm_device>);
+
+impl Device {
+ /// Creates a reference to a [`Device`] from a valid C pointer.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that `ptr` is valid and remains valid for the lifetime of the
+ /// returned [`Device`] reference.
+ pub(crate) unsafe fn as_ref<'a>(ptr: *mut bindings::pwm_device) -> &'a Self {
+ // SAFETY: The safety requirements guarantee the validity of the dereference, while the
+ // `Device` type being transparent makes the cast ok.
+ unsafe { &*ptr.cast::<Self>() }
+ }
+
+ /// Returns a raw pointer to the underlying `pwm_device`.
+ fn as_raw(&self) -> *mut bindings::pwm_device {
+ self.0.get()
+ }
+
+ /// Gets the hardware PWM index for this device within its chip.
+ pub fn hwpwm(&self) -> u32 {
+ // SAFETY: `self.as_raw()` provides a valid pointer for `self`'s lifetime.
+ unsafe { (*self.as_raw()).hwpwm }
+ }
+
+ /// Gets a reference to the parent `Chip` that this device belongs to.
+ pub fn chip<T: ForeignOwnable>(&self) -> &Chip<T> {
+ // SAFETY: `self.as_raw()` provides a valid pointer. (*self.as_raw()).chip
+ // is assumed to be a valid pointer to `pwm_chip` managed by the kernel.
+ // Chip::as_ref's safety conditions must be met.
+ unsafe { Chip::<T>::as_ref((*self.as_raw()).chip) }
+ }
+
+ /// Gets the label for this PWM device, if any.
+ pub fn label(&self) -> Option<&CStr> {
+ // SAFETY: self.as_raw() provides a valid pointer.
+ let label_ptr = unsafe { (*self.as_raw()).label };
+ if label_ptr.is_null() {
+ None
+ } else {
+ // SAFETY: label_ptr is non-null and points to a C string
+ // managed by the kernel, valid for the lifetime of the PWM device.
+ Some(unsafe { CStr::from_char_ptr(label_ptr) })
+ }
+ }
+
+ /// Gets a copy of the board-dependent arguments for this PWM device.
+ pub fn args(&self) -> Args {
+ // SAFETY: self.as_raw() gives a valid pointer to `pwm_device`.
+ // The `args` field is a valid `pwm_args` struct embedded within `pwm_device`.
+ // `Args::from_c_ptr`'s safety conditions are met by providing this pointer.
+ unsafe { Args::from_c_ptr(&(*self.as_raw()).args) }
+ }
+
+ /// Gets a copy of the current state of this PWM device.
+ pub fn state(&self) -> State {
+ // SAFETY: `self.as_raw()` gives a valid pointer. `(*self.as_raw()).state`
+ // is a valid `pwm_state` struct. `State::from_c` copies this data.
+ State::from_c(unsafe { (*self.as_raw()).state })
+ }
+
+ /// Sets the PWM waveform configuration and enables the PWM signal.
+ pub fn set_waveform(&self, wf: &Waveform, exact: bool) -> Result {
+ let c_wf = bindings::pwm_waveform::from(*wf);
+
+ // 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)
+ }
+
+ /// Queries the hardware for the configuration it would apply for a given
+ /// request.
+ pub fn round_waveform(&self, wf: &mut Waveform) -> Result<RoundingOutcome> {
+ let mut c_wf = bindings::pwm_waveform::from(*wf);
+
+ // SAFETY: `self.as_raw()` provides a valid `*mut pwm_device` pointer.
+ // `&mut c_wf` is a valid pointer to a mutable `pwm_waveform` struct that
+ // the C function will update.
+ let ret = unsafe { bindings::pwm_round_waveform_might_sleep(self.as_raw(), &mut c_wf) };
+
+ to_result(ret)?;
+
+ *wf = Waveform::from(c_wf);
+
+ if ret == 1 {
+ Ok(RoundingOutcome::RoundedUp)
+ } else {
+ Ok(RoundingOutcome::ExactOrRoundedDown)
+ }
+ }
+
+ /// Reads the current waveform configuration directly from the hardware.
+ pub fn get_waveform(&self) -> Result<Waveform> {
+ let mut c_wf = bindings::pwm_waveform::default();
+
+ // 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)?;
+
+ Ok(Waveform::from(c_wf))
+ }
+}
+
+/// Trait defining the operations for a PWM driver.
+pub trait PwmOps: 'static + Sized {
+ /// The type of the owned driver data (e.g., `Pin<KBox<...>>`).
+ type DrvData: 'static + ForeignOwnable;
+ /// The driver-specific hardware representation of a waveform.
+ ///
+ /// This type must be [`Copy`], [`Default`], and fit within `PWM_WFHWSIZE`.
+ type WfHw: Copy + Default;
+
+ /// Optional hook for when a PWM device is requested.
+ fn request(
+ _chip: &Chip<Self::DrvData>,
+ _pwm: &Device,
+ _parent_dev: &device::Device<Bound>,
+ ) -> Result {
+ Ok(())
+ }
+
+ /// Optional hook for capturing a PWM signal.
+ fn capture(
+ _chip: &Chip<Self::DrvData>,
+ _pwm: &Device,
+ _result: &mut bindings::pwm_capture,
+ _timeout: usize,
+ _parent_dev: &device::Device<Bound>,
+ ) -> Result {
+ Err(ENOTSUPP)
+ }
+
+ /// Convert a generic waveform to the hardware-specific representation.
+ /// This is typically a pure calculation and does not perform I/O.
+ fn round_waveform_tohw(
+ _chip: &Chip<Self::DrvData>,
+ _pwm: &Device,
+ _wf: &Waveform,
+ ) -> Result<(c_int, Self::WfHw)> {
+ Err(ENOTSUPP)
+ }
+
+ /// Convert a hardware-specific representation back to a generic waveform.
+ /// This is typically a pure calculation and does not perform I/O.
+ fn round_waveform_fromhw(
+ _chip: &Chip<Self::DrvData>,
+ _pwm: &Device,
+ _wfhw: &Self::WfHw,
+ _wf: &mut Waveform,
+ ) -> Result<c_int> {
+ Err(ENOTSUPP)
+ }
+
+ /// Read the current hardware configuration into the hardware-specific representation.
+ fn read_waveform(
+ _chip: &Chip<Self::DrvData>,
+ _pwm: &Device,
+ _parent_dev: &device::Device<Bound>,
+ ) -> Result<Self::WfHw> {
+ Err(ENOTSUPP)
+ }
+
+ /// Write a hardware-specific waveform configuration to the hardware.
+ fn write_waveform(
+ _chip: &Chip<Self::DrvData>,
+ _pwm: &Device,
+ _wfhw: &Self::WfHw,
+ _parent_dev: &device::Device<Bound>,
+ ) -> Result {
+ Err(ENOTSUPP)
+ }
+}
+/// Bridges Rust `PwmOps` to the C `pwm_ops` vtable.
+struct Adapter<T: PwmOps> {
+ _p: PhantomData<T>,
+}
+
+impl<T: PwmOps> Adapter<T> {
+ const VTABLE: PwmOpsVTable = create_pwm_ops::<T>();
+
+ /// # Safety
+ ///
+ /// `wfhw_ptr` must be valid for writes of `size_of::<T::WfHw>()` bytes.
+ unsafe fn serialize_wfhw(wfhw: &T::WfHw, wfhw_ptr: *mut c_void) -> Result {
+ let size = core::mem::size_of::<T::WfHw>();
+ if size > bindings::PWM_WFHWSIZE as usize {
+ return Err(EINVAL);
+ }
+
+ // SAFETY: The caller ensures `wfhw_ptr` is valid for `size` bytes.
+ unsafe {
+ core::ptr::copy_nonoverlapping(
+ core::ptr::from_ref::<T::WfHw>(wfhw).cast::<u8>(),
+ wfhw_ptr.cast::<u8>(),
+ size,
+ );
+ }
+
+ Ok(())
+ }
+
+ /// # Safety
+ ///
+ /// `wfhw_ptr` must be valid for reads of `size_of::<T::WfHw>()` bytes.
+ unsafe fn deserialize_wfhw(wfhw_ptr: *const c_void) -> Result<T::WfHw> {
+ let size = core::mem::size_of::<T::WfHw>();
+ if size > bindings::PWM_WFHWSIZE as usize {
+ return Err(EINVAL);
+ }
+
+ let mut wfhw = T::WfHw::default();
+ // SAFETY: The caller ensures `wfhw_ptr` is valid for `size` bytes.
+ unsafe {
+ core::ptr::copy_nonoverlapping(
+ wfhw_ptr.cast::<u8>(),
+ core::ptr::from_mut::<T::WfHw>(&mut wfhw).cast::<u8>(),
+ size,
+ );
+ }
+
+ Ok(wfhw)
+ }
+
+ /// # Safety
+ ///
+ /// `dev` must be a valid pointer to a `bindings::device` embedded within a
+ /// `bindings::pwm_chip`. This function is called by the device core when the
+ /// last reference to the device is dropped.
+ unsafe extern "C" fn release_callback(dev: *mut bindings::device) {
+ // SAFETY: The function's contract guarantees that `dev` points to a `device`
+ // field embedded within a valid `pwm_chip`. `container_of!` can therefore
+ // safely calculate the address of the containing struct.
+ let c_chip_ptr = unsafe { container_of!(dev, bindings::pwm_chip, dev) };
+
+ // SAFETY: `c_chip_ptr` is a valid pointer to a `pwm_chip` as established
+ // above. Calling this FFI function is safe.
+ let drvdata_ptr = unsafe { bindings::pwmchip_get_drvdata(c_chip_ptr) };
+
+ // SAFETY: `drvdata_ptr` was stored by `Chip::new` from an owned `T::DrvData`
+ // and is guaranteed to be valid if non-null. `from_foreign` can safely
+ // reclaim ownership to allow Rust to drop and free the data.
+ let _owned_drvdata = unsafe { T::DrvData::from_foreign(drvdata_ptr.cast()) };
+
+ // 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); }
+ }
+
+ /// # Safety
+ ///
+ /// Pointers from C must be valid.
+ unsafe extern "C" fn request_callback(
+ c: *mut bindings::pwm_chip,
+ p: *mut bindings::pwm_device,
+ ) -> c_int {
+ // SAFETY: PWM core guarentees `c` and `p` are valid pointers.
+ let (chip, pwm) = unsafe { (Chip::<T::DrvData>::as_ref(c), Device::as_ref(p)) };
+
+ // SAFETY: The PWM core guarantees the parent device exists and is bound during callbacks.
+ let bound_parent = unsafe { chip.bound_parent_device() };
+ match T::request(chip, pwm, bound_parent) {
+ Ok(()) => 0,
+ Err(e) => e.to_errno(),
+ }
+ }
+
+ /// # Safety
+ ///
+ /// Pointers from C must be valid.
+ unsafe extern "C" fn capture_callback(
+ c: *mut bindings::pwm_chip,
+ p: *mut bindings::pwm_device,
+ res: *mut bindings::pwm_capture,
+ timeout: usize,
+ ) -> c_int {
+ // SAFETY: Relies on the function's contract that `c` and `p` are valid pointers.
+ let (chip, pwm, result) =
+ unsafe { (Chip::<T::DrvData>::as_ref(c), Device::as_ref(p), &mut *res) };
+
+ // SAFETY: The PWM core guarantees the parent device exists and is bound during callbacks.
+ let bound_parent = unsafe { chip.bound_parent_device() };
+ match T::capture(chip, pwm, result, timeout, bound_parent) {
+ Ok(()) => 0,
+ Err(e) => e.to_errno(),
+ }
+ }
+
+ /// # Safety
+ ///
+ /// Pointers from C must be valid.
+ unsafe extern "C" fn round_waveform_tohw_callback(
+ c: *mut bindings::pwm_chip,
+ p: *mut bindings::pwm_device,
+ w: *const bindings::pwm_waveform,
+ wh: *mut c_void,
+ ) -> c_int {
+ // SAFETY: Relies on the function's contract that `c` and `p` are valid pointers.
+ let (chip, pwm, wf) = unsafe {
+ (
+ Chip::<T::DrvData>::as_ref(c),
+ Device::as_ref(p),
+ Waveform::from(*w),
+ )
+ };
+ match T::round_waveform_tohw(chip, pwm, &wf) {
+ Ok((status, wfhw)) => {
+ // SAFETY: `wh` is valid per this function's safety contract.
+ if unsafe { Self::serialize_wfhw(&wfhw, wh) }.is_err() {
+ return EINVAL.to_errno();
+ }
+ status
+ }
+ Err(e) => e.to_errno(),
+ }
+ }
+
+ /// # Safety
+ ///
+ /// Pointers from C must be valid.
+ unsafe extern "C" fn round_waveform_fromhw_callback(
+ c: *mut bindings::pwm_chip,
+ p: *mut bindings::pwm_device,
+ wh: *const c_void,
+ w: *mut bindings::pwm_waveform,
+ ) -> c_int {
+ // SAFETY: Relies on the function's contract that `c` and `p` are valid pointers.
+ let (chip, pwm) = unsafe { (Chip::<T::DrvData>::as_ref(c), Device::as_ref(p)) };
+ // SAFETY: `deserialize_wfhw`'s safety contract is met by this function's contract.
+ let wfhw = match unsafe { Self::deserialize_wfhw(wh) } {
+ Ok(v) => v,
+ Err(e) => return e.to_errno(),
+ };
+
+ let mut rust_wf = Waveform::default();
+ match T::round_waveform_fromhw(chip, pwm, &wfhw, &mut rust_wf) {
+ Ok(ret) => {
+ // SAFETY: `w` is guaranteed valid by the C caller.
+ unsafe {
+ *w = rust_wf.into();
+ };
+ ret
+ }
+ Err(e) => e.to_errno(),
+ }
+ }
+
+ /// # Safety
+ ///
+ /// Pointers from C must be valid.
+ unsafe extern "C" fn read_waveform_callback(
+ c: *mut bindings::pwm_chip,
+ p: *mut bindings::pwm_device,
+ wh: *mut c_void,
+ ) -> c_int {
+ // SAFETY: Relies on the function's contract that `c` and `p` are valid pointers.
+ let (chip, pwm) = unsafe { (Chip::<T::DrvData>::as_ref(c), Device::as_ref(p)) };
+
+ // SAFETY: The PWM core guarantees the parent device exists and is bound during callbacks.
+ let bound_parent = unsafe { chip.bound_parent_device() };
+ match T::read_waveform(chip, pwm, bound_parent) {
+ // SAFETY: `wh` is valid per this function's safety contract.
+ Ok(wfhw) => match unsafe { Self::serialize_wfhw(&wfhw, wh) } {
+ Ok(()) => 0,
+ Err(e) => e.to_errno(),
+ },
+ Err(e) => e.to_errno(),
+ }
+ }
+
+ /// # Safety
+ ///
+ /// Pointers from C must be valid.
+ unsafe extern "C" fn write_waveform_callback(
+ c: *mut bindings::pwm_chip,
+ p: *mut bindings::pwm_device,
+ wh: *const c_void,
+ ) -> c_int {
+ // SAFETY: Relies on the function's contract that `c` and `p` are valid pointers.
+ let (chip, pwm) = unsafe { (Chip::<T::DrvData>::as_ref(c), Device::as_ref(p)) };
+
+ // SAFETY: The PWM core guarantees the parent device exists and is bound during callbacks.
+ let bound_parent = unsafe { chip.bound_parent_device() };
+
+ // SAFETY: `wh` is valid per this function's safety contract.
+ let wfhw = match unsafe { Self::deserialize_wfhw(wh) } {
+ Ok(v) => v,
+ Err(e) => return e.to_errno(),
+ };
+ match T::write_waveform(chip, pwm, &wfhw, bound_parent) {
+ Ok(()) => 0,
+ Err(e) => e.to_errno(),
+ }
+ }
+}
+
+/// VTable structure wrapper for PWM operations.
+/// Mirrors [`struct pwm_ops`](srctree/include/linux/pwm.h).
+#[repr(transparent)]
+pub struct PwmOpsVTable(bindings::pwm_ops);
+
+// SAFETY: PwmOpsVTable is Send. The vtable contains only function pointers
+// and a size, which are simple data types that can be safely moved across
+// threads. The thread-safety of calling these functions is handled by the
+// kernel's locking mechanisms.
+unsafe impl Send for PwmOpsVTable {}
+
+// SAFETY: PwmOpsVTable is Sync. The vtable is immutable after it is created,
+// so it can be safely referenced and accessed concurrently by multiple threads
+// e.g. to read the function pointers.
+unsafe impl Sync for PwmOpsVTable {}
+
+impl PwmOpsVTable {
+ /// Returns a raw pointer to the underlying `pwm_ops` struct.
+ pub(crate) fn as_raw(&self) -> *const bindings::pwm_ops {
+ &self.0
+ }
+}
+
+/// Creates a PWM operations vtable for a type `T` that implements `PwmOps`.
+///
+/// This is used to bridge Rust trait implementations to the C `struct pwm_ops`
+/// expected by the kernel.
+pub const fn create_pwm_ops<T: PwmOps>() -> PwmOpsVTable {
+ // SAFETY: `core::mem::zeroed()` is unsafe. For `pwm_ops`, all fields are
+ // `Option<extern "C" fn(...)>` or data, so a zeroed pattern (None/0) is valid initially.
+ let mut ops: bindings::pwm_ops = unsafe { core::mem::zeroed() };
+
+ ops.request = Some(Adapter::<T>::request_callback);
+ ops.capture = Some(Adapter::<T>::capture_callback);
+
+ ops.round_waveform_tohw = Some(Adapter::<T>::round_waveform_tohw_callback);
+ ops.round_waveform_fromhw = Some(Adapter::<T>::round_waveform_fromhw_callback);
+ ops.read_waveform = Some(Adapter::<T>::read_waveform_callback);
+ ops.write_waveform = Some(Adapter::<T>::write_waveform_callback);
+ ops.sizeof_wfhw = core::mem::size_of::<T::WfHw>();
+
+ PwmOpsVTable(ops)
+}
+
+/// Wrapper for a PWM chip/controller ([`struct pwm_chip`](srctree/include/linux/pwm.h)).
+#[repr(transparent)]
+pub struct Chip<T: ForeignOwnable>(Opaque<bindings::pwm_chip>, PhantomData<T>);
+
+impl<T: ForeignOwnable> Chip<T> {
+ /// Creates a reference to a [`Chip`] from a valid pointer.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that `ptr` is valid and remains valid for the lifetime of the
+ /// returned [`Chip`] reference.
+ pub(crate) unsafe fn as_ref<'a>(ptr: *mut bindings::pwm_chip) -> &'a Self {
+ // SAFETY: The safety requirements guarantee the validity of the dereference, while the
+ // `Chip` type being transparent makes the cast ok.
+ unsafe { &*ptr.cast::<Self>() }
+ }
+
+ /// Returns a raw pointer to the underlying `pwm_chip`.
+ pub(crate) fn as_raw(&self) -> *mut bindings::pwm_chip {
+ self.0.get()
+ }
+
+ /// Gets the number of PWM channels (hardware PWMs) on this chip.
+ pub fn npwm(&self) -> u32 {
+ // SAFETY: `self.as_raw()` provides a valid pointer for `self`'s lifetime.
+ unsafe { (*self.as_raw()).npwm }
+ }
+
+ /// Returns `true` if the chip supports atomic operations for configuration.
+ pub fn is_atomic(&self) -> bool {
+ // SAFETY: `self.as_raw()` provides a valid pointer for `self`'s lifetime.
+ unsafe { (*self.as_raw()).atomic }
+ }
+
+ /// Returns a reference to the embedded `struct device` abstraction.
+ pub fn device(&self) -> &device::Device {
+ // SAFETY: `self.as_raw()` provides a valid pointer to `bindings::pwm_chip`.
+ // The `dev` field is an instance of `bindings::device` embedded within `pwm_chip`.
+ // Taking a pointer to this embedded field is valid.
+ // `device::Device` is `#[repr(transparent)]`.
+ // The lifetime of the returned reference is tied to `self`.
+ unsafe { device::Device::as_ref(&raw mut (*self.as_raw()).dev) }
+ }
+
+ /// Returns a reference to the parent device of this PWM chip's device.
+ ///
+ /// # Safety
+ ///
+ /// The caller must guarantee that the parent device exists and is bound.
+ /// This is guaranteed by the PWM core during `PwmOps` callbacks.
+ unsafe fn bound_parent_device(&self) -> &device::Device<Bound> {
+ // SAFETY: Per the function's safety contract, the parent device exists.
+ let parent = unsafe { self.device().parent().unwrap_unchecked() };
+
+ // SAFETY: Per the function's safety contract, the parent device is bound.
+ // The pointer is cast from `&Device` to `&Device<Bound>`.
+ unsafe { &*core::ptr::from_ref(parent).cast::<device::Device<Bound>>() }
+ }
+}
+
+impl<T: 'static + ForeignOwnable> Chip<T> {
+ /// Allocates and wraps a PWM chip using `bindings::pwmchip_alloc`.
+ ///
+ /// Returns an [`ARef<Chip>`] managing the chip's lifetime via refcounting
+ /// on its embedded `struct device`.
+ pub fn new<O: PwmOps<DrvData = T>>(
+ parent_dev: &device::Device,
+ npwm: u32,
+ sizeof_priv: usize,
+ drvdata: T,
+ ) -> Result<ARef<Self>> {
+ // SAFETY: `parent_device_for_dev_field.as_raw()` is valid.
+ // `bindings::pwmchip_alloc` returns a valid `*mut bindings::pwm_chip` (refcount 1)
+ // or an ERR_PTR.
+ let c_chip_ptr_raw =
+ unsafe { bindings::pwmchip_alloc(parent_dev.as_raw(), npwm, sizeof_priv) };
+
+ let c_chip_ptr: *mut bindings::pwm_chip = error::from_err_ptr(c_chip_ptr_raw)?;
+
+ // Set the custom release function on the embedded device. This is the crucial step
+ // to ensure `drvdata` is freed when the chip's refcount reaches zero, regardless
+ // of whether `Registration::register` was called.
+ // SAFETY: `c_chip_ptr` points to a valid chip.
+ unsafe { (*c_chip_ptr).dev.release = Some(Adapter::<O>::release_callback); }
+
+ // SAFETY: `c_chip_ptr` points to a valid chip from `pwmchip_alloc`.
+ // The `Adapter`'s `VTABLE` has a 'static lifetime, so the pointer
+ // returned by `as_raw()` is always valid.
+ unsafe { (*c_chip_ptr).ops = Adapter::<O>::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
+ // `Opaque<T>` is `repr(transparent)` over `T`.
+ let chip_ptr_as_self = c_chip_ptr.cast::<Self>();
+
+ // SAFETY: The pointer is valid, so we can create a temporary ref to set data.
+ let chip_ref = unsafe { &*chip_ptr_as_self };
+ // SAFETY: `chip_ref` points to a valid chip from `pwmchip_alloc` and `drvdata` is a valid,
+ // owned pointer from `ForeignOwnable` to be stored in the chip's private data.
+ unsafe { bindings::pwmchip_set_drvdata(chip_ref.as_raw(), drvdata.into_foreign().cast()) }
+
+ // 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)) })
+ }
+
+ /// Gets the *typed* driver-specific data associated with this chip's embedded device.
+ pub fn drvdata(&self) -> T::Borrowed<'_> {
+ // SAFETY: `self.as_raw()` gives a valid pwm_chip pointer.
+ // `bindings::pwmchip_get_drvdata` is the C function to retrieve driver data.
+ let ptr = unsafe { bindings::pwmchip_get_drvdata(self.as_raw()) };
+
+ // SAFETY: The only way to create a chip is through Chip::new, which initializes
+ // this pointer.
+ unsafe { T::borrow(ptr.cast()) }
+ }
+}
+
+// SAFETY: Implements refcounting for `Chip` using the embedded `struct device`.
+unsafe impl<T: ForeignOwnable> AlwaysRefCounted for Chip<T> {
+ #[inline]
+ 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); }
+ }
+
+ #[inline]
+ unsafe fn dec_ref(obj: NonNull<Chip<T>>) {
+ let c_chip_ptr = obj.cast::<bindings::pwm_chip>().as_ptr();
+
+ // 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); }
+ }
+}
+
+// SAFETY: `Chip` is a wrapper around `*mut bindings::pwm_chip`. The underlying C
+// 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: ForeignOwnable + Send> 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: ForeignOwnable + Sync> 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: ForeignOwnable> {
+ chip: ARef<Chip<T>>,
+}
+
+impl<T: 'static + ForeignOwnable + Send + Sync> Registration<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();
+
+ // 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()))?;
+ }
+
+ let registration = Registration { chip };
+
+ devres::register(dev, registration, GFP_KERNEL)
+ }
+}
+
+impl<T: ForeignOwnable> Drop for Registration<T> {
+ fn drop(&mut self) {
+ let chip_raw = self.chip.as_raw();
+
+ // 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);
+ }
+ }
+}
--
2.34.1
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v10 4/7] pwm: Add Rust driver for T-HEAD TH1520 SoC
[not found] ` <CGME20250707094932eucas1p13a9a26fd50a868c3212802a9bf8cb585@eucas1p1.samsung.com>
@ 2025-07-07 9:48 ` Michal Wilczynski
0 siblings, 0 replies; 23+ messages in thread
From: Michal Wilczynski @ 2025-07-07 9:48 UTC (permalink / raw)
To: Uwe Kleine-König, Miguel Ojeda, Alex Gaynor, Boqun Feng,
Gary Guo, Björn Roy Baron, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Michal Wilczynski, Guo Ren,
Fu Wei, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti,
Marek Szyprowski, Benno Lossin, Michael Turquette, Drew Fustini,
Benno Lossin, Drew Fustini
Cc: linux-kernel, linux-pwm, rust-for-linux, linux-riscv, devicetree
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.
Signed-off-by: Michal Wilczynski <m.wilczynski@samsung.com>
---
MAINTAINERS | 1 +
drivers/pwm/Kconfig | 11 ++
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm_th1520.rs | 352 ++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 365 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 494de42ca8c36b30d80e14b03d3c9e0e054fb267..2449178a6d7b83b5202f8209c0b38e8302bc6b15 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21531,6 +21531,7 @@ F: drivers/net/ethernet/stmicro/stmmac/dwmac-thead.c
F: drivers/pinctrl/pinctrl-th1520.c
F: drivers/pmdomain/thead/
F: drivers/power/sequencing/pwrseq-thead-gpu.c
+F: drivers/pwm/pwm_th1520.rs
F: drivers/reset/reset-th1520.c
F: include/dt-bindings/clock/thead,th1520-clk-ap.h
F: include/dt-bindings/power/thead,th1520-power.h
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index c32655566d6ab9eff9d10f29e469f9aef89cecfa..02faf93600b6464d3c02495eeb5824ea541cff35 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -728,6 +728,17 @@ config PWM_TEGRA
To compile this driver as a module, choose M here: the module
will be called pwm-tegra.
+config PWM_TH1520
+ tristate "TH1520 PWM support"
+ depends on RUST
+ select RUST_PWM_ABSTRACTIONS
+ help
+ This option enables the driver for the PWM controller found on the
+ T-HEAD TH1520 SoC.
+
+ To compile this driver as a module, choose M here; the module
+ will be called pwm-th1520. If you are unsure, say N.
+
config PWM_TIECAP
tristate "ECAP PWM support"
depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX || ARCH_KEYSTONE || ARCH_K3 || COMPILE_TEST
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index ff4f47e5fb7a0dbac72c12de82c3773e5582db6d..5c15c95c6e49143969389198657eed0ecf4086b2 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -67,6 +67,7 @@ obj-$(CONFIG_PWM_STMPE) += pwm-stmpe.o
obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o
obj-$(CONFIG_PWM_SUNPLUS) += pwm-sunplus.o
obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o
+obj-$(CONFIG_PWM_TH1520) += pwm_th1520.o
obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o
obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o
obj-$(CONFIG_PWM_TWL) += pwm-twl.o
diff --git a/drivers/pwm/pwm_th1520.rs b/drivers/pwm/pwm_th1520.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c6cd90552cc57219cd393e1c867dfbf5587cee00
--- /dev/null
+++ b/drivers/pwm/pwm_th1520.rs
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2025 Samsung Electronics Co., Ltd.
+// Author: Michal Wilczynski <m.wilczynski@samsung.com>
+
+//! Rust T-HEAD TH1520 PWM driver
+//!
+//! Limitations:
+//! - The period and duty cycle are controlled by 32-bit hardware registers,
+//! limiting the maximum resolution.
+//! - The driver supports continuous output mode only; one-shot mode is not
+//! implemented.
+//! - The controller hardware provides up to 6 PWM channels.
+//! - Reconfiguration is glitch free - new period and duty cycle values are
+//! latched and take effect at the start of the next period.
+//! - Polarity is handled via a simple hardware inversion bit; arbitrary
+//! duty cycle offsets are not supported.
+//! - Disabling a channel is achieved by configuring its duty cycle to zero to
+//! produce a static low output. Clearing the `start` does not reliably
+//! force the static inactive level defined by the `INACTOUT` bit. Hence
+//! this method is not used in this driver.
+//!
+
+use core::ops::Deref;
+use kernel::{
+ c_str,
+ clk::Clk,
+ device::{Bound, Core, Device},
+ devres,
+ io::mem::IoMem,
+ of, platform,
+ prelude::*,
+ pwm, time,
+};
+
+const TH1520_MAX_PWM_NUM: u32 = 6;
+
+// Register offsets
+const fn th1520_pwm_chn_base(n: u32) -> usize {
+ (n * 0x20) as usize
+}
+
+const fn th1520_pwm_ctrl(n: u32) -> usize {
+ th1520_pwm_chn_base(n)
+}
+
+const fn th1520_pwm_per(n: u32) -> usize {
+ th1520_pwm_chn_base(n) + 0x08
+}
+
+const fn th1520_pwm_fp(n: u32) -> usize {
+ th1520_pwm_chn_base(n) + 0x0c
+}
+
+// Control register bits
+const TH1520_PWM_START: u32 = 1 << 0;
+const TH1520_PWM_CFG_UPDATE: u32 = 1 << 2;
+const TH1520_PWM_CONTINUOUS_MODE: u32 = 1 << 5;
+const TH1520_PWM_FPOUT: u32 = 1 << 8;
+
+const TH1520_PWM_REG_SIZE: usize = 0xB0;
+
+fn ns_to_cycles(ns: u64, rate_hz: u64) -> u64 {
+ const NSEC_PER_SEC_U64: u64 = time::NSEC_PER_SEC as u64;
+
+ (match ns.checked_mul(rate_hz) {
+ Some(product) => product,
+ None => u64::MAX,
+ }) / NSEC_PER_SEC_U64
+}
+
+fn cycles_to_ns(cycles: u64, rate_hz: u64) -> u64 {
+ const NSEC_PER_SEC_U64: u64 = time::NSEC_PER_SEC as u64;
+
+ // Round up
+ let Some(numerator) = cycles
+ .checked_mul(NSEC_PER_SEC_U64)
+ .and_then(|p| p.checked_add(rate_hz - 1))
+ else {
+ return u64::MAX;
+ };
+
+ numerator / rate_hz
+}
+
+/// Hardware-specific waveform representation for TH1520.
+#[derive(Copy, Clone, Debug, Default)]
+struct Th1520WfHw {
+ period_cycles: u32,
+ duty_cycles: u32,
+ ctrl_val: u32,
+ enabled: bool,
+}
+
+/// The driver's private data struct. It holds all necessary devres managed resources.
+#[pin_data(PinnedDrop)]
+struct Th1520PwmDriverData {
+ #[pin]
+ iomem: devres::Devres<IoMem<TH1520_PWM_REG_SIZE>>,
+ clk: Clk,
+}
+
+// This `unsafe` implementation is a temporary necessity because the underlying `kernel::clk::Clk`
+// type does not yet expose `Send` and `Sync` implementations. This block should be removed
+// as soon as the clock abstraction provides these guarantees directly.
+// TODO: Remove those unsafe impl's when Clk will support them itself.
+
+// SAFETY: The `devres` framework requires the driver's private data to be `Send` and `Sync`.
+// We can guarantee this because the PWM core synchronizes all callbacks, preventing concurrent
+// access to the contained `iomem` and `clk` resources.
+unsafe impl Send for Th1520PwmDriverData {}
+
+// SAFETY: The same reasoning applies as for `Send`. The PWM core's synchronization
+// guarantees that it is safe for multiple threads to have shared access (`&self`)
+// to the driver data during callbacks.
+unsafe impl Sync for Th1520PwmDriverData {}
+
+impl pwm::PwmOps for Th1520PwmDriverData {
+ type DrvData = Pin<KBox<Th1520PwmDriverData>>;
+ type WfHw = Th1520WfHw;
+
+ fn round_waveform_tohw(
+ chip: &pwm::Chip<Self::DrvData>,
+ _pwm: &pwm::Device,
+ wf: &pwm::Waveform,
+ ) -> Result<(c_int, Self::WfHw)> {
+ let data = chip.drvdata();
+
+ if wf.period_length_ns == 0 {
+ return Ok((
+ 0,
+ Th1520WfHw {
+ enabled: false,
+ ..Default::default()
+ },
+ ));
+ }
+
+ let rate_hz = data.clk.rate().as_hz() as u64;
+
+ let period_cycles = ns_to_cycles(wf.period_length_ns, rate_hz).min(u64::from(u32::MAX));
+ let mut duty_cycles = ns_to_cycles(wf.duty_length_ns, rate_hz).min(u64::from(u32::MAX));
+
+ let mut ctrl_val = TH1520_PWM_CONTINUOUS_MODE;
+
+ let is_inversed = wf.duty_length_ns > 0
+ && wf.duty_offset_ns > 0
+ && wf.duty_length_ns + wf.duty_offset_ns >= wf.period_length_ns;
+ if is_inversed {
+ duty_cycles = period_cycles - duty_cycles;
+ } else {
+ ctrl_val |= TH1520_PWM_FPOUT;
+ }
+
+ let wfhw = Th1520WfHw {
+ period_cycles: period_cycles as u32,
+ duty_cycles: duty_cycles as u32,
+ ctrl_val,
+ enabled: true,
+ };
+
+ dev_dbg!(
+ chip.device(),
+ "clk_rate: {}Hz Requested: period {}ns, duty {}ns, offset {}ns -> HW: period {} cyc, duty {} cyc, ctrl 0x{:x}\n",
+ rate_hz,
+ wf.period_length_ns,
+ wf.duty_length_ns,
+ wf.duty_offset_ns,
+ wfhw.period_cycles,
+ wfhw.duty_cycles,
+ wfhw.ctrl_val
+ );
+
+ Ok((0, wfhw))
+ }
+
+ fn round_waveform_fromhw(
+ chip: &pwm::Chip<Self::DrvData>,
+ _pwm: &pwm::Device,
+ wfhw: &Self::WfHw,
+ wf: &mut pwm::Waveform,
+ ) -> Result<c_int> {
+ let data = chip.drvdata();
+ let rate_hz = data.clk.rate().as_hz() as u64;
+
+ wf.period_length_ns = cycles_to_ns(u64::from(wfhw.period_cycles), rate_hz);
+
+ let duty_cycles = u64::from(wfhw.duty_cycles);
+
+ if (wfhw.ctrl_val & TH1520_PWM_FPOUT) != 0 {
+ wf.duty_length_ns = cycles_to_ns(duty_cycles, rate_hz);
+ wf.duty_offset_ns = 0;
+ } else {
+ let period_cycles = u64::from(wfhw.period_cycles);
+ let original_duty_cycles = period_cycles.saturating_sub(duty_cycles);
+
+ // For an inverted signal, `duty_length_ns` is the high time (period - low_time).
+ wf.duty_length_ns = cycles_to_ns(original_duty_cycles, rate_hz);
+ // The offset is the initial low time, which is what the hardware register provides.
+ wf.duty_offset_ns = cycles_to_ns(duty_cycles, rate_hz);
+ }
+
+ Ok(0)
+ }
+
+ fn read_waveform(
+ chip: &pwm::Chip<Self::DrvData>,
+ pwm: &pwm::Device,
+ parent_dev: &Device<Bound>,
+ ) -> Result<Self::WfHw> {
+ let data = chip.drvdata();
+ let hwpwm = pwm.hwpwm();
+ let iomem_accessor = data.iomem.access(parent_dev)?;
+ let iomap = iomem_accessor.deref();
+
+ let ctrl = iomap.try_read32(th1520_pwm_ctrl(hwpwm))?;
+ let period_cycles = iomap.try_read32(th1520_pwm_per(hwpwm))?;
+ let duty_cycles = iomap.try_read32(th1520_pwm_fp(hwpwm))?;
+
+ let wfhw = Th1520WfHw {
+ period_cycles,
+ duty_cycles,
+ ctrl_val: ctrl,
+ enabled: duty_cycles != 0,
+ };
+
+ dev_dbg!(
+ chip.device(),
+ "PWM-{}: read_waveform: Read hw state - period: {}, duty: {}, ctrl: 0x{:x}, enabled: {}",
+ hwpwm,
+ wfhw.period_cycles,
+ wfhw.duty_cycles,
+ wfhw.ctrl_val,
+ wfhw.enabled
+ );
+
+ Ok(wfhw)
+ }
+
+ fn write_waveform(
+ chip: &pwm::Chip<Self::DrvData>,
+ pwm: &pwm::Device,
+ wfhw: &Self::WfHw,
+ parent_dev: &Device<Bound>,
+ ) -> Result {
+ let data = chip.drvdata();
+ let hwpwm = pwm.hwpwm();
+ let iomem_accessor = data.iomem.access(parent_dev)?;
+ let iomap = iomem_accessor.deref();
+ let was_enabled = pwm.state().enabled();
+
+ if !wfhw.enabled {
+ if was_enabled {
+ iomap.try_write32(wfhw.ctrl_val, th1520_pwm_ctrl(hwpwm))?;
+ iomap.try_write32(0, th1520_pwm_fp(hwpwm))?;
+ iomap.try_write32(wfhw.ctrl_val | TH1520_PWM_CFG_UPDATE, th1520_pwm_ctrl(hwpwm))?;
+ }
+ return Ok(());
+ }
+
+ iomap.try_write32(wfhw.ctrl_val, th1520_pwm_ctrl(hwpwm))?;
+ iomap.try_write32(wfhw.period_cycles, th1520_pwm_per(hwpwm))?;
+ iomap.try_write32(wfhw.duty_cycles, th1520_pwm_fp(hwpwm))?;
+ iomap.try_write32(wfhw.ctrl_val | TH1520_PWM_CFG_UPDATE, th1520_pwm_ctrl(hwpwm))?;
+
+ // The `TH1520_PWM_START` bit must be written in a separate, final transaction, and
+ // only when enabling the channel from a disabled state.
+ if !was_enabled {
+ iomap.try_write32(wfhw.ctrl_val | TH1520_PWM_START, th1520_pwm_ctrl(hwpwm))?;
+ }
+
+ dev_dbg!(
+ chip.device(),
+ "PWM-{}: Wrote (per: {}, duty: {})",
+ hwpwm,
+ wfhw.period_cycles,
+ wfhw.duty_cycles,
+ );
+
+ Ok(())
+ }
+}
+
+#[pinned_drop]
+impl PinnedDrop for Th1520PwmDriverData {
+ fn drop(self: Pin<&mut Self>) {
+ self.clk.disable_unprepare();
+ }
+}
+
+struct Th1520PwmPlatformDriver;
+
+kernel::of_device_table!(
+ OF_TABLE,
+ MODULE_OF_TABLE,
+ <Th1520PwmPlatformDriver as platform::Driver>::IdInfo,
+ [(of::DeviceId::new(c_str!("thead,th1520-pwm")), ())]
+);
+
+impl platform::Driver for Th1520PwmPlatformDriver {
+ type IdInfo = ();
+ const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+
+ fn probe(
+ pdev: &platform::Device<Core>,
+ _id_info: Option<&Self::IdInfo>,
+ ) -> Result<Pin<KBox<Self>>> {
+ let dev = pdev.as_ref();
+ let resource = pdev.resource(0).ok_or(ENODEV)?;
+
+ let drvdata = KBox::pin_init(
+ try_pin_init!(Th1520PwmDriverData {
+ iomem <- pdev.ioremap_resource_sized::<TH1520_PWM_REG_SIZE>(resource),
+ clk <- Clk::get(dev, None),
+ }),
+ GFP_KERNEL,
+ )?;
+
+ drvdata.clk.prepare_enable()?;
+
+ // TODO: Get exclusive ownership of the clock to prevent rate changes.
+ // The Rust equivalent of `clk_rate_exclusive_get()` is not yet available.
+ // This should be updated once it is implemented.
+ let rate_hz = drvdata.clk.rate().as_hz();
+ if rate_hz == 0 {
+ dev_err!(dev, "Clock rate is zero\n");
+ return Err(EINVAL);
+ }
+
+ if rate_hz > time::NSEC_PER_SEC as usize {
+ dev_err!(
+ dev,
+ "Clock rate {} Hz is too high, not supported.\n",
+ rate_hz
+ );
+ return Err(ERANGE);
+ }
+
+ let chip = pwm::Chip::new::<Th1520PwmDriverData>(dev, TH1520_MAX_PWM_NUM, 0, drvdata)?;
+
+ pwm::Registration::register(dev, chip)?;
+
+ Ok(KBox::new(Th1520PwmPlatformDriver, GFP_KERNEL)?.into())
+ }
+}
+
+kernel::module_platform_driver! {
+ type: Th1520PwmPlatformDriver,
+ name: "pwm-th1520",
+ authors: ["Michal Wilczynski <m.wilczynski@samsung.com>"],
+ description: "T-HEAD TH1520 PWM driver",
+ license: "GPL v2",
+}
--
2.34.1
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v10 5/7] dt-bindings: pwm: thead: Add T-HEAD TH1520 PWM controller
[not found] ` <CGME20250707094933eucas1p13228bedac1f0787d158956932ed81fd9@eucas1p1.samsung.com>
@ 2025-07-07 9:48 ` Michal Wilczynski
0 siblings, 0 replies; 23+ messages in thread
From: Michal Wilczynski @ 2025-07-07 9:48 UTC (permalink / raw)
To: Uwe Kleine-König, Miguel Ojeda, Alex Gaynor, Boqun Feng,
Gary Guo, Björn Roy Baron, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Michal Wilczynski, Guo Ren,
Fu Wei, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti,
Marek Szyprowski, Benno Lossin, Michael Turquette, Drew Fustini,
Benno Lossin, Drew Fustini
Cc: linux-kernel, linux-pwm, rust-for-linux, linux-riscv, devicetree,
Krzysztof Kozlowski
Add the Device Tree binding documentation for the T-HEAD
TH1520 SoC PWM controller.
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Tested-by: Drew Fustini <fustini@kernel.org>
Signed-off-by: Michal Wilczynski <m.wilczynski@samsung.com>
---
.../devicetree/bindings/pwm/thead,th1520-pwm.yaml | 48 ++++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 49 insertions(+)
diff --git a/Documentation/devicetree/bindings/pwm/thead,th1520-pwm.yaml b/Documentation/devicetree/bindings/pwm/thead,th1520-pwm.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..855aec59ac53c430adc849271235686e87b10e6c
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/thead,th1520-pwm.yaml
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pwm/thead,th1520-pwm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: T-HEAD TH1520 PWM controller
+
+maintainers:
+ - Michal Wilczynski <m.wilczynski@samsung.com>
+
+allOf:
+ - $ref: pwm.yaml#
+
+properties:
+ compatible:
+ const: thead,th1520-pwm
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: SoC PWM clock
+
+ "#pwm-cells":
+ const: 3
+
+required:
+ - compatible
+ - reg
+ - clocks
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/thead,th1520-clk-ap.h>
+ soc {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ pwm@ffec01c000 {
+ compatible = "thead,th1520-pwm";
+ reg = <0xff 0xec01c000 0x0 0x4000>;
+ clocks = <&clk CLK_PWM>;
+ #pwm-cells = <3>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 2449178a6d7b83b5202f8209c0b38e8302bc6b15..b58853a503875f16a338274eba080f31c3442f9f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21522,6 +21522,7 @@ F: Documentation/devicetree/bindings/firmware/thead,th1520-aon.yaml
F: Documentation/devicetree/bindings/mailbox/thead,th1520-mbox.yaml
F: Documentation/devicetree/bindings/net/thead,th1520-gmac.yaml
F: Documentation/devicetree/bindings/pinctrl/thead,th1520-pinctrl.yaml
+F: Documentation/devicetree/bindings/pwm/thead,th1520-pwm.yaml
F: Documentation/devicetree/bindings/reset/thead,th1520-reset.yaml
F: arch/riscv/boot/dts/thead/
F: drivers/clk/thead/clk-th1520-ap.c
--
2.34.1
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v10 6/7] riscv: dts: thead: Add PWM controller node
[not found] ` <CGME20250707094934eucas1p156403009dcb9df7e9466c3418aac738a@eucas1p1.samsung.com>
@ 2025-07-07 9:48 ` Michal Wilczynski
0 siblings, 0 replies; 23+ messages in thread
From: Michal Wilczynski @ 2025-07-07 9:48 UTC (permalink / raw)
To: Uwe Kleine-König, Miguel Ojeda, Alex Gaynor, Boqun Feng,
Gary Guo, Björn Roy Baron, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Michal Wilczynski, Guo Ren,
Fu Wei, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti,
Marek Szyprowski, Benno Lossin, Michael Turquette, Drew Fustini,
Benno Lossin, Drew Fustini
Cc: linux-kernel, linux-pwm, rust-for-linux, linux-riscv, devicetree
Add the Device Tree node for the T-HEAD TH1520 SoC's PWM controller.
Reviewed-by: Drew Fustini <fustini@kernel.org>
Tested-by: Drew Fustini <fustini@kernel.org>
Signed-off-by: Michal Wilczynski <m.wilczynski@samsung.com>
---
arch/riscv/boot/dts/thead/th1520.dtsi | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/arch/riscv/boot/dts/thead/th1520.dtsi b/arch/riscv/boot/dts/thead/th1520.dtsi
index 42724bf7e90e08fac326c464d0f080e3bd2cd59b..513dc6977b2633503515ad260913156fbe57d92f 100644
--- a/arch/riscv/boot/dts/thead/th1520.dtsi
+++ b/arch/riscv/boot/dts/thead/th1520.dtsi
@@ -493,6 +493,13 @@ uart2: serial@ffec010000 {
status = "disabled";
};
+ pwm: pwm@ffec01c000 {
+ compatible = "thead,th1520-pwm";
+ reg = <0xff 0xec01c000 0x0 0x4000>;
+ clocks = <&clk CLK_PWM>;
+ #pwm-cells = <3>;
+ };
+
clk: clock-controller@ffef010000 {
compatible = "thead,th1520-clk-ap";
reg = <0xff 0xef010000 0x0 0x1000>;
--
2.34.1
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v10 7/7] riscv: dts: thead: Add PWM fan and thermal control
[not found] ` <CGME20250707094935eucas1p1d9ee9b8dac94ac16c48ae3a084884622@eucas1p1.samsung.com>
@ 2025-07-07 9:48 ` Michal Wilczynski
0 siblings, 0 replies; 23+ messages in thread
From: Michal Wilczynski @ 2025-07-07 9:48 UTC (permalink / raw)
To: Uwe Kleine-König, Miguel Ojeda, Alex Gaynor, Boqun Feng,
Gary Guo, Björn Roy Baron, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Michal Wilczynski, Guo Ren,
Fu Wei, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti,
Marek Szyprowski, Benno Lossin, Michael Turquette, Drew Fustini,
Benno Lossin, Drew Fustini
Cc: linux-kernel, linux-pwm, rust-for-linux, linux-riscv, devicetree
Add Device Tree nodes to enable a PWM controlled fan and it's associated
thermal management for the Lichee Pi 4A board.
This enables temperature-controlled active cooling for the Lichee Pi 4A
board based on SoC temperature.
Reviewed-by: Drew Fustini <fustini@kernel.org>
Tested-by: Drew Fustini <fustini@kernel.org>
Signed-off-by: Michal Wilczynski <m.wilczynski@samsung.com>
---
arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts | 67 +++++++++++++++++++++++
1 file changed, 67 insertions(+)
diff --git a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts
index 4020c727f09e8e2286fdc7fecd79dbd8eba69556..c58c2085ca92a3234f1350500cedae4157f0c35f 100644
--- a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts
+++ b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts
@@ -28,9 +28,76 @@ aliases {
chosen {
stdout-path = "serial0:115200n8";
};
+
+ thermal-zones {
+ cpu-thermal {
+ polling-delay = <1000>;
+ polling-delay-passive = <1000>;
+ thermal-sensors = <&pvt 0>;
+
+ trips {
+ fan_config0: fan-trip0 {
+ temperature = <39000>;
+ hysteresis = <5000>;
+ type = "active";
+ };
+
+ fan_config1: fan-trip1 {
+ temperature = <50000>;
+ hysteresis = <5000>;
+ type = "active";
+ };
+
+ fan_config2: fan-trip2 {
+ temperature = <60000>;
+ hysteresis = <5000>;
+ type = "active";
+ };
+ };
+
+ cooling-maps {
+ map-active-0 {
+ cooling-device = <&fan 1 1>;
+ trip = <&fan_config0>;
+ };
+
+ map-active-1 {
+ cooling-device = <&fan 2 2>;
+ trip = <&fan_config1>;
+ };
+
+ map-active-2 {
+ cooling-device = <&fan 3 3>;
+ trip = <&fan_config2>;
+ };
+ };
+ };
+ };
+
+ fan: pwm-fan {
+ pinctrl-names = "default";
+ pinctrl-0 = <&fan_pins>;
+ compatible = "pwm-fan";
+ #cooling-cells = <2>;
+ pwms = <&pwm 1 10000000 0>;
+ cooling-levels = <0 66 196 255>;
+ };
+
};
&padctrl0_apsys {
+ fan_pins: fan-0 {
+ pwm1-pins {
+ pins = "GPIO3_3"; /* PWM1 */
+ function = "pwm";
+ bias-disable;
+ drive-strength = <25>;
+ input-disable;
+ input-schmitt-disable;
+ slew-rate = <0>;
+ };
+ };
+
uart0_pins: uart0-0 {
tx-pins {
pins = "UART0_TXD";
--
2.34.1
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver
2025-07-07 9:48 ` [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver Michal Wilczynski
` (6 preceding siblings ...)
[not found] ` <CGME20250707094935eucas1p1d9ee9b8dac94ac16c48ae3a084884622@eucas1p1.samsung.com>
@ 2025-07-10 8:42 ` Michal Wilczynski
2025-07-10 10:17 ` Danilo Krummrich
2025-07-10 13:10 ` Uwe Kleine-König
7 siblings, 2 replies; 23+ messages in thread
From: Michal Wilczynski @ 2025-07-10 8:42 UTC (permalink / raw)
To: Uwe Kleine-König, Miguel Ojeda, Alex Gaynor, Boqun Feng,
Gary Guo, Björn Roy Baron, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Guo Ren, Fu Wei, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Marek Szyprowski, Benno Lossin,
Michael Turquette, Drew Fustini
Cc: linux-kernel, linux-pwm, rust-for-linux, linux-riscv, devicetree,
Krzysztof Kozlowski
On 7/7/25 11:48, Michal Wilczynski wrote:
> This patch series introduces Rust support for the T-HEAD TH1520 PWM
> controller and demonstrates its use for fan control on the Sipeed Lichee
> Pi 4A board.
>
> The primary goal of this patch series is to introduce a basic set of
> Rust abstractions for the Linux PWM subsystem. As a first user and
> practical demonstration of these abstractions, the series also provides
> a functional PWM driver for the T-HEAD TH1520 SoC. This allows control
> of its PWM channels and ultimately enables temperature controlled fan
> support for the Lichee Pi 4A board. This work aims to explore the use of
> Rust for PWM drivers and lay a foundation for potential future
> Rust based PWM drivers.
>
> The core of this series is a new rust/kernel/pwm.rs module that provides
> abstractions for writing PWM chip provider drivers in Rust. This has
> been significantly reworked from v1 based on extensive feedback. The key
> features of the new abstraction layer include:
>
> - Ownership and Lifetime Management: The pwm::Chip wrapper is managed
> by ARef, correctly tying its lifetime to its embedded struct device
> reference counter. Chip registration is handled by a pwm::Registration
> RAII guard, which guarantees that pwmchip_add is always paired with
> pwmchip_remove, preventing resource leaks.
>
> - Modern and Safe API: The PwmOps trait is now based on the modern
> waveform API (round_waveform_tohw, write_waveform, etc.) as recommended
> by the subsystem maintainer. It is generic over a driver's
> hardware specific data structure, moving all unsafe serialization logic
> into the abstraction layer and allowing drivers to be written in 100%
> safe Rust.
>
> - Ergonomics: The API provides safe, idiomatic wrappers for other PWM
> types (State, Args, Device, etc.) and uses standard kernel error
> handling patterns.
>
> The series is structured as follows:
> - Expose static function pwmchip_release.
> - Rust PWM Abstractions: The new safe abstraction layer.
> - TH1520 PWM Driver: A new Rust driver for the TH1520 SoC, built on
> top of the new abstractions.
> - Device Tree Bindings & Nodes: The remaining patches add the necessary
> DT bindings and nodes for the TH1520 PWM controller, and the PWM fan
> configuration for the Lichee Pi 4A board.
>
> Testing:
> Tested on the TH1520 SoC. The fan works correctly. The duty/period
> calculations are correct. Fan starts slow when the chip is not hot and
> gradually increases the speed when PVT reports higher temperatures.
>
> The patches are based on mainline, with some dependencies which are not
> merged yet - platform Io support [1].
>
> Reference repository with all the patches together can be found on
> github [2].
>
> [1] - https://lore.kernel.org/rust-for-linux/20250509-topics-tyr-platform_iomem-v8-0-e9f1725a40da@collabora.com/
> [2] - https://github.com/mwilczy/linux/commits/rust-next-pwm-working-fan-for-sending-v15/
>
> ---
> Changes in v10:
> - Exported the C pwmchip_release function and called it from the custom
> Rust release_callback to fix a memory leak of the pwm_chip struct.
> - Removed the PwmOps::free callback, as it is not needed for idiomatic
> Rust resource management.
> - Removed the redundant is_null check for drvdata in the release handler,
> as the Rust API guarantees a valid pointer is always provided.
>
> - Link to v9: https://lore.kernel.org/r/20250706-rust-next-pwm-working-fan-for-sending-v9-0-42b5ac2101c7@samsung.com
>
> Changes in v9:
> - Encapsulated vtable setup in Chip::new(): The Chip::new() function is
> now generic over the PwmOps implementation. This allows it to create and
> assign the vtable internally, which simplifies the public API by
> removing the ops_vtable parameter from Registration::register().
> - Fixed memory leak with a release handler: A custom release_callback is
> now assigned to the embedded struct device's release hook. This
> guarantees that driver specific data is always freed when the chip is
> destroyed, even if registration fails.
> - The PwmOpsVTable is now defined as a const associated item to ensure
> it has a 'static lifetime.
> - Combined introductory commits: The Device, Chip, and PwmOps abstractions
> are now introduced in a single commit. This was necessary to resolve the
> circular dependencies between them and present a clean, compilable unit
> for review.
>
> - Link to v8: https://lore.kernel.org/r/20250704-rust-next-pwm-working-fan-for-sending-v8-0-951e5482c9fd@samsung.com
>
> Changes in v8:
> - Dropped already accepted commit, re-based on top of linux-next
> - Reworked the Chip and PwmOps APIs to address the drvdata() type-safety
> comment. Chip is now generic, and PwmOps uses an associated type
> to provide compile-time guarantees.
> - Added a parent device sanity check to Registration::register().
> - Updated drvdata() to return the idiomatic T::Borrowed<'_>.
> - added temporary unsafe blocks in the driver, as the current
> abstraction for Clk is neiter Safe nor Sync. I think eventually
> proper abstraction for Clk will be added as in a current state it's
> not very useful.
>
> - Link to v7: https://lore.kernel.org/r/20250702-rust-next-pwm-working-fan-for-sending-v7-0-67ef39ff1d29@samsung.com
>
> Changes in v7:
> - Made parent_device function private and moved casts to Device<Bound>
> there as well.
> - Link to v6: https://lore.kernel.org/r/20250701-rust-next-pwm-working-fan-for-sending-v6-0-2710932f6f6b@samsung.com
>
> Changes in v6:
> - Re-based on top of linux-next, dropped two already accepted commits.
> - After re-basing the IoMem dependent patchset stopped working,
> reworked it to use similar API like the PCI subsystem (I think it
> will end up the same). Re-worked the driver for it as well.
> - Remove the apply and get_state callbacks, and most of the State as
> well, as the old way of implementing drivers should not be possible
> in Rust. Left only enabled(), since it's useful for my driver.
> - Removed the public set_drvdata() method from pwm::Chip
> - Moved WFHWSIZE to the public include/linux/pwm.h header and renamed it
> to PWM_WFHWSIZE, allowing bindgen to create safe FFI bindings.
> - Corrected the ns_to_cycles integer calculation in the TH1520 driver to
> handle overflow correctly.
> - Updated the Kconfig entry for the TH1520 driver to select the Rust
> abstractions for a better user experience.
>
> - Link to v5: https://lore.kernel.org/r/20250623-rust-next-pwm-working-fan-for-sending-v5-0-0ca23747c23e@samsung.com
>
> Changes in v5:
> - Reworked `pwm::Chip` creation to take driver data directly, which
> allowed making the `chip.drvdata()` accessor infallible
> - added missing `pwm.c` file lost during the commit split (sorry !)
> - Link to v4: https://lore.kernel.org/r/20250618-rust-next-pwm-working-fan-for-sending-v4-0-a6a28f2b6d8a@samsung.com
>
> Changes in v4:
> - Reworked the pwm::Registration API to use the devres framework,
> addressing lifetime issue.
> - Corrected the PwmOps trait and its callbacks to use immutable references
> (&Chip, &Device) for improved safety.
> - Applied various code style and naming cleanups based on feedback
>
> - Link to v3: https://lore.kernel.org/r/20250617-rust-next-pwm-working-fan-for-sending-v3-0-1cca847c6f9f@samsung.com
>
> Changes in v3:
> - Addressed feedback from Uwe by making multiple changes to the TH1520
> driver and the abstraction layer.
> - Split the core PWM abstractions into three focused commits to ease
> review per Benno request.
> - Confirmed the driver now works correctly with CONFIG_PWM_DEBUG enabled
> by implementing the full waveform API, which correctly reads the
> hardware state.
> - Refactored the Rust code to build cleanly with
> CONFIG_RUST_BUILD_ASSERT_ALLOW=n, primarily by using the try_* family of
> functions for IoMem access.
> - Included several cosmetic changes and cleanups to the abstractions
> per Miguel review.
>
> - Link to v2: https://lore.kernel.org/r/20250610-rust-next-pwm-working-fan-for-sending-v2-0-753e2955f110@samsung.com
>
> Changes in v2:
> - Reworked the PWM abstraction layer based on extensive feedback.
> - Replaced initial devm allocation with a proper ARef<Chip> lifetime model
> using AlwaysRefCounted.
> - Implemented a Registration RAII guard to ensure safe chip add/remove.
> - Migrated the PwmOps trait from the legacy .apply callback to the modern
> waveform API.
> - Refactored the TH1520 driver to use the new, safer abstractions.
> - Added a patch to mark essential bus clocks as CLK_IGNORE_UNUSED to fix
> boot hangs when the PWM and thermal sensors are enabled.
> - Link to v1: https://lore.kernel.org/r/20250524-rust-next-pwm-working-fan-for-sending-v1-0-bdd2d5094ff7@samsung.com
>
> ---
> Michal Wilczynski (7):
> pwm: Export `pwmchip_release` for external use
> rust: pwm: Add Kconfig and basic data structures
> rust: pwm: Add complete abstraction layer
> pwm: Add Rust driver for T-HEAD TH1520 SoC
> dt-bindings: pwm: thead: Add T-HEAD TH1520 PWM controller
> riscv: dts: thead: Add PWM controller node
> riscv: dts: thead: Add PWM fan and thermal control
>
> .../devicetree/bindings/pwm/thead,th1520-pwm.yaml | 48 ++
> MAINTAINERS | 8 +
> arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts | 67 ++
> arch/riscv/boot/dts/thead/th1520.dtsi | 7 +
> drivers/pwm/Kconfig | 24 +
> drivers/pwm/Makefile | 1 +
> drivers/pwm/core.c | 2 +-
> drivers/pwm/pwm_th1520.rs | 352 +++++++++
> include/linux/pwm.h | 5 +
> rust/bindings/bindings_helper.h | 1 +
> rust/helpers/helpers.c | 1 +
> rust/helpers/pwm.c | 20 +
> rust/kernel/lib.rs | 2 +
> rust/kernel/pwm.rs | 800 +++++++++++++++++++++
> 14 files changed, 1337 insertions(+), 1 deletion(-)
> ---
> base-commit: 47753b5a1696283930a78aae79b29371f96f5bca
> change-id: 20250524-rust-next-pwm-working-fan-for-sending-552ad2d1b193
>
> Best regards,
Hi Uwe, Danilo,
Many thanks for your reviews and your patience.
I was hoping you could clarify the intended merge path for this series,
as it introduces changes to both the Rust and PWM subsystems.
Is the expectation that the Rust maintainers will take the abstraction
patches into the Rust tree first? Or would Uwe, as the PWM maintainer,
pull the entire series? Any guidance on the coordination would be very
helpful.
I understand that it may be too late in the development cycle to merge
the full series. If that's the case, perhaps patch 2 could be considered
on its own, as it hasn't received comments in the last couple of
revisions. As another possibility, patch 1 and patch 3 are dependent on
each other and could be applied as a pair, depending on your assessment.
The RISC-V driver itself would need to wait for the IoMem series merge [1].
[1] - https://lore.kernel.org/rust-for-linux/20250704-topics-tyr-platform_iomem-v12-0-1d3d4bd8207d@collabora.com/
Best regards,
--
Michal Wilczynski <m.wilczynski@samsung.com>
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver
2025-07-10 8:42 ` [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver Michal Wilczynski
@ 2025-07-10 10:17 ` Danilo Krummrich
2025-07-10 10:29 ` Michal Wilczynski
2025-07-10 13:10 ` Uwe Kleine-König
1 sibling, 1 reply; 23+ messages in thread
From: Danilo Krummrich @ 2025-07-10 10:17 UTC (permalink / raw)
To: Michal Wilczynski
Cc: Uwe Kleine-König, Miguel Ojeda, Alex Gaynor, Boqun Feng,
Gary Guo, Björn Roy Baron, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Guo Ren, Fu Wei, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Marek Szyprowski, Benno Lossin,
Michael Turquette, Drew Fustini, linux-kernel, linux-pwm,
rust-for-linux, linux-riscv, devicetree, Krzysztof Kozlowski
On Thu Jul 10, 2025 at 10:42 AM CEST, Michal Wilczynski wrote:
> I was hoping you could clarify the intended merge path for this series,
> as it introduces changes to both the Rust and PWM subsystems.
>
> Is the expectation that the Rust maintainers will take the abstraction
> patches into the Rust tree first? Or would Uwe, as the PWM maintainer,
> pull the entire series? Any guidance on the coordination would be very
> helpful.
Except for the helpers I only see PWM code, so this is fully on Uwe's purview I
think.
I see that there is a new MAINTAINERS entry:
PWM SUBSYSTEM BINDINGS [RUST]
M: Michal Wilczynski <m.wilczynski@samsung.com>
S: Maintained
F: rust/helpers/pwm.c
F: rust/kernel/pwm.rs
I assume this is agreed with Uwe?
In case there's no agreement yet, the typical options are:
1) Maintain the Rust abstractions as part of the existing MAINTAINERS entry.
Optionally, the author can be added as another maintainer or reviewer.
2) Add a separate MAINTAINERS entry; patches / PRs still go through the same
subsystem tree.
3) Add a separate MAINTAINERS entry; patches don't go through the subsystem
tree (e.g. because the subsystem maintainers don't want to deal with it).
I don't recommend (3), since it's really just a fallback.
The above looks like (2). In this case I recommend to also add the C maintainers
as reviewers, such that they can easily follow along and specifiy the tree (T:).
But, of course, that's up to you and Uwe.
> I understand that it may be too late in the development cycle to merge
> the full series. If that's the case, perhaps patch 2 could be considered
> on its own, as it hasn't received comments in the last couple of
> revisions. As another possibility, patch 1 and patch 3 are dependent on
> each other and could be applied as a pair, depending on your assessment.
>
> The RISC-V driver itself would need to wait for the IoMem series merge [1].
>
> [1] - https://lore.kernel.org/rust-for-linux/20250704-topics-tyr-platform_iomem-v12-0-1d3d4bd8207d@collabora.com/
>
> Best regards,
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver
2025-07-10 10:17 ` Danilo Krummrich
@ 2025-07-10 10:29 ` Michal Wilczynski
2025-07-10 13:36 ` Uwe Kleine-König
0 siblings, 1 reply; 23+ messages in thread
From: Michal Wilczynski @ 2025-07-10 10:29 UTC (permalink / raw)
To: Danilo Krummrich, Uwe Kleine-König
Cc: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Guo Ren, Fu Wei, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti,
Marek Szyprowski, Benno Lossin, Michael Turquette, Drew Fustini,
linux-kernel, linux-pwm, rust-for-linux, linux-riscv, devicetree,
Krzysztof Kozlowski
On 7/10/25 12:17, Danilo Krummrich wrote:
> On Thu Jul 10, 2025 at 10:42 AM CEST, Michal Wilczynski wrote:
>> I was hoping you could clarify the intended merge path for this series,
>> as it introduces changes to both the Rust and PWM subsystems.
>>
>> Is the expectation that the Rust maintainers will take the abstraction
>> patches into the Rust tree first? Or would Uwe, as the PWM maintainer,
>> pull the entire series? Any guidance on the coordination would be very
>> helpful.
>
> Except for the helpers I only see PWM code, so this is fully on Uwe's purview I
> think.
>
> I see that there is a new MAINTAINERS entry:
>
> PWM SUBSYSTEM BINDINGS [RUST]
> M: Michal Wilczynski <m.wilczynski@samsung.com>
> S: Maintained
> F: rust/helpers/pwm.c
> F: rust/kernel/pwm.rs
>
> I assume this is agreed with Uwe?
>
> In case there's no agreement yet, the typical options are:
>
> 1) Maintain the Rust abstractions as part of the existing MAINTAINERS entry.
> Optionally, the author can be added as another maintainer or reviewer.
>
> 2) Add a separate MAINTAINERS entry; patches / PRs still go through the same
> subsystem tree.
>
> 3) Add a separate MAINTAINERS entry; patches don't go through the subsystem
> tree (e.g. because the subsystem maintainers don't want to deal with it).
>
> I don't recommend (3), since it's really just a fallback.
>
> The above looks like (2). In this case I recommend to also add the C maintainers
> as reviewers, such that they can easily follow along and specifiy the tree (T:).
>
> But, of course, that's up to you and Uwe.
Thanks, it is not agreed yet, I've included a MAINTAINERS entry, since I
would like to help with the maintenance of the code, so I would also
vote for the 2) option, but ultimately it's an Uwe decision so I would
be happy to follow on anything he decides.
>
>> I understand that it may be too late in the development cycle to merge
>> the full series. If that's the case, perhaps patch 2 could be considered
>> on its own, as it hasn't received comments in the last couple of
>> revisions. As another possibility, patch 1 and patch 3 are dependent on
>> each other and could be applied as a pair, depending on your assessment.
>>
>> The RISC-V driver itself would need to wait for the IoMem series merge [1].
>>
>> [1] - https://lore.kernel.org/rust-for-linux/20250704-topics-tyr-platform_iomem-v12-0-1d3d4bd8207d@collabora.com/
>>
>> Best regards,
>
>
Best regards,
--
Michal Wilczynski <m.wilczynski@samsung.com>
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver
2025-07-10 8:42 ` [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver Michal Wilczynski
2025-07-10 10:17 ` Danilo Krummrich
@ 2025-07-10 13:10 ` Uwe Kleine-König
2025-07-10 13:48 ` Michal Wilczynski
1 sibling, 1 reply; 23+ messages in thread
From: Uwe Kleine-König @ 2025-07-10 13:10 UTC (permalink / raw)
To: Michal Wilczynski
Cc: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Guo Ren, Fu Wei, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Marek Szyprowski, Benno Lossin,
Michael Turquette, Drew Fustini, linux-kernel, linux-pwm,
rust-for-linux, linux-riscv, devicetree, Krzysztof Kozlowski
[-- Attachment #1: Type: text/plain, Size: 2610 bytes --]
Hello,
On Thu, Jul 10, 2025 at 10:42:07AM +0200, Michal Wilczynski wrote:
> On 7/7/25 11:48, Michal Wilczynski wrote:
> > This patch series introduces Rust support for the T-HEAD TH1520 PWM
> > controller and demonstrates its use for fan control on the Sipeed Lichee
> > Pi 4A board.
> >
> > The primary goal of this patch series is to introduce a basic set of
> > Rust abstractions for the Linux PWM subsystem. As a first user and
> > practical demonstration of these abstractions, the series also provides
> > a functional PWM driver for the T-HEAD TH1520 SoC. This allows control
> > of its PWM channels and ultimately enables temperature controlled fan
> > support for the Lichee Pi 4A board. This work aims to explore the use of
> > Rust for PWM drivers and lay a foundation for potential future
> > Rust based PWM drivers.
> >
> > The core of this series is a new rust/kernel/pwm.rs module that provides
> > abstractions for writing PWM chip provider drivers in Rust. This has
> > been significantly reworked from v1 based on extensive feedback. The key
> > features of the new abstraction layer include:
> >
> > - Ownership and Lifetime Management: The pwm::Chip wrapper is managed
> > by ARef, correctly tying its lifetime to its embedded struct device
> > reference counter. Chip registration is handled by a pwm::Registration
> > RAII guard, which guarantees that pwmchip_add is always paired with
> > pwmchip_remove, preventing resource leaks.
> >
> > - Modern and Safe API: The PwmOps trait is now based on the modern
> > waveform API (round_waveform_tohw, write_waveform, etc.) as recommended
> > by the subsystem maintainer. It is generic over a driver's
> > hardware specific data structure, moving all unsafe serialization logic
> > into the abstraction layer and allowing drivers to be written in 100%
> > safe Rust.
> >
> > - Ergonomics: The API provides safe, idiomatic wrappers for other PWM
> > types (State, Args, Device, etc.) and uses standard kernel error
> > handling patterns.
> >
> > The series is structured as follows:
> > - Expose static function pwmchip_release.
Is this really necessary? I didn't try to understand the requirements
yet, but I wonder about that. If you get the pwmchip from
__pwmchip_add() the right thing to do to release it is to call
pwmchip_remove(). Feels like a layer violation.
> [...]
> > ---
> > base-commit: 47753b5a1696283930a78aae79b29371f96f5bca
I have problems applying this series and don't have this base commit in
my repo.
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver
2025-07-10 10:29 ` Michal Wilczynski
@ 2025-07-10 13:36 ` Uwe Kleine-König
2025-07-10 13:41 ` Danilo Krummrich
0 siblings, 1 reply; 23+ messages in thread
From: Uwe Kleine-König @ 2025-07-10 13:36 UTC (permalink / raw)
To: Michal Wilczynski
Cc: Danilo Krummrich, Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Guo Ren, Fu Wei, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti,
Marek Szyprowski, Benno Lossin, Michael Turquette, Drew Fustini,
linux-kernel, linux-pwm, rust-for-linux, linux-riscv, devicetree,
Krzysztof Kozlowski
[-- Attachment #1: Type: text/plain, Size: 1214 bytes --]
Hello,
On Thu, Jul 10, 2025 at 12:29:59PM +0200, Michal Wilczynski wrote:
> On 7/10/25 12:17, Danilo Krummrich wrote:
> > On Thu Jul 10, 2025 at 10:42 AM CEST, Michal Wilczynski wrote:
> >> I was hoping you could clarify the intended merge path for this series,
> >> as it introduces changes to both the Rust and PWM subsystems.
> >>
> >> Is the expectation that the Rust maintainers will take the abstraction
> >> patches into the Rust tree first? Or would Uwe, as the PWM maintainer,
> >> pull the entire series? Any guidance on the coordination would be very
> >> helpful.
> >
> > Except for the helpers I only see PWM code, so this is fully on Uwe's purview I
> > think.
> >
> > I see that there is a new MAINTAINERS entry:
> >
> > PWM SUBSYSTEM BINDINGS [RUST]
> > M: Michal Wilczynski <m.wilczynski@samsung.com>
> > S: Maintained
> > F: rust/helpers/pwm.c
> > F: rust/kernel/pwm.rs
> >
> > I assume this is agreed with Uwe?
I suggest to add
L: linux-pwm@vger.kernel.org
then I'm happy with it. I'm definitively not capable to review the Rust
details of Rust code. But I think Rust is readable enough that I can
judge the algorithmic parts.
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver
2025-07-10 13:36 ` Uwe Kleine-König
@ 2025-07-10 13:41 ` Danilo Krummrich
0 siblings, 0 replies; 23+ messages in thread
From: Danilo Krummrich @ 2025-07-10 13:41 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Michal Wilczynski, Miguel Ojeda, Alex Gaynor, Boqun Feng,
Gary Guo, Björn Roy Baron, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Guo Ren, Fu Wei, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Marek Szyprowski, Benno Lossin,
Michael Turquette, Drew Fustini, linux-kernel, linux-pwm,
rust-for-linux, linux-riscv, devicetree, Krzysztof Kozlowski
On Thu Jul 10, 2025 at 3:36 PM CEST, Uwe Kleine-König wrote:
> On Thu, Jul 10, 2025 at 12:29:59PM +0200, Michal Wilczynski wrote:
>> On 7/10/25 12:17, Danilo Krummrich wrote:
>> > I see that there is a new MAINTAINERS entry:
>> >
>> > PWM SUBSYSTEM BINDINGS [RUST]
>> > M: Michal Wilczynski <m.wilczynski@samsung.com>
>> > S: Maintained
>> > F: rust/helpers/pwm.c
>> > F: rust/kernel/pwm.rs
>> >
>> > I assume this is agreed with Uwe?
>
> I suggest to add
>
> L: linux-pwm@vger.kernel.org
>
> then I'm happy with it. I'm definitively not capable to review the Rust
> details of Rust code. But I think Rust is readable enough that I can
> judge the algorithmic parts.
What about the merge strategy? Do you want to pick up patches yourself (through
your tree), receive PRs from Michal or none of those?
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver
2025-07-10 13:10 ` Uwe Kleine-König
@ 2025-07-10 13:48 ` Michal Wilczynski
2025-07-10 15:25 ` Uwe Kleine-König
0 siblings, 1 reply; 23+ messages in thread
From: Michal Wilczynski @ 2025-07-10 13:48 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Guo Ren, Fu Wei, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Marek Szyprowski, Benno Lossin,
Michael Turquette, Drew Fustini, linux-kernel, linux-pwm,
rust-for-linux, linux-riscv, devicetree, Krzysztof Kozlowski
On 7/10/25 15:10, Uwe Kleine-König wrote:
> Hello,
>
> On Thu, Jul 10, 2025 at 10:42:07AM +0200, Michal Wilczynski wrote:
>> On 7/7/25 11:48, Michal Wilczynski wrote:
>>> This patch series introduces Rust support for the T-HEAD TH1520 PWM
>>> controller and demonstrates its use for fan control on the Sipeed Lichee
>>> Pi 4A board.
>>>
>>> The primary goal of this patch series is to introduce a basic set of
>>> Rust abstractions for the Linux PWM subsystem. As a first user and
>>> practical demonstration of these abstractions, the series also provides
>>> a functional PWM driver for the T-HEAD TH1520 SoC. This allows control
>>> of its PWM channels and ultimately enables temperature controlled fan
>>> support for the Lichee Pi 4A board. This work aims to explore the use of
>>> Rust for PWM drivers and lay a foundation for potential future
>>> Rust based PWM drivers.
>>>
>>> The core of this series is a new rust/kernel/pwm.rs module that provides
>>> abstractions for writing PWM chip provider drivers in Rust. This has
>>> been significantly reworked from v1 based on extensive feedback. The key
>>> features of the new abstraction layer include:
>>>
>>> - Ownership and Lifetime Management: The pwm::Chip wrapper is managed
>>> by ARef, correctly tying its lifetime to its embedded struct device
>>> reference counter. Chip registration is handled by a pwm::Registration
>>> RAII guard, which guarantees that pwmchip_add is always paired with
>>> pwmchip_remove, preventing resource leaks.
>>>
>>> - Modern and Safe API: The PwmOps trait is now based on the modern
>>> waveform API (round_waveform_tohw, write_waveform, etc.) as recommended
>>> by the subsystem maintainer. It is generic over a driver's
>>> hardware specific data structure, moving all unsafe serialization logic
>>> into the abstraction layer and allowing drivers to be written in 100%
>>> safe Rust.
>>>
>>> - Ergonomics: The API provides safe, idiomatic wrappers for other PWM
>>> types (State, Args, Device, etc.) and uses standard kernel error
>>> handling patterns.
>>>
>>> The series is structured as follows:
>>> - Expose static function pwmchip_release.
>
> Is this really necessary? I didn't try to understand the requirements
> yet, but I wonder about that. If you get the pwmchip from
> __pwmchip_add() the right thing to do to release it is to call
> pwmchip_remove(). Feels like a layer violation.
Hi Uwe,
Thank you for the feedback.
It's required to prevent a memory leak in a specific, critical failure
scenario. The sequence of events is as follows:
pwm::Chip::new() succeeds, allocating both the C struct pwm_chip and
the Rust drvdata.
pwm::Registration::register() (which calls pwmchip_add()) fails for
some reason.
The ARef<Chip> returned by new() is dropped, its reference count
goes to zero, and our custom release_callback is called.
In this state:
Calling pwmchip_remove() would be incorrect because the chip was never
successfully added. If our callback only frees the Rust drvdata, the
memory for the C struct pwm_chip is leaked.
Therefore, our custom release_callback must perform both cleanup tasks:
freeing the Rust drvdata and then calling pwmchip_release to free the C
struct. This "chaining" of the release handlers seems to be the most
robust way to guarantee cleanup in all scenarios.
Note that the bindings still use pwmchip_remove function in 'normal'
failure path.
>
>> [...]
>>> ---
>>> base-commit: 47753b5a1696283930a78aae79b29371f96f5bca
>
> I have problems applying this series and don't have this base commit in
> my repo.
Sorry for the confusion. Base commit doesn't exist in the mainline
kernel or linux-next, cause I've added some dependecies for compilation,
like IoMem for the driver (uploaded full branch on github [1]). The
bindings however doesn't depend on anything that's not in linux-next.
Anyway series applies cleanly on linux-next/master like so:
$ git fetch linux-next
$ git checkout linux-next/master
$ b4 shazam https://lore.kernel.org/all/20250707-rust-next-pwm-working-fan-for-sending-v10-0-d0c5cf342004@samsung.com/
Grabbing thread from lore.kernel.org/all/20250707-rust-next-pwm-working-fan-for-sending-v10-0-d0c5cf342004@samsung.com/t.mbox.gz
Checking for newer revisions
Grabbing search results from lore.kernel.org
Analyzing 14 messages in the thread
Looking for additional code-review trailers on lore.kernel.org
Analyzing 165 code-review messages
Checking attestation on all messages, may take a moment...
---
✓ [PATCH v10 1/7] pwm: Export `pwmchip_release` for external use
✓ [PATCH v10 2/7] rust: pwm: Add Kconfig and basic data structures
✓ [PATCH v10 3/7] rust: pwm: Add complete abstraction layer
✓ [PATCH v10 4/7] pwm: Add Rust driver for T-HEAD TH1520 SoC
✓ [PATCH v10 5/7] dt-bindings: pwm: thead: Add T-HEAD TH1520 PWM controller
✓ [PATCH v10 6/7] riscv: dts: thead: Add PWM controller node
✓ [PATCH v10 7/7] riscv: dts: thead: Add PWM fan and thermal control
---
✓ Signed: DKIM/samsung.com
---
Total patches: 7
---
Base: using specified base-commit 47753b5a1696283930a78aae79b29371f96f5bca
Applying: pwm: Export `pwmchip_release` for external use
Applying: rust: pwm: Add Kconfig and basic data structures
Applying: rust: pwm: Add complete abstraction layer
Applying: pwm: Add Rust driver for T-HEAD TH1520 SoC
Applying: dt-bindings: pwm: thead: Add T-HEAD TH1520 PWM controller
Applying: riscv: dts: thead: Add PWM controller node
Applying: riscv: dts: thead: Add PWM fan and thermal control
>
> Best regards
> Uwe
[1] - https://github.com/mwilczy/linux/commits/rust-next-pwm-working-fan-for-sending-v15/
Best regards,
--
Michal Wilczynski <m.wilczynski@samsung.com>
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver
2025-07-10 13:48 ` Michal Wilczynski
@ 2025-07-10 15:25 ` Uwe Kleine-König
2025-07-10 16:06 ` Danilo Krummrich
2025-07-10 16:58 ` Michal Wilczynski
0 siblings, 2 replies; 23+ messages in thread
From: Uwe Kleine-König @ 2025-07-10 15:25 UTC (permalink / raw)
To: Michal Wilczynski
Cc: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Guo Ren, Fu Wei, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Marek Szyprowski, Benno Lossin,
Michael Turquette, Drew Fustini, linux-kernel, linux-pwm,
rust-for-linux, linux-riscv, devicetree, Krzysztof Kozlowski
[-- Attachment #1: Type: text/plain, Size: 1893 bytes --]
Hello Michal,
On Thu, Jul 10, 2025 at 03:48:08PM +0200, Michal Wilczynski wrote:
> On 7/10/25 15:10, Uwe Kleine-König wrote:
> > On Thu, Jul 10, 2025 at 10:42:07AM +0200, Michal Wilczynski wrote:
> >> On 7/7/25 11:48, Michal Wilczynski wrote:
> >>> The series is structured as follows:
> >>> - Expose static function pwmchip_release.
> >
> > Is this really necessary? I didn't try to understand the requirements
> > yet, but I wonder about that. If you get the pwmchip from
> > __pwmchip_add() the right thing to do to release it is to call
> > pwmchip_remove(). Feels like a layer violation.
>
> It's required to prevent a memory leak in a specific, critical failure
> scenario. The sequence of events is as follows:
>
> pwm::Chip::new() succeeds, allocating both the C struct pwm_chip and
> the Rust drvdata.
>
> pwm::Registration::register() (which calls pwmchip_add()) fails for
> some reason.
If you called pwmchip_alloc() but not yet pwmchip_add(), the right
function to call for cleanup is pwmchip_put().
> The ARef<Chip> returned by new() is dropped, its reference count
> goes to zero, and our custom release_callback is called.
>
> [...]
> >>> ---
> >>> base-commit: 47753b5a1696283930a78aae79b29371f96f5bca
> >
> > I have problems applying this series and don't have this base commit in
> > my repo.
>
> Sorry for the confusion. Base commit doesn't exist in the mainline
> kernel or linux-next, cause I've added some dependecies for compilation,
> like IoMem for the driver (uploaded full branch on github [1]). The
> bindings however doesn't depend on anything that's not in linux-next.
The series didn't apply to my pwm/for-next branch.
Note that the base-commit should always be a publically known commit.
See the chapter about "Base Tree Information" in git-format-patch(1).
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver
2025-07-10 15:25 ` Uwe Kleine-König
@ 2025-07-10 16:06 ` Danilo Krummrich
2025-07-10 20:57 ` Uwe Kleine-König
2025-07-10 16:58 ` Michal Wilczynski
1 sibling, 1 reply; 23+ messages in thread
From: Danilo Krummrich @ 2025-07-10 16:06 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Michal Wilczynski, Miguel Ojeda, Alex Gaynor, Boqun Feng,
Gary Guo, Björn Roy Baron, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Guo Ren, Fu Wei, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Marek Szyprowski, Benno Lossin,
Michael Turquette, Drew Fustini, linux-kernel, linux-pwm,
rust-for-linux, linux-riscv, devicetree, Krzysztof Kozlowski
On Thu Jul 10, 2025 at 5:25 PM CEST, Uwe Kleine-König wrote:
> Hello Michal,
>
> On Thu, Jul 10, 2025 at 03:48:08PM +0200, Michal Wilczynski wrote:
>> On 7/10/25 15:10, Uwe Kleine-König wrote:
>> > On Thu, Jul 10, 2025 at 10:42:07AM +0200, Michal Wilczynski wrote:
>> >> On 7/7/25 11:48, Michal Wilczynski wrote:
>> >>> The series is structured as follows:
>> >>> - Expose static function pwmchip_release.
>> >
>> > Is this really necessary? I didn't try to understand the requirements
>> > yet, but I wonder about that. If you get the pwmchip from
>> > __pwmchip_add() the right thing to do to release it is to call
>> > pwmchip_remove(). Feels like a layer violation.
>>
>> It's required to prevent a memory leak in a specific, critical failure
>> scenario. The sequence of events is as follows:
>>
>> pwm::Chip::new() succeeds, allocating both the C struct pwm_chip and
>> the Rust drvdata.
>>
>> pwm::Registration::register() (which calls pwmchip_add()) fails for
>> some reason.
>
(Just trying to help clear up the confusion.)
> If you called pwmchip_alloc() but not yet pwmchip_add(), the right
> function to call for cleanup is pwmchip_put().
That is exactly what is happening when ARef<Chip> is dropped. If the reference
count drops to zero, pwmchip_release() is called, which frees the chip. However,
this would leave the driver's private data allocation behind, which is owned by
the Chip instance.
So, in Rust we not only have to free the chip itself on release, but also the
driver's private data. The solution Michal went for is overwriting the PWM
chip's dev->release() with a callback that drops the driver's private data and
subsequently calls the "original" pwmchip_release().
This is a common pattern in Rust that we use in DRM as well. One thing that is
different in DRM is, that a struct drm_device (equivalent of struct pwm_chip in
this case), has it's own release callback for drivers that we can attach to.
PWM does not have such a callback AFAICS, hence the Rust abstraction uses the
underlying device's release callback and then forwards to pwmchip_release().
Hope this helps. :)
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver
2025-07-10 15:25 ` Uwe Kleine-König
2025-07-10 16:06 ` Danilo Krummrich
@ 2025-07-10 16:58 ` Michal Wilczynski
2025-07-10 20:39 ` Uwe Kleine-König
1 sibling, 1 reply; 23+ messages in thread
From: Michal Wilczynski @ 2025-07-10 16:58 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Guo Ren, Fu Wei, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Marek Szyprowski, Benno Lossin,
Michael Turquette, Drew Fustini, linux-kernel, linux-pwm,
rust-for-linux, linux-riscv, devicetree, Krzysztof Kozlowski
On 7/10/25 17:25, Uwe Kleine-König wrote:
> Hello Michal,
>
> On Thu, Jul 10, 2025 at 03:48:08PM +0200, Michal Wilczynski wrote:
>> On 7/10/25 15:10, Uwe Kleine-König wrote:
>>> On Thu, Jul 10, 2025 at 10:42:07AM +0200, Michal Wilczynski wrote:
>>>> On 7/7/25 11:48, Michal Wilczynski wrote:
>>>>> The series is structured as follows:
>>>>> - Expose static function pwmchip_release.
>>>
>>> Is this really necessary? I didn't try to understand the requirements
>>> yet, but I wonder about that. If you get the pwmchip from
>>> __pwmchip_add() the right thing to do to release it is to call
>>> pwmchip_remove(). Feels like a layer violation.
>>
>> It's required to prevent a memory leak in a specific, critical failure
>> scenario. The sequence of events is as follows:
>>
>> pwm::Chip::new() succeeds, allocating both the C struct pwm_chip and
>> the Rust drvdata.
>>
>> pwm::Registration::register() (which calls pwmchip_add()) fails for
>> some reason.
>
> If you called pwmchip_alloc() but not yet pwmchip_add(), the right
> function to call for cleanup is pwmchip_put().
>
>> The ARef<Chip> returned by new() is dropped, its reference count
>> goes to zero, and our custom release_callback is called.
>>
>> [...]
>>>>> ---
>>>>> base-commit: 47753b5a1696283930a78aae79b29371f96f5bca
>>>
>>> I have problems applying this series and don't have this base commit in
>>> my repo.
>>
>> Sorry for the confusion. Base commit doesn't exist in the mainline
>> kernel or linux-next, cause I've added some dependecies for compilation,
>> like IoMem for the driver (uploaded full branch on github [1]). The
>> bindings however doesn't depend on anything that's not in linux-next.
>
> The series didn't apply to my pwm/for-next branch.
>
> Note that the base-commit should always be a publically known commit.
> See the chapter about "Base Tree Information" in git-format-patch(1).
Hello Uwe,
Okay, thank you for the clarification. I understand the requirement for
a public base commit.
My intention was to include the TH1520 driver primarily as a practical
demonstration of the new abstractions. However the driver can't be
merged as is, since it depends on the unmerged IoMem series and won't
compile against a public commit.
I will rebase the series on pwm/for-next and drop the driver and its
associated device tree patches for now. I'll send a new version
containing just the core PWM abstraction patches, which apply cleanly.
I will resubmit the driver patches once their dependencies are available
in a public tree.
>
> Best regards
> Uwe
Best regards,
--
Michal Wilczynski <m.wilczynski@samsung.com>
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver
2025-07-10 16:58 ` Michal Wilczynski
@ 2025-07-10 20:39 ` Uwe Kleine-König
2025-07-11 15:19 ` Michal Wilczynski
0 siblings, 1 reply; 23+ messages in thread
From: Uwe Kleine-König @ 2025-07-10 20:39 UTC (permalink / raw)
To: Michal Wilczynski
Cc: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Guo Ren, Fu Wei, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Marek Szyprowski, Benno Lossin,
Michael Turquette, Drew Fustini, linux-kernel, linux-pwm,
rust-for-linux, linux-riscv, devicetree, Krzysztof Kozlowski
[-- Attachment #1: Type: text/plain, Size: 3294 bytes --]
Hello,
On Thu, Jul 10, 2025 at 06:58:41PM +0200, Michal Wilczynski wrote:
> On 7/10/25 17:25, Uwe Kleine-König wrote:
> > On Thu, Jul 10, 2025 at 03:48:08PM +0200, Michal Wilczynski wrote:
> >> On 7/10/25 15:10, Uwe Kleine-König wrote:
> >>> On Thu, Jul 10, 2025 at 10:42:07AM +0200, Michal Wilczynski wrote:
> >>>> On 7/7/25 11:48, Michal Wilczynski wrote:
> >>>>> The series is structured as follows:
> >>>>> - Expose static function pwmchip_release.
> >>>
> >>> Is this really necessary? I didn't try to understand the requirements
> >>> yet, but I wonder about that. If you get the pwmchip from
> >>> __pwmchip_add() the right thing to do to release it is to call
> >>> pwmchip_remove(). Feels like a layer violation.
> >>
> >> It's required to prevent a memory leak in a specific, critical failure
> >> scenario. The sequence of events is as follows:
> >>
> >> pwm::Chip::new() succeeds, allocating both the C struct pwm_chip and
> >> the Rust drvdata.
> >>
> >> pwm::Registration::register() (which calls pwmchip_add()) fails for
> >> some reason.
> >
> > If you called pwmchip_alloc() but not yet pwmchip_add(), the right
> > function to call for cleanup is pwmchip_put().
> >
> >> The ARef<Chip> returned by new() is dropped, its reference count
> >> goes to zero, and our custom release_callback is called.
> >>
> >> [...]
> >>>>> ---
> >>>>> base-commit: 47753b5a1696283930a78aae79b29371f96f5bca
> >>>
> >>> I have problems applying this series and don't have this base commit in
> >>> my repo.
> >>
> >> Sorry for the confusion. Base commit doesn't exist in the mainline
> >> kernel or linux-next, cause I've added some dependecies for compilation,
> >> like IoMem for the driver (uploaded full branch on github [1]). The
> >> bindings however doesn't depend on anything that's not in linux-next.
> >
> > The series didn't apply to my pwm/for-next branch.
> >
> > Note that the base-commit should always be a publically known commit.
> > See the chapter about "Base Tree Information" in git-format-patch(1).
>
> Hello Uwe,
>
> Okay, thank you for the clarification. I understand the requirement for
> a public base commit.
>
> My intention was to include the TH1520 driver primarily as a practical
> demonstration of the new abstractions. However the driver can't be
> merged as is, since it depends on the unmerged IoMem series and won't
> compile against a public commit.
>
> I will rebase the series on pwm/for-next and drop the driver and its
> associated device tree patches for now. I'll send a new version
> containing just the core PWM abstraction patches, which apply cleanly.
>
> I will resubmit the driver patches once their dependencies are available
> in a public tree.
If you base your tree on (say) v6.16-rc1, then add some Rust
dependencies up to 47753b5a1696283930a78aae79b29371f96f5bca and then add
your series, you just do:
git format-patch --base v6.16-rc1 47753b5a1696283930a78aae79b29371f96f5bca..
. This results in a base-commit line that I (and maybe also build bots)
can use and a bunch of further lines listing the commits between
v6.16-rc1 and 47753b5a1696283930a78aae79b29371f96f5bca that might be
findable on lore.k.o.
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver
2025-07-10 16:06 ` Danilo Krummrich
@ 2025-07-10 20:57 ` Uwe Kleine-König
2025-07-10 21:19 ` Danilo Krummrich
0 siblings, 1 reply; 23+ messages in thread
From: Uwe Kleine-König @ 2025-07-10 20:57 UTC (permalink / raw)
To: Danilo Krummrich
Cc: Michal Wilczynski, Miguel Ojeda, Alex Gaynor, Boqun Feng,
Gary Guo, Björn Roy Baron, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Guo Ren, Fu Wei, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Marek Szyprowski, Benno Lossin,
Michael Turquette, Drew Fustini, linux-kernel, linux-pwm,
rust-for-linux, linux-riscv, devicetree, Krzysztof Kozlowski
[-- Attachment #1: Type: text/plain, Size: 2629 bytes --]
On Thu, Jul 10, 2025 at 06:06:26PM +0200, Danilo Krummrich wrote:
> On Thu Jul 10, 2025 at 5:25 PM CEST, Uwe Kleine-König wrote:
> > Hello Michal,
> >
> > On Thu, Jul 10, 2025 at 03:48:08PM +0200, Michal Wilczynski wrote:
> >> On 7/10/25 15:10, Uwe Kleine-König wrote:
> >> > On Thu, Jul 10, 2025 at 10:42:07AM +0200, Michal Wilczynski wrote:
> >> >> On 7/7/25 11:48, Michal Wilczynski wrote:
> >> >>> The series is structured as follows:
> >> >>> - Expose static function pwmchip_release.
> >> >
> >> > Is this really necessary? I didn't try to understand the requirements
> >> > yet, but I wonder about that. If you get the pwmchip from
> >> > __pwmchip_add() the right thing to do to release it is to call
> >> > pwmchip_remove(). Feels like a layer violation.
> >>
> >> It's required to prevent a memory leak in a specific, critical failure
> >> scenario. The sequence of events is as follows:
> >>
> >> pwm::Chip::new() succeeds, allocating both the C struct pwm_chip and
> >> the Rust drvdata.
> >>
> >> pwm::Registration::register() (which calls pwmchip_add()) fails for
> >> some reason.
> >
>
> (Just trying to help clear up the confusion.)
Very appreciated!
> > If you called pwmchip_alloc() but not yet pwmchip_add(), the right
> > function to call for cleanup is pwmchip_put().
>
> That is exactly what is happening when ARef<Chip> is dropped. If the reference
> count drops to zero, pwmchip_release() is called, which frees the chip. However,
> this would leave the driver's private data allocation behind, which is owned by
> the Chip instance.
I don't understand that. The chip and the driver private data both are
located in the same allocation. How is this a problem of the driver
private data only then? The kfree() in pwmchip_release() is good enough
for both?!
> So, in Rust we not only have to free the chip itself on release, but also the
> driver's private data. The solution Michal went for is overwriting the PWM
> chip's dev->release() with a callback that drops the driver's private data and
> subsequently calls the "original" pwmchip_release().
>
> This is a common pattern in Rust that we use in DRM as well. One thing that is
> different in DRM is, that a struct drm_device (equivalent of struct pwm_chip in
> this case), has it's own release callback for drivers that we can attach to.
>
> PWM does not have such a callback AFAICS, hence the Rust abstraction uses the
> underlying device's release callback and then forwards to pwmchip_release().
>
> Hope this helps. :)
Not yet ... :-)
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver
2025-07-10 20:57 ` Uwe Kleine-König
@ 2025-07-10 21:19 ` Danilo Krummrich
2025-07-11 12:36 ` Michal Wilczynski
0 siblings, 1 reply; 23+ messages in thread
From: Danilo Krummrich @ 2025-07-10 21:19 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Michal Wilczynski, Miguel Ojeda, Alex Gaynor, Boqun Feng,
Gary Guo, Björn Roy Baron, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Guo Ren, Fu Wei, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Marek Szyprowski, Benno Lossin,
Michael Turquette, Drew Fustini, linux-kernel, linux-pwm,
rust-for-linux, linux-riscv, devicetree, Krzysztof Kozlowski
On Thu Jul 10, 2025 at 10:57 PM CEST, Uwe Kleine-König wrote:
> On Thu, Jul 10, 2025 at 06:06:26PM +0200, Danilo Krummrich wrote:
>> On Thu Jul 10, 2025 at 5:25 PM CEST, Uwe Kleine-König wrote:
>> > Hello Michal,
>> >
>> > On Thu, Jul 10, 2025 at 03:48:08PM +0200, Michal Wilczynski wrote:
>> >> On 7/10/25 15:10, Uwe Kleine-König wrote:
>> >> > On Thu, Jul 10, 2025 at 10:42:07AM +0200, Michal Wilczynski wrote:
>> >> >> On 7/7/25 11:48, Michal Wilczynski wrote:
>> >> >>> The series is structured as follows:
>> >> >>> - Expose static function pwmchip_release.
>> >> >
>> >> > Is this really necessary? I didn't try to understand the requirements
>> >> > yet, but I wonder about that. If you get the pwmchip from
>> >> > __pwmchip_add() the right thing to do to release it is to call
>> >> > pwmchip_remove(). Feels like a layer violation.
>> >>
>> >> It's required to prevent a memory leak in a specific, critical failure
>> >> scenario. The sequence of events is as follows:
>> >>
>> >> pwm::Chip::new() succeeds, allocating both the C struct pwm_chip and
>> >> the Rust drvdata.
>> >>
>> >> pwm::Registration::register() (which calls pwmchip_add()) fails for
>> >> some reason.
>> >
>>
>> (Just trying to help clear up the confusion.)
>
> Very appreciated!
>
>> > If you called pwmchip_alloc() but not yet pwmchip_add(), the right
>> > function to call for cleanup is pwmchip_put().
>>
>> That is exactly what is happening when ARef<Chip> is dropped. If the reference
>> count drops to zero, pwmchip_release() is called, which frees the chip. However,
>> this would leave the driver's private data allocation behind, which is owned by
>> the Chip instance.
>
> I don't understand that. The chip and the driver private data both are
> located in the same allocation. How is this a problem of the driver
> private data only then? The kfree() in pwmchip_release() is good enough
> for both?!
Not in the current abstractions, there are two allocations, one for the Chip and
one for the driver's private data, or in other words the abstraction uses
pwmchip_set_drvdata() and pwmchip_get_drvdata().
Having a brief look at pwmchip_alloc(), it seems to me that PWM supports the
subclassing pattern with pwmchip_priv().
We should probably take advantage of that. Assuming we do that, the Rust
abstraction still needs a release() callback because we still need to call
drop_in_place() in order to get the destructor of the driver's private data
type called. We actually missed this in DRM and I fixed it up recently [1].
@Michal: With the subclassing pattern the Chip structure would look like this:
#[repr(C)]
#[pin_data]
pub struct Chip<T> {
inner: Opaque<bindings::pwm_chip>,
#[pin]
data: T,
}
And in the release() callback would look like this:
extern "C" fn release(ptr: *mut bindings::pwm_chip) {
// CAST: Casting `ptr` to `Chip<T>` is valid, since [...].
let this = ptr.cast<Chip<T>>();
// SAFETY:
// - When `release` runs it is guaranteed that there is no further access to `this`.
// - `this` is valid for dropping.
unsafe { core::ptr::drop_in_place(this) };
}
This is exactly what we're doing in DRM as well, I would have recommended this
to begin with, but I didn't recognize that PWM supports subclassing. :)
I recommend having a look at [2].
[1] https://lore.kernel.org/all/20250629153747.72536-1-dakr@kernel.org/
[2] https://gitlab.freedesktop.org/drm/misc/kernel/-/blob/drm-misc-fixes/rust/kernel/drm/device.rs
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver
2025-07-10 21:19 ` Danilo Krummrich
@ 2025-07-11 12:36 ` Michal Wilczynski
0 siblings, 0 replies; 23+ messages in thread
From: Michal Wilczynski @ 2025-07-11 12:36 UTC (permalink / raw)
To: Danilo Krummrich, Uwe Kleine-König
Cc: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Guo Ren, Fu Wei, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti,
Marek Szyprowski, Benno Lossin, Michael Turquette, Drew Fustini,
linux-kernel, linux-pwm, rust-for-linux, linux-riscv, devicetree,
Krzysztof Kozlowski
On 7/10/25 23:19, Danilo Krummrich wrote:
> On Thu Jul 10, 2025 at 10:57 PM CEST, Uwe Kleine-König wrote:
>> On Thu, Jul 10, 2025 at 06:06:26PM +0200, Danilo Krummrich wrote:
>>> On Thu Jul 10, 2025 at 5:25 PM CEST, Uwe Kleine-König wrote:
>>>> Hello Michal,
>>>>
>>>> On Thu, Jul 10, 2025 at 03:48:08PM +0200, Michal Wilczynski wrote:
>>>>> On 7/10/25 15:10, Uwe Kleine-König wrote:
>>>>>> On Thu, Jul 10, 2025 at 10:42:07AM +0200, Michal Wilczynski wrote:
>>>>>>> On 7/7/25 11:48, Michal Wilczynski wrote:
>>>>>>>> The series is structured as follows:
>>>>>>>> - Expose static function pwmchip_release.
>>>>>>
>>>>>> Is this really necessary? I didn't try to understand the requirements
>>>>>> yet, but I wonder about that. If you get the pwmchip from
>>>>>> __pwmchip_add() the right thing to do to release it is to call
>>>>>> pwmchip_remove(). Feels like a layer violation.
>>>>>
>>>>> It's required to prevent a memory leak in a specific, critical failure
>>>>> scenario. The sequence of events is as follows:
>>>>>
>>>>> pwm::Chip::new() succeeds, allocating both the C struct pwm_chip and
>>>>> the Rust drvdata.
>>>>>
>>>>> pwm::Registration::register() (which calls pwmchip_add()) fails for
>>>>> some reason.
>>>>
>>>
>>> (Just trying to help clear up the confusion.)
>>
>> Very appreciated!
>>
>>>> If you called pwmchip_alloc() but not yet pwmchip_add(), the right
>>>> function to call for cleanup is pwmchip_put().
>>>
>>> That is exactly what is happening when ARef<Chip> is dropped. If the reference
>>> count drops to zero, pwmchip_release() is called, which frees the chip. However,
>>> this would leave the driver's private data allocation behind, which is owned by
>>> the Chip instance.
>>
>> I don't understand that. The chip and the driver private data both are
>> located in the same allocation. How is this a problem of the driver
>> private data only then? The kfree() in pwmchip_release() is good enough
>> for both?!
>
> Not in the current abstractions, there are two allocations, one for the Chip and
> one for the driver's private data, or in other words the abstraction uses
> pwmchip_set_drvdata() and pwmchip_get_drvdata().
>
> Having a brief look at pwmchip_alloc(), it seems to me that PWM supports the
> subclassing pattern with pwmchip_priv().
>
> We should probably take advantage of that. Assuming we do that, the Rust
> abstraction still needs a release() callback because we still need to call
> drop_in_place() in order to get the destructor of the driver's private data
> type called. We actually missed this in DRM and I fixed it up recently [1].
>
> @Michal: With the subclassing pattern the Chip structure would look like this:
>
> #[repr(C)]
> #[pin_data]
> pub struct Chip<T> {
> inner: Opaque<bindings::pwm_chip>,
> #[pin]
> data: T,
> }
}
Hello
Thank you both for the detailed feedback and suggestions.
Danilo, you are right, we should absolutely use the subclassing pattern
to have a single allocation for the chip and driver data. This is a much
cleaner design.
As I looked into this, the main difference is that the C struct pwm_chip
doesn't have a fixed size because of the pwms[] array at the end. This
prevents us from using the exact struct layout you suggested.
pub pwms: __IncompleteArrayField<pwm_device>,
Therefore, to correctly implement the subclassing pattern, it would be
sufficient to leave the current struct as is and use pwmchip_get_drvdata to
acquire pointers to the allocated drvdata.
pub struct Chip<T: PwmOps>(Opaque<bindings::pwm_chip>, PhantomData<T>);
This will still achieve the goal of a single allocation via
pwmchip_alloc's sizeof_priv argument, while working around the DST
limitation.
>
> And in the release() callback would look like this:
>
> extern "C" fn release(ptr: *mut bindings::pwm_chip) {
> // CAST: Casting `ptr` to `Chip<T>` is valid, since [...].
> let this = ptr.cast<Chip<T>>();
I think this would use pwmchip_get_drvdata instead.
>
> // SAFETY:
> // - When `release` runs it is guaranteed that there is no further access to `this`.
> // - `this` is valid for dropping.
> unsafe { core::ptr::drop_in_place(this) };
> }
>
> This is exactly what we're doing in DRM as well, I would have recommended this
> to begin with, but I didn't recognize that PWM supports subclassing. :)
>
> I recommend having a look at [2].
>
> [1] https://lore.kernel.org/all/20250629153747.72536-1-dakr@kernel.org/
> [2] https://gitlab.freedesktop.org/drm/misc/kernel/-/blob/drm-misc-fixes/rust/kernel/drm/device.rs
>
Best regards,
--
Michal Wilczynski <m.wilczynski@samsung.com>
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver
2025-07-10 20:39 ` Uwe Kleine-König
@ 2025-07-11 15:19 ` Michal Wilczynski
0 siblings, 0 replies; 23+ messages in thread
From: Michal Wilczynski @ 2025-07-11 15:19 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Guo Ren, Fu Wei, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Marek Szyprowski, Benno Lossin,
Michael Turquette, Drew Fustini, linux-kernel, linux-pwm,
rust-for-linux, linux-riscv, devicetree, Krzysztof Kozlowski
On 7/10/25 22:39, Uwe Kleine-König wrote:
> Hello,
>
> On Thu, Jul 10, 2025 at 06:58:41PM +0200, Michal Wilczynski wrote:
>> On 7/10/25 17:25, Uwe Kleine-König wrote:
>>> On Thu, Jul 10, 2025 at 03:48:08PM +0200, Michal Wilczynski wrote:
>>>> On 7/10/25 15:10, Uwe Kleine-König wrote:
>>>>> On Thu, Jul 10, 2025 at 10:42:07AM +0200, Michal Wilczynski wrote:
>>>>>> On 7/7/25 11:48, Michal Wilczynski wrote:
>>>>>>> The series is structured as follows:
>>>>>>> - Expose static function pwmchip_release.
>>>>>
>>>>> Is this really necessary? I didn't try to understand the requirements
>>>>> yet, but I wonder about that. If you get the pwmchip from
>>>>> __pwmchip_add() the right thing to do to release it is to call
>>>>> pwmchip_remove(). Feels like a layer violation.
>>>>
>>>> It's required to prevent a memory leak in a specific, critical failure
>>>> scenario. The sequence of events is as follows:
>>>>
>>>> pwm::Chip::new() succeeds, allocating both the C struct pwm_chip and
>>>> the Rust drvdata.
>>>>
>>>> pwm::Registration::register() (which calls pwmchip_add()) fails for
>>>> some reason.
>>>
>>> If you called pwmchip_alloc() but not yet pwmchip_add(), the right
>>> function to call for cleanup is pwmchip_put().
>>>
>>>> The ARef<Chip> returned by new() is dropped, its reference count
>>>> goes to zero, and our custom release_callback is called.
>>>>
>>>> [...]
>>>>>>> ---
>>>>>>> base-commit: 47753b5a1696283930a78aae79b29371f96f5bca
>>>>>
>>>>> I have problems applying this series and don't have this base commit in
>>>>> my repo.
>>>>
>>>> Sorry for the confusion. Base commit doesn't exist in the mainline
>>>> kernel or linux-next, cause I've added some dependecies for compilation,
>>>> like IoMem for the driver (uploaded full branch on github [1]). The
>>>> bindings however doesn't depend on anything that's not in linux-next.
>>>
>>> The series didn't apply to my pwm/for-next branch.
>>>
>>> Note that the base-commit should always be a publically known commit.
>>> See the chapter about "Base Tree Information" in git-format-patch(1).
>>
>> Hello Uwe,
>>
>> Okay, thank you for the clarification. I understand the requirement for
>> a public base commit.
>>
>> My intention was to include the TH1520 driver primarily as a practical
>> demonstration of the new abstractions. However the driver can't be
>> merged as is, since it depends on the unmerged IoMem series and won't
>> compile against a public commit.
>>
>> I will rebase the series on pwm/for-next and drop the driver and its
>> associated device tree patches for now. I'll send a new version
>> containing just the core PWM abstraction patches, which apply cleanly.
>>
>> I will resubmit the driver patches once their dependencies are available
>> in a public tree.
>
> If you base your tree on (say) v6.16-rc1, then add some Rust
> dependencies up to 47753b5a1696283930a78aae79b29371f96f5bca and then add
> your series, you just do:
>
> git format-patch --base v6.16-rc1 47753b5a1696283930a78aae79b29371f96f5bca..
>
> . This results in a base-commit line that I (and maybe also build bots)
> can use and a bunch of further lines listing the commits between
> v6.16-rc1 and 47753b5a1696283930a78aae79b29371f96f5bca that might be
> findable on lore.k.o.
Hi Uwe,
Thank you very much for the detailed advice on using git format-patch
--base. I appreciate you taking the time to explain the workflow.
I investigated this approach, and the difficulty is that the IoMem
series [1], which my driver depends on, is itself based on an
integration tree rather than a clean public tag.
This means that to create a series based on v6.16-rc1, I would have to
include a very large number of intermediate commits from linux-next,
which, would not be helpful for review.
Therefore, I believe that dropping the driver and its device tree
patches for now is the best path forward. This will result in a much
smaller, self contained series for the core PWM abstractions that
applies cleanly to your pwm/for-next branch.
[1] - https://lore.kernel.org/rust-for-linux/20250704-topics-tyr-platform_iomem-v12-0-1d3d4bd8207d@collabora.com/
>
> Best regards
> Uwe
Best regards,
--
Michal Wilczynski <m.wilczynski@samsung.com>
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2025-07-11 15:19 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <CGME20250707094926eucas1p155bd967b6986c4a999776839b1aa1fc6@eucas1p1.samsung.com>
2025-07-07 9:48 ` [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver Michal Wilczynski
[not found] ` <CGME20250707094927eucas1p2be0302112d89f0ea39e4eeb21c771234@eucas1p2.samsung.com>
2025-07-07 9:48 ` [PATCH v10 1/7] pwm: Export `pwmchip_release` for external use Michal Wilczynski
[not found] ` <CGME20250707094929eucas1p2282257ae8e5351df04b497a6c8664ab2@eucas1p2.samsung.com>
2025-07-07 9:48 ` [PATCH v10 2/7] rust: pwm: Add Kconfig and basic data structures Michal Wilczynski
[not found] ` <CGME20250707094930eucas1p1932276804e345087eb05588cbd65a024@eucas1p1.samsung.com>
2025-07-07 9:48 ` [PATCH v10 3/7] rust: pwm: Add complete abstraction layer Michal Wilczynski
[not found] ` <CGME20250707094932eucas1p13a9a26fd50a868c3212802a9bf8cb585@eucas1p1.samsung.com>
2025-07-07 9:48 ` [PATCH v10 4/7] pwm: Add Rust driver for T-HEAD TH1520 SoC Michal Wilczynski
[not found] ` <CGME20250707094933eucas1p13228bedac1f0787d158956932ed81fd9@eucas1p1.samsung.com>
2025-07-07 9:48 ` [PATCH v10 5/7] dt-bindings: pwm: thead: Add T-HEAD TH1520 PWM controller Michal Wilczynski
[not found] ` <CGME20250707094934eucas1p156403009dcb9df7e9466c3418aac738a@eucas1p1.samsung.com>
2025-07-07 9:48 ` [PATCH v10 6/7] riscv: dts: thead: Add PWM controller node Michal Wilczynski
[not found] ` <CGME20250707094935eucas1p1d9ee9b8dac94ac16c48ae3a084884622@eucas1p1.samsung.com>
2025-07-07 9:48 ` [PATCH v10 7/7] riscv: dts: thead: Add PWM fan and thermal control Michal Wilczynski
2025-07-10 8:42 ` [PATCH v10 0/7] Rust Abstractions for PWM subsystem with TH1520 PWM driver Michal Wilczynski
2025-07-10 10:17 ` Danilo Krummrich
2025-07-10 10:29 ` Michal Wilczynski
2025-07-10 13:36 ` Uwe Kleine-König
2025-07-10 13:41 ` Danilo Krummrich
2025-07-10 13:10 ` Uwe Kleine-König
2025-07-10 13:48 ` Michal Wilczynski
2025-07-10 15:25 ` Uwe Kleine-König
2025-07-10 16:06 ` Danilo Krummrich
2025-07-10 20:57 ` Uwe Kleine-König
2025-07-10 21:19 ` Danilo Krummrich
2025-07-11 12:36 ` Michal Wilczynski
2025-07-10 16:58 ` Michal Wilczynski
2025-07-10 20:39 ` Uwe Kleine-König
2025-07-11 15:19 ` Michal Wilczynski
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).