* Re: [RFC PATCH v1 4/4] rust: add PL031 RTC driver
From: Miguel Ojeda @ 2026-01-04 11:40 UTC (permalink / raw)
To: Ke Sun
Cc: Alexandre Belloni, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, linux-rtc, rust-for-linux,
Alvin Sun, Russell King
In-Reply-To: <20260104060621.3757812-5-sunke@kylinos.cn>
On Sun, Jan 4, 2026 at 7:07 AM Ke Sun <sunke@kylinos.cn> wrote:
>
> Add Rust implementation of the ARM AMBA PrimeCell 031 RTC driver.
> The driver uses the AMBA bus abstractions and RTC core framework
> introduced in previous commits.
Cc'ing AMBA (Russell) here as well, in case it is useful for context.
> + depends on RUST && RTC_CLASS && RUST_BUILD_ASSERT_ALLOW
> + This driver requires CONFIG_RUST_BUILD_ASSERT_ALLOW to be enabled
> + because it uses build-time assertions for memory safety checks.
While replying for the Cc, I noticed this...
One cannot require `CONFIG_RUST_BUILD_ASSERT_ALLOW`. If you need it,
then it is because some of the assertions are *not* true at build
time, in which case either you need to fix them so that the optimizer
can figure out that they are be true or, if they are intended to be
runtime assertions, then use something else (likely normal error
handling).
Put another way, one should always assume
`CONFIG_RUST_BUILD_ASSERT_ALLOW` does not exist, i.e. assume it has to
always be `n`. That config is just an escape hatch, and one can
consider it may get removed at any point.
Cheers,
Miguel
^ permalink raw reply
* Re: [RFC PATCH v1 1/4] rust: add AMBA bus abstractions
From: Miguel Ojeda @ 2026-01-04 11:37 UTC (permalink / raw)
To: Ke Sun
Cc: Alexandre Belloni, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, linux-rtc, rust-for-linux,
Alvin Sun, Russell King
In-Reply-To: <20260104060621.3757812-2-sunke@kylinos.cn>
On Sun, Jan 4, 2026 at 7:06 AM Ke Sun <sunke@kylinos.cn> wrote:
>
> Add Rust abstractions for the ARM AMBA bus, including:
> - Device type wrapper for amba_device
> - DeviceId for device matching
> - TryFrom implementation for converting device::Device to amba::Device
> - IRQ and I/O resource management methods
>
> Signed-off-by: Ke Sun <sunke@kylinos.cn>
Cc'ing AMBA (Russell) -- it is Odd Fixes but he should still be in
case he is interested.
Nice to see AMBA interest again -- it was one of the first, years ago
(https://github.com/Rust-for-Linux/linux/blob/rust/rust/kernel/amba.rs).
Cheers,
Miguel
^ permalink raw reply
* Re: [RFC PATCH v1 4/4] rust: add PL031 RTC driver
From: Dirk Behme @ 2026-01-04 9:02 UTC (permalink / raw)
To: Ke Sun, Alexandre Belloni, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich
Cc: linux-rtc, rust-for-linux, Alvin Sun, Joel Fernandes,
Alexandre Courbot
In-Reply-To: <20260104060621.3757812-5-sunke@kylinos.cn>
Hi,
reading this triggers two questions (discussion topics?) for me. They
are not specific to this driver but more general. Though they should
not block any progress or merging of this driver. I'd like to ask:
On 04.01.26 07:06, Ke Sun wrote:
> Add Rust implementation of the ARM AMBA PrimeCell 031 RTC driver.
>
> This driver supports:
> - ARM, ST v1, and ST v2 variants
> - Time read/write operations
> - Alarm read/write operations
> - Interrupt handling
> - Wake-up support
>
> The driver uses the AMBA bus abstractions and RTC core framework
> introduced in previous commits.
>
> Signed-off-by: Ke Sun <sunke@kylinos.cn>
> ---
> drivers/rtc/Kconfig | 11 +
> drivers/rtc/Makefile | 1 +
> drivers/rtc/rtc_pl031_rust.rs | 529 ++++++++++++++++++++++++++++++++++
> 3 files changed, 541 insertions(+)
> create mode 100644 drivers/rtc/rtc_pl031_rust.rs
>
> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> index 50dc779f7f983..c7ce188dcc5cf 100644
> --- a/drivers/rtc/Kconfig
> +++ b/drivers/rtc/Kconfig
> @@ -1591,6 +1591,17 @@ config RTC_DRV_PL031
> To compile this driver as a module, choose M here: the
> module will be called rtc-pl031.
>
> +config RTC_DRV_PL031_RUST
> + tristate "ARM AMBA PL031 RTC (Rust)"
> + depends on RUST && RTC_CLASS && RUST_BUILD_ASSERT_ALLOW
> + help
> + This is the Rust implementation of the PL031 RTC driver.
> + It provides the same functionality as the C driver but is
> + written in Rust for improved memory safety.
> +
> + This driver requires CONFIG_RUST_BUILD_ASSERT_ALLOW to be enabled
> + because it uses build-time assertions for memory safety checks.
> +
> config RTC_DRV_AT91RM9200
> tristate "AT91RM9200 or some AT91SAM9 RTC"
> depends on ARCH_AT91 || COMPILE_TEST
> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
> index 6cf7e066314e1..10f540e7409b4 100644
> --- a/drivers/rtc/Makefile
> +++ b/drivers/rtc/Makefile
> @@ -139,6 +139,7 @@ obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o
> obj-$(CONFIG_RTC_DRV_PIC32) += rtc-pic32.o
> obj-$(CONFIG_RTC_DRV_PL030) += rtc-pl030.o
> obj-$(CONFIG_RTC_DRV_PL031) += rtc-pl031.o
> +obj-$(CONFIG_RTC_DRV_PL031_RUST) += rtc_pl031_rust.o
> obj-$(CONFIG_RTC_DRV_PM8XXX) += rtc-pm8xxx.o
> obj-$(CONFIG_RTC_DRV_POLARFIRE_SOC) += rtc-mpfs.o
> obj-$(CONFIG_RTC_DRV_PS3) += rtc-ps3.o
> diff --git a/drivers/rtc/rtc_pl031_rust.rs b/drivers/rtc/rtc_pl031_rust.rs
> new file mode 100644
> index 0000000000000..c00a49c2bf94e
> --- /dev/null
> +++ b/drivers/rtc/rtc_pl031_rust.rs
> @@ -0,0 +1,529 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +//! Real Time Clock interface for ARM AMBA PrimeCell 031 RTC
> +//!
> +//! This is a Rust port of the C driver in rtc-pl031.c
> +//!
> +//! Author: Ke Sun <sunke@kylinos.cn>
> +//! Based on: drivers/rtc/rtc-pl031.c
> +
> +use core::ops::Deref;
> +use kernel::{
> + amba,
> + bindings,
> + c_str,
> + device,
> + devres::Devres,
> + error::code,
> + io::mem::IoMem,
> + irq::{
> + Handler,
> + IrqReturn, //
> + },
> + prelude::*,
> + rtc::{
> + self,
> + RtcDevice,
> + RtcDeviceOptions,
> + RtcOperations,
> + RtcTime,
> + RtcWkAlrm, //
> + },
> + sync::aref::ARef, //
> +};
> +
> +// Register definitions
> +const RTC_DR: usize = 0x00; // Data read register
> +const RTC_MR: usize = 0x04; // Match register
> +const RTC_LR: usize = 0x08; // Data load register
> +const RTC_CR: usize = 0x0c; // Control register
> +const RTC_IMSC: usize = 0x10; // Interrupt mask and set register
> +const RTC_RIS: usize = 0x14; // Raw interrupt status register
> +const RTC_MIS: usize = 0x18; // Masked interrupt status register
> +const RTC_ICR: usize = 0x1c; // Interrupt clear register
> +const RTC_YDR: usize = 0x30; // Year data read register
> +const RTC_YMR: usize = 0x34; // Year match register
> +const RTC_YLR: usize = 0x38; // Year data load register
> +
> +// Control register bits
> +const RTC_CR_EN: u32 = 1 << 0; // Counter enable bit
> +const RTC_CR_CWEN: u32 = 1 << 26; // Clockwatch enable bit
> +
> +// Interrupt status and control register bits
> +const RTC_BIT_AI: u32 = 1 << 0; // Alarm interrupt bit
I know that it is not ready yet and still in development, but what's
about the register!() macro [1] [2]?
Do we want a common register access style in Rust drivers and want to
encourge using the register macro for that? Or do we take what we have
at the moment (like here) and eventually convert later to the register
macro? Or not? Opinions?
CCing Joel and Alexandre regarding the register macro.
[1]
https://lore.kernel.org/rust-for-linux/20251003154748.1687160-5-joelagnelf@nvidia.com/
[2]
https://lore.kernel.org/rust-for-linux/DDDQZ8LM2OGP.VSEG03ZE0K04@kernel.org/
> +// RTC event flags
> +#[allow(dead_code)]
> +const RTC_AF: u32 = bindings::RTC_AF;
> +#[allow(dead_code)]
> +const RTC_IRQF: u32 = bindings::RTC_IRQF;
> +
> +// ST v2 time format bit definitions
> +const RTC_SEC_SHIFT: u32 = 0;
> +const RTC_SEC_MASK: u32 = 0x3F << RTC_SEC_SHIFT; // Second [0-59]
> +const RTC_MIN_SHIFT: u32 = 6;
> +const RTC_MIN_MASK: u32 = 0x3F << RTC_MIN_SHIFT; // Minute [0-59]
> +const RTC_HOUR_SHIFT: u32 = 12;
> +const RTC_HOUR_MASK: u32 = 0x1F << RTC_HOUR_SHIFT; // Hour [0-23]
> +const RTC_WDAY_SHIFT: u32 = 17;
> +const RTC_WDAY_MASK: u32 = 0x7 << RTC_WDAY_SHIFT; // Day of week [1-7], 1=Sunday
> +const RTC_MDAY_SHIFT: u32 = 20;
> +const RTC_MDAY_MASK: u32 = 0x1F << RTC_MDAY_SHIFT; // Day of month [1-31]
> +const RTC_MON_SHIFT: u32 = 25;
> +const RTC_MON_MASK: u32 = 0xF << RTC_MON_SHIFT; // Month [1-12], 1=January
> +
> +/// Vendor-specific data for different PL031 variants
> +#[derive(Copy, Clone, PartialEq)]
> +enum VendorVariant {
> + /// Original ARM version
> + Arm,
> + /// First ST derivative
> + StV1,
> + /// Second ST derivative
> + StV2,
> +}
> +
> +impl VendorVariant {
> + fn clockwatch(&self) -> bool {
> + matches!(self, VendorVariant::StV1 | VendorVariant::StV2)
> + }
> +
> + #[allow(dead_code)]
> + fn st_weekday(&self) -> bool {
> + matches!(self, VendorVariant::StV1 | VendorVariant::StV2)
> + }
> +
> + #[allow(dead_code)]
> + fn range_min(&self) -> i64 {
> + match self {
> + VendorVariant::Arm | VendorVariant::StV1 => 0,
> + VendorVariant::StV2 => bindings::RTC_TIMESTAMP_BEGIN_0000,
> + }
> + }
> +
> + #[allow(dead_code)]
> + fn range_max(&self) -> u64 {
> + match self {
> + VendorVariant::Arm | VendorVariant::StV1 => u64::from(u32::MAX),
> + VendorVariant::StV2 => bindings::RTC_TIMESTAMP_END_9999,
> + }
> + }
> +}
> +
> +/// PL031 RTC driver private data.
> +#[pin_data(PinnedDrop)]
> +struct Pl031DrvData {
> + #[pin]
> + base: Devres<IoMem<0>>,
> + variant: VendorVariant,
> + /// RTC device reference for interrupt handler.
> + ///
> + /// Set in `init_rtcdevice` and remains valid for the driver's lifetime
> + /// because the RTC device is managed by devres.
> + rtc_device: Option<ARef<RtcDevice>>,
> +}
> +
> +// SAFETY: `Pl031DrvData` contains only `Send`/`Sync` types: `Devres` (Send+Sync),
> +// `VendorVariant` (Copy), and `Option<ARef<RtcDevice>>` (Send+Sync because `RtcDevice` is
> +// Send+Sync).
> +unsafe impl Send for Pl031DrvData {}
> +// SAFETY: `Pl031DrvData` contains only `Send`/`Sync` types: `Devres` (Send+Sync),
> +// `VendorVariant` (Copy), and `Option<ARef<RtcDevice>>` (Send+Sync because `RtcDevice` is
> +// Send+Sync).
> +unsafe impl Sync for Pl031DrvData {}
> +
> +/// Vendor-specific data for different PL031 variants.
> +#[derive(Copy, Clone)]
> +struct Pl031Variant {
> + variant: VendorVariant,
> +}
> +
> +impl Pl031Variant {
> + const ARM: Self = Self {
> + variant: VendorVariant::Arm,
> + };
> + const STV1: Self = Self {
> + variant: VendorVariant::StV1,
> + };
> + const STV2: Self = Self {
> + variant: VendorVariant::StV2,
> + };
> +}
> +
> +impl Pl031Variant {
> + const fn to_usize(self) -> usize {
> + self.variant as usize
> + }
> +}
> +
> +// Use AMBA device table for matching
> +kernel::amba_device_table!(
> + ID_TABLE,
> + MODULE_ID_TABLE,
> + <Pl031DrvData as rtc::DriverGeneric<rtc::AmbaBus>>::IdInfo,
> + [
> + (
> + amba::DeviceId::new_with_data(0x00041031, 0x000fffff, Pl031Variant::ARM.to_usize()),
> + Pl031Variant::ARM
> + ),
> + (
> + amba::DeviceId::new_with_data(0x00180031, 0x00ffffff, Pl031Variant::STV1.to_usize()),
> + Pl031Variant::STV1
> + ),
> + (
> + amba::DeviceId::new_with_data(0x00280031, 0x00ffffff, Pl031Variant::STV2.to_usize()),
> + Pl031Variant::STV2
> + ),
> + ]
> +);
> +
> +impl rtc::DriverGeneric<rtc::AmbaBus> for Pl031DrvData {
> + type IdInfo = Pl031Variant;
> +
> + fn probe(
> + adev: &amba::Device<device::Core>,
> + id_info: Option<&Self::IdInfo>,
> + ) -> impl PinInit<Self, Error> {
> + pin_init::pin_init_scope(move || {
> + let io_request = adev.io_request().ok_or(code::ENODEV)?;
> +
> + let variant = id_info
> + .map(|info| info.variant)
> + .unwrap_or(VendorVariant::Arm);
> +
> + Ok(try_pin_init!(Self {
> + base <- IoMem::new(io_request),
> + variant,
> + // Set in init_rtcdevice
> + rtc_device: None,
> + }))
> + })
> + }
> +
> + fn init_rtcdevice(
> + rtc: &RtcDevice,
> + drvdata: &mut Self,
> + id_info: Option<&Self::IdInfo>,
> + ) -> Result {
> + let parent = rtc.bound_parent_device();
> + let amba_dev_bound: &amba::Device<device::Bound> = parent.try_into()?;
> +
> + amba_dev_bound.as_ref().init_wakeup()?;
Here and all other places using early (error) return with '?': I know
its idomatic and extremly conveninient. But I (still) feel somehow
uncomfortable with this from debugging point of view. From debugging
point of view, would we get any helpful log if any of these early
returns are taken? Yes, its quite unlikely that this happens. But in
case it happens would we get something more than just a somehow
silently malfunctioning device?
Opinions?
Thanks
Dirk
^ permalink raw reply
* [RFC PATCH v1 4/4] rust: add PL031 RTC driver
From: Ke Sun @ 2026-01-04 6:06 UTC (permalink / raw)
To: Alexandre Belloni, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich
Cc: linux-rtc, rust-for-linux, Alvin Sun, Ke Sun
In-Reply-To: <20260104060621.3757812-1-sunke@kylinos.cn>
Add Rust implementation of the ARM AMBA PrimeCell 031 RTC driver.
This driver supports:
- ARM, ST v1, and ST v2 variants
- Time read/write operations
- Alarm read/write operations
- Interrupt handling
- Wake-up support
The driver uses the AMBA bus abstractions and RTC core framework
introduced in previous commits.
Signed-off-by: Ke Sun <sunke@kylinos.cn>
---
drivers/rtc/Kconfig | 11 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc_pl031_rust.rs | 529 ++++++++++++++++++++++++++++++++++
3 files changed, 541 insertions(+)
create mode 100644 drivers/rtc/rtc_pl031_rust.rs
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 50dc779f7f983..c7ce188dcc5cf 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1591,6 +1591,17 @@ config RTC_DRV_PL031
To compile this driver as a module, choose M here: the
module will be called rtc-pl031.
+config RTC_DRV_PL031_RUST
+ tristate "ARM AMBA PL031 RTC (Rust)"
+ depends on RUST && RTC_CLASS && RUST_BUILD_ASSERT_ALLOW
+ help
+ This is the Rust implementation of the PL031 RTC driver.
+ It provides the same functionality as the C driver but is
+ written in Rust for improved memory safety.
+
+ This driver requires CONFIG_RUST_BUILD_ASSERT_ALLOW to be enabled
+ because it uses build-time assertions for memory safety checks.
+
config RTC_DRV_AT91RM9200
tristate "AT91RM9200 or some AT91SAM9 RTC"
depends on ARCH_AT91 || COMPILE_TEST
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 6cf7e066314e1..10f540e7409b4 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -139,6 +139,7 @@ obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o
obj-$(CONFIG_RTC_DRV_PIC32) += rtc-pic32.o
obj-$(CONFIG_RTC_DRV_PL030) += rtc-pl030.o
obj-$(CONFIG_RTC_DRV_PL031) += rtc-pl031.o
+obj-$(CONFIG_RTC_DRV_PL031_RUST) += rtc_pl031_rust.o
obj-$(CONFIG_RTC_DRV_PM8XXX) += rtc-pm8xxx.o
obj-$(CONFIG_RTC_DRV_POLARFIRE_SOC) += rtc-mpfs.o
obj-$(CONFIG_RTC_DRV_PS3) += rtc-ps3.o
diff --git a/drivers/rtc/rtc_pl031_rust.rs b/drivers/rtc/rtc_pl031_rust.rs
new file mode 100644
index 0000000000000..c00a49c2bf94e
--- /dev/null
+++ b/drivers/rtc/rtc_pl031_rust.rs
@@ -0,0 +1,529 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+//! Real Time Clock interface for ARM AMBA PrimeCell 031 RTC
+//!
+//! This is a Rust port of the C driver in rtc-pl031.c
+//!
+//! Author: Ke Sun <sunke@kylinos.cn>
+//! Based on: drivers/rtc/rtc-pl031.c
+
+use core::ops::Deref;
+use kernel::{
+ amba,
+ bindings,
+ c_str,
+ device,
+ devres::Devres,
+ error::code,
+ io::mem::IoMem,
+ irq::{
+ Handler,
+ IrqReturn, //
+ },
+ prelude::*,
+ rtc::{
+ self,
+ RtcDevice,
+ RtcDeviceOptions,
+ RtcOperations,
+ RtcTime,
+ RtcWkAlrm, //
+ },
+ sync::aref::ARef, //
+};
+
+// Register definitions
+const RTC_DR: usize = 0x00; // Data read register
+const RTC_MR: usize = 0x04; // Match register
+const RTC_LR: usize = 0x08; // Data load register
+const RTC_CR: usize = 0x0c; // Control register
+const RTC_IMSC: usize = 0x10; // Interrupt mask and set register
+const RTC_RIS: usize = 0x14; // Raw interrupt status register
+const RTC_MIS: usize = 0x18; // Masked interrupt status register
+const RTC_ICR: usize = 0x1c; // Interrupt clear register
+const RTC_YDR: usize = 0x30; // Year data read register
+const RTC_YMR: usize = 0x34; // Year match register
+const RTC_YLR: usize = 0x38; // Year data load register
+
+// Control register bits
+const RTC_CR_EN: u32 = 1 << 0; // Counter enable bit
+const RTC_CR_CWEN: u32 = 1 << 26; // Clockwatch enable bit
+
+// Interrupt status and control register bits
+const RTC_BIT_AI: u32 = 1 << 0; // Alarm interrupt bit
+
+// RTC event flags
+#[allow(dead_code)]
+const RTC_AF: u32 = bindings::RTC_AF;
+#[allow(dead_code)]
+const RTC_IRQF: u32 = bindings::RTC_IRQF;
+
+// ST v2 time format bit definitions
+const RTC_SEC_SHIFT: u32 = 0;
+const RTC_SEC_MASK: u32 = 0x3F << RTC_SEC_SHIFT; // Second [0-59]
+const RTC_MIN_SHIFT: u32 = 6;
+const RTC_MIN_MASK: u32 = 0x3F << RTC_MIN_SHIFT; // Minute [0-59]
+const RTC_HOUR_SHIFT: u32 = 12;
+const RTC_HOUR_MASK: u32 = 0x1F << RTC_HOUR_SHIFT; // Hour [0-23]
+const RTC_WDAY_SHIFT: u32 = 17;
+const RTC_WDAY_MASK: u32 = 0x7 << RTC_WDAY_SHIFT; // Day of week [1-7], 1=Sunday
+const RTC_MDAY_SHIFT: u32 = 20;
+const RTC_MDAY_MASK: u32 = 0x1F << RTC_MDAY_SHIFT; // Day of month [1-31]
+const RTC_MON_SHIFT: u32 = 25;
+const RTC_MON_MASK: u32 = 0xF << RTC_MON_SHIFT; // Month [1-12], 1=January
+
+/// Vendor-specific data for different PL031 variants
+#[derive(Copy, Clone, PartialEq)]
+enum VendorVariant {
+ /// Original ARM version
+ Arm,
+ /// First ST derivative
+ StV1,
+ /// Second ST derivative
+ StV2,
+}
+
+impl VendorVariant {
+ fn clockwatch(&self) -> bool {
+ matches!(self, VendorVariant::StV1 | VendorVariant::StV2)
+ }
+
+ #[allow(dead_code)]
+ fn st_weekday(&self) -> bool {
+ matches!(self, VendorVariant::StV1 | VendorVariant::StV2)
+ }
+
+ #[allow(dead_code)]
+ fn range_min(&self) -> i64 {
+ match self {
+ VendorVariant::Arm | VendorVariant::StV1 => 0,
+ VendorVariant::StV2 => bindings::RTC_TIMESTAMP_BEGIN_0000,
+ }
+ }
+
+ #[allow(dead_code)]
+ fn range_max(&self) -> u64 {
+ match self {
+ VendorVariant::Arm | VendorVariant::StV1 => u64::from(u32::MAX),
+ VendorVariant::StV2 => bindings::RTC_TIMESTAMP_END_9999,
+ }
+ }
+}
+
+/// PL031 RTC driver private data.
+#[pin_data(PinnedDrop)]
+struct Pl031DrvData {
+ #[pin]
+ base: Devres<IoMem<0>>,
+ variant: VendorVariant,
+ /// RTC device reference for interrupt handler.
+ ///
+ /// Set in `init_rtcdevice` and remains valid for the driver's lifetime
+ /// because the RTC device is managed by devres.
+ rtc_device: Option<ARef<RtcDevice>>,
+}
+
+// SAFETY: `Pl031DrvData` contains only `Send`/`Sync` types: `Devres` (Send+Sync),
+// `VendorVariant` (Copy), and `Option<ARef<RtcDevice>>` (Send+Sync because `RtcDevice` is
+// Send+Sync).
+unsafe impl Send for Pl031DrvData {}
+// SAFETY: `Pl031DrvData` contains only `Send`/`Sync` types: `Devres` (Send+Sync),
+// `VendorVariant` (Copy), and `Option<ARef<RtcDevice>>` (Send+Sync because `RtcDevice` is
+// Send+Sync).
+unsafe impl Sync for Pl031DrvData {}
+
+/// Vendor-specific data for different PL031 variants.
+#[derive(Copy, Clone)]
+struct Pl031Variant {
+ variant: VendorVariant,
+}
+
+impl Pl031Variant {
+ const ARM: Self = Self {
+ variant: VendorVariant::Arm,
+ };
+ const STV1: Self = Self {
+ variant: VendorVariant::StV1,
+ };
+ const STV2: Self = Self {
+ variant: VendorVariant::StV2,
+ };
+}
+
+impl Pl031Variant {
+ const fn to_usize(self) -> usize {
+ self.variant as usize
+ }
+}
+
+// Use AMBA device table for matching
+kernel::amba_device_table!(
+ ID_TABLE,
+ MODULE_ID_TABLE,
+ <Pl031DrvData as rtc::DriverGeneric<rtc::AmbaBus>>::IdInfo,
+ [
+ (
+ amba::DeviceId::new_with_data(0x00041031, 0x000fffff, Pl031Variant::ARM.to_usize()),
+ Pl031Variant::ARM
+ ),
+ (
+ amba::DeviceId::new_with_data(0x00180031, 0x00ffffff, Pl031Variant::STV1.to_usize()),
+ Pl031Variant::STV1
+ ),
+ (
+ amba::DeviceId::new_with_data(0x00280031, 0x00ffffff, Pl031Variant::STV2.to_usize()),
+ Pl031Variant::STV2
+ ),
+ ]
+);
+
+impl rtc::DriverGeneric<rtc::AmbaBus> for Pl031DrvData {
+ type IdInfo = Pl031Variant;
+
+ fn probe(
+ adev: &amba::Device<device::Core>,
+ id_info: Option<&Self::IdInfo>,
+ ) -> impl PinInit<Self, Error> {
+ pin_init::pin_init_scope(move || {
+ let io_request = adev.io_request().ok_or(code::ENODEV)?;
+
+ let variant = id_info
+ .map(|info| info.variant)
+ .unwrap_or(VendorVariant::Arm);
+
+ Ok(try_pin_init!(Self {
+ base <- IoMem::new(io_request),
+ variant,
+ // Set in init_rtcdevice
+ rtc_device: None,
+ }))
+ })
+ }
+
+ fn init_rtcdevice(
+ rtc: &RtcDevice,
+ drvdata: &mut Self,
+ id_info: Option<&Self::IdInfo>,
+ ) -> Result {
+ let parent = rtc.bound_parent_device();
+ let amba_dev_bound: &amba::Device<device::Bound> = parent.try_into()?;
+
+ amba_dev_bound.as_ref().init_wakeup()?;
+
+ let variant = id_info
+ .map(|info| info.variant)
+ .unwrap_or(VendorVariant::Arm);
+
+ // Initialize RTC control register: enable clockwatch (ST variants) or counter (ARM).
+ {
+ let base_guard = drvdata.base.try_access().ok_or(code::ENXIO)?;
+ let base = base_guard.deref();
+ let mut data = base.try_read32(RTC_CR)?;
+ if variant.clockwatch() {
+ data |= RTC_CR_CWEN;
+ } else {
+ data |= RTC_CR_EN;
+ }
+ base.try_write32(data, RTC_CR)?;
+ }
+
+ rtc.set_range_min(variant.range_min());
+ rtc.set_range_max(variant.range_max());
+
+ // Fix ST weekday hardware bug.
+ if variant.st_weekday() {
+ let base_guard = drvdata.base.try_access().ok_or(code::ENXIO)?;
+ let base = base_guard.deref();
+ let bcd_year = base.try_read32(RTC_YDR)?;
+ if bcd_year == 0x2000 {
+ let st_time = base.try_read32(RTC_DR)?;
+ if (st_time & (RTC_MON_MASK | RTC_MDAY_MASK | RTC_WDAY_MASK)) == 0x02120000 {
+ let fixed_time = st_time | (0x7 << RTC_WDAY_SHIFT);
+ base.try_write32(0x2000, RTC_YLR)?;
+ base.try_write32(fixed_time, RTC_LR)?;
+ }
+ }
+ }
+
+ // Store RTC device reference for interrupt handler.
+ drvdata.rtc_device = Some(ARef::from(rtc));
+
+ // Determine IRQ flags: ST v2 shares IRQ with another block.
+ let irq_flags = if variant == VendorVariant::StV2 {
+ kernel::irq::Flags::SHARED | kernel::irq::Flags::COND_SUSPEND
+ } else {
+ kernel::irq::Flags::SHARED
+ };
+
+ // Request IRQ (optional, may not be available).
+ match amba_dev_bound.request_irq_by_index(
+ irq_flags,
+ 0,
+ c_str!("rtc-pl031"),
+ try_pin_init!(Pl031IrqHandler {
+ _pin: core::marker::PhantomPinned,
+ }),
+ ) {
+ Ok(init) => {
+ kernel::devres::register(
+ amba_dev_bound.as_ref(),
+ init,
+ kernel::alloc::flags::GFP_KERNEL,
+ )?;
+
+ if let Ok(irq) = amba_dev_bound.irq_by_index(0) {
+ parent.set_wake_irq(irq.irq() as i32)?;
+ }
+ }
+ Err(_) => {
+ // IRQ not available - clear alarm feature.
+ rtc.clear_feature(bindings::RTC_FEATURE_ALARM);
+ }
+ }
+
+ Ok(())
+ }
+
+ fn rtc_options() -> RtcDeviceOptions {
+ RtcDeviceOptions {
+ name: c_str!("rtc-pl031"),
+ }
+ }
+}
+
+impl rtc::AmbaIdInfos for Pl031DrvData {
+ const ID_TABLE: Option<amba::IdTable<Self::IdInfo>> = Some(&ID_TABLE);
+}
+
+#[pinned_drop]
+impl PinnedDrop for Pl031DrvData {
+ fn drop(self: Pin<&mut Self>) {
+ // Resources are automatically cleaned up by devres.
+ }
+}
+
+/// Converts a Gregorian date to ST v2 RTC format.
+fn stv2_tm_to_time(dev: &device::Device, tm: &RtcTime) -> Result<(u32, u32)> {
+ let year = tm.tm_year() + 1900;
+ let mut wday = tm.tm_wday();
+
+ // Hardware wday masking doesn't work, so wday must be valid.
+ if !(-1..=6).contains(&wday) {
+ dev_err!(dev, "invalid wday value {}\n", tm.tm_wday());
+ return Err(code::EINVAL);
+ } else if wday == -1 {
+ // wday is not provided, calculate it here.
+ let time64 = tm.to_time64();
+ let mut calc_tm = RtcTime::default();
+ calc_tm.set_from_time64(time64);
+ wday = calc_tm.tm_wday();
+ }
+
+ // Convert year to BCD.
+ let bcd_year =
+ (u32::from(bin2bcd((year % 100) as u8))) | (u32::from(bin2bcd((year / 100) as u8)) << 8);
+
+ let st_time = ((tm.tm_mon() + 1) as u32) << RTC_MON_SHIFT
+ | (tm.tm_mday() as u32) << RTC_MDAY_SHIFT
+ | ((wday + 1) as u32) << RTC_WDAY_SHIFT
+ | (tm.tm_hour() as u32) << RTC_HOUR_SHIFT
+ | (tm.tm_min() as u32) << RTC_MIN_SHIFT
+ | (tm.tm_sec() as u32) << RTC_SEC_SHIFT;
+
+ Ok((st_time, bcd_year))
+}
+
+/// Converts ST v2 RTC format to a Gregorian date.
+fn stv2_time_to_tm(st_time: u32, bcd_year: u32, tm: &mut RtcTime) {
+ let year_low = bcd2bin((bcd_year & 0xFF) as u8);
+ let year_high = bcd2bin(((bcd_year >> 8) & 0xFF) as u8);
+ tm.set_tm_year(i32::from(year_low) + i32::from(year_high) * 100);
+ tm.set_tm_mon((((st_time & RTC_MON_MASK) >> RTC_MON_SHIFT) - 1) as i32);
+ tm.set_tm_mday(((st_time & RTC_MDAY_MASK) >> RTC_MDAY_SHIFT) as i32);
+ tm.set_tm_wday((((st_time & RTC_WDAY_MASK) >> RTC_WDAY_SHIFT) - 1) as i32);
+ tm.set_tm_hour(((st_time & RTC_HOUR_MASK) >> RTC_HOUR_SHIFT) as i32);
+ tm.set_tm_min(((st_time & RTC_MIN_MASK) >> RTC_MIN_SHIFT) as i32);
+ tm.set_tm_sec(((st_time & RTC_SEC_MASK) >> RTC_SEC_SHIFT) as i32);
+
+ // Values are from valid RTC time structures and are non-negative.
+ tm.set_tm_yday(tm.year_days());
+ tm.set_tm_year(tm.tm_year() - 1900);
+}
+
+/// Converts a binary value to BCD.
+fn bin2bcd(val: u8) -> u8 {
+ ((val / 10) << 4) | (val % 10)
+}
+
+/// Converts a BCD value to binary.
+fn bcd2bin(val: u8) -> u8 {
+ ((val >> 4) * 10) + (val & 0x0F)
+}
+
+/// IRQ handler for PL031 RTC.
+#[pin_data]
+struct Pl031IrqHandler {
+ #[pin]
+ _pin: core::marker::PhantomPinned,
+}
+
+impl Handler for Pl031IrqHandler {
+ fn handle(&self, dev: &device::Device<device::Bound>) -> IrqReturn {
+ // Get driver data using drvdata.
+ let driver = match dev.drvdata::<Pl031DrvData>() {
+ Ok(driver) => driver,
+ Err(_) => return IrqReturn::None,
+ };
+
+ // Access the MMIO base.
+ let base_guard = match driver.base.try_access() {
+ Some(guard) => guard,
+ None => return IrqReturn::None,
+ };
+ let base = base_guard.deref();
+
+ // Read masked interrupt status.
+ let rtcmis = match base.try_read32(RTC_MIS) {
+ Ok(val) => val,
+ Err(_) => return IrqReturn::None,
+ };
+
+ if (rtcmis & RTC_BIT_AI) != 0 {
+ // Clear the interrupt.
+ if base.try_write32(RTC_BIT_AI, RTC_ICR).is_err() {
+ return IrqReturn::None;
+ }
+
+ // Get RTC device from driver and call rtc_update_irq.
+ if let Some(rtc) = &driver.rtc_device {
+ rtc.update_irq(1, (RTC_AF | RTC_IRQF) as usize);
+ }
+
+ return IrqReturn::Handled;
+ }
+
+ IrqReturn::None
+ }
+}
+
+#[vtable]
+impl RtcOperations for Pl031DrvData {
+ type Ptr = Pin<KBox<Self>>;
+
+ fn read_time(drvdata: Pin<&Self>, tm: &mut RtcTime) -> Result {
+ let base_guard = drvdata.base.try_access().ok_or(code::ENXIO)?;
+ let base = base_guard.deref();
+
+ match drvdata.variant {
+ VendorVariant::Arm | VendorVariant::StV1 => {
+ let time32: u32 = base.try_read32(RTC_DR)?;
+ let time64 = i64::from(time32);
+ tm.set_from_time64(time64);
+ }
+ VendorVariant::StV2 => {
+ let st_time = base.try_read32(RTC_DR)?;
+ let bcd_year = base.try_read32(RTC_YDR)?;
+ stv2_time_to_tm(st_time, bcd_year, tm);
+ }
+ }
+
+ Ok(())
+ }
+
+ fn set_time(drvdata: Pin<&Self>, tm: &mut RtcTime) -> Result {
+ let base_guard = drvdata.base.try_access().ok_or(code::ENXIO)?;
+ let base = base_guard.deref();
+ let rtc_dev = drvdata.base.device();
+
+ match drvdata.variant {
+ VendorVariant::Arm | VendorVariant::StV1 => {
+ let time64 = tm.to_time64();
+ base.try_write32(time64 as u32, RTC_LR)?;
+ }
+ VendorVariant::StV2 => {
+ let (st_time, bcd_year) = stv2_tm_to_time(rtc_dev, tm)?;
+ base.try_write32(bcd_year, RTC_YLR)?;
+ base.try_write32(st_time, RTC_LR)?;
+ }
+ }
+
+ Ok(())
+ }
+
+ fn read_alarm(drvdata: Pin<&Self>, alarm: &mut RtcWkAlrm) -> Result {
+ let base_guard = drvdata.base.try_access().ok_or(code::ENXIO)?;
+ let base = base_guard.deref();
+
+ match drvdata.variant {
+ VendorVariant::Arm | VendorVariant::StV1 => {
+ let time32: u32 = base.try_read32(RTC_MR)?;
+ let time64 = i64::from(time32);
+ crate::rtc::RtcTime::time64_to_tm(time64, alarm.get_time_mut());
+ }
+ VendorVariant::StV2 => {
+ let st_time = base.try_read32(RTC_MR)?;
+ let bcd_year = base.try_read32(RTC_YMR)?;
+ stv2_time_to_tm(st_time, bcd_year, alarm.get_time_mut());
+ }
+ }
+
+ alarm.set_pending(if (base.try_read32(RTC_RIS)? & RTC_BIT_AI) != 0 {
+ 1
+ } else {
+ 0
+ });
+ alarm.set_enabled(if (base.try_read32(RTC_IMSC)? & RTC_BIT_AI) != 0 {
+ 1
+ } else {
+ 0
+ });
+
+ Ok(())
+ }
+
+ fn set_alarm(drvdata: Pin<&Self>, alarm: &mut RtcWkAlrm) -> Result {
+ let base_guard = drvdata.base.try_access().ok_or(code::ENXIO)?;
+ let base = base_guard.deref();
+ let rtc_dev = drvdata.base.device();
+
+ match drvdata.variant {
+ VendorVariant::Arm | VendorVariant::StV1 => {
+ let time64 = alarm.get_time().to_time64();
+ base.try_write32(time64 as u32, RTC_MR)?;
+ }
+ VendorVariant::StV2 => {
+ let (st_time, bcd_year) = stv2_tm_to_time(rtc_dev, alarm.get_time())?;
+ base.try_write32(bcd_year, RTC_YMR)?;
+ base.try_write32(st_time, RTC_MR)?;
+ }
+ }
+
+ Self::alarm_irq_enable(drvdata, u32::from(alarm.enabled()))
+ }
+
+ fn alarm_irq_enable(drvdata: Pin<&Self>, enabled: u32) -> Result {
+ let base_guard = drvdata.base.try_access().ok_or(code::ENXIO)?;
+ let base = base_guard.deref();
+
+ // Clear any pending alarm interrupts.
+ base.try_write32(RTC_BIT_AI, RTC_ICR)?;
+
+ let mut imsc = base.try_read32(RTC_IMSC)?;
+ if enabled == 1 {
+ imsc |= RTC_BIT_AI;
+ } else {
+ imsc &= !RTC_BIT_AI;
+ }
+ base.try_write32(imsc, RTC_IMSC)?;
+
+ Ok(())
+ }
+}
+
+kernel::module_rtc_driver! {
+ bus: AmbaBus,
+ type: Pl031DrvData,
+ name: "rtc-pl031-rust",
+ authors: ["Ke Sun <sunke@kylinos.cn>"],
+ description: "Rust PL031 RTC driver",
+ license: "GPL v2",
+}
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v1 3/4] rust: add RTC core abstractions and data structures
From: Ke Sun @ 2026-01-04 6:06 UTC (permalink / raw)
To: Alexandre Belloni, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich
Cc: linux-rtc, rust-for-linux, Alvin Sun, Ke Sun
In-Reply-To: <20260104060621.3757812-1-sunke@kylinos.cn>
Add Rust abstractions for RTC (Real-Time Clock) devices, including:
- Data structures: RtcTime, RtcWkAlrm, RtcParam wrappers for RTC types
- RtcDevice: Safe wrapper for struct rtc_device
- DriverGeneric trait: Generic RTC driver trait over bus types
- RtcOperations trait: VTable for RTC device operations
- Bus abstractions: PlatformBus, AmbaBus, I2cBus implementations
- Adapter: Generic adapter for RTC driver registration
- module_rtc_driver! macro: Module declaration macro for RTC drivers
This provides the foundation for RTC drivers on multiple bus types
(platform, AMBA, and I2C).
Signed-off-by: Ke Sun <sunke@kylinos.cn>
---
rust/helpers/helpers.c | 1 +
rust/helpers/rtc.c | 9 +
rust/kernel/lib.rs | 2 +
rust/kernel/rtc.rs | 1710 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 1722 insertions(+)
create mode 100644 rust/helpers/rtc.c
create mode 100644 rust/kernel/rtc.rs
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 79c72762ad9c4..1a5c103fb24ba 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -48,6 +48,7 @@
#include "rcu.c"
#include "refcount.c"
#include "regulator.c"
+#include "rtc.c"
#include "scatterlist.c"
#include "security.c"
#include "signal.c"
diff --git a/rust/helpers/rtc.c b/rust/helpers/rtc.c
new file mode 100644
index 0000000000000..862cd61670bfc
--- /dev/null
+++ b/rust/helpers/rtc.c
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/rtc.h>
+
+int rust_helper_devm_rtc_register_device(struct rtc_device *rtc)
+{
+ return __devm_rtc_register_device(THIS_MODULE, rtc);
+}
+
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 3e557335fc5fe..1390073e4ae27 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -135,6 +135,8 @@
pub mod rbtree;
pub mod regulator;
pub mod revocable;
+#[cfg(CONFIG_RTC_CLASS)]
+pub mod rtc;
pub mod scatterlist;
pub mod security;
pub mod seq_file;
diff --git a/rust/kernel/rtc.rs b/rust/kernel/rtc.rs
new file mode 100644
index 0000000000000..1d14d38650839
--- /dev/null
+++ b/rust/kernel/rtc.rs
@@ -0,0 +1,1710 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! RTC (Real-Time Clock) device support.
+//!
+//! C headers: [`include/linux/rtc.h`](srctree/include/linux/rtc.h).
+//!
+//! Reference: <https://www.kernel.org/doc/html/latest/driver-api/rtc.html>
+use crate::{
+ acpi,
+ amba,
+ bindings,
+ bitmap::Bitmap,
+ device::{self},
+ driver,
+ error::{
+ code,
+ from_result,
+ to_result,
+ VTABLE_DEFAULT_ERROR, //
+ },
+ i2c,
+ of,
+ platform,
+ prelude::*,
+ seq_file::SeqFile,
+ sync::aref::AlwaysRefCounted,
+ types::{
+ ForeignOwnable,
+ Opaque, //
+ },
+ ThisModule, //
+};
+
+use core::{
+ marker::PhantomData,
+ ptr::NonNull, //
+};
+
+/// RTC time structure.
+///
+/// Wraps the C `struct rtc_time` from `include/uapi/linux/rtc.h`.
+#[repr(transparent)]
+#[derive(Clone, Copy)]
+pub struct RtcTime(pub bindings::rtc_time);
+
+impl RtcTime {
+ /// Creates a new `RtcTime` from a time64_t value (seconds since 1970-01-01 00:00:00 UTC).
+ pub fn from_time64(time: i64) -> Self {
+ // SAFETY: `rtc_time` is a C struct with only integer fields, so it's safe to
+ // zero-initialize.
+ let mut tm = Self(unsafe { core::mem::zeroed() });
+ // SAFETY: `rtc_time64_to_tm` is a pure function that only writes to the provided
+ // `struct rtc_time` pointer. The pointer is valid because `tm.0` is a valid mutable
+ // reference, and the function does not retain any references to it.
+ unsafe {
+ bindings::rtc_time64_to_tm(time, &mut tm.0);
+ }
+ tm
+ }
+
+ /// Converts this `RtcTime` to a time64_t value (seconds since 1970-01-01 00:00:00 UTC).
+ pub fn to_time64(&self) -> i64 {
+ // SAFETY: `rtc_tm_to_time64` is a pure function that only reads from the provided
+ // `struct rtc_time` pointer. The pointer is valid because `self.0` is a valid reference,
+ // and the function does not retain any references to it.
+ unsafe { bindings::rtc_tm_to_time64(core::ptr::from_ref(&self.0).cast_mut()) }
+ }
+
+ /// Sets this `RtcTime` from a time64_t value (seconds since 1970-01-01 00:00:00 UTC).
+ pub fn set_from_time64(&mut self, time: i64) {
+ // SAFETY: `rtc_time64_to_tm` is a pure function that only writes to the provided
+ // `struct rtc_time` pointer. The pointer is valid because `self.0` is a valid mutable
+ // reference, and the function does not retain any references to it.
+ unsafe {
+ bindings::rtc_time64_to_tm(time, &mut self.0);
+ }
+ }
+
+ /// Converts a time64_t value to an RTC time structure.
+ #[inline]
+ pub fn time64_to_tm(time: i64, tm: &mut Self) {
+ tm.set_from_time64(time);
+ }
+
+ /// Converts an RTC time structure to a time64_t value (seconds since 1970-01-01 00:00:00 UTC).
+ #[inline]
+ pub fn tm_to_time64(tm: &Self) -> i64 {
+ tm.to_time64()
+ }
+
+ /// Calculates the day of the year (0-365) from the date components.
+ #[inline]
+ pub fn year_days(&self) -> i32 {
+ // SAFETY: `rtc_year_days` is a pure function that only performs calculations based on
+ // the provided parameters. The values should be from valid RTC time structures and
+ // non-negative, but the function itself is safe to call with any values.
+ unsafe {
+ bindings::rtc_year_days(
+ self.0.tm_mday as u32,
+ self.0.tm_mon as u32,
+ self.0.tm_year as u32,
+ )
+ }
+ }
+
+ /// Returns the seconds field (0-59).
+ #[inline]
+ pub fn tm_sec(&self) -> i32 {
+ self.0.tm_sec
+ }
+
+ /// Sets the seconds field (0-59).
+ #[inline]
+ pub fn set_tm_sec(&mut self, sec: i32) {
+ self.0.tm_sec = sec;
+ }
+
+ /// Returns the minutes field (0-59).
+ #[inline]
+ pub fn tm_min(&self) -> i32 {
+ self.0.tm_min
+ }
+
+ /// Sets the minutes field (0-59).
+ #[inline]
+ pub fn set_tm_min(&mut self, min: i32) {
+ self.0.tm_min = min;
+ }
+
+ /// Returns the hours field (0-23).
+ #[inline]
+ pub fn tm_hour(&self) -> i32 {
+ self.0.tm_hour
+ }
+
+ /// Sets the hours field (0-23).
+ #[inline]
+ pub fn set_tm_hour(&mut self, hour: i32) {
+ self.0.tm_hour = hour;
+ }
+
+ /// Returns the day of the month (1-31).
+ #[inline]
+ pub fn tm_mday(&self) -> i32 {
+ self.0.tm_mday
+ }
+
+ /// Sets the day of the month (1-31).
+ #[inline]
+ pub fn set_tm_mday(&mut self, mday: i32) {
+ self.0.tm_mday = mday;
+ }
+
+ /// Returns the month (0-11, where 0 is January).
+ #[inline]
+ pub fn tm_mon(&self) -> i32 {
+ self.0.tm_mon
+ }
+
+ /// Sets the month (0-11, where 0 is January).
+ #[inline]
+ pub fn set_tm_mon(&mut self, mon: i32) {
+ self.0.tm_mon = mon;
+ }
+
+ /// Returns the year (years since 1900).
+ #[inline]
+ pub fn tm_year(&self) -> i32 {
+ self.0.tm_year
+ }
+
+ /// Sets the year (years since 1900).
+ #[inline]
+ pub fn set_tm_year(&mut self, year: i32) {
+ self.0.tm_year = year;
+ }
+
+ /// Returns the day of the week (0-6, where 0 is Sunday).
+ #[inline]
+ pub fn tm_wday(&self) -> i32 {
+ self.0.tm_wday
+ }
+
+ /// Sets the day of the week (0-6, where 0 is Sunday).
+ #[inline]
+ pub fn set_tm_wday(&mut self, wday: i32) {
+ self.0.tm_wday = wday;
+ }
+
+ /// Returns the day of the year (0-365).
+ #[inline]
+ pub fn tm_yday(&self) -> i32 {
+ self.0.tm_yday
+ }
+
+ /// Sets the day of the year (0-365).
+ #[inline]
+ pub fn set_tm_yday(&mut self, yday: i32) {
+ self.0.tm_yday = yday;
+ }
+
+ /// Returns the daylight saving time flag.
+ #[inline]
+ pub fn tm_isdst(&self) -> i32 {
+ self.0.tm_isdst
+ }
+
+ /// Sets the daylight saving time flag.
+ #[inline]
+ pub fn set_tm_isdst(&mut self, isdst: i32) {
+ self.0.tm_isdst = isdst;
+ }
+}
+
+impl Default for RtcTime {
+ fn default() -> Self {
+ // SAFETY: `rtc_time` is a C struct with only integer fields, so it's safe to
+ // zero-initialize.
+ Self(unsafe { core::mem::zeroed() })
+ }
+}
+
+/// RTC wake alarm structure.
+///
+/// Wraps the C `struct rtc_wkalrm` from `include/uapi/linux/rtc.h`.
+#[repr(transparent)]
+#[derive(Clone, Copy)]
+pub struct RtcWkAlrm(pub bindings::rtc_wkalrm);
+
+impl Default for RtcWkAlrm {
+ fn default() -> Self {
+ // SAFETY: `rtc_wkalrm` is a C struct with only integer fields and a nested `rtc_time`
+ // struct (which also has only integer fields), so it's safe to zero-initialize.
+ Self(unsafe { core::mem::zeroed() })
+ }
+}
+
+impl RtcWkAlrm {
+ /// Returns a reference to the alarm time.
+ #[inline]
+ pub fn get_time(&self) -> &RtcTime {
+ // SAFETY: `RtcTime` is `#[repr(transparent)]` over `bindings::rtc_time`, so the memory
+ // layout is identical. We can safely reinterpret the reference.
+ unsafe { &*(&raw const self.0.time).cast::<RtcTime>() }
+ }
+
+ /// Returns a mutable reference to the alarm time.
+ #[inline]
+ pub fn get_time_mut(&mut self) -> &mut RtcTime {
+ // SAFETY: `RtcTime` is `#[repr(transparent)]` over `bindings::rtc_time`, so the memory
+ // layout is identical. We can safely reinterpret the reference.
+ unsafe { &mut *(&raw mut self.0.time).cast::<RtcTime>() }
+ }
+
+ /// Sets the alarm time from a `RtcTime` value.
+ #[inline]
+ pub fn set_time(&mut self, time: RtcTime) {
+ self.0.time = time.0;
+ }
+
+ /// Returns the enabled field.
+ #[inline]
+ pub fn enabled(&self) -> u8 {
+ self.0.enabled
+ }
+
+ /// Sets the `enabled` field.
+ #[inline]
+ pub fn set_enabled(&mut self, enabled: u8) {
+ self.0.enabled = enabled;
+ }
+
+ /// Returns the pending field.
+ #[inline]
+ pub fn pending(&self) -> u8 {
+ self.0.pending
+ }
+
+ /// Sets the `pending` field.
+ #[inline]
+ pub fn set_pending(&mut self, pending: u8) {
+ self.0.pending = pending;
+ }
+}
+
+/// RTC parameter structure.
+///
+/// Wraps the C `struct rtc_param` from `include/uapi/linux/rtc.h`.
+#[repr(transparent)]
+#[derive(Clone, Copy)]
+pub struct RtcParam(pub bindings::rtc_param);
+
+impl Default for RtcParam {
+ fn default() -> Self {
+ // SAFETY: FFI type is valid to be zero-initialized.
+ Self(unsafe { core::mem::zeroed() })
+ }
+}
+
+/// A Rust wrapper for the C `struct rtc_device`.
+///
+/// This type provides safe access to RTC device operations. The underlying `rtc_device`
+/// is managed by the kernel and remains valid for the lifetime of the `RtcDevice`.
+///
+/// # Invariants
+///
+/// A [`RtcDevice`] instance holds a pointer to a valid [`struct rtc_device`] that is
+/// registered and managed by the kernel.
+///
+/// # Examples
+///
+/// ```rust
+/// # use kernel::{
+/// # prelude::*,
+/// # rtc::{
+/// # DriverGeneric,
+/// # PlatformBus,
+/// # RtcDevice, //
+/// # }, //
+/// # };
+/// // Example IdInfo type with range fields
+/// struct DeviceInfo {
+/// range_min: i64,
+/// range_max: u64,
+/// }
+///
+/// // In your DriverGeneric implementation:
+/// // fn init_rtcdevice(
+/// // rtc: &RtcDevice,
+/// // _driver: Pin<&Self>,
+/// // id_info: Option<&DeviceInfo>,
+/// // ) -> Result {
+/// // // Set the time range for the RTC device based on device variant
+/// // if let Some(info) = id_info {
+/// // rtc.set_range_min(info.range_min);
+/// // rtc.set_range_max(info.range_max);
+/// // } else {
+/// // rtc.set_range_min(0);
+/// // rtc.set_range_max(u64::MAX);
+/// // }
+/// // Ok(())
+/// // }
+/// ```
+///
+/// [`struct rtc_device`]: https://docs.kernel.org/driver-api/rtc.html
+#[repr(transparent)]
+pub struct RtcDevice(Opaque<bindings::rtc_device>);
+
+impl RtcDevice {
+ /// Obtain the raw [`struct rtc_device`] pointer.
+ #[inline]
+ pub fn as_raw(&self) -> *mut bindings::rtc_device {
+ self.0.get()
+ }
+
+ /// Returns a bound reference to the parent [`device::Device`].
+ pub fn bound_parent_device(&self) -> &device::Device<device::Bound> {
+ // SAFETY: `device()` returns a valid device, and RTC devices always have a parent.
+ let parent = unsafe { self.device().parent().unwrap_unchecked() };
+ // SAFETY: A bound RTC device always has a bound parent device.
+ unsafe { parent.as_bound() }
+ }
+
+ /// Returns a reference to the [`device::Device`] that the RTC device is bound to.
+ pub fn device(&self) -> &device::Device {
+ // SAFETY: `self.as_raw()` is a valid pointer to a `struct rtc_device`, and `dev` is a valid
+ // field within it.
+ unsafe { device::Device::from_raw(&raw mut (*self.as_raw()).dev) }
+ }
+
+ /// Set the minimum time range for the RTC device.
+ #[inline]
+ pub fn set_range_min(&self, min: i64) {
+ // SAFETY: By the type invariants, self.as_raw() is a valid pointer to a
+ // `struct rtc_device`, and we're only writing to the `range_min` field.
+ unsafe {
+ (*self.as_raw()).range_min = min;
+ }
+ }
+
+ /// Set the maximum time range for the RTC device.
+ #[inline]
+ pub fn set_range_max(&self, max: u64) {
+ // SAFETY: By the type invariants, self.as_raw() is a valid pointer to a
+ // `struct rtc_device`, and we're only writing to the `range_max` field.
+ unsafe {
+ (*self.as_raw()).range_max = max;
+ }
+ }
+
+ /// Get the minimum time range for the RTC device.
+ #[inline]
+ pub fn range_min(&self) -> i64 {
+ // SAFETY: By the type invariants, self.as_raw() is a valid pointer to a
+ // `struct rtc_device`, and we're only reading the `range_min` field.
+ unsafe { (*self.as_raw()).range_min }
+ }
+
+ /// Get the maximum time range for the RTC device.
+ #[inline]
+ pub fn range_max(&self) -> u64 {
+ // SAFETY: By the type invariants, self.as_raw() is a valid pointer to a
+ // `struct rtc_device`, and we're only reading the `range_max` field.
+ unsafe { (*self.as_raw()).range_max }
+ }
+
+ /// Notify the RTC framework that an interrupt has occurred.
+ ///
+ /// Should be called from interrupt handlers. Schedules work to handle the interrupt
+ /// in process context.
+ #[inline]
+ pub fn update_irq(&self, num: usize, events: usize) {
+ // SAFETY: By the type invariants, self.as_raw() is a valid pointer to a
+ // `struct rtc_device`. The rtc_update_irq function handles NULL/ERR checks internally.
+ unsafe {
+ bindings::rtc_update_irq(self.as_raw(), num, events);
+ }
+ }
+
+ /// Clear a feature bit in the RTC device.
+ #[inline]
+ pub fn clear_feature(&self, feature: u32) {
+ // SAFETY: By the type invariants, self.as_raw() is a valid pointer to a
+ // `struct rtc_device`, and features is a valid bitmap array with RTC_FEATURE_CNT bits.
+ let features_bitmap = unsafe {
+ Bitmap::from_raw_mut(
+ (*self.as_raw()).features.as_mut_ptr().cast::<usize>(),
+ bindings::RTC_FEATURE_CNT as usize,
+ )
+ };
+ features_bitmap.clear_bit(feature as usize);
+ }
+}
+
+impl<Ctx: device::DeviceContext> AsRef<device::Device<Ctx>> for RtcDevice {
+ fn as_ref(&self) -> &device::Device<Ctx> {
+ let raw = self.as_raw();
+ // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a valid
+ // `struct rtc_device`.
+ let dev = unsafe { &raw mut (*raw).dev };
+
+ // SAFETY: `dev` points to a valid `struct device`.
+ unsafe { device::Device::from_raw(dev) }
+ }
+}
+
+// SAFETY: Instances of `RtcDevice` are always reference-counted via the underlying `device`.
+// The `struct rtc_device` contains a `struct device dev` as its first field, and the
+// reference counting is managed through `get_device`/`put_device` on the `dev` field.
+unsafe impl AlwaysRefCounted for RtcDevice {
+ fn inc_ref(&self) {
+ // SAFETY: The existence of a shared reference guarantees that the refcount is non-zero.
+ // `self.device()` returns a reference to the `dev` field of `struct rtc_device`.
+ unsafe { bindings::get_device(self.device().as_raw()) };
+ }
+
+ unsafe fn dec_ref(obj: NonNull<Self>) {
+ let rtc: *mut bindings::rtc_device = obj.cast().as_ptr();
+
+ // SAFETY: By the type invariant of `Self`, `rtc` is a pointer to a valid
+ // `struct rtc_device`. The `dev` field is the first field of `struct rtc_device`,
+ // so we can safely access it.
+ let dev = unsafe { &raw mut (*rtc).dev };
+
+ // SAFETY: The safety requirements guarantee that the refcount is non-zero.
+ unsafe { bindings::put_device(dev) };
+ }
+}
+
+// SAFETY: `RtcDevice` is reference-counted and can be released from any thread.
+unsafe impl Send for RtcDevice {}
+
+// SAFETY: `RtcDevice` can be shared among threads because all immutable methods are
+// protected by the synchronization in `struct rtc_device` (via `ops_lock` mutex).
+unsafe impl Sync for RtcDevice {}
+
+/// The RTC driver trait.
+///
+/// RTC drivers are registered as platform drivers, so they use platform device matching
+/// (OF and ACPI device IDs).
+///
+/// # Examples
+///
+///```
+/// # use kernel::{
+/// # acpi,
+/// # bindings,
+/// # c_str,
+/// # device::Core,
+/// # of,
+/// # platform,
+/// # rtc, //
+/// # };
+/// use kernel::prelude::*;
+///
+/// struct MyRtcDriver;
+///
+/// kernel::of_device_table!(
+/// OF_TABLE,
+/// MODULE_OF_TABLE,
+/// <MyRtcDriver as rtc::DriverGeneric<rtc::PlatformBus>>::IdInfo,
+/// [
+/// (of::DeviceId::new(c_str!("test,rtc")), ()),
+/// ]
+/// );
+///
+/// kernel::acpi_device_table!(
+/// ACPI_TABLE,
+/// MODULE_ACPI_TABLE,
+/// <MyRtcDriver as rtc::DriverGeneric<rtc::PlatformBus>>::IdInfo,
+/// [
+/// (acpi::DeviceId::new(c_str!("RSTRTC00")), ()),
+/// ]
+/// );
+///
+/// impl rtc::DriverGeneric<rtc::PlatformBus> for MyRtcDriver {
+/// type IdInfo = ();
+///
+/// fn probe(
+/// _pdev: &platform::Device<Core>,
+/// _info: Option<&Self::IdInfo>,
+/// ) -> impl PinInit<Self, Error> {
+/// Err(ENODEV)
+/// }
+/// }
+///
+/// impl rtc::PlatformIdInfos for MyRtcDriver {
+/// const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+/// const ACPI_ID_TABLE: Option<acpi::IdTable<Self::IdInfo>> = Some(&ACPI_TABLE);
+/// }
+///```
+/// Trait for different bus types that RTC drivers can use.
+///
+/// This trait abstracts the differences between different bus types (platform, amba, etc.)
+/// and provides the necessary types and methods for driver registration.
+pub trait Bus: Send + Sync + Sized + 'static {
+ /// The device type for this bus.
+ type Device;
+
+ /// The driver registration type for this bus (e.g., `platform_driver`, `amba_driver`).
+ type RegType: Default;
+
+ /// The raw C device pointer type for probe callbacks.
+ type RawDevice;
+
+ /// The probe function signature for this bus type.
+ /// Different buses have different probe function signatures:
+ /// - Platform: `unsafe extern "C" fn(*mut platform_device) -> i32`
+ /// - AMBA: `unsafe extern "C" fn(*mut amba_device, *const amba_id) -> i32`
+ type ProbeFn;
+
+ /// The remove function signature for this bus type.
+ /// Currently, all buses use the same remove function signature:
+ /// `unsafe extern "C" fn(*mut RawDevice)`
+ type RemoveFn;
+
+ /// Get device ID info from a device.
+ fn id_info<T: DriverGeneric<Self>>(dev: &device::Device) -> Option<&'static T::IdInfo>
+ where
+ Self: Sized;
+
+ /// Convert raw device pointer to typed device reference.
+ ///
+ /// The safety requirements are satisfied by the caller, which ensures that `raw` is a valid
+ /// pointer to the appropriate device type. The returned reference is valid for the lifetime of
+ /// the raw pointer.
+ fn from_raw<'a>(raw: *mut Self::RawDevice) -> &'a Self::Device;
+
+ /// Get the underlying device from a bus device.
+ fn as_device(dev: &Self::Device) -> &device::Device;
+
+ /// Register the driver.
+ ///
+ /// This is the unified interface for driver registration. It delegates to
+ /// `register_bus_driver` which is implemented by each specific bus type.
+ ///
+ /// The safety requirements are satisfied by the caller (typically `Adapter::register`),
+ /// which is marked as `unsafe` and ensures that `reg` is a valid pointer to the driver
+ /// registration structure. The implementation itself only calls non-unsafe functions.
+ fn register<T: DriverGeneric<Self> + RtcOperations + 'static>(
+ reg: &Opaque<Self::RegType>,
+ name: &'static CStr,
+ module: &'static ThisModule,
+ ) -> Result
+ where
+ Self: BusRegistration<T>;
+
+ /// Unregister the driver.
+ ///
+ /// This is the unified interface for driver unregistration. It delegates to
+ /// `unregister_bus_driver` which is implemented by each specific bus type.
+ ///
+ /// The safety requirements are satisfied by the caller (typically `Adapter::unregister`),
+ /// which is marked as `unsafe` and ensures that `reg` is a valid pointer that was previously
+ /// registered with `register`. The implementation itself only calls non-unsafe functions.
+ fn unregister<T: DriverGeneric<Self> + RtcOperations + 'static>(reg: &Opaque<Self::RegType>)
+ where
+ Self: BusRegistration<T>;
+}
+
+/// Trait for bus-specific driver registration.
+///
+/// Each bus type (Platform, AMBA, etc.) implements this trait to handle
+/// bus-specific registration requirements, including device tables and callbacks.
+/// Different bus types may use different device table types:
+/// - Platform bus: `of_table` and `acpi_table`
+/// - AMBA bus: `id_table`
+pub trait BusRegistration<T: DriverGeneric<Self>>: Bus {
+ /// Register the bus driver with device tables and callbacks.
+ ///
+ /// This method is implemented by each specific bus type (PlatformBus, AmbaBus, etc.)
+ /// to handle bus-specific registration requirements.
+ ///
+ /// The safety requirements are satisfied by the caller (`Bus::register`), which is
+ /// marked as `unsafe` and ensures that `reg` is a valid pointer to the driver
+ /// registration structure, and that `probe_fn` and `remove_fn` are valid callback
+ /// functions.
+ fn register_bus_driver(
+ reg: &Opaque<Self::RegType>,
+ name: &'static CStr,
+ module: &'static ThisModule,
+ probe_fn: Self::ProbeFn,
+ remove_fn: Self::RemoveFn,
+ ) -> Result;
+
+ /// Unregister the bus driver.
+ ///
+ /// This method is implemented by each specific bus type (PlatformBus, AmbaBus, etc.)
+ /// to handle bus-specific unregistration.
+ ///
+ /// The safety requirements are satisfied by the caller (`Bus::unregister`), which is
+ /// marked as `unsafe` and ensures that `reg` is a valid pointer that was previously
+ /// registered with `register_bus_driver`.
+ fn unregister_bus_driver(reg: &Opaque<Self::RegType>);
+
+ /// Get the probe function for the driver.
+ ///
+ /// This method is implemented by each bus type to provide the probe function
+ /// with the appropriate trait bounds.
+ fn get_probe_fn() -> Self::ProbeFn
+ where
+ T: DriverGeneric<Self> + RtcOperations + 'static;
+
+ /// Get the remove function for the driver.
+ ///
+ /// This method is implemented by each bus type to provide the remove function
+ /// with the appropriate trait bounds.
+ fn get_remove_fn() -> Self::RemoveFn
+ where
+ T: DriverGeneric<Self> + RtcOperations + 'static;
+}
+
+/// Trait for Platform bus driver device tables.
+///
+/// Platform bus drivers should implement this trait to provide OF and ACPI device tables.
+/// This trait requires the driver to also implement `DriverGeneric<PlatformBus>`.
+pub trait PlatformIdInfos: DriverGeneric<PlatformBus> {
+ /// The table of OF device ids supported by the driver.
+ const OF_ID_TABLE: Option<of::IdTable<<Self as DriverGeneric<PlatformBus>>::IdInfo>>;
+
+ /// The table of ACPI device ids supported by the driver.
+ const ACPI_ID_TABLE: Option<acpi::IdTable<<Self as DriverGeneric<PlatformBus>>::IdInfo>>;
+}
+
+/// Macro to generate `impl Bus` for different bus types.
+///
+/// This macro reduces code duplication across PlatformBus, AmbaBus, and I2cBus.
+macro_rules! impl_bus {
+ (
+ $bus:ident,
+ Device = $device:ty,
+ RegType = $reg_type:ty,
+ RawDevice = $raw_device:ty,
+ ProbeFn = $probe_fn:ty,
+ RemoveFn = $remove_fn:ty,
+ from_raw_device = $from_raw_device:ty
+ ) => {
+ impl Bus for $bus {
+ type Device = $device;
+ type RegType = $reg_type;
+ type RawDevice = $raw_device;
+ type ProbeFn = $probe_fn;
+ type RemoveFn = $remove_fn;
+
+ fn register<T: DriverGeneric<Self> + RtcOperations + 'static>(
+ reg: &Opaque<Self::RegType>,
+ name: &'static CStr,
+ module: &'static ThisModule,
+ ) -> Result
+ where
+ Self: BusRegistration<T>,
+ {
+ // Get callbacks from Adapter
+ // We use BusRegistration::get_probe_fn and get_remove_fn to get the callbacks
+ // These methods require the appropriate trait bounds
+ // The trait bound is satisfied because BusRegistration<T> requires the
+ // appropriate trait
+ let probe_fn = <Self as BusRegistration<T>>::get_probe_fn();
+ let remove_fn = <Self as BusRegistration<T>>::get_remove_fn();
+
+ // Delegate to bus-specific registration.
+ // Each bus type (PlatformBus, AmbaBus, etc.) will extract the appropriate
+ // device tables from T in its register_bus_driver implementation.
+ Self::register_bus_driver(reg, name, module, probe_fn, remove_fn)
+ }
+
+ fn id_info<T: DriverGeneric<Self>>(
+ _dev: &device::Device,
+ ) -> Option<&'static T::IdInfo> {
+ // ID info is extracted in `Adapter::probe_callback` instead.
+ None
+ }
+
+ fn from_raw<'a>(raw: *mut Self::RawDevice) -> &'a Self::Device {
+ // SAFETY: Caller guarantees `raw` is valid.
+ // `Device` is `#[repr(transparent)]`, so `CoreInternal` and `Core` have the
+ // same layout.
+ unsafe { &*raw.cast::<$from_raw_device>() }
+ }
+
+ fn as_device(dev: &Self::Device) -> &device::Device {
+ dev.as_ref()
+ }
+
+ fn unregister<T: DriverGeneric<Self> + RtcOperations + 'static>(
+ reg: &Opaque<Self::RegType>,
+ ) where
+ Self: BusRegistration<T>,
+ {
+ // Delegate to bus-specific unregistration
+ Self::unregister_bus_driver(reg)
+ }
+ }
+ };
+}
+
+/// Platform bus implementation.
+pub struct PlatformBus;
+
+impl_bus!(
+ PlatformBus,
+ Device = platform::Device<device::Core>,
+ RegType = bindings::platform_driver,
+ RawDevice = bindings::platform_device,
+ ProbeFn = unsafe extern "C" fn(*mut bindings::platform_device) -> c_int,
+ RemoveFn = unsafe extern "C" fn(*mut bindings::platform_device),
+ from_raw_device = platform::Device<device::Core>
+);
+
+impl<T: DriverGeneric<PlatformBus> + PlatformIdInfos> BusRegistration<T> for PlatformBus {
+ fn get_probe_fn() -> Self::ProbeFn
+ where
+ T: DriverGeneric<Self> + RtcOperations + 'static,
+ {
+ <Adapter<T, Self>>::probe_callback
+ }
+
+ fn get_remove_fn() -> Self::RemoveFn
+ where
+ T: DriverGeneric<Self> + RtcOperations + 'static,
+ {
+ <Adapter<T, Self>>::remove_callback
+ }
+
+ fn register_bus_driver(
+ reg: &Opaque<Self::RegType>,
+ name: &'static CStr,
+ module: &'static ThisModule,
+ probe_fn: Self::ProbeFn,
+ remove_fn: Self::RemoveFn,
+ ) -> Result {
+ // Get device tables from the driver (platform-specific: OF and ACPI)
+ let of_table = match <T as PlatformIdInfos>::OF_ID_TABLE {
+ Some(table) => table.as_ptr(),
+ None => core::ptr::null(),
+ };
+
+ let acpi_table = match <T as PlatformIdInfos>::ACPI_ID_TABLE {
+ Some(table) => table.as_ptr(),
+ None => core::ptr::null(),
+ };
+
+ // SAFETY: It's safe to set the fields of `struct platform_driver` on initialization.
+ unsafe {
+ (*reg.get()).driver.name = name.as_char_ptr();
+ (*reg.get()).probe = Some(probe_fn);
+ (*reg.get()).remove = Some(remove_fn);
+ (*reg.get()).driver.of_match_table = of_table;
+ (*reg.get()).driver.acpi_match_table = acpi_table;
+ }
+
+ // SAFETY: `reg` is guaranteed to be a valid `RegType`.
+ to_result(unsafe { bindings::__platform_driver_register(reg.get(), module.0) })
+ }
+
+ fn unregister_bus_driver(reg: &Opaque<Self::RegType>) {
+ // SAFETY: `reg` is guaranteed to be a valid `RegType` that was previously registered.
+ unsafe { bindings::platform_driver_unregister(reg.get()) };
+ }
+}
+
+/// AMBA bus implementation.
+pub struct AmbaBus;
+
+impl_bus!(
+ AmbaBus,
+ Device = amba::Device<device::Core>,
+ RegType = bindings::amba_driver,
+ RawDevice = bindings::amba_device,
+ ProbeFn = unsafe extern "C" fn(*mut bindings::amba_device, *const bindings::amba_id) -> c_int,
+ RemoveFn = unsafe extern "C" fn(*mut bindings::amba_device),
+ from_raw_device = amba::Device<device::Core>
+);
+
+/// Trait for AMBA bus driver device tables.
+///
+/// AMBA bus drivers should implement this trait to provide AMBA device ID tables.
+/// This trait requires the driver to also implement `DriverGeneric<AmbaBus>`.
+pub trait AmbaIdInfos: DriverGeneric<AmbaBus> {
+ /// The table of AMBA device ids supported by the driver.
+ const ID_TABLE: Option<amba::IdTable<<Self as DriverGeneric<AmbaBus>>::IdInfo>>;
+}
+
+impl<T: DriverGeneric<AmbaBus> + AmbaIdInfos> BusRegistration<T> for AmbaBus {
+ fn get_probe_fn() -> Self::ProbeFn
+ where
+ T: DriverGeneric<Self> + RtcOperations + 'static,
+ {
+ <Adapter<T, Self>>::probe_callback
+ }
+
+ fn get_remove_fn() -> Self::RemoveFn
+ where
+ T: DriverGeneric<Self> + RtcOperations + 'static,
+ {
+ <Adapter<T, Self>>::remove_callback
+ }
+
+ fn register_bus_driver(
+ reg: &Opaque<Self::RegType>,
+ name: &'static CStr,
+ module: &'static ThisModule,
+ probe_fn: Self::ProbeFn,
+ remove_fn: Self::RemoveFn,
+ ) -> Result {
+ // Get device table from the driver (AMBA-specific: id_table)
+ let id_table = match <T as AmbaIdInfos>::ID_TABLE {
+ Some(table) => table.as_ptr(),
+ None => core::ptr::null(),
+ };
+
+ // SAFETY: It's safe to set the fields of `struct amba_driver` on initialization.
+ unsafe {
+ (*reg.get()).drv.name = name.as_char_ptr();
+ (*reg.get()).probe = Some(probe_fn);
+ (*reg.get()).remove = Some(remove_fn);
+ (*reg.get()).id_table = id_table;
+ }
+
+ // SAFETY: `reg` is guaranteed to be a valid `RegType`.
+ to_result(unsafe { bindings::__amba_driver_register(reg.get(), module.0) })
+ }
+
+ fn unregister_bus_driver(reg: &Opaque<Self::RegType>) {
+ // SAFETY: `reg` is guaranteed to be a valid `RegType` that was previously registered.
+ unsafe { bindings::amba_driver_unregister(reg.get()) };
+ }
+}
+
+/// I2C bus implementation.
+pub struct I2cBus;
+
+impl_bus!(
+ I2cBus,
+ Device = i2c::I2cClient<device::Core>,
+ RegType = bindings::i2c_driver,
+ RawDevice = bindings::i2c_client,
+ ProbeFn = unsafe extern "C" fn(*mut bindings::i2c_client) -> c_int,
+ RemoveFn = unsafe extern "C" fn(*mut bindings::i2c_client),
+ from_raw_device = i2c::I2cClient<device::Core>
+);
+
+/// Trait for I2C bus driver device tables.
+///
+/// I2C bus drivers should implement this trait to provide I2C device ID tables.
+/// This trait requires the driver to also implement `DriverGeneric<I2cBus>`.
+pub trait I2cIdInfos: DriverGeneric<I2cBus> {
+ /// The table of I2C device ids supported by the driver.
+ const I2C_ID_TABLE: Option<i2c::IdTable<<Self as DriverGeneric<I2cBus>>::IdInfo>>;
+ /// The table of OF device ids supported by the driver.
+ const OF_ID_TABLE: Option<of::IdTable<<Self as DriverGeneric<I2cBus>>::IdInfo>> = None;
+ /// The table of ACPI device ids supported by the driver.
+ const ACPI_ID_TABLE: Option<acpi::IdTable<<Self as DriverGeneric<I2cBus>>::IdInfo>> = None;
+}
+
+impl<T: DriverGeneric<I2cBus> + I2cIdInfos> BusRegistration<T> for I2cBus {
+ fn get_probe_fn() -> Self::ProbeFn
+ where
+ T: DriverGeneric<Self> + RtcOperations + 'static,
+ {
+ <Adapter<T, Self>>::probe_callback
+ }
+
+ fn get_remove_fn() -> Self::RemoveFn
+ where
+ T: DriverGeneric<Self> + RtcOperations + 'static,
+ {
+ <Adapter<T, Self>>::remove_callback
+ }
+
+ fn register_bus_driver(
+ reg: &Opaque<Self::RegType>,
+ name: &'static CStr,
+ module: &'static ThisModule,
+ probe_fn: Self::ProbeFn,
+ remove_fn: Self::RemoveFn,
+ ) -> Result {
+ // Get device tables from the driver (I2C-specific: id_table, of_table, acpi_table)
+ let i2c_table = match <T as I2cIdInfos>::I2C_ID_TABLE {
+ Some(table) => table.as_ptr(),
+ None => core::ptr::null(),
+ };
+
+ let of_table = match <T as I2cIdInfos>::OF_ID_TABLE {
+ Some(table) => table.as_ptr(),
+ None => core::ptr::null(),
+ };
+
+ let acpi_table = match <T as I2cIdInfos>::ACPI_ID_TABLE {
+ Some(table) => table.as_ptr(),
+ None => core::ptr::null(),
+ };
+
+ // SAFETY: It's safe to set the fields of `struct i2c_driver` on initialization.
+ unsafe {
+ (*reg.get()).driver.name = name.as_char_ptr();
+ (*reg.get()).probe = Some(probe_fn);
+ (*reg.get()).remove = Some(remove_fn);
+ (*reg.get()).id_table = i2c_table;
+ (*reg.get()).driver.of_match_table = of_table;
+ (*reg.get()).driver.acpi_match_table = acpi_table;
+ }
+
+ // SAFETY: `reg` is guaranteed to be a valid `RegType`.
+ to_result(unsafe { bindings::i2c_register_driver(module.0, reg.get()) })
+ }
+
+ fn unregister_bus_driver(reg: &Opaque<Self::RegType>) {
+ // SAFETY: `reg` is guaranteed to be a valid `RegType` that was previously registered.
+ unsafe { bindings::i2c_del_driver(reg.get()) };
+ }
+}
+
+/// The RTC driver trait, generic over bus type.
+///
+/// RTC drivers can be registered on different bus types (platform, amba, etc.).
+/// The `B` type parameter specifies which bus type the driver uses.
+///
+/// For backward compatibility, the `Driver` trait is provided as an alias for
+/// `DriverGeneric<PlatformBus>`.
+///
+/// Note: Device tables (OF, ACPI, etc.) are defined in bus-specific traits
+/// (e.g., `PlatformIdInfos` for Platform bus).
+pub trait DriverGeneric<B: Bus>: Send {
+ /// The type holding driver private data about each device id supported by the driver.
+ type IdInfo: 'static;
+
+ /// RTC driver probe.
+ ///
+ /// Called when a new device is bound to this driver.
+ /// Implementers should initialize the driver's private data here.
+ /// The RTC device registration is handled automatically by the adapter.
+ fn probe(dev: &B::Device, id_info: Option<&Self::IdInfo>) -> impl PinInit<Self, Error>;
+
+ /// Initialize the RTC device after allocation but before registration.
+ ///
+ /// This method is called by the adapter to initialize the RTC device.
+ /// The `id_info` parameter contains the device ID information that was matched
+ /// during probe, which can be used to configure device-specific settings.
+ /// The `drvdata` parameter provides mutable access to the driver's private data,
+ /// which must be obtained via `Device::drvdata_borrow()`.
+ /// The `rtc` parameter provides access to the RTC device, from which the parent
+ /// bus device can be obtained via `rtc.bound_parent_device()`.
+ /// The default implementation does nothing.
+ fn init_rtcdevice(
+ _rtc: &RtcDevice,
+ _drvdata: &mut Self,
+ _id_info: Option<&Self::IdInfo>,
+ ) -> Result {
+ Ok(())
+ }
+
+ /// Get the RTC device options.
+ ///
+ /// Returns the options for creating the RTC device.
+ fn rtc_options() -> RtcDeviceOptions {
+ RtcDeviceOptions {
+ name: crate::c_str!("rtc"),
+ }
+ }
+
+ /// RTC driver unbind.
+ ///
+ /// Called when the device is about to be unbound from this driver.
+ fn unbind(dev: &B::Device, data: Pin<&Self>) {
+ let _ = (dev, data);
+ }
+}
+
+/// An adapter for the registration of RTC drivers.
+///
+/// RTC drivers can be registered on different bus types (platform, amba, etc.).
+/// The adapter wraps the bus-specific driver registration mechanism while providing
+/// RTC-specific semantics.
+pub struct Adapter<T: DriverGeneric<B>, B: Bus>(PhantomData<(T, B)>);
+
+// SAFETY: A call to `unregister` for a given instance of `RegType` is guaranteed to be valid if
+// a preceding call to `register` has been successful.
+unsafe impl<T, B> driver::RegistrationOps for Adapter<T, B>
+where
+ T: DriverGeneric<B> + RtcOperations + 'static,
+ B: Bus + BusRegistration<T>,
+{
+ type RegType = B::RegType;
+
+ unsafe fn register(
+ reg: &Opaque<Self::RegType>,
+ name: &'static CStr,
+ module: &'static ThisModule,
+ ) -> Result {
+ // Delegate to the bus-specific registration.
+ // The bus implementation will handle device tables and callbacks internally.
+ B::register::<T>(reg, name, module)
+ }
+
+ unsafe fn unregister(reg: &Opaque<Self::RegType>) {
+ // Delegate to the bus-specific unregistration.
+ B::unregister::<T>(reg)
+ }
+}
+
+// PlatformBus-specific probe callback (single parameter)
+impl<T: DriverGeneric<PlatformBus> + PlatformIdInfos + RtcOperations + 'static>
+ Adapter<T, PlatformBus>
+{
+ extern "C" fn probe_callback(raw: *mut bindings::platform_device) -> c_int {
+ // SAFETY: The bus only ever calls the probe callback with a valid pointer.
+ // `Device` is `#[repr(transparent)]`, so `CoreInternal` and `Core` have the same layout.
+ let dev = unsafe { &*raw.cast::<platform::Device<device::Core>>() };
+ let dev_ref = PlatformBus::as_device(dev);
+
+ // Get ID info from the bus
+ let info = PlatformBus::id_info::<T>(dev_ref);
+
+ Self::probe_impl(dev, info)
+ }
+}
+
+// AmbaBus-specific probe callback (two parameters)
+impl<T: DriverGeneric<AmbaBus> + AmbaIdInfos + RtcOperations + 'static> Adapter<T, AmbaBus> {
+ extern "C" fn probe_callback(
+ raw: *mut bindings::amba_device,
+ id: *const bindings::amba_id,
+ ) -> c_int {
+ // SAFETY: The bus only ever calls the probe callback with a valid pointer.
+ // `Device` is `#[repr(transparent)]`, so `CoreInternal` and `Core` have the same layout.
+ let dev = unsafe { &*raw.cast::<amba::Device<device::Core>>() };
+
+ let info = NonNull::new(id.cast_mut()).and_then(|id_ptr| {
+ // SAFETY: `DeviceId` is `#[repr(transparent)]` over `amba_id`, so it's safe to cast.
+ let device_id = unsafe { &*id_ptr.as_ptr().cast::<amba::DeviceId>() };
+ <T as AmbaIdInfos>::ID_TABLE.map(|table| {
+ table.info(<amba::DeviceId as crate::device_id::RawDeviceIdIndex>::index(device_id))
+ })
+ });
+
+ Self::probe_impl(dev, info)
+ }
+}
+
+// I2cBus-specific probe callback (single parameter)
+impl<T: DriverGeneric<I2cBus> + I2cIdInfos + RtcOperations + 'static> Adapter<T, I2cBus> {
+ extern "C" fn probe_callback(raw: *mut bindings::i2c_client) -> c_int {
+ // SAFETY: The bus only ever calls the probe callback with a valid pointer.
+ // `Device` is `#[repr(transparent)]`, so `CoreInternal` and `Core` have the same layout.
+ let dev = unsafe { &*raw.cast::<i2c::I2cClient<device::Core>>() };
+
+ // Try I2C ID table first, then fall back to OF/ACPI.
+ let info = Self::i2c_id_info(dev).or_else(|| {
+ let dev_ref = I2cBus::as_device(dev);
+ <Adapter<T, I2cBus> as driver::Adapter>::id_info(dev_ref)
+ });
+
+ Self::probe_impl(dev, info)
+ }
+
+ /// The [`i2c::IdTable`] of the corresponding driver.
+ fn i2c_id_table() -> Option<i2c::IdTable<<Self as driver::Adapter>::IdInfo>> {
+ <T as I2cIdInfos>::I2C_ID_TABLE
+ }
+
+ /// Returns the driver's private data from the matching entry in the [`i2c::IdTable`], if any.
+ ///
+ /// If this returns `None`, it means there is no match with an entry in the [`i2c::IdTable`].
+ fn i2c_id_info(dev: &i2c::I2cClient) -> Option<&'static <Self as driver::Adapter>::IdInfo> {
+ let table = Self::i2c_id_table()?;
+
+ let dev_ref = dev.as_ref();
+ let raw_client = dev_ref.as_raw().cast::<bindings::i2c_client>();
+
+ // SAFETY: `table` has static lifetime, hence it's valid for reads.
+ // `dev` is guaranteed to be valid while it's alive, and so is `raw_client`.
+ let raw_id = unsafe { bindings::i2c_match_id(table.as_ptr(), raw_client) };
+
+ if raw_id.is_null() {
+ return None;
+ }
+
+ // SAFETY: `DeviceId` is a `#[repr(transparent)]` wrapper of `struct i2c_device_id` and
+ // does not add additional invariants, so it's safe to cast.
+ let id = unsafe { &*raw_id.cast::<i2c::DeviceId>() };
+
+ Some(table.info(<i2c::DeviceId as crate::device_id::RawDeviceIdIndex>::index(id)))
+ }
+}
+
+// Common probe implementation
+impl<T: DriverGeneric<B> + RtcOperations + 'static, B: Bus> Adapter<T, B> {
+ fn probe_impl(dev: &B::Device, info: Option<&T::IdInfo>) -> c_int {
+ from_result(|| {
+ let data = T::probe(dev, info);
+ let dev_ref = B::as_device(dev);
+
+ // Store the driver data in the device first.
+ // SAFETY: `Device<Core>` and `Device<CoreInternal>` have the same layout, and
+ // `from_ref(dev_ref)` is guaranteed to be valid.
+ let dev_internal = unsafe {
+ &*core::ptr::from_ref(dev_ref).cast::<device::Device<device::CoreInternal>>()
+ };
+ dev_internal.set_drvdata(data)?;
+
+ // Allocate RTC device.
+ let dev_raw = dev_ref.as_raw();
+ // SAFETY: `devm_rtc_allocate_device` returns a pointer to a devm-managed rtc_device.
+ let rtc: *mut bindings::rtc_device =
+ unsafe { bindings::devm_rtc_allocate_device(dev_raw) };
+ if rtc.is_null() {
+ return Err(code::ENOMEM);
+ }
+
+ // Initialize the RTC device.
+ // SAFETY: `rtc` is a valid pointer to a newly allocated rtc_device.
+ // `RtcDevice` is `#[repr(transparent)]` over `Opaque<rtc_device>`, so we can safely
+ // cast.
+ let rtc_device = unsafe { &*rtc.cast() };
+ // SAFETY: `dev_internal` is a valid device with driver data set by `set_drvdata` above.
+ let drvdata_ptr = unsafe { bindings::dev_get_drvdata(dev_internal.as_raw()) };
+ // SAFETY: `drvdata_ptr` is a valid pointer to `T` that was set by `set_drvdata` above.
+ let drvdata = unsafe { &mut *drvdata_ptr.cast::<T>() };
+ T::init_rtcdevice(rtc_device, drvdata, info)?;
+
+ // Set the RTC device ops.
+ // SAFETY: We just allocated the RTC device, so it's safe to set the ops.
+ unsafe {
+ (*rtc).ops = &RtcVTable::<T>::VTABLE;
+ }
+
+ // Register the RTC device.
+ // SAFETY: The device will be automatically unregistered when the parent device
+ // is removed (devm cleanup). The helper function uses `THIS_MODULE` internally.
+ let err = unsafe { bindings::devm_rtc_register_device(rtc) };
+
+ if err != 0 {
+ return Err(Error::from_errno(err));
+ }
+
+ Ok(0)
+ })
+ }
+
+ extern "C" fn remove_callback(raw: *mut B::RawDevice) {
+ // The bus only ever calls the remove callback with a valid pointer.
+ let dev = B::from_raw(raw);
+
+ let dev_ref = B::as_device(dev);
+ // SAFETY: `Device<Core>` and `Device<CoreInternal>` have the same layout.
+ let dev_internal = unsafe {
+ &*core::ptr::from_ref(dev_ref).cast::<device::Device<device::CoreInternal>>()
+ };
+ // SAFETY: Driver data has been set during probe.
+ let data = unsafe { dev_internal.drvdata_obtain::<T>() };
+
+ T::unbind(dev, data.as_ref());
+ }
+}
+
+// Implementation for PlatformBus
+impl<T: DriverGeneric<PlatformBus> + PlatformIdInfos + RtcOperations + 'static> driver::Adapter
+ for Adapter<T, PlatformBus>
+{
+ type IdInfo = T::IdInfo;
+
+ fn of_id_table() -> Option<of::IdTable<Self::IdInfo>> {
+ <T as PlatformIdInfos>::OF_ID_TABLE
+ }
+
+ fn acpi_id_table() -> Option<acpi::IdTable<Self::IdInfo>> {
+ <T as PlatformIdInfos>::ACPI_ID_TABLE
+ }
+}
+
+// Implementation for I2cBus
+impl<T: DriverGeneric<I2cBus> + I2cIdInfos + RtcOperations + 'static> driver::Adapter
+ for Adapter<T, I2cBus>
+{
+ type IdInfo = T::IdInfo;
+
+ fn of_id_table() -> Option<of::IdTable<Self::IdInfo>> {
+ <T as I2cIdInfos>::OF_ID_TABLE
+ }
+
+ fn acpi_id_table() -> Option<acpi::IdTable<Self::IdInfo>> {
+ <T as I2cIdInfos>::ACPI_ID_TABLE
+ }
+}
+
+/// Options for creating an RTC device.
+#[derive(Copy, Clone)]
+pub struct RtcDeviceOptions {
+ /// The name of the RTC device.
+ pub name: &'static CStr,
+}
+
+/// Trait implemented by RTC device operations.
+///
+/// This trait defines the operations that an RTC device driver must implement.
+/// Most methods are optional and have default implementations that return an error.
+#[vtable]
+pub trait RtcOperations: Sized {
+ /// What kind of pointer should `Self` be wrapped in.
+ type Ptr: ForeignOwnable + Send + Sync;
+
+ /// Read the current time from the RTC.
+ ///
+ /// This is a required method and must be implemented.
+ fn read_time(drvdata: <Self::Ptr as ForeignOwnable>::Borrowed<'_>, tm: &mut RtcTime) -> Result;
+
+ /// Set the time in the RTC.
+ ///
+ /// This is a required method and must be implemented.
+ ///
+ /// Note: The parameter is `&mut` to match the C API signature, even though
+ /// it's conceptually read-only from the Rust side.
+ fn set_time(drvdata: <Self::Ptr as ForeignOwnable>::Borrowed<'_>, tm: &mut RtcTime) -> Result;
+
+ /// Read the alarm time from the RTC.
+ fn read_alarm(
+ _drvdata: <Self::Ptr as ForeignOwnable>::Borrowed<'_>,
+ _alarm: &mut RtcWkAlrm,
+ ) -> Result {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Set the alarm time in the RTC.
+ ///
+ /// Note: The parameter is `&mut` to match the C API signature, even though
+ /// it's conceptually read-only from the Rust side.
+ fn set_alarm(
+ _drvdata: <Self::Ptr as ForeignOwnable>::Borrowed<'_>,
+ _alarm: &mut RtcWkAlrm,
+ ) -> Result {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Enable or disable the alarm interrupt.
+ ///
+ /// `enabled` is non-zero to enable, zero to disable.
+ fn alarm_irq_enable(
+ _drvdata: <Self::Ptr as ForeignOwnable>::Borrowed<'_>,
+ _enabled: u32,
+ ) -> Result {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Handle custom ioctl commands.
+ fn ioctl(
+ _drvdata: <Self::Ptr as ForeignOwnable>::Borrowed<'_>,
+ _cmd: u32,
+ _arg: c_ulong,
+ ) -> Result<c_int> {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Show information in /proc/driver/rtc.
+ fn proc(_drvdata: <Self::Ptr as ForeignOwnable>::Borrowed<'_>, _seq: &mut SeqFile) -> Result {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Read the time offset.
+ fn read_offset(
+ _drvdata: <Self::Ptr as ForeignOwnable>::Borrowed<'_>,
+ _offset: &mut i64,
+ ) -> Result {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Set the time offset.
+ fn set_offset(_drvdata: <Self::Ptr as ForeignOwnable>::Borrowed<'_>, _offset: i64) -> Result {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Get an RTC parameter.
+ fn param_get(
+ _drvdata: <Self::Ptr as ForeignOwnable>::Borrowed<'_>,
+ _param: &mut RtcParam,
+ ) -> Result {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Set an RTC parameter.
+ ///
+ /// Note: The parameter is `&mut` to match the C API signature, even though
+ /// it's conceptually read-only from the Rust side.
+ fn param_set(
+ _drvdata: <Self::Ptr as ForeignOwnable>::Borrowed<'_>,
+ _param: &mut RtcParam,
+ ) -> Result {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+}
+
+/// A vtable for the RTC operations of a Rust RTC device.
+pub struct RtcVTable<T: RtcOperations>(PhantomData<T>);
+
+impl<T: RtcOperations> RtcVTable<T> {
+ /// Get the vtable for the RTC operations.
+ pub const fn get() -> &'static bindings::rtc_class_ops {
+ &Self::VTABLE
+ }
+
+ /// # Safety
+ ///
+ /// `dev` must be a valid pointer to the platform device's `struct device`
+ /// (rtc->dev.parent) that has driver_data set to a pointer returned by
+ /// `T::Ptr::into_foreign`. `tm` must be a valid pointer to a `struct rtc_time`.
+ unsafe extern "C" fn read_time(
+ dev: *mut bindings::device,
+ tm: *mut bindings::rtc_time,
+ ) -> c_int {
+ // SAFETY: The caller ensures that `dev` is valid and has driver_data set to a pointer
+ // returned by `T::Ptr::into_foreign`.
+ let private = unsafe { bindings::dev_get_drvdata(dev) };
+ if private.is_null() {
+ return code::ENODEV.to_errno();
+ }
+ // SAFETY: RTC operations can borrow the private data of the device.
+ let drvdata = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };
+ // SAFETY: The caller ensures that `tm` is valid and writable.
+ // `RtcTime` is `#[repr(transparent)]` over `bindings::rtc_time`, so we can safely cast.
+ let rtc_tm = unsafe { &mut *tm.cast::<RtcTime>() };
+
+ match T::read_time(drvdata, rtc_tm) {
+ Ok(()) => 0,
+ Err(err) => err.to_errno(),
+ }
+ }
+
+ /// # Safety
+ ///
+ /// `dev` must be a valid pointer to the platform device's `struct device`
+ /// (rtc->dev.parent) that has driver_data set to a pointer returned by
+ /// `T::Ptr::into_foreign`. `tm` must be a valid pointer to a `struct rtc_time`.
+ unsafe extern "C" fn set_time(
+ dev: *mut bindings::device,
+ tm: *mut bindings::rtc_time,
+ ) -> c_int {
+ // SAFETY: The caller ensures that `dev` is valid and has driver_data set to a pointer
+ // returned by `T::Ptr::into_foreign`.
+ let private = unsafe { bindings::dev_get_drvdata(dev) };
+ if private.is_null() {
+ return code::ENODEV.to_errno();
+ }
+ // SAFETY: RTC operations can borrow the private data of the device.
+ let drvdata = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };
+ // SAFETY: The caller ensures that `tm` is valid and writable.
+ // `RtcTime` is `#[repr(transparent)]` over `bindings::rtc_time`, so we can safely cast.
+ let rtc_tm = unsafe { &mut *tm.cast::<RtcTime>() };
+
+ match T::set_time(drvdata, rtc_tm) {
+ Ok(()) => 0,
+ Err(err) => err.to_errno(),
+ }
+ }
+
+ /// # Safety
+ ///
+ /// `dev` must be a valid pointer to the platform device's `struct device` (rtc->dev.parent)
+ /// that has driver_data set to a pointer returned by `T::Ptr::into_foreign`.
+ /// `alarm` must be a valid pointer to a `struct rtc_wkalrm`.
+ unsafe extern "C" fn read_alarm(
+ dev: *mut bindings::device,
+ alarm: *mut bindings::rtc_wkalrm,
+ ) -> c_int {
+ // SAFETY: The caller ensures that `dev` is valid and has driver_data set to a pointer
+ // returned by `T::Ptr::into_foreign`.
+ let private = unsafe { bindings::dev_get_drvdata(dev) };
+ if private.is_null() {
+ return code::ENODEV.to_errno();
+ }
+ // SAFETY: RTC operations can borrow the private data of the device.
+ let drvdata = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };
+ // SAFETY: The caller ensures that `alarm` is valid and writable.
+ // `RtcWkAlrm` is `#[repr(transparent)]` over `bindings::rtc_wkalrm`, so we can safely cast.
+ let rtc_alarm = unsafe { &mut *alarm.cast::<RtcWkAlrm>() };
+
+ match T::read_alarm(drvdata, rtc_alarm) {
+ Ok(()) => 0,
+ Err(err) => err.to_errno(),
+ }
+ }
+
+ /// # Safety
+ ///
+ /// `dev` must be a valid pointer to the platform device's `struct device` (rtc->dev.parent)
+ /// that has driver_data set to a pointer returned by `T::Ptr::into_foreign`.
+ /// `alarm` must be a valid pointer to a `struct rtc_wkalrm`.
+ unsafe extern "C" fn set_alarm(
+ dev: *mut bindings::device,
+ alarm: *mut bindings::rtc_wkalrm,
+ ) -> c_int {
+ // SAFETY: The caller ensures that `dev` is valid and has driver_data set to a pointer
+ // returned by `T::Ptr::into_foreign`.
+ let private = unsafe { bindings::dev_get_drvdata(dev) };
+ if private.is_null() {
+ return code::ENODEV.to_errno();
+ }
+ // SAFETY: RTC operations can borrow the private data of the device.
+ let drvdata = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };
+ // SAFETY: The caller ensures that `alarm` is valid and writable.
+ // `RtcWkAlrm` is `#[repr(transparent)]` over `bindings::rtc_wkalrm`, so we can safely cast.
+ let rtc_alarm = unsafe { &mut *alarm.cast::<RtcWkAlrm>() };
+
+ match T::set_alarm(drvdata, rtc_alarm) {
+ Ok(()) => 0,
+ Err(err) => err.to_errno(),
+ }
+ }
+
+ /// # Safety
+ ///
+ /// `dev` must be a valid pointer to the platform device's `struct device` (rtc->dev.parent)
+ /// that has driver_data set to a pointer returned by `T::Ptr::into_foreign`.
+ unsafe extern "C" fn alarm_irq_enable(dev: *mut bindings::device, enabled: c_uint) -> c_int {
+ // SAFETY: The caller ensures that `dev` is valid and has driver_data set to a pointer
+ // returned by `T::Ptr::into_foreign`.
+ let private = unsafe { bindings::dev_get_drvdata(dev) };
+ if private.is_null() {
+ return code::ENODEV.to_errno();
+ }
+ // SAFETY: RTC operations can borrow the private data of the device.
+ let drvdata = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };
+
+ match T::alarm_irq_enable(drvdata, enabled) {
+ Ok(()) => 0,
+ Err(err) => err.to_errno(),
+ }
+ }
+
+ /// # Safety
+ ///
+ /// `dev` must be a valid pointer to the platform device's `struct device` (rtc->dev.parent)
+ /// that has driver_data set to a pointer returned by `T::Ptr::into_foreign`.
+ unsafe extern "C" fn ioctl(dev: *mut bindings::device, cmd: c_uint, arg: c_ulong) -> c_int {
+ // SAFETY: The caller ensures that `dev` is valid and has driver_data set to a pointer
+ // returned by `T::Ptr::into_foreign`.
+ let private = unsafe { bindings::dev_get_drvdata(dev) };
+ if private.is_null() {
+ return code::ENODEV.to_errno();
+ }
+ // SAFETY: RTC operations can borrow the private data of the device.
+ let drvdata = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };
+
+ match T::ioctl(drvdata, cmd, arg) {
+ Ok(ret) => ret,
+ Err(err) => err.to_errno(),
+ }
+ }
+
+ /// # Safety
+ ///
+ /// `dev` must be a valid pointer to the platform device's `struct device`
+ /// (rtc->dev.parent) that has driver_data set to a pointer returned by
+ /// `T::Ptr::into_foreign`. `seq` must be a valid pointer to a `struct seq_file`.
+ unsafe extern "C" fn proc(dev: *mut bindings::device, seq: *mut bindings::seq_file) -> c_int {
+ // SAFETY: The caller ensures that `dev` is valid and has driver_data set to a pointer
+ // returned by `T::Ptr::into_foreign`.
+ let private = unsafe { bindings::dev_get_drvdata(dev) };
+ if private.is_null() {
+ return code::ENODEV.to_errno();
+ }
+ // SAFETY: RTC operations can borrow the private data of the device.
+ let drvdata = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };
+ // SAFETY: The caller ensures that `seq` is valid and writable.
+ let seq_file = unsafe { &mut *seq.cast::<SeqFile>() };
+
+ match T::proc(drvdata, seq_file) {
+ Ok(()) => 0,
+ Err(err) => err.to_errno(),
+ }
+ }
+
+ /// # Safety
+ ///
+ /// `dev` must be a valid pointer to the platform device's `struct device`
+ /// (rtc->dev.parent) that has driver_data set to a pointer returned by
+ /// `T::Ptr::into_foreign`. `offset` must be a valid pointer to a `long`.
+ unsafe extern "C" fn read_offset(dev: *mut bindings::device, offset: *mut c_long) -> c_int {
+ // SAFETY: The caller ensures that `dev` is valid and has driver_data set to a pointer
+ // returned by `T::Ptr::into_foreign`.
+ let private = unsafe { bindings::dev_get_drvdata(dev) };
+ if private.is_null() {
+ return code::ENODEV.to_errno();
+ }
+ // SAFETY: RTC operations can borrow the private data of the device.
+ let drvdata = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };
+ // SAFETY: The caller ensures that `offset` is valid and writable.
+ let mut offset_val: i64 = unsafe { *offset.cast() };
+
+ match T::read_offset(drvdata, &mut offset_val) {
+ Ok(()) => {
+ // SAFETY: The caller ensures that `offset` is valid and writable.
+ unsafe { *offset.cast() = offset_val as c_long };
+ 0
+ }
+ Err(err) => err.to_errno(),
+ }
+ }
+
+ /// # Safety
+ ///
+ /// `dev` must be a valid pointer to the platform device's `struct device` (rtc->dev.parent)
+ /// that has driver_data set to a pointer returned by `T::Ptr::into_foreign`.
+ unsafe extern "C" fn set_offset(dev: *mut bindings::device, offset: c_long) -> c_int {
+ // SAFETY: The caller ensures that `dev` is valid and has driver_data set to a pointer
+ // returned by `T::Ptr::into_foreign`.
+ let private = unsafe { bindings::dev_get_drvdata(dev) };
+ if private.is_null() {
+ return code::ENODEV.to_errno();
+ }
+ // SAFETY: RTC operations can borrow the private data of the device.
+ let drvdata = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };
+
+ match T::set_offset(drvdata, offset as i64) {
+ Ok(()) => 0,
+ Err(err) => err.to_errno(),
+ }
+ }
+
+ /// # Safety
+ ///
+ /// `dev` must be a valid pointer to the platform device's `struct device`
+ /// (rtc->dev.parent) that has driver_data set to a pointer returned by
+ /// `T::Ptr::into_foreign`. `param` must be a valid pointer to a `struct rtc_param`.
+ unsafe extern "C" fn param_get(
+ dev: *mut bindings::device,
+ param: *mut bindings::rtc_param,
+ ) -> c_int {
+ // SAFETY: The caller ensures that `dev` is valid and has driver_data set to a pointer
+ // returned by `T::Ptr::into_foreign`.
+ let private = unsafe { bindings::dev_get_drvdata(dev) };
+ if private.is_null() {
+ return code::ENODEV.to_errno();
+ }
+ // SAFETY: RTC operations can borrow the private data of the device.
+ let drvdata = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };
+ // SAFETY: The caller ensures that `param` is valid and writable.
+ // `RtcParam` is `#[repr(transparent)]` over `bindings::rtc_param`, so we can safely cast.
+ let rtc_param = unsafe { &mut *param.cast::<RtcParam>() };
+
+ match T::param_get(drvdata, rtc_param) {
+ Ok(()) => 0,
+ Err(err) => err.to_errno(),
+ }
+ }
+
+ /// # Safety
+ ///
+ /// `dev` must be a valid pointer to the platform device's `struct device`
+ /// (rtc->dev.parent) that has driver_data set to a pointer returned by
+ /// `T::Ptr::into_foreign`. `param` must be a valid pointer to a `struct rtc_param`.
+ unsafe extern "C" fn param_set(
+ dev: *mut bindings::device,
+ param: *mut bindings::rtc_param,
+ ) -> c_int {
+ // SAFETY: The caller ensures that `dev` is valid and has driver_data set to a pointer
+ // returned by `T::Ptr::into_foreign`.
+ let private = unsafe { bindings::dev_get_drvdata(dev) };
+ if private.is_null() {
+ return code::ENODEV.to_errno();
+ }
+ // SAFETY: RTC operations can borrow the private data of the device.
+ let drvdata = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };
+ // SAFETY: The caller ensures that `param` is valid and writable.
+ // `RtcParam` is `#[repr(transparent)]` over `bindings::rtc_param`, so we can safely cast.
+ let rtc_param = unsafe { &mut *param.cast::<RtcParam>() };
+
+ match T::param_set(drvdata, rtc_param) {
+ Ok(()) => 0,
+ Err(err) => err.to_errno(),
+ }
+ }
+
+ const VTABLE: bindings::rtc_class_ops = bindings::rtc_class_ops {
+ read_time: Some(Self::read_time),
+ set_time: Some(Self::set_time),
+ read_alarm: if T::HAS_READ_ALARM {
+ Some(Self::read_alarm)
+ } else {
+ None
+ },
+ set_alarm: if T::HAS_SET_ALARM {
+ Some(Self::set_alarm)
+ } else {
+ None
+ },
+ alarm_irq_enable: if T::HAS_ALARM_IRQ_ENABLE {
+ Some(Self::alarm_irq_enable)
+ } else {
+ None
+ },
+ ioctl: if T::HAS_IOCTL {
+ Some(Self::ioctl)
+ } else {
+ None
+ },
+ proc_: if T::HAS_PROC { Some(Self::proc) } else { None },
+ read_offset: if T::HAS_READ_OFFSET {
+ Some(Self::read_offset)
+ } else {
+ None
+ },
+ set_offset: if T::HAS_SET_OFFSET {
+ Some(Self::set_offset)
+ } else {
+ None
+ },
+ param_get: if T::HAS_PARAM_GET {
+ Some(Self::param_get)
+ } else {
+ None
+ },
+ param_set: if T::HAS_PARAM_SET {
+ Some(Self::param_set)
+ } else {
+ None
+ },
+ };
+}
+
+/// Declares a kernel module that exposes a single RTC driver.
+///
+/// This macro uses `module_driver!` with the RTC-specific [`Adapter`] to register
+/// an RTC driver. RTC drivers can be registered on different bus types (platform, amba, etc.).
+///
+/// # Examples
+///
+/// For Platform bus:
+/// ```ignore
+/// kernel::module_rtc_driver! {
+/// bus: PlatformBus,
+/// type: MyRtcDriver,
+/// name: "my_rtc",
+/// authors: ["Author name"],
+/// description: "My RTC driver",
+/// license: "GPL v2",
+/// }
+/// ```
+///
+/// For AMBA bus:
+/// ```ignore
+/// kernel::module_rtc_driver! {
+/// bus: AmbaBus,
+/// type: MyRtcDriver,
+/// name: "my_rtc",
+/// authors: ["Author name"],
+/// description: "My RTC driver",
+/// license: "GPL v2",
+/// }
+/// ```
+///
+/// For I2C bus:
+/// ```ignore
+/// kernel::module_rtc_driver! {
+/// bus: I2cBus,
+/// type: MyRtcDriver,
+/// name: "my_rtc",
+/// authors: ["Author name"],
+/// description: "My RTC driver",
+/// license: "GPL v2",
+/// }
+/// ```
+///
+/// The `bus` parameter is required. Valid bus types are `PlatformBus`, `AmbaBus`, and `I2cBus`.
+#[macro_export]
+macro_rules! module_rtc_driver {
+ // With explicit bus parameter (required)
+ (bus: $bus:ident, $($f:tt)*) => {
+ $crate::module_driver!(<T>, $crate::rtc::Adapter<T, $crate::rtc::$bus>, { $($f)* });
+ };
+}
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v1 2/4] rust: add device wakeup support
From: Ke Sun @ 2026-01-04 6:06 UTC (permalink / raw)
To: Alexandre Belloni, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich
Cc: linux-rtc, rust-for-linux, Alvin Sun, Ke Sun
In-Reply-To: <20260104060621.3757812-1-sunke@kylinos.cn>
Add device wakeup initialization support to the Rust kernel:
- Add wakeup-related headers to bindings_helper.h
- Add rust_helper_devm_device_init_wakeup helper function
- Add init_wakeup() method to Device<Bound> for resource-managed
wakeup initialization
- Add set_wake_irq() method to Device<Bound> for setting wakeup IRQ
This enables RTC drivers and other drivers to properly initialize
device wakeup capability.
Signed-off-by: Ke Sun <sunke@kylinos.cn>
---
rust/bindings/bindings_helper.h | 2 ++
rust/helpers/device.c | 7 +++++++
rust/kernel/device.rs | 35 +++++++++++++++++++++++++++++++++
3 files changed, 44 insertions(+)
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index fa697287cf71b..d6c2b06ac4107 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -88,6 +88,8 @@
#include <linux/workqueue.h>
#include <linux/xarray.h>
#include <trace/events/rust_sample.h>
+#include <linux/pm_wakeup.h>
+#include <linux/pm_wakeirq.h>
/*
* The driver-core Rust code needs to know about some C driver-core private
diff --git a/rust/helpers/device.c b/rust/helpers/device.c
index 9a4316bafedfb..cae26edd83696 100644
--- a/rust/helpers/device.c
+++ b/rust/helpers/device.c
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/device.h>
+#include <linux/pm_wakeup.h>
+#include <linux/pm_wakeirq.h>
int rust_helper_devm_add_action(struct device *dev,
void (*action)(void *),
@@ -25,3 +27,8 @@ void rust_helper_dev_set_drvdata(struct device *dev, void *data)
{
dev_set_drvdata(dev, data);
}
+
+int rust_helper_devm_device_init_wakeup(struct device *dev)
+{
+ return devm_device_init_wakeup(dev);
+}
diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs
index c79be2e2bfe38..c064111a24531 100644
--- a/rust/kernel/device.rs
+++ b/rust/kernel/device.rs
@@ -325,6 +325,41 @@ pub fn drvdata<T: 'static>(&self) -> Result<Pin<&T>> {
// - We've just checked that the type of the driver's private data is in fact `T`.
Ok(unsafe { self.drvdata_unchecked() })
}
+
+ /// Initialize device wakeup capability.
+ ///
+ /// Marks the device as wakeup-capable and enables wakeup. The wakeup capability is
+ /// automatically disabled when the device is removed (resource-managed).
+ ///
+ /// Returns `Ok(())` on success, or an error code on failure.
+ pub fn init_wakeup(&self) -> Result {
+ // SAFETY: `self.as_raw()` is a valid pointer to a `struct device`.
+ // The function is exported from bindings_helper module via pub use.
+ let ret = unsafe { bindings::devm_device_init_wakeup(self.as_raw()) };
+ if ret != 0 {
+ return Err(Error::from_errno(ret));
+ }
+ Ok(())
+ }
+
+ /// Set a device interrupt as a wake IRQ.
+ ///
+ /// Attaches the interrupt `irq` as a wake IRQ for this device. The wake IRQ is
+ /// automatically configured for wake-up from suspend. Must be called after
+ /// [`Device::init_wakeup`].
+ ///
+ /// Returns `Ok(())` on success, or an error code on failure.
+ pub fn set_wake_irq(&self, irq: i32) -> Result {
+ if irq < 0 {
+ return Err(crate::error::code::EINVAL);
+ }
+ // SAFETY: `self.as_raw()` is a valid pointer to a `struct device`.
+ let ret = unsafe { bindings::dev_pm_set_wake_irq(self.as_raw(), irq) };
+ if ret != 0 {
+ return Err(Error::from_errno(ret));
+ }
+ Ok(())
+ }
}
impl<Ctx: DeviceContext> Device<Ctx> {
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v1 1/4] rust: add AMBA bus abstractions
From: Ke Sun @ 2026-01-04 6:06 UTC (permalink / raw)
To: Alexandre Belloni, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich
Cc: linux-rtc, rust-for-linux, Alvin Sun, Ke Sun
In-Reply-To: <20260104060621.3757812-1-sunke@kylinos.cn>
Add Rust abstractions for the ARM AMBA bus, including:
- Device type wrapper for amba_device
- DeviceId for device matching
- TryFrom implementation for converting device::Device to amba::Device
- IRQ and I/O resource management methods
Signed-off-by: Ke Sun <sunke@kylinos.cn>
---
rust/bindings/bindings_helper.h | 1 +
rust/kernel/amba.rs | 234 ++++++++++++++++++++++++++++++++
rust/kernel/lib.rs | 2 +
3 files changed, 237 insertions(+)
create mode 100644 rust/kernel/amba.rs
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index a067038b4b422..fa697287cf71b 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -29,6 +29,7 @@
#include <linux/hrtimer_types.h>
#include <linux/acpi.h>
+#include <linux/amba/bus.h>
#include <drm/drm_device.h>
#include <drm/drm_drv.h>
#include <drm/drm_file.h>
diff --git a/rust/kernel/amba.rs b/rust/kernel/amba.rs
new file mode 100644
index 0000000000000..44528082ab44c
--- /dev/null
+++ b/rust/kernel/amba.rs
@@ -0,0 +1,234 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! ARM AMBA bus abstractions.
+//!
+//! C header: [`include/linux/amba/bus.h`](srctree/include/linux/amba/bus.h)
+
+use crate::{
+ bindings,
+ container_of,
+ device,
+ device_id::{
+ RawDeviceId,
+ RawDeviceIdIndex, //
+ },
+ io::{
+ mem::IoRequest,
+ resource::Resource, //
+ },
+ irq::{
+ self,
+ IrqRequest, //
+ },
+ prelude::*,
+ sync::aref::AlwaysRefCounted,
+ types::Opaque, //
+};
+use core::{
+ marker::PhantomData,
+ ptr::NonNull, //
+};
+
+/// Device ID table type for AMBA drivers.
+pub type IdTable<T> = &'static dyn kernel::device_id::IdTable<DeviceId, T>;
+
+/// AMBA device identifier.
+///
+/// Wraps the C `struct amba_id` from `include/linux/amba/bus.h`.
+#[repr(transparent)]
+#[derive(Clone, Copy)]
+pub struct DeviceId(pub(crate) bindings::amba_id);
+
+// SAFETY: `DeviceId` is a transparent wrapper over `amba_id` with no additional
+// invariants.
+unsafe impl RawDeviceId for DeviceId {
+ type RawType = bindings::amba_id;
+}
+
+// SAFETY: The offset matches the `data` field in `struct amba_id`.
+unsafe impl RawDeviceIdIndex for DeviceId {
+ const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::amba_id, data);
+
+ fn index(&self) -> usize {
+ self.0.data as usize
+ }
+}
+
+impl DeviceId {
+ /// Creates a new device ID from an AMBA device ID and mask.
+ ///
+ /// A driver binds to a device when `(hardware_device_id & mask) == id`.
+ #[inline(always)]
+ pub const fn new(id: u32, mask: u32) -> Self {
+ // SAFETY: FFI type is valid to be zero-initialized.
+ let mut amba: bindings::amba_id = unsafe { core::mem::zeroed() };
+ amba.id = id;
+ amba.mask = mask;
+ amba.data = core::ptr::null_mut();
+
+ Self(amba)
+ }
+
+ /// Creates a new device ID with driver-specific data.
+ #[inline(always)]
+ pub const fn new_with_data(id: u32, mask: u32, data: usize) -> Self {
+ // SAFETY: FFI type is valid to be zero-initialized.
+ let mut amba: bindings::amba_id = unsafe { core::mem::zeroed() };
+ amba.id = id;
+ amba.mask = mask;
+ amba.data = data as *mut core::ffi::c_void;
+
+ Self(amba)
+ }
+
+ /// Returns the device ID.
+ #[inline(always)]
+ pub const fn id(&self) -> u32 {
+ self.0.id
+ }
+
+ /// Returns the device ID mask.
+ #[inline(always)]
+ pub const fn mask(&self) -> u32 {
+ self.0.mask
+ }
+}
+
+/// Creates an AMBA device ID table with a module alias for modpost.
+#[macro_export]
+macro_rules! amba_device_table {
+ ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => {
+ const $table_name: $crate::device_id::IdArray<
+ $crate::amba::DeviceId,
+ $id_info_type,
+ { $table_data.len() },
+ > = $crate::device_id::IdArray::new($table_data);
+
+ $crate::module_device_table!("amba", $module_table_name, $table_name);
+ };
+}
+
+/// An AMBA device.
+///
+/// Wraps the C `struct amba_device` from `include/linux/amba/bus.h`.
+pub struct Device<Ctx: device::DeviceContext = device::Normal>(
+ Opaque<bindings::amba_device>,
+ PhantomData<Ctx>,
+);
+
+impl<Ctx: device::DeviceContext> Device<Ctx> {
+ /// Obtain the raw `struct amba_device` pointer.
+ pub fn as_raw(&self) -> *mut bindings::amba_device {
+ self.0.get()
+ }
+
+ /// Returns the memory resource.
+ pub fn resource(&self) -> Option<&Resource> {
+ // SAFETY: `self.as_raw()` returns a valid pointer to a `struct amba_device`.
+ let resource = unsafe { &raw mut (*self.as_raw()).res };
+ // SAFETY: `resource` is a valid pointer to a `struct resource`.
+ Some(unsafe { Resource::from_raw(resource) })
+ }
+}
+
+impl<Ctx: device::DeviceContext> AsRef<device::Device<Ctx>> for Device<Ctx> {
+ fn as_ref(&self) -> &device::Device<Ctx> {
+ // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a
+ // valid `struct amba_device`.
+ let dev = unsafe { &raw mut (*self.as_raw()).dev };
+
+ // SAFETY: `dev` points to a valid `struct device`.
+ unsafe { device::Device::from_raw(dev) }
+ }
+}
+
+// SAFETY: `Device` is a transparent wrapper that doesn't depend on its generic
+// argument.
+crate::impl_device_context_deref!(unsafe { Device });
+crate::impl_device_context_into_aref!(Device);
+
+impl<Ctx: device::DeviceContext> TryFrom<&device::Device<Ctx>> for &Device<Ctx> {
+ type Error = kernel::error::Error;
+
+ fn try_from(dev: &device::Device<Ctx>) -> Result<Self, Self::Error> {
+ // SAFETY: By the type invariant of `Device`, `dev.as_raw()` is a valid pointer
+ // to a `struct device`.
+ if !unsafe { bindings::dev_is_amba(dev.as_raw()) } {
+ return Err(crate::error::code::EINVAL);
+ }
+
+ // SAFETY: We've just verified that the bus type of `dev` equals
+ // `bindings::amba_bustype`, hence `dev` must be embedded in a valid
+ // `struct amba_device` as guaranteed by the corresponding C code.
+ let adev = unsafe { container_of!(dev.as_raw(), bindings::amba_device, dev) };
+
+ // SAFETY: `adev` is a valid pointer to a `struct amba_device`.
+ Ok(unsafe { &*adev.cast() })
+ }
+}
+
+impl Device<device::Core> {}
+
+impl Device<device::Bound> {
+ /// Returns an [`IoRequest`] for the memory resource.
+ pub fn io_request(&self) -> Option<IoRequest<'_>> {
+ self.resource()
+ // SAFETY: `resource` is valid for the lifetime of the `IoRequest`.
+ .map(|resource| unsafe { IoRequest::new(self.as_ref(), resource) })
+ }
+
+ /// Returns an [`IrqRequest`] for the IRQ at the given index.
+ pub fn irq_by_index(&self, index: u32) -> Result<IrqRequest<'_>> {
+ if index >= bindings::AMBA_NR_IRQS {
+ return Err(crate::error::code::EINVAL);
+ }
+
+ // SAFETY: `self.as_raw()` returns a valid pointer to a `struct amba_device`.
+ let irq = unsafe { (*self.as_raw()).irq[index as usize] };
+
+ if irq == 0 {
+ return Err(crate::error::code::ENXIO);
+ }
+
+ // SAFETY: `irq` is guaranteed to be a valid IRQ number for `&self`.
+ Ok(unsafe { IrqRequest::new(self.as_ref(), irq) })
+ }
+
+ /// Requests an IRQ at the given index and returns a [`irq::Registration`].
+ pub fn request_irq_by_index<'a, T: irq::Handler + 'static>(
+ &'a self,
+ flags: irq::Flags,
+ index: u32,
+ name: &'static CStr,
+ handler: impl PinInit<T, Error> + 'a,
+ ) -> Result<impl PinInit<irq::Registration<T>, Error> + 'a> {
+ let request = self.irq_by_index(index)?;
+
+ Ok(irq::Registration::<T>::new(request, flags, name, handler))
+ }
+}
+
+// SAFETY: `Device` instances are always reference-counted via the underlying
+// `device`.
+unsafe impl AlwaysRefCounted for Device {
+ fn inc_ref(&self) {
+ // SAFETY: A shared reference guarantees the refcount is non-zero.
+ unsafe { bindings::get_device(self.as_ref().as_raw()) };
+ }
+
+ unsafe fn dec_ref(obj: NonNull<Self>) {
+ // Use `put_device` directly since `amba_device_put` is just a wrapper
+ // around it.
+ let adev: *mut bindings::amba_device = obj.cast().as_ptr();
+ // SAFETY: `amba_device` contains `device` as its first field.
+ let dev: *mut bindings::device = unsafe { &raw mut (*adev).dev };
+ // SAFETY: The safety requirements guarantee that the refcount is non-zero.
+ unsafe { bindings::put_device(dev) }
+ }
+}
+
+// SAFETY: `Device` is reference-counted and can be released from any thread.
+unsafe impl Send for Device {}
+
+// SAFETY: All methods of `Device` (i.e., `Device<Normal>`) are thread-safe.
+unsafe impl Sync for Device {}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index f812cf1200428..3e557335fc5fe 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -66,6 +66,8 @@
pub mod acpi;
pub mod alloc;
+#[cfg(CONFIG_ARM_AMBA)]
+pub mod amba;
#[cfg(CONFIG_AUXILIARY_BUS)]
pub mod auxiliary;
pub mod bitmap;
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v1 0/4] rust: Add RTC driver support
From: Ke Sun @ 2026-01-04 6:06 UTC (permalink / raw)
To: Alexandre Belloni, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich
Cc: linux-rtc, rust-for-linux, Alvin Sun, Ke Sun
This patch series adds RTC (Real-Time Clock) driver support for the Rust
kernel, including the necessary infrastructure and a complete driver implementation
for the ARM AMBA PrimeCell 031 RTC.
The implementation provides a generic RTC framework supporting multiple bus types
(Platform, AMBA, I2C) and demonstrates its usage with a complete PL031 RTC driver.
---
v1:
- Add AMBA bus abstractions
- Add device wakeup support
- Add RTC core framework with multi-bus support
- Add PL031 RTC driver
---
Ke Sun (4):
rust: add AMBA bus abstractions
rust: add device wakeup support
rust: add RTC core abstractions and data structures
rust: add PL031 RTC driver
drivers/rtc/Kconfig | 11 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc_pl031_rust.rs | 529 ++++++++++
rust/bindings/bindings_helper.h | 3 +
rust/helpers/device.c | 7 +
rust/helpers/helpers.c | 1 +
rust/helpers/rtc.c | 9 +
rust/kernel/amba.rs | 234 +++++
rust/kernel/device.rs | 35 +
rust/kernel/lib.rs | 4 +
rust/kernel/rtc.rs | 1710 +++++++++++++++++++++++++++++++
11 files changed, 2544 insertions(+)
create mode 100644 drivers/rtc/rtc_pl031_rust.rs
create mode 100644 rust/helpers/rtc.c
create mode 100644 rust/kernel/amba.rs
create mode 100644 rust/kernel/rtc.rs
base-commit: 805f9a061372164d43ddef771d7cd63e3ba6d845
--
2.43.0
^ permalink raw reply
* Re: [PATCH] rtc: nvvrs: Add ARCH_TEGRA to the NV VRS RTC driver
From: Jon Hunter @ 2026-01-02 11:01 UTC (permalink / raw)
To: Peter Robinson, Alexandre Belloni, linux-rtc
Cc: Shubhi Garg, linux-tegra@vger.kernel.org
In-Reply-To: <20251222035651.433603-1-pbrobinson@gmail.com>
On 22/12/2025 03:56, Peter Robinson wrote:
> The NV VRS RTC driver currently is only supported on the
> Tegra platform so add a dep for ARCH_TEGRA and compile test
> so it doesn't show up universally across all arches/platforms.
>
> Fixes: 9d6d6b06933c8 ("rtc: nvvrs: add NVIDIA VRS RTC device driver")
> Cc: Shubhi Garg <shgarg@nvidia.com>
> Cc: Jon Hunter <jonathanh@nvidia.com>
> Signed-off-by: Peter Robinson <pbrobinson@gmail.com>
> ---
> drivers/rtc/Kconfig | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> index 50dc779f7f983..50ba48609d74e 100644
> --- a/drivers/rtc/Kconfig
> +++ b/drivers/rtc/Kconfig
> @@ -418,6 +418,7 @@ config RTC_DRV_SPACEMIT_P1
>
> config RTC_DRV_NVIDIA_VRS10
> tristate "NVIDIA VRS10 RTC device"
> + depends on ARCH_TEGRA || COMPILE_TEST
> help
> If you say yes here you will get support for the battery backed RTC device
> of NVIDIA VRS (Voltage Regulator Specification). The RTC is connected via
Looks good to me.
Acked-by: Jon Hunter <jonathanh@nvidia.com>
Thanks!
Jon
--
nvpublic
^ permalink raw reply
* [PATCH] rtc: interface: Fix softlockup in rtc_timer_do_work()
From: Jinjie Ruan @ 2025-12-31 9:23 UTC (permalink / raw)
To: alexandre.belloni, linux-rtc, linux-kernel; +Cc: ruanjinjie
On kvm qemu with cmos rtc and mc146818 chip, when the read time jump to
a future time after set the uie timer expire with a current RTC time,
rtc_timer_do_work() will loop for a while util softlockup because
the expiration of the uie timer was way before the current
RTC time and a new timer will be enqueued until the current rtc time
is reached, as below:
Fix it by voluntarily yield the CPU in the loop in rtc_timer_do_work().
RTC_UIE_ON:
read now: 2019:04:08:12:32:27, add timer0 (expire: 2019:04:08:12:32:28)
^^^^^^^^^^^^^^^^^^^^
...
rtc_timer_do_work() iterate the list in a loop:
read now: 2033:12:02:07:27:15
^^^^^^^^^^^^^^^^^^^
handle timer0, add timer1 to the list (expire: 2019:04:08:12:32:29)
handle timer1, add timer2 to the list (expire: 2019:04:08:12:32:30)
handle timer2, add timer3: 2019:04:08:12:32:31
...
-> softlockup
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
drivers/rtc/interface.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/rtc/interface.c b/drivers/rtc/interface.c
index b8b298efd9a9..9ded10e82f4b 100644
--- a/drivers/rtc/interface.c
+++ b/drivers/rtc/interface.c
@@ -964,6 +964,7 @@ void rtc_timer_do_work(struct work_struct *work)
timer->enabled = 1;
timerqueue_add(&rtc->timerqueue, &timer->node);
trace_rtc_timer_enqueue(timer);
+ cond_resched();
}
}
--
2.34.1
^ permalink raw reply related
* Re: [PATCH v1 1/2] dt-binding: rtc: loongson: Document Loongson-2K0300 compatible
From: Binbin Zhou @ 2025-12-30 2:31 UTC (permalink / raw)
To: Rob Herring
Cc: Binbin Zhou, Huacai Chen, Krzysztof Kozlowski, Conor Dooley,
Alexandre Belloni, linux-rtc, Xiaochuang Mao, Huacai Chen,
Xuerui Wang, loongarch, devicetree, linux-mips, Keguang Zhang
In-Reply-To: <20251230021517.GA3156793-robh@kernel.org>
Hi Rob:
Thanks for your review.
On Tue, Dec 30, 2025 at 10:15 AM Rob Herring <robh@kernel.org> wrote:
>
> On Tue, Dec 23, 2025 at 02:42:12PM +0800, Binbin Zhou wrote:
> > Add "loongson,ls2k0300-rtc" dedicated compatible to represent the RTC
> > interface of the Loongson-2K0300 chip.
> >
> > Its hardware design is similar to that of the Loongson-1B, but it does
> > not support the alarm feature.
> >
> > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> > ---
> > .../devicetree/bindings/rtc/loongson,rtc.yaml | 13 +++++++++++++
> > 1 file changed, 13 insertions(+)
> >
> > diff --git a/Documentation/devicetree/bindings/rtc/loongson,rtc.yaml b/Documentation/devicetree/bindings/rtc/loongson,rtc.yaml
> > index f89c1f660aee..aac91c79ffdb 100644
> > --- a/Documentation/devicetree/bindings/rtc/loongson,rtc.yaml
> > +++ b/Documentation/devicetree/bindings/rtc/loongson,rtc.yaml
> > @@ -23,6 +23,7 @@ properties:
> > - loongson,ls1b-rtc
> > - loongson,ls1c-rtc
> > - loongson,ls7a-rtc
> > + - loongson,ls2k0300-rtc
> > - loongson,ls2k1000-rtc
> > - items:
> > - enum:
> > @@ -42,6 +43,18 @@ required:
> >
> > unevaluatedProperties: false
> >
> > +if:
> > + properties:
> > + compatible:
> > + contains:
> > + enum:
> > + - loongson,ls1c-rtc
>
> This seems unrelated?
Loongson-1C does not support the alarm feature, so the `interrupts`
property is not needed.
Technically, this is my fault and should have been described in the
binding before, just correcting it now.
Perhaps I should have mentioned this in the commit message.
>
> > + - loongson,ls2k0300-rtc
> > +
> > +then:
> > + properties:
> > + interrupts: false
> > +
> > examples:
> > - |
> > #include <dt-bindings/interrupt-controller/irq.h>
> > --
> > 2.47.3
> >
--
Thanks.
Binbin
^ permalink raw reply
* Re: [PATCH v1 1/2] dt-binding: rtc: loongson: Document Loongson-2K0300 compatible
From: Rob Herring @ 2025-12-30 2:15 UTC (permalink / raw)
To: Binbin Zhou
Cc: Binbin Zhou, Huacai Chen, Krzysztof Kozlowski, Conor Dooley,
Alexandre Belloni, linux-rtc, Xiaochuang Mao, Huacai Chen,
Xuerui Wang, loongarch, devicetree, linux-mips, Keguang Zhang
In-Reply-To: <36544ba0d847bca639632ea0c74907de90975f80.1766471839.git.zhoubinbin@loongson.cn>
On Tue, Dec 23, 2025 at 02:42:12PM +0800, Binbin Zhou wrote:
> Add "loongson,ls2k0300-rtc" dedicated compatible to represent the RTC
> interface of the Loongson-2K0300 chip.
>
> Its hardware design is similar to that of the Loongson-1B, but it does
> not support the alarm feature.
>
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
> .../devicetree/bindings/rtc/loongson,rtc.yaml | 13 +++++++++++++
> 1 file changed, 13 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/rtc/loongson,rtc.yaml b/Documentation/devicetree/bindings/rtc/loongson,rtc.yaml
> index f89c1f660aee..aac91c79ffdb 100644
> --- a/Documentation/devicetree/bindings/rtc/loongson,rtc.yaml
> +++ b/Documentation/devicetree/bindings/rtc/loongson,rtc.yaml
> @@ -23,6 +23,7 @@ properties:
> - loongson,ls1b-rtc
> - loongson,ls1c-rtc
> - loongson,ls7a-rtc
> + - loongson,ls2k0300-rtc
> - loongson,ls2k1000-rtc
> - items:
> - enum:
> @@ -42,6 +43,18 @@ required:
>
> unevaluatedProperties: false
>
> +if:
> + properties:
> + compatible:
> + contains:
> + enum:
> + - loongson,ls1c-rtc
This seems unrelated?
> + - loongson,ls2k0300-rtc
> +
> +then:
> + properties:
> + interrupts: false
> +
> examples:
> - |
> #include <dt-bindings/interrupt-controller/irq.h>
> --
> 2.47.3
>
^ permalink raw reply
* Re: [PATCH v4 3/3] rtc: spacemit: default module when MFD_SPACEMIT_P1 is enabled
From: Alex Elder @ 2025-12-30 1:46 UTC (permalink / raw)
To: Alexandre Belloni
Cc: Troy Mitchell, Lee Jones, Yixun Lan, Andi Shyti, Liam Girdwood,
Mark Brown, linux-kernel, linux-riscv, spacemit, linux-i2c,
linux-rtc
In-Reply-To: <20251230005142d1bfc6f7@mail.local>
On 12/29/25 6:51 PM, Alexandre Belloni wrote:
> On 29/12/2025 12:02:23-0600, Alex Elder wrote:
>> On 12/25/25 10:53 AM, Alexandre Belloni wrote:
>>> On 25/12/2025 15:46:33+0800, Troy Mitchell wrote:
>>>> The RTC driver defaulted to the same value as MFD_SPACEMIT_P1, which
>>>> caused it to be built-in automatically whenever the PMIC support was
>>>> set to y.
>>>>
>>>> This is not always desirable, as the RTC function is not required on
>>>> all platforms using the SpacemiT P1 PMIC.
>>>
>>> But then, can't people simply change the config? I don't feel like
>>> this is an improvement.
>>
>> It's not an improvement for people who want to use the SpacemiT
>> P1 PMIC, but it's an improvement for all the other RISC-V builds
>> using "defconfig" that would rather have that support be modular
>> to avoid needlessly consuming resources.
>
> But then, wouldn't MFD_SPACEMIT_P1 be simply not set or set to m ? So
> this doesn't have any impact on other RISC-V builds while it makes
> people using the SpacemiT P1 PMIC jump through hoops to be able to use
> the RTC as this is a very uncommon way to set default values.
>
> My point is:
> - other RISC-V platforms would simply not select MFD_SPACEMIT_P1 or
> have MFD_SPACEMIT_P1 set to m
> - having RTC_DRV_SPACEMIT_P1 built-in by default when MFD_SPACEMIT_P1
> is built-in doesn't really hurt any SpacemiT P1 users but would be
> the expectation of those using the RTC.
The "hurt" isn't about P1 users, it's about everyone else.
> - those wanting to optimise because they won't use the RTC, they can
> already simply unselect RTC_DRV_SPACEMIT_P1 or set it to m.
The purpose is to make the driver a module (not built-in)
when "defconfig" is used (without the need for any Kconfig
fragments to unselect things).
In arch/riscv/configs/defconfig, we have this:
CONFIG_ARCH_SPACEMIT=y
In drivers/mfd/Kconfig b/drivers/mfd/Kconfig, we have this
(added by patch 2 in this series):
config MFD_SPACEMIT_P1
default m if ARCH_SPACEMIT
So when using defconfig (alone), MFD_SPACEMIT_P1 is set to m,
to benefit non-SpacemiT RISC-V platforms.
This patch is trying to do the same thing for the RTC,
i.e. having RTC_DRV_SPACEMIT_P1 be defined as a module
by default.
I think you understand.
-Alex
>> I haven't done any testing on this but it looks fine to me.
>>
>> Acked-by: Alex Elder <elder@riscstar.com>
>>
>> I think it's a small change worth merging. I don't think
>> doing so does any harm. Your call or course, Alexandre.
>>
>> -Alex
>>
>>>> Signed-off-by: Troy Mitchell <troy.mitchell@linux.spacemit.com>
>>>> ---
>>>> drivers/rtc/Kconfig | 2 +-
>>>> 1 file changed, 1 insertion(+), 1 deletion(-)
>>>>
>>>> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
>>>> index 50dc779f7f983074df7882200c90f0df21d142f2..53866493e9bbaf35ff0de85cbfe43e8343eadc1e 100644
>>>> --- a/drivers/rtc/Kconfig
>>>> +++ b/drivers/rtc/Kconfig
>>>> @@ -410,7 +410,7 @@ config RTC_DRV_SPACEMIT_P1
>>>> tristate "SpacemiT P1 RTC"
>>>> depends on ARCH_SPACEMIT || COMPILE_TEST
>>>> depends on MFD_SPACEMIT_P1
>>>> - default MFD_SPACEMIT_P1
>>>> + default m if MFD_SPACEMIT_P1
>>>> help
>>>> Enable support for the RTC function in the SpacemiT P1 PMIC.
>>>> This driver can also be built as a module, which will be called
>>>>
>>>> --
>>>> 2.52.0
>>>>
>>>
>>
>
^ permalink raw reply
* Re: [PATCH v4 3/3] rtc: spacemit: default module when MFD_SPACEMIT_P1 is enabled
From: Alexandre Belloni @ 2025-12-30 0:51 UTC (permalink / raw)
To: Alex Elder
Cc: Troy Mitchell, Lee Jones, Yixun Lan, Andi Shyti, Liam Girdwood,
Mark Brown, linux-kernel, linux-riscv, spacemit, linux-i2c,
linux-rtc
In-Reply-To: <4c7c0f69-4732-4f62-970a-2a9273b3b5c7@riscstar.com>
On 29/12/2025 12:02:23-0600, Alex Elder wrote:
> On 12/25/25 10:53 AM, Alexandre Belloni wrote:
> > On 25/12/2025 15:46:33+0800, Troy Mitchell wrote:
> > > The RTC driver defaulted to the same value as MFD_SPACEMIT_P1, which
> > > caused it to be built-in automatically whenever the PMIC support was
> > > set to y.
> > >
> > > This is not always desirable, as the RTC function is not required on
> > > all platforms using the SpacemiT P1 PMIC.
> >
> > But then, can't people simply change the config? I don't feel like
> > this is an improvement.
>
> It's not an improvement for people who want to use the SpacemiT
> P1 PMIC, but it's an improvement for all the other RISC-V builds
> using "defconfig" that would rather have that support be modular
> to avoid needlessly consuming resources.
But then, wouldn't MFD_SPACEMIT_P1 be simply not set or set to m ? So
this doesn't have any impact on other RISC-V builds while it makes
people using the SpacemiT P1 PMIC jump through hoops to be able to use
the RTC as this is a very uncommon way to set default values.
My point is:
- other RISC-V platforms would simply not select MFD_SPACEMIT_P1 or
have MFD_SPACEMIT_P1 set to m
- having RTC_DRV_SPACEMIT_P1 built-in by default when MFD_SPACEMIT_P1
is built-in doesn't really hurt any SpacemiT P1 users but would be
the expectation of those using the RTC.
- those wanting to optimise because they won't use the RTC, they can
already simply unselect RTC_DRV_SPACEMIT_P1 or set it to m.
>
> I haven't done any testing on this but it looks fine to me.
>
> Acked-by: Alex Elder <elder@riscstar.com>
>
> I think it's a small change worth merging. I don't think
> doing so does any harm. Your call or course, Alexandre.
>
> -Alex
>
> > > Signed-off-by: Troy Mitchell <troy.mitchell@linux.spacemit.com>
> > > ---
> > > drivers/rtc/Kconfig | 2 +-
> > > 1 file changed, 1 insertion(+), 1 deletion(-)
> > >
> > > diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> > > index 50dc779f7f983074df7882200c90f0df21d142f2..53866493e9bbaf35ff0de85cbfe43e8343eadc1e 100644
> > > --- a/drivers/rtc/Kconfig
> > > +++ b/drivers/rtc/Kconfig
> > > @@ -410,7 +410,7 @@ config RTC_DRV_SPACEMIT_P1
> > > tristate "SpacemiT P1 RTC"
> > > depends on ARCH_SPACEMIT || COMPILE_TEST
> > > depends on MFD_SPACEMIT_P1
> > > - default MFD_SPACEMIT_P1
> > > + default m if MFD_SPACEMIT_P1
> > > help
> > > Enable support for the RTC function in the SpacemiT P1 PMIC.
> > > This driver can also be built as a module, which will be called
> > >
> > > --
> > > 2.52.0
> > >
> >
>
--
Alexandre Belloni, co-owner and COO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply
* Re: [PATCH v4 3/3] rtc: spacemit: default module when MFD_SPACEMIT_P1 is enabled
From: Alex Elder @ 2025-12-29 18:02 UTC (permalink / raw)
To: Alexandre Belloni, Troy Mitchell
Cc: Lee Jones, Yixun Lan, Andi Shyti, Liam Girdwood, Mark Brown,
linux-kernel, linux-riscv, spacemit, linux-i2c, linux-rtc
In-Reply-To: <202512251653368b33c7e7@mail.local>
On 12/25/25 10:53 AM, Alexandre Belloni wrote:
> On 25/12/2025 15:46:33+0800, Troy Mitchell wrote:
>> The RTC driver defaulted to the same value as MFD_SPACEMIT_P1, which
>> caused it to be built-in automatically whenever the PMIC support was
>> set to y.
>>
>> This is not always desirable, as the RTC function is not required on
>> all platforms using the SpacemiT P1 PMIC.
>
> But then, can't people simply change the config? I don't feel like
> this is an improvement.
It's not an improvement for people who want to use the SpacemiT
P1 PMIC, but it's an improvement for all the other RISC-V builds
using "defconfig" that would rather have that support be modular
to avoid needlessly consuming resources.
I haven't done any testing on this but it looks fine to me.
Acked-by: Alex Elder <elder@riscstar.com>
I think it's a small change worth merging. I don't think
doing so does any harm. Your call or course, Alexandre.
-Alex
>> Signed-off-by: Troy Mitchell <troy.mitchell@linux.spacemit.com>
>> ---
>> drivers/rtc/Kconfig | 2 +-
>> 1 file changed, 1 insertion(+), 1 deletion(-)
>>
>> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
>> index 50dc779f7f983074df7882200c90f0df21d142f2..53866493e9bbaf35ff0de85cbfe43e8343eadc1e 100644
>> --- a/drivers/rtc/Kconfig
>> +++ b/drivers/rtc/Kconfig
>> @@ -410,7 +410,7 @@ config RTC_DRV_SPACEMIT_P1
>> tristate "SpacemiT P1 RTC"
>> depends on ARCH_SPACEMIT || COMPILE_TEST
>> depends on MFD_SPACEMIT_P1
>> - default MFD_SPACEMIT_P1
>> + default m if MFD_SPACEMIT_P1
>> help
>> Enable support for the RTC function in the SpacemiT P1 PMIC.
>> This driver can also be built as a module, which will be called
>>
>> --
>> 2.52.0
>>
>
^ permalink raw reply
* Re: [PATCH v3 1/4] i2c: spacemit: configure ILCR for accurate SCL frequency
From: Troy Mitchell @ 2025-12-29 1:20 UTC (permalink / raw)
To: Alex Elder, Troy Mitchell, Lee Jones, Yixun Lan, Andi Shyti,
Alexandre Belloni, Liam Girdwood, Mark Brown
Cc: linux-kernel, linux-riscv, spacemit, linux-i2c, linux-rtc
In-Reply-To: <4a76e9bf-926e-4b77-a2f8-ee4a72b2f1dd@riscstar.com>
On Sun, Dec 28, 2025 at 06:58:00PM -0600, Alex Elder wrote:
> On 12/26/25 1:52 AM, Troy Mitchell wrote:
> > > > > +static int spacemit_i2c_clk_set_rate(struct clk_hw *hw, unsigned long rate,
> > > > > + unsigned long parent_rate)
> > > > > +{
> > > > > + struct spacemit_i2c_dev *i2c = container_of(hw, struct spacemit_i2c_dev, scl_clk_hw);
> > > > > + u32 lv, lcr, mask, shift, max_lv;
> > > > > +
> > > > > + lv = DIV_ROUND_UP(parent_rate, rate);
> > > >
> > > > Would DIV_ROUND_CLOSEST() give a more accurate value?
> > > I'll test it.
> > Same result. So I'll keep it.
>
> Is that true for all clock rates?
I only test 400k.
> Anyway, it's not
> a huge deal, but especially when the number of rates
> isn't very high this can make a difference.
I'll test more. Thanks!
- Troy
^ permalink raw reply
* Re: [PATCH v3 1/4] i2c: spacemit: configure ILCR for accurate SCL frequency
From: Alex Elder @ 2025-12-29 0:58 UTC (permalink / raw)
To: Troy Mitchell, Lee Jones, Yixun Lan, Andi Shyti,
Alexandre Belloni, Liam Girdwood, Mark Brown
Cc: linux-kernel, linux-riscv, spacemit, linux-i2c, linux-rtc
In-Reply-To: <569E6DA87DE510D5+aU4-1Jl9XxjAWQq4@kernel.org>
On 12/26/25 1:52 AM, Troy Mitchell wrote:
>>>> +static int spacemit_i2c_clk_set_rate(struct clk_hw *hw, unsigned long rate,
>>>> + unsigned long parent_rate)
>>>> +{
>>>> + struct spacemit_i2c_dev *i2c = container_of(hw, struct spacemit_i2c_dev, scl_clk_hw);
>>>> + u32 lv, lcr, mask, shift, max_lv;
>>>> +
>>>> + lv = DIV_ROUND_UP(parent_rate, rate);
>>>
>>> Would DIV_ROUND_CLOSEST() give a more accurate value?
>> I'll test it.
> Same result. So I'll keep it.
Is that true for all clock rates? Anyway, it's not
a huge deal, but especially when the number of rates
isn't very high this can make a difference.
-Alex
> - Troy
^ permalink raw reply
* Re: [PATCH v1 0/2] RTC: Add Loongson-2K0300 support
From: Keguang Zhang @ 2025-12-26 10:03 UTC (permalink / raw)
To: Binbin Zhou
Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Alexandre Belloni, linux-rtc, Xiaochuang Mao,
Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-mips
In-Reply-To: <cover.1766471839.git.zhoubinbin@loongson.cn>
For the whole series:
Reviewed-by: Keguang Zhang <keguang.zhang@gmail.com>
Tested-by: Keguang Zhang <keguang.zhang@gmail.com> # on LS1B & LS1C
On Tue, Dec 23, 2025 at 2:42 PM Binbin Zhou <zhoubinbin@loongson.cn> wrote:
>
> Hi all:
>
> This patch set introduces the Loongson-2K0300 RTC, which has a similar
> hardware design to the Loongson-1B, but without the alarm feature.
>
> To Keguang:
> Would you have time to test the driver on a Loongson-1?
>
> Thanks.
> Binbin
>
> Binbin Zhou (2):
> dt-binding: rtc: loongson: Document Loongson-2K0300 compatible
> rtc: loongson: Add Loongson-2K0300 support
>
> .../devicetree/bindings/rtc/loongson,rtc.yaml | 13 ++++
> drivers/rtc/rtc-loongson.c | 65 +++++++++++++------
> 2 files changed, 57 insertions(+), 21 deletions(-)
>
>
> base-commit: 16bd954c93360145bc77cc601e350913fc28182d
> --
> 2.47.3
>
--
Best regards,
Keguang Zhang
^ permalink raw reply
* Re: [PATCH v3 1/4] i2c: spacemit: configure ILCR for accurate SCL frequency
From: Troy Mitchell @ 2025-12-26 7:52 UTC (permalink / raw)
To: Alex Elder, Troy Mitchell, Lee Jones, Yixun Lan, Andi Shyti,
Alexandre Belloni, Liam Girdwood, Mark Brown
Cc: linux-kernel, linux-riscv, spacemit, linux-i2c, linux-rtc
In-Reply-To: <BD74A47E5BB66010+aU4j6CgGxebcBV5I@kernel.org>
> > > +static int spacemit_i2c_clk_set_rate(struct clk_hw *hw, unsigned long rate,
> > > + unsigned long parent_rate)
> > > +{
> > > + struct spacemit_i2c_dev *i2c = container_of(hw, struct spacemit_i2c_dev, scl_clk_hw);
> > > + u32 lv, lcr, mask, shift, max_lv;
> > > +
> > > + lv = DIV_ROUND_UP(parent_rate, rate);
> >
> > Would DIV_ROUND_CLOSEST() give a more accurate value?
> I'll test it.
Same result. So I'll keep it.
- Troy
^ permalink raw reply
* Re: [PATCH v3 1/4] i2c: spacemit: configure ILCR for accurate SCL frequency
From: Troy Mitchell @ 2025-12-26 5:58 UTC (permalink / raw)
To: Alex Elder, Troy Mitchell, Lee Jones, Yixun Lan, Andi Shyti,
Alexandre Belloni, Liam Girdwood, Mark Brown
Cc: linux-kernel, linux-riscv, spacemit, linux-i2c, linux-rtc
In-Reply-To: <81eca0ab-47a3-4b12-98ae-fbd46a15ff93@riscstar.com>
On Tue, Nov 18, 2025 at 11:32:44AM -0600, Alex Elder wrote:
> On 11/18/25 12:08 AM, Troy Mitchell wrote:
> > The SpacemiT I2C controller's SCL (Serial Clock Line) frequency for
> > master mode operations is determined by the ILCR (I2C Load Count Register).
> > Previously, the driver relied on the hardware's reset default
> > values for this register.
> >
> > The hardware's default ILCR values (SLV=0x156, FLV=0x5d) yield SCL
> > frequencies lower than intended. For example, with the default
> > 31.5 MHz input clock, these default settings result in an SCL
> > frequency of approximately 93 kHz (standard mode) when targeting 100 kHz,
> > and approximately 338 kHz (fast mode) when targeting 400 kHz.
> > These frequencies are below the 100 kHz/400 kHz nominal speeds.
> >
> > This patch integrates the SCL frequency management into
> > the Common Clock Framework (CCF). Specifically, the ILCR register,
> > which acts as a frequency divider for the SCL clock, is now registered
> > as a managed clock (scl_clk) within the CCF.
> >
> > This patch also cleans up unnecessary whitespace
> > in the included header files.
>
> I have a few comments below. Sorry I didn't comment on
> earlier versions. > Reviewed-by: Yixun Lan <dlan@gentoo.org>
> > Link: https://lore.kernel.org/all/176244506110.1925720.10807118665958896958.b4-ty@kernel.org/ [1]
> > Signed-off-by: Troy Mitchell <troy.mitchell@linux.spacemit.com>
> > ---
> > This patch was affected by the P1 Kconfig, which caused the maintainer
> > to revert it.
> > The current commit is a direct cherry-pick and reserves the original changelog.
> > This note is to clarify for anyone who sees the cover letter marked as v2
> > while the changelog entries reach v4.
> > ---
> > Changelog in v4:
> > - initialize clk_init_data with {} so that init.flags is implicitly set to 0
> > - minor cleanup and style fixes for better readability
> > - remove unused spacemit_i2c_scl_clk_exclusive_put() cleanup callback
> > - replace clk_set_rate_exclusive()/clk_rate_exclusive_put() pair with clk_set_rate()
> > - simplify LCR LV field macros by using FIELD_GET/FIELD_MAX helpers
> > - Link to v3: https://lore.kernel.org/all/20250814-k1-i2c-ilcr-v3-1-317723e74bcd@linux.spacemit.com/
> >
> > Changelog in v3:
> > - use MASK macro in `recalc_rate` function
> > - rename clock name
> > - Link to v2: https://lore.kernel.org/r/20250718-k1-i2c-ilcr-v2-1-b4c68f13dcb1@linux.spacemit.com
> >
> > Changelog in v2:
> > - Align line breaks.
> > - Check `lv` in `clk_set_rate` function.
> > - Force fast mode when SCL frequency is illegal or unavailable.
> > - Change "linux/bits.h" to <linux/bits.h>
> > - Kconfig: Add dependency on CCF.
> > - Link to v1: https://lore.kernel.org/all/20250710-k1-i2c-ilcr-v1-1-188d1f460c7d@linux.spacemit.com/
> > ---
> > drivers/i2c/busses/Kconfig | 2 +-
> > drivers/i2c/busses/i2c-k1.c | 159 ++++++++++++++++++++++++++++++++++++++++----
> > 2 files changed, 146 insertions(+), 15 deletions(-)
> >
> > diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> > index fd81e49638aaa161ae264a722e9e06adc7914cda..fedf5d31f9035b73a27a7f8a764bf5c26975d0e1 100644
> > --- a/drivers/i2c/busses/Kconfig
> > +++ b/drivers/i2c/busses/Kconfig
> > @@ -798,7 +798,7 @@ config I2C_JZ4780
> > config I2C_K1
> > tristate "SpacemiT K1 I2C adapter"
> > depends on ARCH_SPACEMIT || COMPILE_TEST
> > - depends on OF
> > + depends on OF && COMMON_CLK
> > help
> > This option enables support for the I2C interface on the SpacemiT K1
> > platform.
> > diff --git a/drivers/i2c/busses/i2c-k1.c b/drivers/i2c/busses/i2c-k1.c
> > index 6b918770e612e098b8ad17418f420d87c94df166..e38a0ba71734ca602854c85672dcb61423453515 100644
> > --- a/drivers/i2c/busses/i2c-k1.c
> > +++ b/drivers/i2c/busses/i2c-k1.c
> > @@ -4,18 +4,21 @@
> > */
> > #include <linux/bitfield.h>
> > - #include <linux/clk.h>
> > - #include <linux/i2c.h>
> > - #include <linux/iopoll.h>
> > - #include <linux/module.h>
> > - #include <linux/of_address.h>
> > - #include <linux/platform_device.h>
> > +#include <linux/bits.h>
> > +#include <linux/clk.h>
> > +#include <linux/clk-provider.h>
> > +#include <linux/i2c.h>
> > +#include <linux/iopoll.h>
> > +#include <linux/module.h>
> > +#include <linux/of_address.h>
> > +#include <linux/platform_device.h>
> > /* spacemit i2c registers */
> > #define SPACEMIT_ICR 0x0 /* Control register */
> > #define SPACEMIT_ISR 0x4 /* Status register */
> > #define SPACEMIT_IDBR 0xc /* Data buffer register */
> > #define SPACEMIT_IRCR 0x18 /* Reset cycle counter */
> > +#define SPACEMIT_ILCR 0x10 /* Load Count Register */
> > #define SPACEMIT_IBMR 0x1c /* Bus monitor register */
> > /* SPACEMIT_ICR register fields */
> > @@ -87,6 +90,13 @@
> > #define SPACEMIT_BMR_SDA BIT(0) /* SDA line level */
> > #define SPACEMIT_BMR_SCL BIT(1) /* SCL line level */
> > +#define SPACEMIT_LCR_LV_STANDARD_SHIFT 0
> > +#define SPACEMIT_LCR_LV_FAST_SHIFT 9
>
> Why do you need these SHIFT symbols? Just use the masks
> and FIELD_GET() related macros. I'll provide examples below.
>
> > +#define SPACEMIT_LCR_LV_STANDARD_MASK GENMASK(8, 0)
> > +#define SPACEMIT_LCR_LV_FAST_MASK GENMASK(17, 9)
> > +#define SPACEMIT_LCR_LV_STANDARD_MAX_VALUE FIELD_MAX(SPACEMIT_LCR_LV_STANDARD_MASK)
> > +#define SPACEMIT_LCR_LV_FAST_MAX_VALUE FIELD_MAX(SPACEMIT_LCR_LV_FAST_MASK)
> > +
> > /* i2c bus recover timeout: us */
> > #define SPACEMIT_I2C_BUS_BUSY_TIMEOUT 100000
> > @@ -104,11 +114,20 @@ enum spacemit_i2c_state {
> > SPACEMIT_STATE_WRITE,
> > };
> > +enum spacemit_i2c_mode {
> > + SPACEMIT_MODE_STANDARD,
> > + SPACEMIT_MODE_FAST
> > +};
>
> Will there ever be more than two i2c modes? If not, this
> could simply be a Boolean.
Yes, Will. See below.
>
> > +
> > /* i2c-spacemit driver's main struct */
> > struct spacemit_i2c_dev {
> > struct device *dev;
> > struct i2c_adapter adapt;
> > + struct clk_hw scl_clk_hw;
> > + struct clk *scl_clk;
> > + enum spacemit_i2c_mode mode;
>
> Perhaps this could be:
>
> bool fast_mode;
hardware also supports high-speed mode.
>
> > +
> > /* hardware resources */
> > void __iomem *base;
> > int irq;
> > @@ -129,6 +148,79 @@ struct spacemit_i2c_dev {
> > u32 status;
> > };
> > +static void spacemit_i2c_scl_clk_disable_unprepare(void *data)
> > +{
> > + struct spacemit_i2c_dev *i2c = data;
> > +
> > + clk_disable_unprepare(i2c->scl_clk);
> > +}
> > +
> > +static int spacemit_i2c_clk_set_rate(struct clk_hw *hw, unsigned long rate,
> > + unsigned long parent_rate)
> > +{
> > + struct spacemit_i2c_dev *i2c = container_of(hw, struct spacemit_i2c_dev, scl_clk_hw);
> > + u32 lv, lcr, mask, shift, max_lv;
> > +
> > + lv = DIV_ROUND_UP(parent_rate, rate);
>
> Would DIV_ROUND_CLOSEST() give a more accurate value?
I'll test it.
>
> > +
> > + if (i2c->mode == SPACEMIT_MODE_STANDARD) {
> > + mask = SPACEMIT_LCR_LV_STANDARD_MASK;
> > + shift = SPACEMIT_LCR_LV_STANDARD_SHIFT;
> > + max_lv = SPACEMIT_LCR_LV_STANDARD_MAX_VALUE;
> > + } else if (i2c->mode == SPACEMIT_MODE_FAST) {
> > + mask = SPACEMIT_LCR_LV_FAST_MASK;
> > + shift = SPACEMIT_LCR_LV_FAST_SHIFT;
> > + max_lv = SPACEMIT_LCR_LV_FAST_MAX_VALUE;
> > + }
> > +
> > + if (!lv || lv > max_lv) {
> > + dev_err(i2c->dev, "set scl clock failed: lv 0x%x", lv);
> > + return -EINVAL;
> > + }
> > +
> > + lcr = readl(i2c->base + SPACEMIT_ILCR);
> > + lcr &= ~mask;
> > + lcr |= lv << shift;
> > + writel(lcr, i2c->base + SPACEMIT_ILCR);
>
> FIELD_MODIFY(mask, &lcr, lv);
>
> I suppose this might give you trouble because the mask isn't
> constant at compile time, but anyway I think something like
> this is simpler:
>
> lv = DIV_ROUND_CLOSEST(parent_rate, rate);
> lcr = readl(i2c->base + SPACEMIT_ILCR);
> if (i2c->fast_mode)
> FIELD_MODIFY(SPACEMIT_LCR_LV_FAST_MASK, &lcr, lv);
> else
> FIELD_MODIFY(SPACEMIT_LCR_LV_STANDARD_MASK, &lcr, lv);
> writel(lcr, i2c->base + SPACEMIT_ILCR);
>
> Note: I've never used FIELD_MODIFY(), but it looks like this is
> how it's supposed to be used.
Thanks! I'll give it a go.
>
> > + return 0;
> > +}
> > +
> > +static long spacemit_i2c_clk_round_rate(struct clk_hw *hw, unsigned long rate,
> > + unsigned long *parent_rate)
> > +{
> > + u32 lv, freq;
> > +
> > + lv = DIV_ROUND_UP(*parent_rate, rate);
> > + freq = DIV_ROUND_UP(*parent_rate, lv);
>
> Consider whether DIV_ROUND_CLOSEST() (in one or both of
> these) provides a rate that is as close as possible to the
> requested rate.
>
> > +
> > + return freq;
> > +}
> > +
> > +static unsigned long spacemit_i2c_clk_recalc_rate(struct clk_hw *hw,
> > + unsigned long parent_rate)
> > +{
> > + struct spacemit_i2c_dev *i2c = container_of(hw, struct spacemit_i2c_dev, scl_clk_hw);
> > + u32 lcr, lv = 0;
> > +
> > + lcr = readl(i2c->base + SPACEMIT_ILCR);
> > +
> > + if (i2c->mode == SPACEMIT_MODE_STANDARD)
> > + lv = FIELD_GET(SPACEMIT_LCR_LV_STANDARD_MASK, lcr);
> > + else if (i2c->mode == SPACEMIT_MODE_FAST)
> > + lv = FIELD_GET(SPACEMIT_LCR_LV_FAST_MASK, lcr);
> > + else
> > + return 0;
>
> You shouldn't need the last else here. You can probably tell
> by inspection that it will always be one mode or the other.
> And a Boolean reinforces that.
I'll drop else here.
>
> > +
> > + return DIV_ROUND_UP(parent_rate, lv);
> > +}
> > +
> > +static const struct clk_ops spacemit_i2c_clk_ops = {
> > + .set_rate = spacemit_i2c_clk_set_rate,
> > + .round_rate = spacemit_i2c_clk_round_rate,
> > + .recalc_rate = spacemit_i2c_clk_recalc_rate,
> > +};
> > +
> > static void spacemit_i2c_enable(struct spacemit_i2c_dev *i2c)
> > {
> > u32 val;
> > @@ -147,6 +239,26 @@ static void spacemit_i2c_disable(struct spacemit_i2c_dev *i2c)
> > writel(val, i2c->base + SPACEMIT_ICR);
> > }
> > +static struct clk *spacemit_i2c_register_scl_clk(struct spacemit_i2c_dev *i2c,
> > + struct clk *parent)
> > +{
> > + struct clk_init_data init = {};
> > + char name[32];
> > +
> > + snprintf(name, sizeof(name), "%s_scl_clk", dev_name(i2c->dev));
>
> What if dev_name(i2c->dev) is longer than 24? You should
> be checking the return value here.
Thanks! I'll check the return value and extend the array size to 64.
>
> > +
> > + init.name = name;
> > + init.ops = &spacemit_i2c_clk_ops;
> > + init.parent_data = (struct clk_parent_data[]) {
> > + { .fw_name = "func" },
> > + };
> > + init.num_parents = 1;
> > +
> > + i2c->scl_clk_hw.init = &init;
> > +
> > + return devm_clk_register(i2c->dev, &i2c->scl_clk_hw);
> > +}
> > +
> > static void spacemit_i2c_reset(struct spacemit_i2c_dev *i2c)
> > {
> > writel(SPACEMIT_CR_UR, i2c->base + SPACEMIT_ICR);
> > @@ -246,7 +358,7 @@ static void spacemit_i2c_init(struct spacemit_i2c_dev *i2c)
> > */
> > val |= SPACEMIT_CR_DRFIE;
> > - if (i2c->clock_freq == SPACEMIT_I2C_MAX_FAST_MODE_FREQ)
> > + if (i2c->mode == SPACEMIT_MODE_FAST)
> > val |= SPACEMIT_CR_MODE_FAST;
> > /* disable response to general call */
> > @@ -538,14 +650,15 @@ static int spacemit_i2c_probe(struct platform_device *pdev)
> > dev_warn(dev, "failed to read clock-frequency property: %d\n", ret);
>
> I don't think there's any need to warn when the "clock-frequency"
> property is not found. It's an optional property, and the default
> is specified in the binding to be 400 KHz.
I will fix it in another patch.
>
> > /* For now, this driver doesn't support high-speed. */
> > - if (!i2c->clock_freq || i2c->clock_freq > SPACEMIT_I2C_MAX_FAST_MODE_FREQ) {
> > - dev_warn(dev, "unsupported clock frequency %u; using %u\n",
> > - i2c->clock_freq, SPACEMIT_I2C_MAX_FAST_MODE_FREQ);
> > + if (i2c->clock_freq > SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ &&
> > + i2c->clock_freq <= SPACEMIT_I2C_MAX_FAST_MODE_FREQ) {
> > + i2c->mode = SPACEMIT_MODE_FAST;
> > + } else if (i2c->clock_freq && i2c->clock_freq <= SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ) {
> > + i2c->mode = SPACEMIT_MODE_STANDARD;
> So this I2C driver will literally support *either* the standard
> speed *or* the high speed frequency. Is this a necessary
> restriction? If it is, perhaps the DT binding should be
> clear that the speeds should be one of those supported
> (because anything else will result in using a supported
> speed, not the one provided).
I think there may be a misunderstanding here.
The documentation already states that:
K1 supports three different modes running at different frequencies:
- standard speed mode: up to 100000 (100 kHz)
- fast speed mode : up to 400000 (400 kHz)
- high speed mode........
The mode only defines the valid frequency range.
In standard mode, the maximum frequency is 100 kHz; in fast mode,
the valid range is 100 kHz to 400 kHz.
The exact operating frequency is determined by the ILCR register.
This is precisely the motivation for this patch: without it, once
the mode is selected, the actual bus frequency depends solely on the
ILCR reset value rather than the clock-frequency provided by DT.
- Troy
>
>
> (Sorry, these last two comments are not about your patch,
> but about the driver that's already accepted.)
>
> -Alex
>
> > + } else {
> > + dev_warn(i2c->dev, "invalid clock-frequency, fallback to fast mode");
> > + i2c->mode = SPACEMIT_MODE_FAST;
> > i2c->clock_freq = SPACEMIT_I2C_MAX_FAST_MODE_FREQ;
> > - } else if (i2c->clock_freq < SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ) {
> > - dev_warn(dev, "unsupported clock frequency %u; using %u\n",
> > - i2c->clock_freq, SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ);
> > - i2c->clock_freq = SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ;
> > }
> > i2c->dev = &pdev->dev;
> > @@ -567,10 +680,28 @@ static int spacemit_i2c_probe(struct platform_device *pdev)
> > if (IS_ERR(clk))
> > return dev_err_probe(dev, PTR_ERR(clk), "failed to enable func clock");
> > + i2c->scl_clk = spacemit_i2c_register_scl_clk(i2c, clk);
> > + if (IS_ERR(i2c->scl_clk))
> > + return dev_err_probe(&pdev->dev, PTR_ERR(i2c->scl_clk),
> > + "failed to register scl clock\n");
> > +
> > clk = devm_clk_get_enabled(dev, "bus");
> > if (IS_ERR(clk))
> > return dev_err_probe(dev, PTR_ERR(clk), "failed to enable bus clock");
> > + ret = clk_set_rate(i2c->scl_clk, i2c->clock_freq);
> > + if (ret)
> > + return dev_err_probe(&pdev->dev, ret, "failed to set rate for SCL clock");
> > +
> > + ret = clk_prepare_enable(i2c->scl_clk);
> > + if (ret)
> > + return dev_err_probe(&pdev->dev, ret, "failed to prepare and enable clock");
> > +
> > + ret = devm_add_action_or_reset(dev, spacemit_i2c_scl_clk_disable_unprepare, i2c);
> > + if (ret)
> > + return dev_err_probe(&pdev->dev, ret,
> > + "failed to register cleanup action for clk disable and unprepare");
> > +
> > spacemit_i2c_reset(i2c);
> > i2c_set_adapdata(&i2c->adapt, i2c);
> >
>
^ permalink raw reply
* Re: [PATCH v4 3/3] rtc: spacemit: default module when MFD_SPACEMIT_P1 is enabled
From: Alexandre Belloni @ 2025-12-25 16:53 UTC (permalink / raw)
To: Troy Mitchell
Cc: Lee Jones, Yixun Lan, Alex Elder, Andi Shyti, Liam Girdwood,
Mark Brown, linux-kernel, linux-riscv, spacemit, linux-i2c,
linux-rtc
In-Reply-To: <20251225-p1-kconfig-fix-v4-3-44b6728117c1@linux.spacemit.com>
On 25/12/2025 15:46:33+0800, Troy Mitchell wrote:
> The RTC driver defaulted to the same value as MFD_SPACEMIT_P1, which
> caused it to be built-in automatically whenever the PMIC support was
> set to y.
>
> This is not always desirable, as the RTC function is not required on
> all platforms using the SpacemiT P1 PMIC.
But then, can't people simply change the config? I don't feel like
this is an improvement.
>
> Signed-off-by: Troy Mitchell <troy.mitchell@linux.spacemit.com>
> ---
> drivers/rtc/Kconfig | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> index 50dc779f7f983074df7882200c90f0df21d142f2..53866493e9bbaf35ff0de85cbfe43e8343eadc1e 100644
> --- a/drivers/rtc/Kconfig
> +++ b/drivers/rtc/Kconfig
> @@ -410,7 +410,7 @@ config RTC_DRV_SPACEMIT_P1
> tristate "SpacemiT P1 RTC"
> depends on ARCH_SPACEMIT || COMPILE_TEST
> depends on MFD_SPACEMIT_P1
> - default MFD_SPACEMIT_P1
> + default m if MFD_SPACEMIT_P1
> help
> Enable support for the RTC function in the SpacemiT P1 PMIC.
> This driver can also be built as a module, which will be called
>
> --
> 2.52.0
>
--
Alexandre Belloni, co-owner and COO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply
* [PATCH v4 3/3] rtc: spacemit: default module when MFD_SPACEMIT_P1 is enabled
From: Troy Mitchell @ 2025-12-25 7:46 UTC (permalink / raw)
To: Lee Jones, Yixun Lan, Alex Elder, Andi Shyti, Alexandre Belloni,
Liam Girdwood, Mark Brown
Cc: linux-kernel, linux-riscv, spacemit, linux-i2c, linux-rtc,
Troy Mitchell
In-Reply-To: <20251225-p1-kconfig-fix-v4-0-44b6728117c1@linux.spacemit.com>
The RTC driver defaulted to the same value as MFD_SPACEMIT_P1, which
caused it to be built-in automatically whenever the PMIC support was
set to y.
This is not always desirable, as the RTC function is not required on
all platforms using the SpacemiT P1 PMIC.
Signed-off-by: Troy Mitchell <troy.mitchell@linux.spacemit.com>
---
drivers/rtc/Kconfig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 50dc779f7f983074df7882200c90f0df21d142f2..53866493e9bbaf35ff0de85cbfe43e8343eadc1e 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -410,7 +410,7 @@ config RTC_DRV_SPACEMIT_P1
tristate "SpacemiT P1 RTC"
depends on ARCH_SPACEMIT || COMPILE_TEST
depends on MFD_SPACEMIT_P1
- default MFD_SPACEMIT_P1
+ default m if MFD_SPACEMIT_P1
help
Enable support for the RTC function in the SpacemiT P1 PMIC.
This driver can also be built as a module, which will be called
--
2.52.0
^ permalink raw reply related
* [PATCH v4 2/3] mfd: simple-mfd-i2c: add default value
From: Troy Mitchell @ 2025-12-25 7:46 UTC (permalink / raw)
To: Lee Jones, Yixun Lan, Alex Elder, Andi Shyti, Alexandre Belloni,
Liam Girdwood, Mark Brown
Cc: linux-kernel, linux-riscv, spacemit, linux-i2c, linux-rtc,
Troy Mitchell
In-Reply-To: <20251225-p1-kconfig-fix-v4-0-44b6728117c1@linux.spacemit.com>
The default value of the P1 sub-device depends on the value
of P1, so P1 should have a default value here.
Acked-by: Alex Elder <elder@riscstar.com>
Signed-off-by: Troy Mitchell <troy.mitchell@linux.spacemit.com>
---
Change log in v4:
- default m if ARCH_SPACEMIT instead of default ARCH_SPACEMIT
- Link to v3: https://lore.kernel.org/all/20251118-p1-kconfig-fix-v3-4-8839c5ac5db3@linux.spacemit.com/
---
drivers/mfd/Kconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index aace5766b38aa5e46e32a8a7b42eea238159fbcf..c757bc365029dc794c658fc5b10084a0f29ac9b6 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1276,6 +1276,7 @@ config MFD_SPACEMIT_P1
depends on ARCH_SPACEMIT || COMPILE_TEST
depends on I2C
select MFD_SIMPLE_MFD_I2C
+ default m if ARCH_SPACEMIT
help
This option supports the I2C-based SpacemiT P1 PMIC, which
contains regulators, a power switch, GPIOs, an RTC, and more.
--
2.52.0
^ permalink raw reply related
* [PATCH v4 1/3] regulator: spacemit: MFD_SPACEMIT_P1 as dependencies
From: Troy Mitchell @ 2025-12-25 7:46 UTC (permalink / raw)
To: Lee Jones, Yixun Lan, Alex Elder, Andi Shyti, Alexandre Belloni,
Liam Girdwood, Mark Brown
Cc: linux-kernel, linux-riscv, spacemit, linux-i2c, linux-rtc,
Troy Mitchell
In-Reply-To: <20251225-p1-kconfig-fix-v4-0-44b6728117c1@linux.spacemit.com>
REGULATOR_SPACEMIT_P1 is a subdevice of P1 and should depend on
MFD_SPACEMIT_P1 rather than selecting it directly. Using 'select'
does not always respect the parent's dependencies, so 'depends on'
is the safer and more correct choice.
Since MFD_SPACEMIT_P1 already depends on I2C_K1, the dependency
in REGULATOR_SPACEMIT_P1 is now redundant.
Additionally, the default value depends on MFD_SPACEMIT_P1 rather
than ARCH_SPACEMIT.
Acked-by: Mark Brown <broonie@kernel.org>
Acked-by: Alex Elder <elder@riscstar.com>
Signed-off-by: Troy Mitchell <troy.mitchell@linux.spacemit.com>
---
Change log in v4:
- default m if MFD_SPACEMIT_P1 instead of default MFD_SPACEMIT_P1
Link to v3: https://lore.kernel.org/all/20251118-p1-kconfig-fix-v3-3-8839c5ac5db3@linux.spacemit.com/
Changelog in v3:
- modify commit message
- change default value from ARCH_SPACEMIT to MFD_SPACEMIT_P1
- Link to v2: https://lore.kernel.org/all/20251027-p1-kconfig-fix-v2-4-49688f30bae8@linux.spacemit.com/
---
drivers/regulator/Kconfig | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index d2335276cce5ffbd500bbaf251d1761a9116aee9..b51888a9a78f399a6af3294fc19f60792576332c 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -1496,9 +1496,8 @@ config REGULATOR_SLG51000
config REGULATOR_SPACEMIT_P1
tristate "SpacemiT P1 regulators"
depends on ARCH_SPACEMIT || COMPILE_TEST
- depends on I2C
- select MFD_SPACEMIT_P1
- default ARCH_SPACEMIT
+ depends on MFD_SPACEMIT_P1
+ default m if MFD_SPACEMIT_P1
help
Enable support for regulators implemented by the SpacemiT P1
power controller. The P1 implements 6 high-efficiency buck
--
2.52.0
^ permalink raw reply related
* [PATCH v4 0/3] spacemit: fix P1 sub-device Kconfig defaults and dependencies
From: Troy Mitchell @ 2025-12-25 7:46 UTC (permalink / raw)
To: Lee Jones, Yixun Lan, Alex Elder, Andi Shyti, Alexandre Belloni,
Liam Girdwood, Mark Brown
Cc: linux-kernel, linux-riscv, spacemit, linux-i2c, linux-rtc,
Troy Mitchell
This series fixes Kconfig default value and dependency handling for
the SpacemiT P1 PMIC and its sub-devices.
Signed-off-by: Troy Mitchell <troy.mitchell@linux.spacemit.com>
---
Troy Mitchell (3):
regulator: spacemit: MFD_SPACEMIT_P1 as dependencies
mfd: simple-mfd-i2c: add default value
rtc: spacemit: default module when MFD_SPACEMIT_P1 is enabled
drivers/mfd/Kconfig | 1 +
drivers/regulator/Kconfig | 5 ++---
drivers/rtc/Kconfig | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
---
base-commit: b87881a3c93345252ce8559ad763369febfdb75d
change-id: 20251021-p1-kconfig-fix-6d2b59d03b8f
Best regards,
--
Troy Mitchell <troy.mitchell@linux.spacemit.com>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox