leds: wago-m4: register LEDs at probe time for immediate trigger activation

Previously LED class devices were registered inside wago_rpmsg_probe(),
which is called only after the RPMsg channel is announced by the M4.
With the M4 firmware loaded by U-Boot/SPL the channel still takes a
few hundred ms to appear, and before that no LED trigger could run.

This change makes the driver register all LED class devices directly
in wago_m4_led_probe() so that kernel triggers (timer, pattern, ...)
start working immediately at probe time, independently of the RPMsg
channel state. wago_led_set() already drops frames silently when
rpdev == NULL, so the trigger runs without error until the channel
comes up and real hardware updates start flowing.

Additionally, probe() now peeks at the rproc state via a temporary
rproc_get_by_phandle() call: when the rproc is already in
RPROC_DETACHED state (U-Boot/SPL loaded the firmware), boot_work is
scheduled with delay=0 instead of WAGO_BOOT_INITIAL_DELAY_MS so that
the RPMsg attach happens as fast as possible.

Summary of changes:
- wago_m4_led_probe(): call wago_led_register_leds() before the rpmsg
  driver registration; detect RPROC_DETACHED and set boot_delay_ms=0.
- wago_rpmsg_probe(): remove wago_led_register_leds() call; only store
  rpdev so wago_led_set() can begin sending real frames.
- Update header comment to document both boot paths.

Signed-off-by: Heinrich Toews <ht@twx-software.de>
This commit is contained in:
Heinrich Toews
2026-04-09 17:21:33 +02:00
parent e1e42b2f42
commit 63dc8ec804
+93 -34
View File
@@ -31,27 +31,47 @@
*
* Firmware loading and boot sequencing
* -------------------------------------
* The M4 firmware must come from the filesystem (/lib/firmware). At probe()
* time the rootfs may not yet be mounted, so the driver uses a
* delayed_work retry loop:
* Two boot paths are supported:
*
* a) U-Boot / SPL path (default for production):
* The M4 firmware is loaded by SPL before Linux starts. The rproc is
* already in RPROC_DETACHED state when the driver probes.
*
* In this path the driver registers all LED class devices immediately
* at probe() time so that kernel LED triggers (e.g. timer, pattern)
* start working without any delay. wago_led_set() silently drops
* frames while rpdev == NULL; once the RPMsg channel is announced
* (typically within a few hundred ms) real hardware updates flow.
*
* The boot_work is scheduled with zero delay to call rproc_boot()
* (attach) as early as possible.
*
* b) Linux-boot path (development / fallback):
* The driver loads the firmware from /lib/firmware itself via rproc_boot().
* Because the rootfs may not yet be mounted at probe() time, boot_work
* retries with WAGO_BOOT_RETRY_MS until the file appears.
*
* In this path LED class devices are also registered at probe() time
* so triggers work immediately, but the M4 will not actually render
* colours until rproc_boot() completes and the RPMsg channel appears.
*
* probe()
* -> register LED class devices immediately (triggers start at once)
* -> register RPMsg driver (waits for M4 channel announcement)
* -> schedule wago_boot_work (first attempt after WAGO_BOOT_INITIAL_DELAY)
* -> schedule wago_boot_work:
* RPROC_DETACHED -> delay=0 (attach right away)
* otherwise -> delay=WAGO_BOOT_INITIAL_DELAY_MS
*
* wago_boot_work
* -> rproc_get + rproc_set_firmware + rproc_boot
* success -> done (LEDs appear when RPMsg channel is announced)
* success -> RPMsg channel appears, wago_rpmsg_probe() sets rpdev
* -ENOENT -> filesystem not ready, reschedule after WAGO_BOOT_RETRY_MS
* other -> fatal, stop retrying
*
* If the M4 is already running (RPROC_DETACHED, bootloader path), the
* work attaches immediately without touching the filesystem.
*
* Sysfs example
* -------------
* echo "0 128 128" > /sys/class/leds/m4-led3/multi_intensity
* echo 255 > /sys/class/leds/m4-led3/brightness
* echo "0 128 128" > /sys/class/leds/sys/multi_intensity
* echo 255 > /sys/class/leds/sys/brightness
*
* # Send raw commands directly to the M4 for testing
* # (requires CONFIG_LEDS_WAGO_M4_WRAPPER_SYSFS_PASSTHROUGH=y):
@@ -553,7 +573,6 @@ static int wago_rpmsg_probe(struct rpmsg_device *rpdev)
*/
struct device *rproc_dev = rpdev->dev.parent->parent->parent;
struct wago_m4_led_priv *priv;
int ret;
/* Try three levels up first, then four (rproc->dev.parent layout
* may differ across kernel versions). */
@@ -569,17 +588,16 @@ static int wago_rpmsg_probe(struct rpmsg_device *rpdev)
return -ENODEV;
}
/*
* RPMsg channel is up LED class devices are already registered
* (done at platform probe time). Just store the channel handle so
* that wago_led_set() can start sending real frames to the M4.
*/
priv->rpdev = rpdev;
dev_set_drvdata(&rpdev->dev, priv);
ret = wago_led_register_leds(priv);
if (ret) {
priv->rpdev = NULL;
return ret;
}
dev_info(&rpdev->dev,
"WAGO M4 LED wrapper ready — %d RGB LEDs on strip\n",
"WAGO M4 RPMsg channel up — LED strip active (%d LEDs)\n",
WAGO_LED_NUM_LEDS);
return 0;
}
@@ -616,11 +634,15 @@ static struct rpmsg_driver wago_rpmsg_driver = {
* ---------------------------------------------------------------------- */
/*
* wago_boot_work - try to get the rproc handle and boot the M4.
* wago_boot_work - try to get the rproc handle and attach/boot the M4.
*
* Called from a workqueue, so blocking operations are safe.
* Reschedules itself with WAGO_BOOT_RETRY_MS if the firmware file is not
* yet available (-ENOENT / -EAGAIN), giving the rootfs time to appear.
*
* When the M4 is already running (RPROC_DETACHED, U-Boot/SPL path) this
* work is scheduled with zero delay from probe() so the RPMsg channel
* comes up as fast as possible.
*/
static void wago_boot_work(struct work_struct *work)
{
@@ -659,22 +681,22 @@ static void wago_boot_work(struct work_struct *work)
}
/*
* If the M4 is already powered (RPROC_DETACHED = bootloader path),
* just attach no firmware file needed.
* U-Boot / SPL path: M4 is already powered (RPROC_DETACHED).
* Just attach no firmware file needed.
*/
if (priv->rproc->state == RPROC_DETACHED) {
dev_info(dev, "M4 already running — attaching\n");
dev_info(dev, "M4 already running (SPL path) — attaching\n");
ret = rproc_boot(priv->rproc);
if (ret) {
dev_err(dev, "rproc attach failed: %d\n", ret);
goto fatal;
}
priv->rproc_booted_by_us = true;
dev_info(dev, "M4 attached, waiting for RPMsg channel\n");
dev_info(dev, "M4 attached, RPMsg channel expected shortly\n");
return;
}
/* Set the firmware name before each attempt (idempotent) */
/* Linux-boot path: load firmware from filesystem */
ret = rproc_set_firmware(priv->rproc, priv->fw_name);
if (ret) {
dev_err(dev, "rproc_set_firmware failed: %d\n", ret);
@@ -746,7 +768,9 @@ static void wago_rproc_stop(struct wago_m4_led_priv *priv)
static int wago_m4_led_probe(struct platform_device *pdev)
{
struct wago_m4_led_priv *priv;
struct device_node *rproc_np;
const char *fw_name;
unsigned long boot_delay_ms;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
@@ -764,9 +788,21 @@ static int wago_m4_led_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, priv);
/*
* Register the RPMsg driver first. The rpmsg core will call
* wago_rpmsg_probe() once the M4 announces the "rpmsg-tty" channel,
* regardless of whether the boot was done by us or the bootloader.
* Register LED class devices immediately so that kernel triggers
* (timer, pattern, ...) start working right away regardless of
* whether the RPMsg channel is up yet.
* wago_led_set() drops frames silently while rpdev == NULL.
*/
ret = wago_led_register_leds(priv);
if (ret) {
dev_err(&pdev->dev, "Failed to register LEDs: %d\n", ret);
return ret;
}
/*
* Register the RPMsg driver. The rpmsg core will call
* wago_rpmsg_probe() once the M4 announces the endpoint,
* regardless of whether the boot was done by SPL or by us.
*/
ret = register_rpmsg_driver(&wago_rpmsg_driver);
if (ret) {
@@ -786,18 +822,41 @@ static int wago_m4_led_probe(struct platform_device *pdev)
#endif /* CONFIG_LEDS_WAGO_M4_WRAPPER_SYSFS_PASSTHROUGH */
/*
* Schedule the boot work. The first attempt fires after
* WAGO_BOOT_INITIAL_DELAY_MS, giving the rootfs a head start.
* If the M4 is already running (bootloader path), the work will
* attach immediately without loading any firmware.
* Determine boot delay:
*
* If the rproc is already available and in RPROC_DETACHED state
* (U-Boot / SPL loaded the firmware), schedule boot_work immediately
* so the RPMsg channel comes up as fast as possible.
*
* Otherwise use WAGO_BOOT_INITIAL_DELAY_MS to give the rootfs time
* to mount before we try to load the firmware file.
*/
boot_delay_ms = WAGO_BOOT_INITIAL_DELAY_MS;
rproc_np = of_parse_phandle(pdev->dev.of_node, "remoteproc", 0);
if (rproc_np) {
struct rproc *rp = rproc_get_by_phandle(rproc_np->phandle);
of_node_put(rproc_np);
if (!IS_ERR_OR_NULL(rp)) {
if (rp->state == RPROC_DETACHED) {
boot_delay_ms = 0;
dev_info(&pdev->dev,
"M4 already running (SPL path), "
"attaching immediately\n");
}
/* rproc_boot_work will re-acquire it lazily */
rproc_put(rp);
}
}
INIT_DELAYED_WORK(&priv->boot_work, wago_boot_work);
schedule_delayed_work(&priv->boot_work,
msecs_to_jiffies(WAGO_BOOT_INITIAL_DELAY_MS));
msecs_to_jiffies(boot_delay_ms));
dev_info(&pdev->dev,
"WAGO M4 LED wrapper probed, boot in %d ms\n",
WAGO_BOOT_INITIAL_DELAY_MS);
"WAGO M4 LED wrapper probed, %d LEDs active, "
"RPMsg attach in %lu ms\n",
WAGO_LED_NUM_LEDS, boot_delay_ms);
return 0;
}