From nobody Sat Oct 4 05:01:30 2025 Received: from mailout2.w1.samsung.com (mailout2.w1.samsung.com [210.118.77.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 136BB22332E for ; Tue, 30 Sep 2025 12:27:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=210.118.77.12 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759235255; cv=none; b=JcqGVowXE3vxpqaGtBrVUvSCQawygBTD7dtfQwxbZplApwp+bQ9pJNspRCmeLDtlenV5L8ifZVYO9ttbouQiIaVTjc85rLl9q485narNiSCj96hkubVyuLiXDVlWbGfsyKsFVHJW4IDmzznxZtka5gDHd89j5wzaLe2dTcGcfak= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759235255; c=relaxed/simple; bh=OsXGZpjhn/bcvoR4nyicnkd/6UvKt5+0TaTkZnyK7gw=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=Ibi73VgA+TW/y6uS+wPHM3j0kKG2wZi+ZWKl1OL+k6CCgL5tJJ431u+TMZ6V+aIjlWLGg1Pdix8rsFQ0NYDS2XycM0hro7LAQNTRvi4bO/Izd+YbFucQDmdoRBhoTOpvV4fbvNuTLzDAfQ6pOF1O7XDdqJYeqnOQmGcKdt8wtwc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com; spf=pass smtp.mailfrom=samsung.com; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b=H0a4/G8R; arc=none smtp.client-ip=210.118.77.12 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=samsung.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b="H0a4/G8R" Received: from eucas1p1.samsung.com (unknown [182.198.249.206]) by mailout2.w1.samsung.com (KnoxPortal) with ESMTP id 20250930122731euoutp02147e0e1d036ad74c5ca937bb38a074e0~qDyPwSli62140121401euoutp028 for ; Tue, 30 Sep 2025 12:27:31 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout2.w1.samsung.com 20250930122731euoutp02147e0e1d036ad74c5ca937bb38a074e0~qDyPwSli62140121401euoutp028 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1759235251; bh=Zaf3CvVE7vDNtuforIU/AX8V1l2za6jRnyAi7V9DPZE=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=H0a4/G8R3TYJ0AuMPhinlZLL2iloUNBh4BJiKfvjzTC7u1U96akSK88N8nbvnh5Lx KwjVf0MN+ayfhJUcYvNdHbLffJUHD9jXCPfD0OiunkBUVBidc6e70/tcHvJR+1jiZm uQhPCuTcLLN27p/zQeQayk/BjjTjcdSoXjMrcUVk= Received: from eusmtip2.samsung.com (unknown [203.254.199.222]) by eucas1p1.samsung.com (KnoxPortal) with ESMTPA id 20250930122730eucas1p11afe23eee92daac1023a826768b1f92d~qDyPOjAV12445224452eucas1p1h; Tue, 30 Sep 2025 12:27:30 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip2.samsung.com (KnoxPortal) with ESMTPA id 20250930122729eusmtip2ae44be48f37c522de1ee16749443ad86~qDyOH45bz2176221762eusmtip2L; Tue, 30 Sep 2025 12:27:29 +0000 (GMT) From: Michal Wilczynski Date: Tue, 30 Sep 2025 14:20:32 +0200 Subject: [PATCH v15 1/7] pwm: Export `pwmchip_release` for external use Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Message-Id: <20250930-rust-next-pwm-working-fan-for-sending-v15-1-5661c3090877@samsung.com> In-Reply-To: <20250930-rust-next-pwm-working-fan-for-sending-v15-0-5661c3090877@samsung.com> To: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_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 , Daniel Almeida , Benno Lossin , Drew Fustini Cc: linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-riscv@lists.infradead.org, devicetree@vger.kernel.org, Elle Rhumsaa X-Mailer: b4 0.15-dev X-CMS-MailID: 20250930122730eucas1p11afe23eee92daac1023a826768b1f92d X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250930122730eucas1p11afe23eee92daac1023a826768b1f92d X-EPHeader: CA X-CMS-RootMailID: 20250930122730eucas1p11afe23eee92daac1023a826768b1f92d References: <20250930-rust-next-pwm-working-fan-for-sending-v15-0-5661c3090877@samsung.com> 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. Reviewed-by: Elle Rhumsaa Signed-off-by: Michal Wilczynski --- drivers/pwm/core.c | 3 ++- include/linux/pwm.h | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 0d66376a83ec350e0c3718959f4d794efd71595a..a33da3dff608fdff91251e5fd07= b0dbd295be022 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -1600,12 +1600,13 @@ void pwmchip_put(struct pwm_chip *chip) } EXPORT_SYMBOL_GPL(pwmchip_put); =20 -static void pwmchip_release(struct device *pwmchip_dev) +void pwmchip_release(struct device *pwmchip_dev) { struct pwm_chip *chip =3D pwmchip_from_dev(pwmchip_dev); =20 kfree(chip); } +EXPORT_SYMBOL_GPL(pwmchip_release); =20 struct pwm_chip *pwmchip_alloc(struct device *parent, unsigned int npwm, s= ize_t sizeof_priv) { diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 8cafc483db53addf95591d1ac74287532c0fa0ee..d86061024b52172edf3845bf925= 2a966f120e365 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -485,6 +485,12 @@ int __pwmchip_add(struct pwm_chip *chip, struct module= *owner); #define pwmchip_add(chip) __pwmchip_add(chip, THIS_MODULE) void pwmchip_remove(struct pwm_chip *chip); =20 +/* + * For FFI wrapper use only: + * The Rust PWM abstraction needs this to properly free the pwm_chip. + */ +void pwmchip_release(struct device *dev); + int __devm_pwmchip_add(struct device *dev, struct pwm_chip *chip, struct m= odule *owner); #define devm_pwmchip_add(dev, chip) __devm_pwmchip_add(dev, chip, THIS_MOD= ULE) =20 --=20 2.34.1 From nobody Sat Oct 4 05:01:30 2025 Received: from mailout1.w1.samsung.com (mailout1.w1.samsung.com [210.118.77.11]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0E0892FBDE4 for ; Tue, 30 Sep 2025 12:27:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=210.118.77.11 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759235263; cv=none; b=cepiDw2NmFO7F5ZyXUscr7IQTLR9nEV5Jm6ypo+/OGuXu2KQtY6vrIHfDedsAdZrNjG5wo5cVMTZGg8par6XrW4C8+ow7jNNIaMGoAsBspca5B4d6c9d4rLI1aSI4rSjfdHEIhP9tSbvYTALEvqSMf6//DC60CCID5hlnjtPxs0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759235263; c=relaxed/simple; bh=GhamRMgiRuEY8EYeFYRn0QQWl17r0gKB8us6iMNro3w=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=WzGudqlnGhLbdgz4akc062w85K/cntIlkhUpigqYTDVxIPmhrjdwggUi2JeAya3ZDbrP7eggDmi3vYgnLTCEy8CiJoJJwcWVrXpwgodbzmKfXLkfwGaWwiw+HVdzCr4tAL1IHZSn5/lIfaqmPSb+QpSPkOR5s09KNMS6rprtk6M= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com; spf=pass smtp.mailfrom=samsung.com; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b=Eu5z51NA; arc=none smtp.client-ip=210.118.77.11 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=samsung.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b="Eu5z51NA" Received: from eucas1p1.samsung.com (unknown [182.198.249.206]) by mailout1.w1.samsung.com (KnoxPortal) with ESMTP id 20250930122732euoutp01e01dec61d3f9d29d6138a47182f0781d~qDyQ_DzI-0459504595euoutp01S for ; Tue, 30 Sep 2025 12:27:32 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout1.w1.samsung.com 20250930122732euoutp01e01dec61d3f9d29d6138a47182f0781d~qDyQ_DzI-0459504595euoutp01S DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1759235252; bh=1YXxUdy2h6s4QZQQ5Cl3vowj7ed1PIKtu4444nNQC8g=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=Eu5z51NA7Yk+M1VBGwFFYmkydDctsBCFpi6uE0v/2tVcIkow7s7NMGOHxOxLfY8hT K3EfR/tRy5V9ejM6xuZ/mml9sO1tvt2dU2m8kbQLV1I6WEmrkMPbdz9RYoSeon2VAX QmaI2P8tMznOT+x86A00BMA19cGFLQHg9a14P9nw= Received: from eusmtip2.samsung.com (unknown [203.254.199.222]) by eucas1p2.samsung.com (KnoxPortal) with ESMTPA id 20250930122732eucas1p2fbf9c050934b356165377776a8aef247~qDyQdc00B2309923099eucas1p2w; Tue, 30 Sep 2025 12:27:32 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip2.samsung.com (KnoxPortal) with ESMTPA id 20250930122730eusmtip2fa430534fbeba49aa7a2a625f228c890~qDyPSuez52410724107eusmtip2r; Tue, 30 Sep 2025 12:27:30 +0000 (GMT) From: Michal Wilczynski Date: Tue, 30 Sep 2025 14:20:33 +0200 Subject: [PATCH v15 2/7] rust: pwm: Add Kconfig and basic data structures Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Message-Id: <20250930-rust-next-pwm-working-fan-for-sending-v15-2-5661c3090877@samsung.com> In-Reply-To: <20250930-rust-next-pwm-working-fan-for-sending-v15-0-5661c3090877@samsung.com> To: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_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 , Daniel Almeida , Benno Lossin , Drew Fustini Cc: linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-riscv@lists.infradead.org, devicetree@vger.kernel.org, Elle Rhumsaa X-Mailer: b4 0.15-dev X-CMS-MailID: 20250930122732eucas1p2fbf9c050934b356165377776a8aef247 X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250930122732eucas1p2fbf9c050934b356165377776a8aef247 X-EPHeader: CA X-CMS-RootMailID: 20250930122732eucas1p2fbf9c050934b356165377776a8aef247 References: <20250930-rust-next-pwm-working-fan-for-sending-v15-0-5661c3090877@samsung.com> 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`. - `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 Reviewed-by: Daniel Almeida Reviewed-by: Elle Rhumsaa Signed-off-by: Michal Wilczynski --- MAINTAINERS | 8 ++++ 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 | 102 ++++++++++++++++++++++++++++++++++++= ++++ 7 files changed, 147 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index fe168477caa45799dfe07de2f54de6d6a1ce0615..5d7c0676c1d00a02b3d7db2de88= b039c08c99c6e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20387,6 +20387,14 @@ F: include/linux/pwm.h F: include/linux/pwm_backlight.h K: pwm_(config|apply_might_sleep|apply_atomic|ops) =20 +PWM SUBSYSTEM BINDINGS [RUST] +M: Michal Wilczynski +L: linux-pwm@vger.kernel.org +L: rust-for-linux@vger.kernel.org +S: Maintained +F: rust/helpers/pwm.c +F: rust/kernel/pwm.rs + PXA GPIO DRIVER M: Robert Jarzmik L: linux-gpio@vger.kernel.org diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index f00ce973dddf651287168b44228574f4d5c28dc0..2b608f4378138775ee3ba4d53f6= 82952e1914118 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -800,4 +800,17 @@ config PWM_XILINX To compile this driver as a module, choose M here: the module will be called pwm-xilinx. =20 + config RUST_PWM_ABSTRACTIONS + bool "Rust PWM abstractions support" + depends on RUST + depends on PWM=3Dy + 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_helpe= r.h index 84d60635e8a9baef1f1a1b2752dc0fa044f8542f..7a06ee5781eadc9f21ccd456b57= 4a9cb152cd58c 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -66,6 +66,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 7cf7fe95e41dd51717050648d6160bebebdf4b26..861052ffffaff60e9c2e8109e55= f3b6158ff2281 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -35,6 +35,7 @@ #include "platform.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..d75c588863685d3990b525bb1b8= 4aa4bc35ac397 --- /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 + +#include + +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 ed53169e795c0badf548025a57f946fa18bc73e3..e339b552f9650803b1efa1eb8ec= c6fe9d2c56563 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -117,6 +117,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..beabf0086a2f1beea01e0b0a9f6= 540c601f77a49 --- /dev/null +++ b/rust/kernel/pwm.rs @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2025 Samsung Electronics Co., Ltd. +// Author: Michal Wilczynski + +//! 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 for Polarity { + type Error =3D Error; + + fn try_from(polarity: bindings::pwm_polarity) -> Result { + match polarity { + bindings::pwm_polarity_PWM_POLARITY_NORMAL =3D> Ok(Polarity::N= ormal), + bindings::pwm_polarity_PWM_POLARITY_INVERSED =3D> Ok(Polarity:= :Inversed), + _ =3D> Err(EINVAL), + } + } +} + +impl From for bindings::pwm_polarity { + fn from(polarity: Polarity) -> Self { + match polarity { + Polarity::Normal =3D> bindings::pwm_polarity_PWM_POLARITY_NORM= AL, + Polarity::Inversed =3D> bindings::pwm_polarity_PWM_POLARITY_IN= VERSED, + } + } +} + +/// 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=E2=80=99s start. + pub duty_offset_ns: u64, +} + +impl From 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 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 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` v= alue. + 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 + } +} --=20 2.34.1 From nobody Sat Oct 4 05:01:30 2025 Received: from mailout2.w1.samsung.com (mailout2.w1.samsung.com [210.118.77.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0F6B02F5A11 for ; Tue, 30 Sep 2025 12:27:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=210.118.77.12 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759235258; cv=none; b=h6grDBSAK4JLfSGlVLo1MsGswTNcn/ukYZIsbmmuT7FAn5M6GumrQ8gmbcX8qdmY+wVutW6jX7dMtX+y7OrS4XEEZrbB6lhbsz1R4oB9cNp+Sg9v3+YnkV99uux/7fqkKFwkgvEsVWesZ+QW7X4BkVeDBgKlQ1bscK8V/F+8aVk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759235258; c=relaxed/simple; bh=M71w713gM+rDFWDWe6flKFeEbtzX/6xnB2+/iH9k70c=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=AxJN2IHXUD595u9iDgQRwHgFc7YLv3IwnZRxPYqVri9pW5CZ1xTf3uOGE5inTW5YuBDVSimQUydMoiWZgwbC3gm8CSsu4EONroLS+7aoj54Qoe4AQCQSiCpL/A4OXBn7bVux1Sm3V7r2chl04McOb9gr+yLFfmlxwWgtImzA6PQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com; spf=pass smtp.mailfrom=samsung.com; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b=byNsyCUU; arc=none smtp.client-ip=210.118.77.12 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=samsung.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b="byNsyCUU" Received: from eucas1p1.samsung.com (unknown [182.198.249.206]) by mailout2.w1.samsung.com (KnoxPortal) with ESMTP id 20250930122734euoutp022315c7808d92cfa78cb4766b9b61fc94~qDySUvYra2349223492euoutp02T for ; Tue, 30 Sep 2025 12:27:34 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout2.w1.samsung.com 20250930122734euoutp022315c7808d92cfa78cb4766b9b61fc94~qDySUvYra2349223492euoutp02T DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1759235254; bh=g7Jbap+IvPifuHGuNv2BxUb3Xjc4IDLUXlZu8dkmSTs=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=byNsyCUUKuqMbcwbS7jmpy03YA0xxPz5QRIARESTgGYTSCFq5ENEisXZKNcgM/F9c APuB65j3zP+Tnyon8pQrloris4ruob49lu4S7A2VYtYsPgoglWUPpIXxhA75cL8AES 0yYgxfBM1uwUfcTyGlBMfsIvGBjCxMy4i4yRIXiM= Received: from eusmtip2.samsung.com (unknown [203.254.199.222]) by eucas1p1.samsung.com (KnoxPortal) with ESMTPA id 20250930122733eucas1p1017471af8564a40f60be74c5ae50bbc4~qDyRnB8rF2460724607eucas1p1h; Tue, 30 Sep 2025 12:27:33 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip2.samsung.com (KnoxPortal) with ESMTPA id 20250930122732eusmtip283a70a9948821fe695bc06dea7d377e3~qDyQhUpxG1482714827eusmtip2U; Tue, 30 Sep 2025 12:27:32 +0000 (GMT) From: Michal Wilczynski Date: Tue, 30 Sep 2025 14:20:34 +0200 Subject: [PATCH v15 3/7] rust: pwm: Add complete abstraction layer Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Message-Id: <20250930-rust-next-pwm-working-fan-for-sending-v15-3-5661c3090877@samsung.com> In-Reply-To: <20250930-rust-next-pwm-working-fan-for-sending-v15-0-5661c3090877@samsung.com> To: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_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 , Daniel Almeida , Benno Lossin , Drew Fustini Cc: linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-riscv@lists.infradead.org, devicetree@vger.kernel.org, Elle Rhumsaa X-Mailer: b4 0.15-dev X-CMS-MailID: 20250930122733eucas1p1017471af8564a40f60be74c5ae50bbc4 X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250930122733eucas1p1017471af8564a40f60be74c5ae50bbc4 X-EPHeader: CA X-CMS-RootMailID: 20250930122733eucas1p1017471af8564a40f60be74c5ae50bbc4 References: <20250930-rust-next-pwm-working-fan-for-sending-v15-0-5661c3090877@samsung.com> 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 Reviewed-by: Elle Rhumsaa Signed-off-by: Michal Wilczynski --- rust/kernel/pwm.rs | 664 +++++++++++++++++++++++++++++++++++++++++++++++++= +++- 1 file changed, 662 insertions(+), 2 deletions(-) diff --git a/rust/kernel/pwm.rs b/rust/kernel/pwm.rs index beabf0086a2f1beea01e0b0a9f6540c601f77a49..79fbb13cd47f75681283648ddc4= fffb7889be930 100644 --- a/rust/kernel/pwm.rs +++ b/rust/kernel/pwm.rs @@ -8,10 +8,14 @@ =20 use crate::{ bindings, + container_of, + device::{self, Bound}, + devres, + error::{self, to_result}, prelude::*, - types::Opaque, + types::{ARef, AlwaysRefCounted, Opaque}, }; -use core::convert::TryFrom; +use core::{convert::TryFrom, marker::PhantomData, ptr::NonNull}; =20 /// PWM polarity. Mirrors [`enum pwm_polarity`](srctree/include/linux/pwm.= h). #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -100,3 +104,659 @@ 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 value= s down. + ExactOrRoundedDown, + + /// The requested waveform could only be achieved by rounding up. + RoundedUp, +} + +/// Wrapper for a PWM device [`struct pwm_device`](srctree/include/linux/p= wm.h). +#[repr(transparent)] +pub struct Device(Opaque); + +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 t= he lifetime of the + /// returned [`Device`] reference. + pub(crate) unsafe fn from_raw<'a>(ptr: *mut bindings::pwm_device) -> &= 'a Self { + // SAFETY: The safety requirements guarantee the validity of the d= ereference, while the + // `Device` type being transparent makes the cast ok. + unsafe { &*ptr.cast::() } + } + + /// 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 l= ifetime. + unsafe { (*self.as_raw()).hwpwm } + } + + /// Gets a reference to the parent `Chip` that this device belongs to. + pub fn chip(&self) -> &Chip { + // 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 k= ernel. + // Chip::from_raw's safety conditions must be met. + unsafe { Chip::::from_raw((*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 =3D unsafe { (*self.as_raw()).label }; + if label_ptr.is_null() { + return None + } + + // 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 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 =3D bindings::pwm_waveform::from(*wf); + + // SAFETY: `self.as_raw()` provides a valid `*mut pwm_device` poin= ter. + // `&c_wf` is a valid pointer to a `pwm_waveform` struct. The C fu= nction + // handles all necessary internal locking. + let ret =3D unsafe { bindings::pwm_set_waveform_might_sleep(self.a= s_raw(), &c_wf, exact) }; + to_result(ret) + } + + /// Queries the hardware for the configuration it would apply for a gi= ven + /// request. + pub fn round_waveform(&self, wf: &mut Waveform) -> Result { + let mut c_wf =3D bindings::pwm_waveform::from(*wf); + + // SAFETY: `self.as_raw()` provides a valid `*mut pwm_device` poin= ter. + // `&mut c_wf` is a valid pointer to a mutable `pwm_waveform` stru= ct that + // the C function will update. + let ret =3D unsafe { bindings::pwm_round_waveform_might_sleep(self= .as_raw(), &mut c_wf) }; + + to_result(ret)?; + + *wf =3D Waveform::from(c_wf); + + if ret =3D=3D 1 { + Ok(RoundingOutcome::RoundedUp) + } else { + Ok(RoundingOutcome::ExactOrRoundedDown) + } + } + + /// Reads the current waveform configuration directly from the hardwar= e. + pub fn get_waveform(&self) -> Result { + let mut c_wf =3D 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 fi= ll. + let ret =3D unsafe { bindings::pwm_get_waveform_might_sleep(self.a= s_raw(), &mut c_wf) }; + + to_result(ret)?; + + Ok(Waveform::from(c_wf)) + } +} + +/// The result of a `round_waveform_tohw` operation. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct RoundedWaveform { + /// A status code, 0 for success or 1 if values were rounded up. + pub status: c_int, + /// The driver-specific hardware representation of the waveform. + pub hardware_waveform: WfHw, +} + +/// Trait defining the operations for a PWM driver. +pub trait PwmOps: 'static + Sized { + /// The driver-specific hardware representation of a waveform. + /// + /// This type must be [`Copy`], [`Default`], and fit within `PWM_WFHWS= IZE`. + type WfHw: Copy + Default; + + /// Optional hook for when a PWM device is requested. + fn request( + _chip: &Chip, + _pwm: &Device, + _parent_dev: &device::Device, + ) -> Result { + Ok(()) + } + + /// Optional hook for capturing a PWM signal. + fn capture( + _chip: &Chip, + _pwm: &Device, + _result: &mut bindings::pwm_capture, + _timeout: usize, + _parent_dev: &device::Device, + ) -> 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, + _pwm: &Device, + _wf: &Waveform, + ) -> Result> { + Err(ENOTSUPP) + } + + /// Convert a hardware-specific representation back to a generic wavef= orm. + /// This is typically a pure calculation and does not perform I/O. + fn round_waveform_fromhw( + _chip: &Chip, + _pwm: &Device, + _wfhw: &Self::WfHw, + _wf: &mut Waveform, + ) -> Result { + Err(ENOTSUPP) + } + + /// Read the current hardware configuration into the hardware-specific= representation. + fn read_waveform( + _chip: &Chip, + _pwm: &Device, + _parent_dev: &device::Device, + ) -> Result { + Err(ENOTSUPP) + } + + /// Write a hardware-specific waveform configuration to the hardware. + fn write_waveform( + _chip: &Chip, + _pwm: &Device, + _wfhw: &Self::WfHw, + _parent_dev: &device::Device, + ) -> Result { + Err(ENOTSUPP) + } +} + +/// Bridges Rust `PwmOps` to the C `pwm_ops` vtable. +struct Adapter { + _p: PhantomData, +} + +impl Adapter { + const VTABLE: PwmOpsVTable =3D create_pwm_ops::(); + + /// # Safety + /// + /// `wfhw_ptr` must be valid for writes of `size_of::()` byte= s. + unsafe fn serialize_wfhw(wfhw: &T::WfHw, wfhw_ptr: *mut c_void) -> Res= ult { + let size =3D core::mem::size_of::(); + + build_assert!(size <=3D bindings::PWM_WFHWSIZE as usize); + + // SAFETY: The caller ensures `wfhw_ptr` is valid for `size` bytes. + unsafe { + core::ptr::copy_nonoverlapping( + core::ptr::from_ref::(wfhw).cast::(), + wfhw_ptr.cast::(), + size, + ); + } + + Ok(()) + } + + /// # Safety + /// + /// `wfhw_ptr` must be valid for reads of `size_of::()` bytes. + unsafe fn deserialize_wfhw(wfhw_ptr: *const c_void) -> Result= { + let size =3D core::mem::size_of::(); + + build_assert!(size <=3D bindings::PWM_WFHWSIZE as usize); + + let mut wfhw =3D T::WfHw::default(); + // SAFETY: The caller ensures `wfhw_ptr` is valid for `size` bytes. + unsafe { + core::ptr::copy_nonoverlapping( + wfhw_ptr.cast::(), + core::ptr::from_mut::(&mut wfhw).cast::(), + size, + ); + } + + Ok(wfhw) + } + + /// # Safety + /// + /// `dev` must be a valid pointer to a `bindings::device` embedded wit= hin a + /// `bindings::pwm_chip`. This function is called by the device core w= hen 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 t= herefore + // safely calculate the address of the containing struct. + let c_chip_ptr =3D unsafe { container_of!(dev, bindings::pwm_chip,= dev) }; + + // SAFETY: `c_chip_ptr` is a valid pointer to a `pwm_chip` as esta= blished + // above. Calling this FFI function is safe. + let drvdata_ptr =3D unsafe { bindings::pwmchip_get_drvdata(c_chip_= ptr) }; + + // SAFETY: The driver data was initialized in `new`. We run its de= structor here. + unsafe { core::ptr::drop_in_place(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, w= hich 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( + chip_ptr: *mut bindings::pwm_chip, + pwm_ptr: *mut bindings::pwm_device, + ) -> c_int { + // SAFETY: PWM core guarentees `chip_ptr` and `pwm_ptr` are valid = pointers. + let (chip, pwm) =3D unsafe { (Chip::::from_raw(chip_ptr), Devic= e::from_raw(pwm_ptr)) }; + + // SAFETY: The PWM core guarantees the parent device exists and is= bound during callbacks. + let bound_parent =3D unsafe { chip.bound_parent_device() }; + match T::request(chip, pwm, bound_parent) { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// + /// Pointers from C must be valid. + unsafe extern "C" fn capture_callback( + chip_ptr: *mut bindings::pwm_chip, + pwm_ptr: *mut bindings::pwm_device, + res: *mut bindings::pwm_capture, + timeout: usize, + ) -> c_int { + // SAFETY: Relies on the function's contract that `chip_ptr` and `= pwm_ptr` are valid + // pointers. + let (chip, pwm, result) =3D unsafe { + ( + Chip::::from_raw(chip_ptr), + Device::from_raw(pwm_ptr), + &mut *res, + ) + }; + + // SAFETY: The PWM core guarantees the parent device exists and is= bound during callbacks. + let bound_parent =3D unsafe { chip.bound_parent_device() }; + match T::capture(chip, pwm, result, timeout, bound_parent) { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// + /// Pointers from C must be valid. + unsafe extern "C" fn round_waveform_tohw_callback( + chip_ptr: *mut bindings::pwm_chip, + pwm_ptr: *mut bindings::pwm_device, + wf_ptr: *const bindings::pwm_waveform, + wfhw_ptr: *mut c_void, + ) -> c_int { + // SAFETY: Relies on the function's contract that `chip_ptr` and `= pwm_ptr` are valid + // pointers. + let (chip, pwm, wf) =3D unsafe { + ( + Chip::::from_raw(chip_ptr), + Device::from_raw(pwm_ptr), + Waveform::from(*wf_ptr), + ) + }; + match T::round_waveform_tohw(chip, pwm, &wf) { + Ok(rounded) =3D> { + // SAFETY: `wfhw_ptr` is valid per this function's safety = contract. + if unsafe { Self::serialize_wfhw(&rounded.hardware_wavefor= m, wfhw_ptr) }.is_err() { + return EINVAL.to_errno(); + } + rounded.status + } + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// + /// Pointers from C must be valid. + unsafe extern "C" fn round_waveform_fromhw_callback( + chip_ptr: *mut bindings::pwm_chip, + pwm_ptr: *mut bindings::pwm_device, + wfhw_ptr: *const c_void, + wf_ptr: *mut bindings::pwm_waveform, + ) -> c_int { + // SAFETY: Relies on the function's contract that `chip_ptr` and `= pwm_ptr` are valid + // pointers. + let (chip, pwm) =3D unsafe { (Chip::::from_raw(chip_ptr), Devic= e::from_raw(pwm_ptr)) }; + // SAFETY: `deserialize_wfhw`'s safety contract is met by this fun= ction's contract. + let wfhw =3D match unsafe { Self::deserialize_wfhw(wfhw_ptr) } { + Ok(v) =3D> v, + Err(e) =3D> return e.to_errno(), + }; + + let mut rust_wf =3D Waveform::default(); + match T::round_waveform_fromhw(chip, pwm, &wfhw, &mut rust_wf) { + Ok(()) =3D> { + // SAFETY: `wf_ptr` is guaranteed valid by the C caller. + unsafe { + *wf_ptr =3D rust_wf.into(); + }; + 0 + } + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// + /// Pointers from C must be valid. + unsafe extern "C" fn read_waveform_callback( + chip_ptr: *mut bindings::pwm_chip, + pwm_ptr: *mut bindings::pwm_device, + wfhw_ptr: *mut c_void, + ) -> c_int { + // SAFETY: Relies on the function's contract that `chip_ptr` and `= pwm_ptr` are valid + // pointers. + let (chip, pwm) =3D unsafe { (Chip::::from_raw(chip_ptr), Devic= e::from_raw(pwm_ptr)) }; + + // SAFETY: The PWM core guarantees the parent device exists and is= bound during callbacks. + let bound_parent =3D unsafe { chip.bound_parent_device() }; + match T::read_waveform(chip, pwm, bound_parent) { + // SAFETY: `wfhw_ptr` is valid per this function's safety cont= ract. + Ok(wfhw) =3D> match unsafe { Self::serialize_wfhw(&wfhw, wfhw_= ptr) } { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + }, + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// + /// Pointers from C must be valid. + unsafe extern "C" fn write_waveform_callback( + chip_ptr: *mut bindings::pwm_chip, + pwm_ptr: *mut bindings::pwm_device, + wfhw_ptr: *const c_void, + ) -> c_int { + // SAFETY: Relies on the function's contract that `chip_ptr` and `= pwm_ptr` are valid + // pointers. + let (chip, pwm) =3D unsafe { (Chip::::from_raw(chip_ptr), Devic= e::from_raw(pwm_ptr)) }; + + // SAFETY: The PWM core guarantees the parent device exists and is= bound during callbacks. + let bound_parent =3D unsafe { chip.bound_parent_device() }; + + // SAFETY: `wfhw_ptr` is valid per this function's safety contract. + let wfhw =3D match unsafe { Self::deserialize_wfhw(wfhw_ptr) } { + Ok(v) =3D> v, + Err(e) =3D> return e.to_errno(), + }; + match T::write_waveform(chip, pwm, &wfhw, bound_parent) { + Ok(()) =3D> 0, + Err(e) =3D> 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 creat= ed, +// so it can be safely referenced and accessed concurrently by multiple th= reads +// 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() -> PwmOpsVTable { + // SAFETY: `core::mem::zeroed()` is unsafe. For `pwm_ops`, all fields = are + // `Option` or data, so a zeroed pattern (None/0) = is valid initially. + let mut ops: bindings::pwm_ops =3D unsafe { core::mem::zeroed() }; + + ops.request =3D Some(Adapter::::request_callback); + ops.capture =3D Some(Adapter::::capture_callback); + + ops.round_waveform_tohw =3D Some(Adapter::::round_waveform_tohw_cal= lback); + ops.round_waveform_fromhw =3D Some(Adapter::::round_waveform_fromhw= _callback); + ops.read_waveform =3D Some(Adapter::::read_waveform_callback); + ops.write_waveform =3D Some(Adapter::::write_waveform_callback); + ops.sizeof_wfhw =3D core::mem::size_of::(); + + PwmOpsVTable(ops) +} + +/// Wrapper for a PWM chip/controller ([`struct pwm_chip`](srctree/include= /linux/pwm.h)). +#[repr(transparent)] +pub struct Chip(Opaque, PhantomData); + +impl Chip { + /// Creates a reference to a [`Chip`] from a valid pointer. + /// + /// # Safety + /// + /// The caller must ensure that `ptr` is valid and remains valid for t= he lifetime of the + /// returned [`Chip`] reference. + pub(crate) unsafe fn from_raw<'a>(ptr: *mut bindings::pwm_chip) -> &'a= Self { + // SAFETY: The safety requirements guarantee the validity of the d= ereference, while the + // `Chip` type being transparent makes the cast ok. + unsafe { &*ptr.cast::() } + } + + /// 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 num_channels(&self) -> u32 { + // SAFETY: `self.as_raw()` provides a valid pointer for `self`'s l= ifetime. + unsafe { (*self.as_raw()).npwm } + } + + /// Returns `true` if the chip supports atomic operations for configur= ation. + pub fn is_atomic(&self) -> bool { + // SAFETY: `self.as_raw()` provides a valid pointer for `self`'s l= ifetime. + 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_ch= ip`. + // - 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::from_raw(&raw mut (*self.as_raw()).dev) } + } + + /// Gets the typed driver specific data associated with this chip's em= bedded device. + pub fn drvdata(&self) -> &T { + // SAFETY: `pwmchip_get_drvdata` returns the pointer to the privat= e data area, + // which we know holds our `T`. The pointer is valid for the lifet= ime of `self`. + unsafe { &*bindings::pwmchip_get_drvdata(self.as_raw()).cast::(= ) } + } + + /// 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 bou= nd. + /// This is guaranteed by the PWM core during `PwmOps` callbacks. + unsafe fn bound_parent_device(&self) -> &device::Device { + // SAFETY: Per the function's safety contract, the parent device e= xists. + let parent =3D unsafe { self.device().parent().unwrap_unchecked() = }; + + // SAFETY: Per the function's safety contract, the parent device i= s bound. + // This is guaranteed by the PWM core during `PwmOps` callbacks. + unsafe { parent.as_bound() } + } + + /// Allocates and wraps a PWM chip using `bindings::pwmchip_alloc`. + /// + /// Returns an [`ARef`] managing the chip's lifetime via refcoun= ting + /// on its embedded `struct device`. + pub fn new( + parent_dev: &device::Device, + num_channels: u32, + data: impl pin_init::PinInit, + ) -> Result> { + let sizeof_priv =3D core::mem::size_of::(); + // SAFETY: `pwmchip_alloc` allocates memory for the C struct and o= ur private data. + let c_chip_ptr_raw =3D unsafe { + bindings::pwmchip_alloc(parent_dev.as_raw(), num_channels, siz= eof_priv) + }; + + let c_chip_ptr: *mut bindings::pwm_chip =3D error::from_err_ptr(c_= chip_ptr_raw)?; + + // SAFETY: The `drvdata` pointer is the start of the private area,= which is where + // we will construct our `T` object. + let drvdata_ptr =3D unsafe { bindings::pwmchip_get_drvdata(c_chip_= ptr) }; + + // SAFETY: We construct the `T` object in-place in the allocated p= rivate memory. + unsafe { data.__pinned_init(drvdata_ptr.cast())? }; + + // SAFETY: `c_chip_ptr` points to a valid chip. + unsafe { (*c_chip_ptr).dev.release =3D Some(Adapter::::release_= callback); } + + // SAFETY: `c_chip_ptr` points to a valid chip. + // The `Adapter`'s `VTABLE` has a 'static lifetime, so the pointer + // returned by `as_raw()` is always valid. + unsafe { (*c_chip_ptr).ops =3D Adapter::::VTABLE.as_raw(); } + + // Cast the `*mut bindings::pwm_chip` to `*mut Chip`. This is vali= d because + // `Chip` is `repr(transparent)` over `Opaque`= , and + // `Opaque` is `repr(transparent)` over `T`. + let chip_ptr_as_self =3D c_chip_ptr.cast::(); + + // SAFETY: `chip_ptr_as_self` points to a valid `Chip` (layout-com= patible with + // `bindings::pwm_chip`) whose embedded device has refcount 1. + // `ARef::from_raw` takes this pointer and manages it via `AlwaysR= efCounted`. + Ok(unsafe { ARef::from_raw(NonNull::new_unchecked(chip_ptr_as_self= )) }) + } +} + +// SAFETY: Implements refcounting for `Chip` using the embedded `struct de= vice`. +unsafe impl AlwaysRefCounted for Chip { + #[inline] + fn inc_ref(&self) { + // SAFETY: `self.0.get()` points to a valid `pwm_chip` because `se= lf` exists. + // The embedded `dev` is valid. `get_device` increments its refcou= nt. + unsafe { bindings::get_device(&raw mut (*self.0.get()).dev); } + } + + #[inline] + unsafe fn dec_ref(obj: NonNull>) { + let c_chip_ptr =3D obj.cast::().as_ptr(); + + // SAFETY: `obj` is a valid pointer to a `Chip` (and thus `binding= s::pwm_chip`) + // with a non-zero refcount. `put_device` handles decrement and fi= nal release. + unsafe { bindings::put_device(&raw mut (*c_chip_ptr).dev); } + } +} + +// SAFETY: `Chip` is a wrapper around `*mut bindings::pwm_chip`. The under= lying C +// structure's state is managed and synchronized by the kernel's device mo= del +// and PWM core locking mechanisms. Therefore, it is safe to move the `Chi= p` +// wrapper (and the pointer it contains) across threads. +unsafe impl Send for Chip {} + +// 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 app= ropriate +// kernel locks, which the C core is responsible for. Any interior mutabil= ity is +// handled and synchronized by the C kernel code. +unsafe impl Sync for Chip {} + +/// A resource guard that ensures `pwmchip_remove` is called on drop. +/// +/// This struct is intended to be managed by the `devres` framework by tra= nsferring its ownership +/// via [`Devres::register`]. This ties the lifetime of the PWM chip regis= tration +/// to the lifetime of the underlying device. +pub struct Registration { + chip: ARef>, +} + +impl Registration { + /// 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, + chip: ARef>, + ) -> Result { + let chip_parent =3D chip.device().parent().ok_or(EINVAL)?; + if dev.as_raw() !=3D chip_parent.as_raw() { + return Err(EINVAL); + } + + let c_chip_ptr =3D chip.as_raw(); + + // SAFETY: `c_chip_ptr` points to a valid chip with its ops initia= lized. + // `__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 =3D Registration { chip }; + + devres::register(dev, registration, GFP_KERNEL) + } +} + +impl Drop for Registration { + fn drop(&mut self) { + let chip_raw =3D self.chip.as_raw(); + + // SAFETY: `chip_raw` points to a chip that was successfully regis= tered. + // `bindings::pwmchip_remove` is the correct C function to unregis= ter it. + // This `drop` implementation is called automatically by `devres` = on driver unbind. + unsafe { bindings::pwmchip_remove(chip_raw); } + } +} --=20 2.34.1 From nobody Sat Oct 4 05:01:30 2025 Received: from mailout1.w1.samsung.com (mailout1.w1.samsung.com [210.118.77.11]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 604A72FBE13 for ; Tue, 30 Sep 2025 12:27:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=210.118.77.11 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759235265; cv=none; b=EHSun7iel8no4VhO9qCZQ50sBwDKQ2PWbf4r459nmbxQtxtE/k27bX29jLzn5U5xZWAMn+gnccDy1TOgwCMp8VqX0fOttSJHo+rUZHlSjHks9ChYfOQQCxA3JlTb+qetsL/L6OnCzqlxG/3ys455iG2PctJgCeT12Wt71Q+5ksk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759235265; c=relaxed/simple; bh=f6gIX255v28i5uFhjlIpQzZSrsB5Hk6S6Br1oksl624=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=kocQa1YhryuDn6fvgxQHEG+MuIAI9+qtOGUC4SZyGpxRZYVWelSG3R9K7NQgZ053S+zRgtoFPdULj8RAUM+barfF6FZ/RFA/4ildg1988GGJZK9KQ3It4PCOD1DE/FGUjjjrL4CbtjwiAx0CgxZaOldcqk4MvrVwr9Aw+iXthJA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com; spf=pass smtp.mailfrom=samsung.com; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b=nrYR+Spk; arc=none smtp.client-ip=210.118.77.11 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=samsung.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b="nrYR+Spk" Received: from eucas1p2.samsung.com (unknown [182.198.249.207]) by mailout1.w1.samsung.com (KnoxPortal) with ESMTP id 20250930122735euoutp01d05050dba985a61621c96867857fcfc0~qDyTW6jTJ0378403784euoutp01l for ; Tue, 30 Sep 2025 12:27:35 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout1.w1.samsung.com 20250930122735euoutp01d05050dba985a61621c96867857fcfc0~qDyTW6jTJ0378403784euoutp01l DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1759235255; bh=fhkdeIOTEqxwSfWdKIOYung7kOzWffgMRFEDumAxt9c=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=nrYR+Spkl5SE9tyW7zjOy5WGTIu7rPl9bTCKKgER021xfL42aZUZ0kHWEsxCHYoAc QFEnugVZPH8F+qwfqZIbP1hd8QyENgyxT/25dPdAcaUBgNzRrPU9j72GeM0PipON7f HRrPEWvI75HDfipR87iDrGbDCI/d1BM2nRya8fD4= Received: from eusmtip2.samsung.com (unknown [203.254.199.222]) by eucas1p2.samsung.com (KnoxPortal) with ESMTPA id 20250930122734eucas1p22bfa9a51f4f47fa9b32cf11ea76de2ca~qDySv3mhV2093520935eucas1p2g; Tue, 30 Sep 2025 12:27:34 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip2.samsung.com (KnoxPortal) with ESMTPA id 20250930122733eusmtip2cb8c9f03a3f0a02bac8ef0ed07e5aaa8~qDyRqzkei2138921389eusmtip2m; Tue, 30 Sep 2025 12:27:33 +0000 (GMT) From: Michal Wilczynski Date: Tue, 30 Sep 2025 14:20:35 +0200 Subject: [PATCH v15 4/7] pwm: Add Rust driver for T-HEAD TH1520 SoC Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Message-Id: <20250930-rust-next-pwm-working-fan-for-sending-v15-4-5661c3090877@samsung.com> In-Reply-To: <20250930-rust-next-pwm-working-fan-for-sending-v15-0-5661c3090877@samsung.com> To: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_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 , Daniel Almeida , Benno Lossin , Drew Fustini Cc: linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-riscv@lists.infradead.org, devicetree@vger.kernel.org, Elle Rhumsaa X-Mailer: b4 0.15-dev X-CMS-MailID: 20250930122734eucas1p22bfa9a51f4f47fa9b32cf11ea76de2ca X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250930122734eucas1p22bfa9a51f4f47fa9b32cf11ea76de2ca X-EPHeader: CA X-CMS-RootMailID: 20250930122734eucas1p22bfa9a51f4f47fa9b32cf11ea76de2ca References: <20250930-rust-next-pwm-working-fan-for-sending-v15-0-5661c3090877@samsung.com> Introduce a PWM driver for the T-HEAD TH1520 SoC, written in Rust and utilizing the safe PWM abstractions from the preceding commit. The driver implements the pwm::PwmOps trait using the modern waveform API (round_waveform_tohw, write_waveform, etc.) to support configuration of period, duty cycle, and polarity for the TH1520's PWM channels. Resource management is handled using idiomatic Rust patterns. The PWM chip object is allocated via pwm::Chip::new and its registration with the PWM core is managed by the pwm::Registration RAII guard. This ensures pwmchip_remove is always called when the driver unbinds, preventing resource leaks. Device managed resources are used for the MMIO region, and the clock lifecycle is correctly managed in the driver's private data Drop implementation. The driver's core logic is written entirely in safe Rust, with no unsafe blocks, except for the Send and Sync implementations for the driver data, which are explained in the comments. Reviewed-by: Elle Rhumsaa Signed-off-by: Michal Wilczynski --- MAINTAINERS | 1 + drivers/pwm/Kconfig | 11 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm_th1520.rs | 382 ++++++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 395 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 5d7c0676c1d00a02b3d7db2de88b039c08c99c6e..d79dc21f22d143ca8cde6a06194= 545fbc640e695 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21741,6 +21741,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 2b608f4378138775ee3ba4d53f682952e1914118..dd6db01832ee985e2e588a413a1= 3df869a029d3d 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -729,6 +729,17 @@ config PWM_TEGRA To compile this driver as a module, choose M here: the module will be called pwm-tegra. =20 +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..5c15c95c6e49143969389198657= eed0ecf4086b2 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_PWM_STMPE) +=3D pwm-stmpe.o obj-$(CONFIG_PWM_SUN4I) +=3D pwm-sun4i.o obj-$(CONFIG_PWM_SUNPLUS) +=3D pwm-sunplus.o obj-$(CONFIG_PWM_TEGRA) +=3D pwm-tegra.o +obj-$(CONFIG_PWM_TH1520) +=3D pwm_th1520.o obj-$(CONFIG_PWM_TIECAP) +=3D pwm-tiecap.o obj-$(CONFIG_PWM_TIEHRPWM) +=3D pwm-tiehrpwm.o obj-$(CONFIG_PWM_TWL) +=3D pwm-twl.o diff --git a/drivers/pwm/pwm_th1520.rs b/drivers/pwm/pwm_th1520.rs new file mode 100644 index 0000000000000000000000000000000000000000..c9fd1d8d17bcdb20d20b1b48a9b= 207d7d638bcfd --- /dev/null +++ b/drivers/pwm/pwm_th1520.rs @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2025 Samsung Electronics Co., Ltd. +// Author: Michal Wilczynski + +//! Rust T-HEAD TH1520 PWM driver +//! +//! Limitations: +//! - The period and duty cycle are controlled by 32-bit hardware register= s, +//! 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 zer= o 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 =3D 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 =3D 1 << 0; +const TH1520_PWM_CFG_UPDATE: u32 =3D 1 << 2; +const TH1520_PWM_CONTINUOUS_MODE: u32 =3D 1 << 5; +const TH1520_PWM_FPOUT: u32 =3D 1 << 8; + +const TH1520_PWM_REG_SIZE: usize =3D 0xB0; + +fn ns_to_cycles(ns: u64, rate_hz: u64) -> u64 { + const NSEC_PER_SEC_U64: u64 =3D time::NSEC_PER_SEC as u64; + + (match ns.checked_mul(rate_hz) { + Some(product) =3D> product, + None =3D> u64::MAX, + }) / NSEC_PER_SEC_U64 +} + +fn cycles_to_ns(cycles: u64, rate_hz: u64) -> u64 { + const NSEC_PER_SEC_U64: u64 =3D time::NSEC_PER_SEC as u64; + + // TODO: Replace with a kernel helper like `mul_u64_u64_div_u64_roundu= p` + // once available in Rust. + let numerator =3D cycles + .saturating_mul(NSEC_PER_SEC_U64) + .saturating_add(rate_hz - 1); + + 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 manage= d resources. +#[pin_data(PinnedDrop)] +struct Th1520PwmDriverData { + #[pin] + iomem: devres::Devres>, + clk: Clk, +} + +// This `unsafe` implementation is a temporary necessity because the under= lying `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 synchr= onization +// 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 WfHw =3D Th1520WfHw; + + fn round_waveform_tohw( + chip: &pwm::Chip, + _pwm: &pwm::Device, + wf: &pwm::Waveform, + ) -> Result> { + let data =3D chip.drvdata(); + + if wf.period_length_ns =3D=3D 0 { + dev_dbg!(chip.device(), "Requested period is 0, disabling PWM.= \n"); + + return Ok(pwm::RoundedWaveform { + status: 0, + hardware_waveform: Th1520WfHw { + enabled: false, + ..Default::default() + }, + }); + } + + let rate_hz =3D data.clk.rate().as_hz() as u64; + + let period_cycles =3D ns_to_cycles(wf.period_length_ns, rate_hz).m= in(u64::from(u32::MAX)); + + if period_cycles =3D=3D 0 { + dev_dbg!( + chip.device(), + "Requested period {} ns is too small for clock rate {} Hz,= disabling PWM.\n", + wf.period_length_ns, + rate_hz + ); + + return Ok(pwm::RoundedWaveform { + status: 0, + hardware_waveform: Th1520WfHw { + enabled: false, + ..Default::default() + }, + }); + } + + let mut duty_cycles =3D ns_to_cycles(wf.duty_length_ns, rate_hz).m= in(u64::from(u32::MAX)); + + let mut ctrl_val =3D TH1520_PWM_CONTINUOUS_MODE; + + let is_inversed =3D wf.duty_length_ns > 0 + && wf.duty_offset_ns > 0 + && wf.duty_offset_ns >=3D wf.period_length_ns.saturating_sub(w= f.duty_length_ns); + if is_inversed { + duty_cycles =3D period_cycles - duty_cycles; + } else { + ctrl_val |=3D TH1520_PWM_FPOUT; + } + + let wfhw =3D Th1520WfHw { + // The cast is safe because the value was clamped with `.min(u= 64::from(u32::MAX))`. + period_cycles: period_cycles as u32, + duty_cycles: duty_cycles as u32, + ctrl_val, + enabled: true, + }; + + dev_dbg!( + chip.device(), + "Requested: {}/{} ns [+{} ns] -> HW: {}/{} cycles, ctrl 0x{:x}= , rate {} Hz\n", + wf.duty_length_ns, + wf.period_length_ns, + wf.duty_offset_ns, + wfhw.duty_cycles, + wfhw.period_cycles, + wfhw.ctrl_val, + rate_hz + ); + + Ok(pwm::RoundedWaveform { + status: 0, + hardware_waveform: wfhw, + }) + } + + fn round_waveform_fromhw( + chip: &pwm::Chip, + _pwm: &pwm::Device, + wfhw: &Self::WfHw, + wf: &mut pwm::Waveform, + ) -> Result { + let data =3D chip.drvdata(); + let rate_hz =3D data.clk.rate().as_hz() as u64; + + if wfhw.period_cycles =3D=3D 0 { + dev_dbg!(chip.device(), "HW state has zero period, reporting a= s disabled.\n"); + *wf =3D pwm::Waveform::default(); + return Ok(()); + } + + wf.period_length_ns =3D cycles_to_ns(u64::from(wfhw.period_cycles)= , rate_hz); + + let duty_cycles =3D u64::from(wfhw.duty_cycles); + + if (wfhw.ctrl_val & TH1520_PWM_FPOUT) !=3D 0 { + wf.duty_length_ns =3D cycles_to_ns(duty_cycles, rate_hz); + wf.duty_offset_ns =3D 0; + } else { + let period_cycles =3D u64::from(wfhw.period_cycles); + let original_duty_cycles =3D period_cycles.saturating_sub(duty= _cycles); + + // For an inverted signal, `duty_length_ns` is the high time (= period - low_time). + wf.duty_length_ns =3D cycles_to_ns(original_duty_cycles, rate_= hz); + // The offset is the initial low time, which is what the hardw= are register provides. + wf.duty_offset_ns =3D cycles_to_ns(duty_cycles, rate_hz); + } + + Ok(()) + } + + fn read_waveform( + chip: &pwm::Chip, + pwm: &pwm::Device, + parent_dev: &Device, + ) -> Result { + let data =3D chip.drvdata(); + let hwpwm =3D pwm.hwpwm(); + let iomem_accessor =3D data.iomem.access(parent_dev)?; + let iomap =3D iomem_accessor.deref(); + + let ctrl =3D iomap.try_read32(th1520_pwm_ctrl(hwpwm))?; + let period_cycles =3D iomap.try_read32(th1520_pwm_per(hwpwm))?; + let duty_cycles =3D iomap.try_read32(th1520_pwm_fp(hwpwm))?; + + let wfhw =3D Th1520WfHw { + period_cycles, + duty_cycles, + ctrl_val: ctrl, + enabled: duty_cycles !=3D 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, + pwm: &pwm::Device, + wfhw: &Self::WfHw, + parent_dev: &Device, + ) -> Result { + let data =3D chip.drvdata(); + let hwpwm =3D pwm.hwpwm(); + let iomem_accessor =3D data.iomem.access(parent_dev)?; + let iomap =3D iomem_accessor.deref(); + let duty_cycles =3D iomap.try_read32(th1520_pwm_fp(hwpwm))?; + let was_enabled =3D duty_cycles !=3D 0; + + if !wfhw.enabled { + dev_dbg!(chip.device(), "PWM-{}: Disabling channel.\n", hwpwm); + 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, t= h1520_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_pw= m_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 {}/{} cycles", + hwpwm, + wfhw.duty_cycles, + wfhw.period_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, + ::IdInfo, + [(of::DeviceId::new(c_str!("thead,th1520-pwm")), ())] +); + +impl platform::Driver for Th1520PwmPlatformDriver { + type IdInfo =3D (); + const OF_ID_TABLE: Option> =3D Some(&OF_TABL= E); + + fn probe( + pdev: &platform::Device, + _id_info: Option<&Self::IdInfo>, + ) -> Result>> { + let dev =3D pdev.as_ref(); + let request =3D pdev.io_request_by_index(0).ok_or(ENODEV)?; + + let clk =3D Clk::get(dev, None)?; + + clk.prepare_enable()?; + + // TODO: Get exclusive ownership of the clock to prevent rate chan= ges. + // The Rust equivalent of `clk_rate_exclusive_get()` is not yet av= ailable. + // This should be updated once it is implemented. + let rate_hz =3D clk.rate().as_hz(); + if rate_hz =3D=3D 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 =3D pwm::Chip::new( + dev, + TH1520_MAX_PWM_NUM, + try_pin_init!(Th1520PwmDriverData { + iomem <- request.iomap_sized::(), + clk <- clk, + }), + )?; + + pwm::Registration::register(dev, chip)?; + + Ok(KBox::new(Th1520PwmPlatformDriver, GFP_KERNEL)?.into()) + } +} + +kernel::module_platform_driver! { + type: Th1520PwmPlatformDriver, + name: "pwm-th1520", + authors: ["Michal Wilczynski "], + description: "T-HEAD TH1520 PWM driver", + license: "GPL v2", +} --=20 2.34.1 From nobody Sat Oct 4 05:01:30 2025 Received: from mailout2.w1.samsung.com (mailout2.w1.samsung.com [210.118.77.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DCCD82FB0A0 for ; Tue, 30 Sep 2025 12:27:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=210.118.77.12 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759235259; cv=none; b=JhGZWGreUGQAEZI7Nvjb/ZRWg8TjSjXuqu4bLE8+MxSvie6JZ8pfxe86T6CsPFYhvBSerIHnRWGldyeqfdyvQdMbPuOJjqMOrq+3YN8rrklDBUCkZ0D0qP8ClBkY9XhuBSuI5xkZtq/9U2eNNYbq5a+JJjoWn65YsW8BSNGbHSs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759235259; c=relaxed/simple; bh=49NWivPMHPAo5HRACL6DcvmKwJWBPu0z3rZ3GrgxkOs=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=D2fCDYZjT3uaDYdKt8KkrDHBfwwx8FABUncMxz7aS/El/AkjjVI/NkduQVG/24ejFR6p0At+9WgVoG25XhHNIzlq7TIywbKXhxiaNfkJqA7rmpNI6SWfio2kr2C4GAIgpwf0GyY4NUApQBDoDS94FvTwnyvWx5k2NNNTIhNkwAw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com; spf=pass smtp.mailfrom=samsung.com; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b=a3dQBX49; arc=none smtp.client-ip=210.118.77.12 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=samsung.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b="a3dQBX49" Received: from eucas1p1.samsung.com (unknown [182.198.249.206]) by mailout2.w1.samsung.com (KnoxPortal) with ESMTP id 20250930122736euoutp0229c3808d4b784084f97ca32a89b5fd1e~qDyUTO9ja2360123601euoutp02G for ; Tue, 30 Sep 2025 12:27:36 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout2.w1.samsung.com 20250930122736euoutp0229c3808d4b784084f97ca32a89b5fd1e~qDyUTO9ja2360123601euoutp02G DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1759235256; bh=069VGgzbvpqwglyZP67I06Ccx7fSCG+j21IgRx+oINE=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=a3dQBX49y1n21wYXGFkPjg6Csu2+GG4vIHXnwQxUUqsCuD4A3mHlE9jyDRcIQLQeA BvGgKruznSNDZ09IsyoQC5yhz0ZSVCR2I6yRdeweFhErz8dJ7ygO4Wj62oyxiVw1K7 E+SfZWh5UbscNtvH4gpBtCefInqHFxi7IGnU6tvU= Received: from eusmtip2.samsung.com (unknown [203.254.199.222]) by eucas1p1.samsung.com (KnoxPortal) with ESMTPA id 20250930122735eucas1p1c49ed11a4a48155c123ead6aec4b64a2~qDyT7BsKj2313623136eucas1p1n; Tue, 30 Sep 2025 12:27:35 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip2.samsung.com (KnoxPortal) with ESMTPA id 20250930122734eusmtip2ce63b35a7348bc8b76224d7d65c22c50~qDySzoWMc2410724107eusmtip2s; Tue, 30 Sep 2025 12:27:34 +0000 (GMT) From: Michal Wilczynski Date: Tue, 30 Sep 2025 14:20:36 +0200 Subject: [PATCH v15 5/7] dt-bindings: pwm: thead: Add T-HEAD TH1520 PWM controller Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Message-Id: <20250930-rust-next-pwm-working-fan-for-sending-v15-5-5661c3090877@samsung.com> In-Reply-To: <20250930-rust-next-pwm-working-fan-for-sending-v15-0-5661c3090877@samsung.com> To: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_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 , Daniel Almeida , Benno Lossin , Drew Fustini Cc: linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-riscv@lists.infradead.org, devicetree@vger.kernel.org, Krzysztof Kozlowski , Elle Rhumsaa X-Mailer: b4 0.15-dev X-CMS-MailID: 20250930122735eucas1p1c49ed11a4a48155c123ead6aec4b64a2 X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250930122735eucas1p1c49ed11a4a48155c123ead6aec4b64a2 X-EPHeader: CA X-CMS-RootMailID: 20250930122735eucas1p1c49ed11a4a48155c123ead6aec4b64a2 References: <20250930-rust-next-pwm-working-fan-for-sending-v15-0-5661c3090877@samsung.com> Add the Device Tree binding documentation for the T-HEAD TH1520 SoC PWM controller. Reviewed-by: Krzysztof Kozlowski Tested-by: Drew Fustini Reviewed-by: Elle Rhumsaa Signed-off-by: Michal Wilczynski Acked-by: Drew Fustini --- .../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..855aec59ac53c430adc84927123= 5686e87b10e6c --- /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 + +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 + soc { + #address-cells =3D <2>; + #size-cells =3D <2>; + pwm@ffec01c000 { + compatible =3D "thead,th1520-pwm"; + reg =3D <0xff 0xec01c000 0x0 0x4000>; + clocks =3D <&clk CLK_PWM>; + #pwm-cells =3D <3>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index d79dc21f22d143ca8cde6a06194545fbc640e695..a64027f441e8e23c579b469b245= 1b514e5d2802c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21732,6 +21732,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 --=20 2.34.1 From nobody Sat Oct 4 05:01:30 2025 Received: from mailout1.w1.samsung.com (mailout1.w1.samsung.com [210.118.77.11]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2D5AC2FD1B0 for ; Tue, 30 Sep 2025 12:27:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=210.118.77.11 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759235267; cv=none; b=IZasaChe024bAYn+EgOMZkfb69AkJwDXWiD4sXp244fHPJUOciQKDVaROy6WIuJZymK1voptDExoPdCD43n8I4Fntu/dBgCRl7aK2+GvMlAnswcieWZTP5hWC8ZVMeWjo+wiQubJ7RAf3NMLynwtD76QHPrnVWu9shF1sUVTdZU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759235267; c=relaxed/simple; bh=gcsLL+OYdYVlUpralYTdFw0jI8A+oCehXo1j38hYJLY=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=ceR8m0Y8tnKuy12Pgpa9XxaValyB0D8FSZQvuMEh0vL2BWRYH9uMAQraWRmf6Mz2fmFMtrdQbwPVfvBw8jq8L3eKA/Ya5v6JTi0ZfybL6u+sP2XN523ZcTvSJevIMD+l/JmAfh6NHJSTPL2tDcrmt3QIFGlPIIE0It+PLTE7viM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com; spf=pass smtp.mailfrom=samsung.com; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b=rRkb2Vrm; arc=none smtp.client-ip=210.118.77.11 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=samsung.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b="rRkb2Vrm" Received: from eucas1p2.samsung.com (unknown [182.198.249.207]) by mailout1.w1.samsung.com (KnoxPortal) with ESMTP id 20250930122738euoutp01e71bedf899054341566c9e116deb40e2~qDyV3LC-Y0245902459euoutp01P for ; Tue, 30 Sep 2025 12:27:38 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout1.w1.samsung.com 20250930122738euoutp01e71bedf899054341566c9e116deb40e2~qDyV3LC-Y0245902459euoutp01P DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1759235258; bh=GGb78YSEZMSjoaa+oC6oCUglv6Dc1sWOZiypzXGHKbw=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=rRkb2Vrmk2kkzh8vw1qA/Cyhw3KUWKIxeUw5axeodZ+c5CQryVxBOk18BSwD5D5dZ XlU7SAs0TFhujsAErliP8WxnR2SlcYmdklnbeqxVdy7gDzPzGVOIe1wfSOsTqd3JcV t4WBj/9dZfgRrCGCjb/Oe9doWBJn16wl+jyMCy6M= Received: from eusmtip2.samsung.com (unknown [203.254.199.222]) by eucas1p1.samsung.com (KnoxPortal) with ESMTPA id 20250930122737eucas1p1b3b1afe746a30463c9bdd159ef801543~qDyVD1vjA2573025730eucas1p1s; Tue, 30 Sep 2025 12:27:37 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip2.samsung.com (KnoxPortal) with ESMTPA id 20250930122736eusmtip2699b60bfd1c7abd20bc6c44973a47378~qDyT_rWbk1846318463eusmtip2a; Tue, 30 Sep 2025 12:27:35 +0000 (GMT) From: Michal Wilczynski Date: Tue, 30 Sep 2025 14:20:37 +0200 Subject: [PATCH v15 6/7] riscv: dts: thead: Add PWM controller node Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Message-Id: <20250930-rust-next-pwm-working-fan-for-sending-v15-6-5661c3090877@samsung.com> In-Reply-To: <20250930-rust-next-pwm-working-fan-for-sending-v15-0-5661c3090877@samsung.com> To: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_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 , Daniel Almeida , Benno Lossin , Drew Fustini Cc: linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-riscv@lists.infradead.org, devicetree@vger.kernel.org, Elle Rhumsaa X-Mailer: b4 0.15-dev X-CMS-MailID: 20250930122737eucas1p1b3b1afe746a30463c9bdd159ef801543 X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250930122737eucas1p1b3b1afe746a30463c9bdd159ef801543 X-EPHeader: CA X-CMS-RootMailID: 20250930122737eucas1p1b3b1afe746a30463c9bdd159ef801543 References: <20250930-rust-next-pwm-working-fan-for-sending-v15-0-5661c3090877@samsung.com> Add the Device Tree node for the T-HEAD TH1520 SoC's PWM controller. Reviewed-by: Drew Fustini Tested-by: Drew Fustini Reviewed-by: Elle Rhumsaa Signed-off-by: Michal Wilczynski --- 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/th= ead/th1520.dtsi index 42724bf7e90e08fac326c464d0f080e3bd2cd59b..513dc6977b2633503515ad26091= 3156fbe57d92f 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 =3D "disabled"; }; =20 + pwm: pwm@ffec01c000 { + compatible =3D "thead,th1520-pwm"; + reg =3D <0xff 0xec01c000 0x0 0x4000>; + clocks =3D <&clk CLK_PWM>; + #pwm-cells =3D <3>; + }; + clk: clock-controller@ffef010000 { compatible =3D "thead,th1520-clk-ap"; reg =3D <0xff 0xef010000 0x0 0x1000>; --=20 2.34.1 From nobody Sat Oct 4 05:01:30 2025 Received: from mailout2.w1.samsung.com (mailout2.w1.samsung.com [210.118.77.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5F18A2FB972 for ; Tue, 30 Sep 2025 12:27:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=210.118.77.12 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759235262; cv=none; b=BwvEKbxYXOvapoRX8vnMm/iSXYjvIQDvzjhQi1LaGO0tLvQDWn0Bg2JugYpdcenLVwo1Caqsb5tYK+dgfceItRm9x4HHCYSAyyUQ+fDkQR1nPeSMBPxNtPRis52az1nPKv/EAi15GFzizRCtJprZfyYa8sJtEknt7hxhW1tKVlc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759235262; c=relaxed/simple; bh=K+Qo9U5/8bG6S1LdtOr0WdY7ffLEzR7To9QOriX0cvM=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=FNyZMs+nE9isX3HnGtY6fsf7kz2XM2cn3IXAS7vzxlg9sYerMg0hB9oMKDrwR+IvRPK3SjZbs2tPXQQOawNJ92t3kO8GAUrOuhOV2YEhYxmMwjg+h5HklbBl/bFhNHil071HuRl+6st0a95gfwj/7g+y2O0GcKBz3EV87PUKoc0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com; spf=pass smtp.mailfrom=samsung.com; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b=oJjXbtJC; arc=none smtp.client-ip=210.118.77.12 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=samsung.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b="oJjXbtJC" Received: from eucas1p1.samsung.com (unknown [182.198.249.206]) by mailout2.w1.samsung.com (KnoxPortal) with ESMTP id 20250930122738euoutp02dd42f7cf10a21db7f7dc25d5161dfd32~qDyWwU9vW2140121401euoutp02D for ; Tue, 30 Sep 2025 12:27:38 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout2.w1.samsung.com 20250930122738euoutp02dd42f7cf10a21db7f7dc25d5161dfd32~qDyWwU9vW2140121401euoutp02D DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1759235259; bh=8ViuUbQvKkza5fLDio1ZaLLg5ogfLWW3Jq7KsiXlUPY=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=oJjXbtJCYw+yBIPOhvnXdlQEd2Tsjhmulhhnr6Y+itkMvbkQBykHYRIG608ppiskt sXxh+9nO1mSkpb/hM0FXWZZ1AksnI3q/2riEjtxjC1SFMoaIZHadtbJvFz11J/cWn3 oQytSIM/7R78XfAA254PjoV7hgNE0sUmKgaRhuSk= Received: from eusmtip2.samsung.com (unknown [203.254.199.222]) by eucas1p2.samsung.com (KnoxPortal) with ESMTPA id 20250930122738eucas1p2ee9244532b39860f982fd7daa4cf788e~qDyWNI4cs2094020940eucas1p2K; Tue, 30 Sep 2025 12:27:38 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip2.samsung.com (KnoxPortal) with ESMTPA id 20250930122737eusmtip247e3c37468f4e6bb2b9f63c17840ea9f~qDyVHlxQY2370623706eusmtip2T; Tue, 30 Sep 2025 12:27:37 +0000 (GMT) From: Michal Wilczynski Date: Tue, 30 Sep 2025 14:20:38 +0200 Subject: [PATCH v15 7/7] riscv: dts: thead: Add PWM fan and thermal control Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Message-Id: <20250930-rust-next-pwm-working-fan-for-sending-v15-7-5661c3090877@samsung.com> In-Reply-To: <20250930-rust-next-pwm-working-fan-for-sending-v15-0-5661c3090877@samsung.com> To: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_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 , Daniel Almeida , Benno Lossin , Drew Fustini Cc: linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-riscv@lists.infradead.org, devicetree@vger.kernel.org, Elle Rhumsaa X-Mailer: b4 0.15-dev X-CMS-MailID: 20250930122738eucas1p2ee9244532b39860f982fd7daa4cf788e X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250930122738eucas1p2ee9244532b39860f982fd7daa4cf788e X-EPHeader: CA X-CMS-RootMailID: 20250930122738eucas1p2ee9244532b39860f982fd7daa4cf788e References: <20250930-rust-next-pwm-working-fan-for-sending-v15-0-5661c3090877@samsung.com> 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 Tested-by: Drew Fustini Reviewed-by: Elle Rhumsaa Signed-off-by: Michal Wilczynski --- 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..c58c2085ca92a3234f1350500ce= dae4157f0c35f 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 =3D "serial0:115200n8"; }; + + thermal-zones { + cpu-thermal { + polling-delay =3D <1000>; + polling-delay-passive =3D <1000>; + thermal-sensors =3D <&pvt 0>; + + trips { + fan_config0: fan-trip0 { + temperature =3D <39000>; + hysteresis =3D <5000>; + type =3D "active"; + }; + + fan_config1: fan-trip1 { + temperature =3D <50000>; + hysteresis =3D <5000>; + type =3D "active"; + }; + + fan_config2: fan-trip2 { + temperature =3D <60000>; + hysteresis =3D <5000>; + type =3D "active"; + }; + }; + + cooling-maps { + map-active-0 { + cooling-device =3D <&fan 1 1>; + trip =3D <&fan_config0>; + }; + + map-active-1 { + cooling-device =3D <&fan 2 2>; + trip =3D <&fan_config1>; + }; + + map-active-2 { + cooling-device =3D <&fan 3 3>; + trip =3D <&fan_config2>; + }; + }; + }; + }; + + fan: pwm-fan { + pinctrl-names =3D "default"; + pinctrl-0 =3D <&fan_pins>; + compatible =3D "pwm-fan"; + #cooling-cells =3D <2>; + pwms =3D <&pwm 1 10000000 0>; + cooling-levels =3D <0 66 196 255>; + }; + }; =20 &padctrl0_apsys { + fan_pins: fan-0 { + pwm1-pins { + pins =3D "GPIO3_3"; /* PWM1 */ + function =3D "pwm"; + bias-disable; + drive-strength =3D <25>; + input-disable; + input-schmitt-disable; + slew-rate =3D <0>; + }; + }; + uart0_pins: uart0-0 { tx-pins { pins =3D "UART0_TXD"; --=20 2.34.1