From nobody Sun Feb 8 01:51:50 2026 Received: from bali.collaboradmins.com (bali.collaboradmins.com [148.251.105.195]) (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 5430A363C6C; Wed, 7 Jan 2026 15:10:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.251.105.195 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767798609; cv=none; b=iH8J8x7nN/reh517O4MN8b63cdrzRUUj37L8gqm6NB6W33JSLtWNLeZbNOipVafHkWCPH7gm3moQhSFZ1PTfxqBgK60RxmfVeGoUgiX/eSeQ+pHLxtIl10VwTQr5HEz4+fSx0x7MqZbDMG+rSH3dM76ZQhUdnAzUzZWLCGpyTFA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767798609; c=relaxed/simple; bh=BPMa7EyGU4omU0N14YW7w2FFy9qgNG4FJNETsw3M0jY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=oOi/FqGdG7fDdiiVly7avBY6ImHfBr6ovNk45x6ItCvNwd/TEAGSbwTixeLoBdgP3CkmLUH41aWnBuqUW2bCc3hmtSxEeslJ3b/9RcMsLb9w2RAnSHMGSn3cYeYhvErulZdAo5CHGSNz/wGnmCqq80pIh6tFIktt1ND2prcPTnI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=collabora.com; spf=pass smtp.mailfrom=collabora.com; dkim=pass (2048-bit key) header.d=collabora.com header.i=@collabora.com header.b=fbISWVLZ; arc=none smtp.client-ip=148.251.105.195 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=collabora.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=collabora.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=collabora.com header.i=@collabora.com header.b="fbISWVLZ" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1767798604; bh=BPMa7EyGU4omU0N14YW7w2FFy9qgNG4FJNETsw3M0jY=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=fbISWVLZYpmoBQfjjmeOfvUBlCKoFBcpaih/89f0uxxHdwkpROypQH1xhG8mG8ZaF ij3Xp09LGxB/riP1qMUH8Hlmstg3U21uDaZjiAxEFigyXkLnVEfsCIBwqR8o6PGTvH qQO+EWUmnyGhTgwFLih4z5eqQ35hDMBleLOn7QuDg0yfRdWFDHnbl0JNAPgbpYfa+p ECSbi5+lpjmBKUhpr/wN63uyQv3v6iAL3CfcYylivGSthcGGJFp5saVNQu+7DP4M2k zaz175k/we5k3DfqoUMLadRfbJNSZWcLqhp0wUciL4F/LN4gX66/Zl/V8Tq+n8y3K1 qEgyqs4Run5Rg== Received: from [192.168.0.79] (unknown [IPv6:2804:14d:72b4:81ae:67c:16ff:fe57:b5a3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: dwlsalmeida) by bali.collaboradmins.com (Postfix) with ESMTPSA id A486D17E13F6; Wed, 7 Jan 2026 16:09:59 +0100 (CET) From: Daniel Almeida Date: Wed, 07 Jan 2026 12:09:52 -0300 Subject: [PATCH v3 1/3] rust: clk: use the type-state pattern Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260107-clk-type-state-v3-1-77d3e3ee59c2@collabora.com> References: <20260107-clk-type-state-v3-0-77d3e3ee59c2@collabora.com> In-Reply-To: <20260107-clk-type-state-v3-0-77d3e3ee59c2@collabora.com> To: "Rafael J. Wysocki" , Viresh Kumar , Danilo Krummrich , Alice Ryhl , Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , David Airlie , Simona Vetter , Drew Fustini , Guo Ren , Fu Wei , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Michael Turquette , Stephen Boyd , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross Cc: linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-riscv@lists.infradead.org, linux-pwm@vger.kernel.org, linux-clk@vger.kernel.org, rust-for-linux@vger.kernel.org, Daniel Almeida X-Mailer: b4 0.14.3 The current Clk abstraction can still be improved on the following issues: a) It only keeps track of a count to clk_get(), which means that users have to manually call disable() and unprepare(), or a variation of those, like disable_unprepare(). b) It allows repeated calls to prepare() or enable(), but it keeps no track of how often these were called, i.e., it's currently legal to write the following: clk.prepare(); clk.prepare(); clk.enable(); clk.enable(); And nothing gets undone on drop(). c) It adds a OptionalClk type that is probably not needed. There is no "struct optional_clk" in C and we should probably not add one. d) It does not let a user express the state of the clk through the type system. For example, there is currently no way to encode that a Clk is enabled via the type system alone. In light of the Regulator abstraction that was recently merged, switch this abstraction to use the type-state pattern instead. It solves both a) and b) by establishing a number of states and the valid ways to transition between them. It also automatically undoes any call to clk_get(), clk_prepare() and clk_enable() as applicable on drop(), so users do not have to do anything special before Clk goes out of scope. It solves c) by removing the OptionalClk type, which is now simply encoded as a Clk whose inner pointer is NULL. It solves d) by directly encoding the state of the Clk into the type, e.g.: Clk is now known to be a Clk that is enabled. The INVARIANTS section for Clk is expanded to highlight the relationship between the states and the respective reference counts that are owned by each of them. The examples are expanded to highlight how a user can transition between states, as well as highlight some of the shortcuts built into the API. The current implementation is also more flexible, in the sense that it allows for more states to be added in the future. This lets us implement different strategies for handling clocks, including one that mimics the current API, allowing for multiple calls to prepare() and enable(). The users (cpufreq.rs/ rcpufreq_dt.rs) were updated by this patch (and not a separate one) to reflect the new changes. This is needed, because otherwise this patch would break the build. Link: https://crates.io/crates/sealed [1] Signed-off-by: Daniel Almeida --- drivers/cpufreq/rcpufreq_dt.rs | 2 +- drivers/gpu/drm/tyr/driver.rs | 31 +--- drivers/pwm/pwm_th1520.rs | 17 +- rust/kernel/clk.rs | 399 +++++++++++++++++++++++++++----------= ---- rust/kernel/cpufreq.rs | 8 +- 5 files changed, 281 insertions(+), 176 deletions(-) diff --git a/drivers/cpufreq/rcpufreq_dt.rs b/drivers/cpufreq/rcpufreq_dt.rs index 31e07f0279db..f1bd7d71ed54 100644 --- a/drivers/cpufreq/rcpufreq_dt.rs +++ b/drivers/cpufreq/rcpufreq_dt.rs @@ -41,7 +41,7 @@ struct CPUFreqDTDevice { freq_table: opp::FreqTable, _mask: CpumaskVar, _token: Option, - _clk: Clk, + _clk: Clk, } =20 #[derive(Default)] diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs index 09711fb7fe0b..5692def25621 100644 --- a/drivers/gpu/drm/tyr/driver.rs +++ b/drivers/gpu/drm/tyr/driver.rs @@ -2,7 +2,7 @@ =20 use kernel::c_str; use kernel::clk::Clk; -use kernel::clk::OptionalClk; +use kernel::clk::Enabled; use kernel::device::Bound; use kernel::device::Core; use kernel::device::Device; @@ -37,7 +37,7 @@ pub(crate) struct TyrDriver { device: ARef, } =20 -#[pin_data(PinnedDrop)] +#[pin_data] pub(crate) struct TyrData { pub(crate) pdev: ARef, =20 @@ -92,13 +92,9 @@ fn probe( pdev: &platform::Device, _info: Option<&Self::IdInfo>, ) -> impl PinInit { - let core_clk =3D Clk::get(pdev.as_ref(), Some(c_str!("core")))?; - let stacks_clk =3D OptionalClk::get(pdev.as_ref(), Some(c_str!("st= acks")))?; - let coregroup_clk =3D OptionalClk::get(pdev.as_ref(), Some(c_str!(= "coregroup")))?; - - core_clk.prepare_enable()?; - stacks_clk.prepare_enable()?; - coregroup_clk.prepare_enable()?; + let core_clk =3D Clk::::get(pdev.as_ref(), Some(c_str!("c= ore")))?; + let stacks_clk =3D Clk::::get_optional(pdev.as_ref(), Som= e(c_str!("stacks")))?; + let coregroup_clk =3D Clk::::get_optional(pdev.as_ref(), = Some(c_str!("coregroup")))?; =20 let mali_regulator =3D Regulator::::get(pdev.a= s_ref(), c_str!("mali"))?; let sram_regulator =3D Regulator::::get(pdev.a= s_ref(), c_str!("sram"))?; @@ -145,17 +141,6 @@ impl PinnedDrop for TyrDriver { fn drop(self: Pin<&mut Self>) {} } =20 -#[pinned_drop] -impl PinnedDrop for TyrData { - fn drop(self: Pin<&mut Self>) { - // TODO: the type-state pattern for Clks will fix this. - let clks =3D self.clks.lock(); - clks.core.disable_unprepare(); - clks.stacks.disable_unprepare(); - clks.coregroup.disable_unprepare(); - } -} - // We need to retain the name "panthor" to achieve drop-in compatibility w= ith // the C driver in the userspace stack. const INFO: drm::DriverInfo =3D drm::DriverInfo { @@ -181,9 +166,9 @@ impl drm::Driver for TyrDriver { =20 #[pin_data] struct Clocks { - core: Clk, - stacks: OptionalClk, - coregroup: OptionalClk, + core: Clk, + stacks: Clk, + coregroup: Clk, } =20 #[pin_data] diff --git a/drivers/pwm/pwm_th1520.rs b/drivers/pwm/pwm_th1520.rs index 043dc4dbc623..f4d03b988533 100644 --- a/drivers/pwm/pwm_th1520.rs +++ b/drivers/pwm/pwm_th1520.rs @@ -23,7 +23,7 @@ use core::ops::Deref; use kernel::{ c_str, - clk::Clk, + clk::{Clk, Enabled}, device::{Bound, Core, Device}, devres, io::mem::IoMem, @@ -90,11 +90,11 @@ struct Th1520WfHw { } =20 /// The driver's private data struct. It holds all necessary devres manage= d resources. -#[pin_data(PinnedDrop)] +#[pin_data] struct Th1520PwmDriverData { #[pin] iomem: devres::Devres>, - clk: Clk, + clk: Clk, } =20 impl pwm::PwmOps for Th1520PwmDriverData { @@ -299,13 +299,6 @@ fn write_waveform( } } =20 -#[pinned_drop] -impl PinnedDrop for Th1520PwmDriverData { - fn drop(self: Pin<&mut Self>) { - self.clk.disable_unprepare(); - } -} - struct Th1520PwmPlatformDriver; =20 kernel::of_device_table!( @@ -326,9 +319,7 @@ fn probe( let dev =3D pdev.as_ref(); let request =3D pdev.io_request_by_index(0).ok_or(ENODEV)?; =20 - let clk =3D Clk::get(dev, None)?; - - clk.prepare_enable()?; + let clk =3D Clk::::get(dev, None)?; =20 // 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. diff --git a/rust/kernel/clk.rs b/rust/kernel/clk.rs index d192fbd97861..6323b40dc7ba 100644 --- a/rust/kernel/clk.rs +++ b/rust/kernel/clk.rs @@ -80,17 +80,78 @@ fn from(freq: Hertz) -> Self { mod common_clk { use super::Hertz; use crate::{ - device::Device, + device::{Bound, Device}, error::{from_err_ptr, to_result, Result}, prelude::*, }; =20 - use core::{ops::Deref, ptr}; + use core::{marker::PhantomData, mem::ManuallyDrop, ptr}; + + mod private { + pub trait Sealed {} + + impl Sealed for super::Unprepared {} + impl Sealed for super::Prepared {} + impl Sealed for super::Enabled {} + } + + /// A trait representing the different states that a [`Clk`] can be in. + pub trait ClkState: private::Sealed { + /// Whether the clock should be disabled when dropped. + const DISABLE_ON_DROP: bool; + + /// Whether the clock should be unprepared when dropped. + const UNPREPARE_ON_DROP: bool; + } + + /// A state where the [`Clk`] is not prepared and not enabled. + pub struct Unprepared; + + /// A state where the [`Clk`] is prepared but not enabled. + pub struct Prepared; + + /// A state where the [`Clk`] is both prepared and enabled. + pub struct Enabled; + + impl ClkState for Unprepared { + const DISABLE_ON_DROP: bool =3D false; + const UNPREPARE_ON_DROP: bool =3D false; + } + + impl ClkState for Prepared { + const DISABLE_ON_DROP: bool =3D false; + const UNPREPARE_ON_DROP: bool =3D true; + } + + impl ClkState for Enabled { + const DISABLE_ON_DROP: bool =3D true; + const UNPREPARE_ON_DROP: bool =3D true; + } + + /// An error that can occur when trying to convert a [`Clk`] between s= tates. + pub struct Error { + /// The error that occurred. + pub error: kernel::error::Error, + + /// The [`Clk`] that caused the error, so that the operation may be + /// retried. + pub clk: Clk, + } =20 /// A reference-counted clock. /// /// Rust abstraction for the C [`struct clk`]. /// + /// A [`Clk`] instance represents a clock that can be in one of several + /// states: [`Unprepared`], [`Prepared`], or [`Enabled`]. + /// + /// No action needs to be taken when a [`Clk`] is dropped. The calls to + /// `clk_unprepare()` and `clk_disable()` will be placed as applicable. + /// + /// An optional [`Clk`] is treated just like a regular [`Clk`], but its + /// inner `struct clk` pointer is `NULL`. This interfaces correctly wi= th the + /// C API and also exposes all the methods of a regular [`Clk`] to use= rs. + /// /// # Invariants /// /// A [`Clk`] instance holds either a pointer to a valid [`struct clk`= ] created by the C @@ -99,20 +160,39 @@ mod common_clk { /// Instances of this type are reference-counted. Calling [`Clk::get`]= ensures that the /// allocation remains valid for the lifetime of the [`Clk`]. /// + /// The [`Prepared`] state is associated with a single count of + /// `clk_prepare()`, and the [`Enabled`] state is associated with a si= ngle + /// count of `clk_enable()`, and the [`Prepared`] state is associated = with a + /// single count of `clk_prepare()` and `clk_enable()`. + /// + /// All states are associated with a single count of `clk_get()`. + /// /// # Examples /// /// The following example demonstrates how to obtain and configure a c= lock for a device. /// /// ``` /// use kernel::c_str; - /// use kernel::clk::{Clk, Hertz}; - /// use kernel::device::Device; + /// use kernel::clk::{Clk, Enabled, Hertz, Unprepared, Prepared}; + /// use kernel::device::{Bound, Device}; /// use kernel::error::Result; /// - /// fn configure_clk(dev: &Device) -> Result { - /// let clk =3D Clk::get(dev, Some(c_str!("apb_clk")))?; + /// fn configure_clk(dev: &Device) -> Result { + /// // The fastest way is to use a version of `Clk::get` for the d= esired + /// // state, i.e.: + /// let clk: Clk =3D Clk::::get(dev, Some(c_str!= ("apb_clk")))?; /// - /// clk.prepare_enable()?; + /// // Any other state is also possible, e.g.: + /// let clk: Clk =3D Clk::::get(dev, Some(c_st= r!("apb_clk")))?; + /// + /// // Later: + /// let clk: Clk =3D clk.enable().map_err(|error| { + /// error.error + /// })?; + /// + /// // Note that error.clk is the original `clk` if the operation + /// // failed. It is provided as a convenience so that the operati= on may be + /// // retried in case of errors. /// /// let expected_rate =3D Hertz::from_ghz(1); /// @@ -120,111 +200,208 @@ mod common_clk { /// clk.set_rate(expected_rate)?; /// } /// - /// clk.disable_unprepare(); + /// // Nothing is needed here. The drop implementation will undo a= ny + /// // operations as appropriate. + /// Ok(()) + /// } + /// + /// fn shutdown(clk: Clk) -> Result { + /// // The states can be traversed "in the reverse order" as well: + /// let clk: Clk =3D clk.disable().map_err(|error| { + /// error.error + /// })?; + /// + /// // This is of type `Clk`. + /// let clk =3D clk.unprepare(); + /// /// Ok(()) /// } /// ``` /// /// [`struct clk`]: https://docs.kernel.org/driver-api/clk.html #[repr(transparent)] - pub struct Clk(*mut bindings::clk); + pub struct Clk { + inner: *mut bindings::clk, + _phantom: core::marker::PhantomData, + } =20 // SAFETY: It is safe to call `clk_put` on another thread than where `= clk_get` was called. - unsafe impl Send for Clk {} + unsafe impl Send for Clk {} =20 // SAFETY: It is safe to call any combination of the `&self` methods i= n parallel, as the // methods are synchronized internally. - unsafe impl Sync for Clk {} + unsafe impl Sync for Clk {} =20 - impl Clk { - /// Gets [`Clk`] corresponding to a [`Device`] and a connection id. + impl Clk { + /// Gets [`Clk`] corresponding to a bound [`Device`] and a connect= ion + /// id. /// /// Equivalent to the kernel's [`clk_get`] API. /// /// [`clk_get`]: https://docs.kernel.org/core-api/kernel-api.html#= c.clk_get - pub fn get(dev: &Device, name: Option<&CStr>) -> Result { + #[inline] + pub fn get(dev: &Device, name: Option<&CStr>) -> Result> { let con_id =3D name.map_or(ptr::null(), |n| n.as_char_ptr()); =20 - // SAFETY: It is safe to call [`clk_get`] for a valid device p= ointer. - // + // SAFETY: It is safe to call [`clk_get`] for a valid device p= ointer + // and any `con_id`, including NULL. + let inner =3D from_err_ptr(unsafe { bindings::clk_get(dev.as_r= aw(), con_id) })?; + // INVARIANT: The reference-count is decremented when [`Clk`] = goes out of scope. - Ok(Self(from_err_ptr(unsafe { - bindings::clk_get(dev.as_raw(), con_id) - })?)) + Ok(Self { + inner, + _phantom: PhantomData, + }) } =20 - /// Obtain the raw [`struct clk`] pointer. + /// Behaves the same as [`Self::get`], except when there is no clo= ck + /// producer. In this case, instead of returning [`ENOENT`], it re= turns + /// a dummy [`Clk`]. #[inline] - pub fn as_raw(&self) -> *mut bindings::clk { - self.0 + pub fn get_optional(dev: &Device, name: Option<&CStr>) -> R= esult> { + let con_id =3D name.map_or(ptr::null(), |n| n.as_char_ptr()); + + // SAFETY: It is safe to call [`clk_get`] for a valid device p= ointer + // and any `con_id`, including NULL. + let inner =3D from_err_ptr(unsafe { bindings::clk_get_optional= (dev.as_raw(), con_id) })?; + + // INVARIANT: The reference-count is decremented when [`Clk`] = goes out of scope. + Ok(Self { + inner, + _phantom: PhantomData, + }) } =20 - /// Enable the clock. + /// Attempts to convert the [`Clk`] to a [`Prepared`] state. /// - /// Equivalent to the kernel's [`clk_enable`] API. + /// Equivalent to the kernel's [`clk_prepare`] API. /// - /// [`clk_enable`]: https://docs.kernel.org/core-api/kernel-api.ht= ml#c.clk_enable + /// [`clk_prepare`]: https://docs.kernel.org/core-api/kernel-api.h= tml#c.clk_prepare #[inline] - pub fn enable(&self) -> Result { - // SAFETY: By the type invariants, self.as_raw() is a valid ar= gument for - // [`clk_enable`]. - to_result(unsafe { bindings::clk_enable(self.as_raw()) }) + pub fn prepare(self) -> Result, Error> { + // We will be transferring the ownership of our `clk_get()` co= unt to + // `Clk`. + let clk =3D ManuallyDrop::new(self); + + // SAFETY: By the type invariants, `self.0` is a valid argumen= t for + // [`clk_prepare`]. + to_result(unsafe { bindings::clk_prepare(clk.as_raw()) }) + .map(|()| Clk { + inner: clk.inner, + _phantom: PhantomData, + }) + .map_err(|error| Error { + error, + clk: ManuallyDrop::into_inner(clk), + }) } + } =20 - /// Disable the clock. - /// - /// Equivalent to the kernel's [`clk_disable`] API. + impl Clk { + /// Obtains a [`Clk`] from a bound [`Device`] and a connection id = and + /// prepares it. /// - /// [`clk_disable`]: https://docs.kernel.org/core-api/kernel-api.h= tml#c.clk_disable + /// Equivalent to calling [`Clk::get`], followed by [`Clk::prepare= `], #[inline] - pub fn disable(&self) { - // SAFETY: By the type invariants, self.as_raw() is a valid ar= gument for - // [`clk_disable`]. - unsafe { bindings::clk_disable(self.as_raw()) }; + pub fn get(dev: &Device, name: Option<&CStr>) -> Result> { + Clk::::get(dev, name)? + .prepare() + .map_err(|error| error.error) } =20 - /// Prepare the clock. - /// - /// Equivalent to the kernel's [`clk_prepare`] API. - /// - /// [`clk_prepare`]: https://docs.kernel.org/core-api/kernel-api.h= tml#c.clk_prepare + /// Behaves the same as [`Self::get`], except when there is no clo= ck + /// producer. In this case, instead of returning [`ENOENT`], it re= turns + /// a dummy [`Clk`]. #[inline] - pub fn prepare(&self) -> Result { - // SAFETY: By the type invariants, self.as_raw() is a valid ar= gument for - // [`clk_prepare`]. - to_result(unsafe { bindings::clk_prepare(self.as_raw()) }) + pub fn get_optional(dev: &Device, name: Option<&CStr>) -> R= esult> { + Clk::::get_optional(dev, name)? + .prepare() + .map_err(|error| error.error) } =20 - /// Unprepare the clock. + /// Attempts to convert the [`Clk`] to an [`Unprepared`] state. /// /// Equivalent to the kernel's [`clk_unprepare`] API. - /// - /// [`clk_unprepare`]: https://docs.kernel.org/core-api/kernel-api= .html#c.clk_unprepare #[inline] - pub fn unprepare(&self) { - // SAFETY: By the type invariants, self.as_raw() is a valid ar= gument for + pub fn unprepare(self) -> Clk { + // We will be transferring the ownership of our `clk_get()` co= unt to + // `Clk`. + let clk =3D ManuallyDrop::new(self); + + // SAFETY: By the type invariants, `self.0` is a valid argumen= t for // [`clk_unprepare`]. - unsafe { bindings::clk_unprepare(self.as_raw()) }; + unsafe { bindings::clk_unprepare(clk.as_raw()) } + + Clk { + inner: clk.inner, + _phantom: PhantomData, + } } =20 - /// Prepare and enable the clock. + /// Attempts to convert the [`Clk`] to an [`Enabled`] state. + /// + /// Equivalent to the kernel's [`clk_enable`] API. /// - /// Equivalent to calling [`Clk::prepare`] followed by [`Clk::enab= le`]. + /// [`clk_enable`]: https://docs.kernel.org/core-api/kernel-api.ht= ml#c.clk_enable #[inline] - pub fn prepare_enable(&self) -> Result { - // SAFETY: By the type invariants, self.as_raw() is a valid ar= gument for - // [`clk_prepare_enable`]. - to_result(unsafe { bindings::clk_prepare_enable(self.as_raw())= }) + pub fn enable(self) -> Result, Error> { + // We will be transferring the ownership of our `clk_get()` and + // `clk_prepare()` counts to `Clk`. + let clk =3D ManuallyDrop::new(self); + + // SAFETY: By the type invariants, `self.0` is a valid argumen= t for + // [`clk_enable`]. + to_result(unsafe { bindings::clk_enable(clk.as_raw()) }) + .map(|()| Clk { + inner: clk.inner, + _phantom: PhantomData, + }) + .map_err(|error| Error { + error, + clk: ManuallyDrop::into_inner(clk), + }) } + } =20 - /// Disable and unprepare the clock. + impl Clk { + /// Gets [`Clk`] corresponding to a bound [`Device`] and a connect= ion id + /// and then prepares and enables it. /// - /// Equivalent to calling [`Clk::disable`] followed by [`Clk::unpr= epare`]. + /// Equivalent to calling [`Clk::get`], followed by [`Clk::prepare= `], + /// followed by [`Clk::enable`]. #[inline] - pub fn disable_unprepare(&self) { - // SAFETY: By the type invariants, self.as_raw() is a valid ar= gument for - // [`clk_disable_unprepare`]. - unsafe { bindings::clk_disable_unprepare(self.as_raw()) }; + pub fn get(dev: &Device, name: Option<&CStr>) -> Result> { + Clk::::get(dev, name)? + .enable() + .map_err(|error| error.error) + } + + /// Behaves the same as [`Self::get`], except when there is no clo= ck + /// producer. In this case, instead of returning [`ENOENT`], it re= turns + /// a dummy [`Clk`]. + #[inline] + pub fn get_optional(dev: &Device, name: Option<&CStr>) -> R= esult> { + Clk::::get_optional(dev, name)? + .enable() + .map_err(|error| error.error) + } + + /// Attempts to disable the [`Clk`] and convert it to the [`Prepar= ed`] + /// state. + #[inline] + pub fn disable(self) -> Result, Error> { + // We will be transferring the ownership of our `clk_get()` and + // `clk_enable()` counts to `Clk`. + let clk =3D ManuallyDrop::new(self); + + // SAFETY: By the type invariants, `self.0` is a valid argumen= t for + // [`clk_disable`]. + unsafe { bindings::clk_disable(clk.as_raw()) }; + + Ok(Clk { + inner: clk.inner, + _phantom: PhantomData, + }) } =20 /// Get clock's rate. @@ -252,83 +429,31 @@ pub fn set_rate(&self, rate: Hertz) -> Result { } } =20 - impl Drop for Clk { - fn drop(&mut self) { - // SAFETY: By the type invariants, self.as_raw() is a valid ar= gument for [`clk_put`]. - unsafe { bindings::clk_put(self.as_raw()) }; - } - } - - /// A reference-counted optional clock. - /// - /// A lightweight wrapper around an optional [`Clk`]. An [`OptionalClk= `] represents a [`Clk`] - /// that a driver can function without but may improve performance or = enable additional - /// features when available. - /// - /// # Invariants - /// - /// An [`OptionalClk`] instance encapsulates a [`Clk`] with either a v= alid [`struct clk`] or - /// `NULL` pointer. - /// - /// Instances of this type are reference-counted. Calling [`OptionalCl= k::get`] ensures that the - /// allocation remains valid for the lifetime of the [`OptionalClk`]. - /// - /// # Examples - /// - /// The following example demonstrates how to obtain and configure an = optional clock for a - /// device. The code functions correctly whether or not the clock is a= vailable. - /// - /// ``` - /// use kernel::c_str; - /// use kernel::clk::{OptionalClk, Hertz}; - /// use kernel::device::Device; - /// use kernel::error::Result; - /// - /// fn configure_clk(dev: &Device) -> Result { - /// let clk =3D OptionalClk::get(dev, Some(c_str!("apb_clk")))?; - /// - /// clk.prepare_enable()?; - /// - /// let expected_rate =3D Hertz::from_ghz(1); - /// - /// if clk.rate() !=3D expected_rate { - /// clk.set_rate(expected_rate)?; - /// } - /// - /// clk.disable_unprepare(); - /// Ok(()) - /// } - /// ``` - /// - /// [`struct clk`]: https://docs.kernel.org/driver-api/clk.html - pub struct OptionalClk(Clk); - - impl OptionalClk { - /// Gets [`OptionalClk`] corresponding to a [`Device`] and a conne= ction id. - /// - /// Equivalent to the kernel's [`clk_get_optional`] API. - /// - /// [`clk_get_optional`]: - /// https://docs.kernel.org/core-api/kernel-api.html#c.clk_get_opt= ional - pub fn get(dev: &Device, name: Option<&CStr>) -> Result { - let con_id =3D name.map_or(ptr::null(), |n| n.as_char_ptr()); - - // SAFETY: It is safe to call [`clk_get_optional`] for a valid= device pointer. - // - // INVARIANT: The reference-count is decremented when [`Option= alClk`] goes out of - // scope. - Ok(Self(Clk(from_err_ptr(unsafe { - bindings::clk_get_optional(dev.as_raw(), con_id) - })?))) + impl Clk { + /// Obtain the raw [`struct clk`] pointer. + #[inline] + pub fn as_raw(&self) -> *mut bindings::clk { + self.inner } } =20 - // Make [`OptionalClk`] behave like [`Clk`]. - impl Deref for OptionalClk { - type Target =3D Clk; + impl Drop for Clk { + fn drop(&mut self) { + if T::DISABLE_ON_DROP { + // SAFETY: By the type invariants, self.as_raw() is a vali= d argument for + // [`clk_disable`]. + unsafe { bindings::clk_disable(self.as_raw()) }; + } + + if T::UNPREPARE_ON_DROP { + // SAFETY: By the type invariants, self.as_raw() is a vali= d argument for + // [`clk_unprepare`]. + unsafe { bindings::clk_unprepare(self.as_raw()) }; + } =20 - fn deref(&self) -> &Clk { - &self.0 + // SAFETY: By the type invariants, self.as_raw() is a valid ar= gument for + // [`clk_put`]. + unsafe { bindings::clk_put(self.as_raw()) }; } } } diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs index f968fbd22890..d51c697cad9e 100644 --- a/rust/kernel/cpufreq.rs +++ b/rust/kernel/cpufreq.rs @@ -552,8 +552,12 @@ pub fn cpus(&mut self) -> &mut cpumask::Cpumask { /// The caller must guarantee that the returned [`Clk`] is not dropped= while it is getting used /// by the C code. #[cfg(CONFIG_COMMON_CLK)] - pub unsafe fn set_clk(&mut self, dev: &Device, name: Option<&CStr>) ->= Result { - let clk =3D Clk::get(dev, name)?; + pub unsafe fn set_clk( + &mut self, + dev: &Device, + name: Option<&CStr>, + ) -> Result> { + let clk =3D Clk::::get(dev, name)?; self.as_mut_ref().clk =3D clk.as_raw(); Ok(clk) } --=20 2.52.0 From nobody Sun Feb 8 01:51:50 2026 Received: from bali.collaboradmins.com (bali.collaboradmins.com [148.251.105.195]) (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 852A836403C; Wed, 7 Jan 2026 15:10:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.251.105.195 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767798612; cv=none; b=sw3mYKFd6COqyHgNg6xWsd4hFTy9R4Aay3ApB/ILjfYnX8wCHRdZ6wBkniyxOc5NxEArFcYsdQESTipOuEgPpY0m29kXmZ4wdcvlxj5V4HuRPGMpiE393ir+brnzujvCGGIQrhExSDjUffjo04sWw5aiKjNpf7+gFbuUTvK1yk8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767798612; c=relaxed/simple; bh=90di0w1PwxPIRzuSClWm8hTlTurueeQPrjHhHA1IgeE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Lr+CpMhW1a+OY2t5pf2yTBL9mZjZMO5gYCpsDxlCP68xj2UY8LxyVbTTeQeapxwfXOGxFlrjg9POunI+e8ym/ukQrYwUIF6/sWe28VCf+L3NDgpT7oeLXp+lZ6t1KT5JttyXbOPSGpfMbCdyUGlQPTB5gEG3thyASDItwgJRi7U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=collabora.com; spf=pass smtp.mailfrom=collabora.com; dkim=pass (2048-bit key) header.d=collabora.com header.i=@collabora.com header.b=WbxUGimf; arc=none smtp.client-ip=148.251.105.195 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=collabora.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=collabora.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=collabora.com header.i=@collabora.com header.b="WbxUGimf" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1767798608; bh=90di0w1PwxPIRzuSClWm8hTlTurueeQPrjHhHA1IgeE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=WbxUGimfzP1F2xdrdKe/4cd+wlHBTKhKTGbIJq3+nq4YJ2hsGZfl7PcRgQ4RGiBs1 dtSuohh7qCBvMt0aJuuvXZfkQTiaG4QDZ9nx9ak+XP+t/JRMwRc+9iCl5jI/diqDNK dLqasMDWtpSjOnCaPPPKpS/agyuMomX7YkrtoFWONOXCLUgvmzyl/n8cmA2SJNpkX5 GeJSvxuSN4GhvqLeXEOdJnjSEcxCU0LxaUjx+hQSbqBzrZrppwCiz9bvJBwLCAFWiX w/tYrDOBR7aAWG6Ydn2+yoQJp0lXhTUYV09eF92e5fEmZ7M1Y81NwfByPFfBig9Pzy dv/MMlIrcuw8A== Received: from [192.168.0.79] (unknown [IPv6:2804:14d:72b4:81ae:67c:16ff:fe57:b5a3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: dwlsalmeida) by bali.collaboradmins.com (Postfix) with ESMTPSA id 75C9117E1529; Wed, 7 Jan 2026 16:10:04 +0100 (CET) From: Daniel Almeida Date: Wed, 07 Jan 2026 12:09:53 -0300 Subject: [PATCH v3 2/3] rust: clk: add devres-managed clks Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260107-clk-type-state-v3-2-77d3e3ee59c2@collabora.com> References: <20260107-clk-type-state-v3-0-77d3e3ee59c2@collabora.com> In-Reply-To: <20260107-clk-type-state-v3-0-77d3e3ee59c2@collabora.com> To: "Rafael J. Wysocki" , Viresh Kumar , Danilo Krummrich , Alice Ryhl , Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , David Airlie , Simona Vetter , Drew Fustini , Guo Ren , Fu Wei , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Michael Turquette , Stephen Boyd , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross Cc: linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-riscv@lists.infradead.org, linux-pwm@vger.kernel.org, linux-clk@vger.kernel.org, rust-for-linux@vger.kernel.org, Daniel Almeida X-Mailer: b4 0.14.3 The clk API allows fine-grained control, but some drivers might be more interested in a "set and forget" API. Expand the current API to support this. The clock will automatically be disabled, unprepared and freed when the device is unbound from the bus without further intervention by the driver. Signed-off-by: Daniel Almeida Reviewed-by: Gary Guo --- rust/kernel/clk.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/rust/kernel/clk.rs b/rust/kernel/clk.rs index 6323b40dc7ba..e840e7c20af7 100644 --- a/rust/kernel/clk.rs +++ b/rust/kernel/clk.rs @@ -95,6 +95,49 @@ impl Sealed for super::Prepared {} impl Sealed for super::Enabled {} } =20 + /// Obtains and enables a [`devres`]-managed [`Clk`] for a bound devic= e. + /// + /// [`devres`]: crate::devres::Devres + pub fn devm_enable(dev: &Device, name: Option<&CStr>) -> Result= { + let name =3D name.map_or(ptr::null(), |n| n.as_char_ptr()); + + // SAFETY: It is safe to call [`devm_clk_get_enabled`] with a valid + // device pointer. + from_err_ptr(unsafe { bindings::devm_clk_get_enabled(dev.as_raw(),= name) })?; + Ok(()) + } + + /// Obtains and enables a [`devres`]-managed [`Clk`] for a bound devic= e. + /// + /// This does not print any error messages if the clock is not found. + /// + /// [`devres`]: crate::devres::Devres + pub fn devm_enable_optional(dev: &Device, name: Option<&CStr>) = -> Result { + let name =3D name.map_or(ptr::null(), |n| n.as_char_ptr()); + + // SAFETY: It is safe to call [`devm_clk_get_optional_enabled`] wi= th a + // valid device pointer. + from_err_ptr(unsafe { bindings::devm_clk_get_optional_enabled(dev.= as_raw(), name) })?; + Ok(()) + } + + /// Same as [`devm_enable_optional`], but also sets the rate. + pub fn devm_enable_optional_with_rate( + dev: &Device, + name: Option<&CStr>, + rate: Hertz, + ) -> Result { + let name =3D name.map_or(ptr::null(), |n| n.as_char_ptr()); + + // SAFETY: It is safe to call + // [`devm_clk_get_optional_enabled_with_rate`] with a valid device + // pointer. + from_err_ptr(unsafe { + bindings::devm_clk_get_optional_enabled_with_rate(dev.as_raw()= , name, rate.as_hz()) + })?; + Ok(()) + } + /// A trait representing the different states that a [`Clk`] can be in. pub trait ClkState: private::Sealed { /// Whether the clock should be disabled when dropped. --=20 2.52.0 From nobody Sun Feb 8 01:51:50 2026 Received: from bali.collaboradmins.com (bali.collaboradmins.com [148.251.105.195]) (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 2944036403A; Wed, 7 Jan 2026 15:10:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.251.105.195 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767798617; cv=none; b=ngBuqc4R6NHFPiIMHJgNYeM6mwJ/VB8fy7jtk4lsKFQZJfmtMsDwZrrD2XdSuo9hPpoi+0RPyegLhnj3vNwqrPUzt08J94g0AYgz06fPr0qKFZ2NGNhaU3KdEIA0oPLZuuo9eZUC/L8aPnT2TXMeFNhHdg98EKZsIAcYdvu1uBQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767798617; c=relaxed/simple; bh=ruLJwy622onfjhp6xw4ADLI848biIUuAYSyoTpnglgk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=EXZU1um8PsGdkyqdrILpaLcRPp3tqKVoBLLPBgBiqz8rynZICKJBWf+IULPFXTtFitF1KxMZYoWVg7O/20Dk7VvE4D5+zK3yBG8jRtKmJKR4QyPV3BQLp4VHwDgMETTU+QYTHiHR9nrprEemY6gTLpJIlBFk/HyzEeFtOI5TVBQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=collabora.com; spf=pass smtp.mailfrom=collabora.com; dkim=pass (2048-bit key) header.d=collabora.com header.i=@collabora.com header.b=Ns//fbuH; arc=none smtp.client-ip=148.251.105.195 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=collabora.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=collabora.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=collabora.com header.i=@collabora.com header.b="Ns//fbuH" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1767798613; bh=ruLJwy622onfjhp6xw4ADLI848biIUuAYSyoTpnglgk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Ns//fbuHmUm/HgngBvDsWHryUZciMHDGToh4HmqboY21nv7PgA6YRLVzqwc/ussOs ZQP3qHnEY7LqDX37RDggfwg/Ml0FDGY0sMDUIiqk025dyeZ/kBFFdFq1l/kvUm0b5r WXGEzLK7OW0bpfwKe3UVLwAfAsO6VZFPBNuX15y7DalJk+OIispsFWxNJqFoIgCif8 5BkFdTvV+ZVCAekmzuVxH/MuWSigA7mpmnDmERHcXxWk+noVZu12+Q28ujpxY4BxnW pfNz18biBjrIzEvESliCT7I1uNocZbY439hV4UveGeKp3PvIvaxLxcSIUtDiznvijR 6j8wQDyLu0zfA== Received: from [192.168.0.79] (unknown [IPv6:2804:14d:72b4:81ae:67c:16ff:fe57:b5a3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: dwlsalmeida) by bali.collaboradmins.com (Postfix) with ESMTPSA id 2D03E17E1534; Wed, 7 Jan 2026 16:10:09 +0100 (CET) From: Daniel Almeida Date: Wed, 07 Jan 2026 12:09:54 -0300 Subject: [PATCH v3 3/3] rust: clk: use 'kernel vertical style' for imports Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260107-clk-type-state-v3-3-77d3e3ee59c2@collabora.com> References: <20260107-clk-type-state-v3-0-77d3e3ee59c2@collabora.com> In-Reply-To: <20260107-clk-type-state-v3-0-77d3e3ee59c2@collabora.com> To: "Rafael J. Wysocki" , Viresh Kumar , Danilo Krummrich , Alice Ryhl , Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , David Airlie , Simona Vetter , Drew Fustini , Guo Ren , Fu Wei , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Michael Turquette , Stephen Boyd , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross Cc: linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-riscv@lists.infradead.org, linux-pwm@vger.kernel.org, linux-clk@vger.kernel.org, rust-for-linux@vger.kernel.org, Daniel Almeida X-Mailer: b4 0.14.3 Convert all imports to use the new import style. This will make it easier to land new changes in the future. No change of functionality implied. Link: https://docs.kernel.org/rust/coding-guidelines.html#imports Signed-off-by: Daniel Almeida Reviewed-by: Maxime Ripard --- rust/kernel/clk.rs | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/rust/kernel/clk.rs b/rust/kernel/clk.rs index e840e7c20af7..73a2b51414a1 100644 --- a/rust/kernel/clk.rs +++ b/rust/kernel/clk.rs @@ -80,12 +80,23 @@ fn from(freq: Hertz) -> Self { mod common_clk { use super::Hertz; use crate::{ - device::{Bound, Device}, - error::{from_err_ptr, to_result, Result}, - prelude::*, + device::{ + Bound, + Device, // + }, + error::{ + from_err_ptr, + to_result, + Result, // + }, + prelude::*, // }; =20 - use core::{marker::PhantomData, mem::ManuallyDrop, ptr}; + use core::{ + marker::PhantomData, + mem::ManuallyDrop, + ptr, // + }; =20 mod private { pub trait Sealed {} @@ -216,8 +227,17 @@ pub struct Error { /// /// ``` /// use kernel::c_str; - /// use kernel::clk::{Clk, Enabled, Hertz, Unprepared, Prepared}; - /// use kernel::device::{Bound, Device}; + /// use kernel::clk::{ + /// Clk, + /// Enabled, + /// Hertz, + /// Prepared, + /// Unprepared, // + /// }; + /// use kernel::device::{ + /// Bound, + /// Device, // + /// }; /// use kernel::error::Result; /// /// fn configure_clk(dev: &Device) -> Result { --=20 2.52.0