From: Markus Probst <markus.probst@posteo.de>
To: "Lee Jones" <lee@kernel.org>, "Rob Herring" <robh@kernel.org>,
"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
"Conor Dooley" <conor+dt@kernel.org>,
"Greg Kroah-Hartman" <gregkh@linuxfoundation.org>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>
Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
rust-for-linux@vger.kernel.org,
Markus Probst <markus.probst@posteo.de>
Subject: [PATCH v2 2/2] mfd: Add initial synology microp driver
Date: Sun, 08 Mar 2026 18:41:20 +0000 [thread overview]
Message-ID: <20260308-synology_microp_initial-v2-2-9389963f31c5@posteo.de> (raw)
In-Reply-To: <20260308-synology_microp_initial-v2-0-9389963f31c5@posteo.de>
Add a initial synology microp driver, written in Rust.
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.
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
MAINTAINERS | 6 +
drivers/mfd/Kconfig | 2 +
drivers/mfd/Makefile | 2 +
drivers/mfd/synology_microp/Kconfig | 14 ++
drivers/mfd/synology_microp/Makefile | 2 +
drivers/mfd/synology_microp/TODO | 7 +
drivers/mfd/synology_microp/command.rs | 50 +++++
drivers/mfd/synology_microp/led.rs | 275 +++++++++++++++++++++++++
drivers/mfd/synology_microp/synology_microp.rs | 82 ++++++++
rust/uapi/uapi_helper.h | 2 +
10 files changed, 442 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index e9e83ab552c7..092cd9e8a730 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25550,6 +25550,12 @@ F: drivers/dma-buf/sync_*
F: include/linux/sync_file.h
F: include/uapi/linux/sync_file.h
+SYNOLOGY MICROP DRIVER
+M: Markus Probst <markus.probst@posteo.de>
+S: Maintained
+F: Documentation/devicetree/bindings/mfd/synology,microp.yaml
+F: drivers/mfd/synology_microp/
+
SYNOPSYS ARC ARCHITECTURE
M: Vineet Gupta <vgupta@kernel.org>
L: linux-snps-arc@lists.infradead.org
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 7192c9d1d268..bc269719749f 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2580,5 +2580,7 @@ config MFD_MAX7360
additional drivers must be enabled in order to use the functionality
of the device.
+source "drivers/mfd/synology_microp/Kconfig"
+
endmenu
endif
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index e75e8045c28a..0a6fa33d5c35 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -304,3 +304,5 @@ obj-$(CONFIG_MFD_RSMU_SPI) += rsmu_spi.o rsmu_core.o
obj-$(CONFIG_MFD_UPBOARD_FPGA) += upboard-fpga.o
obj-$(CONFIG_MFD_LOONGSON_SE) += loongson-se.o
+
+obj-$(CONFIG_MFD_SYNOLOGY_MICROP) += synology_microp/
diff --git a/drivers/mfd/synology_microp/Kconfig b/drivers/mfd/synology_microp/Kconfig
new file mode 100644
index 000000000000..4bbbcf0b6e94
--- /dev/null
+++ b/drivers/mfd/synology_microp/Kconfig
@@ -0,0 +1,14 @@
+
+config MFD_SYNOLOGY_MICROP
+ tristate "Synology Microp driver"
+ depends on RUST
+ depends on SERIAL_DEV_BUS
+ depends on LEDS_CLASS && LEDS_CLASS_MULTICOLOR
+ default n
+ help
+ Enable support for the MCU found in Synology NAS devices.
+
+ This is needed to properly shutdown and reboot the device, as well as
+ additional functionality like fan and LED control.
+
+ This driver is work in progress and may not be fully functional.
diff --git a/drivers/mfd/synology_microp/Makefile b/drivers/mfd/synology_microp/Makefile
new file mode 100644
index 000000000000..d762cada20c9
--- /dev/null
+++ b/drivers/mfd/synology_microp/Makefile
@@ -0,0 +1,2 @@
+
+obj-y += synology_microp.o
diff --git a/drivers/mfd/synology_microp/TODO b/drivers/mfd/synology_microp/TODO
new file mode 100644
index 000000000000..1961a33115db
--- /dev/null
+++ b/drivers/mfd/synology_microp/TODO
@@ -0,0 +1,7 @@
+TODO:
+- 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/mfd/synology_microp/command.rs b/drivers/mfd/synology_microp/command.rs
new file mode 100644
index 000000000000..78f82a86f1b2
--- /dev/null
+++ b/drivers/mfd/synology_microp/command.rs
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0
+
+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/mfd/synology_microp/led.rs b/drivers/mfd/synology_microp/led.rs
new file mode 100644
index 000000000000..28a765b1e6c2
--- /dev/null
+++ b/drivers/mfd/synology_microp/led.rs
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use core::sync::atomic::{
+ AtomicBool,
+ Ordering, //
+};
+
+use kernel::{
+ device::{
+ property::FwNode,
+ Bound, //
+ },
+ devres::Devres,
+ error::Error,
+ led::{self, MultiColorSubLed},
+ macros::vtable,
+ prelude::*,
+ serdev,
+ types::ARef, //
+};
+
+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_by_fwnode<'a>(
+ parent: &'a serdev::Device<Bound>,
+ default_trigger: &'static CStr,
+ brightness: u32,
+ color: led::Color,
+ map: fn(State) -> Command,
+ fwnode: Option<ARef<FwNode>>,
+ ) -> impl PinInit<Devres<led::Device<Self>>, Error> + 'a {
+ led::DeviceBuilder::new()
+ .fwnode(fwnode)
+ .default_trigger(default_trigger)
+ .initial_brightness(brightness)
+ .devicename(c"synology-microp")
+ .color(color)
+ .build(
+ parent,
+ Ok(Self {
+ blink: AtomicBool::new(true),
+ map,
+ }),
+ )
+ }
+
+ 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 {
+ Self::register_by_fwnode(
+ parent,
+ default_trigger,
+ brightness,
+ color,
+ map,
+ parent
+ .as_ref()
+ .fwnode()
+ .and_then(|fwnode| fwnode.get_child_by_name(fwnode_child_name)),
+ )
+ }
+
+ fn register_optional<'a>(
+ parent: &'a serdev::Device<Bound>,
+ fwnode_child_name: &'static CStr,
+ default_trigger: &'static CStr,
+ brightness: u32,
+ color: led::Color,
+ map: fn(State) -> Command,
+ ) -> Option<impl PinInit<Devres<led::Device<Self>>, Error> + 'a> {
+ parent
+ .as_ref()
+ .fwnode()
+ .and_then(|fwnode| fwnode.get_child_by_name(fwnode_child_name))
+ .map(|fwnode| {
+ Self::register_by_fwnode(
+ parent,
+ default_trigger,
+ brightness,
+ color,
+ map,
+ Some(fwnode),
+ )
+ })
+ }
+
+ 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>,
+ ) -> Option<impl PinInit<Devres<led::Device<Self>>, Error> + 'a> {
+ Self::register_optional(
+ parent,
+ c"alert-led",
+ c"none",
+ 0,
+ led::Color::Orange,
+ Command::AlertLed,
+ )
+ }
+
+ pub(crate) fn register_usb<'a>(
+ parent: &'a serdev::Device<Bound>,
+ ) -> Option<impl PinInit<Devres<led::Device<Self>>, Error> + 'a> {
+ Self::register_optional(
+ 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/mfd/synology_microp/synology_microp.rs b/drivers/mfd/synology_microp/synology_microp.rs
new file mode 100644
index 000000000000..e92de7da3e46
--- /dev/null
+++ b/drivers/mfd/synology_microp/synology_microp.rs
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Synology Microp driver
+
+use kernel::{
+ device,
+ devres::{self, 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",
+ params: {
+ check_fan: i32 {
+ default: 1,
+ description: "Check for cpu fan failures",
+ },
+ },
+}
+
+#[pin_data]
+struct SynologyMicropDriver {
+ #[pin]
+ power_led: Devres<kernel::led::Device<SynologyMicropLedHandler>>,
+ #[pin]
+ status_led: Devres<kernel::led::MultiColorDevice<SynologyMicropStatusLedHandler>>,
+}
+
+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)?;
+
+ // TODO: Replace with Option field on SynologyMicropDriver once
+ // https://github.com/Rust-for-Linux/pin-init/issues/59 has been resolved.
+ if let Some(alert_led) = SynologyMicropLedHandler::register_alert(dev) {
+ devres::register(dev.as_ref(), alert_led, GFP_KERNEL)?;
+ }
+ if let Some(usb_led) = SynologyMicropLedHandler::register_usb(dev) {
+ devres::register(dev.as_ref(), usb_led, GFP_KERNEL)?;
+ }
+
+ Ok(try_pin_init!(Self {
+ power_led <- SynologyMicropLedHandler::register_power(dev),
+ status_led <- SynologyMicropStatusLedHandler::register(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
next prev parent reply other threads:[~2026-03-08 18:41 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-08 18:41 [PATCH v2 0/2] Introduce Synology Microp driver Markus Probst
2026-03-08 18:41 ` [PATCH v2 1/2] dt-bindings: mfd: Add synology,microp device Markus Probst
2026-03-09 7:17 ` Krzysztof Kozlowski
2026-03-08 18:41 ` Markus Probst [this message]
2026-03-08 18:55 ` [PATCH v2 2/2] mfd: Add initial synology microp driver Greg Kroah-Hartman
2026-03-08 19:15 ` Markus Probst
2026-03-09 5:56 ` Greg Kroah-Hartman
2026-03-09 9:43 ` Lee Jones
2026-03-09 12:52 ` Markus Probst
2026-03-09 13:07 ` Greg Kroah-Hartman
2026-03-09 13:34 ` Markus Probst
2026-03-09 13:32 ` Danilo Krummrich
2026-03-09 13:38 ` Markus Probst
2026-03-09 15:15 ` Lee Jones
2026-03-09 15:20 ` Markus Probst
2026-03-09 15:27 ` Lee Jones
2026-03-09 15:37 ` Danilo Krummrich
2026-03-09 15:50 ` Lee Jones
2026-03-08 18:56 ` Greg Kroah-Hartman
2026-03-08 19:23 ` Markus Probst
2026-03-09 5:55 ` Greg Kroah-Hartman
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260308-synology_microp_initial-v2-2-9389963f31c5@posteo.de \
--to=markus.probst@posteo.de \
--cc=a.hindborg@kernel.org \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=conor+dt@kernel.org \
--cc=dakr@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=gary@garyguo.net \
--cc=gregkh@linuxfoundation.org \
--cc=krzk+dt@kernel.org \
--cc=lee@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=ojeda@kernel.org \
--cc=robh@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tmgross@umich.edu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®