On 15/07/2025 17:16, Daniel Almeida wrote: > Changes in v7: > - Rebased on top of driver-core-next > - Added Flags::new(), which is a const fn. This lets us use build_assert!() > to verify the casts (hopefully this is what you meant, Alice?) > - Changed the Flags inner type to take c_ulong directly, to minimize casts > (Thanks, Alice) > - Moved the flag constants into Impl Flags, instead of using a separate > module (Alice) > - Reverted to using #[repr(u32)] in Threaded/IrqReturn (Thanks Alice, > Benno) > - Fixed all instances where the full path was specified for types in the > prelude (Alice) > - Removed 'static from the CStr used to perform the lookup in the platform > accessor (Alice) > - Renamed the PCI accessors, as asked by Danilo > - Added more docs to Flags, going into more detail on what they do and how > to use them (Miguel) > - Fixed the indentation in some of the docs (Alice) > - Added Alice's r-b as appropriate > - Link to v6: https://lore.kernel.org/rust-for-linux/20250703-topics-tyr-request_irq-v6-0-74103bdc7c52@xxxxxxxxxxxxx/ Looking for an easy way to test interrupts on an ARM64 Renesas RCar3 SoC I found a quite simple timer unit (TMU) which has a configurable (start value & frequency) count down. An interrupt is generated when the counter reaches 0. And the counter restarts then. There is a C driver for this already [1]. Using this patch series together with Alice's [2] I got a quite simple periodic 1 min interrupt handling to run (just for testing, of course not a full driver): [3] (output [4]). With that: Tested-by: Dirk Behme <dirk.behme@xxxxxxxxxxxx> Thanks to Daniel for the support! Dirk [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/clocksource/sh_tmu.c [2] https://lore.kernel.org/rust-for-linux/20250721-irq-bound-device-v1-1-4fb2af418a63@xxxxxxxxxx/ [3] diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index 645f517a1ac2..d009a0e3508c 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -554,7 +554,16 @@ config RENESAS_OSTM Enables the support for the Renesas OSTM. config SH_TIMER_TMU - bool "Renesas TMU timer driver" if COMPILE_TEST + bool "Renesas TMU timer driver" + depends on HAS_IOMEM + default SYS_SUPPORTS_SH_TMU + help + This enables build of a clocksource and clockevent driver for + the 32-bit Timer Unit (TMU) hardware available on a wide range + SoCs from Renesas. + +config SH_TIMER_TMU_RUST + bool "Renesas TMU Rust timer driver" depends on HAS_IOMEM default SYS_SUPPORTS_SH_TMU help diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 205bf3b0a8f3..66567f871502 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_SH_TIMER_CMT) += sh_cmt.o obj-$(CONFIG_SH_TIMER_MTU2) += sh_mtu2.o obj-$(CONFIG_RENESAS_OSTM) += renesas-ostm.o obj-$(CONFIG_SH_TIMER_TMU) += sh_tmu.o +obj-$(CONFIG_SH_TIMER_TMU_RUST) += sh_tmu_rust.o obj-$(CONFIG_EM_TIMER_STI) += em_sti.o obj-$(CONFIG_CLKBLD_I8253) += i8253.o obj-$(CONFIG_CLKSRC_MMIO) += mmio.o diff --git a/drivers/clocksource/sh_tmu_rust.rs b/drivers/clocksource/sh_tmu_rust.rs new file mode 100644 index 000000000000..328f9541d1bb --- /dev/null +++ b/drivers/clocksource/sh_tmu_rust.rs @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust Renesas TMU driver. + +use kernel::{ + c_str, + device::{Core, Device, Bound}, + devres::Devres, + io::mem::IoMem, + irq::{flags::Flags, IrqReturn, Registration}, + of, platform, + prelude::*, + sync::Arc, + types::ARef, +}; + +struct RenesasTMUDriver { + pdev: ARef<platform::Device>, + _registration: Arc<Registration<Handler>>, + _iomem: Arc<Devres<IoMem>>, +} + +struct Info; + +kernel::of_device_table!( + OF_TABLE, + MODULE_OF_TABLE, + <RenesasTMUDriver as platform::Driver>::IdInfo, + [(of::DeviceId::new(c_str!("renesas,tmu")), Info)] +); + +const TSTR: usize = 0x4; // 8 Bit register +const TCOR: usize = 0x8; // 32 Bit register +const TCNT: usize = 0xC; // 32 Bit register +const TCR: usize = 0x10; // 16 Bit register + +struct Handler { + iomem: Arc<Devres<IoMem>>, +} + +impl kernel::irq::request::Handler for Handler { + fn handle(&self, dev: &Device<Bound>) -> IrqReturn { + pr_info!("Renesas TMU IRQ handler called.\n"); + + // Reset the underflow flag + let io = self.iomem.access(dev).unwrap(); + let tcr = io.try_read16_relaxed(TCR).unwrap_or(0); + if tcr & (0x1 << 8) != 0 { + io.try_write16_relaxed(tcr & !(0x1 << 8), TCR).unwrap_or(()); + } + + IrqReturn::Handled + } +} + +impl platform::Driver for RenesasTMUDriver { + type IdInfo = Info; + const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE); + + fn probe( + pdev: &platform::Device<Core>, + _info: Option<&Self::IdInfo>, + ) -> Result<Pin<KBox<Self>>> { + let dev = pdev.as_ref(); + + dev_dbg!(dev, "Probe Rust Renesas TMU driver.\n"); + + let request = pdev.request_io_by_index(0).ok_or(EINVAL)?; + let iomem = Arc::pin_init(request.iomap()?, GFP_KERNEL)?; + let io = Arc::pin_init(iomem.access(dev)?, GFP_KERNEL)?; + + // Set count to 1 minute. Clock is 16.66MHz / 4 = 4.165MHz + let timeout = 4165000 * 60; // 1 minute in clock ticks + io.try_write32_relaxed(timeout, TCOR)?; + io.try_write32_relaxed(timeout, TCNT)?; + // Enable underflow interrupt (UNIE, Underflow Interrupt Control) + let tcr = io.try_read16_relaxed(TCR)?; + io.try_write16_relaxed(tcr | 0x1 << 5, TCR)?; + + let request = pdev.irq_by_index(0)?; + dev_info!(dev, "IRQ: {}\n", request.irq()); + let registration = Registration::new(request, Flags::SHARED, c_str!("tmu"), Handler{iomem: iomem.clone()}); + let registration = Arc::pin_init(registration, GFP_KERNEL)?; + + // Enable TMU + io.try_write8_relaxed(0x1, TSTR)?; + // Read back registers to verify + dev_info!(dev, "TSTR: 0x{:x}\n", io.try_read8_relaxed(TSTR)?); + dev_info!(dev, "TCOR: 0x{:x}\n", io.try_read32_relaxed(TCOR)?); + dev_info!(dev, "TCNT: 0x{:x}\n", io.try_read32_relaxed(TCNT)?); + dev_info!(dev, "TCR: 0x{:x}\n", io.try_read16_relaxed(TCR)?); + + let drvdata = KBox::pin_init(Self { pdev: pdev.into(), _registration: registration, _iomem: iomem.clone()}, GFP_KERNEL)?; + + dev_info!(dev, "probe done\n"); + + Ok(drvdata) + } +} + +impl Drop for RenesasTMUDriver { + fn drop(&mut self) { + dev_dbg!(self.pdev.as_ref(), "Remove Rust Renesas TMU driver.\n"); + } +} + +kernel::module_platform_driver! { + type: RenesasTMUDriver, + name: "rust_tmu", + authors: ["Dirk Behme"], + description: "Rust Renesas TMU driver", + license: "GPL v2", +} [4] Interrupt each 60s: ... [ 430.655055] rust_tmu: Renesas TMU IRQ handler called. [ 490.637054] rust_tmu: Renesas TMU IRQ handler called. [ 550.619052] rust_tmu: Renesas TMU IRQ handler called. [ 610.601050] rust_tmu: Renesas TMU IRQ handler called. [ 670.583049] rust_tmu: Renesas TMU IRQ handler called. [ 730.565047] rust_tmu: Renesas TMU IRQ handler called. ...