Add a initial synology microp driver, written in Rust, to the staging tree.
The driver targets a microcontroller found in Synology NAS devices. It
currently only supports controlling of the power led, status led, alert
led and usb led. Other components such as fan control or handling
on-device buttons will be added once the required rust abstractions are
there.
---
MAINTAINERS | 6 +
drivers/staging/Kconfig | 2 +
drivers/staging/Makefile | 1 +
drivers/staging/synology_microp/Kconfig | 4 +
drivers/staging/synology_microp/Makefile | 2 +
drivers/staging/synology_microp/TODO | 8 +
drivers/staging/synology_microp/command.rs | 48 +++++
drivers/staging/synology_microp/led.rs | 229 +++++++++++++++++++++
drivers/staging/synology_microp/synology_microp.rs | 73 +++++++
rust/uapi/uapi_helper.h | 2 +
10 files changed, 375 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index e9e83ab552c7..a1f8dec31db2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25143,6 +25143,12 @@ L: linux-fbdev@vger.kernel.org
S: Maintained
F: drivers/staging/sm750fb/
+STAGING - SYNOLOGY MICROP DRIVER
+M: Markus Probst <markus.probst@posteo.de>
+S: Maintained
+F: Documentation/devicetree/bindings/mfd/synology,microp.yaml
+F: drivers/staging/synology_microp/
+
STAGING SUBSYSTEM
M: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
L: linux-staging@lists.linux.dev
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 2f92cd698bef..193d4a5d7f56 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -48,4 +48,6 @@ source "drivers/staging/axis-fifo/Kconfig"
source "drivers/staging/vme_user/Kconfig"
+source "drivers/staging/synology_microp/Kconfig"
+
endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index f5b8876aa536..cb0cadd08ae2 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -13,3 +13,4 @@ obj-$(CONFIG_MOST) += most/
obj-$(CONFIG_GREYBUS) += greybus/
obj-$(CONFIG_BCM2835_VCHIQ) += vc04_services/
obj-$(CONFIG_XIL_AXIS_FIFO) += axis-fifo/
+obj-$(CONFIG_MFD_SYNOLOGY_MICROP) += synology_microp/
diff --git a/drivers/staging/synology_microp/Kconfig b/drivers/staging/synology_microp/Kconfig
new file mode 100644
index 000000000000..8d315d2576d8
--- /dev/null
+++ b/drivers/staging/synology_microp/Kconfig
@@ -0,0 +1,4 @@
+
+config MFD_SYNOLOGY_MICROP
+ tristate "Synology Microp driver"
+ depends on RUST && SERIAL_DEV_BUS && LEDS_CLASS && LEDS_CLASS_MULTICOLOR
diff --git a/drivers/staging/synology_microp/Makefile b/drivers/staging/synology_microp/Makefile
new file mode 100644
index 000000000000..d762cada20c9
--- /dev/null
+++ b/drivers/staging/synology_microp/Makefile
@@ -0,0 +1,2 @@
+
+obj-y += synology_microp.o
diff --git a/drivers/staging/synology_microp/TODO b/drivers/staging/synology_microp/TODO
new file mode 100644
index 000000000000..d432ddd8403c
--- /dev/null
+++ b/drivers/staging/synology_microp/TODO
@@ -0,0 +1,8 @@
+TODO:
+- not all devices have a alert or usb led. Only register them, if a fwnode entry is present.
+- add missing components:
+ - handle on-device buttons (Power, Factory reset, "USB Copy")
+ - handle fan failure
+ - beeper
+ - fan speed control
+ - correctly perform device power-off and restart on Synology devices
diff --git a/drivers/staging/synology_microp/command.rs b/drivers/staging/synology_microp/command.rs
new file mode 100644
index 000000000000..e98e46423e2d
--- /dev/null
+++ b/drivers/staging/synology_microp/command.rs
@@ -0,0 +1,48 @@
+use kernel::{
+ device::Bound,
+ error::Result,
+ serdev, //
+};
+
+use crate::led;
+
+#[derive(Copy, Clone)]
+#[expect(
+ clippy::enum_variant_names,
+ reason = "future variants will not end with Led"
+)]
+pub(crate) enum Command {
+ PowerLed(led::State),
+ StatusLed(led::StatusLedColor, led::State),
+ AlertLed(led::State),
+ UsbLed(led::State),
+}
+
+impl Command {
+ pub(crate) fn write(self, dev: &serdev::Device<Bound>) -> Result<()> {
+ dev.write_all(
+ match self {
+ Command::PowerLed(led::State::On) => &[0x34],
+ Command::PowerLed(led::State::Blink) => &[0x35],
+ Command::PowerLed(led::State::Off) => &[0x36],
+
+ Command::StatusLed(_, led::State::Off) => &[0x37],
+ Command::StatusLed(led::StatusLedColor::Green, led::State::On) => &[0x38],
+ Command::StatusLed(led::StatusLedColor::Green, led::State::Blink) => &[0x39],
+ Command::StatusLed(led::StatusLedColor::Orange, led::State::On) => &[0x3A],
+ Command::StatusLed(led::StatusLedColor::Orange, led::State::Blink) => &[0x3B],
+
+ Command::AlertLed(led::State::On) => &[0x4C, 0x41, 0x31],
+ Command::AlertLed(led::State::Blink) => &[0x4C, 0x41, 0x32],
+ Command::AlertLed(led::State::Off) => &[0x4C, 0x41, 0x33],
+
+ Command::UsbLed(led::State::On) => &[0x40],
+ Command::UsbLed(led::State::Blink) => &[0x41],
+ Command::UsbLed(led::State::Off) => &[0x42],
+ },
+ serdev::Timeout::Max,
+ )?;
+ dev.wait_until_sent(serdev::Timeout::Max);
+ Ok(())
+ }
+}
diff --git a/drivers/staging/synology_microp/led.rs b/drivers/staging/synology_microp/led.rs
new file mode 100644
index 000000000000..deeece661937
--- /dev/null
+++ b/drivers/staging/synology_microp/led.rs
@@ -0,0 +1,229 @@
+use core::sync::atomic::{
+ AtomicBool,
+ Ordering, //
+};
+
+use kernel::{
+ device::Bound,
+ devres::Devres,
+ error::Error,
+ led::{self, MultiColorSubLed},
+ macros::vtable,
+ prelude::*,
+ serdev, //
+};
+
+use crate::command::Command;
+
+pub(crate) struct SynologyMicropLedHandler {
+ blink: AtomicBool,
+ map: fn(State) -> Command,
+}
+
+pub(crate) struct SynologyMicropStatusLedHandler {
+ blink: AtomicBool,
+}
+
+#[derive(Copy, Clone)]
+pub(crate) enum State {
+ On,
+ Blink,
+ Off,
+}
+
+#[derive(Copy, Clone)]
+pub(crate) enum StatusLedColor {
+ Green,
+ Orange,
+}
+
+impl SynologyMicropLedHandler {
+ fn register<'a>(
+ parent: &'a serdev::Device<Bound>,
+ fwnode_child_name: &'static CStr,
+ default_trigger: &'static CStr,
+ brightness: u32,
+ color: led::Color,
+ map: fn(State) -> Command,
+ ) -> impl PinInit<Devres<led::Device<Self>>, Error> + 'a {
+ led::DeviceBuilder::new()
+ .fwnode(
+ parent
+ .as_ref()
+ .fwnode()
+ .and_then(|fwnode| fwnode.get_child_by_name(fwnode_child_name)),
+ )
+ .default_trigger(default_trigger)
+ .initial_brightness(brightness)
+ .devicename(c"synology-microp")
+ .color(color)
+ .build(
+ parent,
+ Ok(Self {
+ blink: AtomicBool::new(true),
+ map,
+ }),
+ )
+ }
+
+ pub(crate) fn register_power<'a>(
+ parent: &'a serdev::Device<Bound>,
+ ) -> impl PinInit<Devres<led::Device<Self>>, Error> + 'a {
+ Self::register(
+ parent,
+ c"power-led",
+ c"timer",
+ 1,
+ led::Color::Blue,
+ Command::PowerLed,
+ )
+ }
+
+ pub(crate) fn register_alert<'a>(
+ parent: &'a serdev::Device<Bound>,
+ ) -> impl PinInit<Devres<led::Device<Self>>, Error> + 'a {
+ Self::register(
+ parent,
+ c"alert-led",
+ c"none",
+ 0,
+ led::Color::Orange,
+ Command::AlertLed,
+ )
+ }
+
+ pub(crate) fn register_usb<'a>(
+ parent: &'a serdev::Device<Bound>,
+ ) -> impl PinInit<Devres<led::Device<Self>>, Error> + 'a {
+ Self::register(
+ parent,
+ c"usb-led",
+ c"none",
+ 0,
+ led::Color::Green,
+ Command::UsbLed,
+ )
+ }
+}
+
+#[vtable]
+impl led::LedOps for SynologyMicropLedHandler {
+ type Bus = serdev::Device<Bound>;
+ type Mode = led::Normal;
+ const BLOCKING: bool = true;
+ const MAX_BRIGHTNESS: u32 = 1;
+
+ fn brightness_set(
+ &self,
+ dev: &Self::Bus,
+ _classdev: &led::Device<Self>,
+ brightness: u32,
+ ) -> Result<()> {
+ (self.map)(if brightness == 0 {
+ self.blink.store(false, Ordering::Relaxed);
+ State::Off
+ } else if self.blink.load(Ordering::Relaxed) {
+ State::Blink
+ } else {
+ State::On
+ })
+ .write(dev)
+ }
+
+ fn blink_set(
+ &self,
+ dev: &Self::Bus,
+ _classdev: &led::Device<Self>,
+ delay_on: &mut usize,
+ delay_off: &mut usize,
+ ) -> Result<()> {
+ *delay_on = 167;
+ *delay_off = 167;
+
+ self.blink.store(true, Ordering::Relaxed);
+ (self.map)(State::Blink).write(dev)
+ }
+}
+
+impl SynologyMicropStatusLedHandler {
+ pub(crate) fn register(
+ parent: &serdev::Device<Bound>,
+ ) -> impl PinInit<Devres<led::MultiColorDevice<Self>>, Error> + '_ {
+ const SUBLEDS: &[MultiColorSubLed] = &[
+ MultiColorSubLed::new(led::Color::Green).initial_intensity(1),
+ MultiColorSubLed::new(led::Color::Orange),
+ ];
+
+ led::DeviceBuilder::new()
+ .fwnode(
+ parent
+ .as_ref()
+ .fwnode()
+ .and_then(|fwnode| fwnode.get_child_by_name(c"status-led")),
+ )
+ .devicename(c"synology-microp")
+ .color(led::Color::Multi)
+ .build_multicolor(
+ parent,
+ Ok(SynologyMicropStatusLedHandler {
+ blink: AtomicBool::new(false),
+ }),
+ SUBLEDS,
+ )
+ }
+}
+
+#[vtable]
+impl led::LedOps for SynologyMicropStatusLedHandler {
+ type Bus = serdev::Device<Bound>;
+ type Mode = led::MultiColor;
+ const BLOCKING: bool = true;
+ const MAX_BRIGHTNESS: u32 = 1;
+
+ fn brightness_set(
+ &self,
+ dev: &Self::Bus,
+ classdev: &led::MultiColorDevice<Self>,
+ brightness: u32,
+ ) -> Result<()> {
+ if brightness == 0 {
+ self.blink.store(false, Ordering::Relaxed);
+ }
+
+ let (color, subled_brightness) = if classdev.subleds()[1].brightness == 0 {
+ (StatusLedColor::Green, classdev.subleds()[0].brightness)
+ } else {
+ (StatusLedColor::Orange, classdev.subleds()[1].brightness)
+ };
+
+ if subled_brightness == 0 {
+ Command::StatusLed(color, State::Off)
+ } else if self.blink.load(Ordering::Relaxed) {
+ Command::StatusLed(color, State::Blink)
+ } else {
+ Command::StatusLed(color, State::On)
+ }
+ .write(dev)
+ }
+
+ fn blink_set(
+ &self,
+ dev: &Self::Bus,
+ classdev: &led::MultiColorDevice<Self>,
+ delay_on: &mut usize,
+ delay_off: &mut usize,
+ ) -> Result<()> {
+ *delay_on = 167;
+ *delay_off = 167;
+
+ self.blink.store(true, Ordering::Relaxed);
+
+ let color = if classdev.subleds()[1].brightness == 0 {
+ StatusLedColor::Green
+ } else {
+ StatusLedColor::Orange
+ };
+
+ Command::StatusLed(color, State::Blink).write(dev)
+ }
+}
diff --git a/drivers/staging/synology_microp/synology_microp.rs b/drivers/staging/synology_microp/synology_microp.rs
new file mode 100644
index 000000000000..abc513edc590
--- /dev/null
+++ b/drivers/staging/synology_microp/synology_microp.rs
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Synology Microp driver
+
+use kernel::{
+ device,
+ devres::Devres,
+ of,
+ prelude::*,
+ serdev, //
+};
+use pin_init::pin_init_scope;
+
+use crate::{
+ led::{
+ SynologyMicropLedHandler,
+ SynologyMicropStatusLedHandler, //
+ }, //
+};
+
+pub(crate) mod command;
+mod led;
+
+kernel::module_serdev_device_driver! {
+ type: SynologyMicropDriver,
+ name: "synology_microp",
+ authors: ["Markus Probst <markus.probst@posteo.de>"],
+ description: "Synology Microp driver",
+ license: "GPL v2",
+}
+
+#[pin_data]
+struct SynologyMicropDriver {
+ #[pin]
+ power_led: Devres<kernel::led::Device<SynologyMicropLedHandler>>,
+ #[pin]
+ status_led: Devres<kernel::led::MultiColorDevice<SynologyMicropStatusLedHandler>>,
+ #[pin]
+ alert_led: Devres<kernel::led::Device<SynologyMicropLedHandler>>,
+ #[pin]
+ usb_led: Devres<kernel::led::Device<SynologyMicropLedHandler>>,
+}
+
+kernel::of_device_table!(
+ OF_TABLE,
+ MODULE_OF_TABLE,
+ <SynologyMicropDriver as serdev::Driver>::IdInfo,
+ [(of::DeviceId::new(c"synology,microp"), ()),]
+);
+
+#[vtable]
+impl serdev::Driver for SynologyMicropDriver {
+ type IdInfo = ();
+ const OF_ID_TABLE: Option<kernel::of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+
+ fn probe(
+ dev: &serdev::Device<device::Core>,
+ _id_info: Option<&Self::IdInfo>,
+ ) -> impl PinInit<Self, kernel::error::Error> {
+ pin_init_scope(move || {
+ let _ = dev.set_baudrate(9600);
+ dev.set_flow_control(false);
+ dev.set_parity(serdev::Parity::None)?;
+
+ Ok(try_pin_init!(Self {
+ power_led <- SynologyMicropLedHandler::register_power(dev),
+ status_led <- SynologyMicropStatusLedHandler::register(dev),
+ alert_led <- SynologyMicropLedHandler::register_alert(dev),
+ usb_led <- SynologyMicropLedHandler::register_usb(dev),
+ }))
+ })
+ }
+}
diff --git a/rust/uapi/uapi_helper.h b/rust/uapi/uapi_helper.h
index 06d7d1a2e8da..94b6c1b59e56 100644
--- a/rust/uapi/uapi_helper.h
+++ b/rust/uapi/uapi_helper.h
@@ -14,3 +14,5 @@
#include <uapi/linux/mdio.h>
#include <uapi/linux/mii.h>
#include <uapi/linux/ethtool.h>
+#include <uapi/linux/serial_reg.h>
+
--
2.52.0
On Fri, Mar 06, 2026 at 07:38:30PM +0000, Markus Probst wrote: > Add a initial synology microp driver, written in Rust, to the staging tree. > The driver targets a microcontroller found in Synology NAS devices. It > currently only supports controlling of the power led, status led, alert > led and usb led. Other components such as fan control or handling > on-device buttons will be added once the required rust abstractions are > there. > --- > MAINTAINERS | 6 + > drivers/staging/Kconfig | 2 + > drivers/staging/Makefile | 1 + > drivers/staging/synology_microp/Kconfig | 4 + > drivers/staging/synology_microp/Makefile | 2 + > drivers/staging/synology_microp/TODO | 8 + > drivers/staging/synology_microp/command.rs | 48 +++++ > drivers/staging/synology_microp/led.rs | 229 +++++++++++++++++++++ > drivers/staging/synology_microp/synology_microp.rs | 73 +++++++ > rust/uapi/uapi_helper.h | 2 + > 10 files changed, 375 insertions(+) No signed-off-by? And why staging? That's for stuff that needs obvious cleanups done to it, you don't list that here. > diff --git a/MAINTAINERS b/MAINTAINERS > index e9e83ab552c7..a1f8dec31db2 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -25143,6 +25143,12 @@ L: linux-fbdev@vger.kernel.org > S: Maintained > F: drivers/staging/sm750fb/ > > +STAGING - SYNOLOGY MICROP DRIVER > +M: Markus Probst <markus.probst@posteo.de> > +S: Maintained > +F: Documentation/devicetree/bindings/mfd/synology,microp.yaml > +F: drivers/staging/synology_microp/ No tabs? thanks, greg k-h
On Sat, 2026-03-07 at 08:15 +0100, Greg Kroah-Hartman wrote: > On Fri, Mar 06, 2026 at 07:38:30PM +0000, Markus Probst wrote: > > Add a initial synology microp driver, written in Rust, to the staging tree. > > The driver targets a microcontroller found in Synology NAS devices. It > > currently only supports controlling of the power led, status led, alert > > led and usb led. Other components such as fan control or handling > > on-device buttons will be added once the required rust abstractions are > > there. > > --- > > MAINTAINERS | 6 + > > drivers/staging/Kconfig | 2 + > > drivers/staging/Makefile | 1 + > > drivers/staging/synology_microp/Kconfig | 4 + > > drivers/staging/synology_microp/Makefile | 2 + > > drivers/staging/synology_microp/TODO | 8 + > > drivers/staging/synology_microp/command.rs | 48 +++++ > > drivers/staging/synology_microp/led.rs | 229 +++++++++++++++++++++ > > drivers/staging/synology_microp/synology_microp.rs | 73 +++++++ > > rust/uapi/uapi_helper.h | 2 + > > 10 files changed, 375 insertions(+) > > No signed-off-by? Signed-off-by: Markus Probst <markus.probst@posteo.de> > > And why staging? That's for stuff that needs obvious cleanups done to > it, you don't list that here. Because the driver is not done yet. There are components that do not exist yet, because the rust abstractions are missing. Merging an initial version of this driver into staging makes it possible for the serdev and led rust abstractions to be merged (as their now exists a driver). After this I can work on the missing rust abstractions. Once the driver is complete, is can be moved into the mfd tree. > > > diff --git a/MAINTAINERS b/MAINTAINERS > > index e9e83ab552c7..a1f8dec31db2 100644 > > --- a/MAINTAINERS > > +++ b/MAINTAINERS > > @@ -25143,6 +25143,12 @@ L: linux-fbdev@vger.kernel.org > > S: Maintained > > F: drivers/staging/sm750fb/ > > > > +STAGING - SYNOLOGY MICROP DRIVER > > +M: Markus Probst <markus.probst@posteo.de> > > +S: Maintained > > +F: Documentation/devicetree/bindings/mfd/synology,microp.yaml > > +F: drivers/staging/synology_microp/ > > No tabs? Will be fixed. > > thanks, > > greg k-h Thanks - Markus Probst
On Sat, Mar 07, 2026 at 12:58:31PM +0000, Markus Probst wrote: > On Sat, 2026-03-07 at 08:15 +0100, Greg Kroah-Hartman wrote: > > On Fri, Mar 06, 2026 at 07:38:30PM +0000, Markus Probst wrote: > > > Add a initial synology microp driver, written in Rust, to the staging tree. > > > The driver targets a microcontroller found in Synology NAS devices. It > > > currently only supports controlling of the power led, status led, alert > > > led and usb led. Other components such as fan control or handling > > > on-device buttons will be added once the required rust abstractions are > > > there. > > > --- > > > MAINTAINERS | 6 + > > > drivers/staging/Kconfig | 2 + > > > drivers/staging/Makefile | 1 + > > > drivers/staging/synology_microp/Kconfig | 4 + > > > drivers/staging/synology_microp/Makefile | 2 + > > > drivers/staging/synology_microp/TODO | 8 + > > > drivers/staging/synology_microp/command.rs | 48 +++++ > > > drivers/staging/synology_microp/led.rs | 229 +++++++++++++++++++++ > > > drivers/staging/synology_microp/synology_microp.rs | 73 +++++++ > > > rust/uapi/uapi_helper.h | 2 + > > > 10 files changed, 375 insertions(+) > > > > No signed-off-by? > Signed-off-by: Markus Probst <markus.probst@posteo.de> That has to go into the real part of the patch :) > > > > And why staging? That's for stuff that needs obvious cleanups done to > > it, you don't list that here. > Because the driver is not done yet. There are components that do not > exist yet, because the rust abstractions are missing. That is not what staging is for, sorry. Please just work to get the driver working properly and merge it to the correct location first. thanks, greg k-h
© 2016 - 2026 Red Hat, Inc.