From b04c22d06c09bb7b7e41aa22f7ce48ed6801a34c Mon Sep 17 00:00:00 2001 From: Heinrich Toews Date: Thu, 2 Apr 2026 15:44:28 +0200 Subject: [PATCH] leds: rgb: wago-m4-led-wrapper: add sysfs cmd passthrough Add a `wago_led_cmd` sysfs attribute to the leds-m4 platform device that allows raw ASCII commands to be forwarded directly to the M4 co-processor via RPMsg. This is useful for testing and debugging M4 LED firmware without going through the LED class abstraction. The attribute strips trailing whitespace and re-appends a single newline before forwarding, matching the protocol expected by the Zephyr application. A read of the attribute reports the RPMsg channel state (online/offline). The sysfs group is created during probe and removed on teardown. Signed-off-by: Heinrich Toews --- drivers/leds/rgb/wago-m4-led-wrapper.c | 80 ++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 6 deletions(-) diff --git a/drivers/leds/rgb/wago-m4-led-wrapper.c b/drivers/leds/rgb/wago-m4-led-wrapper.c index feec60ff4482..50e44f9ce820 100644 --- a/drivers/leds/rgb/wago-m4-led-wrapper.c +++ b/drivers/leds/rgb/wago-m4-led-wrapper.c @@ -50,6 +50,10 @@ * echo "0 128 128" > /sys/class/leds/m4-led3/multi_intensity * echo 255 > /sys/class/leds/m4-led3/brightness * + * # Send raw commands directly to the M4 for testing: + * echo "CMD-IDL" > /sys/bus/platform/devices/leds-m4/wago_led_cmd + * echo "CMD-CYC-50-128" > /sys/bus/platform/devices/leds-m4/wago_led_cmd + * * Author: WAGO GmbH & Co. KG */ @@ -202,16 +206,71 @@ static int wago_send_cmd(struct wago_m4_led_priv *priv, const char *cmd) } /* ------------------------------------------------------------------------- - * LED multiclass brightness_set callback (non-blocking, fire-and-forget) + * Sysfs attribute: wago_led_cmd * - * Called by the LED core whenever userspace writes to: - * /sys/class/leds/m4-led/brightness (master) - * /sys/class/leds/m4-led/multi_intensity (R G B) + * Allows sending raw ASCII commands to the M4 directly from the shell. + * The command string is forwarded as-is via RPMsg (fire-and-forget). * - * Registered as brightness_set (not brightness_set_blocking) because we no - * longer wait for an ACK. The LED core may call this from any context. + * Usage: + * echo "CMD-IDL" > /sys/bus/platform/devices/leds-m4/wago_led_cmd + * echo "CMD-CYC-50-128" > /sys/bus/platform/devices/leds-m4/wago_led_cmd + * echo "CMD-BLK-0-R-200-255" > /sys/bus/platform/devices/leds-m4/wago_led_cmd + * echo "CMD-FAD-0-G-20-5" > /sys/bus/platform/devices/leds-m4/wago_led_cmd + * echo "CMD-WLED-3-B-128" > /sys/bus/platform/devices/leds-m4/wago_led_cmd * ---------------------------------------------------------------------- */ +static ssize_t wago_led_cmd_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct wago_m4_led_priv *priv = dev_get_drvdata(dev); + char cmd[WAGO_CMD_MAX_LEN]; + size_t len; + int ret; + + /* Strip trailing newline added by echo and copy into local buffer */ + len = min(count, sizeof(cmd) - 2); + memcpy(cmd, buf, len); + + /* Remove trailing whitespace / newline */ + while (len > 0 && (cmd[len - 1] == '\n' || + cmd[len - 1] == '\r' || + cmd[len - 1] == ' ')) + len--; + + /* Re-add a single newline — the Zephyr app expects it */ + cmd[len++] = '\n'; + cmd[len] = '\0'; + + mutex_lock(&priv->send_lock); + ret = wago_send_cmd(priv, cmd); + mutex_unlock(&priv->send_lock); + + return ret ? ret : count; +} + +static ssize_t wago_led_cmd_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wago_m4_led_priv *priv = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%s\n", + priv->rpdev ? "online" : "offline"); +} + +static DEVICE_ATTR_RW(wago_led_cmd); + +static struct attribute *wago_led_attrs[] = { + &dev_attr_wago_led_cmd.attr, + NULL, +}; + +static const struct attribute_group wago_led_attr_group = { + .attrs = wago_led_attrs, +}; + + + static void wago_led_set(struct led_classdev *led_cdev, enum led_brightness brightness) { @@ -549,6 +608,14 @@ static int wago_m4_led_probe(struct platform_device *pdev) return ret; } + ret = sysfs_create_group(&pdev->dev.kobj, &wago_led_attr_group); + if (ret) { + dev_err(&pdev->dev, + "Failed to create sysfs group: %d\n", ret); + unregister_rpmsg_driver(&wago_rpmsg_driver); + return ret; + } + /* * Schedule the boot work. The first attempt fires after * WAGO_BOOT_INITIAL_DELAY_MS, giving the rootfs a head start. @@ -572,6 +639,7 @@ static int wago_m4_led_remove(struct platform_device *pdev) /* Cancel any pending boot retry before tearing down */ cancel_delayed_work_sync(&priv->boot_work); + sysfs_remove_group(&pdev->dev.kobj, &wago_led_attr_group); unregister_rpmsg_driver(&wago_rpmsg_driver); wago_rproc_stop(priv);