Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 305190aba7 | |||
| 5fd796f64e | |||
| 4988fa5c86 | |||
| c3ed670c91 | |||
| 4d1f5a13ae | |||
| e0657990ef | |||
| 37580c48f9 | |||
| 9698958e83 | |||
| 1bbcd3bd76 | |||
| 5781a66491 | |||
| e3fd89f943 | |||
| fbb5aac346 | |||
| 59f1d87a2c | |||
| 7c8e5ded71 | |||
| ad4a4f96a3 | |||
| 3bbeb76f8d | |||
| 1c9d167f39 | |||
| c2f1a0dbf7 | |||
| f40a482cfa | |||
| 5a450e9f83 | |||
| fc1cce5f76 | |||
| af7d0d54df | |||
| a6e86cb411 | |||
| dd08a46be6 | |||
| 8c4152a66a | |||
| dc4f8882fe | |||
| f99c43a2d2 | |||
| e4dd81bf6d | |||
| da7fae1b03 | |||
| cce6dbfb08 | |||
| 9778b2faa4 | |||
| 32b1b0da35 | |||
| f28267ee58 | |||
| 44e5bd9b8c | |||
| 0d9d7d4c5a | |||
| 13361187d2 | |||
| 67e0e9c205 | |||
| 267524ab51 | |||
| e93894be4a | |||
| 036060f93d | |||
| abeb97feae | |||
| 38da587d35 | |||
| dd0c614012 | |||
| fd81267439 | |||
| 527d216fa5 | |||
| 5c81f0089b | |||
| 03ce0e95a4 | |||
| 69818ae138 | |||
| 50c4d42a31 | |||
| e9ad8bc142 | |||
| 26cea97562 | |||
| 98b840c366 | |||
| 1259dde287 | |||
| c02ca98757 | |||
| 83f91a7720 |
@@ -0,0 +1,60 @@
|
||||
# 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 "rpmsg-tty" channel using
|
||||
ASCII text commands (CMD-WLED / CMD-IDL). Each LED is exposed as a Linux LED
|
||||
multiclass (RGB) device under /sys/class/leds/m4-led<N>/.
|
||||
|
||||
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";
|
||||
};
|
||||
@@ -50,6 +50,14 @@ properties:
|
||||
maxItems: 1
|
||||
description: Name of firmware to load for the M4F core
|
||||
|
||||
ti,no-auto-boot:
|
||||
type: boolean
|
||||
description:
|
||||
If present, the remoteproc will not automatically load and boot the
|
||||
M4F firmware at probe time. Firmware loading is then delegated to an
|
||||
external driver that calls rproc_boot() explicitly once the
|
||||
filesystem is available.
|
||||
|
||||
mboxes:
|
||||
description:
|
||||
OMAP Mailbox specifier denoting the sub-mailbox, to be used for
|
||||
|
||||
@@ -89,7 +89,7 @@ static int wsysinit_init_probe_dt(struct platform_device *pdev)
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct __kernel_timex tmx = {0};
|
||||
|
||||
if (of_property_present(np, "dp,rst")) {
|
||||
if (of_property_present(np, "dp,rst-gpios")) {
|
||||
wsysinitset.dp_gpio_rst = devm_gpiod_get(&pdev->dev, "dp,rst", GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(wsysinitset.dp_gpio_rst)) {
|
||||
dev_err(&pdev->dev,
|
||||
|
||||
@@ -26,6 +26,15 @@ dtb-$(CONFIG_ARCH_K3) += k3-am62-lp-sk-hdmi-audio.dtb
|
||||
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) += 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
|
||||
@@ -86,3 +95,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,165 @@
|
||||
/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,74 @@
|
||||
* 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 {
|
||||
compatible = "wago,m4-led-wrapper";
|
||||
remoteproc = <&mcu_m4fss>;
|
||||
firmware-name = "wago-led-server-app.elf";
|
||||
wago,reset-on-init;
|
||||
#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";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
&wsysinit {
|
||||
@@ -21,6 +81,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,15 +40,13 @@
|
||||
interrupt-parent = <&main_gpio0>;
|
||||
interrupts = <13 IRQ_TYPE_EDGE_FALLING>;
|
||||
|
||||
microchip,single-led-mode;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
label = "ethX3";
|
||||
label = "ethX1";
|
||||
phy-mode = "internal";
|
||||
};
|
||||
|
||||
@@ -57,19 +58,19 @@
|
||||
|
||||
port@2 {
|
||||
reg = <2>;
|
||||
label = "ethX1";
|
||||
label = "ethX3";
|
||||
phy-mode = "internal";
|
||||
};
|
||||
|
||||
port@3 {
|
||||
reg = <3>;
|
||||
label = "ethEM";
|
||||
label = "ethX4";
|
||||
phy-mode = "internal";
|
||||
};
|
||||
|
||||
port@4 {
|
||||
reg = <4>;
|
||||
label = "ethX4";
|
||||
label = "ethEM";
|
||||
phy-mode = "internal";
|
||||
};
|
||||
|
||||
@@ -90,6 +91,9 @@
|
||||
};
|
||||
|
||||
&main_i2c1 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
eeprom_hw_id_bb: at24c02@54 {
|
||||
status = "disabled";
|
||||
compatible = "atmel,24c02";
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
};
|
||||
|
||||
&main_i2c1 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
status = "okay";
|
||||
|
||||
eeprom_hw_id_ib: at24c02@54 {
|
||||
@@ -291,7 +294,7 @@
|
||||
* 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";
|
||||
|
||||
@@ -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 {
|
||||
@@ -178,6 +170,8 @@
|
||||
};
|
||||
|
||||
&ospi0 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
compatible = "ti,am654-ospi";
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&ospi0_pins_default>;
|
||||
@@ -208,10 +202,10 @@
|
||||
cdns,read-delay = <5>;
|
||||
|
||||
/* Tighten timings for 200 MHz (T=5ns) */
|
||||
cdns,tshsl-ns = <30>; /* CS# high pulse width */
|
||||
cdns,tsd2d-ns = <30>; /* Delay between back-to-back transfers */
|
||||
cdns,tchsh-ns = <10>; /* CS# hold time */
|
||||
cdns,tslch-ns = <10>; /* CS# setup time */
|
||||
cdns,tshsl-ns = <200>; /* CS# high pulse width (increased for stability) */
|
||||
cdns,tsd2d-ns = <200>; /* Delay between back-to-back transfers */
|
||||
cdns,tchsh-ns = <3>; /* CS# hold time */
|
||||
cdns,tslch-ns = <3>; /* CS# setup time */
|
||||
|
||||
spi-rx-bus-width = <8>;
|
||||
spi-tx-bus-width = <8>;
|
||||
@@ -230,6 +224,8 @@
|
||||
};
|
||||
|
||||
&main_i2c0 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
status = "okay";
|
||||
|
||||
eeprom_hw_id_wosm: at24c02@50 {
|
||||
@@ -238,86 +234,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,6 +110,17 @@
|
||||
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>;
|
||||
@@ -285,6 +296,7 @@
|
||||
status = "okay";
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&main_uart4_pins_default>;
|
||||
rts-gpios = <&main_gpio0 38 GPIO_ACTIVE_HIGH>;
|
||||
};
|
||||
|
||||
&main_i2c0 { /* type label */
|
||||
@@ -394,5 +406,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,8 +32,7 @@
|
||||
};
|
||||
|
||||
&main_uart4 { /* RS485 Interface */
|
||||
rts-gpios = <&main_gpio0 38 GPIO_ACTIVE_HIGH>;
|
||||
rs485-term-gpios = <&io_exp_8bit 0 GPIO_ACTIVE_HIGH>;
|
||||
/* RS485 termination is controlled via GPIO userspace tools */
|
||||
rs485-rts-active-high;
|
||||
linux,rs485-enabled-at-boot-time;
|
||||
};
|
||||
|
||||
@@ -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,15 @@
|
||||
/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,24 @@
|
||||
/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,9 @@
|
||||
/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"
|
||||
@@ -0,0 +1,9 @@
|
||||
/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"
|
||||
@@ -0,0 +1,9 @@
|
||||
/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"
|
||||
@@ -0,0 +1,9 @@
|
||||
/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,49 @@
|
||||
/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,9 @@
|
||||
/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,44 @@
|
||||
/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>;
|
||||
};
|
||||
@@ -189,7 +189,6 @@ CONFIG_TUN=y
|
||||
CONFIG_VETH=m
|
||||
CONFIG_NET_DSA_MICROCHIP_KSZ_COMMON=y
|
||||
CONFIG_NET_DSA_MICROCHIP_KSZ_SPI=y
|
||||
CONFIG_NET_DSA_MICROCHIP_KSZ9477_SYSFS=y
|
||||
# CONFIG_NET_VENDOR_ALACRITECH is not set
|
||||
# CONFIG_NET_VENDOR_AMAZON is not set
|
||||
# CONFIG_NET_VENDOR_AMD is not set
|
||||
@@ -342,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
|
||||
@@ -349,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
|
||||
@@ -467,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
|
||||
|
||||
@@ -42,22 +42,27 @@ static ssize_t multi_intensity_store(struct device *dev,
|
||||
|
||||
mutex_lock(&led_cdev->led_access);
|
||||
|
||||
for (i = 0; i < mcled_cdev->num_colors; i++) {
|
||||
for (i = 0; i < LED_COLOR_ID_MAX; i++) {
|
||||
ret = sscanf(buf + offset, "%i%n",
|
||||
&intensity_value[i], &nrchars);
|
||||
if (ret != 1) {
|
||||
ret = -EINVAL;
|
||||
goto err_out;
|
||||
}
|
||||
if (ret != 1)
|
||||
break;
|
||||
offset += nrchars;
|
||||
}
|
||||
|
||||
/* Expect newline; reject surplus trailing chars */
|
||||
offset++;
|
||||
if (offset < size) {
|
||||
ret = -EINVAL;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
/* Require at least the physical number of colors */
|
||||
if (i < mcled_cdev->num_colors) {
|
||||
ret = -EINVAL;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
for (i = 0; i < mcled_cdev->num_colors; i++)
|
||||
mcled_cdev->subled_info[i].intensity = intensity_value[i];
|
||||
|
||||
|
||||
@@ -51,4 +51,40 @@ 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-tty/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" channel using ASCII text commands and exposes each LED
|
||||
as a Linux LED multiclass (RGB) device under
|
||||
/sys/class/leds/m4-led<N>/.
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -70,6 +70,7 @@ static int leds_gmc_probe(struct platform_device *pdev)
|
||||
struct leds_multicolor *priv;
|
||||
unsigned int max_brightness = 0;
|
||||
int i, ret, count = 0;
|
||||
bool allow_subled_writes = false;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
@@ -121,28 +122,39 @@ static int leds_gmc_probe(struct platform_device *pdev)
|
||||
priv->mc_cdev.num_colors = count;
|
||||
|
||||
init_data.fwnode = dev_fwnode(dev);
|
||||
allow_subled_writes = fwnode_property_present(init_data.fwnode, "mc-allow-subled-writes");
|
||||
ret = devm_led_classdev_multicolor_register_ext(dev, &priv->mc_cdev, &init_data);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to register multicolor LED for %s.\n",
|
||||
cdev->name);
|
||||
|
||||
ret = leds_gmc_set(cdev, cdev->brightness);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to set LED value for %s.", cdev->name);
|
||||
/* Honor DT default-state using LED core helper */
|
||||
{
|
||||
int def = led_init_default_state_get(dev_fwnode(dev));
|
||||
|
||||
if (def != LEDS_DEFSTATE_KEEP) {
|
||||
ret = leds_gmc_set(cdev, cdev->brightness);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to set LED value for %s.", cdev->name);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
struct led_classdev *led_cdev = priv->monochromatics[i];
|
||||
|
||||
/*
|
||||
* Make the individual LED sysfs interface read-only to prevent the user
|
||||
* to change the brightness of the individual LEDs of the group.
|
||||
*/
|
||||
mutex_lock(&led_cdev->led_access);
|
||||
led_sysfs_disable(led_cdev);
|
||||
mutex_unlock(&led_cdev->led_access);
|
||||
/* Optionally keep sub-LED sysfs writable for legacy userspace */
|
||||
if (!allow_subled_writes) {
|
||||
/*
|
||||
* Make the individual LED sysfs interface read-only to prevent the user
|
||||
* to change the brightness of the individual LEDs of the group.
|
||||
*/
|
||||
mutex_lock(&led_cdev->led_access);
|
||||
led_sysfs_disable(led_cdev);
|
||||
mutex_unlock(&led_cdev->led_access);
|
||||
|
||||
/* Restore the write access to the LED sysfs when the group is destroyed */
|
||||
devm_add_action_or_reset(dev, restore_sysfs_write_access, led_cdev);
|
||||
/* Restore the write access to the LED sysfs when the group is destroyed */
|
||||
devm_add_action_or_reset(dev, restore_sysfs_write_access, led_cdev);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -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 */
|
||||
@@ -0,0 +1,900 @@
|
||||
// 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 1–3 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_RETRY_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"
|
||||
|
||||
#define WAGO_LED_NUM_LEDS 10
|
||||
#define WAGO_LED_NUM_CHANNELS 3 /* R, G, B */
|
||||
|
||||
/* 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 number of boot attempts before giving up */
|
||||
#define WAGO_BOOT_MAX_RETRIES 150
|
||||
|
||||
/*
|
||||
* 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: number of boot 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;
|
||||
|
||||
/* 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 void 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);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 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 = 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", ®)) {
|
||||
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)
|
||||
priv->rpdev = NULL;
|
||||
|
||||
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) {
|
||||
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 v2");
|
||||
MODULE_ALIAS("platform:" DRIVER_NAME);
|
||||
@@ -13,6 +13,9 @@
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/led-class-multicolor.h>
|
||||
|
||||
#define MAX_PATTERNS 1024
|
||||
/*
|
||||
@@ -34,8 +37,56 @@ struct pattern_trig_data {
|
||||
bool is_indefinite;
|
||||
bool is_hw_pattern;
|
||||
struct timer_list timer;
|
||||
#if IS_ENABLED(CONFIG_LEDS_CLASS_MULTICOLOR)
|
||||
struct led_classdev_mc *mcled;
|
||||
#endif
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_LEDS_CLASS_MULTICOLOR)
|
||||
static void pattern_trig_apply_color(struct pattern_trig_data *data,
|
||||
const struct led_pattern *pat)
|
||||
{
|
||||
int i;
|
||||
unsigned int max_intensity;
|
||||
bool has_red = false, has_green = false, has_blue = false;
|
||||
|
||||
if (!data->mcled || !pat->has_color)
|
||||
return;
|
||||
|
||||
max_intensity = data->mcled->led_cdev.max_brightness;
|
||||
|
||||
for (i = 0; i < data->mcled->num_colors; i++) {
|
||||
switch (data->mcled->subled_info[i].color_index) {
|
||||
case LED_COLOR_ID_RED:
|
||||
data->mcled->subled_info[i].intensity =
|
||||
DIV_ROUND_CLOSEST(pat->red * max_intensity, 255);
|
||||
has_red = true;
|
||||
break;
|
||||
case LED_COLOR_ID_GREEN:
|
||||
data->mcled->subled_info[i].intensity =
|
||||
DIV_ROUND_CLOSEST(pat->green * max_intensity, 255);
|
||||
has_green = true;
|
||||
break;
|
||||
case LED_COLOR_ID_BLUE:
|
||||
data->mcled->subled_info[i].intensity =
|
||||
DIV_ROUND_CLOSEST(pat->blue * max_intensity, 255);
|
||||
has_blue = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((!has_red && pat->red) || (!has_green && pat->green) ||
|
||||
(!has_blue && pat->blue))
|
||||
dev_warn_once(data->led_cdev->dev,
|
||||
"pattern: color component set for absent channel\n");
|
||||
}
|
||||
#else
|
||||
static inline void pattern_trig_apply_color(struct pattern_trig_data *data,
|
||||
const struct led_pattern *pat) { }
|
||||
#endif
|
||||
|
||||
static void pattern_trig_update_patterns(struct pattern_trig_data *data)
|
||||
{
|
||||
data->curr = data->next;
|
||||
@@ -81,6 +132,7 @@ static void pattern_trig_timer_function(struct timer_list *t)
|
||||
|
||||
if (data->curr->brightness == data->next->brightness) {
|
||||
/* Step change of brightness */
|
||||
pattern_trig_apply_color(data, data->curr);
|
||||
led_set_brightness(data->led_cdev,
|
||||
data->curr->brightness);
|
||||
mod_timer(&data->timer,
|
||||
@@ -104,6 +156,8 @@ static void pattern_trig_timer_function(struct timer_list *t)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data->delta_t == 0)
|
||||
pattern_trig_apply_color(data, data->curr);
|
||||
led_set_brightness(data->led_cdev,
|
||||
pattern_trig_compute_brightness(data));
|
||||
mod_timer(&data->timer,
|
||||
@@ -207,10 +261,18 @@ static ssize_t pattern_trig_show_patterns(struct pattern_trig_data *data,
|
||||
goto out;
|
||||
|
||||
for (i = 0; i < data->npatterns; i++) {
|
||||
count += scnprintf(buf + count, PAGE_SIZE - count,
|
||||
"%d %u ",
|
||||
data->patterns[i].brightness,
|
||||
data->patterns[i].delta_t);
|
||||
const struct led_pattern *p = &data->patterns[i];
|
||||
|
||||
if (!hw_pattern && p->has_color)
|
||||
count += scnprintf(buf + count, PAGE_SIZE - count,
|
||||
"%d:%02x%02x%02x %u ",
|
||||
p->brightness, p->red, p->green,
|
||||
p->blue, p->delta_t);
|
||||
else
|
||||
count += scnprintf(buf + count, PAGE_SIZE - count,
|
||||
"%d %u ",
|
||||
p->brightness,
|
||||
p->delta_t);
|
||||
}
|
||||
|
||||
buf[count - 1] = '\n';
|
||||
@@ -223,22 +285,52 @@ out:
|
||||
static int pattern_trig_store_patterns_string(struct pattern_trig_data *data,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int ccount, cr, offset = 0;
|
||||
int consumed, ccount;
|
||||
unsigned int brightness, delta_t;
|
||||
size_t offset = 0;
|
||||
u8 r, g, b;
|
||||
|
||||
while (offset < count - 1 && data->npatterns < MAX_PATTERNS) {
|
||||
cr = 0;
|
||||
ccount = sscanf(buf + offset, "%u %u %n",
|
||||
&data->patterns[data->npatterns].brightness,
|
||||
&data->patterns[data->npatterns].delta_t, &cr);
|
||||
while (offset < count && data->npatterns < MAX_PATTERNS) {
|
||||
while (offset < count && isspace(buf[offset]))
|
||||
offset++;
|
||||
if (offset >= count)
|
||||
break;
|
||||
|
||||
if (ccount != 2 ||
|
||||
data->patterns[data->npatterns].brightness > data->led_cdev->max_brightness) {
|
||||
if (!data->is_hw_pattern) {
|
||||
consumed = 0;
|
||||
ccount = sscanf(buf + offset,
|
||||
"%u:%2hhx%2hhx%2hhx %u %n",
|
||||
&brightness, &r, &g, &b,
|
||||
&delta_t, &consumed);
|
||||
if (ccount == 5) {
|
||||
if (brightness > data->led_cdev->max_brightness) {
|
||||
data->npatterns = 0;
|
||||
return -EINVAL;
|
||||
}
|
||||
data->patterns[data->npatterns].brightness = brightness;
|
||||
data->patterns[data->npatterns].delta_t = delta_t;
|
||||
data->patterns[data->npatterns].has_color = true;
|
||||
data->patterns[data->npatterns].red = r;
|
||||
data->patterns[data->npatterns].green = g;
|
||||
data->patterns[data->npatterns].blue = b;
|
||||
offset += consumed;
|
||||
data->npatterns++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
consumed = 0;
|
||||
ccount = sscanf(buf + offset, "%u %u %n", &brightness, &delta_t, &consumed);
|
||||
if (ccount != 2 || brightness > data->led_cdev->max_brightness) {
|
||||
data->npatterns = 0;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
offset += cr;
|
||||
data->patterns[data->npatterns].brightness = brightness;
|
||||
data->patterns[data->npatterns].delta_t = delta_t;
|
||||
data->npatterns++;
|
||||
if (consumed == 0)
|
||||
break;
|
||||
offset += consumed;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -274,6 +366,8 @@ static ssize_t pattern_trig_store_patterns(struct led_classdev *led_cdev,
|
||||
|
||||
data->is_hw_pattern = hw_pattern;
|
||||
data->npatterns = 0;
|
||||
/* Zero all pattern entries so per-tuple color field clearing is unnecessary */
|
||||
memset(data->patterns, 0, sizeof(data->patterns));
|
||||
|
||||
if (buf)
|
||||
err = pattern_trig_store_patterns_string(data, buf, count);
|
||||
@@ -404,6 +498,11 @@ static int pattern_trig_activate(struct led_classdev *led_cdev)
|
||||
data->last_repeat = -1;
|
||||
mutex_init(&data->lock);
|
||||
data->led_cdev = led_cdev;
|
||||
#if IS_ENABLED(CONFIG_LEDS_CLASS_MULTICOLOR)
|
||||
if (led_cdev->color == LED_COLOR_ID_MULTI ||
|
||||
led_cdev->color == LED_COLOR_ID_RGB)
|
||||
data->mcled = lcdev_to_mccdev(led_cdev);
|
||||
#endif
|
||||
led_set_trigger_data(led_cdev, data);
|
||||
timer_setup(&data->timer, pattern_trig_timer_function, 0);
|
||||
led_cdev->activated = true;
|
||||
@@ -459,5 +558,5 @@ module_exit(pattern_trig_exit);
|
||||
|
||||
MODULE_AUTHOR("Raphael Teysseyre <rteysseyre@gmail.com>");
|
||||
MODULE_AUTHOR("Baolin Wang <baolin.wang@linaro.org>");
|
||||
MODULE_DESCRIPTION("LED Pattern trigger");
|
||||
MODULE_DESCRIPTION("LED Pattern trigger (optional per-node RGB)");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
@@ -1381,11 +1381,12 @@ static int mtdchar_mmap(struct file *file, struct vm_area_struct *vma)
|
||||
struct mtd_info *mtd = mfi->mtd;
|
||||
struct map_info *map = mtd->priv;
|
||||
|
||||
/* This is broken because it assumes the MTD device is map-based
|
||||
and that mtd->priv is a valid struct map_info. It should be
|
||||
replaced with something that uses the mtd_get_unmapped_area()
|
||||
operation properly. */
|
||||
if (0 /*mtd->type == MTD_RAM || mtd->type == MTD_ROM*/) {
|
||||
/*
|
||||
* Only MTD_RAM devices that have a valid map_info with a physical
|
||||
* address (e.g. MRAM on a Cadence OSPI DAC window) support mmap().
|
||||
* All other MTD types return -ENODEV as before.
|
||||
*/
|
||||
if (mtd->type == MTD_RAM && map && map->phys != NO_XIP) {
|
||||
#ifdef pgprot_noncached
|
||||
if (file->f_flags & O_DSYNC || map->phys >= __pa(high_memory))
|
||||
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <linux/math64.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/map.h>
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of_platform.h>
|
||||
@@ -3420,8 +3421,24 @@ static void spi_nor_set_mtd_info(struct spi_nor *nor)
|
||||
mtd->dev.parent = dev;
|
||||
if (!mtd->name)
|
||||
mtd->name = dev_name(dev);
|
||||
mtd->type = MTD_NORFLASH;
|
||||
mtd->flags = MTD_CAP_NORFLASH;
|
||||
/*
|
||||
* If a fixup hook has already populated mtd->priv with a struct
|
||||
* map_info (e.g. Everspin MRAM on Cadence OSPI DAC window), honour
|
||||
* that and advertise the device as byte-addressable RAM so that
|
||||
* mtdchar_mmap() can call vm_iomap_memory() for zero-copy access.
|
||||
* Otherwise fall back to the standard NOR-flash type.
|
||||
*/
|
||||
if (mtd->priv) {
|
||||
mtd->type = MTD_RAM;
|
||||
mtd->flags = MTD_CAP_RAM;
|
||||
dev_info(dev,
|
||||
"MTD type set to MTD_RAM (mmap enabled), "
|
||||
"phys base: %pa\n",
|
||||
&((struct map_info *)mtd->priv)->phys);
|
||||
} else {
|
||||
mtd->type = MTD_NORFLASH;
|
||||
mtd->flags = MTD_CAP_NORFLASH;
|
||||
}
|
||||
/* Unset BIT_WRITEABLE to enable JFFS2 write buffer for ECC'd NOR */
|
||||
if (nor->flags & SNOR_F_ECC)
|
||||
mtd->flags &= ~MTD_BIT_WRITEABLE;
|
||||
@@ -3466,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.
|
||||
|
||||
@@ -5,10 +5,18 @@
|
||||
*/
|
||||
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
#include <linux/mtd/map.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/spi-mem.h>
|
||||
#include "core.h"
|
||||
|
||||
/* Private data stored in nor->priv for MRAM mmap support */
|
||||
struct everspin_mram_priv {
|
||||
struct map_info map;
|
||||
};
|
||||
|
||||
/* Optimization for 200 MHz: 20 Dummy Cycles are typically required */
|
||||
#define EVERSPIN_MRAM_DUMMY_CYCLES 8
|
||||
#define EVERSPIN_MRAM_DUMMY_CYCLES_FAST 20
|
||||
@@ -154,6 +162,7 @@ static void everspin_mram_default_init(struct spi_nor *nor)
|
||||
ret = spi_nor_read_any_reg(nor, &op, SNOR_PROTO_1_1_1);
|
||||
if (!ret) {
|
||||
u8 sr1 = nor->bouncebuf[0];
|
||||
|
||||
dev_info(nor->dev, "Initial SR1: 0x%02x (BP-Bits: 0x%x)\n", sr1, (sr1 & 0x3c) >> 2);
|
||||
if (sr1 & GENMASK(5, 2))
|
||||
everspin_mram_unlock(nor);
|
||||
@@ -190,6 +199,7 @@ static void everspin_mram_default_init(struct spi_nor *nor)
|
||||
if (!ret) {
|
||||
/* Access the result from the DMA-safe bounce buffer */
|
||||
u8 status = nor->bouncebuf[0];
|
||||
|
||||
dev_info(nor->dev, "MRAM Status Register (8s-0-8s): 0x%02x\n", status);
|
||||
}
|
||||
|
||||
@@ -289,6 +299,9 @@ static int everspin_mram_setup(struct spi_nor *nor,
|
||||
static int everspin_mram_late_init(struct spi_nor *nor)
|
||||
{
|
||||
struct spi_nor_flash_parameter *params = nor->params;
|
||||
struct platform_device *ctlr_pdev;
|
||||
struct everspin_mram_priv *priv;
|
||||
struct resource *res_ahb;
|
||||
|
||||
dev_info(nor->dev, "Finalizing 8s-8s-8s STR: Write/Read fully functional.\n");
|
||||
|
||||
@@ -326,6 +339,52 @@ static int everspin_mram_late_init(struct spi_nor *nor)
|
||||
|
||||
nor->params->setup = everspin_mram_setup;
|
||||
|
||||
/*
|
||||
* Set up MTD_RAM so that /dev/mtd0 supports mmap().
|
||||
*
|
||||
* The Cadence OSPI controller exposes the flash memory as a directly
|
||||
* accessible window in the CPU address space (DAC / AHB mode). We
|
||||
* read the physical base address of that window from the controller's
|
||||
* platform_device resource[1] – the same resource that cqspi_probe()
|
||||
* uses for ioremap. This lets mtdchar_mmap() call vm_iomap_memory()
|
||||
* and hand the physical pages directly to user-space.
|
||||
*/
|
||||
priv = devm_kzalloc(nor->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* nor->spimem->spi->controller->dev.parent is the platform_device
|
||||
* of the Cadence QSPI controller. Its IORESOURCE_MEM[1] is the AHB
|
||||
* (DAC) window – index 0 is the APB register block.
|
||||
*/
|
||||
ctlr_pdev = to_platform_device(nor->spimem->spi->controller->dev.parent);
|
||||
res_ahb = platform_get_resource(ctlr_pdev, IORESOURCE_MEM, 1);
|
||||
if (!res_ahb) {
|
||||
dev_warn(nor->dev,
|
||||
"MRAM: cannot find AHB resource – mmap() disabled\n");
|
||||
devm_kfree(nor->dev, priv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
priv->map.name = nor->mtd.name;
|
||||
priv->map.phys = res_ahb->start;
|
||||
priv->map.size = resource_size(res_ahb);
|
||||
priv->map.virt = NULL; /* not needed for mmap path */
|
||||
|
||||
nor->priv = priv;
|
||||
|
||||
/*
|
||||
* mtd->type and mtd->flags will be set to MTD_RAM / MTD_CAP_RAM
|
||||
* by spi_nor_set_mtd_info() in core.c once it sees mtd->priv != NULL.
|
||||
* mtdchar_mmap() will then call vm_iomap_memory() via map->phys.
|
||||
*/
|
||||
nor->mtd.priv = &priv->map;
|
||||
|
||||
dev_info(nor->dev,
|
||||
"MRAM: AHB window at %pa size %pa – mmap() enabled\n",
|
||||
&res_ahb->start, &priv->map.size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -899,6 +899,7 @@ static int k3_m4_rproc_probe(struct platform_device *pdev)
|
||||
|
||||
rproc->has_iommu = false;
|
||||
rproc->recovery_disabled = true;
|
||||
rproc->auto_boot = !of_property_read_bool(np, "ti,no-auto-boot");
|
||||
if (data->uses_lreset) {
|
||||
rproc->ops->prepare = k3_m4_rproc_prepare;
|
||||
rproc->ops->unprepare = k3_m4_rproc_unprepare;
|
||||
|
||||
+14
-3
@@ -706,13 +706,24 @@ static inline void led_classdev_notify_brightness_hw_changed(
|
||||
#endif
|
||||
|
||||
/**
|
||||
* struct led_pattern - pattern interval settings
|
||||
* @delta_t: pattern interval delay, in milliseconds
|
||||
* @brightness: pattern interval brightness
|
||||
* struct led_pattern - pattern interval settings (optional RGB per node)
|
||||
* @delta_t: pattern interval delay (ms)
|
||||
* @brightness: pattern interval brightness (0..max_brightness)
|
||||
* @has_color: true if this node specifies new RGB intensities
|
||||
* @red: red component (0..255, valid if has_color)
|
||||
* @green: green component (0..255, valid if has_color)
|
||||
* @blue: blue component (0..255, valid if has_color)
|
||||
*
|
||||
* If has_color is false the previously applied color (if any) remains
|
||||
* unchanged; only brightness/duration are processed.
|
||||
*/
|
||||
struct led_pattern {
|
||||
u32 delta_t;
|
||||
int brightness;
|
||||
bool has_color;
|
||||
u8 red;
|
||||
u8 green;
|
||||
u8 blue;
|
||||
};
|
||||
|
||||
enum led_audio {
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
-w05.12.00
|
||||
-w05.13.01
|
||||
|
||||
Executable
+383
@@ -0,0 +1,383 @@
|
||||
#!/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 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
|
||||
@@ -0,0 +1,33 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
include ../scripts/Makefile.include
|
||||
|
||||
bindir ?= /usr/sbin
|
||||
|
||||
ifeq ($(srctree),)
|
||||
srctree := $(patsubst %/,%,$(dir $(CURDIR)))
|
||||
srctree := $(patsubst %/,%,$(dir $(srctree)))
|
||||
endif
|
||||
|
||||
# Do not use make's built-in rules
|
||||
# (this improves performance and avoids hard-to-debug behaviour);
|
||||
MAKEFLAGS += -r
|
||||
|
||||
CFLAGS += -O2 -Wall -Wextra -g -D_GNU_SOURCE
|
||||
|
||||
ALL_TARGETS := mram_io
|
||||
ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS))
|
||||
|
||||
all: $(ALL_PROGRAMS)
|
||||
|
||||
export srctree OUTPUT CC LD CFLAGS
|
||||
include $(srctree)/tools/build/Makefile.include
|
||||
|
||||
$(OUTPUT)mram_io: mram_io.c
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
clean:
|
||||
$(RM) $(ALL_PROGRAMS)
|
||||
|
||||
install: all
|
||||
install -d $(DESTDIR)$(bindir)
|
||||
install -m 755 $(ALL_PROGRAMS) $(DESTDIR)$(bindir)
|
||||
@@ -0,0 +1,334 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* mram_io - Byte-wise read/write tool for MTD_RAM devices via mmap()
|
||||
*
|
||||
* Requires a MTD device that supports mmap() (e.g. Everspin MRAM on
|
||||
* Cadence OSPI in DAC mode, registered as MTD_RAM).
|
||||
*
|
||||
* Usage:
|
||||
* mram_io --read --device <dev> --offset <offset> --length <n>
|
||||
* mram_io --write --device <dev> --offset <offset> --data <b0> [b1 b2 ...]
|
||||
*
|
||||
* Examples:
|
||||
* mram_io --read --device /dev/mtd0 --offset 0x100 --length 16
|
||||
* mram_io --write --device /dev/mtd0 --offset 0x100 --data 0xde 0xad 0xbe 0xef
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void usage(const char *prog)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Usage:\n"
|
||||
" %s --read --device <dev> --offset <off> --length <n>\n"
|
||||
" %s --write --device <dev> --offset <off> --data <b0> [b1 ...]\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
" --device, -D <path> MTD character device, e.g. /dev/mtd0\n"
|
||||
" --offset, -o <n> Byte offset into the device (hex or dec)\n"
|
||||
" --length, -l <n> Number of bytes to read\n"
|
||||
" --data, -d Following args are bytes to write (hex or dec)\n"
|
||||
" --read, -r Read mode\n"
|
||||
" --write, -w Write mode\n"
|
||||
" --help, -h Show this help\n"
|
||||
"\n"
|
||||
"Output (read): hex-dump with offset, hex columns and ASCII column.\n",
|
||||
prog, prog);
|
||||
}
|
||||
|
||||
static unsigned long parse_num(const char *s)
|
||||
{
|
||||
char *end;
|
||||
unsigned long v = strtoul(s, &end, 0);
|
||||
|
||||
if (*end != '\0') {
|
||||
fprintf(stderr, "Invalid number: '%s'\n", s);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/* Returns the size of the MTD device via lseek(). */
|
||||
static off_t mtd_size(int fd)
|
||||
{
|
||||
off_t sz = lseek(fd, 0, SEEK_END);
|
||||
|
||||
if (sz < 0) {
|
||||
perror("lseek (SEEK_END)");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
return sz;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* main */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
enum mode { MODE_NONE, MODE_READ, MODE_WRITE };
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
static const struct option long_opts[] = {
|
||||
{ "read", no_argument, NULL, 'r' },
|
||||
{ "write", no_argument, NULL, 'w' },
|
||||
{ "device", required_argument, NULL, 'D' },
|
||||
{ "offset", required_argument, NULL, 'o' },
|
||||
{ "length", required_argument, NULL, 'l' },
|
||||
{ "data", no_argument, NULL, 'd' },
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ NULL, 0, NULL, 0 },
|
||||
};
|
||||
|
||||
enum mode mode = MODE_NONE;
|
||||
const char *device = NULL;
|
||||
unsigned long offset = 0;
|
||||
unsigned long length = 0;
|
||||
int have_offset = 0;
|
||||
int have_length = 0;
|
||||
int data_mode = 0;
|
||||
|
||||
int opt;
|
||||
int opt_idx = 0;
|
||||
|
||||
/*
|
||||
* Parse all named options first. Stop at --data/-d: everything
|
||||
* after it is a raw byte value for --write, not an option.
|
||||
*/
|
||||
while (!data_mode &&
|
||||
(opt = getopt_long(argc, argv, "rwD:o:l:dh",
|
||||
long_opts, &opt_idx)) != -1) {
|
||||
switch (opt) {
|
||||
case 'r':
|
||||
mode = MODE_READ;
|
||||
break;
|
||||
case 'w':
|
||||
mode = MODE_WRITE;
|
||||
break;
|
||||
case 'D':
|
||||
device = optarg;
|
||||
break;
|
||||
case 'o':
|
||||
offset = parse_num(optarg);
|
||||
have_offset = 1;
|
||||
break;
|
||||
case 'l':
|
||||
length = parse_num(optarg);
|
||||
have_length = 1;
|
||||
break;
|
||||
case 'd':
|
||||
data_mode = 1;
|
||||
break;
|
||||
case 'h':
|
||||
usage(argv[0]);
|
||||
return EXIT_SUCCESS;
|
||||
default:
|
||||
usage(argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Validate ------------------------------------------------- */
|
||||
if (!device) {
|
||||
fprintf(stderr, "Error: --device <path> is required.\n\n");
|
||||
usage(argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (mode == MODE_NONE) {
|
||||
fprintf(stderr, "Error: specify --read or --write.\n\n");
|
||||
usage(argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!have_offset) {
|
||||
fprintf(stderr, "Error: --offset is required.\n\n");
|
||||
usage(argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Collect write bytes from remaining argv (after --data) */
|
||||
uint8_t *write_buf = NULL;
|
||||
size_t write_cnt = 0;
|
||||
|
||||
if (mode == MODE_WRITE) {
|
||||
if (!data_mode) {
|
||||
fprintf(stderr,
|
||||
"Error: --write requires --data <b0> [b1 ...].\n\n");
|
||||
usage(argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
write_cnt = (size_t)(argc - optind);
|
||||
if (write_cnt == 0) {
|
||||
fprintf(stderr, "Error: no data bytes given after --data.\n\n");
|
||||
usage(argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
write_buf = malloc(write_cnt);
|
||||
if (!write_buf) {
|
||||
perror("malloc");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < write_cnt; i++) {
|
||||
unsigned long v = parse_num(argv[optind + i]);
|
||||
if (v > 0xff) {
|
||||
fprintf(stderr,
|
||||
"Error: byte value 0x%lx out of range.\n", v);
|
||||
free(write_buf);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
write_buf[i] = (uint8_t)v;
|
||||
}
|
||||
|
||||
length = write_cnt;
|
||||
}
|
||||
|
||||
if (mode == MODE_READ && !have_length) {
|
||||
fprintf(stderr, "Error: --read requires --length.\n\n");
|
||||
usage(argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* --- Open device ---------------------------------------------- */
|
||||
int flags = (mode == MODE_WRITE) ? (O_RDWR | O_SYNC) : O_RDONLY;
|
||||
int fd = open(device, flags);
|
||||
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "open(%s): %s\n", device, strerror(errno));
|
||||
free(write_buf);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
off_t dev_size = mtd_size(fd);
|
||||
|
||||
if ((unsigned long)dev_size == 0) {
|
||||
fprintf(stderr, "Error: device size is 0.\n");
|
||||
goto err_close;
|
||||
}
|
||||
|
||||
if (offset >= (unsigned long)dev_size) {
|
||||
fprintf(stderr,
|
||||
"Error: offset 0x%lx is beyond device size 0x%lx.\n",
|
||||
offset, (unsigned long)dev_size);
|
||||
goto err_close;
|
||||
}
|
||||
|
||||
if (offset + length > (unsigned long)dev_size) {
|
||||
fprintf(stderr,
|
||||
"Warning: clamping length from %lu to %lu (end of device).\n",
|
||||
length, (unsigned long)dev_size - offset);
|
||||
length = (unsigned long)dev_size - offset;
|
||||
}
|
||||
|
||||
/* --- mmap ----------------------------------------------------- */
|
||||
/*
|
||||
* mmap() the whole device. The kernel (mtdchar_mmap) maps the
|
||||
* physical AHB window directly into our address space; accesses
|
||||
* go straight to the MRAM without any kernel copy.
|
||||
*/
|
||||
int prot = PROT_READ | ((mode == MODE_WRITE) ? PROT_WRITE : 0);
|
||||
void *map = mmap(NULL, (size_t)dev_size, prot, MAP_SHARED, fd, 0);
|
||||
|
||||
if (map == MAP_FAILED) {
|
||||
fprintf(stderr, "mmap(%s): %s\n", device, strerror(errno));
|
||||
fprintf(stderr,
|
||||
"Note: device must be of type MTD_RAM with a physical "
|
||||
"AHB window (e.g. Everspin MRAM on Cadence OSPI).\n");
|
||||
goto err_close;
|
||||
}
|
||||
|
||||
uint8_t *base = (uint8_t *)map + offset;
|
||||
|
||||
/* --- Read ----------------------------------------------------- */
|
||||
if (mode == MODE_READ) {
|
||||
printf("Reading %lu byte(s) from %s at offset 0x%lx:\n\n",
|
||||
length, device, offset);
|
||||
printf(" Offset 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f"
|
||||
" ASCII\n");
|
||||
printf(" -------- -----------------------------------------------"
|
||||
" ----------------\n");
|
||||
|
||||
for (unsigned long i = 0; i < length; ) {
|
||||
unsigned long row_off = offset + i;
|
||||
unsigned long row_start = i;
|
||||
|
||||
/* Offset column */
|
||||
printf(" %08lx ", row_off);
|
||||
|
||||
/* Hex bytes: 16 per row, split 8+8 */
|
||||
for (int col = 0; col < 16; col++) {
|
||||
if (col == 8)
|
||||
printf(" ");
|
||||
if (row_start + col < length)
|
||||
printf("%02x ", base[row_start + col]);
|
||||
else
|
||||
printf(" ");
|
||||
}
|
||||
|
||||
/* ASCII column */
|
||||
printf(" |");
|
||||
for (int col = 0; col < 16 && row_start + col < length; col++) {
|
||||
uint8_t c = base[row_start + col];
|
||||
printf("%c", (c >= 0x20 && c < 0x7f) ? c : '.');
|
||||
}
|
||||
printf("|\n");
|
||||
|
||||
i += 16;
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
/* --- Write ---------------------------------------------------- */
|
||||
if (mode == MODE_WRITE) {
|
||||
printf("Writing %lu byte(s) to %s at offset 0x%lx:\n",
|
||||
length, device, offset);
|
||||
|
||||
for (size_t i = 0; i < write_cnt; i++) {
|
||||
/*
|
||||
* Volatile write: ensure each byte reaches the hardware
|
||||
* window. The mmap region is pgprot_noncached, but being
|
||||
* explicit prevents compiler from merging byte stores.
|
||||
*/
|
||||
volatile uint8_t *dst = (volatile uint8_t *)base + i;
|
||||
*dst = write_buf[i];
|
||||
printf(" [0x%08lx] <- 0x%02x\n",
|
||||
offset + i, write_buf[i]);
|
||||
}
|
||||
|
||||
/* Read back for verification */
|
||||
printf("\nVerification (read-back):\n");
|
||||
for (size_t i = 0; i < write_cnt; i++) {
|
||||
uint8_t got = *((volatile uint8_t *)base + i);
|
||||
const char *status = (got == write_buf[i]) ? "OK" : "MISMATCH";
|
||||
|
||||
printf(" [0x%08lx] wrote 0x%02x read 0x%02x %s\n",
|
||||
offset + i, write_buf[i], got, status);
|
||||
}
|
||||
}
|
||||
|
||||
munmap(map, (size_t)dev_size);
|
||||
free(write_buf);
|
||||
close(fd);
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
err_close:
|
||||
free(write_buf);
|
||||
close(fd);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
Reference in New Issue
Block a user