diff --git a/drivers/leds/rgb/wago-m4-led-wrapper.c b/drivers/leds/rgb/wago-m4-led-wrapper.c index e0d4bbb75b61..29263035c619 100644 --- a/drivers/leds/rgb/wago-m4-led-wrapper.c +++ b/drivers/leds/rgb/wago-m4-led-wrapper.c @@ -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; }