diff --git a/MAINTAINERS b/MAINTAINERS
index cef929b57159..954dbd311a55 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13020,6 +13020,7 @@ F:	drivers/leds/
 F:	include/dt-bindings/leds/
 F:	include/linux/leds.h
 F:	rust/kernel/leds.rs
+F:	rust/kernel/leds/
 
 LEGO MINDSTORMS EV3
 R:	David Lechner <david@lechnology.com>
diff --git a/rust/kernel/leds.rs b/rust/kernel/leds.rs
index 980af7c405d4..f10a10b56e23 100644
--- a/rust/kernel/leds.rs
+++ b/rust/kernel/leds.rs
@@ -10,9 +10,14 @@
 use crate::error::from_result;
 use crate::ffi::c_ulong;
 use crate::prelude::*;
+#[cfg(CONFIG_LEDS_TRIGGERS)]
+use crate::sync::Arc;
 use crate::time::Delta;
 use crate::types::Opaque;
 
+#[cfg(CONFIG_LEDS_TRIGGERS)]
+pub mod triggers;
+
 /// Color of an LED.
 #[allow(missing_docs)]
 #[derive(Copy, Clone)]
@@ -110,12 +115,34 @@ fn try_from(value: u32) -> Result<Self, Self::Error> {
 }
 
 /// Data used for led registration.
-#[derive(Clone)]
-pub struct LedConfig<'name> {
+pub struct LedConfig<'name, T> {
     /// Name to give the led.
     pub name: Option<&'name CStr>,
     /// Color of the LED.
     pub color: Color,
+    /// Private data of the LED.
+    pub data: T,
+
+    /// Default trigger name.
+    pub default_trigger: Option<&'static CStr>,
+    /// Hardware trigger.
+    ///
+    /// Setting this to some also defaults the default trigger to this hardware trigger.
+    /// Use `default_trigger: Some("none")` to overwrite this.
+    #[cfg(CONFIG_LEDS_TRIGGERS)]
+    pub hardware_trigger: Option<Arc<triggers::Hardware<T>>>,
+}
+
+impl<'name, T> LedConfig<'name, T> {
+    /// Create a new LedConfig
+    pub fn new(color: Color, data: T) -> Self {
+        Self {
+            color,
+            data,
+            // SAFETY: all other fields are valid with zeroes.
+            ..unsafe { core::mem::zeroed() }
+        }
+    }
 }
 
 /// A Led backed by a C `struct led_classdev`, additionally offering
@@ -141,8 +168,7 @@ impl<T> Led<T>
     #[cfg(CONFIG_LEDS_CLASS)]
     pub fn register<'a>(
         device: Option<&'a Device>,
-        config: &'a LedConfig<'a>,
-        data: T,
+        config: LedConfig<'a, T>,
     ) -> impl PinInit<Self, Error> + 'a
     where
         T: 'a,
@@ -188,14 +214,46 @@ pub fn register<'a>(
                 unsafe { ptr::write(set_fn_ptr, Some(blink_set::<T>)) };
             }
 
+        #[cfg(CONFIG_LEDS_TRIGGERS)]
+        if let Some(trigger) = config.hardware_trigger {
+            let trigger = trigger.into_raw();
+            // SAFETY: `place` is pointing to a live allocation.
+            let trigger_type_ptr = unsafe { ptr::addr_of_mut!((*place).trigger_type) };
+            // SAFETY: `trigger` is a valid pointer
+            let hw_trigger = unsafe { ptr::addr_of!((*trigger).hw_type) };
+            // SAFETY: `trigger_type_ptr` points to a valid allocation and we have exclusive access.
+            unsafe { ptr::write(trigger_type_ptr, hw_trigger.cast_mut().cast()) };
+
+            // SAFETY: trigger points to a valid hardware trigger struct.
+            let trigger_name_ptr = unsafe { Opaque::raw_get(ptr::addr_of!( (*trigger).trigger)) };
+            // SAFETY: trigger points to a valid hardware trigger struct.
+            let trigger_name_ptr = unsafe { (*trigger_name_ptr).name };
+            // SAFETY: `place` is pointing to a live allocation.
+            let default_trigger_ptr = unsafe { ptr::addr_of_mut!((*place).default_trigger) };
+            // SAFETY: `default_trigger_ptr` points to a valid allocation and we have exclusive access.
+            unsafe { ptr::write(default_trigger_ptr, trigger_name_ptr) };
+
+            // SAFETY: `place` is pointing to a live allocation.
+            let hw_ctrl_trigger_ptr = unsafe { ptr::addr_of_mut!((*place).hw_control_trigger) };
+            // SAFETY: `hw_ctrl_trigger_ptr` points to a valid allocation and we have exclusive access.
+            unsafe { ptr::write(hw_ctrl_trigger_ptr, trigger_name_ptr) };
+        }
+
+        // After hw trigger impl, to overwrite default trigger
+        if let Some(default_trigger) = config.default_trigger {
+            // SAFETY: `place` is pointing to a live allocation.
+            let default_trigger_ptr = unsafe { ptr::addr_of_mut!((*place).default_trigger) };
+            // SAFETY: `default_trigger_ptr` points to a valid allocation and we have exclusive access.
+            unsafe { ptr::write(default_trigger_ptr, default_trigger.as_char_ptr()) };
+        }
 
-            let dev = device.map(|dev| dev.as_raw()).unwrap_or(ptr::null_mut());
-            // SAFETY: `place` is a pointer to a live allocation of `bindings::led_classdev`.
-            crate::error::to_result(unsafe {
-                bindings::led_classdev_register_ext(dev, place, ptr::null_mut())
-            })
+        let dev = device.map(|dev| dev.as_raw()).unwrap_or(ptr::null_mut());
+        // SAFETY: `place` is a pointer to a live allocation of `bindings::led_classdev`.
+        crate::error::to_result(unsafe {
+                    bindings::led_classdev_register_ext(dev, place, ptr::null_mut())
+        })
             }),
-            data: data,
+            data: config.data,
         })
     }
 }
@@ -220,6 +278,23 @@ fn drop(self: Pin<&mut Self>) {
         unsafe {
             bindings::led_classdev_unregister(self.led.get())
         }
+
+        // drop trigger if there is a hw trigger defined.
+        #[cfg(CONFIG_LEDS_TRIGGERS)]
+        {
+            // SAFETY: `self.led` is a valid led.
+            let hw_trigger_type =
+                unsafe { ptr::read(ptr::addr_of!((*self.led.get()).trigger_type)) };
+            if !hw_trigger_type.is_null() {
+                // SAFETY: hw_trigger_type is a valid and non null pointer into a Hardware trigger.
+                let hw_trigger_type = unsafe {
+                    crate::container_of!(hw_trigger_type, triggers::Hardware<T>, hw_type)
+                };
+                // SAFETY: `hw_trigger_type` is a valid pointer that came from an arc.
+                let hw_trigger_type = unsafe { Arc::from_raw(hw_trigger_type) };
+                drop(hw_trigger_type);
+            }
+        }
     }
 }
 
diff --git a/rust/kernel/leds/triggers.rs b/rust/kernel/leds/triggers.rs
new file mode 100644
index 000000000000..d5f2b8252645
--- /dev/null
+++ b/rust/kernel/leds/triggers.rs
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! LED trigger abstractions.
+
+use core::marker::PhantomData;
+use core::ptr;
+
+use crate::error::{from_result, to_result};
+use crate::prelude::*;
+use crate::types::Opaque;
+
+use super::FromLedClassdev;
+
+/// LED Hardware trigger.
+///
+/// Used to impement a hardware operation mode for an LED.
+#[pin_data(PinnedDrop)]
+pub struct Hardware<T> {
+    #[pin]
+    pub(crate) hw_type: Opaque<bindings::led_hw_trigger_type>,
+    #[pin]
+    pub(crate) trigger: Opaque<bindings::led_trigger>,
+    _t: PhantomData<T>,
+}
+
+impl<T> Hardware<T>
+where
+    T: HardwareOperations,
+{
+    /// Register a new hardware Trigger with a given name.
+    pub fn register(name: &'static CStr) -> impl PinInit<Self, Error> {
+        try_pin_init!( Self {
+            // SAFETY: `led_hw_trigger_type` is valid with all zeroes.
+            hw_type: Opaque::new(unsafe { core::mem::zeroed() }),
+            trigger <- Opaque::try_ffi_init(move |place: *mut bindings::led_trigger| {
+            // SAFETY: `place` is a pointer to a live allocation, so erasing is valid.
+            unsafe { place.write_bytes(0, 1) };
+
+            // Add name
+            // SAFETY: `place` is pointing to a live allocation, so the deref is safe.
+            let name_ptr = unsafe { ptr::addr_of_mut!((*place).name) };
+            // SAFETY: `name_ptr` points to a valid allocation and we have exclusive access.
+            unsafe { ptr::write(name_ptr, name.as_char_ptr()) };
+
+            // Add fn pointers
+            // SAFETY: `place` is pointing to a live allocation, so the deref is safe.
+            let activate_fn_ptr: *mut Option<_> = unsafe { ptr::addr_of_mut!((*place).activate) };
+            // SAFETY: `activate_fn_ptr` points to a valid allocation and we have exclusive access.
+            unsafe { ptr::write(activate_fn_ptr, Some(trigger_activate::<T>)) };
+
+            if T::HAS_DEACTIVATE {
+                // SAFETY: `place` is pointing to a live allocation, so the deref is safe.
+                let deactivate_fn_ptr: *mut Option<_> = unsafe { ptr::addr_of_mut!((*place).deactivate) };
+                // SAFETY: `deactivate_fn_ptr` points to a valid allocation and we have exclusive access.
+                unsafe { ptr::write(deactivate_fn_ptr, Some(trigger_deactivate::<T>)) };
+            }
+
+            // Add hardware trigger
+            // SAFETY: `place` is pointing to a live allocation, so the deref is safe.
+            let trigger_type_ptr = unsafe { ptr::addr_of_mut!((*place).trigger_type) };
+            // SAFETY: `place` is pointing to a live allocation, so the deref is safe.
+            let trigger_type = unsafe { crate::container_of!(place, Self, trigger).cast_mut() };
+            // SAFETY: `trigger_type` is pointing to a live allocation of Self.
+            let trigger_type = unsafe { ptr::addr_of!((*trigger_type).hw_type) };
+            // SAFETY: `trigger_type_ptr` points to a valid allocation and we have exclusive access.
+            unsafe{ ptr::write(trigger_type_ptr, Opaque::raw_get(trigger_type)) };
+
+        // SAFETY: ffi call, `place` is sufficently filled with data at this point
+            to_result(unsafe {
+                bindings::led_trigger_register(place)
+            })
+            }),
+            _t: PhantomData,
+        })
+    }
+}
+
+#[pinned_drop]
+impl<T> PinnedDrop for Hardware<T> {
+    fn drop(self: Pin<&mut Self>) {
+        // SAFETY: trigger is pointing to a live and registered allocation
+        unsafe {
+            bindings::led_trigger_unregister(self.trigger.get());
+        }
+    }
+}
+
+/// Operations for the Hardware trigger
+#[macros::vtable]
+pub trait HardwareOperations: super::Operations {
+    /// Activate the hardware trigger.
+    fn activate(this: &mut Self::This) -> Result;
+    /// Deactivate the hardware trigger.
+    fn deactivate(_this: &mut Self::This) {
+        crate::build_error(crate::error::VTABLE_DEFAULT_ERROR)
+    }
+}
+
+/// `trigger_activate` function pointer
+///
+/// # Safety
+///
+/// `led_cdev` must be passed by the corresponding callback in `led_trigger`.
+unsafe extern "C" fn trigger_activate<T>(led_cdev: *mut bindings::led_classdev) -> i32
+where
+    T: HardwareOperations,
+{
+    from_result(|| {
+        // SAFETY: By the safety requirement of this function `led_cdev` is embedded inside a `T::This<T>`.
+        let led = unsafe { &mut *(T::This::led_container_of(led_cdev.cast())) };
+        T::activate(led)?;
+        Ok(0)
+    })
+}
+
+/// `trigger_deactivate` function pointer
+///
+/// # Safety
+///
+/// `led_cdev` must be passed by the corresponding callback in `led_trigger`.
+unsafe extern "C" fn trigger_deactivate<T>(led_cdev: *mut bindings::led_classdev)
+where
+    T: HardwareOperations,
+{
+    // SAFETY: By the safety requirement of this function `led_cdev` is embedded inside a `T::This<T>`.
+    let led = unsafe { &mut *(T::This::led_container_of(led_cdev.cast())) };
+    T::deactivate(led)
+}
