Linux RTC
 help / color / mirror / Atom feed
* [PATCH v3 2/6] rtc: zynqmp: correct frequency value
From: Tomas Melin @ 2026-01-19  9:51 UTC (permalink / raw)
  To: Alexandre Belloni, Michal Simek
  Cc: linux-rtc, linux-arm-kernel, linux-kernel, Tomas Melin, Harini T
In-Reply-To: <20260119-zynqmp-rtc-updates-v3-0-acd902fdeab1@vaisala.com>

Fix calibration value in case a clock reference is provided.
The actual calibration value written into register is
frequency - 1.

Reviewed-by: Harini T <harini.t@amd.com>
Tested-by: Harini T <harini.t@amd.com>
Signed-off-by: Tomas Melin <tomas.melin@vaisala.com>
---
 drivers/rtc/rtc-zynqmp.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/rtc/rtc-zynqmp.c b/drivers/rtc/rtc-zynqmp.c
index 3baa2b481d9f2008750046005283b98a0d546c5c..856bc1678e7d31144f320ae9f75fc58c742a2a64 100644
--- a/drivers/rtc/rtc-zynqmp.c
+++ b/drivers/rtc/rtc-zynqmp.c
@@ -345,7 +345,10 @@ static int xlnx_rtc_probe(struct platform_device *pdev)
 					   &xrtcdev->freq);
 		if (ret)
 			xrtcdev->freq = RTC_CALIB_DEF;
+	} else {
+		xrtcdev->freq--;
 	}
+
 	ret = readl(xrtcdev->reg_base + RTC_CALIB_RD);
 	if (!ret)
 		writel(xrtcdev->freq, (xrtcdev->reg_base + RTC_CALIB_WR));

-- 
2.47.3


^ permalink raw reply related

* Re: [PATCH v2 3/5] rtc: zynqmp: rework read_offset
From: Tomas Melin @ 2026-01-19  9:45 UTC (permalink / raw)
  To: kernel test robot, Alexandre Belloni, Michal Simek
  Cc: oe-kbuild-all, linux-rtc, linux-arm-kernel, linux-kernel
In-Reply-To: <fded3e4f-f292-48bf-aea7-0c8e71f7e056@vaisala.com>



On 15/01/2026 09:41, Tomas Melin wrote:
> Hi,
> 
> On 15/01/2026 02:42, kernel test robot wrote:
> 
>>
>>    arm-linux-gnueabi-ld: drivers/spi/spi-amlogic-spifc-a4.o: in function `aml_sfc_set_bus_width':
>>    spi-amlogic-spifc-a4.c:(.text.aml_sfc_set_bus_width+0x8c): undefined reference to `__ffsdi2'
>>    arm-linux-gnueabi-ld: spi-amlogic-spifc-a4.c:(.text.aml_sfc_set_bus_width+0xac): undefined reference to `__ffsdi2'
>>    arm-linux-gnueabi-ld: spi-amlogic-spifc-a4.c:(.text.aml_sfc_set_bus_width+0xcc): undefined reference to `__ffsdi2'
>>    arm-linux-gnueabi-ld: drivers/rtc/rtc-zynqmp.o: in function `xlnx_rtc_read_offset':
>>>> rtc-zynqmp.c:(.text.xlnx_rtc_read_offset+0xd0): undefined reference to `__aeabi_ldivmod'
>>>> arm-linux-gnueabi-ld: rtc-zynqmp.c:(.text.xlnx_rtc_read_offset+0x15c): undefined reference to `__aeabi_ldivmod'
> AFAIU this is related to compiling for arm 32 bit target. Is this error
> relevant since this driver is for aarch64 zynqmp specifically? If so,
> what would be correct way of fixing?

Answering my self, I think correct course of action here is probably to
limit building of this driver for the zynqmp architecture. I will add
this in next version.

Thanks,
Tomas


> 
> Thanks,
> Tomas
> 
> 
> 
>>
> 
> 


^ permalink raw reply

* Re: [RFC PATCH v3 5/5] rust: add PL031 RTC driver
From: Ke Sun @ 2026-01-19  9:12 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
In-Reply-To: <20260116163401.312002-1-sunke@kylinos.cn>

This driver has been tested on QEMU with the following command:
qemu-system-aarch64 -machine virt,virtualization=on -cpu max -smp 2 -m 4g \
   -serial mon:stdio -kernel arch/arm64/boot/Image  -hda 
PATHTO/virtualdisk.qcow2 \
   -append "root=/dev/vdaX no_console_suspend loglevel=8 console=ttyAMA0" \
   -display none -nic user,hostfwd=tcp::10022-:22

Test Results:

Driver Registration:
    [    1.886444][    T1] rtc-pl031-rust 9010000.pl031: registered as rtc0
    [    1.888070][    T1] rtc-pl031-rust 9010000.pl031: setting system 
clock to 2026-01-19T08:49:36 UTC (1768812576)

Interrupt Registration:
    # cat /proc/interrupts |grep pl031
     21:          0          0    GICv2  34 Level     rtc-pl031

RTC Time Reading:
    # hwclock -r
    2026-01-19 09:03:24.961787+08:00

Suspend/Resume with Alarm:
    # rtcwake --mode mem --seconds 5
    rtcwake: wakeup from "mem" using /dev/rtc0 at Mon Jan 19 09:04:13 2026
    [  242.699314][ T2832] PM: suspend entry (s2idle)
    [  242.717296][   T12] Filesystems sync: 0.009 seconds
    [  242.741471][ T2832] Freezing user space processes
    [  242.761472][ T2832] Freezing user space processes completed 
(elapsed 0.019 seconds)
    [  242.763281][ T2832] OOM killer disabled.
    [  242.763681][ T2832] Freezing remaining freezable tasks
    [  242.766629][ T2832] Freezing remaining freezable tasks completed 
(elapsed 0.002 seconds)
    [  248.678304][   T82] virtio_blk virtio1: 2/0/0 default/read/poll 
queues
    [  248.692577][ T2832] OOM killer enabled.
    [  248.692724][ T2832] Restarting tasks: Starting
    [  248.744504][ T2832] Restarting tasks: Done
    [  248.745530][ T2832] random: crng reseeded on system resumption
    [  248.746749][ T2832] PM: suspend exit

Driver Rebind:
    /sys/bus/amba/drivers/rtc-pl031-rust# echo 9010000.pl031 > unbind
    /sys/bus/amba/drivers/rtc-pl031-rust# echo 9010000.pl031 > bind
    [  334.060940][ T1837] rtc-pl031-rust 9010000.pl031: registered as rtc1

All tests passed successfully.

On 1/17/26 00:34, Ke Sun wrote:
> Add Rust implementation of the PL031 RTC driver.
>
> Signed-off-by: Ke Sun <sunke@kylinos.cn>
> ---
>   drivers/rtc/Kconfig           |   9 +
>   drivers/rtc/Makefile          |   1 +
>   drivers/rtc/rtc_pl031_rust.rs | 513 ++++++++++++++++++++++++++++++++++
>   3 files changed, 523 insertions(+)
>   create mode 100644 drivers/rtc/rtc_pl031_rust.rs
>
> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> index 50dc779f7f983..137cea1824edd 100644
> --- a/drivers/rtc/Kconfig
> +++ b/drivers/rtc/Kconfig
> @@ -1591,6 +1591,15 @@ 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
> +	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. The driver supports
> +	  ARM, ST v1, and ST v2 variants of the PL031 RTC controller.
> +
>   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..8ae48d96d1f94
> --- /dev/null
> +++ b/drivers/rtc/rtc_pl031_rust.rs
> @@ -0,0 +1,513 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +//! Rust ARM AMBA PrimeCell 031 RTC driver
> +//!
> +//! This driver provides Real Time Clock functionality for ARM AMBA PrimeCell
> +//! 031 RTC controllers and their ST Microelectronics derivatives.
> +
> +use core::marker::PhantomPinned;
> +use kernel::{
> +    amba,
> +    bindings,
> +    c_str,
> +    device::{
> +        self,
> +        Core, //
> +    },
> +    devres::Devres,
> +    io::mem::IoMem,
> +    irq::{
> +        self,
> +        Handler,
> +        IrqReturn, //
> +    },
> +    prelude::*,
> +    rtc::{
> +        RtcDevice,
> +        RtcOps,
> +        RtcTime,
> +        RtcWkAlrm, //
> +    },
> +    sync::aref::ARef, //
> +};
> +
> +// Register offsets
> +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
> +
> +// ST variants have additional timer functionality
> +#[allow(dead_code)]
> +const RTC_TDR: usize = 0x20; // Timer data read register
> +#[allow(dead_code)]
> +const RTC_TLR: usize = 0x24; // Timer data load register
> +#[allow(dead_code)]
> +const RTC_TCR: usize = 0x28; // Timer control 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
> +const PL031_REG_SIZE: usize = RTC_YLR + 4;
> +
> +// Control register bits
> +const RTC_CR_EN: u32 = 1 << 0; // Counter enable bit
> +const RTC_CR_CWEN: u32 = 1 << 26; // Clockwatch enable bit
> +
> +#[allow(dead_code)]
> +const RTC_TCR_EN: u32 = 1 << 1; // Periodic timer enable bit
> +
> +// Interrupt status and control register bits
> +const RTC_BIT_AI: u32 = 1 << 0; // Alarm interrupt bit
> +#[allow(dead_code)]
> +const RTC_BIT_PI: u32 = 1 << 1; // Periodic interrupt bit (ST variants only)
> +
> +// 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
> +
> +/// 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)
> +}
> +
> +/// Converts a Gregorian date to ST v2 RTC packed BCD format.
> +///
> +/// Returns a tuple of (packed_time, bcd_year) where packed_time contains
> +/// month, day, weekday, hour, minute, and second in a single 32-bit value.
> +fn stv2_tm_to_time(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) {
> +        return Err(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 packed BCD format to a Gregorian date.
> +///
> +/// Extracts time components from the packed 32-bit value and BCD year register,
> +/// then returns an RtcTime structure.
> +fn stv2_time_to_tm(st_time: u32, bcd_year: u32) -> RtcTime {
> +    let year_low = bcd2bin((bcd_year & 0xFF) as u8);
> +    let year_high = bcd2bin(((bcd_year >> 8) & 0xFF) as u8);
> +    let mut tm = RtcTime::default();
> +    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);
> +    tm
> +}
> +
> +/// Vendor-specific variant identifier for PL031 RTC controllers.
> +#[derive(Copy, Clone, PartialEq)]
> +enum VendorVariant {
> +    /// Original ARM version with 32-bit Unix timestamp format.
> +    Arm,
> +    /// First ST derivative with clockwatch mode and weekday support.
> +    StV1,
> +    /// Second ST derivative with packed BCD time format and year register.
> +    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,
> +        }
> +    }
> +}
> +
> +/// The driver's private data struct. It holds all necessary devres managed
> +/// resources.
> +#[pin_data]
> +struct Pl031DrvData {
> +    #[pin]
> +    regs: Devres<IoMem<PL031_REG_SIZE>>,
> +    hw_variant: VendorVariant,
> +}
> +
> +// SAFETY: `Pl031DrvData` contains only `Send`/`Sync` types: `Devres`
> +// (Send+Sync) and `VendorVariant` (Copy).
> +unsafe impl Send for Pl031DrvData {}
> +// SAFETY: `Pl031DrvData` contains only `Send`/`Sync` types: `Devres`
> +// (Send+Sync) and `VendorVariant` (Copy).
> +unsafe impl Sync for Pl031DrvData {}
> +
> +/// Vendor-specific variant identifier used in AMBA device table.
> +#[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,
> +    };
> +}
> +
> +// Use AMBA device table for matching
> +kernel::amba_device_table!(
> +    ID_TABLE,
> +    MODULE_ID_TABLE,
> +    <Pl031AmbaDriver as amba::Driver>::IdInfo,
> +    [
> +        (
> +            amba::DeviceId::new(0x00041031, 0x000fffff),
> +            Pl031Variant::ARM
> +        ),
> +        (
> +            amba::DeviceId::new(0x00180031, 0x00ffffff),
> +            Pl031Variant::STV1
> +        ),
> +        (
> +            amba::DeviceId::new(0x00280031, 0x00ffffff),
> +            Pl031Variant::STV2
> +        ),
> +    ]
> +);
> +
> +#[pin_data]
> +struct Pl031AmbaDriver {
> +    #[pin]
> +    irqreg: irq::Registration<Pl031IrqHandler>,
> +}
> +
> +impl amba::Driver for Pl031AmbaDriver {
> +    type IdInfo = Pl031Variant;
> +    const AMBA_ID_TABLE: amba::IdTable<Self::IdInfo> = &ID_TABLE;
> +
> +    fn probe(
> +        adev: &amba::Device<Core>,
> +        id_info: Option<&Self::IdInfo>,
> +    ) -> impl PinInit<Self, Error> {
> +        pin_init::pin_init_scope(move || {
> +            let dev = adev.as_ref();
> +            let io_request = adev.io_request().ok_or(ENODEV)?;
> +            let variant = id_info
> +                .map(|info| info.variant)
> +                .unwrap_or(VendorVariant::Arm);
> +
> +            let rtcdev = RtcDevice::<Pl031DrvData>::new(
> +                dev,
> +                try_pin_init!(Pl031DrvData {
> +                    regs <- IoMem::new(io_request),
> +                    hw_variant: variant,
> +                }),
> +            )?;
> +
> +            dev.devm_init_wakeup()?;
> +
> +            let drvdata = rtcdev.drvdata()?;
> +            let regs = drvdata.regs.access(dev)?;
> +
> +            // Enable the clockwatch on ST Variants
> +            let mut cr = regs.read32(RTC_CR);
> +            if variant.clockwatch() {
> +                cr |= RTC_CR_CWEN;
> +            } else {
> +                cr |= RTC_CR_EN;
> +            }
> +            regs.write32(cr, RTC_CR);
> +
> +            // On ST PL031 variants, the RTC reset value does not provide
> +            // correct weekday for 2000-01-01. Correct the erroneous sunday
> +            // to saturday.
> +            if variant.st_weekday() {
> +                let bcd_year = regs.read32(RTC_YDR);
> +                if bcd_year == 0x2000 {
> +                    let st_time = regs.read32(RTC_DR);
> +                    if (st_time & (RTC_MON_MASK | RTC_MDAY_MASK | RTC_WDAY_MASK)) == 0x02120000 {
> +                        regs.write32(0x2000, RTC_YLR);
> +                        regs.write32(st_time | (0x7 << RTC_WDAY_SHIFT), RTC_LR);
> +                    }
> +                }
> +            }
> +
> +            rtcdev.set_range_min(variant.range_min());
> +            rtcdev.set_range_max(variant.range_max());
> +
> +            // This variant shares the IRQ with another block and must not
> +            // suspend that IRQ line.
> +            let irq_flags = if variant == VendorVariant::StV2 {
> +                kernel::irq::Flags::SHARED | kernel::irq::Flags::COND_SUSPEND
> +            } else {
> +                kernel::irq::Flags::SHARED
> +            };
> +
> +            if adev
> +                .irq_by_index(0)
> +                .and_then(|irq| irq.devm_set_wake_irq())
> +                .is_err()
> +            {
> +                rtcdev.clear_feature(bindings::RTC_FEATURE_ALARM);
> +            }
> +
> +            rtcdev.register()?;
> +
> +            Ok(try_pin_init!(Pl031AmbaDriver {
> +                irqreg <- adev.request_irq_by_index(
> +                    irq_flags,
> +                    0,
> +                    c_str!("rtc-pl031"),
> +                    try_pin_init!(Pl031IrqHandler {
> +                        _pin: PhantomPinned,
> +                        rtcdev: rtcdev.clone(),
> +                    }),
> +                ),
> +            }))
> +        })
> +    }
> +}
> +
> +/// Interrupt handler for PL031 RTC alarm events.
> +#[pin_data]
> +struct Pl031IrqHandler {
> +    #[pin]
> +    _pin: PhantomPinned,
> +    rtcdev: ARef<RtcDevice<Pl031DrvData>>,
> +}
> +
> +impl Handler for Pl031IrqHandler {
> +    fn handle(&self, dev: &device::Device<device::Bound>) -> IrqReturn {
> +        // Get driver data using drvdata.
> +        let drvdata = match self.rtcdev.drvdata() {
> +            Ok(drvdata) => drvdata,
> +            Err(_) => return IrqReturn::None,
> +        };
> +
> +        // Access the MMIO registers.
> +        let regs = match drvdata.regs.access(dev) {
> +            Ok(regs) => regs,
> +            Err(_) => return IrqReturn::None,
> +        };
> +
> +        // Read masked interrupt status.
> +        let rtcmis = regs.read32(RTC_MIS);
> +
> +        if (rtcmis & RTC_BIT_AI) != 0 {
> +            regs.write32(RTC_BIT_AI, RTC_ICR);
> +            self.rtcdev.update_irq(1, (RTC_AF | RTC_IRQF) as usize);
> +            return IrqReturn::Handled;
> +        }
> +
> +        IrqReturn::None
> +    }
> +}
> +
> +#[vtable]
> +impl RtcOps for Pl031DrvData {
> +    fn read_time(
> +        rtcdev: &RtcDevice<Self>,
> +        tm: &mut RtcTime,
> +        parent_dev: &device::Device<device::Bound>,
> +    ) -> Result {
> +        let drvdata = rtcdev.drvdata()?;
> +        let regs = drvdata.regs.access(parent_dev)?;
> +
> +        match drvdata.hw_variant {
> +            VendorVariant::Arm | VendorVariant::StV1 => {
> +                let time32: u32 = regs.read32(RTC_DR);
> +                let time64 = i64::from(time32);
> +                tm.set_from_time64(time64);
> +            }
> +            VendorVariant::StV2 => {
> +                let st_time = regs.read32(RTC_DR);
> +                let bcd_year = regs.read32(RTC_YDR);
> +                *tm = stv2_time_to_tm(st_time, bcd_year);
> +            }
> +        }
> +
> +        Ok(())
> +    }
> +
> +    fn set_time(
> +        rtcdev: &RtcDevice<Self>,
> +        tm: &RtcTime,
> +        parent_dev: &device::Device<device::Bound>,
> +    ) -> Result {
> +        let dev: &device::Device = rtcdev.as_ref();
> +        let drvdata = rtcdev.drvdata()?;
> +        let regs = drvdata.regs.access(parent_dev)?;
> +
> +        match drvdata.hw_variant {
> +            VendorVariant::Arm | VendorVariant::StV1 => {
> +                let time64 = tm.to_time64();
> +                regs.write32(time64 as u32, RTC_LR);
> +            }
> +            VendorVariant::StV2 => {
> +                let (st_time, bcd_year) = stv2_tm_to_time(tm).inspect_err(|&err| {
> +                    if err == EINVAL {
> +                        dev_err!(dev, "invalid wday value {}\n", tm.tm_wday());
> +                    }
> +                })?;
> +                regs.write32(bcd_year, RTC_YLR);
> +                regs.write32(st_time, RTC_LR);
> +            }
> +        }
> +
> +        Ok(())
> +    }
> +
> +    fn read_alarm(
> +        rtcdev: &RtcDevice<Self>,
> +        alarm: &mut RtcWkAlrm,
> +        parent_dev: &device::Device<device::Bound>,
> +    ) -> Result {
> +        let drvdata = rtcdev.drvdata()?;
> +        let regs = drvdata.regs.access(parent_dev)?;
> +
> +        match drvdata.hw_variant {
> +            VendorVariant::Arm | VendorVariant::StV1 => {
> +                let time32: u32 = regs.read32(RTC_MR);
> +                let time64 = i64::from(time32);
> +                RtcTime::time64_to_tm(time64, alarm.get_time_mut());
> +            }
> +            VendorVariant::StV2 => {
> +                let st_time = regs.read32(RTC_MR);
> +                let bcd_year = regs.read32(RTC_YMR);
> +                *alarm.get_time_mut() = stv2_time_to_tm(st_time, bcd_year);
> +            }
> +        }
> +
> +        alarm.set_pending((regs.read32(RTC_RIS) & RTC_BIT_AI) != 0);
> +        alarm.set_enabled((regs.read32(RTC_IMSC) & RTC_BIT_AI) != 0);
> +
> +        Ok(())
> +    }
> +
> +    fn set_alarm(
> +        rtcdev: &RtcDevice<Self>,
> +        alarm: &RtcWkAlrm,
> +        parent_dev: &device::Device<device::Bound>,
> +    ) -> Result {
> +        let dev: &device::Device = rtcdev.as_ref();
> +        let drvdata = rtcdev.drvdata()?;
> +        let regs = drvdata.regs.access(parent_dev)?;
> +
> +        match drvdata.hw_variant {
> +            VendorVariant::Arm | VendorVariant::StV1 => {
> +                let time64 = alarm.get_time().to_time64();
> +                regs.write32(time64 as u32, RTC_MR);
> +            }
> +            VendorVariant::StV2 => {
> +                let (st_time, bcd_year) =
> +                    stv2_tm_to_time(alarm.get_time()).inspect_err(|&err| {
> +                        if err == EINVAL {
> +                            dev_err!(dev, "invalid wday value {}\n", alarm.get_time().tm_wday());
> +                        }
> +                    })?;
> +                regs.write32(bcd_year, RTC_YMR);
> +                regs.write32(st_time, RTC_MR);
> +            }
> +        }
> +
> +        Self::alarm_irq_enable(rtcdev, alarm.enabled(), parent_dev)
> +    }
> +
> +    fn alarm_irq_enable(
> +        rtcdev: &RtcDevice<Self>,
> +        enabled: bool,
> +        parent_dev: &device::Device<device::Bound>,
> +    ) -> Result {
> +        let drvdata = rtcdev.drvdata()?;
> +        let regs = drvdata.regs.access(parent_dev)?;
> +
> +        // Clear any pending alarm interrupts.
> +        regs.write32(RTC_BIT_AI, RTC_ICR);
> +
> +        let mut imsc = regs.read32(RTC_IMSC);
> +        if enabled {
> +            imsc |= RTC_BIT_AI;
> +        } else {
> +            imsc &= !RTC_BIT_AI;
> +        }
> +        regs.write32(imsc, RTC_IMSC);
> +
> +        Ok(())
> +    }
> +}
> +
> +kernel::module_amba_driver! {
> +    type: Pl031AmbaDriver,
> +    name: "rtc-pl031-rust",
> +    authors: ["Ke Sun <sunke@kylinos.cn>"],
> +    description: "Rust PL031 RTC driver",
> +    license: "GPL v2",
> +    imports_ns: ["RTC"],
> +}

^ permalink raw reply

* [PATCH v1] rtc: add device selector for rtc_class_ops callbacks
From: Ke Sun @ 2026-01-19  7:57 UTC (permalink / raw)
  To: Alexandre Belloni; +Cc: linux-rtc, Ke Sun

Add rtc_ops_dev() helper to allow drivers to choose whether
rtc_class_ops callbacks receive rtc->dev or rtc->dev.parent. This
enables Rust RTC drivers to store driver data on the RTC device [1].

The helper checks RTC_OPS_USE_RTC_DEV flag: if set, returns &rtc->dev;
otherwise returns rtc->dev.parent.

Update all rtc_class_ops callback invocations to use rtc_ops_dev(rtc)
instead of directly accessing rtc->dev.parent.

Maintains backward compatibility for existing C drivers.

Link: https://lore.kernel.org/rust-for-linux/DFHJM2HAG7Q3.1HGZ3P7H55FD2@kernel.org/ [1]
Signed-off-by: Ke Sun <sunke@kylinos.cn>
---
 drivers/rtc/dev.c       |  6 +++---
 drivers/rtc/interface.c | 18 +++++++++---------
 drivers/rtc/proc.c      |  2 +-
 include/linux/rtc.h     | 15 +++++++++++++++
 4 files changed, 28 insertions(+), 13 deletions(-)

diff --git a/drivers/rtc/dev.c b/drivers/rtc/dev.c
index baf1a8ca8b2b1..eddcc5a69db3b 100644
--- a/drivers/rtc/dev.c
+++ b/drivers/rtc/dev.c
@@ -410,7 +410,7 @@ static long rtc_dev_ioctl(struct file *file,
 		}
 		default:
 			if (rtc->ops->param_get)
-				err = rtc->ops->param_get(rtc->dev.parent, &param);
+				err = rtc->ops->param_get(rtc_ops_dev(rtc), &param);
 			else
 				err = -EINVAL;
 		}
@@ -440,7 +440,7 @@ static long rtc_dev_ioctl(struct file *file,
 
 		default:
 			if (rtc->ops->param_set)
-				err = rtc->ops->param_set(rtc->dev.parent, &param);
+				err = rtc->ops->param_set(rtc_ops_dev(rtc), &param);
 			else
 				err = -EINVAL;
 		}
@@ -450,7 +450,7 @@ static long rtc_dev_ioctl(struct file *file,
 	default:
 		/* Finally try the driver's ioctl interface */
 		if (ops->ioctl) {
-			err = ops->ioctl(rtc->dev.parent, cmd, arg);
+			err = ops->ioctl(rtc_ops_dev(rtc), cmd, arg);
 			if (err == -ENOIOCTLCMD)
 				err = -ENOTTY;
 		} else {
diff --git a/drivers/rtc/interface.c b/drivers/rtc/interface.c
index b8b298efd9a9c..4c81130fb0394 100644
--- a/drivers/rtc/interface.c
+++ b/drivers/rtc/interface.c
@@ -91,7 +91,7 @@ static int __rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
 		err = -EINVAL;
 	} else {
 		memset(tm, 0, sizeof(struct rtc_time));
-		err = rtc->ops->read_time(rtc->dev.parent, tm);
+		err = rtc->ops->read_time(rtc_ops_dev(rtc), tm);
 		if (err < 0) {
 			dev_dbg(&rtc->dev, "read_time: fail to read: %d\n",
 				err);
@@ -155,7 +155,7 @@ int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm)
 	if (!rtc->ops)
 		err = -ENODEV;
 	else if (rtc->ops->set_time)
-		err = rtc->ops->set_time(rtc->dev.parent, tm);
+		err = rtc->ops->set_time(rtc_ops_dev(rtc), tm);
 	else
 		err = -EINVAL;
 
@@ -200,7 +200,7 @@ static int rtc_read_alarm_internal(struct rtc_device *rtc,
 		alarm->time.tm_wday = -1;
 		alarm->time.tm_yday = -1;
 		alarm->time.tm_isdst = -1;
-		err = rtc->ops->read_alarm(rtc->dev.parent, alarm);
+		err = rtc->ops->read_alarm(rtc_ops_dev(rtc), alarm);
 	}
 
 	mutex_unlock(&rtc->ops_lock);
@@ -441,7 +441,7 @@ static int __rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
 	else if (!test_bit(RTC_FEATURE_ALARM, rtc->features))
 		err = -EINVAL;
 	else
-		err = rtc->ops->set_alarm(rtc->dev.parent, alarm);
+		err = rtc->ops->set_alarm(rtc_ops_dev(rtc), alarm);
 
 	/*
 	 * Check for potential race described above. If the waiting for next
@@ -568,7 +568,7 @@ int rtc_alarm_irq_enable(struct rtc_device *rtc, unsigned int enabled)
 	else if (!test_bit(RTC_FEATURE_ALARM, rtc->features) || !rtc->ops->alarm_irq_enable)
 		err = -EINVAL;
 	else
-		err = rtc->ops->alarm_irq_enable(rtc->dev.parent, enabled);
+		err = rtc->ops->alarm_irq_enable(rtc_ops_dev(rtc), enabled);
 
 	mutex_unlock(&rtc->ops_lock);
 
@@ -618,7 +618,7 @@ int rtc_update_irq_enable(struct rtc_device *rtc, unsigned int enabled)
 		rtc->uie_rtctimer.period = ktime_set(1, 0);
 		err = rtc_timer_enqueue(rtc, &rtc->uie_rtctimer);
 		if (!err && rtc->ops && rtc->ops->alarm_irq_enable)
-			err = rtc->ops->alarm_irq_enable(rtc->dev.parent, 1);
+			err = rtc->ops->alarm_irq_enable(rtc_ops_dev(rtc), 1);
 		if (err)
 			goto out;
 	} else {
@@ -874,7 +874,7 @@ static void rtc_alarm_disable(struct rtc_device *rtc)
 	if (!rtc->ops || !test_bit(RTC_FEATURE_ALARM, rtc->features) || !rtc->ops->alarm_irq_enable)
 		return;
 
-	rtc->ops->alarm_irq_enable(rtc->dev.parent, false);
+	rtc->ops->alarm_irq_enable(rtc_ops_dev(rtc), false);
 	trace_rtc_alarm_irq_enable(0, 0);
 }
 
@@ -1076,7 +1076,7 @@ int rtc_read_offset(struct rtc_device *rtc, long *offset)
 		return -EINVAL;
 
 	mutex_lock(&rtc->ops_lock);
-	ret = rtc->ops->read_offset(rtc->dev.parent, offset);
+	ret = rtc->ops->read_offset(rtc_ops_dev(rtc), offset);
 	mutex_unlock(&rtc->ops_lock);
 
 	trace_rtc_read_offset(*offset, ret);
@@ -1111,7 +1111,7 @@ int rtc_set_offset(struct rtc_device *rtc, long offset)
 		return -EINVAL;
 
 	mutex_lock(&rtc->ops_lock);
-	ret = rtc->ops->set_offset(rtc->dev.parent, offset);
+	ret = rtc->ops->set_offset(rtc_ops_dev(rtc), offset);
 	mutex_unlock(&rtc->ops_lock);
 
 	trace_rtc_set_offset(offset, ret);
diff --git a/drivers/rtc/proc.c b/drivers/rtc/proc.c
index cbcdbb19d848e..bf688079d0fbb 100644
--- a/drivers/rtc/proc.c
+++ b/drivers/rtc/proc.c
@@ -73,7 +73,7 @@ static int rtc_proc_show(struct seq_file *seq, void *offset)
 	seq_printf(seq, "24hr\t\t: yes\n");
 
 	if (ops->proc)
-		ops->proc(rtc->dev.parent, seq);
+		ops->proc(rtc_ops_dev(rtc), seq);
 
 	return 0;
 }
diff --git a/include/linux/rtc.h b/include/linux/rtc.h
index 95da051fb155d..1dd4a45d0186e 100644
--- a/include/linux/rtc.h
+++ b/include/linux/rtc.h
@@ -83,6 +83,7 @@ struct rtc_timer {
 /* flags */
 #define RTC_DEV_BUSY 0
 #define RTC_NO_CDEV  1
+#define RTC_OPS_USE_RTC_DEV 2
 
 struct rtc_device {
 	struct device dev;
@@ -167,6 +168,20 @@ struct rtc_device {
 #define rtc_lock(d) mutex_lock(&d->ops_lock)
 #define rtc_unlock(d) mutex_unlock(&d->ops_lock)
 
+/**
+ * rtc_ops_dev - Get the device pointer for RTC ops callbacks
+ * @rtc: RTC device
+ *
+ * Returns &rtc->dev if RTC_OPS_USE_RTC_DEV flag is set,
+ * otherwise returns rtc->dev.parent.
+ */
+static inline struct device *rtc_ops_dev(struct rtc_device *rtc)
+{
+	if (test_bit(RTC_OPS_USE_RTC_DEV, &rtc->flags))
+		return &rtc->dev;
+	return rtc->dev.parent;
+}
+
 /* useful timestamps */
 #define RTC_TIMESTAMP_BEGIN_0000	-62167219200ULL /* 0000-01-01 00:00:00 */
 #define RTC_TIMESTAMP_BEGIN_1900	-2208988800LL /* 1900-01-01 00:00:00 */
-- 
2.43.0


^ permalink raw reply related

* [PATCH v3 2/3] dt-bindings: rtc: loongson: Document Loongson-2K0300 compatible
From: Binbin Zhou @ 2026-01-17  2:26 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Alexandre Belloni, linux-rtc
  Cc: Xiaochuang Mao, Huacai Chen, Xuerui Wang, loongarch, devicetree,
	linux-mips, Keguang Zhang, Binbin Zhou
In-Reply-To: <cover.1768616276.git.zhoubinbin@loongson.cn>

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.

Reviewed-by: Huacai Chen <chenhuacai@loongson.cn>
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
 Documentation/devicetree/bindings/rtc/loongson,rtc.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/rtc/loongson,rtc.yaml b/Documentation/devicetree/bindings/rtc/loongson,rtc.yaml
index fac90a18153e..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:
@@ -48,6 +49,7 @@ if:
       contains:
         enum:
           - loongson,ls1c-rtc
+          - loongson,ls2k0300-rtc
 
 then:
   properties:
-- 
2.47.3


^ permalink raw reply related

* [PATCH v3 3/3] rtc: loongson: Add Loongson-2K0300 support
From: Binbin Zhou @ 2026-01-17  2:26 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Alexandre Belloni, linux-rtc
  Cc: Xiaochuang Mao, Huacai Chen, Xuerui Wang, loongarch, devicetree,
	linux-mips, Keguang Zhang, Binbin Zhou
In-Reply-To: <cover.1768616276.git.zhoubinbin@loongson.cn>

The Loongson-2K0300's rtc hardware design is similar to that of the
Loongson-1B, but it does not support the alarm feature.

Introduce `LOONGSON_RTC_ALARM_WORKAROUND`, which indicates a chip that
does not support the alarm feature, and rewrite the related logic in
`loongson_rtc_alarm_setting()`.

Reviewed-by: Huacai Chen <chenhuacai@loongson.cn>
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
 drivers/rtc/rtc-loongson.c | 71 +++++++++++++++++++++++++-------------
 1 file changed, 47 insertions(+), 24 deletions(-)

diff --git a/drivers/rtc/rtc-loongson.c b/drivers/rtc/rtc-loongson.c
index 2ca7ffd5d7a9..066f0644d1c3 100644
--- a/drivers/rtc/rtc-loongson.c
+++ b/drivers/rtc/rtc-loongson.c
@@ -66,7 +66,8 @@
  * According to the LS1C manual, RTC_CTRL and alarm-related registers are not defined.
  * Accessing the relevant registers will cause the system to hang.
  */
-#define LS1C_RTC_CTRL_WORKAROUND	BIT(0)
+#define LOONGSON_RTC_CTRL_WORKAROUND	BIT(0)
+#define LOONGSON_RTC_ALARM_WORKAROUND	BIT(1)
 
 struct loongson_rtc_config {
 	u32 pm_offset;	/* Offset of PM domain, for RTC alarm wakeup */
@@ -89,7 +90,7 @@ static const struct loongson_rtc_config ls1b_rtc_config = {
 
 static const struct loongson_rtc_config ls1c_rtc_config = {
 	.pm_offset = 0,
-	.flags = LS1C_RTC_CTRL_WORKAROUND,
+	.flags = LOONGSON_RTC_CTRL_WORKAROUND | LOONGSON_RTC_ALARM_WORKAROUND,
 };
 
 static const struct loongson_rtc_config generic_rtc_config = {
@@ -97,6 +98,11 @@ static const struct loongson_rtc_config generic_rtc_config = {
 	.flags = 0,
 };
 
+static const struct loongson_rtc_config ls2k0300_rtc_config = {
+	.pm_offset = 0x0,
+	.flags = LOONGSON_RTC_ALARM_WORKAROUND,
+};
+
 static const struct loongson_rtc_config ls2k1000_rtc_config = {
 	.pm_offset = 0x800,
 	.flags = 0,
@@ -153,7 +159,7 @@ static int loongson_rtc_set_enabled(struct device *dev)
 {
 	struct loongson_rtc_priv *priv = dev_get_drvdata(dev);
 
-	if (priv->config->flags & LS1C_RTC_CTRL_WORKAROUND)
+	if (priv->config->flags & LOONGSON_RTC_CTRL_WORKAROUND)
 		return 0;
 
 	/* Enable RTC TOY counters and crystal */
@@ -167,7 +173,7 @@ static bool loongson_rtc_get_enabled(struct device *dev)
 	u32 ctrl_data;
 	struct loongson_rtc_priv *priv = dev_get_drvdata(dev);
 
-	if (priv->config->flags & LS1C_RTC_CTRL_WORKAROUND)
+	if (priv->config->flags & LOONGSON_RTC_CTRL_WORKAROUND)
 		return true;
 
 	ret = regmap_read(priv->regmap, RTC_CTRL_REG, &ctrl_data);
@@ -299,9 +305,41 @@ static const struct rtc_class_ops loongson_rtc_ops = {
 	.alarm_irq_enable = loongson_rtc_alarm_irq_enable,
 };
 
+static int loongson_rtc_alarm_setting(struct platform_device *pdev, void __iomem *regs)
+{
+	int ret = 0, alarm_irq;
+	struct device *dev = &pdev->dev;
+	struct loongson_rtc_priv *priv = dev_get_drvdata(dev);
+
+	if (priv->config->flags & LOONGSON_RTC_ALARM_WORKAROUND) {
+		/* Loongson-1C/Loongson-2K0300 RTC does not support alarm */
+		clear_bit(RTC_FEATURE_ALARM, priv->rtcdev->features);
+		return 0;
+	}
+
+	/* Get RTC alarm irq */
+	alarm_irq = platform_get_irq(pdev, 0);
+	if (alarm_irq < 0)
+		return alarm_irq;
+
+	ret = devm_request_irq(dev, alarm_irq, loongson_rtc_isr, 0, "loongson-alarm",
+			       priv);
+	if (ret < 0)
+		return ret;
+
+	priv->pm_base = regs - priv->config->pm_offset;
+	device_init_wakeup(dev, true);
+
+	if (has_acpi_companion(dev))
+		acpi_install_fixed_event_handler(ACPI_EVENT_RTC,
+						 loongson_rtc_handler, priv);
+
+	return ret;
+}
+
 static int loongson_rtc_probe(struct platform_device *pdev)
 {
-	int ret, alarm_irq;
+	int ret;
 	void __iomem *regs;
 	struct loongson_rtc_priv *priv;
 	struct device *dev = &pdev->dev;
@@ -330,25 +368,9 @@ static int loongson_rtc_probe(struct platform_device *pdev)
 		return dev_err_probe(dev, PTR_ERR(priv->rtcdev),
 				     "devm_rtc_allocate_device failed\n");
 
-	/* Get RTC alarm irq */
-	alarm_irq = platform_get_irq(pdev, 0);
-	if (alarm_irq > 0) {
-		ret = devm_request_irq(dev, alarm_irq, loongson_rtc_isr,
-				       0, "loongson-alarm", priv);
-		if (ret < 0)
-			return dev_err_probe(dev, ret, "Unable to request irq %d\n",
-					     alarm_irq);
-
-		priv->pm_base = regs - priv->config->pm_offset;
-		device_init_wakeup(dev, true);
-
-		if (has_acpi_companion(dev))
-			acpi_install_fixed_event_handler(ACPI_EVENT_RTC,
-							 loongson_rtc_handler, priv);
-	} else {
-		/* Loongson-1C RTC does not support alarm */
-		clear_bit(RTC_FEATURE_ALARM, priv->rtcdev->features);
-	}
+	ret = loongson_rtc_alarm_setting(pdev, regs);
+	if (ret)
+		return ret;
 
 	/* Loongson RTC does not support UIE */
 	clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, priv->rtcdev->features);
@@ -379,6 +401,7 @@ static const struct of_device_id loongson_rtc_of_match[] = {
 	{ .compatible = "loongson,ls1b-rtc", .data = &ls1b_rtc_config },
 	{ .compatible = "loongson,ls1c-rtc", .data = &ls1c_rtc_config },
 	{ .compatible = "loongson,ls7a-rtc", .data = &generic_rtc_config },
+	{ .compatible = "loongson,ls2k0300-rtc", .data = &ls2k0300_rtc_config },
 	{ .compatible = "loongson,ls2k1000-rtc", .data = &ls2k1000_rtc_config },
 	{ /* sentinel */ }
 };
-- 
2.47.3


^ permalink raw reply related

* [PATCH v3 1/3] dt-bindings: rtc: loongson: Correct Loongson-1C interrupts property
From: Binbin Zhou @ 2026-01-17  2:26 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Alexandre Belloni, linux-rtc
  Cc: Xiaochuang Mao, Huacai Chen, Xuerui Wang, loongarch, devicetree,
	linux-mips, Keguang Zhang, Binbin Zhou
In-Reply-To: <cover.1768616276.git.zhoubinbin@loongson.cn>

The `interrupts` property indicates an RTC alarm interrupt, which is
required for RTCs that support the alarm feature, which is not supported
by the Loongson-1C RTC. We exclude it for a more accurate description.

Changing the `allowed` property is ABI-breaking behavior, but
throughout the existing Loongson DTS{i}, the description of the RTC
nodes conforms to the modified bingding rules.

Thus, the existing Loongson DTS{i} will not be affected.

Fixes: 487ef32caebe ("dt-bindings: rtc: Split loongson,ls2x-rtc into SoC-based compatibles")
Reviewed-by: Huacai Chen <chenhuacai@loongson.cn>
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
 .../devicetree/bindings/rtc/loongson,rtc.yaml         | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/Documentation/devicetree/bindings/rtc/loongson,rtc.yaml b/Documentation/devicetree/bindings/rtc/loongson,rtc.yaml
index f89c1f660aee..fac90a18153e 100644
--- a/Documentation/devicetree/bindings/rtc/loongson,rtc.yaml
+++ b/Documentation/devicetree/bindings/rtc/loongson,rtc.yaml
@@ -42,6 +42,17 @@ required:
 
 unevaluatedProperties: false
 
+if:
+  properties:
+    compatible:
+      contains:
+        enum:
+          - loongson,ls1c-rtc
+
+then:
+  properties:
+    interrupts: false
+
 examples:
   - |
     #include <dt-bindings/interrupt-controller/irq.h>
-- 
2.47.3


^ permalink raw reply related

* [PATCH v3 0/3] RTC: Add Loongson-2K0300 support
From: Binbin Zhou @ 2026-01-17  2:26 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Alexandre Belloni, linux-rtc
  Cc: Xiaochuang Mao, Huacai Chen, Xuerui Wang, loongarch, devicetree,
	linux-mips, Keguang Zhang, Binbin Zhou

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.

Thanks.
Binbin

==========
V3:
- Add Reviewed-by tag from Huacai, thanks.

Patch (1/3):
 - Update commit title: dt-binding -> dt-bindings;
 - Update the commit message to describe the binding ABI breaking.
 - Drop `not` and  flip the 'then' and 'else' schemas around.

Patch (2/3):
 - Update commit title: dt-binding -> dt-bindings.

Link to V2:
https://lore.kernel.org/all/cover.1767663073.git.zhoubinbin@loongson.cn/

V2:
Patch (1/3):
 - New patch, correct Loongson-1C `interrupts` property;

Patch (2/3):
 - Drop Loongson-1C changes;

Patch (3/3):
 - Rename LS1C_RTC_CTRL_WORKAROUND to LOONGSON_RTC_CTRL_WORKAROUND for
   consistency.

Link to V1:
https://lore.kernel.org/all/cover.1766471839.git.zhoubinbin@loongson.cn/

Binbin Zhou (3):
  dt-bindings: rtc: loongson: Correct Loongson-1C interrupts property
  dt-bindings: rtc: loongson: Document Loongson-2K0300 compatible
  rtc: loongson: Add Loongson-2K0300 support

 .../devicetree/bindings/rtc/loongson,rtc.yaml | 13 ++++
 drivers/rtc/rtc-loongson.c                    | 71 ++++++++++++-------
 2 files changed, 60 insertions(+), 24 deletions(-)


base-commit: 8f0b4cce4481fb22653697cced8d0d04027cb1e8
-- 
2.47.3


^ permalink raw reply

* [RFC PATCH v3 3/5] rust: add device wakeup capability support
From: Ke Sun @ 2026-01-17  0:44 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: <20260116162203.296844-4-sunke@kylinos.cn>

Add Rust bindings and wrappers for device wakeup functionality,
including devm_device_init_wakeup() and dev_pm_set_wake_irq().

Signed-off-by: Ke Sun <sunke@kylinos.cn>
---
Changes:
- Add new include headers in alphabetical order
---
 rust/bindings/bindings_helper.h |  2 ++
 rust/helpers/device.c           |  6 ++++++
 rust/kernel/device.rs           | 18 +++++++++++++++++-
 rust/kernel/irq/request.rs      | 17 +++++++++++++++++
 4 files changed, 42 insertions(+), 1 deletion(-)

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index fa697287cf71b..327298fb39b00 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -72,6 +72,8 @@
 #include <linux/pid_namespace.h>
 #include <linux/platform_device.h>
 #include <linux/pm_opp.h>
+#include <linux/pm_wakeirq.h>
+#include <linux/pm_wakeup.h>
 #include <linux/poll.h>
 #include <linux/property.h>
 #include <linux/pwm.h>
diff --git a/rust/helpers/device.c b/rust/helpers/device.c
index 9a4316bafedfb..5e31e42e8ff00 100644
--- a/rust/helpers/device.c
+++ b/rust/helpers/device.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 
 #include <linux/device.h>
+#include <linux/pm_wakeup.h>
 
 int rust_helper_devm_add_action(struct device *dev,
 				void (*action)(void *),
@@ -25,3 +26,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 71b200df0f400..b8f7e185e32dd 100644
--- a/rust/kernel/device.rs
+++ b/rust/kernel/device.rs
@@ -5,7 +5,9 @@
 //! C header: [`include/linux/device.h`](srctree/include/linux/device.h)
 
 use crate::{
-    bindings, fmt,
+    bindings,
+    error::to_result,
+    fmt,
     prelude::*,
     sync::aref::ARef,
     types::{ForeignOwnable, Opaque},
@@ -324,6 +326,20 @@ 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. Both the
+    /// wakeup capability and wakeup enable state are automatically
+    /// cleared when the device is removed (resource-managed).
+    ///
+    /// Returns `Ok(())` on success, or an error code on failure.
+    pub fn devm_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()) };
+        to_result(ret)
+    }
 }
 
 impl<Ctx: DeviceContext> Device<Ctx> {
diff --git a/rust/kernel/irq/request.rs b/rust/kernel/irq/request.rs
index b150563fdef80..2484e4b53cdc3 100644
--- a/rust/kernel/irq/request.rs
+++ b/rust/kernel/irq/request.rs
@@ -120,6 +120,23 @@ pub(crate) unsafe fn new(dev: &'a Device<Bound>, irq: u32) -> Self {
     pub fn irq(&self) -> u32 {
         self.irq
     }
+
+    /// Attach the IRQ as a device wake IRQ.
+    ///
+    /// Attaches the device IO interrupt as a wake IRQ. The wake IRQ gets
+    /// automatically configured for wake-up from suspend based on the
+    /// device's sysfs wakeup entry. Typically called during driver probe
+    /// after calling `devm_init_wakeup()`.
+    ///
+    /// The wake IRQ is automatically cleared when the device is removed
+    /// (resource-managed).
+    ///
+    /// Returns `Ok(())` on success, or an error code on failure.
+    pub fn devm_set_wake_irq(&self) -> Result {
+        // SAFETY: `self.as_raw()` is a valid pointer to a `struct device`.
+        let ret = unsafe { bindings::devm_pm_set_wake_irq(self.dev.as_raw(), self.irq as i32) };
+        to_result(ret)
+    }
 }
 
 /// A registration of an IRQ handler for a given IRQ line.
-- 
2.43.0


^ permalink raw reply related

* [RFC PATCH v3 5/5] rust: add PL031 RTC driver
From: Ke Sun @ 2026-01-16 16:34 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: <20260116162203.296844-1-sunke@kylinos.cn>

Add Rust implementation of the PL031 RTC driver.

Signed-off-by: Ke Sun <sunke@kylinos.cn>
---
 drivers/rtc/Kconfig           |   9 +
 drivers/rtc/Makefile          |   1 +
 drivers/rtc/rtc_pl031_rust.rs | 513 ++++++++++++++++++++++++++++++++++
 3 files changed, 523 insertions(+)
 create mode 100644 drivers/rtc/rtc_pl031_rust.rs

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 50dc779f7f983..137cea1824edd 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1591,6 +1591,15 @@ 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
+	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. The driver supports
+	  ARM, ST v1, and ST v2 variants of the PL031 RTC controller.
+
 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..8ae48d96d1f94
--- /dev/null
+++ b/drivers/rtc/rtc_pl031_rust.rs
@@ -0,0 +1,513 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+//! Rust ARM AMBA PrimeCell 031 RTC driver
+//!
+//! This driver provides Real Time Clock functionality for ARM AMBA PrimeCell
+//! 031 RTC controllers and their ST Microelectronics derivatives.
+
+use core::marker::PhantomPinned;
+use kernel::{
+    amba,
+    bindings,
+    c_str,
+    device::{
+        self,
+        Core, //
+    },
+    devres::Devres,
+    io::mem::IoMem,
+    irq::{
+        self,
+        Handler,
+        IrqReturn, //
+    },
+    prelude::*,
+    rtc::{
+        RtcDevice,
+        RtcOps,
+        RtcTime,
+        RtcWkAlrm, //
+    },
+    sync::aref::ARef, //
+};
+
+// Register offsets
+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
+
+// ST variants have additional timer functionality
+#[allow(dead_code)]
+const RTC_TDR: usize = 0x20; // Timer data read register
+#[allow(dead_code)]
+const RTC_TLR: usize = 0x24; // Timer data load register
+#[allow(dead_code)]
+const RTC_TCR: usize = 0x28; // Timer control 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
+const PL031_REG_SIZE: usize = RTC_YLR + 4;
+
+// Control register bits
+const RTC_CR_EN: u32 = 1 << 0; // Counter enable bit
+const RTC_CR_CWEN: u32 = 1 << 26; // Clockwatch enable bit
+
+#[allow(dead_code)]
+const RTC_TCR_EN: u32 = 1 << 1; // Periodic timer enable bit
+
+// Interrupt status and control register bits
+const RTC_BIT_AI: u32 = 1 << 0; // Alarm interrupt bit
+#[allow(dead_code)]
+const RTC_BIT_PI: u32 = 1 << 1; // Periodic interrupt bit (ST variants only)
+
+// 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
+
+/// 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)
+}
+
+/// Converts a Gregorian date to ST v2 RTC packed BCD format.
+///
+/// Returns a tuple of (packed_time, bcd_year) where packed_time contains
+/// month, day, weekday, hour, minute, and second in a single 32-bit value.
+fn stv2_tm_to_time(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) {
+        return Err(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 packed BCD format to a Gregorian date.
+///
+/// Extracts time components from the packed 32-bit value and BCD year register,
+/// then returns an RtcTime structure.
+fn stv2_time_to_tm(st_time: u32, bcd_year: u32) -> RtcTime {
+    let year_low = bcd2bin((bcd_year & 0xFF) as u8);
+    let year_high = bcd2bin(((bcd_year >> 8) & 0xFF) as u8);
+    let mut tm = RtcTime::default();
+    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);
+    tm
+}
+
+/// Vendor-specific variant identifier for PL031 RTC controllers.
+#[derive(Copy, Clone, PartialEq)]
+enum VendorVariant {
+    /// Original ARM version with 32-bit Unix timestamp format.
+    Arm,
+    /// First ST derivative with clockwatch mode and weekday support.
+    StV1,
+    /// Second ST derivative with packed BCD time format and year register.
+    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,
+        }
+    }
+}
+
+/// The driver's private data struct. It holds all necessary devres managed
+/// resources.
+#[pin_data]
+struct Pl031DrvData {
+    #[pin]
+    regs: Devres<IoMem<PL031_REG_SIZE>>,
+    hw_variant: VendorVariant,
+}
+
+// SAFETY: `Pl031DrvData` contains only `Send`/`Sync` types: `Devres`
+// (Send+Sync) and `VendorVariant` (Copy).
+unsafe impl Send for Pl031DrvData {}
+// SAFETY: `Pl031DrvData` contains only `Send`/`Sync` types: `Devres`
+// (Send+Sync) and `VendorVariant` (Copy).
+unsafe impl Sync for Pl031DrvData {}
+
+/// Vendor-specific variant identifier used in AMBA device table.
+#[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,
+    };
+}
+
+// Use AMBA device table for matching
+kernel::amba_device_table!(
+    ID_TABLE,
+    MODULE_ID_TABLE,
+    <Pl031AmbaDriver as amba::Driver>::IdInfo,
+    [
+        (
+            amba::DeviceId::new(0x00041031, 0x000fffff),
+            Pl031Variant::ARM
+        ),
+        (
+            amba::DeviceId::new(0x00180031, 0x00ffffff),
+            Pl031Variant::STV1
+        ),
+        (
+            amba::DeviceId::new(0x00280031, 0x00ffffff),
+            Pl031Variant::STV2
+        ),
+    ]
+);
+
+#[pin_data]
+struct Pl031AmbaDriver {
+    #[pin]
+    irqreg: irq::Registration<Pl031IrqHandler>,
+}
+
+impl amba::Driver for Pl031AmbaDriver {
+    type IdInfo = Pl031Variant;
+    const AMBA_ID_TABLE: amba::IdTable<Self::IdInfo> = &ID_TABLE;
+
+    fn probe(
+        adev: &amba::Device<Core>,
+        id_info: Option<&Self::IdInfo>,
+    ) -> impl PinInit<Self, Error> {
+        pin_init::pin_init_scope(move || {
+            let dev = adev.as_ref();
+            let io_request = adev.io_request().ok_or(ENODEV)?;
+            let variant = id_info
+                .map(|info| info.variant)
+                .unwrap_or(VendorVariant::Arm);
+
+            let rtcdev = RtcDevice::<Pl031DrvData>::new(
+                dev,
+                try_pin_init!(Pl031DrvData {
+                    regs <- IoMem::new(io_request),
+                    hw_variant: variant,
+                }),
+            )?;
+
+            dev.devm_init_wakeup()?;
+
+            let drvdata = rtcdev.drvdata()?;
+            let regs = drvdata.regs.access(dev)?;
+
+            // Enable the clockwatch on ST Variants
+            let mut cr = regs.read32(RTC_CR);
+            if variant.clockwatch() {
+                cr |= RTC_CR_CWEN;
+            } else {
+                cr |= RTC_CR_EN;
+            }
+            regs.write32(cr, RTC_CR);
+
+            // On ST PL031 variants, the RTC reset value does not provide
+            // correct weekday for 2000-01-01. Correct the erroneous sunday
+            // to saturday.
+            if variant.st_weekday() {
+                let bcd_year = regs.read32(RTC_YDR);
+                if bcd_year == 0x2000 {
+                    let st_time = regs.read32(RTC_DR);
+                    if (st_time & (RTC_MON_MASK | RTC_MDAY_MASK | RTC_WDAY_MASK)) == 0x02120000 {
+                        regs.write32(0x2000, RTC_YLR);
+                        regs.write32(st_time | (0x7 << RTC_WDAY_SHIFT), RTC_LR);
+                    }
+                }
+            }
+
+            rtcdev.set_range_min(variant.range_min());
+            rtcdev.set_range_max(variant.range_max());
+
+            // This variant shares the IRQ with another block and must not
+            // suspend that IRQ line.
+            let irq_flags = if variant == VendorVariant::StV2 {
+                kernel::irq::Flags::SHARED | kernel::irq::Flags::COND_SUSPEND
+            } else {
+                kernel::irq::Flags::SHARED
+            };
+
+            if adev
+                .irq_by_index(0)
+                .and_then(|irq| irq.devm_set_wake_irq())
+                .is_err()
+            {
+                rtcdev.clear_feature(bindings::RTC_FEATURE_ALARM);
+            }
+
+            rtcdev.register()?;
+
+            Ok(try_pin_init!(Pl031AmbaDriver {
+                irqreg <- adev.request_irq_by_index(
+                    irq_flags,
+                    0,
+                    c_str!("rtc-pl031"),
+                    try_pin_init!(Pl031IrqHandler {
+                        _pin: PhantomPinned,
+                        rtcdev: rtcdev.clone(),
+                    }),
+                ),
+            }))
+        })
+    }
+}
+
+/// Interrupt handler for PL031 RTC alarm events.
+#[pin_data]
+struct Pl031IrqHandler {
+    #[pin]
+    _pin: PhantomPinned,
+    rtcdev: ARef<RtcDevice<Pl031DrvData>>,
+}
+
+impl Handler for Pl031IrqHandler {
+    fn handle(&self, dev: &device::Device<device::Bound>) -> IrqReturn {
+        // Get driver data using drvdata.
+        let drvdata = match self.rtcdev.drvdata() {
+            Ok(drvdata) => drvdata,
+            Err(_) => return IrqReturn::None,
+        };
+
+        // Access the MMIO registers.
+        let regs = match drvdata.regs.access(dev) {
+            Ok(regs) => regs,
+            Err(_) => return IrqReturn::None,
+        };
+
+        // Read masked interrupt status.
+        let rtcmis = regs.read32(RTC_MIS);
+
+        if (rtcmis & RTC_BIT_AI) != 0 {
+            regs.write32(RTC_BIT_AI, RTC_ICR);
+            self.rtcdev.update_irq(1, (RTC_AF | RTC_IRQF) as usize);
+            return IrqReturn::Handled;
+        }
+
+        IrqReturn::None
+    }
+}
+
+#[vtable]
+impl RtcOps for Pl031DrvData {
+    fn read_time(
+        rtcdev: &RtcDevice<Self>,
+        tm: &mut RtcTime,
+        parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        let drvdata = rtcdev.drvdata()?;
+        let regs = drvdata.regs.access(parent_dev)?;
+
+        match drvdata.hw_variant {
+            VendorVariant::Arm | VendorVariant::StV1 => {
+                let time32: u32 = regs.read32(RTC_DR);
+                let time64 = i64::from(time32);
+                tm.set_from_time64(time64);
+            }
+            VendorVariant::StV2 => {
+                let st_time = regs.read32(RTC_DR);
+                let bcd_year = regs.read32(RTC_YDR);
+                *tm = stv2_time_to_tm(st_time, bcd_year);
+            }
+        }
+
+        Ok(())
+    }
+
+    fn set_time(
+        rtcdev: &RtcDevice<Self>,
+        tm: &RtcTime,
+        parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        let dev: &device::Device = rtcdev.as_ref();
+        let drvdata = rtcdev.drvdata()?;
+        let regs = drvdata.regs.access(parent_dev)?;
+
+        match drvdata.hw_variant {
+            VendorVariant::Arm | VendorVariant::StV1 => {
+                let time64 = tm.to_time64();
+                regs.write32(time64 as u32, RTC_LR);
+            }
+            VendorVariant::StV2 => {
+                let (st_time, bcd_year) = stv2_tm_to_time(tm).inspect_err(|&err| {
+                    if err == EINVAL {
+                        dev_err!(dev, "invalid wday value {}\n", tm.tm_wday());
+                    }
+                })?;
+                regs.write32(bcd_year, RTC_YLR);
+                regs.write32(st_time, RTC_LR);
+            }
+        }
+
+        Ok(())
+    }
+
+    fn read_alarm(
+        rtcdev: &RtcDevice<Self>,
+        alarm: &mut RtcWkAlrm,
+        parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        let drvdata = rtcdev.drvdata()?;
+        let regs = drvdata.regs.access(parent_dev)?;
+
+        match drvdata.hw_variant {
+            VendorVariant::Arm | VendorVariant::StV1 => {
+                let time32: u32 = regs.read32(RTC_MR);
+                let time64 = i64::from(time32);
+                RtcTime::time64_to_tm(time64, alarm.get_time_mut());
+            }
+            VendorVariant::StV2 => {
+                let st_time = regs.read32(RTC_MR);
+                let bcd_year = regs.read32(RTC_YMR);
+                *alarm.get_time_mut() = stv2_time_to_tm(st_time, bcd_year);
+            }
+        }
+
+        alarm.set_pending((regs.read32(RTC_RIS) & RTC_BIT_AI) != 0);
+        alarm.set_enabled((regs.read32(RTC_IMSC) & RTC_BIT_AI) != 0);
+
+        Ok(())
+    }
+
+    fn set_alarm(
+        rtcdev: &RtcDevice<Self>,
+        alarm: &RtcWkAlrm,
+        parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        let dev: &device::Device = rtcdev.as_ref();
+        let drvdata = rtcdev.drvdata()?;
+        let regs = drvdata.regs.access(parent_dev)?;
+
+        match drvdata.hw_variant {
+            VendorVariant::Arm | VendorVariant::StV1 => {
+                let time64 = alarm.get_time().to_time64();
+                regs.write32(time64 as u32, RTC_MR);
+            }
+            VendorVariant::StV2 => {
+                let (st_time, bcd_year) =
+                    stv2_tm_to_time(alarm.get_time()).inspect_err(|&err| {
+                        if err == EINVAL {
+                            dev_err!(dev, "invalid wday value {}\n", alarm.get_time().tm_wday());
+                        }
+                    })?;
+                regs.write32(bcd_year, RTC_YMR);
+                regs.write32(st_time, RTC_MR);
+            }
+        }
+
+        Self::alarm_irq_enable(rtcdev, alarm.enabled(), parent_dev)
+    }
+
+    fn alarm_irq_enable(
+        rtcdev: &RtcDevice<Self>,
+        enabled: bool,
+        parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        let drvdata = rtcdev.drvdata()?;
+        let regs = drvdata.regs.access(parent_dev)?;
+
+        // Clear any pending alarm interrupts.
+        regs.write32(RTC_BIT_AI, RTC_ICR);
+
+        let mut imsc = regs.read32(RTC_IMSC);
+        if enabled {
+            imsc |= RTC_BIT_AI;
+        } else {
+            imsc &= !RTC_BIT_AI;
+        }
+        regs.write32(imsc, RTC_IMSC);
+
+        Ok(())
+    }
+}
+
+kernel::module_amba_driver! {
+    type: Pl031AmbaDriver,
+    name: "rtc-pl031-rust",
+    authors: ["Ke Sun <sunke@kylinos.cn>"],
+    description: "Rust PL031 RTC driver",
+    license: "GPL v2",
+    imports_ns: ["RTC"],
+}
-- 
2.43.0


^ permalink raw reply related

* Re: [RFC PATCH v3 1/5] rtc: add device selector for rtc_class_ops callbacks
From: Ke Sun @ 2026-01-16 16:24 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
In-Reply-To: <20260116162203.296844-2-sunke@kylinos.cn>

Hi Alexandre,

I have an alternative solution based on our previous v2 discussion [1]. I've
completed modifications for most of the drivers and am currently doing a 
manual
review. If you're interested, I can share it for discussion.

[1] 
https://lore.kernel.org/rust-for-linux/7c6af8a1-9c5e-46b1-8c17-8ffd443fa6aa@kylinos.cn/

Best regards,
Ke Sun

On 1/17/26 00:21, Ke Sun wrote:
> Add rtc_ops_dev() helper to allow drivers to choose whether
> rtc_class_ops callbacks receive rtc->dev or rtc->dev.parent. This
> enables Rust RTC drivers to store driver data on the RTC device.
>
> The helper checks RTC_OPS_USE_RTC_DEV flag: if set, returns &rtc->dev;
> otherwise returns rtc->dev.parent.
>
> Update all rtc_class_ops callback invocations to use rtc_ops_dev(rtc)
> instead of directly accessing rtc->dev.parent.
>
> Maintains backward compatibility for existing C drivers.
>
> Signed-off-by: Ke Sun <sunke@kylinos.cn>
> ---
>   drivers/rtc/dev.c       |  6 +++---
>   drivers/rtc/interface.c | 18 +++++++++---------
>   drivers/rtc/proc.c      |  2 +-
>   include/linux/rtc.h     | 15 +++++++++++++++
>   4 files changed, 28 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/rtc/dev.c b/drivers/rtc/dev.c
> index baf1a8ca8b2b1..eddcc5a69db3b 100644
> --- a/drivers/rtc/dev.c
> +++ b/drivers/rtc/dev.c
> @@ -410,7 +410,7 @@ static long rtc_dev_ioctl(struct file *file,
>   		}
>   		default:
>   			if (rtc->ops->param_get)
> -				err = rtc->ops->param_get(rtc->dev.parent, &param);
> +				err = rtc->ops->param_get(rtc_ops_dev(rtc), &param);
>   			else
>   				err = -EINVAL;
>   		}
> @@ -440,7 +440,7 @@ static long rtc_dev_ioctl(struct file *file,
>   
>   		default:
>   			if (rtc->ops->param_set)
> -				err = rtc->ops->param_set(rtc->dev.parent, &param);
> +				err = rtc->ops->param_set(rtc_ops_dev(rtc), &param);
>   			else
>   				err = -EINVAL;
>   		}
> @@ -450,7 +450,7 @@ static long rtc_dev_ioctl(struct file *file,
>   	default:
>   		/* Finally try the driver's ioctl interface */
>   		if (ops->ioctl) {
> -			err = ops->ioctl(rtc->dev.parent, cmd, arg);
> +			err = ops->ioctl(rtc_ops_dev(rtc), cmd, arg);
>   			if (err == -ENOIOCTLCMD)
>   				err = -ENOTTY;
>   		} else {
> diff --git a/drivers/rtc/interface.c b/drivers/rtc/interface.c
> index b8b298efd9a9c..4c81130fb0394 100644
> --- a/drivers/rtc/interface.c
> +++ b/drivers/rtc/interface.c
> @@ -91,7 +91,7 @@ static int __rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
>   		err = -EINVAL;
>   	} else {
>   		memset(tm, 0, sizeof(struct rtc_time));
> -		err = rtc->ops->read_time(rtc->dev.parent, tm);
> +		err = rtc->ops->read_time(rtc_ops_dev(rtc), tm);
>   		if (err < 0) {
>   			dev_dbg(&rtc->dev, "read_time: fail to read: %d\n",
>   				err);
> @@ -155,7 +155,7 @@ int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm)
>   	if (!rtc->ops)
>   		err = -ENODEV;
>   	else if (rtc->ops->set_time)
> -		err = rtc->ops->set_time(rtc->dev.parent, tm);
> +		err = rtc->ops->set_time(rtc_ops_dev(rtc), tm);
>   	else
>   		err = -EINVAL;
>   
> @@ -200,7 +200,7 @@ static int rtc_read_alarm_internal(struct rtc_device *rtc,
>   		alarm->time.tm_wday = -1;
>   		alarm->time.tm_yday = -1;
>   		alarm->time.tm_isdst = -1;
> -		err = rtc->ops->read_alarm(rtc->dev.parent, alarm);
> +		err = rtc->ops->read_alarm(rtc_ops_dev(rtc), alarm);
>   	}
>   
>   	mutex_unlock(&rtc->ops_lock);
> @@ -441,7 +441,7 @@ static int __rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
>   	else if (!test_bit(RTC_FEATURE_ALARM, rtc->features))
>   		err = -EINVAL;
>   	else
> -		err = rtc->ops->set_alarm(rtc->dev.parent, alarm);
> +		err = rtc->ops->set_alarm(rtc_ops_dev(rtc), alarm);
>   
>   	/*
>   	 * Check for potential race described above. If the waiting for next
> @@ -568,7 +568,7 @@ int rtc_alarm_irq_enable(struct rtc_device *rtc, unsigned int enabled)
>   	else if (!test_bit(RTC_FEATURE_ALARM, rtc->features) || !rtc->ops->alarm_irq_enable)
>   		err = -EINVAL;
>   	else
> -		err = rtc->ops->alarm_irq_enable(rtc->dev.parent, enabled);
> +		err = rtc->ops->alarm_irq_enable(rtc_ops_dev(rtc), enabled);
>   
>   	mutex_unlock(&rtc->ops_lock);
>   
> @@ -618,7 +618,7 @@ int rtc_update_irq_enable(struct rtc_device *rtc, unsigned int enabled)
>   		rtc->uie_rtctimer.period = ktime_set(1, 0);
>   		err = rtc_timer_enqueue(rtc, &rtc->uie_rtctimer);
>   		if (!err && rtc->ops && rtc->ops->alarm_irq_enable)
> -			err = rtc->ops->alarm_irq_enable(rtc->dev.parent, 1);
> +			err = rtc->ops->alarm_irq_enable(rtc_ops_dev(rtc), 1);
>   		if (err)
>   			goto out;
>   	} else {
> @@ -874,7 +874,7 @@ static void rtc_alarm_disable(struct rtc_device *rtc)
>   	if (!rtc->ops || !test_bit(RTC_FEATURE_ALARM, rtc->features) || !rtc->ops->alarm_irq_enable)
>   		return;
>   
> -	rtc->ops->alarm_irq_enable(rtc->dev.parent, false);
> +	rtc->ops->alarm_irq_enable(rtc_ops_dev(rtc), false);
>   	trace_rtc_alarm_irq_enable(0, 0);
>   }
>   
> @@ -1076,7 +1076,7 @@ int rtc_read_offset(struct rtc_device *rtc, long *offset)
>   		return -EINVAL;
>   
>   	mutex_lock(&rtc->ops_lock);
> -	ret = rtc->ops->read_offset(rtc->dev.parent, offset);
> +	ret = rtc->ops->read_offset(rtc_ops_dev(rtc), offset);
>   	mutex_unlock(&rtc->ops_lock);
>   
>   	trace_rtc_read_offset(*offset, ret);
> @@ -1111,7 +1111,7 @@ int rtc_set_offset(struct rtc_device *rtc, long offset)
>   		return -EINVAL;
>   
>   	mutex_lock(&rtc->ops_lock);
> -	ret = rtc->ops->set_offset(rtc->dev.parent, offset);
> +	ret = rtc->ops->set_offset(rtc_ops_dev(rtc), offset);
>   	mutex_unlock(&rtc->ops_lock);
>   
>   	trace_rtc_set_offset(offset, ret);
> diff --git a/drivers/rtc/proc.c b/drivers/rtc/proc.c
> index cbcdbb19d848e..bf688079d0fbb 100644
> --- a/drivers/rtc/proc.c
> +++ b/drivers/rtc/proc.c
> @@ -73,7 +73,7 @@ static int rtc_proc_show(struct seq_file *seq, void *offset)
>   	seq_printf(seq, "24hr\t\t: yes\n");
>   
>   	if (ops->proc)
> -		ops->proc(rtc->dev.parent, seq);
> +		ops->proc(rtc_ops_dev(rtc), seq);
>   
>   	return 0;
>   }
> diff --git a/include/linux/rtc.h b/include/linux/rtc.h
> index 95da051fb155d..1dd4a45d0186e 100644
> --- a/include/linux/rtc.h
> +++ b/include/linux/rtc.h
> @@ -83,6 +83,7 @@ struct rtc_timer {
>   /* flags */
>   #define RTC_DEV_BUSY 0
>   #define RTC_NO_CDEV  1
> +#define RTC_OPS_USE_RTC_DEV 2
>   
>   struct rtc_device {
>   	struct device dev;
> @@ -167,6 +168,20 @@ struct rtc_device {
>   #define rtc_lock(d) mutex_lock(&d->ops_lock)
>   #define rtc_unlock(d) mutex_unlock(&d->ops_lock)
>   
> +/**
> + * rtc_ops_dev - Get the device pointer for RTC ops callbacks
> + * @rtc: RTC device
> + *
> + * Returns &rtc->dev if RTC_OPS_USE_RTC_DEV flag is set,
> + * otherwise returns rtc->dev.parent.
> + */
> +static inline struct device *rtc_ops_dev(struct rtc_device *rtc)
> +{
> +	if (test_bit(RTC_OPS_USE_RTC_DEV, &rtc->flags))
> +		return &rtc->dev;
> +	return rtc->dev.parent;
> +}
> +
>   /* useful timestamps */
>   #define RTC_TIMESTAMP_BEGIN_0000	-62167219200ULL /* 0000-01-01 00:00:00 */
>   #define RTC_TIMESTAMP_BEGIN_1900	-2208988800LL /* 1900-01-01 00:00:00 */

^ permalink raw reply

* [RFC PATCH v3 4/5] rust: add RTC core abstractions and data structures
From: Ke Sun @ 2026-01-16 16:22 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: <20260116162203.296844-1-sunke@kylinos.cn>

Add Rust abstractions for RTC subsystem, including RtcDevice,
RtcTime, RtcWkAlrm data structures, RtcOps trait for driver
operations, and devm-managed device registration support.

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     | 1008 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1020 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..27066ef900f0a
--- /dev/null
+++ b/rust/kernel/rtc.rs
@@ -0,0 +1,1008 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! RTC (Real-Time Clock) device support.
+//!
+//! C header: [`include/linux/rtc.h`](srctree/include/linux/rtc.h).
+use crate::{
+    bindings,
+    bitmap::Bitmap,
+    container_of,
+    device, //
+    error::{
+        from_err_ptr,
+        to_result,
+        Error,
+        VTABLE_DEFAULT_ERROR, //
+    },
+    prelude::*,
+    seq_file::SeqFile,
+    sync::aref::{
+        ARef,
+        AlwaysRefCounted, //
+    },
+    types::{
+        ForeignOwnable,
+        Opaque, //
+    },
+};
+
+use core::{
+    ffi::c_void,
+    marker::PhantomData,
+    ptr::NonNull, //
+};
+
+/// RTC time structure.
+/// Mirrors [`struct rtc_time`](srctree/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.
+    pub fn from_time64(time: i64) -> Self {
+        let mut tm = Self(pin_init::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.
+    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.
+    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.
+    #[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 {
+        Self(pin_init::zeroed())
+    }
+}
+
+/// RTC wake alarm structure.
+/// Mirrors [`struct rtc_wkalrm`](srctree/include/uapi/linux/rtc.h).
+#[repr(transparent)]
+#[derive(Clone, Copy)]
+pub struct RtcWkAlrm(pub bindings::rtc_wkalrm);
+
+impl Default for RtcWkAlrm {
+    fn default() -> Self {
+        Self(pin_init::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.
+    #[inline]
+    pub fn set_time(&mut self, time: RtcTime) {
+        self.0.time = time.0;
+    }
+
+    /// Returns the enabled field.
+    #[inline]
+    pub fn enabled(&self) -> bool {
+        self.0.enabled != 0
+    }
+
+    /// Sets the `enabled` field.
+    #[inline]
+    pub fn set_enabled(&mut self, enabled: bool) {
+        self.0.enabled = u8::from(enabled);
+    }
+
+    /// Returns the pending field.
+    #[inline]
+    pub fn pending(&self) -> bool {
+        self.0.pending != 0
+    }
+
+    /// Sets the `pending` field.
+    #[inline]
+    pub fn set_pending(&mut self, pending: bool) {
+        self.0.pending = u8::from(pending);
+    }
+}
+
+/// RTC parameter structure.
+/// Mirrors [`struct rtc_param`](srctree/include/uapi/linux/rtc.h).
+#[repr(transparent)]
+#[derive(Clone, Copy)]
+pub struct RtcParam(pub bindings::rtc_param);
+
+impl Default for RtcParam {
+    fn default() -> Self {
+        Self(pin_init::zeroed())
+    }
+}
+
+/// Wrapper for an RTC device
+/// [`struct rtc_device`](srctree/include/linux/rtc.h).
+///
+/// # Invariants
+///
+/// A [`RtcDevice`] instance holds a pointer to a valid
+/// [`struct rtc_device`] that is registered and managed by the kernel.
+///
+#[repr(transparent)]
+pub struct RtcDevice<T: 'static = ()>(Opaque<bindings::rtc_device>, PhantomData<T>);
+
+impl<T: 'static> RtcDevice<T> {
+    /// Returns a raw pointer to the underlying `rtc_device`.
+    #[inline]
+    pub fn as_raw(&self) -> *mut bindings::rtc_device {
+        self.0.get()
+    }
+
+    /// Sets 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 };
+    }
+
+    /// Sets 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 };
+    }
+
+    /// Gets 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 }
+    }
+
+    /// Gets 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 }
+    }
+
+    /// Notifies the RTC framework that an interrupt has occurred.
+    #[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) };
+    }
+
+    /// Clears 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<T: 'static> AsRef<device::Device> for RtcDevice<T> {
+    fn as_ref(&self) -> &device::Device {
+        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<T: 'static> AlwaysRefCounted for RtcDevice<T> {
+    fn inc_ref(&self) {
+        let dev: &device::Device = self.as_ref();
+        // SAFETY: The existence of a shared reference guarantees that the
+        // refcount is non-zero. `dev.as_raw()` is a valid pointer to a
+        // `struct device` with a non-zero refcount.
+        unsafe { bindings::get_device(dev.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<T: 'static> Send for RtcDevice<T> {}
+
+// 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<T: 'static> Sync for RtcDevice<T> {}
+
+impl<T: RtcOps> RtcDevice<T> {
+    /// Allocates a new RTC device managed by devres.
+    pub fn new(
+        parent_dev: &device::Device<device::Bound>,
+        init: impl PinInit<T, Error>,
+    ) -> Result<ARef<Self>> {
+        // Allocate RTC device.
+        // SAFETY: `devm_rtc_allocate_device` returns a pointer to a
+        // devm-managed rtc_device. We use `dev_internal.as_raw()` which is
+        // `pub(crate)`, but we can access it through the same device pointer.
+        let rtc: *mut bindings::rtc_device =
+            from_err_ptr(unsafe { bindings::devm_rtc_allocate_device(parent_dev.as_raw()) })?;
+
+        // Set the RTC device ops.
+        // SAFETY: We just allocated the RTC device, so it's safe to set
+        // the ops.
+        unsafe { (*rtc).ops = Adapter::<T>::VTABLE.as_raw() };
+
+        // 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 rtcdev = unsafe { &*rtc.cast::<Self>() };
+        // SAFETY: `rtc_device.as_raw()` is a valid pointer to a `struct
+        // rtc_device`.
+        let flags = unsafe {
+            Bitmap::from_raw_mut(
+                &mut (*rtcdev.as_raw()).flags,
+                bindings::RTC_FEATURE_CNT as usize,
+            )
+        };
+        flags.set_bit(bindings::RTC_OPS_USE_RTC_DEV as usize);
+
+        rtcdev.set_drvdata(init)?;
+        Ok(rtcdev.into())
+    }
+
+    /// Store a pointer to the bound driver's private data.
+    fn set_drvdata(&self, data: impl PinInit<T, Error>) -> Result {
+        let data = KBox::pin_init(data, GFP_KERNEL)?;
+
+        // SAFETY: `self.as_raw()` is a valid pointer to a `struct rtc_device`.
+        unsafe { bindings::dev_set_drvdata(self.as_ref().as_raw(), data.into_foreign().cast()) };
+        Ok(())
+    }
+
+    /// Borrows the driver's private data bound to this [`RtcDevice`].
+    pub fn drvdata(&self) -> Result<Pin<&T>> {
+        // SAFETY: `self.as_raw()` is a valid pointer to a `struct device`.
+        let ptr = unsafe { bindings::dev_get_drvdata(self.as_ref().as_raw()) };
+
+        if ptr.is_null() {
+            return Err(ENOENT);
+        }
+
+        // SAFETY: The caller ensures that `ptr` is valid and writable.
+        Ok(unsafe { Pin::<KBox<T>>::borrow(ptr.cast()) })
+    }
+
+    /// Registers this RTC device with the RTC subsystem.
+    pub fn register(&self) -> Result {
+        // Registers an RTC device with the RTC subsystem.
+        // SAFETY: The device will be automatically unregistered when the parent
+        // device is removed (devm cleanup). The helper function uses
+        // `THIS_MODULE` internally.
+        to_result(unsafe { bindings::devm_rtc_register_device(self.as_raw()) })
+    }
+
+    /// Returns a reference to the parent device of this RTC device.
+    ///
+    /// # Safety
+    ///
+    /// The caller must guarantee that the parent device exists and is bound.
+    /// This is guaranteed by the RTC core during `RtcOps` callbacks.
+    pub unsafe fn bound_parent_device(&self) -> &device::Device<device::Bound> {
+        // SAFETY: Per the function's safety contract, the parent device exists.
+        let parent = unsafe { self.as_ref().parent().unwrap_unchecked() };
+
+        // SAFETY: Per the function's safety contract, the parent device is
+        // bound. This is guaranteed by the RTC core during `RtcOps`
+        // callbacks.
+        unsafe { parent.as_bound() }
+    }
+}
+
+impl<T: 'static> Drop for RtcDevice<T> {
+    fn drop(&mut self) {
+        let dev = self.as_ref().as_raw();
+
+        // SAFETY: By the type invariants, `self.as_raw()` is a valid pointer to
+        // a `struct device`.
+        let ptr: *mut c_void = unsafe { bindings::dev_get_drvdata(dev) };
+
+        // SAFETY: By the type invariants, `self.as_raw()` is a valid pointer to
+        // a `struct device`.
+        unsafe { bindings::dev_set_drvdata(dev, core::ptr::null_mut()) };
+
+        if !ptr.is_null() {
+            // SAFETY: `ptr` comes from a previous call to `into_foreign()`,
+            // and `dev_get_drvdata()` guarantees to return the same pointer
+            // given to `dev_set_drvdata()`.
+            unsafe { drop(Pin::<KBox<T>>::from_foreign(ptr.cast())) };
+        }
+    }
+}
+
+/// Options for creating an RTC device.
+#[derive(Copy, Clone)]
+pub struct RtcDeviceOptions {
+    /// The name of the RTC device.
+    pub name: &'static CStr,
+}
+
+/// Trait defining the operations for an RTC driver.
+#[vtable]
+pub trait RtcOps: Sized + 'static {
+    /// Reads the current time from the RTC.
+    fn read_time(
+        _rtcdev: &RtcDevice<Self>,
+        _tm: &mut RtcTime,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Sets the time in the RTC.
+    fn set_time(
+        _rtcdev: &RtcDevice<Self>,
+        _tm: &RtcTime,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Reads the alarm time from the RTC.
+    fn read_alarm(
+        _rtcdev: &RtcDevice<Self>,
+        _alarm: &mut RtcWkAlrm,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Sets the alarm time in the RTC.
+    fn set_alarm(
+        _rtcdev: &RtcDevice<Self>,
+        _alarm: &RtcWkAlrm,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Enables or disables the alarm interrupt.
+    fn alarm_irq_enable(
+        _rtcdev: &RtcDevice<Self>,
+        _enabled: bool,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Handles custom ioctl commands.
+    fn ioctl(
+        _rtcdev: &RtcDevice<Self>,
+        _cmd: u32,
+        _arg: c_ulong,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result<c_int> {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Shows information in /proc/driver/rtc.
+    fn proc(
+        _rtcdev: &RtcDevice<Self>,
+        _seq: &mut SeqFile,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Reads the time offset.
+    fn read_offset(
+        _rtcdev: &RtcDevice<Self>,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result<i64> {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Sets the time offset.
+    fn set_offset(
+        _rtcdev: &RtcDevice<Self>,
+        _offset: i64,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Gets an RTC parameter.
+    fn param_get(
+        _rtcdev: &RtcDevice<Self>,
+        _param: &mut RtcParam,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Sets an RTC parameter.
+    fn param_set(
+        _rtcdev: &RtcDevice<Self>,
+        _param: &RtcParam,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+}
+
+struct Adapter<T: RtcOps> {
+    _p: PhantomData<T>,
+}
+
+impl<T: RtcOps> Adapter<T> {
+    const VTABLE: RtcOpsVTable = create_rtc_ops::<T>();
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    /// `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: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // 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>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::read_time(rtc_dev, rtc_tm, parent_dev) {
+            Ok(()) => 0,
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    /// `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: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The caller ensures that `tm` is valid.
+        // `RtcTime` is `#[repr(transparent)]` over `bindings::rtc_time`, so we
+        // can safely cast.
+        let rtc_tm = unsafe { &*tm.cast::<RtcTime>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::set_time(rtc_dev, rtc_tm, parent_dev) {
+            Ok(()) => 0,
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    /// `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: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // 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>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::read_alarm(rtc_dev, rtc_alarm, parent_dev) {
+            Ok(()) => 0,
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    /// `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: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The caller ensures that `alarm` is valid.
+        // `RtcWkAlrm` is `#[repr(transparent)]` over `bindings::rtc_wkalrm`, so
+        // we can safely cast.
+        let rtc_alarm = unsafe { &*alarm.cast::<RtcWkAlrm>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::set_alarm(rtc_dev, rtc_alarm, parent_dev) {
+            Ok(()) => 0,
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    unsafe extern "C" fn alarm_irq_enable(dev: *mut bindings::device, enabled: c_uint) -> c_int {
+        // SAFETY: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::alarm_irq_enable(rtc_dev, enabled != 0, parent_dev) {
+            Ok(()) => 0,
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    unsafe extern "C" fn ioctl(dev: *mut bindings::device, cmd: c_uint, arg: c_ulong) -> c_int {
+        // SAFETY: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::ioctl(rtc_dev, cmd, arg, parent_dev) {
+            Ok(ret) => ret,
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    /// `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: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The caller ensures that `seq` is valid and writable.
+        let seq_file = unsafe { &mut *seq.cast::<SeqFile>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::proc(rtc_dev, seq_file, parent_dev) {
+            Ok(()) => 0,
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    /// `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: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::read_offset(rtc_dev, parent_dev) {
+            Ok(offset_val) => {
+                // 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 `struct device` embedded in a
+    /// `struct rtc_device`.
+    unsafe extern "C" fn set_offset(dev: *mut bindings::device, offset: c_long) -> c_int {
+        // SAFETY: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::set_offset(rtc_dev, offset as i64, parent_dev) {
+            Ok(()) => 0,
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    /// `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: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // 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>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::param_get(rtc_dev, rtc_param, parent_dev) {
+            Ok(()) => 0,
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    /// `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: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The caller ensures that `param` is valid.
+        // `RtcParam` is `#[repr(transparent)]` over `bindings::rtc_param`,
+        // so we can safely cast.
+        let rtc_param = unsafe { &*param.cast::<RtcParam>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::param_set(rtc_dev, rtc_param, parent_dev) {
+            Ok(()) => 0,
+            Err(err) => err.to_errno(),
+        }
+    }
+}
+
+/// VTable structure wrapper for RTC operations.
+/// Mirrors [`struct rtc_class_ops`](srctree/include/linux/rtc.h).
+#[repr(transparent)]
+pub struct RtcOpsVTable(bindings::rtc_class_ops);
+
+// SAFETY: RtcOpsVTable is Send. The vtable contains only function pointers,
+// which are simple data types that can be safely moved across threads. The
+// thread-safety of calling these functions is handled by the kernel's locking
+// mechanisms.
+unsafe impl Send for RtcOpsVTable {}
+
+// SAFETY: RtcOpsVTable is Sync. The vtable is immutable after it is created,
+// so it can be safely referenced and accessed concurrently by multiple threads
+// e.g. to read the function pointers.
+unsafe impl Sync for RtcOpsVTable {}
+
+impl RtcOpsVTable {
+    /// Returns a raw pointer to the underlying `rtc_class_ops` struct.
+    pub(crate) const fn as_raw(&self) -> *const bindings::rtc_class_ops {
+        &self.0
+    }
+}
+
+/// Creates an RTC operations vtable for a type `T` that implements
+/// `RtcOps`.
+pub const fn create_rtc_ops<T: RtcOps>() -> RtcOpsVTable {
+    let mut ops: bindings::rtc_class_ops = pin_init::zeroed();
+
+    ops.read_time = if T::HAS_READ_TIME {
+        Some(Adapter::<T>::read_time)
+    } else {
+        None
+    };
+    ops.set_time = if T::HAS_SET_TIME {
+        Some(Adapter::<T>::set_time)
+    } else {
+        None
+    };
+    ops.read_alarm = if T::HAS_READ_ALARM {
+        Some(Adapter::<T>::read_alarm)
+    } else {
+        None
+    };
+    ops.set_alarm = if T::HAS_SET_ALARM {
+        Some(Adapter::<T>::set_alarm)
+    } else {
+        None
+    };
+    ops.alarm_irq_enable = if T::HAS_ALARM_IRQ_ENABLE {
+        Some(Adapter::<T>::alarm_irq_enable)
+    } else {
+        None
+    };
+    ops.ioctl = if T::HAS_IOCTL {
+        Some(Adapter::<T>::ioctl)
+    } else {
+        None
+    };
+    ops.proc_ = if T::HAS_PROC {
+        Some(Adapter::<T>::proc)
+    } else {
+        None
+    };
+    ops.read_offset = if T::HAS_READ_OFFSET {
+        Some(Adapter::<T>::read_offset)
+    } else {
+        None
+    };
+    ops.set_offset = if T::HAS_SET_OFFSET {
+        Some(Adapter::<T>::set_offset)
+    } else {
+        None
+    };
+    ops.param_get = if T::HAS_PARAM_GET {
+        Some(Adapter::<T>::param_get)
+    } else {
+        None
+    };
+    ops.param_set = if T::HAS_PARAM_SET {
+        Some(Adapter::<T>::param_set)
+    } else {
+        None
+    };
+
+    RtcOpsVTable(ops)
+}
+
+/// Declares a kernel module that exposes a single RTC driver.
+///
+/// # Examples
+///
+///```ignore
+/// kernel::module_rtc_platform_driver! {
+///     type: MyDriver,
+///     name: "Module name",
+///     authors: ["Author name"],
+///     description: "Description",
+///     license: "GPL v2",
+/// }
+///```
+#[macro_export]
+macro_rules! module_rtc_platform_driver {
+    ($($user_args:tt)*) => {
+        $crate::module_platform_driver! {
+            $($user_args)*
+            imports_ns: ["RTC"],
+        }
+    };
+}
-- 
2.43.0


^ permalink raw reply related

* [RFC PATCH v3 3/5] rust: add device wakeup capability support
From: Ke Sun @ 2026-01-16 16:22 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: <20260116162203.296844-1-sunke@kylinos.cn>

Add Rust bindings and wrappers for device wakeup functionality,
including devm_device_init_wakeup() and dev_pm_set_wake_irq().

Signed-off-by: Ke Sun <sunke@kylinos.cn>
---
 rust/bindings/bindings_helper.h |  2 ++
 rust/helpers/device.c           |  6 ++++++
 rust/kernel/device.rs           | 18 +++++++++++++++++-
 rust/kernel/irq/request.rs      | 17 +++++++++++++++++
 4 files changed, 42 insertions(+), 1 deletion(-)

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..5e31e42e8ff00 100644
--- a/rust/helpers/device.c
+++ b/rust/helpers/device.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 
 #include <linux/device.h>
+#include <linux/pm_wakeup.h>
 
 int rust_helper_devm_add_action(struct device *dev,
 				void (*action)(void *),
@@ -25,3 +26,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 71b200df0f400..b8f7e185e32dd 100644
--- a/rust/kernel/device.rs
+++ b/rust/kernel/device.rs
@@ -5,7 +5,9 @@
 //! C header: [`include/linux/device.h`](srctree/include/linux/device.h)
 
 use crate::{
-    bindings, fmt,
+    bindings,
+    error::to_result,
+    fmt,
     prelude::*,
     sync::aref::ARef,
     types::{ForeignOwnable, Opaque},
@@ -324,6 +326,20 @@ 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. Both the
+    /// wakeup capability and wakeup enable state are automatically
+    /// cleared when the device is removed (resource-managed).
+    ///
+    /// Returns `Ok(())` on success, or an error code on failure.
+    pub fn devm_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()) };
+        to_result(ret)
+    }
 }
 
 impl<Ctx: DeviceContext> Device<Ctx> {
diff --git a/rust/kernel/irq/request.rs b/rust/kernel/irq/request.rs
index b150563fdef80..2484e4b53cdc3 100644
--- a/rust/kernel/irq/request.rs
+++ b/rust/kernel/irq/request.rs
@@ -120,6 +120,23 @@ pub(crate) unsafe fn new(dev: &'a Device<Bound>, irq: u32) -> Self {
     pub fn irq(&self) -> u32 {
         self.irq
     }
+
+    /// Attach the IRQ as a device wake IRQ.
+    ///
+    /// Attaches the device IO interrupt as a wake IRQ. The wake IRQ gets
+    /// automatically configured for wake-up from suspend based on the
+    /// device's sysfs wakeup entry. Typically called during driver probe
+    /// after calling `devm_init_wakeup()`.
+    ///
+    /// The wake IRQ is automatically cleared when the device is removed
+    /// (resource-managed).
+    ///
+    /// Returns `Ok(())` on success, or an error code on failure.
+    pub fn devm_set_wake_irq(&self) -> Result {
+        // SAFETY: `self.as_raw()` is a valid pointer to a `struct device`.
+        let ret = unsafe { bindings::devm_pm_set_wake_irq(self.dev.as_raw(), self.irq as i32) };
+        to_result(ret)
+    }
 }
 
 /// A registration of an IRQ handler for a given IRQ line.
-- 
2.43.0


^ permalink raw reply related

* [RFC PATCH v3 2/5] rust: add AMBA bus driver support
From: Ke Sun @ 2026-01-16 16:22 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: <20260116162203.296844-1-sunke@kylinos.cn>

Add Rust abstractions for ARM AMBA bus drivers, enabling Rust drivers
to interact with AMBA devices through a type-safe interface.

Signed-off-by: Ke Sun <sunke@kylinos.cn>
---
 rust/bindings/bindings_helper.h |   1 +
 rust/kernel/amba.rs             | 441 ++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs              |   2 +
 3 files changed, 444 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..0564595c3655d
--- /dev/null
+++ b/rust/kernel/amba.rs
@@ -0,0 +1,441 @@
+// 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,
+    device,
+    device_id::{
+        RawDeviceId,
+        RawDeviceIdIndex, //
+    },
+    driver,
+    error::{
+        from_result,
+        to_result, //
+    },
+    io::{
+        mem::IoRequest,
+        resource::Resource, //
+    },
+    irq::{
+        self,
+        IrqRequest, //
+    },
+    prelude::*,
+    sync::aref::AlwaysRefCounted,
+    types::Opaque,
+    ThisModule, //
+};
+
+use core::{
+    marker::PhantomData,
+    mem::offset_of,
+    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.
+    #[inline(always)]
+    pub const fn new(id: u32, mask: u32) -> Self {
+        let mut amba: bindings::amba_id = pin_init::zeroed();
+        amba.id = id;
+        amba.mask = mask;
+        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);
+    };
+}
+
+/// The AMBA device representation.
+///
+/// This structure represents the Rust abstraction for a C `struct
+/// amba_device`. The implementation abstracts the usage of an already
+/// existing C `struct amba_device` within Rust code that we get passed
+/// from the C side.
+///
+/// # Invariants
+///
+/// A [`Device`] instance represents a valid `struct amba_device` created
+/// by the C portion of the kernel.
+#[repr(transparent)]
+pub struct Device<Ctx: device::DeviceContext = device::Normal>(
+    Opaque<bindings::amba_device>,
+    PhantomData<Ctx>,
+);
+
+impl<Ctx: device::DeviceContext> Device<Ctx> {
+    fn as_raw(&self) -> *mut bindings::amba_device {
+        self.0.get()
+    }
+
+    /// Returns the memory resource, if any.
+    ///
+    /// AMBA devices have a single memory resource that can be accessed
+    /// via this method.
+    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 };
+        if resource.is_null() {
+            None
+        } else {
+            // SAFETY: `resource` is a valid pointer to a `struct resource`
+            // as returned by the AMBA bus.
+            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 Device<device::Bound> {
+    /// Returns an [`IoRequest`] for the memory resource, if any.
+    ///
+    /// This method provides access to the device's memory-mapped I/O region.
+    pub fn io_request(&self) -> Option<IoRequest<'_>> {
+        self.resource()
+            // SAFETY: `resource` is a valid resource for `&self` during the
+            // lifetime of the `IoRequest`.
+            .map(|resource| unsafe { IoRequest::new(self.as_ref(), resource) })
+    }
+
+    /// Returns an [`IrqRequest`] for the IRQ at the given index, if any.
+    ///
+    /// # Errors
+    ///
+    /// Returns `EINVAL` if `index` is out of bounds, or `ENXIO` if the
+    /// IRQ at that index is not configured.
+    pub fn irq_by_index(&self, index: u32) -> Result<IrqRequest<'_>> {
+        if index >= bindings::AMBA_NR_IRQS {
+            return Err(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(ENXIO);
+        }
+        // SAFETY: `irq` is guaranteed to be a valid IRQ number for
+        // `&self` as it was read from the device structure.
+        Ok(unsafe { IrqRequest::new(self.as_ref(), irq) })
+    }
+
+    /// Returns a [`irq::Registration`] for the IRQ at the given index.
+    ///
+    /// This method registers an interrupt handler for the IRQ at the
+    /// specified index.
+    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,
+    ) -> impl PinInit<irq::Registration<T>, Error> + 'a {
+        pin_init::pin_init_scope(move || {
+            let request = self.irq_by_index(index)?;
+            Ok(irq::Registration::<T>::new(request, flags, name, handler))
+        })
+    }
+}
+
+// SAFETY: Instances of `Device` are always reference-counted.
+unsafe impl AlwaysRefCounted for Device {
+    fn inc_ref(&self) {
+        // SAFETY: The existence of a shared reference guarantees that
+        // the refcount is non-zero.
+        unsafe { bindings::get_device(self.as_ref().as_raw()) };
+    }
+
+    unsafe fn dec_ref(obj: NonNull<Self>) {
+        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 a transparent wrapper of `struct amba_device`.
+// The offset is guaranteed to point to a valid device field inside
+// `Device`.
+unsafe impl<Ctx: device::DeviceContext> device::AsBusDevice<Ctx> for Device<Ctx> {
+    const OFFSET: usize = offset_of!(bindings::amba_device, dev);
+}
+
+// SAFETY: A `Device` is always reference-counted and can be released
+// from any thread.
+unsafe impl Send for Device {}
+
+// SAFETY: `Device` can be shared among threads because all methods of
+// `Device` (i.e., `Device<Normal>`) are thread-safe.
+unsafe impl Sync for Device {}
+
+/// An adapter for the registration of AMBA drivers.
+///
+/// This adapter bridges the gap between the Rust [`Driver`] trait and
+/// the C `struct amba_driver`. It handles the registration and
+/// unregistration of AMBA drivers with the kernel's AMBA bus subsystem.
+pub struct Adapter<T: Driver>(T);
+
+// 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: Driver + 'static> driver::RegistrationOps for Adapter<T> {
+    type RegType = bindings::amba_driver;
+
+    unsafe fn register(
+        adrv: &Opaque<Self::RegType>,
+        name: &'static CStr,
+        module: &'static ThisModule,
+    ) -> Result {
+        let amba_table = T::AMBA_ID_TABLE.as_ptr();
+        // SAFETY: It's safe to set the fields of `struct amba_driver` on
+        // initialization.
+        unsafe {
+            (*adrv.get()).drv.name = name.as_char_ptr();
+            (*adrv.get()).probe = Some(Self::probe_callback);
+            (*adrv.get()).remove = Some(Self::remove_callback);
+            (*adrv.get()).shutdown = Some(Self::shutdown_callback);
+            (*adrv.get()).id_table = amba_table;
+        }
+        // SAFETY: `adrv` is guaranteed to be a valid `RegType`.
+        to_result(unsafe { bindings::__amba_driver_register(adrv.get(), module.0) })
+    }
+
+    unsafe fn unregister(adrv: &Opaque<Self::RegType>) {
+        // SAFETY: `adrv` is guaranteed to be a valid `RegType`.
+        unsafe { bindings::amba_driver_unregister(adrv.get()) };
+    }
+}
+
+impl<T: Driver + 'static> Adapter<T> {
+    extern "C" fn probe_callback(
+        adev: *mut bindings::amba_device,
+        id: *const bindings::amba_id,
+    ) -> kernel::ffi::c_int {
+        // SAFETY: The AMBA bus only ever calls the probe callback with a
+        // valid pointer to a `struct amba_device`.
+        //
+        // INVARIANT: `adev` is valid for the duration of `probe_callback()`.
+        let dev = unsafe { &*adev.cast::<Device<device::CoreInternal>>() };
+        let info = Self::amba_id_info(dev, id);
+        from_result(|| {
+            let datapin = T::probe(dev, info);
+            dev.as_ref().set_drvdata(datapin)?;
+            Ok(0)
+        })
+    }
+
+    extern "C" fn remove_callback(adev: *mut bindings::amba_device) {
+        // SAFETY: The AMBA bus only ever calls the remove callback with a
+        // valid pointer to a `struct amba_device`.
+        //
+        // INVARIANT: `adev` is valid for the duration of `remove_callback()`.
+        let dev = unsafe { &*adev.cast::<Device<device::CoreInternal>>() };
+        // SAFETY: `remove_callback` is only ever called after a successful
+        // call to `probe_callback`, hence it's guaranteed that
+        // `Device::set_drvdata()` has been called and stored a
+        // `Pin<KBox<T>>`.
+        let data = unsafe { dev.as_ref().drvdata_obtain::<T>() };
+        T::unbind(dev, data.as_ref());
+    }
+
+    extern "C" fn shutdown_callback(adev: *mut bindings::amba_device) {
+        // SAFETY: `shutdown_callback` is only ever called for a valid `adev`.
+        //
+        // INVARIANT: `adev` is valid for the duration of `shutdown_callback()`.
+        let dev = unsafe { &*adev.cast::<Device<device::CoreInternal>>() };
+        // SAFETY: `shutdown_callback` is only ever called after a
+        // successful call to `probe_callback`, hence it's guaranteed that
+        // `Device::set_drvdata()` has been called and stored a
+        // `Pin<KBox<T>>`.
+        let data = unsafe { dev.as_ref().drvdata_borrow::<T>() };
+        T::shutdown(dev, data.as_ref());
+    }
+
+    /// Extracts the device ID information from a matched AMBA device ID.
+    ///
+    /// This function looks up the driver-specific information associated
+    /// with the matched `struct amba_id` in the driver's device ID table.
+    fn amba_id_info(_dev: &Device, id: *const bindings::amba_id) -> Option<&'static T::IdInfo> {
+        if id.is_null() {
+            return None;
+        }
+        let table = T::AMBA_ID_TABLE;
+        // SAFETY: `id` is a valid pointer to a `struct amba_id` that was
+        // matched by the kernel. `DeviceId` is a `#[repr(transparent)]`
+        // wrapper of `struct amba_id` and does not add additional
+        // invariants, so it's safe to transmute.
+        let device_id = unsafe { &*id.cast::<DeviceId>() };
+        Some(table.info(<DeviceId as RawDeviceIdIndex>::index(device_id)))
+    }
+}
+
+/// The AMBA driver trait.
+///
+/// Drivers must implement this trait in order to get an AMBA driver registered.
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::{amba, bindings, c_str, device::Core};
+///
+/// struct MyDriver;
+///
+/// kernel::amba_device_table!(
+///     AMBA_TABLE,
+///     MODULE_AMBA_TABLE,
+///     <MyDriver as amba::Driver>::IdInfo,
+///     [
+///         (amba::DeviceId::new(0x00041031, 0x000fffff), ())
+///     ]
+/// );
+///
+/// impl amba::Driver for MyDriver {
+///     type IdInfo = ();
+///     const AMBA_ID_TABLE: amba::IdTable<Self::IdInfo> = &AMBA_TABLE;
+///
+///     fn probe(
+///         _adev: &amba::Device<Core>,
+///         _id_info: Option<&Self::IdInfo>,
+///     ) -> impl PinInit<Self, Error> {
+///         Err(ENODEV)
+///     }
+/// }
+/// ```
+pub trait Driver: Send {
+    /// The type holding driver private data about each device id
+    /// supported by the driver.
+    type IdInfo: 'static;
+    /// The table of device ids supported by the driver.
+    const AMBA_ID_TABLE: IdTable<Self::IdInfo>;
+
+    /// AMBA driver probe.
+    ///
+    /// Called when a new AMBA device is added or discovered.
+    /// Implementers should attempt to initialize the device here.
+    fn probe(
+        dev: &Device<device::Core>,
+        id_info: Option<&Self::IdInfo>,
+    ) -> impl PinInit<Self, Error>;
+
+    /// AMBA driver shutdown.
+    ///
+    /// Called by the kernel during system reboot or power-off to allow
+    /// the [`Driver`] to bring the [`Device`] into a safe state.
+    /// Implementing this callback is optional.
+    ///
+    /// Typical actions include stopping transfers, disabling interrupts,
+    /// or resetting the hardware to prevent undesired behavior during
+    /// shutdown.
+    ///
+    /// This callback is distinct from final resource cleanup, as the
+    /// driver instance remains valid after it returns. Any deallocation
+    /// or teardown of driver-owned resources should instead be handled in
+    /// `Self::drop`.
+    fn shutdown(dev: &Device<device::Core>, this: Pin<&Self>) {
+        let _ = (dev, this);
+    }
+
+    /// AMBA driver unbind.
+    ///
+    /// Called when the [`Device`] is unbound from its bound [`Driver`].
+    /// Implementing this callback is optional.
+    ///
+    /// This callback serves as a place for drivers to perform teardown
+    /// operations that require a `&Device<Core>` or `&Device<Bound>`
+    /// reference. For instance, drivers may try to perform I/O operations
+    /// to gracefully tear down the device.
+    ///
+    /// Otherwise, release operations for driver resources should be
+    /// performed in `Self::drop`.
+    fn unbind(dev: &Device<device::Core>, this: Pin<&Self>) {
+        let _ = (dev, this);
+    }
+}
+
+/// Declares a kernel module that exposes a single AMBA driver.
+///
+/// # Examples
+///
+/// ```ignore
+/// kernel::module_amba_driver! {
+///     type: MyDriver,
+///     name: "Module name",
+///     authors: ["Author name"],
+///     description: "Description",
+///     license: "GPL v2",
+/// }
+/// ```
+#[macro_export]
+macro_rules! module_amba_driver {
+    ($($f:tt)*) => {
+        $crate::module_driver!(<T>, $crate::amba::Adapter<T>, { $($f)* });
+    };
+}
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 v3 1/5] rtc: add device selector for rtc_class_ops callbacks
From: Ke Sun @ 2026-01-16 16:21 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: <20260116162203.296844-1-sunke@kylinos.cn>

Add rtc_ops_dev() helper to allow drivers to choose whether
rtc_class_ops callbacks receive rtc->dev or rtc->dev.parent. This
enables Rust RTC drivers to store driver data on the RTC device.

The helper checks RTC_OPS_USE_RTC_DEV flag: if set, returns &rtc->dev;
otherwise returns rtc->dev.parent.

Update all rtc_class_ops callback invocations to use rtc_ops_dev(rtc)
instead of directly accessing rtc->dev.parent.

Maintains backward compatibility for existing C drivers.

Signed-off-by: Ke Sun <sunke@kylinos.cn>
---
 drivers/rtc/dev.c       |  6 +++---
 drivers/rtc/interface.c | 18 +++++++++---------
 drivers/rtc/proc.c      |  2 +-
 include/linux/rtc.h     | 15 +++++++++++++++
 4 files changed, 28 insertions(+), 13 deletions(-)

diff --git a/drivers/rtc/dev.c b/drivers/rtc/dev.c
index baf1a8ca8b2b1..eddcc5a69db3b 100644
--- a/drivers/rtc/dev.c
+++ b/drivers/rtc/dev.c
@@ -410,7 +410,7 @@ static long rtc_dev_ioctl(struct file *file,
 		}
 		default:
 			if (rtc->ops->param_get)
-				err = rtc->ops->param_get(rtc->dev.parent, &param);
+				err = rtc->ops->param_get(rtc_ops_dev(rtc), &param);
 			else
 				err = -EINVAL;
 		}
@@ -440,7 +440,7 @@ static long rtc_dev_ioctl(struct file *file,
 
 		default:
 			if (rtc->ops->param_set)
-				err = rtc->ops->param_set(rtc->dev.parent, &param);
+				err = rtc->ops->param_set(rtc_ops_dev(rtc), &param);
 			else
 				err = -EINVAL;
 		}
@@ -450,7 +450,7 @@ static long rtc_dev_ioctl(struct file *file,
 	default:
 		/* Finally try the driver's ioctl interface */
 		if (ops->ioctl) {
-			err = ops->ioctl(rtc->dev.parent, cmd, arg);
+			err = ops->ioctl(rtc_ops_dev(rtc), cmd, arg);
 			if (err == -ENOIOCTLCMD)
 				err = -ENOTTY;
 		} else {
diff --git a/drivers/rtc/interface.c b/drivers/rtc/interface.c
index b8b298efd9a9c..4c81130fb0394 100644
--- a/drivers/rtc/interface.c
+++ b/drivers/rtc/interface.c
@@ -91,7 +91,7 @@ static int __rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
 		err = -EINVAL;
 	} else {
 		memset(tm, 0, sizeof(struct rtc_time));
-		err = rtc->ops->read_time(rtc->dev.parent, tm);
+		err = rtc->ops->read_time(rtc_ops_dev(rtc), tm);
 		if (err < 0) {
 			dev_dbg(&rtc->dev, "read_time: fail to read: %d\n",
 				err);
@@ -155,7 +155,7 @@ int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm)
 	if (!rtc->ops)
 		err = -ENODEV;
 	else if (rtc->ops->set_time)
-		err = rtc->ops->set_time(rtc->dev.parent, tm);
+		err = rtc->ops->set_time(rtc_ops_dev(rtc), tm);
 	else
 		err = -EINVAL;
 
@@ -200,7 +200,7 @@ static int rtc_read_alarm_internal(struct rtc_device *rtc,
 		alarm->time.tm_wday = -1;
 		alarm->time.tm_yday = -1;
 		alarm->time.tm_isdst = -1;
-		err = rtc->ops->read_alarm(rtc->dev.parent, alarm);
+		err = rtc->ops->read_alarm(rtc_ops_dev(rtc), alarm);
 	}
 
 	mutex_unlock(&rtc->ops_lock);
@@ -441,7 +441,7 @@ static int __rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
 	else if (!test_bit(RTC_FEATURE_ALARM, rtc->features))
 		err = -EINVAL;
 	else
-		err = rtc->ops->set_alarm(rtc->dev.parent, alarm);
+		err = rtc->ops->set_alarm(rtc_ops_dev(rtc), alarm);
 
 	/*
 	 * Check for potential race described above. If the waiting for next
@@ -568,7 +568,7 @@ int rtc_alarm_irq_enable(struct rtc_device *rtc, unsigned int enabled)
 	else if (!test_bit(RTC_FEATURE_ALARM, rtc->features) || !rtc->ops->alarm_irq_enable)
 		err = -EINVAL;
 	else
-		err = rtc->ops->alarm_irq_enable(rtc->dev.parent, enabled);
+		err = rtc->ops->alarm_irq_enable(rtc_ops_dev(rtc), enabled);
 
 	mutex_unlock(&rtc->ops_lock);
 
@@ -618,7 +618,7 @@ int rtc_update_irq_enable(struct rtc_device *rtc, unsigned int enabled)
 		rtc->uie_rtctimer.period = ktime_set(1, 0);
 		err = rtc_timer_enqueue(rtc, &rtc->uie_rtctimer);
 		if (!err && rtc->ops && rtc->ops->alarm_irq_enable)
-			err = rtc->ops->alarm_irq_enable(rtc->dev.parent, 1);
+			err = rtc->ops->alarm_irq_enable(rtc_ops_dev(rtc), 1);
 		if (err)
 			goto out;
 	} else {
@@ -874,7 +874,7 @@ static void rtc_alarm_disable(struct rtc_device *rtc)
 	if (!rtc->ops || !test_bit(RTC_FEATURE_ALARM, rtc->features) || !rtc->ops->alarm_irq_enable)
 		return;
 
-	rtc->ops->alarm_irq_enable(rtc->dev.parent, false);
+	rtc->ops->alarm_irq_enable(rtc_ops_dev(rtc), false);
 	trace_rtc_alarm_irq_enable(0, 0);
 }
 
@@ -1076,7 +1076,7 @@ int rtc_read_offset(struct rtc_device *rtc, long *offset)
 		return -EINVAL;
 
 	mutex_lock(&rtc->ops_lock);
-	ret = rtc->ops->read_offset(rtc->dev.parent, offset);
+	ret = rtc->ops->read_offset(rtc_ops_dev(rtc), offset);
 	mutex_unlock(&rtc->ops_lock);
 
 	trace_rtc_read_offset(*offset, ret);
@@ -1111,7 +1111,7 @@ int rtc_set_offset(struct rtc_device *rtc, long offset)
 		return -EINVAL;
 
 	mutex_lock(&rtc->ops_lock);
-	ret = rtc->ops->set_offset(rtc->dev.parent, offset);
+	ret = rtc->ops->set_offset(rtc_ops_dev(rtc), offset);
 	mutex_unlock(&rtc->ops_lock);
 
 	trace_rtc_set_offset(offset, ret);
diff --git a/drivers/rtc/proc.c b/drivers/rtc/proc.c
index cbcdbb19d848e..bf688079d0fbb 100644
--- a/drivers/rtc/proc.c
+++ b/drivers/rtc/proc.c
@@ -73,7 +73,7 @@ static int rtc_proc_show(struct seq_file *seq, void *offset)
 	seq_printf(seq, "24hr\t\t: yes\n");
 
 	if (ops->proc)
-		ops->proc(rtc->dev.parent, seq);
+		ops->proc(rtc_ops_dev(rtc), seq);
 
 	return 0;
 }
diff --git a/include/linux/rtc.h b/include/linux/rtc.h
index 95da051fb155d..1dd4a45d0186e 100644
--- a/include/linux/rtc.h
+++ b/include/linux/rtc.h
@@ -83,6 +83,7 @@ struct rtc_timer {
 /* flags */
 #define RTC_DEV_BUSY 0
 #define RTC_NO_CDEV  1
+#define RTC_OPS_USE_RTC_DEV 2
 
 struct rtc_device {
 	struct device dev;
@@ -167,6 +168,20 @@ struct rtc_device {
 #define rtc_lock(d) mutex_lock(&d->ops_lock)
 #define rtc_unlock(d) mutex_unlock(&d->ops_lock)
 
+/**
+ * rtc_ops_dev - Get the device pointer for RTC ops callbacks
+ * @rtc: RTC device
+ *
+ * Returns &rtc->dev if RTC_OPS_USE_RTC_DEV flag is set,
+ * otherwise returns rtc->dev.parent.
+ */
+static inline struct device *rtc_ops_dev(struct rtc_device *rtc)
+{
+	if (test_bit(RTC_OPS_USE_RTC_DEV, &rtc->flags))
+		return &rtc->dev;
+	return rtc->dev.parent;
+}
+
 /* useful timestamps */
 #define RTC_TIMESTAMP_BEGIN_0000	-62167219200ULL /* 0000-01-01 00:00:00 */
 #define RTC_TIMESTAMP_BEGIN_1900	-2208988800LL /* 1900-01-01 00:00:00 */
-- 
2.43.0


^ permalink raw reply related

* [RFC PATCH v3 0/5] rust: Add RTC driver support
From: Ke Sun @ 2026-01-16 16:21 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 PL031 RTC.

---
v3:
- Add rtc_ops_dev() helper with RTC_OPS_USE_RTC_DEV flag to allow drivers to
  choose device pointer for rtc_class_ops callbacks. This enables Rust RTC
  drivers to store drvdata on rtc->dev while maintaining backward compatibility
  for existing C drivers without requiring driver modifications
- Refactor AMBA and RTC abstractions to address v2 review feedback

v2: https://lore.kernel.org/rust-for-linux/20260107143738.3021892-1-sunke@kylinos.cn/
- Migrate RTC driver data storage from parent device to RTC device for
  unified interface
- Expand AMBA bus abstractions to full driver support with enhanced
  functionality
- Refactor device wakeup API by moving wake IRQ setup to IRQ layer
- Simplify RTC core framework by removing multi-bus abstractions,
  focusing on core operations
- Optimize PL031 driver implementation and remove build assertion
  dependency

v1: https://lore.kernel.org/rust-for-linux/20260104060621.3757812-1-sunke@kylinos.cn/
- Add AMBA bus abstractions
- Add device wakeup support
- Add RTC core framework with multi-bus support
- Add PL031 RTC driver

---

Ke Sun (5):
  rtc: add device selector for rtc_class_ops callbacks
  rust: add AMBA bus driver support
  rust: add device wakeup capability support
  rust: add RTC core abstractions and data structures
  rust: add PL031 RTC driver

 drivers/rtc/Kconfig             |    9 +
 drivers/rtc/Makefile            |    1 +
 drivers/rtc/dev.c               |    6 +-
 drivers/rtc/interface.c         |   18 +-
 drivers/rtc/proc.c              |    2 +-
 drivers/rtc/rtc_pl031_rust.rs   |  513 ++++++++++++++++
 include/linux/rtc.h             |   15 +
 rust/bindings/bindings_helper.h |    3 +
 rust/helpers/device.c           |    6 +
 rust/helpers/helpers.c          |    1 +
 rust/helpers/rtc.c              |    9 +
 rust/kernel/amba.rs             |  441 ++++++++++++++
 rust/kernel/device.rs           |   18 +-
 rust/kernel/irq/request.rs      |   17 +
 rust/kernel/lib.rs              |    4 +
 rust/kernel/rtc.rs              | 1008 +++++++++++++++++++++++++++++++
 16 files changed, 2057 insertions(+), 14 deletions(-)
 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: 944aacb68baf7624ab8d277d0ebf07f025ca137c
-- 
2.43.0


^ permalink raw reply

* Re: [PATCH v2 2/3] dt-binding: rtc: loongson: Document Loongson-2K0300 compatible
From: Binbin Zhou @ 2026-01-16  1:14 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: <CAL_JsqJVD3o41Zch6fMY6s-qmyd9cQg6CJ+iya+3kdtuqvNMoA@mail.gmail.com>

Hi Rob:

Thanks for your reply.

On Fri, Jan 16, 2026 at 1:00 AM Rob Herring <robh@kernel.org> wrote:
>
> On Thu, Jan 15, 2026 at 1:39 AM Binbin Zhou <zhoubb.aaron@gmail.com> wrote:
> >
> > Hi Rob:
> >
> > Thanks for your reply.
> >
> > On Thu, Jan 15, 2026 at 4:58 AM Rob Herring <robh@kernel.org> wrote:
> > >
> > > On Wed, Jan 07, 2026 at 09:22:41AM +0800, Binbin Zhou wrote:
> > > > Hi Rob:
> > > >
> > > > Thanks for your review.
> > > >
> > > > On Wed, Jan 7, 2026 at 3:13 AM Rob Herring <robh@kernel.org> wrote:
> > > > >
> > > > > On Tue, Jan 06, 2026 at 09:33:32AM +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.
> > > > >
> > > > > But you are requiring the interrupt property for it? Isn't it no alarm
> > > > > feature means no interrupt?
> > > >
> > > > Yes, the `interrupts` attribute is not required without the alarm feature.
> > > >
> > > > But my judgment condition is `not contains` (added in patch-1[1]).
> > > > There are only a few SoCs on the Loongson platform that don't support
> > > > the RTC alarm feature, so I think `not contains` looks cleaner and
> > > > simpler.
> > >
> > > I should have said allowing rather than requiring.
> > >
> > > You are allowing (though not requiring) 'interrupts' for Loongson-1B and
> > > Loongson-2K0300. In patch 1, you made it required for other platforms
> > > which is an ABI change. That's fine if it was a mistake and is truly
> > > required.
> >
> > Emm, it's true that for the binding interface, Patch-1 is indeed an
> > ABI change, but it's more of a fixed patch.
> >
> > Throughout all existing Loongson DTS{i}, RTC nodes decide whether to
> > include the `interrupts` property or not based on the alarm feature.
> > Loongson-1c rtc nodes do not include the `interrupts` attribute [1],
> > while all other Loongson chip rtc nodes do [2].
> >
> > So, while this is an ABI change, I don't think it affects existing
> > Loongson DTS{i} rtc nodes. Also, it more accurately describes the
> > features of the corresponding RTC device.
> >
> > Therefore, I would like to clarify it in the Patch-1 commit message of
> > the next patch version and fix the error in the commit title:
> > dt-binding -> dt-bindings.
> >
> > How do you feel about that?
>
> That's fine, but you also need:
>
> else:
>   properties:
>     interrupts: false
>
> So that on the 2 platforms without an interrupt(alarm), 'interrupts'
> is not allowed.
>
> With that, you might as well just drop the 'not' and flip the 'then'
> and 'else' schemas around.

OK, I'll fall back to the writeup in the v1 patchset as follows:

if:
  properties:
    compatible:
      contains:
        enum:
          - loongson,ls1c-rtc
          - loongson,ls2k0300-rtc

then:
  properties:
    interrupts: false

>
> Rob

-- 
Thanks.
Binbin

^ permalink raw reply

* Re: [PATCH 00/27] clk: remove deprecated API divider_round_rate() and friends
From: Dmitry Baryshkov @ 2026-01-15 21:05 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Brian Masney
  Cc: linux-clk, linux-kernel, Chen Wang, Inochi Amaoto, sophgo,
	Chen-Yu Tsai, Maxime Ripard, Jernej Skrabec, Samuel Holland,
	linux-arm-kernel, linux-sunxi, Alexandre Belloni, linux-rtc,
	Andreas Färber, Manivannan Sadhasivam, linux-actions,
	Keguang Zhang, linux-mips, Taichi Sugaya, Takao Orito,
	Jacky Huang, Shan-Chun Hung, Vladimir Zapolskiy,
	Piotr Wojtaszczyk, Bjorn Andersson, linux-arm-msm, Orson Zhai,
	Baolin Wang, Chunyan Zhang, Maxime Coquelin, Alexandre Torgue,
	linux-stm32, Michal Simek, Rob Clark, Dmitry Baryshkov,
	David Airlie, Simona Vetter, Abhinav Kumar, Jessica Zhang,
	Sean Paul, Marijn Suijten, dri-devel, freedreno, Vinod Koul,
	Neil Armstrong, linux-phy
In-Reply-To: <20260108-clk-divider-round-rate-v1-0-535a3ed73bf3@redhat.com>

On Thu, 08 Jan 2026 16:16:18 -0500, Brian Masney wrote:
> Here's a series that gets rid of the deprecated APIs
> divider_round_rate(), divider_round_rate_parent(), and
> divider_ro_round_rate_parent() since these functions are just wrappers
> for the determine_rate variant.
> 
> Note that when I converted some of these drivers from round_rate to
> determine_rate, this was mistakenly converted to the following in some
> cases:
> 
> [...]

Applied to msm-next, thanks!

[24/27] drm/msm/dsi_phy_14nm: convert from divider_round_rate() to divider_determine_rate()
        https://gitlab.freedesktop.org/lumag/msm/-/commit/1d232f793d4d

Best regards,
-- 
With best wishes
Dmitry



^ permalink raw reply

* Re: [PATCH v2 2/3] dt-binding: rtc: loongson: Document Loongson-2K0300 compatible
From: Rob Herring @ 2026-01-15 17:00 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: <CAMpQs4+v=KhLW_Cru801p06vmpjJFotvyDfKBALg6mbwxFU5-A@mail.gmail.com>

On Thu, Jan 15, 2026 at 1:39 AM Binbin Zhou <zhoubb.aaron@gmail.com> wrote:
>
> Hi Rob:
>
> Thanks for your reply.
>
> On Thu, Jan 15, 2026 at 4:58 AM Rob Herring <robh@kernel.org> wrote:
> >
> > On Wed, Jan 07, 2026 at 09:22:41AM +0800, Binbin Zhou wrote:
> > > Hi Rob:
> > >
> > > Thanks for your review.
> > >
> > > On Wed, Jan 7, 2026 at 3:13 AM Rob Herring <robh@kernel.org> wrote:
> > > >
> > > > On Tue, Jan 06, 2026 at 09:33:32AM +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.
> > > >
> > > > But you are requiring the interrupt property for it? Isn't it no alarm
> > > > feature means no interrupt?
> > >
> > > Yes, the `interrupts` attribute is not required without the alarm feature.
> > >
> > > But my judgment condition is `not contains` (added in patch-1[1]).
> > > There are only a few SoCs on the Loongson platform that don't support
> > > the RTC alarm feature, so I think `not contains` looks cleaner and
> > > simpler.
> >
> > I should have said allowing rather than requiring.
> >
> > You are allowing (though not requiring) 'interrupts' for Loongson-1B and
> > Loongson-2K0300. In patch 1, you made it required for other platforms
> > which is an ABI change. That's fine if it was a mistake and is truly
> > required.
>
> Emm, it's true that for the binding interface, Patch-1 is indeed an
> ABI change, but it's more of a fixed patch.
>
> Throughout all existing Loongson DTS{i}, RTC nodes decide whether to
> include the `interrupts` property or not based on the alarm feature.
> Loongson-1c rtc nodes do not include the `interrupts` attribute [1],
> while all other Loongson chip rtc nodes do [2].
>
> So, while this is an ABI change, I don't think it affects existing
> Loongson DTS{i} rtc nodes. Also, it more accurately describes the
> features of the corresponding RTC device.
>
> Therefore, I would like to clarify it in the Patch-1 commit message of
> the next patch version and fix the error in the commit title:
> dt-binding -> dt-bindings.
>
> How do you feel about that?

That's fine, but you also need:

else:
  properties:
    interrupts: false

So that on the 2 platforms without an interrupt(alarm), 'interrupts'
is not allowed.

With that, you might as well just drop the 'not' and flip the 'then'
and 'else' schemas around.

Rob

^ permalink raw reply

* [GIT PULL] Immutable branch between MFD, Clk, GPIO, Power, Regulator and RTC due for the v6.20 merge window
From: Lee Jones @ 2026-01-15 13:49 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Matti Vaittinen, Pavel Machek, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Sebastian Reichel, Liam Girdwood, Mark Brown,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Alexandre Belloni, linux-leds, devicetree,
	linux-kernel, linux-pm, linux-clk, linux-gpio, linux-rtc,
	Andreas Kemnade
In-Reply-To: <cover.1765804226.git.mazziesaccount@gmail.com>

Enjoy!

The following changes since commit 8f0b4cce4481fb22653697cced8d0d04027cb1e8:

  Linux 6.19-rc1 (2025-12-14 16:05:07 +1200)

are available in the Git repository at:

  git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git ib-mfd-clk-gpio-power-regulator-rtc-v6.20

for you to fetch changes up to e39951f8ad500648b9ab132f8042d6e47da441cf:

  MAINTAINERS: Add ROHM BD72720 PMIC (2026-01-13 12:50:37 +0000)

----------------------------------------------------------------
Immutable branch between MFD, Clk, GPIO, Power, Regulator and RTC due for the v6.20 merge window

----------------------------------------------------------------
Matti Vaittinen (17):
      dt-bindings: regulator: ROHM BD72720
      dt-bindings: battery: Clarify trickle-charge
      dt-bindings: battery: Add trickle-charge upper limit
      dt-bindings: battery: Voltage drop properties
      dt-bindings: mfd: ROHM BD72720
      dt-bindings: leds: bd72720: Add BD72720
      mfd: rohm-bd71828: Use regmap_reg_range()
      mfd: rohm-bd71828: Use standard file header format
      mfd: rohm-bd71828: Support ROHM BD72720
      regulator: bd71828: rename IC specific entities
      regulator: bd71828: Support ROHM BD72720
      gpio: Support ROHM BD72720 gpios
      clk: clk-bd718x7: Support BD72720 clk gate
      rtc: bd70528: Support BD72720 rtc
      power: supply: bd71828: Support wider register addresses
      power: supply: bd71828-power: Support ROHM BD72720
      MAINTAINERS: Add ROHM BD72720 PMIC

 .../bindings/leds/rohm,bd71828-leds.yaml           |    7 +-
 .../devicetree/bindings/mfd/rohm,bd72720-pmic.yaml |  339 +++++++
 .../devicetree/bindings/power/supply/battery.yaml  |   33 +-
 .../bindings/regulator/rohm,bd72720-regulator.yaml |  148 +++
 MAINTAINERS                                        |    2 +
 drivers/clk/Kconfig                                |    4 +-
 drivers/clk/clk-bd718x7.c                          |   10 +-
 drivers/gpio/Kconfig                               |    9 +
 drivers/gpio/Makefile                              |    1 +
 drivers/gpio/gpio-bd72720.c                        |  281 ++++++
 drivers/mfd/Kconfig                                |   18 +-
 drivers/mfd/rohm-bd71828.c                         |  555 ++++++++++-
 drivers/power/supply/bd71828-power.c               |  160 ++-
 drivers/regulator/Kconfig                          |    8 +-
 drivers/regulator/bd71828-regulator.c              | 1025 +++++++++++++++++++-
 drivers/rtc/Kconfig                                |    3 +-
 drivers/rtc/rtc-bd70528.c                          |   21 +-
 include/linux/mfd/rohm-bd72720.h                   |  634 ++++++++++++
 include/linux/mfd/rohm-generic.h                   |    1 +
 19 files changed, 3127 insertions(+), 132 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mfd/rohm,bd72720-pmic.yaml
 create mode 100644 Documentation/devicetree/bindings/regulator/rohm,bd72720-regulator.yaml
 create mode 100644 drivers/gpio/gpio-bd72720.c
 create mode 100644 include/linux/mfd/rohm-bd72720.h

-- 
Lee Jones [李琼斯]

^ permalink raw reply

* Re: [PATCH v2 3/5] rtc: zynqmp: rework read_offset
From: Tomas Melin @ 2026-01-15  7:41 UTC (permalink / raw)
  To: kernel test robot, Alexandre Belloni, Michal Simek
  Cc: oe-kbuild-all, linux-rtc, linux-arm-kernel, linux-kernel
In-Reply-To: <202601150836.Yk8DcSZW-lkp@intel.com>

Hi,

On 15/01/2026 02:42, kernel test robot wrote:

> 
>    arm-linux-gnueabi-ld: drivers/spi/spi-amlogic-spifc-a4.o: in function `aml_sfc_set_bus_width':
>    spi-amlogic-spifc-a4.c:(.text.aml_sfc_set_bus_width+0x8c): undefined reference to `__ffsdi2'
>    arm-linux-gnueabi-ld: spi-amlogic-spifc-a4.c:(.text.aml_sfc_set_bus_width+0xac): undefined reference to `__ffsdi2'
>    arm-linux-gnueabi-ld: spi-amlogic-spifc-a4.c:(.text.aml_sfc_set_bus_width+0xcc): undefined reference to `__ffsdi2'
>    arm-linux-gnueabi-ld: drivers/rtc/rtc-zynqmp.o: in function `xlnx_rtc_read_offset':
>>> rtc-zynqmp.c:(.text.xlnx_rtc_read_offset+0xd0): undefined reference to `__aeabi_ldivmod'
>>> arm-linux-gnueabi-ld: rtc-zynqmp.c:(.text.xlnx_rtc_read_offset+0x15c): undefined reference to `__aeabi_ldivmod'
AFAIU this is related to compiling for arm 32 bit target. Is this error
relevant since this driver is for aarch64 zynqmp specifically? If so,
what would be correct way of fixing?

Thanks,
Tomas



> 


^ permalink raw reply

* Re: [PATCH v2 2/3] dt-binding: rtc: loongson: Document Loongson-2K0300 compatible
From: Binbin Zhou @ 2026-01-15  7:38 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: <20260114205855.GA3190839-robh@kernel.org>

Hi Rob:

Thanks for your reply.

On Thu, Jan 15, 2026 at 4:58 AM Rob Herring <robh@kernel.org> wrote:
>
> On Wed, Jan 07, 2026 at 09:22:41AM +0800, Binbin Zhou wrote:
> > Hi Rob:
> >
> > Thanks for your review.
> >
> > On Wed, Jan 7, 2026 at 3:13 AM Rob Herring <robh@kernel.org> wrote:
> > >
> > > On Tue, Jan 06, 2026 at 09:33:32AM +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.
> > >
> > > But you are requiring the interrupt property for it? Isn't it no alarm
> > > feature means no interrupt?
> >
> > Yes, the `interrupts` attribute is not required without the alarm feature.
> >
> > But my judgment condition is `not contains` (added in patch-1[1]).
> > There are only a few SoCs on the Loongson platform that don't support
> > the RTC alarm feature, so I think `not contains` looks cleaner and
> > simpler.
>
> I should have said allowing rather than requiring.
>
> You are allowing (though not requiring) 'interrupts' for Loongson-1B and
> Loongson-2K0300. In patch 1, you made it required for other platforms
> which is an ABI change. That's fine if it was a mistake and is truly
> required.

Emm, it's true that for the binding interface, Patch-1 is indeed an
ABI change, but it's more of a fixed patch.

Throughout all existing Loongson DTS{i}, RTC nodes decide whether to
include the `interrupts` property or not based on the alarm feature.
Loongson-1c rtc nodes do not include the `interrupts` attribute [1],
while all other Loongson chip rtc nodes do [2].

So, while this is an ABI change, I don't think it affects existing
Loongson DTS{i} rtc nodes. Also, it more accurately describes the
features of the corresponding RTC device.

Therefore, I would like to clarify it in the Patch-1 commit message of
the next patch version and fix the error in the commit title:
dt-binding -> dt-bindings.

How do you feel about that?

[1]: https://elixir.bootlin.com/linux/v6.18/source/arch/mips/boot/dts/loongson/loongson1c.dtsi#L98
[2]: https://elixir.bootlin.com/linux/v6.18/source/arch/loongarch/boot/dts/loongson-2k0500.dtsi#L486

>
> Rob

-- 
Thanks.
Binbin

^ permalink raw reply

* Re: [PATCH v2 4/5] rtc: zynqmp: rework set_offset
From: Tomas Melin @ 2026-01-15  7:24 UTC (permalink / raw)
  To: T, Harini, Alexandre Belloni, Simek, Michal
  Cc: linux-rtc@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org
In-Reply-To: <LV5PR12MB980481A1DC737957538A9924928FA@LV5PR12MB9804.namprd12.prod.outlook.com>

Hi,


On 14/01/2026 17:37, T, Harini wrote:
>> +++ b/drivers/rtc/rtc-zynqmp.c
>> @@ -208,13 +208,13 @@ static int xlnx_rtc_read_offset(struct device *dev,
>> long *offset)  static int xlnx_rtc_set_offset(struct device *dev, long offset)  {
>>         struct xlnx_rtc_dev *xrtcdev = dev_get_drvdata(dev);
>> -       unsigned long long rtc_ppb = RTC_PPB;
>> -       unsigned int tick_mult = do_div(rtc_ppb, xrtcdev->freq);
>> -       unsigned char fract_tick = 0;
>> +       int max_tick, tick_mult, fract_offset, fract_part;
>>         unsigned int calibval;
>> -       short int  max_tick;
>> -       int fract_offset;
>> +       int fract_data = 0;
>> +       int freq = xrtcdev->freq;
> Please follow reverse XMAS tree style.
I can fix this with a follow up version.

Thanks,
Tomas



>>
>> +       /* Tick to offset multiplier */
>> +       tick_mult = DIV_ROUND_CLOSEST(RTC_PPB, xrtcdev->freq);
>>         if (offset < RTC_MIN_OFFSET || offset > RTC_MAX_OFFSET)
>>                 return -ERANGE;
>>
>> @@ -223,29 +223,22 @@ static int xlnx_rtc_set_offset(struct device *dev,
>> long offset)
>>
>>         /* Number fractional ticks for given offset */
>>         if (fract_offset) {
>> -               if (fract_offset < 0) {
>> -                       fract_offset = fract_offset + tick_mult;
>> +               fract_part = DIV_ROUND_UP(tick_mult, RTC_FR_MAX_TICKS);
>> +               fract_data = fract_offset / fract_part;
>> +               /* Subtract one from max_tick while adding fract_offset */
>> +               if (fract_offset < 0 && fract_data) {
>>                         max_tick--;
>> -               }
>> -               if (fract_offset > (tick_mult / RTC_FR_MAX_TICKS)) {
>> -                       for (fract_tick = 1; fract_tick < 16; fract_tick++) {
>> -                               if (fract_offset <=
>> -                                   (fract_tick *
>> -                                    (tick_mult / RTC_FR_MAX_TICKS)))
>> -                                       break;
>> -                       }
>> +                       fract_data += RTC_FR_MAX_TICKS;
>>                 }
>>         }
>>
>>         /* Zynqmp RTC uses second and fractional tick
>>          * counters for compensation
>>          */
>> -       calibval = max_tick + RTC_CALIB_DEF;
>> -
>> -       if (fract_tick)
>> -               calibval |= RTC_FR_EN;
>> +       calibval = max_tick + freq;
>>
>> -       calibval |= (fract_tick << RTC_FR_DATSHIFT);
>> +       if (fract_data)
>> +               calibval |= (RTC_FR_EN | (fract_data <<
>> + RTC_FR_DATSHIFT));
>>
>>         writel(calibval, (xrtcdev->reg_base + RTC_CALIB_WR));
>>
>>
>> --
>> 2.47.3
>>
> 
> Thanks,
> Harini T


^ permalink raw reply

* Re: [PATCH v2 3/5] rtc: zynqmp: rework read_offset
From: kernel test robot @ 2026-01-15  0:42 UTC (permalink / raw)
  To: Tomas Melin, Alexandre Belloni, Michal Simek
  Cc: oe-kbuild-all, linux-rtc, linux-arm-kernel, linux-kernel,
	Tomas Melin
In-Reply-To: <20260108-zynqmp-rtc-updates-v2-3-864c161fa83d@vaisala.com>

Hi Tomas,

kernel test robot noticed the following build errors:

[auto build test ERROR on abelloni/rtc-next]
[also build test ERROR on xilinx-xlnx/master linus/master v6.19-rc5]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Tomas-Melin/rtc-zynqmp-check-calibration-max-value/20260108-223800
base:   https://git.kernel.org/pub/scm/linux/kernel/git/abelloni/linux.git rtc-next
patch link:    https://lore.kernel.org/r/20260108-zynqmp-rtc-updates-v2-3-864c161fa83d%40vaisala.com
patch subject: [PATCH v2 3/5] rtc: zynqmp: rework read_offset
config: arm-allyesconfig (https://download.01.org/0day-ci/archive/20260115/202601150836.Yk8DcSZW-lkp@intel.com/config)
compiler: arm-linux-gnueabi-gcc (GCC) 15.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260115/202601150836.Yk8DcSZW-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202601150836.Yk8DcSZW-lkp@intel.com/

All errors (new ones prefixed by >>):

   arm-linux-gnueabi-ld: drivers/spi/spi-amlogic-spifc-a4.o: in function `aml_sfc_set_bus_width':
   spi-amlogic-spifc-a4.c:(.text.aml_sfc_set_bus_width+0x8c): undefined reference to `__ffsdi2'
   arm-linux-gnueabi-ld: spi-amlogic-spifc-a4.c:(.text.aml_sfc_set_bus_width+0xac): undefined reference to `__ffsdi2'
   arm-linux-gnueabi-ld: spi-amlogic-spifc-a4.c:(.text.aml_sfc_set_bus_width+0xcc): undefined reference to `__ffsdi2'
   arm-linux-gnueabi-ld: drivers/rtc/rtc-zynqmp.o: in function `xlnx_rtc_read_offset':
>> rtc-zynqmp.c:(.text.xlnx_rtc_read_offset+0xd0): undefined reference to `__aeabi_ldivmod'
>> arm-linux-gnueabi-ld: rtc-zynqmp.c:(.text.xlnx_rtc_read_offset+0x15c): undefined reference to `__aeabi_ldivmod'

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* Re: [RFC PATCH v2 1/5] rtc: migrate driver data to RTC device
From: Danilo Krummrich @ 2026-01-14 23:48 UTC (permalink / raw)
  To: Ke Sun
  Cc: Alexandre Belloni, Greg KH, Miguel Ojeda, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, linux-rtc, rust-for-linux, Alvin Sun
In-Reply-To: <f426f19d-a14c-4d9c-8587-2f7b4290024a@kylinos.cn>

On Thu Jan 15, 2026 at 12:23 AM CET, Ke Sun wrote:
> RTC ops callbacks receive struct device * as the first parameter.
> Traditionally this is rtc->dev.parent (the physical bus device), but Rust
> drivers store driver data on rtc->dev itself,

This is not only about Rust. Class device private data should be stored in the
driver_data field of the struct device embedded in the class device in general.

> so callbacks need &rtc->dev  to access it.

Class device callbacks should just carry the class device itself, rather than
the embedded struct device.

> We considered switching all callbacks to use rtc->dev directly, but that would
> require modifying  182 RTC drivers and extensive testing/validation work.

I don't know if it's that bad, the change would be trivial. You just need to
repeat it pretty often. :) Tools like Coccinelle [1] can help a lot with such
refactorings.

> Instead, we propose an alternative approach:
>
> - Added RTC_OPS_USE_RTC_DEV flag (currently stored in rtc->features bitmap)
> - Created rtc_ops_dev() helper that returns &rtc->dev if flag is set, 
> otherwise
>     rtc->dev.parent. Default behavior (returning rtc->dev.parent) maintains
>     backward compatibility
> - Updated all rtc->ops->callback call sites to use rtc_ops_dev(rtc)

Not sure if that intermediate step is needed, but it doesn't seem unreasonable
to me.

While eventually this is up to the RTC subsystem maintainer, from a driver-core
perspective this refactoring is encouraged:

Drivers should generally distinguish between stuff that is stored in the private
data of the bus device and private data of the class device, e.g. since they
have independent lifecycles and not all data might be relevant in all scopes.

Forcing drivers to also store the class device private data in the parent bus
device private data can be considered an anti-pattern.

[1] https://docs.kernel.org/dev-tools/coccinelle.html

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox