From nobody Wed Oct 8 05:34:58 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 5ECAF2798FA for ; Tue, 1 Jul 2025 16:02:04 +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=1751385726; cv=none; b=pBeP6WBcJ0dbmZFwmCwcEq2S08qPnLlILUrN8DYZA9UuarzVn9oXpf6bBEeztg1ZZrTypBVmI4B8Avw1cyYjJ1xqLKiJA3NYd9j1ynGM77GoSmUivBXWMGX6ae1CEjxHRANgQBu9NBRXZcG9kxE8Y8nthu46Kzq6VvoAR1bKCuI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751385726; c=relaxed/simple; bh=yNpjB9RCPYu8yuSQ1YGzgp3ZJQQOJydTGLTUmWj3om8=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=mP+hhxEllUUpep93WkUx/mYeWhspLVcgHBLmz7B700/A12a+bLJnZArsuag3YEvlzyFVzI3K2NQ5WYGiBRxrrr55UbpdqjUG+LMsWxAgfhw19mWPX1pPJhhXJTNxUOPcTWTcZSmymX+0jNxUYaxin3WCWMcjJGCdHNiZYFS5Kpg= 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=ZQAt6TtC; 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="ZQAt6TtC" Received: from eucas1p2.samsung.com (unknown [182.198.249.207]) by mailout2.w1.samsung.com (KnoxPortal) with ESMTP id 20250701160159euoutp023392d49cf0e9b9fc08ae58816ea0c9b1~OLAhg0c3e2669526695euoutp02N for ; Tue, 1 Jul 2025 16:01:59 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout2.w1.samsung.com 20250701160159euoutp023392d49cf0e9b9fc08ae58816ea0c9b1~OLAhg0c3e2669526695euoutp02N DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1751385719; bh=9Vo2d35sEyLP44uYKj+D2S0nhQkzl0XSExM9Iiwegts=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=ZQAt6TtCcCcETmRmK1KxRG1jJwEG0EuQyjwiykWb8sSRXP8GBB/b/mACARZxLX/R6 uv6xPXBY459Kz6XnfS/iNABH7koUEGgbMkT/g4Awrrsh3onJ7sKxylhXPUOp9l6ouQ XPYCxGNi1zvRCx4TTRiadjtHSq1Tonk57CQ+m5Os= Received: from eusmtip1.samsung.com (unknown [203.254.199.221]) by eucas1p1.samsung.com (KnoxPortal) with ESMTPA id 20250701160158eucas1p1402f4cb2ed2b4d0d672d47f5d3beb3d1~OLAgxb6Yb2273322733eucas1p14; Tue, 1 Jul 2025 16:01:58 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip1.samsung.com (KnoxPortal) with ESMTPA id 20250701160157eusmtip1c29a1b313849ae8a233003c533485342~OLAflrnya0876208762eusmtip1e; Tue, 1 Jul 2025 16:01:57 +0000 (GMT) From: Michal Wilczynski Date: Tue, 01 Jul 2025 18:01:38 +0200 Subject: [PATCH v6 1/8] pwm: Expose PWM_WFHWSIZE in public header 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: <20250701-rust-next-pwm-working-fan-for-sending-v6-1-2710932f6f6b@samsung.com> In-Reply-To: <20250701-rust-next-pwm-working-fan-for-sending-v6-0-2710932f6f6b@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 , Stephen Boyd , 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, linux-clk@vger.kernel.org X-Mailer: b4 0.15-dev X-CMS-MailID: 20250701160158eucas1p1402f4cb2ed2b4d0d672d47f5d3beb3d1 X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250701160158eucas1p1402f4cb2ed2b4d0d672d47f5d3beb3d1 X-EPHeader: CA X-CMS-RootMailID: 20250701160158eucas1p1402f4cb2ed2b4d0d672d47f5d3beb3d1 References: <20250701-rust-next-pwm-working-fan-for-sending-v6-0-2710932f6f6b@samsung.com> The WFHWSIZE constant defines the maximum size for the hardware-specific waveform representation buffer. It is currently local to drivers/pwm/core.c, which makes it inaccessible to external tools like bindgen. Move the constant to include/linux/pwm.h to make it part of the public API. As part of this change, rename it to PWM_WFHWSIZE to follow standard kernel conventions for namespacing macros in public headers. This allows bindgen to automatically generate a corresponding constant for the Rust PWM abstractions, ensuring the value remains synchronized between the C core and Rust code and preventing future maintenance issues. Signed-off-by: Michal Wilczynski Tested-by: Drew Fustini --- drivers/pwm/core.c | 26 ++++++++++++-------------- include/linux/pwm.h | 2 ++ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index b86f06ab2a324ac98115845f72d9386966a0a3b8..c1e8ab1a0945889d92dada00306= 0b8b109f2a138 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -210,8 +210,6 @@ static int __pwm_write_waveform(struct pwm_chip *chip, = struct pwm_device *pwm, c return ret; } =20 -#define WFHWSIZE 20 - /** * pwm_round_waveform_might_sleep - Query hardware capabilities * Cannot be used in atomic context. @@ -248,10 +246,10 @@ int pwm_round_waveform_might_sleep(struct pwm_device = *pwm, struct pwm_waveform * struct pwm_chip *chip =3D pwm->chip; const struct pwm_ops *ops =3D chip->ops; struct pwm_waveform wf_req =3D *wf; - char wfhw[WFHWSIZE]; + char wfhw[PWM_WFHWSIZE]; int ret_tohw, ret_fromhw; =20 - BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw); =20 if (!pwmchip_supports_waveform(chip)) return -EOPNOTSUPP; @@ -306,10 +304,10 @@ int pwm_get_waveform_might_sleep(struct pwm_device *p= wm, struct pwm_waveform *wf { struct pwm_chip *chip =3D pwm->chip; const struct pwm_ops *ops =3D chip->ops; - char wfhw[WFHWSIZE]; + char wfhw[PWM_WFHWSIZE]; int err; =20 - BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw); =20 if (!pwmchip_supports_waveform(chip) || !ops->read_waveform) return -EOPNOTSUPP; @@ -334,11 +332,11 @@ static int __pwm_set_waveform(struct pwm_device *pwm, { struct pwm_chip *chip =3D pwm->chip; const struct pwm_ops *ops =3D chip->ops; - char wfhw[WFHWSIZE]; + char wfhw[PWM_WFHWSIZE]; struct pwm_waveform wf_rounded; int err, ret_tohw; =20 - BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw); =20 if (!pwmchip_supports_waveform(chip)) return -EOPNOTSUPP; @@ -650,9 +648,9 @@ static int __pwm_apply(struct pwm_device *pwm, const st= ruct pwm_state *state) =20 if (pwmchip_supports_waveform(chip)) { struct pwm_waveform wf; - char wfhw[WFHWSIZE]; + char wfhw[PWM_WFHWSIZE]; =20 - BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw); =20 pwm_state2wf(state, &wf); =20 @@ -809,10 +807,10 @@ int pwm_get_state_hw(struct pwm_device *pwm, struct p= wm_state *state) return -ENODEV; =20 if (pwmchip_supports_waveform(chip) && ops->read_waveform) { - char wfhw[WFHWSIZE]; + char wfhw[PWM_WFHWSIZE]; struct pwm_waveform wf; =20 - BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw); =20 ret =3D __pwm_read_waveform(chip, pwm, &wfhw); if (ret) @@ -1696,8 +1694,8 @@ static bool pwm_ops_check(const struct pwm_chip *chip) !ops->write_waveform) return false; =20 - if (WFHWSIZE < ops->sizeof_wfhw) { - dev_warn(pwmchip_parent(chip), "WFHWSIZE < %zu\n", ops->sizeof_wfhw); + if (PWM_WFHWSIZE < ops->sizeof_wfhw) { + dev_warn(pwmchip_parent(chip), "PWM_WFHWSIZE < %zu\n", ops->sizeof_wfhw= ); return false; } } else { diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 2492c91452f9641881e9923e5a97e0705047da59..8cafc483db53addf95591d1ac74= 287532c0fa0ee 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -274,6 +274,8 @@ struct pwm_capture { unsigned int duty_cycle; }; =20 +#define PWM_WFHWSIZE 20 + /** * struct pwm_ops - PWM controller operations * @request: optional hook for requesting a PWM --=20 2.34.1 From nobody Wed Oct 8 05:34:58 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 1CE0527CCF0 for ; Tue, 1 Jul 2025 16:02:03 +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=1751385726; cv=none; b=nSsWjRm4Yig3Fs6cwp7q8rjfjR1l+za9MMUuS6vdZkXb5sXDs9zAL8O9ToZ49pdYdp+X7m7Hw+6G91KYZd4oQk2YPkhv9/AI34vzBVTUdV4o4HWM3KPMwiAk3liGEv83616bv8td6d12ZRGYfN7U4DVtfkS9Q0N2TxLivmA/8GM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751385726; c=relaxed/simple; bh=zC5Z3xmHgYolO9XRFjGiooDQg6jCWVaIUhZIlE3yrr0=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=ZaIillinpJ68pJcGhTEv9MoNustp2V9ciWzN0XgIMX+jE4AmtvSa7uLfArArTYgIzDNq/ZhDU7QQ9oqh7j5AhgkP2Rp+b8Wi/5HpdCDU40Wz6T73lmaXq5SeSvpiEUv3iINI4TP9yOtx4Nxa/Txl/q5+ibgYkKqPFuQpYnPdac4= 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=Ll/VUM7V; 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="Ll/VUM7V" Received: from eucas1p2.samsung.com (unknown [182.198.249.207]) by mailout1.w1.samsung.com (KnoxPortal) with ESMTP id 20250701160200euoutp0165f10cf90328d11d81a4729f9da9a6d6~OLAilmrkT0567905679euoutp01E for ; Tue, 1 Jul 2025 16:02:00 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout1.w1.samsung.com 20250701160200euoutp0165f10cf90328d11d81a4729f9da9a6d6~OLAilmrkT0567905679euoutp01E DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1751385720; bh=Zq2DCfBAYtAMWM947+o+km+PpMzBXfTd6roBQr3uT+I=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=Ll/VUM7V84bOkiFok4s9BHOvKfsq8RY85gX6twDQRNnOfNnTKGSO54qkH3fndnMPQ Y9Vi/0HSIY1i0BCM3CU5xqZN21AJLOQzGdoW8Wbkyc1WfbUw6DrkgnPkcLpk0VM7nJ GZxThB3rrfhQOYrCPhhsbi2yNmpu4CHhfeH395k8= Received: from eusmtip1.samsung.com (unknown [203.254.199.221]) by eucas1p1.samsung.com (KnoxPortal) with ESMTPA id 20250701160200eucas1p150e6aaae8efebc723776476a054ddad1~OLAiBM79Q3124731247eucas1p1d; Tue, 1 Jul 2025 16:02:00 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip1.samsung.com (KnoxPortal) with ESMTPA id 20250701160158eusmtip1224c256253c4369d88f96bd46e6465ec~OLAg1jJ822454624546eusmtip1o; Tue, 1 Jul 2025 16:01:58 +0000 (GMT) From: Michal Wilczynski Date: Tue, 01 Jul 2025 18:01:39 +0200 Subject: [PATCH v6 2/8] 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: <20250701-rust-next-pwm-working-fan-for-sending-v6-2-2710932f6f6b@samsung.com> In-Reply-To: <20250701-rust-next-pwm-working-fan-for-sending-v6-0-2710932f6f6b@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 , Stephen Boyd , 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, linux-clk@vger.kernel.org X-Mailer: b4 0.15-dev X-CMS-MailID: 20250701160200eucas1p150e6aaae8efebc723776476a054ddad1 X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250701160200eucas1p150e6aaae8efebc723776476a054ddad1 X-EPHeader: CA X-CMS-RootMailID: 20250701160200eucas1p150e6aaae8efebc723776476a054ddad1 References: <20250701-rust-next-pwm-working-fan-for-sending-v6-0-2710932f6f6b@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`. - `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. Signed-off-by: Michal Wilczynski Tested-by: Drew Fustini --- 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 b7c9cba99db27d8a761522f94a1d4c7ef09c8632..fe47833a341f7d25f0f65877ea6= bc3dc77261732 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20177,6 +20177,12 @@ 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 +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 3ef1757502ebd92b30584cd10611311a0fbfc03b..c32655566d6ab9eff9d10f29e46= 9f9aef89cecfa 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. =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 5f795e60e889b9fc887013743c81b1cf92a52adb..6a6e1b1736b38f36c1dbdd875de= fb3b526372b67 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 1f800e78920145fc5149befb15579179dfb6e02e..c449d72fa8b19b2ab084be52046= 6ab916c63cea7 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..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 5bbf3627212f0a26d34be0d6c160a370abf1e996..9f7038d3d501982a843d6d86571= d20f1213ba9ee 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..3fad101406eac728d9b12083fad= 7abf7b7f89b25 --- /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 + +//! 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 board-dependent PWM arguments [`struct pwm_args`](srctree/= include/linux/pwm.h). +#[repr(transparent)] +pub struct Args(Opaque); + +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 poin= ter + /// 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` o= wns a copy. + unsafe { (*self.0.get()).period } + } + + /// Returns the polarity of the PWM signal. + pub fn polarity(&self) -> Result { + // 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 =3D 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` 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 Wed Oct 8 05:34:58 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 9A43327D786 for ; Tue, 1 Jul 2025 16:02:04 +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=1751385728; cv=none; b=kkufjCeVuxCYIldEGncoNaoMQ8sRiB9Ig2xYfqKRzFtYPueRh9inInzwgC3+8ybzJ0Ek7lZi14oDB02RZB9ySJk6iX4sTFD+2uLYMyolVafQAU/NeJqYsmp01VwpSqFytDkuDw25MObVJ+wVkjp4gbp5LVcVMtagFLk7vqmZuoo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751385728; c=relaxed/simple; bh=nE61Uw/qTZmpc/kn0nklyAcOdMiEotFRbm3pCw2gIKk=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=bcPy9hNsyUgiuzi38iOwPNnWLPV2aqHbOA1rWfHiKJ9wTlM7Pp94k8YtKBYPyLh9epp1lf4+WnclwFnIjMZsiI2dPuNf42Wf/pXO8Lc0CXShRm7GX8r3u3NGLEub7kY5gbD3UvPXPUp2xeANiv2uim5i11yy5IQ1Jru+ExS4V3c= 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=sFLQpUrE; 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="sFLQpUrE" Received: from eucas1p1.samsung.com (unknown [182.198.249.206]) by mailout1.w1.samsung.com (KnoxPortal) with ESMTP id 20250701160202euoutp01be08190e9f8f78f79e2a2977ee57130f~OLAj7XR-F0567905679euoutp01I for ; Tue, 1 Jul 2025 16:02:02 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout1.w1.samsung.com 20250701160202euoutp01be08190e9f8f78f79e2a2977ee57130f~OLAj7XR-F0567905679euoutp01I DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1751385722; bh=LHJg7ZQs8tMUCQhQbLIi/7KGtk7RrMfqcwEOOjEX5Ek=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=sFLQpUrET7HmpWkpVClAh2526FpbU+3gNc4sD3ToYSTBpcsIzjpuRyqIAlJLmDymf bh3L3GVgPJBeZaVbiy0zlTEvUwTNg10B4/kaBbVs6h//z2JAkSGIX6pJHjV8tl6zz8 tXyM205s3lHIjQpdbTG3uF6Qs1PH6JrYB/VSYR7g= Received: from eusmtip1.samsung.com (unknown [203.254.199.221]) by eucas1p1.samsung.com (KnoxPortal) with ESMTPA id 20250701160201eucas1p14d4182ecd8d6b2034f55ed5262bac646~OLAjSnMcA2257022570eucas1p1v; Tue, 1 Jul 2025 16:02:01 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip1.samsung.com (KnoxPortal) with ESMTPA id 20250701160200eusmtip1f45de6d955f1c8f4a46577a3741a17d1~OLAiFODUz2322723227eusmtip1I; Tue, 1 Jul 2025 16:02:00 +0000 (GMT) From: Michal Wilczynski Date: Tue, 01 Jul 2025 18:01:40 +0200 Subject: [PATCH v6 3/8] rust: pwm: Add core 'Device' and 'Chip' object wrappers 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: <20250701-rust-next-pwm-working-fan-for-sending-v6-3-2710932f6f6b@samsung.com> In-Reply-To: <20250701-rust-next-pwm-working-fan-for-sending-v6-0-2710932f6f6b@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 , Stephen Boyd , 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, linux-clk@vger.kernel.org X-Mailer: b4 0.15-dev X-CMS-MailID: 20250701160201eucas1p14d4182ecd8d6b2034f55ed5262bac646 X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250701160201eucas1p14d4182ecd8d6b2034f55ed5262bac646 X-EPHeader: CA X-CMS-RootMailID: 20250701160201eucas1p14d4182ecd8d6b2034f55ed5262bac646 References: <20250701-rust-next-pwm-working-fan-for-sending-v6-0-2710932f6f6b@samsung.com> Building on the basic data types, this commit introduces the central object abstractions for the PWM subsystem: Device and Chip. It also includes the core trait implementations that make the Chip wrapper a complete, safe, and managed object. The main components of this change are: - Device and Chip Structs: These structs wrap the underlying struct pwm_device and struct pwm_chip C objects, providing safe, idiomatic methods to access their fields. - High-Level `Device` API: Exposes safe wrappers for the modern `waveform` API, allowing consumers to apply, read, and pre-validate hardware configurations. - Core Trait Implementations for Chip: - AlwaysRefCounted: Links the Chip's lifetime to its embedded struct device reference counter. This enables automatic lifetime management via ARef. - Send and Sync: Marks the Chip wrapper as safe for use across threads. This is sound because the C core handles all necessary locking for the underlying object's state. These wrappers and traits form a robust foundation for building PWM drivers in Rust. Signed-off-by: Michal Wilczynski Tested-by: Drew Fustini --- rust/kernel/pwm.rs | 261 +++++++++++++++++++++++++++++++++++++++++++++++++= +++- 1 file changed, 258 insertions(+), 3 deletions(-) diff --git a/rust/kernel/pwm.rs b/rust/kernel/pwm.rs index 3fad101406eac728d9b12083fad7abf7b7f89b25..2c3b6edd8ded8dadaad003b63d3= 8973e04b99379 100644 --- a/rust/kernel/pwm.rs +++ b/rust/kernel/pwm.rs @@ -7,11 +7,12 @@ //! C header: [`include/linux/pwm.h`](srctree/include/linux/pwm.h). =20 use crate::{ - bindings, + bindings, device, + error::{self, to_result}, prelude::*, - types::Opaque, + types::{ARef, AlwaysRefCounted, ForeignOwnable, Opaque}, }; -use core::convert::TryFrom; +use core::{convert::TryFrom, ptr::NonNull}; =20 /// PWM polarity. Mirrors [`enum pwm_polarity`](srctree/include/linux/pwm.= h). #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -135,3 +136,257 @@ 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 as_ref<'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::as_ref's safety conditions must be met. + unsafe { Chip::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 =3D 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 de= vice. + 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 thi= s 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 =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)) + } +} + +/// Wrapper for a PWM chip/controller ([`struct pwm_chip`](srctree/include= /linux/pwm.h)). +#[repr(transparent)] +pub struct Chip(Opaque); + +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 as_ref<'a>(ptr: *mut bindings::pwm_chip) -> &'a S= elf { + // 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 npwm(&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_chip`. + // The `dev` field is an instance of `bindings::device` embedded w= ithin `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`. + let dev_field_ptr =3D unsafe { core::ptr::addr_of!((*self.as_raw()= ).dev) }; + // SAFETY: `dev_field_ptr` is a valid pointer to `bindings::device= `. + // Casting and dereferencing is safe due to `repr(transparent)` an= d lifetime. + unsafe { &*(dev_field_ptr.cast::()) } + } + + /// Returns a reference to the parent device of this PWM chip's device. + pub fn parent_device(&self) -> Option<&device::Device> { + self.device().parent() + } + + /// Gets the *typed* driver-specific data associated with this chip's = embedded device. + pub fn drvdata(&self) -> &T { + // SAFETY: `self.as_raw()` gives a valid pwm_chip pointer. + // `bindings::pwmchip_get_drvdata` is the C function to retrieve d= river data. + let ptr =3D unsafe { bindings::pwmchip_get_drvdata(self.as_raw()) = }; + + // SAFETY: The only way to create a chip is through Chip::new, whi= ch initializes + // this pointer. + unsafe { &*ptr.cast::() } + } + + /// 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, + npwm: u32, + sizeof_priv: usize, + drvdata: T, + ) -> Result> { + // SAFETY: `parent_device_for_dev_field.as_raw()` is valid. + // `bindings::pwmchip_alloc` returns a valid `*mut bindings::pwm_c= hip` (refcount 1) + // or an ERR_PTR. + let c_chip_ptr_raw =3D + unsafe { bindings::pwmchip_alloc(parent_dev.as_raw(), npwm, si= zeof_priv) }; + + let c_chip_ptr: *mut bindings::pwm_chip =3D error::from_err_ptr(c_= chip_ptr_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: The pointer is valid, so we can create a temporary ref = to set data. + let chip_ref =3D 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-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(core::ptr::addr_of_mut!((*self.0.get()).d= ev)); + } + } + + #[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(core::ptr::addr_of_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 {} --=20 2.34.1 From nobody Wed Oct 8 05:34:58 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 647BA2798EC for ; Tue, 1 Jul 2025 16:02:05 +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=1751385729; cv=none; b=mi7Ix3lc/GWyKUp+uqFHX2QnIRAN/kkzfX6q0fzDXV2J3Id4qu7MQNT6i/2J4u9wi9e+jiYYywpq5L9T0HcX8ChvjF/wqj7J8UvKKpIFvLxUe5o3nSYqTorE220HhNORoZWdzeqaJZuCwkX6UuxCxkWd74ADXK5m796yFLYRZRc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751385729; c=relaxed/simple; bh=ca5jE3F7sPcXL3WC4//z7UYKoLAX6D0SRf4wvPOr3qg=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=pWWKESFKMNIzNiI+CHkcA2BqbeXSVzkg31uGApERkHCr0E1NvHz10Q6vzUa/Ul68DUGmjNCTRINcMYagHoA3ipzi+8fCJ/A+Y+Pd61u3OAHJjEsTh3LZVpZIXp7wwUSW5fk95lFK9D5FHV7syxJoEEBb95JNxgmlYVlIdncOTeU= 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=oRRu2+5g; 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="oRRu2+5g" Received: from eucas1p1.samsung.com (unknown [182.198.249.206]) by mailout1.w1.samsung.com (KnoxPortal) with ESMTP id 20250701160203euoutp01edc439e31cf491407abb7bfa879eedeb~OLAlE76iL1086710867euoutp01J for ; Tue, 1 Jul 2025 16:02:03 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout1.w1.samsung.com 20250701160203euoutp01edc439e31cf491407abb7bfa879eedeb~OLAlE76iL1086710867euoutp01J DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1751385723; bh=eD6YWlOIJsniRgR0rO+kUxFOQLcmQ2kTsa2Mo3I+Ufo=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=oRRu2+5gg0p6pOCwbq28zlEkbpZ8bjuo0Kfgbe2uMZLHUaqn8Zji9nGTj7J57I1Op cY+KlPCHBvrGNMtgei0+a0czU9WXD5N7woKSHmJasdtcQqgJm1HrMDWCd1lClPBn20 gJbzaNKgyfuV9FSJ39XoOduvmMfhYjn63t7Zx97k= Received: from eusmtip1.samsung.com (unknown [203.254.199.221]) by eucas1p2.samsung.com (KnoxPortal) with ESMTPA id 20250701160202eucas1p25bc7b00304c064bf35a753d28f970c57~OLAkllLAJ2408424084eucas1p2W; Tue, 1 Jul 2025 16:02:02 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip1.samsung.com (KnoxPortal) with ESMTPA id 20250701160201eusmtip111ce52ce8c2c33e4630ac3a8c61058a9~OLAjXF8yD2454624546eusmtip1p; Tue, 1 Jul 2025 16:02:01 +0000 (GMT) From: Michal Wilczynski Date: Tue, 01 Jul 2025 18:01:41 +0200 Subject: [PATCH v6 4/8] rust: pwm: Add driver operations trait and registration support 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: <20250701-rust-next-pwm-working-fan-for-sending-v6-4-2710932f6f6b@samsung.com> In-Reply-To: <20250701-rust-next-pwm-working-fan-for-sending-v6-0-2710932f6f6b@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 , Stephen Boyd , 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, linux-clk@vger.kernel.org X-Mailer: b4 0.15-dev X-CMS-MailID: 20250701160202eucas1p25bc7b00304c064bf35a753d28f970c57 X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250701160202eucas1p25bc7b00304c064bf35a753d28f970c57 X-EPHeader: CA X-CMS-RootMailID: 20250701160202eucas1p25bc7b00304c064bf35a753d28f970c57 References: <20250701-rust-next-pwm-working-fan-for-sending-v6-0-2710932f6f6b@samsung.com> Complete the PWM abstraction layer by adding the final components required to implement and register a driver. The main additions are: - PwmOps Trait: An interface that drivers can implement to provide their hardware specific logic. It mirrors the C pwm_ops interface, providing hooks for standard PWM operations like apply, request, and waveform handling. - FFI VTable and Adapter: The Adapter struct, PwmOpsVTable wrapper, and create_pwm_ops function are introduced. This scaffolding handles the unsafe FFI translation, bridging the gap between the idiomatic PwmOps trait and the C kernel's function-pointer-based vtable. - Registration Guard: A final RAII guard that uses the vtable to safely register a Chip with the PWM core via pwmchip_add. Its Drop implementation guarantees that pwmchip_remove is always called, preventing resource leaks. With this patch, the PWM abstraction layer is now complete and ready to be used for writing PWM chip drivers in Rust. Reviewed-by: Danilo Krummrich Signed-off-by: Michal Wilczynski Tested-by: Drew Fustini --- rust/kernel/pwm.rs | 421 +++++++++++++++++++++++++++++++++++++++++++++++++= +++- 1 file changed, 419 insertions(+), 2 deletions(-) diff --git a/rust/kernel/pwm.rs b/rust/kernel/pwm.rs index 2c3b6edd8ded8dadaad003b63d38973e04b99379..8ea3be9938727af858a76c34f9d= df3628cb9f339 100644 --- a/rust/kernel/pwm.rs +++ b/rust/kernel/pwm.rs @@ -7,12 +7,14 @@ //! C header: [`include/linux/pwm.h`](srctree/include/linux/pwm.h). =20 use crate::{ - bindings, device, + bindings, + device::{self, Bound}, + devres, error::{self, to_result}, prelude::*, types::{ARef, AlwaysRefCounted, ForeignOwnable, Opaque}, }; -use core::{convert::TryFrom, ptr::NonNull}; +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)] @@ -390,3 +392,418 @@ unsafe impl Send for Chip {} // 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, + ops_vtable: &'static PwmOpsVTable, + ) -> Result { + let c_chip_ptr =3D chip.as_raw(); + + // SAFETY: `c_chip_ptr` is valid because the `ARef` that own= s it exists. + // The vtable pointer is also valid. This sets the `.ops` field on= the C struct. + unsafe { + (*c_chip_ptr).ops =3D ops_vtable.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); + } + } +} + +/// 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 when a PWM device is freed. + fn free(_chip: &Chip, _pwm: &Device, _parent_dev: &device::Device) {} + + /// 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<(c_int, Self::WfHw)> { + 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 { + /// # 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::(); + 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::(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::(); + if size > bindings::PWM_WFHWSIZE as usize { + return Err(EINVAL); + } + + 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 + /// + /// 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) =3D unsafe { (Chip::as_ref(c), Device::as_ref(p)) = }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return EINVAL.to_errno(); + } + }; + + let bound_parent =3D + // SAFETY: The PWM core guarantees the device is bound during = callbacks. + unsafe { + &*core::ptr::from_ref::(parent_dev) + .cast::>() + }; + 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 free_callback(c: *mut bindings::pwm_chip, p: *mut= bindings::pwm_device) { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm) =3D unsafe { (Chip::as_ref(c), Device::as_ref(p)) = }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return; + } + }; + + let bound_parent =3D + // SAFETY: The PWM core guarantees the device is bound during = callbacks. + unsafe { + &*core::ptr::from_ref::(parent_dev) + .cast::>() + }; + T::free(chip, pwm, bound_parent); + } + + /// # 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) =3D unsafe { (Chip::as_ref(c), Device::as_= ref(p), &mut *res) }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return EINVAL.to_errno(); + } + }; + + let bound_parent =3D + // SAFETY: The PWM core guarantees the device is bound during = callbacks. + unsafe { + &*core::ptr::from_ref::(parent_dev) + .cast::>() + }; + 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( + 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) =3D unsafe { (Chip::as_ref(c), Device::as_ref(= p), Waveform::from(*w)) }; + match T::round_waveform_tohw(chip, pwm, &wf) { + Ok((status, wfhw)) =3D> { + // SAFETY: `wh` is valid per this function's safety contra= ct. + if unsafe { Self::serialize_wfhw(&wfhw, wh) }.is_err() { + return EINVAL.to_errno(); + } + status + } + Err(e) =3D> 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) =3D unsafe { (Chip::as_ref(c), Device::as_ref(p)) = }; + // SAFETY: `deserialize_wfhw`'s safety contract is met by this fun= ction's contract. + let wfhw =3D match unsafe { Self::deserialize_wfhw(wh) } { + 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(ret) =3D> { + // SAFETY: `w` is guaranteed valid by the C caller. + unsafe { + *w =3D rust_wf.into(); + }; + ret + } + Err(e) =3D> 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) =3D unsafe { (Chip::as_ref(c), Device::as_ref(p)) = }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return EINVAL.to_errno(); + } + }; + + let bound_parent =3D + // SAFETY: The PWM core guarantees the device is bound during = callbacks. + unsafe { + &*core::ptr::from_ref::(parent_dev) + .cast::>() + }; + match T::read_waveform(chip, pwm, bound_parent) { + // SAFETY: `wh` is valid per this function's safety contract. + Ok(wfhw) =3D> match unsafe { Self::serialize_wfhw(&wfhw, wh) }= { + 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( + 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) =3D unsafe { (Chip::as_ref(c), Device::as_ref(p)) = }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return EINVAL.to_errno(); + } + }; + + let bound_parent =3D + // SAFETY: The PWM core guarantees the device is bound during = callbacks. + unsafe { + &*core::ptr::from_ref::(parent_dev) + .cast::>() + }; + // SAFETY: `wh` is valid per this function's safety contract. + let wfhw =3D match unsafe { Self::deserialize_wfhw(wh) } { + 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(Opaque); + +// 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.get() + } +} + +/// 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.free =3D Some(Adapter::::free_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(Opaque::new(ops)) +} --=20 2.34.1 From nobody Wed Oct 8 05:34:58 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 DEFAE27E07B for ; Tue, 1 Jul 2025 16:02:06 +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=1751385731; cv=none; b=nMlRzcDTdyuXnfbJg6KOw+5SbZeeaPmBon5f3qbHq1v26a65QUaKnfa45PhmXsXk8GgjSuEPU0L5+28BguTu7ziVluXNsk7+Jil7szck8suUixy1W82PpUWhSClJwiAb6SH2LLIDNxAzQxtAS0AAkZUpxb1K+qxB02c40VDkzOE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751385731; c=relaxed/simple; bh=WjVgpfIiSC74PvLXY2CJgl1js9cSOY5adtyyBA8TL2Y=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=MMJSTqpf6JQWcnaXEmUPq4az7vdm2g3AvNTw2O6hfz8K0jnzn999HpOuZtwSNBTcUzDND88sYh3/7djGaY9Nba3fa2WIXl1XgmBy6HhUdNfA8RP/w8/FVgYNXwlawiKQRGAJhiNEDm2NXuFkvzNiDiVaADwbVSrQpfPLadUzkq4= 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=MDdWzk/N; 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="MDdWzk/N" Received: from eucas1p1.samsung.com (unknown [182.198.249.206]) by mailout1.w1.samsung.com (KnoxPortal) with ESMTP id 20250701160204euoutp018dfc5ce4718576e2c7702a34c09866b5~OLAmSPsQR1086710867euoutp01L for ; Tue, 1 Jul 2025 16:02:04 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout1.w1.samsung.com 20250701160204euoutp018dfc5ce4718576e2c7702a34c09866b5~OLAmSPsQR1086710867euoutp01L DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1751385724; bh=1ahmzo5Vz24RWTOnm/eKIKUAnEik3RqLi+QEvnFpepo=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=MDdWzk/NbirWjc8EKFWFupVSoFwfXv+ZZeYmEpFrs1jAyyyevfTQ/pF5Omf5czVsg /UQc3uRBQn7KT9D/3s7CUp8Ia6Z6sBzSqsBaN2CmMgFgELEYQtTil9yjpW0v7bb4SH KzPd+wxT9d6m8kgC9CICKvhZ/F25Jui+ARkpNXeY= Received: from eusmtip1.samsung.com (unknown [203.254.199.221]) by eucas1p2.samsung.com (KnoxPortal) with ESMTPA id 20250701160204eucas1p2d0f1ec8ab7310927f497040bb879ba05~OLAl0EqmD3262732627eucas1p2B; Tue, 1 Jul 2025 16:02:04 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip1.samsung.com (KnoxPortal) with ESMTPA id 20250701160202eusmtip1c0f1192158cd031ea5c25b8a5fa94351~OLAko6P5K2322723227eusmtip1K; Tue, 1 Jul 2025 16:02:02 +0000 (GMT) From: Michal Wilczynski Date: Tue, 01 Jul 2025 18:01:42 +0200 Subject: [PATCH v6 5/8] 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: <20250701-rust-next-pwm-working-fan-for-sending-v6-5-2710932f6f6b@samsung.com> In-Reply-To: <20250701-rust-next-pwm-working-fan-for-sending-v6-0-2710932f6f6b@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 , Stephen Boyd , 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, linux-clk@vger.kernel.org X-Mailer: b4 0.15-dev X-CMS-MailID: 20250701160204eucas1p2d0f1ec8ab7310927f497040bb879ba05 X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250701160204eucas1p2d0f1ec8ab7310927f497040bb879ba05 X-EPHeader: CA X-CMS-RootMailID: 20250701160204eucas1p2d0f1ec8ab7310927f497040bb879ba05 References: <20250701-rust-next-pwm-working-fan-for-sending-v6-0-2710932f6f6b@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. Signed-off-by: Michal Wilczynski Tested-by: Drew Fustini --- MAINTAINERS | 1 + drivers/pwm/Kconfig | 11 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm_th1520.rs | 338 ++++++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 351 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index fe47833a341f7d25f0f65877ea6bc3dc77261732..ebbc24f3ef4752cae1b1028939f= 58f5587074371 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21511,6 +21511,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..02faf93600b6464d3c02495eeb5= 824ea541cff35 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. =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..c4381d4aa101323a599f1d18368= 96bafc33f9706 --- /dev/null +++ b/drivers/pwm/pwm_th1520.rs @@ -0,0 +1,338 @@ +// 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; + + // Round up + let Some(numerator) =3D 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 manage= d resources. +#[pin_data(PinnedDrop)] +struct Th1520PwmDriverData { + #[pin] + iomem: devres::Devres>, + clk: Clk, +} + +impl pwm::PwmOps for Th1520PwmDriverData { + type WfHw =3D Th1520WfHw; + + fn round_waveform_tohw( + chip: &pwm::Chip, + _pwm: &pwm::Device, + wf: &pwm::Waveform, + ) -> Result<(c_int, Self::WfHw)> { + let data: &Self =3D chip.drvdata(); + + if wf.period_length_ns =3D=3D 0 { + return Ok(( + 0, + 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)); + 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_length_ns + wf.duty_offset_ns >=3D wf.period_length= _ns; + if is_inversed { + duty_cycles =3D period_cycles - duty_cycles; + } else { + ctrl_val |=3D TH1520_PWM_FPOUT; + } + + let wfhw =3D 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, + _pwm: &pwm::Device, + wfhw: &Self::WfHw, + wf: &mut pwm::Waveform, + ) -> Result { + let data: &Self =3D chip.drvdata(); + let rate_hz =3D data.clk.rate().as_hz() as u64; + + 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(0) + } + + fn read_waveform( + chip: &pwm::Chip, + pwm: &pwm::Device, + parent_dev: &Device, + ) -> Result { + let data: &Self =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: &Self =3D chip.drvdata(); + let hwpwm =3D pwm.hwpwm(); + let iomem_accessor =3D data.iomem.access(parent_dev)?; + let iomap =3D iomem_accessor.deref(); + let was_enabled =3D 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, 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 (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(); + } +} + +static TH1520_PWM_OPS: pwm::PwmOpsVTable =3D pwm::create_pwm_ops::(); + +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 resource =3D pdev.resource(0).ok_or(ENODEV)?; + + let drvdata =3D KBox::pin_init( + try_pin_init!(Th1520PwmDriverData { + iomem <- pdev.ioremap_resource_sized::(resource), + clk <- Clk::get(dev, None), + }), + GFP_KERNEL, + )?; + + drvdata.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 drvdata.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, 0, drvdata)?; + + pwm::Registration::register(dev, chip, &TH1520_PWM_OPS)?; + + 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 Wed Oct 8 05:34:58 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 923C827EFE6 for ; Tue, 1 Jul 2025 16:02:08 +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=1751385732; cv=none; b=hGMQmJyVgcUJNycViFQrLHMZs2TViihgzidlqiWmXBVOwq4abhTwA7ns5Z08pC76dEFJpYQT7RkyS3zAc9AZBrX952oywOk+677T0UAyTFYEQEWSIn498Z2jc+3X+/cuG1eWE0rpaEOMuKyPIQVKC9R7INuqHlbd2QzcDn1EnUk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751385732; c=relaxed/simple; bh=0WrrBHjAhDWR/T7RKbv5Tyx5Q6bSAJoF2Th/DmIjlPQ=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=Q0J+WD85AuuWTjAXTIV7ieu9vfg2vYyvEXNLj6L0tg5EApDv5MZNzjG1UCcpFMKJYe0gy02+ADB3L4NeQ/36Icx1fh68pFYxBrIaMft3cweIceOlh6Cadnu3af9u4PB/BKup//8NZZ2H79I7qbummobm7yN8o/VwSpOKbTLz9ls= 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=c6v28gxV; 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="c6v28gxV" Received: from eucas1p2.samsung.com (unknown [182.198.249.207]) by mailout2.w1.samsung.com (KnoxPortal) with ESMTP id 20250701160206euoutp0224170694b8b2ed7e00aa54b58cf800cc~OLAnrU4pS2669526695euoutp02a for ; Tue, 1 Jul 2025 16:02:06 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout2.w1.samsung.com 20250701160206euoutp0224170694b8b2ed7e00aa54b58cf800cc~OLAnrU4pS2669526695euoutp02a DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1751385726; bh=468vL6f20bR8cbLJ5mItg8FGJ3SLduAhCDZLfy4Z39o=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=c6v28gxV0QyZZDj1WDIr6UhlKHos44C2y0dD2norXWqJaNCJypddCibzmuhaa9m+j TYO26HEch5dr9fgvFCoC7raEreYZvE4utYaD72Ax5V8xoatYlNzig71Of/fRKLNTzs 3D54N8H3U5LH9U5AodXMj/SSeFIvmZm2BUd84MV8= Received: from eusmtip1.samsung.com (unknown [203.254.199.221]) by eucas1p2.samsung.com (KnoxPortal) with ESMTPA id 20250701160205eucas1p27a77e76ce28deede85e666f0544c6c7c~OLAnHBIk92408824088eucas1p2V; Tue, 1 Jul 2025 16:02:05 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip1.samsung.com (KnoxPortal) with ESMTPA id 20250701160204eusmtip189359772207621dd3a075bd7b5c7b2d1~OLAl4Kk8k2630026300eusmtip1T; Tue, 1 Jul 2025 16:02:04 +0000 (GMT) From: Michal Wilczynski Date: Tue, 01 Jul 2025 18:01:43 +0200 Subject: [PATCH v6 6/8] 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: <20250701-rust-next-pwm-working-fan-for-sending-v6-6-2710932f6f6b@samsung.com> In-Reply-To: <20250701-rust-next-pwm-working-fan-for-sending-v6-0-2710932f6f6b@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 , Stephen Boyd , 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, linux-clk@vger.kernel.org, Krzysztof Kozlowski X-Mailer: b4 0.15-dev X-CMS-MailID: 20250701160205eucas1p27a77e76ce28deede85e666f0544c6c7c X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250701160205eucas1p27a77e76ce28deede85e666f0544c6c7c X-EPHeader: CA X-CMS-RootMailID: 20250701160205eucas1p27a77e76ce28deede85e666f0544c6c7c References: <20250701-rust-next-pwm-working-fan-for-sending-v6-0-2710932f6f6b@samsung.com> Add the Device Tree binding documentation for the T-HEAD TH1520 SoC PWM controller. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Michal Wilczynski Tested-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 ebbc24f3ef4752cae1b1028939f58f5587074371..6e862b3850462af12c02e88ac00= 48fdecccd4c83 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21502,6 +21502,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 Wed Oct 8 05:34:58 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 E227327F16D for ; Tue, 1 Jul 2025 16:02:09 +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=1751385733; cv=none; b=tQMZ/+DMVgO9V8Nkexkrt4xM0K6UTqtfS8tZ4x1Xxz7kBkA8RqtYb40r/jjdwVR5n8oQI/PxkNnNqT0dLkeISVcQqYnqKkuuKO96dU8ZBsowdDL7ug1MzFIIPOPLegbQRsXv3qIlm+SXkJULiZ18KSvK+bFLFy1IxZvGa7rJgYI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751385733; c=relaxed/simple; bh=96pMcI4y7fwgi4gLKghytV1TELMf+f1/6Dts6wtE58I=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=eqnIzjEOUsZAe/mlMFWYymSJJjWgRlnnxgjHa6ytahiQ/cD9LnvIY/Zik9O9Ql91xZdiiAytTZNpauFR6DM2O7bRv+4E3WBoOZ08eU37xgrqeSe/Jn8z09+Iv4Avd0tNVP/zkK5EQVUQJTBrLCYqUl8hGMZ5VFwTDg1OnXWNERs= 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=ui9YSReu; 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="ui9YSReu" Received: from eucas1p2.samsung.com (unknown [182.198.249.207]) by mailout2.w1.samsung.com (KnoxPortal) with ESMTP id 20250701160207euoutp0248a4727e0a203362bf09714d91c9b5fc~OLAo5eR3w2669526695euoutp02c for ; Tue, 1 Jul 2025 16:02:07 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout2.w1.samsung.com 20250701160207euoutp0248a4727e0a203362bf09714d91c9b5fc~OLAo5eR3w2669526695euoutp02c DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1751385727; bh=EDNXQZCXg+li71fh9CrBaFb+lseftTHxvblcVztr9Os=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=ui9YSReu1q6mVRq3Yd9Dx3N3qW/SI4HUOzHH4o64bHMLXZ8UV/pHCru0PnXABLsXB 4IYxkpHPQVc+C3Iq23BItFjuZVg7JRlZlMT6otDFdM+rypsWk9CAvGI+1O2De+G5Uq NuXf6qykx8k8/QKbDsTb8TJdQgHzM8urzGy5mIlY= Received: from eusmtip1.samsung.com (unknown [203.254.199.221]) by eucas1p1.samsung.com (KnoxPortal) with ESMTPA id 20250701160206eucas1p1b4510b4dc09dc7193cac0e51db908784~OLAoVs1tA1059010590eucas1p1B; Tue, 1 Jul 2025 16:02:06 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip1.samsung.com (KnoxPortal) with ESMTPA id 20250701160205eusmtip1c6b68af80f55a9e5dece39ee03782f75~OLAnLQcnF2454624546eusmtip1u; Tue, 1 Jul 2025 16:02:05 +0000 (GMT) From: Michal Wilczynski Date: Tue, 01 Jul 2025 18:01:44 +0200 Subject: [PATCH v6 7/8] 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: <20250701-rust-next-pwm-working-fan-for-sending-v6-7-2710932f6f6b@samsung.com> In-Reply-To: <20250701-rust-next-pwm-working-fan-for-sending-v6-0-2710932f6f6b@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 , Stephen Boyd , 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, linux-clk@vger.kernel.org X-Mailer: b4 0.15-dev X-CMS-MailID: 20250701160206eucas1p1b4510b4dc09dc7193cac0e51db908784 X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250701160206eucas1p1b4510b4dc09dc7193cac0e51db908784 X-EPHeader: CA X-CMS-RootMailID: 20250701160206eucas1p1b4510b4dc09dc7193cac0e51db908784 References: <20250701-rust-next-pwm-working-fan-for-sending-v6-0-2710932f6f6b@samsung.com> Add the Device Tree node for the T-HEAD TH1520 SoC's PWM controller. Signed-off-by: Michal Wilczynski Tested-by: Drew Fustini --- 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 Wed Oct 8 05:34:58 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 33B6A27FB06 for ; Tue, 1 Jul 2025 16:02:11 +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=1751385734; cv=none; b=Qqr02SyipM3WtXJLsdofSzHa7vD5XLYjjn1/tvIzuxSE02mkK2+Rjx/NcSEwkMfKBrFta5rKri3Y8BGrQ3ku/wn3VU1ga+fQs0Ls15JRpybm5U4Jq+TJBmYSLEThEWouNEDMqeVhnVm0W+Ykboj2EaD1EfNYNTfVT6VrFhlniKA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751385734; c=relaxed/simple; bh=pQpdhYmfxqh3Yfxmei5sPv11PaCzGkgZVRufLVaDNj8=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=MtTbyWQZPdV83LTVa76zzfKW4xnmwdxQbnoeN+8AXmRAQre4SX1z9ZpnGEv+7cW9RENskM9s9w3OMzcHyl/MVvPMN5Xv3jLeeL/HXqHpnnxiA2Zp53kBt+Hmy+uqo24eu/OJVqehFNhvdfsiacRV6kCoVf9kQn7w+XXHc/VzJyM= 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=E4CZAKog; 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="E4CZAKog" Received: from eucas1p1.samsung.com (unknown [182.198.249.206]) by mailout1.w1.samsung.com (KnoxPortal) with ESMTP id 20250701160208euoutp0119a188f7a1360d2e7a16d202dbd5980e~OLAp7aMt01089110891euoutp01O for ; Tue, 1 Jul 2025 16:02:08 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout1.w1.samsung.com 20250701160208euoutp0119a188f7a1360d2e7a16d202dbd5980e~OLAp7aMt01089110891euoutp01O DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1751385728; bh=Thc/4MnT4r5I8ag38cbivyQhpbuX345C1nEB0lzCSSU=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=E4CZAKogyAvZo+4zp4YdTXwhS6UT3701qpYlrBkDWHg7t+enBnejRciSq8V2WvOqI LVIWNffSp+f6RsG7Ydj0ZxFSY+cmVPUMR1Tq3K6JdNYBk3FeRzWPdkV6CmUgru2TCu XQ6KQyxk8FijjwV7q4bJZV2EGWx6kRDPr5xIw+XM= Received: from eusmtip1.samsung.com (unknown [203.254.199.221]) by eucas1p2.samsung.com (KnoxPortal) with ESMTPA id 20250701160208eucas1p2633ce5cd0b9ef013999b3d596be74cdd~OLApiaZG32521625216eucas1p2G; Tue, 1 Jul 2025 16:02:08 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip1.samsung.com (KnoxPortal) with ESMTPA id 20250701160206eusmtip192999017bfbc2de977e7580df56a36a7~OLAoZ52AM3260032600eusmtip1D; Tue, 1 Jul 2025 16:02:06 +0000 (GMT) From: Michal Wilczynski Date: Tue, 01 Jul 2025 18:01:45 +0200 Subject: [PATCH v6 8/8] 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: <20250701-rust-next-pwm-working-fan-for-sending-v6-8-2710932f6f6b@samsung.com> In-Reply-To: <20250701-rust-next-pwm-working-fan-for-sending-v6-0-2710932f6f6b@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 , Stephen Boyd , 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, linux-clk@vger.kernel.org X-Mailer: b4 0.15-dev X-CMS-MailID: 20250701160208eucas1p2633ce5cd0b9ef013999b3d596be74cdd X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250701160208eucas1p2633ce5cd0b9ef013999b3d596be74cdd X-EPHeader: CA X-CMS-RootMailID: 20250701160208eucas1p2633ce5cd0b9ef013999b3d596be74cdd References: <20250701-rust-next-pwm-working-fan-for-sending-v6-0-2710932f6f6b@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. Signed-off-by: Michal Wilczynski Tested-by: Drew Fustini --- 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