Compare commits

...

13 Commits

Author SHA1 Message Date
Heinrich Toews ea18e83e94 arm64: dts: pfc-750-8xxx: fix M4F DMA region overlap with IPC shmem
The mcu_m4fss_dma_memory_region (0x9cb00000, size 0x100000) overlapped
with uboot_ipc_shm (0x9cbff000, size 0x1000): the IPC window occupies
exactly the last 4 KiB of the DMA region, triggering:

  OF: reserved mem: OVERLAP DETECTED!
    m4f-dma-memory@9cb00000 overlaps with uboot-ipc-shm@9cbff000

Shrink the DMA region size from 0x100000 to 0xff000, leaving the
last 4 KiB (0x9cbff000–0x9cc00000) exclusively for the U-Boot IPC
sidecar shared-memory window.

Reported-by: Oleg Karfich <oleg.karfich@wago.com>
Signed-off-by: Heinrich Toews <ht@twx-software.de>
2026-06-18 18:13:10 +02:00
Heinrich Toews 869a9bb515 leds: rgb: wago-m4: switch to brightness_set_blocking
wago_led_set() acquires send_lock (a mutex) and is therefore a
sleeping function. Assigning it to brightness_set violates the LED
core API contract: brightness_set is called directly from atomic
context (timer/softirq) and must not sleep. On non-RT kernels this
would trigger BUG: scheduling while atomic.

Use brightness_set_blocking instead. The LED core automatically
defers calls through a workqueue, making the sleeping path safe.
Update the function signature to return int and add return 0 as
required by the blocking callback prototype.

Reported-by: Oleg Karfich <oleg.karfich@wago.com>
Signed-off-by: Heinrich Toews <ht@twx-software.de>
2026-06-18 18:13:10 +02:00
Heinrich Toews 7766560288 leds: rgb: wago-m4: bound firmware filesystem polling retries
The -ENOENT branch in wago_boot_work() rescheduled itself indefinitely
without incrementing any counter or checking any limit. If the firmware
file is permanently absent (wrong path, missing rootfs package), the
driver would poll at 5-second intervals forever and never fail cleanly.

The existing boot_retries counter is not suitable here: it tracks rproc
handle acquisition and may already be non-zero by the time the firmware
load is attempted, making the remaining budget for filesystem polling
non-deterministic.

Introduce a dedicated fs_retries counter and WAGO_BOOT_FS_MAX_RETRIES
limit (3 attempts x 5 s = ~15 s) for the filesystem polling phase,
keeping it independent from the rproc acquisition budget tracked by
boot_retries. On exhaustion the -ENOENT path falls through to the
existing fatal: label to release the rproc handle and stop retrying.

Also fix a stale design-overview comment that named WAGO_BOOT_RETRY_MS
as the -ENOENT retry interval; the correct constant is
WAGO_BOOT_INITIAL_DELAY_MS.

Reported-by: Oleg Karfich <oleg.karfich@wago.com>
Signed-off-by: Heinrich Toews <ht@twx-software.de>
2026-06-18 18:13:10 +02:00
Heinrich Toews e7771f5d84 leds: rgb: wago-m4: fix rpdev NULL-ptr race in rpmsg_remove
wago_rpmsg_remove() cleared priv->rpdev without holding send_lock.
wago_send() checks priv->rpdev under send_lock, but a concurrent
remove could null the pointer between the check and the subsequent
rpmsg_trysend(priv->rpdev->ept, ...) dereference, causing a NULL
pointer fault.

Hold send_lock around the priv->rpdev = NULL assignment so that any
in-flight wago_send() either sees a valid pointer for its entire
critical section or sees NULL from the outset.

Reported-by: Oleg Karfich <oleg.karfich@wago.com>
Signed-off-by: Heinrich Toews <ht@twx-software.de>
2026-06-18 18:13:10 +02:00
Heinrich Toews fede4fbf28 tools: leds: add m4-led-perf-test.sh
Bash/sh script (BusyBox compatible) to stress-test the WAGO M4 RGB LED
strip driver by activating kernel LED triggers on all ten strip LEDs
(sys, run, io, em, u1-u6).

Three trigger modes selectable via --trigger=<mode>:

  pattern   (default)
    Phase 1 - FADE:       staggered smooth brightness ramp (200 ms
              offset per LED) using the pattern trigger
    Phase 2 - FAST FLASH: 50 ms on/off in distinct colours per LED
    Phase 3 - SLOW FLASH: 400 ms on/off with colour cycling

  timer
    Simple on/off blink with staggered delay_on/delay_off values
    (50 ms .. 500 ms) and per-LED colours.

  heartbeat
    Kernel heartbeat trigger, all LEDs white.

Additional options:
  --duration=<sec>   Total test duration in seconds (default: 30)
  --version          Print version string
  --help             Print usage

Cleanup handler (trap INT/TERM) restores trigger=none + brightness=0
on all LEDs when the test ends or is interrupted.

Signed-off-by: Heinrich Toews <ht@twx-software.de>
2026-06-18 18:13:10 +02:00
Heinrich Toews f54cd18f2f arm64: defconfig: enable DYNAMIC_DEBUG for am6xxx
Tracing complex interactions between the application and remote
processors (e.g. RPMsg channels, state synchronizations) requires
comprehensive log granularity without flooding the console by default.

CONFIG_DYNAMIC_DEBUG: Allows selectively enabling or disabling dev_dbg()
  and pr_debug() messages at runtime via the debugfs control file.
  This ensures low-overhead operations normally while permitting in-depth
  event tracing when analyzing boot delays or firmware communication.

Signed-off-by: Heinrich Toews <ht@twx-software.de>
2026-06-18 18:13:10 +02:00
Heinrich Toews be90ea6381 arm64: defconfig: enable WAGO M4 RGB LED wrapper support
Operating the RGB LED strips on WAGO PFC AM6xxx-based platforms requires
the wrapper driver alongside specific LED subsystem features to properly
drive colors and sequences.

CONFIG_LEDS_CLASS_MULTICOLOR: Provides the multicolor LED class
  framework, essential to represent the aggregated RGB channels.
CONFIG_LEDS_WAGO_M4_WRAPPER: Enables the dedicated driver translating
  standard LED commands to RPMsg messages for the M4 firmware.
CONFIG_LEDS_WAGO_M4_WRAPPER_SYSFS_PASSTHROUGH: Serves as a back-door to
  the M4 to execute predefined commands directly, bypassing the Linux LED
  subsystem.
CONFIG_LEDS_TRIGGER_PATTERN: Provides the kernel framework to parse
  and execute complex timing sequences (patterns) for the LEDs.

Signed-off-by: Heinrich Toews <ht@twx-software.de>
2026-06-18 18:13:10 +02:00
Heinrich Toews 6373fa5baf arm64: dts: ti: k3-am623-pfc-750-8400: add Rev.3 support via DT overlay-boot
The PFC400-750-8400 exists in two hardware revisions, each consisting
of several sub-PCBs identified by a Leiterplattenkennung (LPK). Rev.2
(LPK p0003xxx) was supported by a monolithic DTS. Rev.3 (LPK p0004xxx)
introduces new sub-PCB variants that cannot be described alongside
Rev.2 in a single static DTS.

Overlay-boot is introduced to support both revisions simultaneously.
U-Boot reads the LPK from each sub-PCB's ID EEPROM at boot and applies
the matching set of DTBOs to the shared base DTB.

Base DTS restructured:
- Strip k3-am623-pfc-750-8400.dts to the minimal shared base; remove
  direct includes of wosm, baseboard, kbus and interaction dtsis.
- Extract TPS65219 PMIC, 4 GiB memory map, voltage regulators and SD
  card voltage-selector GPIO into new k3-am623-pfc-750-84xx-wosm-base.dtsi,
  shared across both revisions.
- Correct buck1 VDD_CORE min-microvolt from 750 mV to 850 mV, per TI
  recommendation for stable CPU frequency scaling. The PMIC NVM was
  updated accordingly on Rev.3 hardware.
- Enable DTC symbol export (-@) for the 750-8400 DTB so overlays can
  resolve labeled references at runtime.
- Export a stable leds_m4 symbol on the M4 LED wrapper node and mark
  it status = disabled; defer LED child nodes and activation entirely
  to the interaction overlay, establishing a clean overlay interface.
- Add fitImage.its packaging the base DTB and all nine DTBOs into a
  U-Boot FIT image for overlay-boot.

DTS overlay compatibility fixes applied to shared dtsis:
- Replace bare `/ {` with `&{/}` in kbus, interaction and wosm dtsis.
- Add explicit #address-cells/#size-cells to &main_i2c1, &mcu_spi0,
  &main_spi0 and &ospi0 fragments to prevent DTC standalone warnings.
- Move rts-gpios from k3-am623-pfc-rs485.dtsi to the board-level
  k3-am623-pfc-750-8xxx-common.dtsi.
- Strip shared interaction dtsi to truly common i2c1 peripherals only
  (HW-ID EEPROM, ST25DV NFC tag); PCA9552 drivers, their reset-pin
  pinctrl and all leds-group-multicolor RGB groups were incorrectly
  shared and are moved exclusively into the p0003965 overlay.

Nine hardware-variant overlays across four subsystems:

WOSM:
- p0004037 (Rev.2): baseline WOSM, no additions.
- p0004263 (Rev.3): adds MRAM reset GPIO, dual EM-module reset GPIOs,
  second RS485 (UART3) pinctrl and CAN (MCAN0) pinctrl.

Base board:
- p0003964 (Rev.2): restores microchip,single-led-mode on KSZ9477.
- p0004305 (Rev.3): corrects KSZ9477 Ethernet port labels to reflect
  changed port-to-PHY routing on Rev.3 PCB (ethX1<->ethX3,
  ethEM<->ethX4). baseboard.dtsi retains Rev.2 labels as default.
- p0004224: overrides UART4 RS485 pinctrl and GPIO RTS for boards
  with a relocated RS485 transceiver.

Local bus:
- p0003978 (Rev.2): baseline K-Bus / OMS configuration.
- p0004067 (Rev.3): routes RUN/STOP switches through dedicated MCU
  GPIOs; disables the legacy combined oms_stop_run node.

Interaction board:
- p0003965 (Rev.2): owns the full PCA9552 LED subsystem — reset-pin
  pinctrl, led_bar60/61 drivers and ten leds-group-multicolor RGB
  groups.
- p0004065 (Rev.3): activates the M4 firmware LED wrapper (&leds_m4)
  and defines all eight LED child nodes.

Signed-off-by: Heinrich Toews <ht@twx-software.de>
2026-06-18 18:13:10 +02:00
Heinrich Toews db380cc82d arm64: dts: ti: k3-am623-pfc: fix OPP voltage for AM623 PFC 750-8xxx
Correct the operating performance point voltages from 750mV to
850mV across all frequency points. The previous 750mV setting was
insufficient for stable operation at the defined frequencies.

Signed-off-by: Heinrich Toews <ht@twx-software.de>
2026-06-18 18:08:43 +02:00
Heinrich Toews 3168361da4 arm64: dts: ti: k3-am623-pfc: disable M4F auto-boot
The M4F remote processor lifecycle requires explicit management by the
kernel remoteproc framework to ensure proper synchronization with Linux
drivers. Automatic booting bypasses necessary initialization sequences
and prevents controlled RPMsg channel establishment.

Signed-off-by: Heinrich Toews <ht@twx-software.de>
2026-06-18 18:08:43 +02:00
Heinrich Toews 0fc48cf1ad arm64: dts: ti: k3-am62x-pfc: reserve U-Boot IPC sidecar memory
U-Boot establishes an IPC sidecar mechanism utilizing shared memory
regions that must remain untouched by the kernel. Unrestricted access
corrupts the pre-established communication channels.

Signed-off-by: Heinrich Toews <ht@twx-software.de>
2026-06-18 18:08:43 +02:00
Heinrich Toews a938aad692 mtd: spi-nor: core: Restrict blind MRAM reset to mram nodes
The spi_nor_everspin_reboot_fix() was executing unconditionally for any
SPI NOR device lacking a dedicated reset GPIO. This caused unintended
8-8-8 soft reset sequences and an unnecessary boot delay for unrelated
flashes.

While the new WOSM p0004263 hardware revision routes the MRAM nRST to
a GPIO, older revisions (e.g. p0000042) still rely on nPORz and therefore
need this fallback to escape Octal-STR mode during a warm reboot.

Limit the blind reset strictly to nodes named "mram" to avoid breaking
standard SPI NOR flash initializations.

Signed-off-by: Heinrich Toews <ht@twx-software.de>
2026-06-18 18:08:43 +02:00
Heinrich Toews e9cdbb93f3 leds: rgb: add WAGO M4 RGB LED wrapper driver
The WAGO PFC controllers use a dedicated M4 remote processor to manage
RGB LED strips. Communication with the M4 core is handled via a binary
RPMsg protocol. A specialized wrapper is required to translate standard
Linux LED class and pattern trigger events into RPMsg commands understood
by the remote processor firmware.

Support includes device tree based LED naming, initial default intensity
configuration, and dynamic RGB pattern triggers. The driver registers
LED devices at probe time for immediate trigger availability and polls
the remote processor to minimize boot delays. An optional sysfs
passthrough mechanism allows direct command execution for advanced
diagnostics.

Signed-off-by: Heinrich Toews <ht@twx-software.de>
2026-06-18 18:08:43 +02:00
29 changed files with 2529 additions and 471 deletions
@@ -0,0 +1,62 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/leds/wago,m4-led-wrapper.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: WAGO M4 RGB LED Strip Wrapper
maintainers:
- WAGO GmbH & Co. KG <linux@wago.com>
description: |
Driver for a WS2812 RGB LED strip (10 LEDs) controlled by the TI AM62x M4
coprocessor running the Zephyr wago-led-server-app.
The driver communicates with the M4 via the RPMsg "wago-led" endpoint using
a binary protocol of packed byte frames (command ID 0x010x04 defined in
wago-m4-led-protocol.h). Each LED is exposed as a Linux LED multicolor (RGB)
device. ASCII command injection is available only through the optional sysfs
passthrough (CONFIG_LEDS_WAGO_M4_WRAPPER_SYSFS_PASSTHROUGH).
The M4 firmware may either be pre-loaded by the bootloader — in which case
the driver attaches to the already-running core via remoteproc — or it may be
loaded by the driver itself from /lib/firmware.
properties:
compatible:
const: wago,m4-led-wrapper
remoteproc:
$ref: /schemas/types.yaml#/definitions/phandle
description:
Phandle to the remoteproc node that manages the M4 coprocessor.
firmware-name:
$ref: /schemas/types.yaml#/definitions/string
description: |
File name of the M4 ELF firmware image, looked up in /lib/firmware.
Only required when the bootloader has not already started the M4.
default: "wago-led-server.elf"
wago,reset-on-init:
type: boolean
description: |
If present, the driver will reset (turn off) all LEDs when the RPMsg
channel is first established. This is useful for clearing any colors
inherited from the bootloader.
required:
- compatible
- remoteproc
additionalProperties: false
examples:
- |
/* Board .dts snippet */
leds-m4 {
compatible = "wago,m4-led-wrapper";
remoteproc = <&mcu_m4fss>;
firmware-name = "wago-led-server.elf";
};
+10
View File
@@ -27,6 +27,15 @@ dtb-$(CONFIG_ARCH_K3) += k3-am623-pfc-750-8302.dtb
dtb-$(CONFIG_ARCH_K3) += k3-am623-pfc-750-830x-ems.dtb
dtb-$(CONFIG_ARCH_K3) += k3-am623-pfc-750-8400.dtb
dtb-$(CONFIG_ARCH_K3) += k3-am623-spehvac.dtb
dtb-$(CONFIG_ARCH_K3) += overlay-750-8400-wosm-p0004037.dtbo \
overlay-750-8400-wosm-p0004263.dtbo \
overlay-750-8400-base-p0003964.dtbo \
overlay-750-8400-base-p0004305.dtbo \
overlay-750-8400-base-p0004224.dtbo \
overlay-750-8400-localbus-p0003978.dtbo \
overlay-750-8400-localbus-p0004067.dtbo \
overlay-750-8400-interaction-p0003965.dtbo \
overlay-750-8400-interaction-p0004065.dtbo
# Boards with AM62Ax SoC
dtb-$(CONFIG_ARCH_K3) += k3-am62a7-sk.dtb
@@ -87,3 +96,4 @@ DTC_FLAGS_k3-am6548-iot2050-advanced-m2 += -@
DTC_FLAGS_k3-j721e-common-proc-board += -@
DTC_FLAGS_k3-j721s2-common-proc-board += -@
DTC_FLAGS_k3-am623-pfc-750-8302 += -@
DTC_FLAGS_k3-am623-pfc-750-8400 += -@
@@ -0,0 +1,166 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/ {
description = "WAGO AM623 PFC FIT Image";
#address-cells = <1>;
images {
kernel {
description = "Linux Kernel";
data = /incbin/("../../Image");
type = "kernel";
arch = "arm64";
os = "linux";
compression = "none";
load = <0x80080000>;
entry = <0x80080000>;
hash-1 {
algo = "sha256";
};
};
fdt-PFC-750-840x {
description = "Flattened Device Tree blob";
data = /incbin/("k3-am623-pfc-750-8400.dtb");
type = "flat_dt";
arch = "arm64";
compression = "none";
load = <0x88000000>;
hash-1 {
algo = "sha256";
};
};
img-overlay-PFC-750-840x-wosm-p0004037 {
description = "WOSM Overlay";
data = /incbin/("overlay-750-8400-wosm-p0004037.dtbo");
type = "flat_dt";
arch = "arm64";
compression = "none";
};
img-overlay-PFC-750-840x-wosm-p0004263 {
description = "WOSM Overlay p0004263";
data = /incbin/("overlay-750-8400-wosm-p0004263.dtbo");
type = "flat_dt";
arch = "arm64";
compression = "none";
};
img-overlay-PFC-750-840x-base-p0003964 {
description = "Base Overlay";
data = /incbin/("overlay-750-8400-base-p0003964.dtbo");
type = "flat_dt";
arch = "arm64";
compression = "none";
};
img-overlay-PFC-750-840x-base-p0004224 {
description = "Base Overlay p0004224";
data = /incbin/("overlay-750-8400-base-p0004224.dtbo");
type = "flat_dt";
arch = "arm64";
compression = "none";
};
img-overlay-PFC-750-840x-base-p0004305 {
description = "Base Overlay p0004305";
data = /incbin/("overlay-750-8400-base-p0004305.dtbo");
type = "flat_dt";
arch = "arm64";
compression = "none";
};
img-overlay-PFC-750-840x-localbus-p0003978 {
description = "Localbus Overlay";
data = /incbin/("overlay-750-8400-localbus-p0003978.dtbo");
type = "flat_dt";
arch = "arm64";
compression = "none";
};
img-overlay-PFC-750-840x-localbus-p0004067 {
description = "Localbus Overlay p0004067";
data = /incbin/("overlay-750-8400-localbus-p0004067.dtbo");
type = "flat_dt";
arch = "arm64";
compression = "none";
};
img-overlay-PFC-750-840x-interaction-p0003965 {
description = "Interaction Overlay";
data = /incbin/("overlay-750-8400-interaction-p0003965.dtbo");
type = "flat_dt";
arch = "arm64";
compression = "none";
};
img-overlay-PFC-750-840x-interaction-p0004065 {
description = "Interaction Overlay p0004065";
data = /incbin/("overlay-750-8400-interaction-p0004065.dtbo");
type = "flat_dt";
arch = "arm64";
compression = "none";
};
};
configurations {
default = "conf-PFC-750-840x";
conf-PFC-750-840x {
description = "Boot Linux kernel with base FDT and overlays";
kernel = "kernel";
fdt = "fdt-PFC-750-840x";
};
ovl-750-840x-wosm-p0004037 {
description = "WOSM Overlay config";
fdt = "img-overlay-PFC-750-840x-wosm-p0004037";
};
ovl-750-840x-wosm-p0004263 {
description = "WOSM Overlay p0004263 config";
fdt = "img-overlay-PFC-750-840x-wosm-p0004263";
};
ovl-750-840x-base-p0003964 {
description = "Base Overlay config";
fdt = "img-overlay-PFC-750-840x-base-p0003964";
};
ovl-750-840x-base-p0004224 {
description = "Base Overlay p0004224 config";
fdt = "img-overlay-PFC-750-840x-base-p0004224";
};
ovl-750-840x-base-p0004305 {
description = "Base Overlay p0004305 config";
fdt = "img-overlay-PFC-750-840x-base-p0004305";
};
ovl-750-840x-localbus-p0003978 {
description = "Localbus Overlay config";
fdt = "img-overlay-PFC-750-840x-localbus-p0003978";
};
ovl-750-840x-localbus-p0004067 {
description = "Localbus Overlay p0004067 config";
fdt = "img-overlay-PFC-750-840x-localbus-p0004067";
};
ovl-750-840x-interaction-p0003965 {
description = "Interaction Overlay config";
fdt = "img-overlay-PFC-750-840x-interaction-p0003965";
};
ovl-750-840x-interaction-p0004065 {
description = "Interaction Overlay p0004065 config";
fdt = "img-overlay-PFC-750-840x-interaction-p0004065";
};
};
};
@@ -6,14 +6,29 @@
* Copyright (C) 2025 WAGO GmbH & Co. KG - https://www.wago.com/
*/
#include "k3-am623-pfc-750-84xx-wosm.dtsi"
#include "k3-am623-pfc-750-84xx-baseboard.dtsi"
#include "k3-am623-pfc-750-84xx-kbus.dtsi"
#include "k3-am623-pfc-750-84xx-interaction.dtsi"
/dts-v1/;
#include "k3-am623-pfc-750-8xxx-common.dtsi"
#include "k3-am623-pfc-750-84xx-wosm-base.dtsi"
#include "k3-am623-pfc-tpm.dtsi"
/ {
compatible = "wago,am623-pfc-750_8400-100d", "wago,am623-pfc", "ti,am625";
model = "PFC400-750-8400";
aliases {
usb1 = &usb1;
ethernet0 = &cpsw_port2;
ethernet1 = &cpsw_port1;
};
leds_m4: leds-m4 {
compatible = "wago,m4-led-wrapper";
remoteproc = <&mcu_m4fss>;
firmware-name = "wago-led-server-app.elf";
wago,reset-on-init;
status = "disabled";
};
};
&wsysinit {
@@ -21,6 +36,10 @@
board,variant = "pfc400";
};
&slb9670 {
status = "okay";
};
&main_spi2 {
ti,spi-num-cs = <4>;
@@ -23,6 +23,9 @@
};
&mcu_spi0 {
#address-cells = <1>;
#size-cells = <0>;
ksz9477@0 {
compatible = "microchip,ksz9477";
pinctrl-names = "default";
@@ -37,8 +40,6 @@
interrupt-parent = <&main_gpio0>;
interrupts = <13 IRQ_TYPE_EDGE_FALLING>;
microchip,single-led-mode;
ports {
#address-cells = <1>;
#size-cells = <0>;
@@ -90,6 +91,9 @@
};
&main_i2c1 {
#address-cells = <1>;
#size-cells = <0>;
eeprom_hw_id_bb: at24c02@54 {
status = "disabled";
compatible = "atmel,24c02";
@@ -9,15 +9,10 @@
#include "k3-am623-pfc-rs485.dtsi"
#include "k3-am623-pfc-rtc.dtsi"
&mcu_pmx0 {
led_reset_pins_default: led-reset-pins-default {
pinctrl-single,pins = <
AM62X_MCU_IOPAD(0x050, PIN_OUTPUT | INPUT_EN, 7) /* (A9) WKUP_I2C0_SDA.GPIO0_20 nRST-LED-DISP */
>;
};
};
&main_i2c1 {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
eeprom_hw_id_ib: at24c02@54 {
@@ -27,250 +22,6 @@
pagesize = <16>;
};
/*
* NXP PCA9552BS
* 16-Bit I²C-Bus LED Driver
*/
led_bar60: pca9552@60 {
compatible = "nxp,pca9552";
pinctrl-names = "default";
pinctrl-0 = <&led_reset_pins_default>;
#address-cells = <1>;
#size-cells = <0>;
reg = <0x60>;
lb60_0: sys-red@0 {
label = "sys-red";
reg = <0>;
color = <LED_COLOR_ID_RED>;
default-state = "keep";
linux,default-trigger = "timer";
};
lb60_1: sys-green@1 {
label = "sys-green";
reg = <1>;
color = <LED_COLOR_ID_GREEN>;
default-state = "keep";
linux,default-trigger = "timer";
};
lb60_2: sys-blue@2 {
label = "sys-blue";
reg = <2>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
lb60_3: run-red@3 {
label = "run-red";
reg = <3>;
color = <LED_COLOR_ID_RED>;
default-state = "off";
};
lb60_4: run-green@4 {
label = "run-green";
reg = <4>;
color = <LED_COLOR_ID_GREEN>;
default-state = "off";
};
lb60_5: run-blue@5 {
label = "run-blue";
reg = <5>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
lb60_6: io-red@6 {
label = "io-red";
reg = <6>;
color = <LED_COLOR_ID_RED>;
default-state = "off";
};
lb60_7: io-green@7 {
label = "io-green";
reg = <7>;
color = <LED_COLOR_ID_GREEN>;
default-state = "off";
};
lb60_8: io-blue@8 {
label = "io-blue";
reg = <8>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
lb60_9: em-red@9 {
label = "em-red";
reg = <9>;
color = <LED_COLOR_ID_RED>;
default-state = "off";
};
lb60_10: em-green@10 {
label = "em-green";
reg = <10>;
color = <LED_COLOR_ID_GREEN>;
default-state = "off";
};
lb60_11: em-blue@11 {
label = "em-blue";
reg = <11>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
lb60_12: u1-red@12 {
label = "u1-red";
reg = <12>;
color = <LED_COLOR_ID_RED>;
default-state = "off";
};
lb60_13: u1-green@13 {
label = "u1-green";
reg = <13>;
color = <LED_COLOR_ID_GREEN>;
default-state = "off";
};
lb60_14: u1-blue@14 {
label = "u1-blue";
reg = <14>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
lb60_15: sys-overwrite@15 {
label = "sys-overwrite";
reg = <15>;
default-state = "on";
};
};
/*
* NXP PCA9552BS
* 16-Bit I²C-Bus LED Driver
*/
led_bar61: pca9552@61 {
compatible = "nxp,pca9552";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x61>;
lb61_0: u2-red@0 {
label = "u2-red";
reg = <0>;
color = <LED_COLOR_ID_RED>;
default-state = "off";
};
lb61_1: u2-green@1 {
label = "u2-green";
reg = <1>;
color = <LED_COLOR_ID_GREEN>;
default-state = "off";
};
lb61_2: u2-blue@2 {
label = "u2-blue";
reg = <2>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
lb61_3: u3-red@3 {
label = "u3-red";
reg = <3>;
color = <LED_COLOR_ID_RED>;
default-state = "off";
};
lb61_4: u3-green@4 {
label = "u3-green";
reg = <4>;
color = <LED_COLOR_ID_GREEN>;
default-state = "off";
};
lb61_5: u3-blue@5 {
label = "u3-blue";
reg = <5>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
lb61_6: u4-red@6 {
label = "u4-red";
reg = <6>;
color = <LED_COLOR_ID_RED>;
default-state = "off";
};
lb61_7: u4-green@7 {
label = "u4-green";
reg = <7>;
color = <LED_COLOR_ID_GREEN>;
default-state = "off";
};
lb61_8: u4-blue@8 {
label = "u4-blue";
reg = <8>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
lb61_9: u5-red@9 {
label = "u5-red";
reg = <9>;
color = <LED_COLOR_ID_RED>;
default-state = "off";
};
lb61_10: u5-green@10 {
label = "u5-green";
reg = <10>;
color = <LED_COLOR_ID_GREEN>;
default-state = "off";
};
lb61_11: u5-blue@11 {
label = "u5-blue";
reg = <11>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
lb61_12: u6_r@12 {
label = "u6-red";
reg = <12>;
color = <LED_COLOR_ID_RED>;
default-state = "off";
};
lb61_13: u6_g@13 {
label = "u6-green";
reg = <13>;
color = <LED_COLOR_ID_GREEN>;
default-state = "off";
};
lb61_14: u6_b@14 {
label = "u6-blue";
reg = <14>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
};
/*
* Dynamic NFC/RFID tag IC with 4-Kbit EEPROM
*
@@ -284,112 +35,4 @@
compatible = "st25dv,st25dv04k";
reg = <0x53>;
};
};
/*
* Multicolor LED groups for PFC400 (RGB)
* Group triplets from PCA9552 into multicolor LEDs to enable
* led-class-multicolor and pattern trigger RGB support.
*/
/ {
sys_led: sys-led {
compatible = "leds-group-multicolor";
label = "sys";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "keep";
mc-allow-subled-writes;
leds = <&lb60_0 &lb60_1 &lb60_2>;
max-brightness = <255>;
};
run_led: run-led {
compatible = "leds-group-multicolor";
label = "run";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "off";
leds = <&lb60_3 &lb60_4 &lb60_5>;
max-brightness = <255>;
};
io_led: io-led {
compatible = "leds-group-multicolor";
label = "io";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "off";
leds = <&lb60_6 &lb60_7 &lb60_8>;
max-brightness = <255>;
};
em_led: em-led {
compatible = "leds-group-multicolor";
label = "em";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "off";
leds = <&lb60_9 &lb60_10 &lb60_11>;
max-brightness = <255>;
};
u1_led: u1-led {
compatible = "leds-group-multicolor";
label = "u1";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "off";
leds = <&lb60_12 &lb60_13 &lb60_14>;
max-brightness = <255>;
};
u2_led: u2-led {
compatible = "leds-group-multicolor";
label = "u2";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "off";
leds = <&lb61_0 &lb61_1 &lb61_2>;
max-brightness = <255>;
};
u3_led: u3-led {
compatible = "leds-group-multicolor";
label = "u3";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "off";
leds = <&lb61_3 &lb61_4 &lb61_5>;
max-brightness = <255>;
};
u4_led: u4-led {
compatible = "leds-group-multicolor";
label = "u4";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "off";
leds = <&lb61_6 &lb61_7 &lb61_8>;
max-brightness = <255>;
};
u5_led: u5-led {
compatible = "leds-group-multicolor";
label = "u5";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "off";
leds = <&lb61_9 &lb61_10 &lb61_11>;
max-brightness = <255>;
};
u6_led: u6-led {
compatible = "leds-group-multicolor";
label = "u6";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "off";
leds = <&lb61_12 &lb61_13 &lb61_14>;
max-brightness = <255>;
};
};
@@ -8,7 +8,7 @@
#include "k3-am623-pfc-kbus.dtsi"
/ {
&{/} {
/* this name of the gpio-keys device is a
* historical heritage from 3.6.11 kernel.
* the device-name is checked in omsd. So we
@@ -71,6 +71,9 @@
};
&main_i2c1 {
#address-cells = <1>;
#size-cells = <0>;
eeprom_hw_id_lb: at24c02@50 {
status = "disabled";
compatible = "atmel,24c02";
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: GPL-2.0
/ {
compatible = "ti,am623-pfc", "ti,am625";
memory@80000000 {
device_type = "memory";
/* 4 GiB RAM */
reg = <0x00000000 0x80000000 0x00000000 0x80000000>,
<0x00000008 0x80000000 0x00000000 0x80000000>;
};
vcc_1v8_sys: regulator-4 {
compatible = "regulator-fixed";
regulator-name = "vcc_1v8_sys";
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <1800000>;
vin-supply = <&vmain_pd>;
regulator-always-on;
regulator-boot-on;
};
ldo1_sd_1v8_reg: ldo1-sd-1v8-reg {
/* Output VLDO1 of tps65219 */
compatible = "regulator-gpio";
regulator-name = "VDDSHV_SD_1V8_IO_PMIC_GPIO";
pinctrl-names = "default";
pinctrl-0 = <&mcu_sd_vsel_pin>;
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <3300000>;
regulator-boot-on;
vin-supply = <&vcc_5v0>;
gpios = <&mcu_gpio0 10 GPIO_ACTIVE_HIGH>;
states = <1800000 0x0>,
<3300000 0x1>;
};
};
&mcu_pmx0 {
mcu_sd_vsel_pin: mcu-sd-vsel-pins {
pinctrl-single,pins = <
AM64X_MCU_IOPAD(0x028, PIN_OUTPUT, 7) /* (C5) WKUP_UART0_TXD.MCU_GPIO0_10 */
>;
};
};
&cpu0 {
cpu-supply = <&buck1_30_reg>;
};
&cpu1 {
cpu-supply = <&buck1_30_reg>;
};
&cpu2 {
cpu-supply = <&buck1_30_reg>;
};
&cpu3 {
cpu-supply = <&buck1_30_reg>;
};
&main_i2c0 {
tps65219_30: pmic@30 {
compatible = "ti,tps65219";
reg = <0x30>;
system-power-controller;
buck1-supply = <&vcc_5v0>;
buck2-supply = <&vcc_5v0>;
buck3-supply = <&vcc_5v0>;
ldo1-supply = <&vcc_3v3_sys>;
ldo2-supply = <&vcc_1v8_sys>;
ldo3-supply = <&vcc_3v3_sys>;
ldo4-supply = <&vcc_3v3_sys>;
pinctrl-names = "default";
pinctrl-0 = <&pmic_irq_pins_default>;
interrupt-parent = <&main_gpio0>;
interrupts = <68 IRQ_TYPE_EDGE_FALLING>;
/* NOTICE:
*
* We could use "tps65219-gpio" here with GPO1 (EN-DCDC-1V8)
* and GPO2 (CARRIER_PWR_EN for SD-Card and I2C1 devices) enabled,
* but will skip it for now and trust the bootloader or TPS-NVM for the
* correct settings correctly.
*
* For now we declare vcc_1v8_sys as being fixed.
*
*/
regulators {
buck1_30_reg: buck1 {
regulator-name = "VDD_CORE";
regulator-min-microvolt = <850000>;
regulator-max-microvolt = <850000>;
regulator-boot-on;
regulator-always-on;
};
buck2_30_reg: buck2 {
regulator-name = "VCC3V3";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-boot-on;
regulator-always-on;
};
buck3_30_reg: buck3 {
regulator-name = "VDD_LPDDR4";
regulator-min-microvolt = <1200000>;
regulator-max-microvolt = <1200000>;
regulator-boot-on;
regulator-always-on;
};
ldo2_30_reg: ldo2 {
regulator-name = "VDDAR_CORE";
regulator-min-microvolt = <850000>;
regulator-max-microvolt = <850000>;
regulator-boot-on;
regulator-always-on;
};
ldo3_30_reg: ldo3 {
regulator-name = "VDDA_1V8";
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <1800000>;
regulator-boot-on;
regulator-always-on;
};
ldo4_30_reg: ldo4 {
regulator-name = "VDDA_2V5";
regulator-min-microvolt = <2500000>;
regulator-max-microvolt = <2500000>;
regulator-boot-on;
regulator-always-on;
};
};
};
};
@@ -6,17 +6,12 @@
* Copyright (C) 2025 WAGO GmbH & Co. KG - https://www.wago.com/
*/
#include "k3-am623-pfc-750-8xxx-common.dtsi"
#include "k3-am623-pfc-tpm.dtsi"
/ {
&{/} {
compatible = "ti,am623-pfc", "ti,am625";
aliases {
usb1 = &usb1;
ethernet0 = &cpsw_port2;
ethernet1 = &cpsw_port1;
};
#address-cells = <2>;
#size-cells = <2>;
cpus {
cpu@0 {
@@ -71,6 +66,9 @@
};
&fss {
#address-cells = <2>;
#size-cells = <2>;
uio_mram: uio@500000000 {
compatible = "uio_pdrv_genirq";
linux,uio-name = "UIO_OSPI_MRAM";
@@ -93,12 +91,6 @@
AM64X_MCU_IOPAD(0x010, PIN_OUTPUT, 0) /* (C9) MCU_SPI0_D1 MOSI */
>;
};
mcu_sd_vsel_pin: mcu-sd-vsel-pins {
pinctrl-single,pins = <
AM64X_MCU_IOPAD(0x028, PIN_OUTPUT, 7) /* (C5) WKUP_UART0_TXD.MCU_GPIO0_10 */
>;
};
};
&main_pmx0 {
@@ -190,6 +182,8 @@
};
&ospi0 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "ti,am654-ospi";
pinctrl-names = "default";
pinctrl-0 = <&ospi0_pins_default>;
@@ -242,6 +236,8 @@
};
&main_i2c0 {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
eeprom_hw_id_wosm: at24c02@50 {
@@ -250,86 +246,6 @@
reg = <0x50>;
pagesize = <16>;
};
tps65219_30: pmic@30 {
compatible = "ti,tps65219";
reg = <0x30>;
system-power-controller;
buck1-supply = <&vcc_5v0>;
buck2-supply = <&vcc_5v0>;
buck3-supply = <&vcc_5v0>;
ldo1-supply = <&vcc_3v3_sys>;
ldo2-supply = <&vcc_1v8_sys>;
ldo3-supply = <&vcc_3v3_sys>;
ldo4-supply = <&vcc_3v3_sys>;
pinctrl-names = "default";
pinctrl-0 = <&pmic_irq_pins_default>;
interrupt-parent = <&main_gpio0>;
interrupts = <68 IRQ_TYPE_EDGE_FALLING>;
/* NOTICE:
*
* We could use "tps65219-gpio" here with GPO1 (EN-DCDC-1V8)
* and GPO2 (CARRIER_PWR_EN for SD-Card and I2C1 devices) enabled,
* but will skip it for now and trust the bootloader or TPS-NVM for the
* correct settings correctly.
*
* For now we declare vcc_1v8_sys as being fixed.
*
*/
regulators {
buck1_30_reg: buck1 {
regulator-name = "VDD_CORE";
regulator-min-microvolt = <750000>;
regulator-max-microvolt = <850000>;
regulator-boot-on;
regulator-always-on;
};
buck2_30_reg: buck2 {
regulator-name = "VCC3V3";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-boot-on;
regulator-always-on;
};
buck3_30_reg: buck3 {
regulator-name = "VDD_LPDDR4";
regulator-min-microvolt = <1200000>;
regulator-max-microvolt = <1200000>;
regulator-boot-on;
regulator-always-on;
};
ldo2_30_reg: ldo2 {
regulator-name = "VDDAR_CORE";
regulator-min-microvolt = <850000>;
regulator-max-microvolt = <850000>;
regulator-boot-on;
regulator-always-on;
};
ldo3_30_reg: ldo3 {
regulator-name = "VDDA_1V8";
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <1800000>;
regulator-boot-on;
regulator-always-on;
};
ldo4_30_reg: ldo4 {
regulator-name = "VDDA_2V5";
regulator-min-microvolt = <2500000>;
regulator-max-microvolt = <2500000>;
regulator-boot-on;
regulator-always-on;
};
};
};
};
&main_i2c1 {
@@ -28,27 +28,27 @@
opp-table {
opp-200000000 {
opp-microvolt = <750000>;
opp-microvolt = <850000>;
};
opp-400000000 {
opp-microvolt = <750000>;
opp-microvolt = <850000>;
};
opp-600000000 {
opp-microvolt = <750000>;
opp-microvolt = <850000>;
};
opp-800000000 {
opp-microvolt = <750000>;
opp-microvolt = <850000>;
};
opp-1000000000 {
opp-microvolt = <750000>;
opp-microvolt = <850000>;
};
opp-1250000000 {
opp-microvolt = <750000>;
opp-microvolt = <850000>;
};
opp-1400000000 {
@@ -110,9 +110,20 @@
no-map;
};
/*
* U-Boot IPC side-door shared memory.
* Written by U-Boot (A53) to send LED commands to the M4.
* Must not be touched by the Linux kernel.
* See UBOOT_IPC_SHMEM_PHYS in wago_m4_led_protocol.h.
*/
uboot_ipc_shm: uboot-ipc-shm@9cbff000 {
reg = <0x00 0x9cbff000 0x00 0x1000>;
no-map;
};
mcu_m4fss_dma_memory_region: m4f-dma-memory@9cb00000 {
compatible = "shared-dma-pool";
reg = <0x00 0x9cb00000 0x00 0x100000>;
reg = <0x00 0x9cb00000 0x00 0xff000>;
no-map;
};
@@ -275,6 +286,7 @@
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&main_uart4_pins_default>;
rts-gpios = <&main_gpio0 38 GPIO_ACTIVE_HIGH>;
};
&main_i2c0 { /* type label */
@@ -379,5 +391,6 @@
mboxes = <&mailbox0_cluster0 &mbox_m4_0>;
memory-region = <&mcu_m4fss_dma_memory_region>,
<&mcu_m4fss_memory_region>;
ti,no-auto-boot;
status = "okay";
};
@@ -20,6 +20,9 @@
};
&main_spi0 {
#address-cells = <1>;
#size-cells = <0>;
spi-rt;
spi-rt-prio = <81>;
@@ -7,6 +7,9 @@
*/
&main_i2c1 {
#address-cells = <1>;
#size-cells = <0>;
/*
* PCA9538, 8-Bit I/O Expander
*/
@@ -29,7 +32,6 @@
};
&main_uart4 { /* RS485 Interface */
rts-gpios = <&main_gpio0 38 GPIO_ACTIVE_HIGH>;
/* RS485 termination is controlled via GPIO userspace tools */
rs485-rts-active-high;
linux,rs485-enabled-at-boot-time;
+4 -1
View File
@@ -7,11 +7,14 @@
*/
&main_i2c1 {
#address-cells = <1>;
#size-cells = <0>;
rtc@52 {
compatible = "microcrystal,rv3028";
reg = <0x52>;
/*
/*
* The backup goldcap is supplied with a higher voltage (+5V)
* then the RTC (+3,3) itself. It requires a special setup
* to avoid hardware failure.
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
#include "k3-pinctrl.h"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/leds/common.h>
#include "k3-am623-pfc-750-84xx-baseboard.dtsi"
&mcu_spi0 {
ksz9477@0 {
microchip,single-led-mode;
};
};
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
#include "k3-pinctrl.h"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/leds/common.h>
#include "k3-am623-pfc-750-84xx-baseboard.dtsi"
&main_pmx0 {
main_uart4_pins_default: main-uart4-default-pins {
pinctrl-single,pins = <
AM62X_IOPAD(0x0b0, PIN_INPUT, 3) /* (K22) UART4_RXD */
AM62X_IOPAD(0x0b4, PIN_OUTPUT, 3) /* (K24) UART4_TXD */
AM62X_IOPAD(0x0098, PIN_OUTPUT_PULLDOWN, 7) /* (U23) GPIO0_37 (RS-DE) */
>;
};
};
&main_uart4 {
pinctrl-0 = <&main_uart4_pins_default>;
rts-gpios = <&main_gpio0 37 GPIO_ACTIVE_HIGH>;
};
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
#include "k3-pinctrl.h"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/leds/common.h>
#include "k3-am623-pfc-750-84xx-baseboard.dtsi"
/*
* Rev.3 hardware has a different KSZ9477 port-to-PHY routing compared
* to Rev.2. Override the port labels accordingly.
*/
&mcu_spi0 {
ksz9477@0 {
ports {
port@0 {
label = "ethX1";
};
port@2 {
label = "ethX3";
};
port@3 {
label = "ethX4";
};
port@4 {
label = "ethEM";
};
};
};
};
@@ -0,0 +1,375 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
#include "k3-pinctrl.h"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/leds/common.h>
#include "k3-am623-pfc-750-84xx-interaction.dtsi"
&mcu_pmx0 {
led_reset_pins_default: led-reset-pins-default {
pinctrl-single,pins = <
AM62X_MCU_IOPAD(0x050, PIN_OUTPUT | INPUT_EN, 7) /* (A9) WKUP_I2C0_SDA.GPIO0_20 nRST-LED-DISP */
>;
};
};
&main_i2c1 {
#address-cells = <1>;
#size-cells = <0>;
/*
* NXP PCA9552BS
* 16-Bit I2C-Bus LED Driver
*/
led_bar60: pca9552@60 {
compatible = "nxp,pca9552";
pinctrl-names = "default";
pinctrl-0 = <&led_reset_pins_default>;
#address-cells = <1>;
#size-cells = <0>;
reg = <0x60>;
lb60_0: sys-red@0 {
label = "sys-red";
reg = <0>;
color = <LED_COLOR_ID_RED>;
default-state = "keep";
linux,default-trigger = "timer";
};
lb60_1: sys-green@1 {
label = "sys-green";
reg = <1>;
color = <LED_COLOR_ID_GREEN>;
default-state = "keep";
linux,default-trigger = "timer";
};
lb60_2: sys-blue@2 {
label = "sys-blue";
reg = <2>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
lb60_3: run-red@3 {
label = "run-red";
reg = <3>;
color = <LED_COLOR_ID_RED>;
default-state = "off";
};
lb60_4: run-green@4 {
label = "run-green";
reg = <4>;
color = <LED_COLOR_ID_GREEN>;
default-state = "off";
};
lb60_5: run-blue@5 {
label = "run-blue";
reg = <5>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
lb60_6: io-red@6 {
label = "io-red";
reg = <6>;
color = <LED_COLOR_ID_RED>;
default-state = "off";
};
lb60_7: io-green@7 {
label = "io-green";
reg = <7>;
color = <LED_COLOR_ID_GREEN>;
default-state = "off";
};
lb60_8: io-blue@8 {
label = "io-blue";
reg = <8>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
lb60_9: em-red@9 {
label = "em-red";
reg = <9>;
color = <LED_COLOR_ID_RED>;
default-state = "off";
};
lb60_10: em-green@10 {
label = "em-green";
reg = <10>;
color = <LED_COLOR_ID_GREEN>;
default-state = "off";
};
lb60_11: em-blue@11 {
label = "em-blue";
reg = <11>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
lb60_12: u1-red@12 {
label = "u1-red";
reg = <12>;
color = <LED_COLOR_ID_RED>;
default-state = "off";
};
lb60_13: u1-green@13 {
label = "u1-green";
reg = <13>;
color = <LED_COLOR_ID_GREEN>;
default-state = "off";
};
lb60_14: u1-blue@14 {
label = "u1-blue";
reg = <14>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
lb60_15: sys-overwrite@15 {
label = "sys-overwrite";
reg = <15>;
default-state = "on";
};
};
/*
* NXP PCA9552BS
* 16-Bit I2C-Bus LED Driver
*/
led_bar61: pca9552@61 {
compatible = "nxp,pca9552";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x61>;
lb61_0: u2-red@0 {
label = "u2-red";
reg = <0>;
color = <LED_COLOR_ID_RED>;
default-state = "off";
};
lb61_1: u2-green@1 {
label = "u2-green";
reg = <1>;
color = <LED_COLOR_ID_GREEN>;
default-state = "off";
};
lb61_2: u2-blue@2 {
label = "u2-blue";
reg = <2>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
lb61_3: u3-red@3 {
label = "u3-red";
reg = <3>;
color = <LED_COLOR_ID_RED>;
default-state = "off";
};
lb61_4: u3-green@4 {
label = "u3-green";
reg = <4>;
color = <LED_COLOR_ID_GREEN>;
default-state = "off";
};
lb61_5: u3-blue@5 {
label = "u3-blue";
reg = <5>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
lb61_6: u4-red@6 {
label = "u4-red";
reg = <6>;
color = <LED_COLOR_ID_RED>;
default-state = "off";
};
lb61_7: u4-green@7 {
label = "u4-green";
reg = <7>;
color = <LED_COLOR_ID_GREEN>;
default-state = "off";
};
lb61_8: u4-blue@8 {
label = "u4-blue";
reg = <8>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
lb61_9: u5-red@9 {
label = "u5-red";
reg = <9>;
color = <LED_COLOR_ID_RED>;
default-state = "off";
};
lb61_10: u5-green@10 {
label = "u5-green";
reg = <10>;
color = <LED_COLOR_ID_GREEN>;
default-state = "off";
};
lb61_11: u5-blue@11 {
label = "u5-blue";
reg = <11>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
lb61_12: u6_r@12 {
label = "u6-red";
reg = <12>;
color = <LED_COLOR_ID_RED>;
default-state = "off";
};
lb61_13: u6_g@13 {
label = "u6-green";
reg = <13>;
color = <LED_COLOR_ID_GREEN>;
default-state = "off";
};
lb61_14: u6_b@14 {
label = "u6-blue";
reg = <14>;
color = <LED_COLOR_ID_BLUE>;
default-state = "off";
};
};
};
/*
* Multicolor LED groups for PFC400 (RGB)
* Group triplets from PCA9552 into multicolor LEDs to enable
* led-class-multicolor and pattern trigger RGB support.
*/
&{/} {
sys_led: sys-led {
compatible = "leds-group-multicolor";
label = "sys";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "keep";
mc-allow-subled-writes;
leds = <&lb60_0 &lb60_1 &lb60_2>;
max-brightness = <255>;
};
run_led: run-led {
compatible = "leds-group-multicolor";
label = "run";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "off";
leds = <&lb60_3 &lb60_4 &lb60_5>;
max-brightness = <255>;
};
io_led: io-led {
compatible = "leds-group-multicolor";
label = "io";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "off";
leds = <&lb60_6 &lb60_7 &lb60_8>;
max-brightness = <255>;
};
em_led: em-led {
compatible = "leds-group-multicolor";
label = "em";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "off";
leds = <&lb60_9 &lb60_10 &lb60_11>;
max-brightness = <255>;
};
u1_led: u1-led {
compatible = "leds-group-multicolor";
label = "u1";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "off";
leds = <&lb60_12 &lb60_13 &lb60_14>;
max-brightness = <255>;
};
u2_led: u2-led {
compatible = "leds-group-multicolor";
label = "u2";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "off";
leds = <&lb61_0 &lb61_1 &lb61_2>;
max-brightness = <255>;
};
u3_led: u3-led {
compatible = "leds-group-multicolor";
label = "u3";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "off";
leds = <&lb61_3 &lb61_4 &lb61_5>;
max-brightness = <255>;
};
u4_led: u4-led {
compatible = "leds-group-multicolor";
label = "u4";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "off";
leds = <&lb61_6 &lb61_7 &lb61_8>;
max-brightness = <255>;
};
u5_led: u5-led {
compatible = "leds-group-multicolor";
label = "u5";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "off";
leds = <&lb61_9 &lb61_10 &lb61_11>;
max-brightness = <255>;
};
u6_led: u6-led {
compatible = "leds-group-multicolor";
label = "u6";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
default-state = "off";
leds = <&lb61_12 &lb61_13 &lb61_14>;
max-brightness = <255>;
};
};
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
#include "k3-pinctrl.h"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/leds/common.h>
#include "k3-am623-pfc-750-84xx-interaction.dtsi"
&leds_m4 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
label = "sys";
linux,default-trigger = "timer";
led-default-intensity = <255 80 0 64>; /* Orange, brightness 64/255 */
};
led@1 {
reg = <1>;
label = "run";
};
led@2 {
reg = <2>;
label = "io";
};
led@3 {
reg = <3>;
label = "em";
};
led@4 {
reg = <4>;
label = "u1";
};
led@5 {
reg = <5>;
label = "u2";
};
led@6 {
reg = <6>;
label = "u3";
};
led@7 {
reg = <7>;
label = "u4";
};
};
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
#include "k3-pinctrl.h"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/leds/common.h>
#include "k3-am623-pfc-750-84xx-kbus.dtsi"
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
#include "k3-pinctrl.h"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/leds/common.h>
#include "k3-am623-pfc-750-84xx-kbus.dtsi"
&mcu_pmx0 {
oms_mcu_pins_default: oms-mcu-default-pins {
pinctrl-single,pins = <
AM62X_MCU_IOPAD(0x0044, PIN_INPUT, 7) /* (A8) MCU_I2C0_SCL.MCU_GPIO0_17 nRUN-BAS */
AM62X_MCU_IOPAD(0x0048, PIN_INPUT, 7) /* (D10) MCU_I2C0_SDA.MCU_GPIO0_18 nSTOP-BAS */
>;
};
};
&oms_pins_default {
pinctrl-single,pins = <
AM62X_IOPAD(0x01a4, PIN_INPUT, 7) /* (B20) MCASP0_ACLKX.GPIO1_11 IO-RAB */
>;
};
&oms {
pinctrl-0 = <&oms_pins_default &oms_mcu_pins_default>;
poll-interval = <100>;
oms_run: run {
label = "RUN";
gpios = <&mcu_gpio0 17 GPIO_ACTIVE_LOW>;
linux,code = <1>;
linux,input-type = <5>;
debounce-interval = <1>;
};
oms_stop: stop {
label = "STOP";
gpios = <&mcu_gpio0 18 GPIO_ACTIVE_LOW>;
linux,code = <2>;
linux,input-type = <5>;
debounce-interval = <1>;
};
};
&oms_stop_run {
status = "disabled";
};
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
#include "k3-pinctrl.h"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/leds/common.h>
#include "k3-am623-pfc-750-84xx-wosm.dtsi"
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
#include "k3-pinctrl.h"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/leds/common.h>
#include "k3-am623-pfc-750-84xx-wosm.dtsi"
&main_pmx0 {
mram_reset_pins_default: mram-reset-default-pins {
pinctrl-single,pins = <
AM62X_IOPAD(0x0004, PIN_OUTPUT, 7) /* (G25) OSPI0_LBCLKO.GPIO0_1 (nRST) */
>;
};
em_reset_pins_default: em-reset-default-pins {
pinctrl-single,pins = <
AM62X_IOPAD(0x01a0, PIN_OUTPUT, 7) /* (E18) MCASP0_AXR0.GPIO1_10 (nRST-EM2) */
AM62X_IOPAD(0x019c, PIN_OUTPUT, 7) /* (B18) MCASP0_AXR1.GPIO1_9 (nRST-EM3) */
>;
};
rs485_2_pins_default: rs485-2-default-pins {
pinctrl-single,pins = <
AM62X_IOPAD(0x0098, PIN_OUTPUT, 7) /* (U23) GPMC0_WAIT0.GPIO0_38 (RS-2.DE) */
AM62X_IOPAD(0x00c0, PIN_INPUT, 4) /* (W25) VOUT0_DATA2.UART3_RXD (RS-2.RXD) */
AM62X_IOPAD(0x00c4, PIN_OUTPUT, 4) /* (W24) VOUT0_DATA3.UART3_TXD (RS-2.TXD) */
>;
};
can_interface_pins_default: can-interface-default-pins {
pinctrl-single,pins = <
AM62X_IOPAD(0x01dc, PIN_INPUT, 0) /* (E15) MCAN0_RX (CAN.RXD) */
AM62X_IOPAD(0x01d8, PIN_OUTPUT, 0) /* (C15) MCAN0_TX (CAN.TXD) */
>;
};
};
&mram {
pinctrl-0 = <&mram_irq_pins_default &mram_reset_pins_default>;
reset-gpios = <&main_gpio0 1 GPIO_ACTIVE_LOW>;
};
+5
View File
@@ -341,6 +341,9 @@ CONFIG_MMC_SDHCI_PLTFM=y
CONFIG_MMC_SDHCI_AM654=y
CONFIG_NEW_LEDS=y
CONFIG_LEDS_CLASS=y
CONFIG_LEDS_CLASS_MULTICOLOR=y
CONFIG_LEDS_WAGO_M4_WRAPPER=y
CONFIG_LEDS_WAGO_M4_WRAPPER_SYSFS_PASSTHROUGH=y
CONFIG_LEDS_GPIO=y
CONFIG_LEDS_PCA955X=y
CONFIG_LEDS_TRIGGERS=y
@@ -348,6 +351,7 @@ CONFIG_LEDS_TRIGGER_TIMER=y
CONFIG_LEDS_TRIGGER_HEARTBEAT=y
CONFIG_LEDS_TRIGGER_DEFAULT_ON=y
CONFIG_LEDS_TRIGGER_PANIC=y
CONFIG_LEDS_TRIGGER_PATTERN=y
CONFIG_EDAC=y
CONFIG_RTC_CLASS=y
CONFIG_RTC_DRV_RV3028=y
@@ -466,6 +470,7 @@ CONFIG_DMA_CMA=y
CONFIG_CMA_SIZE_MBYTES=32
CONFIG_IRQ_POLL=y
CONFIG_PRINTK_TIME=y
CONFIG_DYNAMIC_DEBUG=y
CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y
CONFIG_MAGIC_SYSRQ=y
CONFIG_DEBUG_FS=y
+35
View File
@@ -51,4 +51,39 @@ config LEDS_MT6370_RGB
This driver can also be built as a module. If so, the module
will be called "leds-mt6370-rgb".
config LEDS_WAGO_M4_WRAPPER
tristate "WAGO M4 RGB LED strip wrapper (RPMsg/remoteproc)"
depends on RPMSG
depends on REMOTEPROC
depends on OF
help
Driver for a 10-LED WS2812 RGB strip on the TI AM62x M4 coprocessor
running the Zephyr wago-led-server-app. Communicates over the RPMsg
"wago-led" endpoint using a binary packed-frame protocol and exposes
each LED as a Linux LED multicolor (RGB) device.
The M4 firmware can be pre-loaded by the bootloader (the driver
will attach) or loaded by this driver from /lib/firmware.
Say M or Y for WAGO boards with an M4-driven WS2812 LED strip.
config LEDS_WAGO_M4_WRAPPER_SYSFS_PASSTHROUGH
bool "WAGO M4 LED wrapper: sysfs raw command passthrough"
depends on LEDS_WAGO_M4_WRAPPER
default n
help
Adds a wago_led_cmd sysfs attribute to the platform device that
forwards raw ASCII commands directly to the M4 via RPMsg, bypassing
the LED multiclass framework.
Useful for development, testing and triggering M4-side effects
(animations, blink patterns) that have no LED class equivalent:
echo "CMD-IDL" > /sys/.../leds-m4/wago_led_cmd
echo "CMD-CYC-50-128" > /sys/.../leds-m4/wago_led_cmd
echo "CMD-BLK-0-R-200-255" > /sys/.../leds-m4/wago_led_cmd
Say N for production builds where direct M4 access should be
restricted to the LED class interface only.
endif # LEDS_CLASS_MULTICOLOR
+1
View File
@@ -4,3 +4,4 @@ obj-$(CONFIG_LEDS_GROUP_MULTICOLOR) += leds-group-multicolor.o
obj-$(CONFIG_LEDS_PWM_MULTICOLOR) += leds-pwm-multicolor.o
obj-$(CONFIG_LEDS_QCOM_LPG) += leds-qcom-lpg.o
obj-$(CONFIG_LEDS_MT6370_RGB) += leds-mt6370-rgb.o
obj-$(CONFIG_LEDS_WAGO_M4_WRAPPER) += wago-m4-led-wrapper.o
+84
View File
@@ -0,0 +1,84 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* WAGO M4 LED Wrapper IPC Protocol Definition
*
* Binary protocol between the Linux wago-m4-led-wrapper driver and the
* Zephyr wago-led-server-app running on the TI AM62x M4 coprocessor.
*
* !! Must match the command IDs and frame layouts in the Zephyr app !!
*
* Author: WAGO GmbH & Co. KG
*/
#ifndef _WAGO_M4_LED_PROTOCOL_H
#define _WAGO_M4_LED_PROTOCOL_H
#include <linux/types.h>
#define WAGO_LED_NUM_LEDS 10
#define WAGO_LED_NUM_CHANNELS 3 /* R, G, B */
/**
* enum wago_led_cmd - Binary command IDs (first byte of every RPMsg frame)
*
* @WAGO_CMD_SET_LED: Set a single LED, all others unchanged. 5 bytes total.
* @WAGO_CMD_SET_STRIP: Set all LEDs atomically with a master brightness.
* 2 + WAGO_LED_NUM_LEDS * 3 bytes total.
* @WAGO_CMD_SET_ALL: Set every LED to the same color. 4 bytes total.
* @WAGO_CMD_IDL: Turn off all LEDs. 1 byte total.
*/
enum wago_led_cmd {
WAGO_CMD_SET_LED = 0x01,
WAGO_CMD_SET_STRIP = 0x02,
WAGO_CMD_SET_ALL = 0x03,
WAGO_CMD_IDL = 0x04,
};
/**
* struct wago_msg_set_led - WAGO_CMD_SET_LED frame
* @cmd: WAGO_CMD_SET_LED
* @led_idx: LED index [0 .. WAGO_LED_NUM_LEDS-1]
* @r: Red intensity [0..255]
* @g: Green intensity [0..255]
* @b: Blue intensity [0..255]
*/
struct wago_msg_set_led {
__u8 cmd;
__u8 led_idx;
__u8 r;
__u8 g;
__u8 b;
} __packed;
/**
* struct wago_msg_set_strip - WAGO_CMD_SET_STRIP frame
* @cmd: WAGO_CMD_SET_STRIP
* @brightness: Master brightness scale [0..255], applied to every channel
* @leds: RGB values for all LEDs; each channel scaled by brightness
* on the M4 side: out = (in * brightness) / 255
*/
struct wago_msg_set_strip {
__u8 cmd;
__u8 brightness;
struct {
__u8 r;
__u8 g;
__u8 b;
} leds[WAGO_LED_NUM_LEDS];
} __packed;
/**
* struct wago_msg_set_all - WAGO_CMD_SET_ALL frame
* @cmd: WAGO_CMD_SET_ALL
* @r: Red intensity [0..255]
* @g: Green intensity [0..255]
* @b: Blue intensity [0..255]
*/
struct wago_msg_set_all {
__u8 cmd;
__u8 r;
__u8 g;
__u8 b;
} __packed;
#endif /* _WAGO_M4_LED_PROTOCOL_H */
+929
View File
@@ -0,0 +1,929 @@
// SPDX-License-Identifier: GPL-2.0
/*
* WAGO M4 LED Wrapper Driver
*
* Controls a WS2812 RGB LED strip (10 LEDs) running on the TI AM62x M4
* coprocessor via the Zephyr wago-led-server-app. Exposes each LED as a
* Linux LED multiclass (RGB) device.
*
* Protocol
* --------
* Communication uses the "wago-led" RPMsg endpoint announced by the Zephyr app.
* The driver sends binary frames fire-and-forget style ACK replies from the
* M4 are intentionally ignored.
*
* Binary frame formats (little-endian, packed):
*
* CMD_SET_LED (0x01) [u8 cmd][u8 idx][u8 r][u8 g][u8 b] 5 bytes
* CMD_SET_STRIP (0x02) [u8 cmd][u8 bri][r0][g0][b0]...[r9][g9][b9] 32 bytes
* CMD_SET_ALL (0x03) [u8 cmd][u8 r][u8 g][u8 b] 4 bytes
* CMD_IDL (0x04) [u8 cmd] 1 byte
*
* The LED multiclass brightness_set callback uses CMD_SET_LED to update
* only the addressed LED without disturbing others on the strip.
*
* Fire-and-forget rationale
* -------------------------
* Waiting for an ACK after every command costs 13 ms per roundtrip (IPM
* interrupt + Zephyr thread wake-up). LED strip updates are idempotent a
* missed frame is harmless. If the vring TX ring is full, rpmsg_trysend()
* returns -ENOMEM; the driver retries up to WAGO_SEND_RETRIES times.
*
* Firmware loading and boot sequencing
* -------------------------------------
* 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:
* 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 -> RPMsg channel appears, wago_rpmsg_probe() sets rpdev
* -ENOENT -> filesystem not ready, reschedule after WAGO_BOOT_INITIAL_DELAY_MS
* other -> fatal, stop retrying
*
* Sysfs example
* -------------
* 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):
* 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
*/
#include <linux/delay.h>
#include <linux/led-class-multicolor.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/property.h>
#include <linux/platform_device.h>
#include <linux/remoteproc.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include "wago-m4-led-protocol.h"
#define DRIVER_NAME "wago-m4-led-wrapper"
/* Endpoint name announced by the Zephyr app via RPMsg name-service.
* Must match RPMSG_TTY_NAME in the Zephyr wago-led-server-app/src/main.c
*/
#define WAGO_RPMSG_EPT_NAME "wago-led"
/* Maximum ASCII command length for sysfs passthrough */
#define WAGO_CMD_MAX_LEN 32
/* First boot attempt this many ms after probe() — Linux-boot path only,
* gives the rootfs time to mount before loading firmware from /lib/firmware.
* Not used in the SPL path (M4 already running).
*/
#define WAGO_BOOT_INITIAL_DELAY_MS 5000
/* Retry interval when rproc is not yet registered or firmware not found */
#define WAGO_BOOT_RETRY_MS 200
/* Maximum rproc-handle acquisition attempts before giving up (~30 s) */
#define WAGO_BOOT_MAX_RETRIES 150
/* Maximum filesystem-polling attempts when firmware file is not found.
* At WAGO_BOOT_INITIAL_DELAY_MS per attempt this bounds the wait to ~15 s.
*/
#define WAGO_BOOT_FS_MAX_RETRIES 3
/*
* vring TX ring retry policy for rpmsg_trysend().
* Total worst-case wait: WAGO_SEND_RETRIES x WAGO_SEND_RETRY_US = 1 ms
*/
#define WAGO_SEND_RETRIES 5
#define WAGO_SEND_RETRY_US 200
/* colour channel indices matching subled_info[] order */
#define CH_RED 0
#define CH_GREEN 1
#define CH_BLUE 2
/* Maximum binary frame size for CMD_SET_STRIP:
* 1 (cmd) + 1 (brightness) + WAGO_LED_NUM_LEDS * 3 (RGB)
*/
#define WAGO_SET_STRIP_LEN \
(2 + WAGO_LED_NUM_LEDS * WAGO_LED_NUM_CHANNELS)
/* -------------------------------------------------------------------------
* Data structures
* ----------------------------------------------------------------------
*/
struct wago_m4_led_priv;
/**
* struct wago_led - per-LED state
* @mc_cdev: multicolor LED class device (wraps led_classdev)
* @subled_info: R/G/B sub-LED descriptors
* @priv: back-pointer to driver private data
* @index: LED index on the strip [0 .. WAGO_LED_NUM_LEDS-1]
*/
struct wago_led {
struct led_classdev_mc mc_cdev;
struct mc_subled subled_info[WAGO_LED_NUM_CHANNELS];
struct wago_m4_led_priv *priv;
unsigned int index;
};
/**
* struct wago_m4_led_priv - driver private data
* @dev: underlying platform device
* @rproc: remoteproc handle for the M4 core
* @rproc_booted_by_us: true when we called rproc_boot() ourselves
* @fw_name: firmware filename (from DT or default)
* @boot_work: delayed work for filesystem-ready retry loop
* @boot_retries: rproc handle acquisition attempts made so far
* @fs_retries: firmware filesystem polling attempts made so far
* @rpdev: RPMsg device (populated when the channel appears)
* @send_lock: serialises concurrent rpmsg_trysend() calls
* @leds: per-LED state array
*/
struct wago_m4_led_priv {
struct device *dev;
/* remoteproc */
struct rproc *rproc;
bool rproc_booted_by_us;
bool reset_on_init;
const char *fw_name;
/* boot retry workqueue */
struct delayed_work boot_work;
int boot_retries;
int fs_retries;
/* RPMsg */
struct rpmsg_device *rpdev;
/* serialises concurrent send calls */
struct mutex send_lock;
/* LED strip */
struct wago_led leds[WAGO_LED_NUM_LEDS];
};
/* -------------------------------------------------------------------------
* RPMsg callback: M4 -> Linux
*
* No ACKs in binary protocol callback registered to satisfy rpmsg core.
* ----------------------------------------------------------------------
*/
static int wago_rpmsg_cb(struct rpmsg_device *rpdev, void *data,
int len, void *priv_data, u32 src)
{
dev_dbg(&rpdev->dev, "rx (ignored, %d bytes)\n", len);
return 0;
}
/* -------------------------------------------------------------------------
* IPC helper: fire-and-forget binary send
*
* Uses rpmsg_trysend() to avoid blocking. Retries up to WAGO_SEND_RETRIES
* times with a short udelay back-off when the vring TX ring is full.
* ----------------------------------------------------------------------
*/
static int wago_send(struct wago_m4_led_priv *priv,
const void *msg, size_t len)
{
int ret, tries;
if (!priv->rpdev) {
dev_dbg(priv->dev, "RPMsg channel not yet available, dropping frame\n");
return -ENODEV;
}
for (tries = 0; tries < WAGO_SEND_RETRIES; tries++) {
ret = rpmsg_trysend(priv->rpdev->ept, (void *)msg, len);
if (ret != -ENOMEM)
break;
udelay(WAGO_SEND_RETRY_US);
}
if (ret)
dev_warn_ratelimited(priv->dev,
"rpmsg_trysend failed after %d tries: %d\n",
tries, ret);
return ret;
}
/* Thin wrapper for sysfs passthrough (ASCII strings) */
static int wago_send_ascii(struct wago_m4_led_priv *priv, const char *cmd)
{
dev_dbg(priv->dev, "tx ascii: %s", cmd);
return wago_send(priv, cmd, strlen(cmd));
}
/* -------------------------------------------------------------------------
* Sysfs attribute: wago_led_cmd
* Only compiled in when CONFIG_LEDS_WAGO_M4_WRAPPER_SYSFS_PASSTHROUGH=y
*
* Allows sending raw ASCII commands to the M4 directly from the shell.
* The command string is forwarded as-is via RPMsg (fire-and-forget).
* Note: only legacy animation commands are handled as ASCII by the M4;
* LED colour updates from the kernel use the binary protocol.
*
* 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
* ----------------------------------------------------------------------
*/
#ifdef CONFIG_LEDS_WAGO_M4_WRAPPER_SYSFS_PASSTHROUGH
/*
* wago_all_leds_off - set brightness=0 and cancel triggers on all LEDs.
*
* Called when CMD-IDL is sent via sysfs passthrough. Without this, any
* active kernel trigger (e.g. "heartbeat") would keep calling
* brightness_set() and immediately re-light LEDs that CMD-IDL just cleared.
*/
static void wago_all_leds_off(struct wago_m4_led_priv *priv)
{
int i;
for (i = 0; i < WAGO_LED_NUM_LEDS; i++) {
struct led_classdev *lcdev =
&priv->leds[i].mc_cdev.led_cdev;
/* led_set_brightness() cancels any active trigger and sets
* brightness=0 through the normal LED core path.
*/
led_set_brightness(lcdev, LED_OFF);
}
}
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';
/* If this is CMD-IDL, silence all kernel-side triggers first.
* Otherwise an active trigger (e.g. "heartbeat") would keep calling
* brightness_set() and immediately re-light the strip after the M4
* has cleared it.
*/
if (strncmp(cmd, "CMD-IDL", 7) == 0)
wago_all_leds_off(priv);
mutex_lock(&priv->send_lock);
ret = wago_send_ascii(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,
};
#endif /* CONFIG_LEDS_WAGO_M4_WRAPPER_SYSFS_PASSTHROUGH */
static int wago_led_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
struct wago_led *led = container_of(mc_cdev, struct wago_led, mc_cdev);
struct wago_m4_led_priv *priv = led->priv;
struct wago_msg_set_led msg;
/*
* The pattern trigger updates the 'intensity' values in mc_cdev->subled_info
* directly (via pattern_trig_apply_color). We must use these current
* intensities instead of recalculating them from the static multi_intensity.
*
* Note: when the pattern trigger does gradual dimming, it updates the
* brightness of the led_cdev while keeping the sub-led intensities constant
* (at the target color). This correctly results in a fading effect.
*/
int i;
u8 components[WAGO_LED_NUM_CHANNELS] = {0};
for (i = 0; i < mc_cdev->num_colors && i < WAGO_LED_NUM_CHANNELS; i++)
components[i] = (u8)((brightness * mc_cdev->subled_info[i].intensity) / LED_FULL);
dev_dbg(priv->dev, "LED%d: bri=%d R=%d G=%d B=%d (ints: %d %d %d)\n",
led->index, brightness,
components[CH_RED], components[CH_GREEN], components[CH_BLUE],
mc_cdev->subled_info[CH_RED].intensity,
mc_cdev->subled_info[CH_GREEN].intensity,
mc_cdev->subled_info[CH_BLUE].intensity);
/*
* Use CMD_SET_LED to update only this LED without touching the
* others. The M4 maintains the full pixel[] buffer internally.
*/
msg.cmd = WAGO_CMD_SET_LED;
msg.led_idx = (u8)led->index;
msg.r = components[CH_RED];
msg.g = components[CH_GREEN];
msg.b = components[CH_BLUE];
mutex_lock(&priv->send_lock);
wago_send(priv, &msg, sizeof(msg));
mutex_unlock(&priv->send_lock);
return 0;
}
/* -------------------------------------------------------------------------
* LED registration
* ----------------------------------------------------------------------
*/
/*
* wago_led_init_subled - initialise sub-LED descriptors common to every strip LED.
*/
static void wago_led_init_subled(struct wago_led *led)
{
led->subled_info[CH_RED].color_index = LED_COLOR_ID_RED;
led->subled_info[CH_RED].intensity = 255;
led->subled_info[CH_RED].channel = CH_RED;
led->subled_info[CH_GREEN].color_index = LED_COLOR_ID_GREEN;
led->subled_info[CH_GREEN].intensity = 255;
led->subled_info[CH_GREEN].channel = CH_GREEN;
led->subled_info[CH_BLUE].color_index = LED_COLOR_ID_BLUE;
led->subled_info[CH_BLUE].intensity = 255;
led->subled_info[CH_BLUE].channel = CH_BLUE;
led->mc_cdev.subled_info = led->subled_info;
led->mc_cdev.num_colors = WAGO_LED_NUM_CHANNELS;
led->mc_cdev.led_cdev.brightness_set_blocking = wago_led_set;
led->mc_cdev.led_cdev.max_brightness = LED_FULL;
led->mc_cdev.led_cdev.flags = LED_CORE_SUSPENDRESUME;
led->mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI;
}
/*
* wago_led_apply_default_intensity - read optional 'led-default-intensity'
* DT property and apply R/G/B values and optional brightness to the LED.
*
* Property format: <R G B> (three u32 values, each 0-255)
* or: <R G B brightness> (four u32 values, each 0-255)
* If the property is absent or malformed the subled defaults (255/255/255)
* set by wago_led_init_subled() are kept unchanged.
*/
static void wago_led_apply_default_intensity(struct device *dev,
struct wago_led *led,
struct fwnode_handle *fwnode)
{
u32 rgba[4] = { 255, 255, 255, LED_FULL };
int count;
/* Accept both 3-cell (R G B) and 4-cell (R G B brightness) */
count = fwnode_property_count_u32(fwnode, "led-default-intensity");
if (count != 3 && count != 4)
return; /* property absent or wrong size — keep defaults */
if (fwnode_property_read_u32_array(fwnode, "led-default-intensity",
rgba, count))
return;
led->subled_info[CH_RED].intensity = clamp_val(rgba[0], 0, 255);
led->subled_info[CH_GREEN].intensity = clamp_val(rgba[1], 0, 255);
led->subled_info[CH_BLUE].intensity = clamp_val(rgba[2], 0, 255);
if (count == 4)
led->mc_cdev.led_cdev.brightness = clamp_val(rgba[3], 0, LED_FULL);
dev_dbg(dev, "LED %u: default intensity RGB(%u,%u,%u) brightness=%u from DT\n",
led->index, rgba[0], rgba[1], rgba[2],
led->mc_cdev.led_cdev.brightness);
}
/*
* wago_led_register_leds - register all strip LEDs with the LED core.
*
* When the DT node contains child nodes (led@N with a 'reg' property),
* they are used to drive naming via led_init_data.fwnode the LED core
* will read the 'label' and 'linux,default-trigger' properties from the
* child node automatically.
*
* When no child nodes are present the driver falls back to the legacy
* hard-coded names "m4-led0" "m4-led9".
*/
static int wago_led_register_leds(struct wago_m4_led_priv *priv)
{
struct device *dev = priv->dev;
struct fwnode_handle *child;
char default_label[16];
int count, ret;
count = device_get_child_node_count(dev);
if (count > 0) {
/*
* DT child-node path: iterate child nodes ordered by their
* 'reg' property which encodes the LED index [0..9].
*/
device_for_each_child_node(dev, child) {
struct led_init_data init_data = {};
struct wago_led *led;
u32 reg;
if (fwnode_property_read_u32(child, "reg", &reg)) {
dev_warn(dev,
"LED child node missing 'reg', skipping\n");
continue;
}
if (reg >= WAGO_LED_NUM_LEDS) {
dev_warn(dev,
"LED child reg %u out of range [0..%d], skipping\n",
reg, WAGO_LED_NUM_LEDS - 1);
continue;
}
led = &priv->leds[reg];
led->priv = priv;
led->index = reg;
wago_led_init_subled(led);
wago_led_apply_default_intensity(dev, led, child);
init_data.fwnode = child;
init_data.devicename = NULL;
init_data.devname_mandatory = false;
ret = devm_led_classdev_multicolor_register_ext(dev,
&led->mc_cdev,
&init_data);
if (ret) {
dev_err(dev,
"Failed to register LED %u: %d\n",
reg, ret);
fwnode_handle_put(child);
return ret;
}
dev_dbg(dev, "Registered LED %u from DT child node\n", reg);
}
} else {
/*
* Fallback: no child nodes use legacy "m4-ledN" names.
*/
int i;
for (i = 0; i < WAGO_LED_NUM_LEDS; i++) {
struct led_init_data init_data = {};
struct wago_led *led = &priv->leds[i];
led->priv = priv;
led->index = i;
wago_led_init_subled(led);
snprintf(default_label, sizeof(default_label), "m4-led%d", i);
init_data.fwnode = NULL;
init_data.devicename = DRIVER_NAME;
init_data.default_label = default_label;
init_data.devname_mandatory = false;
ret = devm_led_classdev_multicolor_register_ext(dev,
&led->mc_cdev,
&init_data);
if (ret) {
dev_err(dev,
"Failed to register LED %d: %d\n",
i, ret);
return ret;
}
dev_dbg(dev, "Registered %s (fallback name)\n",
led->mc_cdev.led_cdev.name);
}
}
return 0;
}
/* -------------------------------------------------------------------------
* RPMsg driver probe / remove
* Called by the rpmsg core when the M4 announces the "rpmsg-tty" service.
* ----------------------------------------------------------------------
*/
static int wago_rpmsg_probe(struct rpmsg_device *rpdev)
{
/*
* Walk up the device parent chain to find our priv pointer.
*
* The chain built by the remoteproc / virtio stack is:
*
* rpdev->dev rpmsg device (this device)
* .parent virtio_device (vrp->vdev)
* .parent rproc_vdev platform_device (rvdev->pdev)
* .parent rproc->dev <- dev_set_drvdata set here
*
* We set dev_set_drvdata on rproc->dev in wago_boot_work() as soon
* as we obtained the rproc handle, so three levels up is correct.
*/
struct device *rproc_dev = rpdev->dev.parent->parent->parent;
struct wago_m4_led_priv *priv;
/* Try three levels up first, then four (rproc->dev.parent layout
* may differ across kernel versions).
*/
priv = dev_get_drvdata(rproc_dev);
if (!priv) {
/* One more level up: rproc->dev.parent = ti_k3_m4 pdev */
priv = dev_get_drvdata(rproc_dev->parent);
}
if (!priv) {
dev_err(&rpdev->dev,
"No driver private data found in parent chain\n");
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);
if (priv->reset_on_init) {
struct wago_msg_set_all msg = {
.cmd = WAGO_CMD_SET_ALL,
.r = 0, .g = 0, .b = 0
};
dev_info(priv->dev, "Resetting all LEDs to black (reset-on-init)\n");
mutex_lock(&priv->send_lock);
wago_send(priv, &msg, sizeof(msg));
mutex_unlock(&priv->send_lock);
}
dev_info(&rpdev->dev,
"WAGO M4 RPMsg channel up — LED strip active (%d LEDs)\n",
WAGO_LED_NUM_LEDS);
return 0;
}
static void wago_rpmsg_remove(struct rpmsg_device *rpdev)
{
struct wago_m4_led_priv *priv = dev_get_drvdata(&rpdev->dev);
if (priv) {
mutex_lock(&priv->send_lock);
priv->rpdev = NULL;
mutex_unlock(&priv->send_lock);
}
dev_info(&rpdev->dev, "WAGO M4 LED RPMsg channel removed\n");
}
static const struct rpmsg_device_id wago_rpmsg_id_table[] = {
{ .name = WAGO_RPMSG_EPT_NAME },
{}
};
MODULE_DEVICE_TABLE(rpmsg, wago_rpmsg_id_table);
static struct rpmsg_driver wago_rpmsg_driver = {
.drv = {
.name = DRIVER_NAME "-rpmsg",
.owner = THIS_MODULE,
},
.id_table = wago_rpmsg_id_table,
.probe = wago_rpmsg_probe,
.callback = wago_rpmsg_cb,
.remove = wago_rpmsg_remove,
};
/* -------------------------------------------------------------------------
* remoteproc boot (delayed-work retry loop)
* ----------------------------------------------------------------------
*/
/*
* 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)
{
struct wago_m4_led_priv *priv =
container_of(work, struct wago_m4_led_priv, boot_work.work);
struct device *dev = priv->dev;
int ret;
/* Lazily obtain the rproc handle on the first run */
if (!priv->rproc) {
struct device_node *np = dev->of_node;
struct device_node *rproc_np;
rproc_np = of_parse_phandle(np, "remoteproc", 0);
if (!rproc_np) {
dev_err(dev, "Missing 'remoteproc' phandle in DT\n");
return;
}
priv->rproc = rproc_get_by_phandle(rproc_np->phandle);
of_node_put(rproc_np);
if (IS_ERR_OR_NULL(priv->rproc)) {
priv->rproc = NULL;
dev_warn(dev, "rproc not yet available, retrying...\n");
goto retry;
}
/*
* Store priv on the rproc device so wago_rpmsg_probe() can
* retrieve it by walking up the parent chain from the rpmsg
* device. This must be done before rproc_boot() triggers the
* virtio / RPMsg stack.
*/
dev_set_drvdata(&priv->rproc->dev, priv);
}
/*
* 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 (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, RPMsg channel expected shortly\n");
return;
}
/* 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);
goto fatal;
}
dev_dbg(dev, "Boot attempt %d/%d: loading %s\n",
priv->boot_retries + 1, WAGO_BOOT_MAX_RETRIES, priv->fw_name);
ret = rproc_boot(priv->rproc);
if (ret == 0) {
priv->rproc_booted_by_us = true;
dev_info(dev, "M4 booted with %s, waiting for RPMsg channel\n",
priv->fw_name);
return;
}
/*
* -ENOENT: firmware file not found rootfs not yet mounted.
* Switch to the longer retry interval for filesystem polling.
*/
if (ret == -ENOENT) {
if (++priv->fs_retries >= WAGO_BOOT_FS_MAX_RETRIES) {
dev_err(dev,
"Firmware '%s' not found after %d attempts, giving up\n",
priv->fw_name, WAGO_BOOT_FS_MAX_RETRIES);
goto fatal;
}
dev_dbg(dev, "Firmware not found yet, retrying in %d ms\n",
WAGO_BOOT_INITIAL_DELAY_MS);
schedule_delayed_work(&priv->boot_work,
msecs_to_jiffies(WAGO_BOOT_INITIAL_DELAY_MS));
return;
}
dev_err(dev, "rproc_boot failed: %d\n", ret);
fatal:
rproc_put(priv->rproc);
priv->rproc = NULL;
return;
retry:
if (++priv->boot_retries >= WAGO_BOOT_MAX_RETRIES) {
dev_err(dev,
"Firmware '%s' not found after %d attempts, giving up\n",
priv->fw_name, WAGO_BOOT_MAX_RETRIES);
if (priv->rproc) {
rproc_put(priv->rproc);
priv->rproc = NULL;
}
return;
}
schedule_delayed_work(&priv->boot_work,
msecs_to_jiffies(WAGO_BOOT_RETRY_MS));
}
static void wago_rproc_stop(struct wago_m4_led_priv *priv)
{
if (!priv->rproc)
return;
if (priv->rproc_booted_by_us) {
rproc_shutdown(priv->rproc);
priv->rproc_booted_by_us = false;
}
rproc_put(priv->rproc);
priv->rproc = NULL;
}
/* -------------------------------------------------------------------------
* Platform driver probe / remove
* ----------------------------------------------------------------------
*/
static int wago_m4_led_probe(struct platform_device *pdev)
{
struct wago_m4_led_priv *priv;
const char *fw_name;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = &pdev->dev;
mutex_init(&priv->send_lock);
/* Read firmware name from DT, fall back to default */
if (of_property_read_string(pdev->dev.of_node, "firmware-name", &fw_name))
fw_name = "wago-led-server.elf";
priv->fw_name = fw_name;
priv->reset_on_init = device_property_read_bool(&pdev->dev, "wago,reset-on-init");
platform_set_drvdata(pdev, priv);
/*
* 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) {
dev_err(&pdev->dev,
"Failed to register rpmsg driver: %d\n", ret);
return ret;
}
#ifdef CONFIG_LEDS_WAGO_M4_WRAPPER_SYSFS_PASSTHROUGH
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;
}
#endif /* CONFIG_LEDS_WAGO_M4_WRAPPER_SYSFS_PASSTHROUGH */
/*
* Schedule boot_work immediately (delay=0).
*
* boot_work polls for the rproc handle with WAGO_BOOT_RETRY_MS
* intervals (200 ms). Once the rproc is registered by the remoteproc
* subsystem it checks the state:
* RPROC_DETACHED -> attach right away (SPL path, ~1 retry needed)
* otherwise -> load firmware from /lib/firmware (Linux-boot path,
* retries until rootfs is mounted)
*/
INIT_DELAYED_WORK(&priv->boot_work, wago_boot_work);
schedule_delayed_work(&priv->boot_work, 0);
dev_info(&pdev->dev,
"WAGO M4 LED wrapper probed, %d LEDs active\n",
WAGO_LED_NUM_LEDS);
return 0;
}
static int wago_m4_led_remove(struct platform_device *pdev)
{
struct wago_m4_led_priv *priv = platform_get_drvdata(pdev);
/* Cancel any pending boot retry before tearing down */
cancel_delayed_work_sync(&priv->boot_work);
#ifdef CONFIG_LEDS_WAGO_M4_WRAPPER_SYSFS_PASSTHROUGH
sysfs_remove_group(&pdev->dev.kobj, &wago_led_attr_group);
#endif
unregister_rpmsg_driver(&wago_rpmsg_driver);
wago_rproc_stop(priv);
dev_info(&pdev->dev, "WAGO M4 LED wrapper removed\n");
return 0;
}
/* -------------------------------------------------------------------------
* Device Tree match table / module boilerplate
* ----------------------------------------------------------------------
*/
static const struct of_device_id wago_m4_led_of_match[] = {
{ .compatible = "wago,m4-led-wrapper" },
{}
};
MODULE_DEVICE_TABLE(of, wago_m4_led_of_match);
static struct platform_driver wago_m4_led_driver = {
.probe = wago_m4_led_probe,
.remove = wago_m4_led_remove,
.driver = {
.name = DRIVER_NAME,
.of_match_table = wago_m4_led_of_match,
},
};
module_platform_driver(wago_m4_led_driver);
MODULE_DESCRIPTION("WAGO M4 RGB LED strip wrapper (rpmsg-tty / remoteproc, fire-and-forget)");
MODULE_AUTHOR("WAGO GmbH & Co. KG");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRIVER_NAME);
+7
View File
@@ -3483,6 +3483,13 @@ static void spi_nor_everspin_reboot_fix(struct spi_nor *nor)
if (!nor->spimem || !nor->dev)
return;
/*
* Limit this hack to nodes explicitly named "mram" to prevent
* sending blind 8-8-8 resets to unrelated SPI NOR flashes.
*/
if (!nor->dev->of_node || !of_node_name_eq(nor->dev->of_node, "mram"))
return;
/*
* Logging as dev_info so it appears in dmesg during boot.
* This helps verify if the fix is being executed.
+384
View File
@@ -0,0 +1,384 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
#
# m4-led-perf-test.sh - Performance / stress test for WAGO M4 RGB LED strip
#
# Exercises all ten M4 strip LEDs (sys, run, io, em, u1-u6) by activating
# kernel LED triggers at various frequencies and patterns. Designed to run
# on a BusyBox-based target (ash/sh compatible, no bashisms).
#
# Triggers supported:
# pattern - (default) three phases: fade, flash-fast, flash-slow
# timer - simple on/off blink at staggered frequencies per LED
# heartbeat - kernel heartbeat trigger (frequency fixed by kernel)
#
# Usage:
# m4-led-perf-test.sh [--trigger=<pattern|timer|heartbeat>]
# [--duration=<seconds>]
# [--help] [--version]
#
# Author: WAGO GmbH & Co. KG
VERSION="1.0.0"
LED_SYSFS="/sys/class/leds"
# All M4 strip LED names (must match DT 'label' properties)
LEDS="sys run io em u1 u2 u3 u4 u5 u6"
# Default options
OPT_TRIGGER="pattern"
OPT_DURATION=30
# ---------------------------------------------------------------------------
# Logging helpers
# ---------------------------------------------------------------------------
log() { echo "[ m4-led-test ] $*"; }
info() { log "INFO $*"; }
warn() { log "WARN $*"; }
err() { log "ERROR $*" >&2; }
die() { err "$*"; cleanup; exit 1; }
# ---------------------------------------------------------------------------
# Usage / version
# ---------------------------------------------------------------------------
usage() {
cat <<EOF
Usage: $(basename "$0") [OPTIONS]
Performance / stress test for the WAGO M4 RGB LED strip.
Activates kernel LED triggers on all ten strip LEDs at various
frequencies to stress the RPMsg / M4 communication path.
Options:
--trigger=<mode> Trigger mode to use (default: pattern)
pattern - three phases: fade in/out, fast flash,
slow flash with colour cycling
timer - simple on/off blink at staggered
frequencies (10 ms .. 500 ms)
heartbeat - kernel heartbeat trigger
--duration=<sec> How long to run the test in seconds (default: 30)
--version Print version and exit
--help Print this help and exit
Examples:
$(basename "$0")
$(basename "$0") --trigger=timer --duration=60
$(basename "$0") --trigger=heartbeat
EOF
}
# ---------------------------------------------------------------------------
# Argument parsing (BusyBox-compatible, no getopt)
# ---------------------------------------------------------------------------
for arg in "$@"; do
case "$arg" in
--help)
usage
exit 0
;;
--version)
echo "$(basename "$0") version $VERSION"
exit 0
;;
--trigger=*)
OPT_TRIGGER="${arg#--trigger=}"
;;
--duration=*)
OPT_DURATION="${arg#--duration=}"
;;
*)
err "Unknown option: $arg"
usage >&2
exit 1
;;
esac
done
# Validate trigger
case "$OPT_TRIGGER" in
pattern|timer|heartbeat) ;;
*) die "Invalid trigger '$OPT_TRIGGER'. Choose: pattern, timer, heartbeat" ;;
esac
# Validate duration (must be a positive integer)
case "$OPT_DURATION" in
''|*[!0-9]*) die "Invalid duration '$OPT_DURATION': must be a positive integer" ;;
esac
[ "$OPT_DURATION" -gt 0 ] || die "Duration must be > 0"
# ---------------------------------------------------------------------------
# LED sysfs helpers
# ---------------------------------------------------------------------------
# led_path <name> -> /sys/class/leds/<name>
led_path() { echo "${LED_SYSFS}/$1"; }
# led_write <name> <file> <value>
led_write() {
local path
path="$(led_path "$1")/$2"
if [ ! -w "$path" ]; then
warn "Not writable: $path — skipping"
return 1
fi
printf '%s' "$3" > "$path" 2>/dev/null || warn "Write failed: $path <- $3"
}
# led_set_trigger <name> <trigger>
led_set_trigger() { led_write "$1" trigger "$2"; }
# led_set_brightness <name> <value 0-255>
led_set_brightness() { led_write "$1" brightness "$2"; }
# led_set_multi_intensity <name> <R> <G> <B>
led_set_multi_intensity() { led_write "$1" multi_intensity "$2 $3 $4"; }
# led_set_pattern <name> <pattern-string>
led_set_pattern() { led_write "$1" pattern "$2"; }
# Check which LEDs are actually present in sysfs
check_leds() {
local found=0 missing=0 name
for name in $LEDS; do
if [ -d "$(led_path "$name")" ]; then
found=$((found + 1))
else
warn "LED '$name' not found in $LED_SYSFS — skipping"
missing=$((missing + 1))
fi
done
info "Found $found / $(echo $LEDS | wc -w) expected LEDs"
[ "$found" -gt 0 ] || die "No M4 LEDs found in $LED_SYSFS. Is the driver loaded?"
}
# Return only the LEDs that exist in sysfs
present_leds() {
local name
for name in $LEDS; do
[ -d "$(led_path "$name")" ] && printf '%s ' "$name"
done
}
# ---------------------------------------------------------------------------
# Cleanup: restore all LEDs to 'none' trigger and brightness=0
# ---------------------------------------------------------------------------
cleanup() {
info "Restoring all LEDs (trigger=none, brightness=0) ..."
local name
for name in $(present_leds); do
led_set_trigger "$name" "none"
led_set_brightness "$name" 0
done
info "Cleanup done."
}
# Trap SIGINT / SIGTERM so Ctrl-C always restores LEDs
trap 'info "Interrupted — cleaning up ..."; cleanup; exit 130' INT TERM
# ---------------------------------------------------------------------------
# Trigger implementations
# ---------------------------------------------------------------------------
# --- HEARTBEAT --------------------------------------------------------------
run_heartbeat() {
info "Setting trigger=heartbeat on all LEDs ..."
local name
for name in $(present_leds); do
# White at full brightness
led_set_multi_intensity "$name" 255 255 255
led_set_brightness "$name" 255
led_set_trigger "$name" heartbeat
info " $name -> heartbeat"
done
}
# --- TIMER ------------------------------------------------------------------
#
# Stagger delay_on / delay_off across LEDs to create a "running" effect.
# Pairs (on_ms, off_ms) cycle through a range from fast (50/50) to slow
# (500/500) and back.
run_timer() {
info "Setting trigger=timer on all LEDs with staggered frequencies ..."
# Pre-defined (on_ms off_ms R G B) tuples — one per LED
# Colours: red, green, blue, cyan, magenta, yellow, white, orange, lime, teal
set -- \
"50 50 255 0 0" \
"100 100 0 255 0" \
"200 200 0 0 255" \
"300 300 0 255 255" \
"400 400 255 0 255" \
"500 500 255 255 0" \
"150 350 255 255 255" \
"80 420 255 128 0" \
"250 250 128 255 0" \
"350 150 0 128 128"
local name idx on off r g b
idx=1
for name in $(present_leds); do
# Extract the idx-th tuple from positional params
eval "tuple=\$$idx"
on=$(echo "$tuple" | awk '{print $1}')
off=$(echo "$tuple" | awk '{print $2}')
r=$(echo "$tuple" | awk '{print $3}')
g=$(echo "$tuple" | awk '{print $4}')
b=$(echo "$tuple" | awk '{print $5}')
led_set_multi_intensity "$name" "$r" "$g" "$b"
led_set_brightness "$name" 255
led_set_trigger "$name" timer
led_write "$name" delay_on "$on"
led_write "$name" delay_off "$off"
info " $name -> timer on=${on}ms off=${off}ms RGB($r,$g,$b)"
idx=$((idx + 1))
done
}
# --- PATTERN ----------------------------------------------------------------
#
# Three phases, each lasting 1/3 of the total duration:
#
# Phase 1 — FADE: smooth brightness ramp up/down (pattern trigger)
# Phase 2 — FAST FLASH: rapid on/off (50 ms) in different colours per LED
# Phase 3 — SLOW FLASH: slow pulse (400 ms) with colour cycling
#
# Pattern format: "<brightness> <duration_ms> <brightness> <duration_ms> ..."
# brightness 0-255, duration in ms. The pattern trigger loops the sequence.
# Smooth fade: 0->255 in steps then 255->0, total ~2 s per cycle
FADE_PATTERN="0 100 32 100 64 100 96 100 128 100 160 100 192 100 224 100"
FADE_PATTERN+=" 255 100 224 100 192 100 160 100 128 100 96 100 64 100 32 100"
# Fast flash: full on 50 ms, off 50 ms
FLASH_FAST_PATTERN="255 50 0 50"
# Slow flash: full on 400 ms, off 400 ms
FLASH_SLOW_PATTERN="255 400 0 400"
run_pattern_phase() {
local phase_name="$1"
local pattern="$2"
shift 2
# Remaining args: "name R G B" tuples
info " Phase: $phase_name"
local name r g b
for name in $(present_leds); do
# Consume next R G B from positional args
r="$1"; g="$2"; b="$3"; shift 3 2>/dev/null || true
led_set_trigger "$name" none
led_set_multi_intensity "$name" "$r" "$g" "$b"
led_set_brightness "$name" 255
led_set_trigger "$name" pattern
led_set_pattern "$name" "$pattern"
info " $name -> pattern='$phase_name' RGB($r,$g,$b)"
done
}
run_pattern() {
local phase_dur total_leds phase_sleep
total_leds=$(present_leds | wc -w)
phase_dur=$(( OPT_DURATION / 3 ))
[ "$phase_dur" -lt 2 ] && phase_dur=2
info "Pattern test: 3 phases x ~${phase_dur}s (${total_leds} LEDs)"
# --- Phase 1: FADE (all white, staggered start via offset pattern) ------
# Give each LED a slightly rotated version of the fade pattern so they
# don't all pulse in sync — we pre-build per-LED patterns with an initial
# offset silence of (index * 200 ms).
info "--- Phase 1/3: FADE ---"
local idx=0 name offset_pattern prefix
for name in $(present_leds); do
offset_pattern=""
if [ "$idx" -gt 0 ]; then
# Insert a leading "0 <offset_ms>" step to stagger each LED
prefix=$((idx * 200))
offset_pattern="0 ${prefix} "
fi
led_set_trigger "$name" none
led_set_multi_intensity "$name" 255 255 255
led_set_brightness "$name" 255
led_set_trigger "$name" pattern
led_set_pattern "$name" "${offset_pattern}${FADE_PATTERN}"
info " $name -> fade (offset ${idx}x200ms)"
idx=$((idx + 1))
done
sleep "$phase_dur"
# --- Phase 2: FAST FLASH — different colour per LED --------------------
info "--- Phase 2/3: FAST FLASH ---"
run_pattern_phase "fast-flash" "$FLASH_FAST_PATTERN" \
255 0 0 \
0 255 0 \
0 0 255 \
255 255 0 \
0 255 255 \
255 0 255 \
255 128 0 \
128 0 255 \
0 255 128 \
255 255 255
sleep "$phase_dur"
# --- Phase 3: SLOW FLASH — colour cycling (shift colours between LEDs) --
info "--- Phase 3/3: SLOW FLASH ---"
run_pattern_phase "slow-flash" "$FLASH_SLOW_PATTERN" \
0 255 255 \
255 0 255 \
255 255 0 \
0 0 255 \
0 255 0 \
255 0 0 \
128 255 128 \
255 128 128 \
128 128 255 \
200 200 200
# Let the last phase run for the remainder of the duration
local elapsed=$(( phase_dur * 2 ))
local remaining=$(( OPT_DURATION - elapsed ))
[ "$remaining" -lt 1 ] && remaining=1
sleep "$remaining"
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
info "============================================"
info " WAGO M4 LED Performance Test v${VERSION}"
info "============================================"
info "Trigger : $OPT_TRIGGER"
info "Duration : ${OPT_DURATION}s"
info "LED sysfs: $LED_SYSFS"
info "--------------------------------------------"
check_leds
case "$OPT_TRIGGER" in
pattern)
run_pattern
;;
timer)
run_timer
info "Running for ${OPT_DURATION}s ..."
sleep "$OPT_DURATION"
;;
heartbeat)
run_heartbeat
info "Running for ${OPT_DURATION}s ..."
sleep "$OPT_DURATION"
;;
esac
cleanup
info "Test complete."
exit 0