Compare commits
109 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 | |||
| 8aaf0bc4dd | |||
| b6bd016ca4 | |||
| e4b0c7a3f3 | |||
| ee6bbf0acb | |||
| fb6487598d | |||
| 84f6b6b2b4 | |||
| c6b83a8a18 | |||
| 440b2636a6 | |||
| 8fbc497d02 | |||
| 364bafa89b | |||
| 973133fb07 | |||
| e9a7a7db80 | |||
| f7bef5c22d | |||
| 07add97641 | |||
| 008d7df4af | |||
| c4cf89b09f | |||
| c037b08a85 | |||
| beb102e610 | |||
| 750daf2748 | |||
| 1f4ef45bc2 | |||
| 0436682951 | |||
| 8811cb9cf5 | |||
| 4535aed8be | |||
| 45a6f8a5b2 | |||
| 835d1cd4c5 | |||
| 4f6af6494f | |||
| b1cdb250c5 | |||
| d20b58cb7c | |||
| 1f75fdeac0 | |||
| b661f3fdcc | |||
| d14d5d186e | |||
| 0e6597e783 | |||
| 238366a634 | |||
| 7d25e1159b | |||
| 1dde1a6b0f | |||
| 58bbb53e99 | |||
| eeaa1fa415 | |||
| 4e58c348b2 | |||
| f3d8580745 | |||
| 1d2ed385ff | |||
| 7587fa5670 | |||
| 0918552295 | |||
| 133e49bfc4 | |||
| 2b10a5afd4 | |||
| 4ae31418d2 | |||
| 6441f580b5 | |||
| 9e6fd10aa1 | |||
| d3d097fd35 | |||
| 2c0c1870ce | |||
| 6f8cbb8517 | |||
| 5a06c5102f | |||
| f44b178c4f | |||
| b5d708e333 | |||
| 7879a50ae7 |
@@ -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";
|
||||
};
|
||||
@@ -0,0 +1,133 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/remoteproc/ti,k3-m4f-rproc.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: TI K3 M4F processor subsystems
|
||||
|
||||
maintainers:
|
||||
- Hari Nagalla <hnagalla@ti.com>
|
||||
- Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
|
||||
description: |
|
||||
Some K3 family SoCs have Arm Cortex M4F cores. AM64x is a SoC in K3
|
||||
family with a M4F core. Typically safety oriented applications may use
|
||||
the M4F core in isolation without an IPC. Where as some industrial and
|
||||
home automation applications, may use the M4F core as a remote processor
|
||||
with IPC communications.
|
||||
|
||||
$ref: /schemas/arm/keystone/ti,k3-sci-common.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- ti,am64-m4fss
|
||||
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
||||
"#address-cells":
|
||||
const: 2
|
||||
|
||||
"#size-cells":
|
||||
const: 2
|
||||
|
||||
reg:
|
||||
items:
|
||||
- description: IRAM internal memory region
|
||||
- description: DRAM internal memory region
|
||||
|
||||
reg-names:
|
||||
items:
|
||||
- const: iram
|
||||
- const: dram
|
||||
|
||||
resets:
|
||||
maxItems: 1
|
||||
|
||||
firmware-name:
|
||||
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
|
||||
communication with the remote processor. This property should match
|
||||
with the sub-mailbox node used in the firmware image.
|
||||
maxItems: 1
|
||||
|
||||
memory-region:
|
||||
description:
|
||||
phandle to the reserved memory nodes to be associated with the
|
||||
remoteproc device. Optional memory regions available for firmware
|
||||
specific purposes.
|
||||
(see reserved-memory/reserved-memory.yaml in dtschema project)
|
||||
maxItems: 8
|
||||
items:
|
||||
- description: regions used for DMA allocations like vrings, vring buffers
|
||||
and memory dedicated to firmware's specific purposes.
|
||||
additionalItems: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- reg-names
|
||||
- ti,sci
|
||||
- ti,sci-dev-id
|
||||
- ti,sci-proc-ids
|
||||
- resets
|
||||
- firmware-name
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
reserved-memory {
|
||||
#address-cells = <2>;
|
||||
#size-cells = <2>;
|
||||
|
||||
mcu_m4fss_dma_memory_region: m4f-dma-memory@9cb00000 {
|
||||
compatible = "shared-dma-pool";
|
||||
reg = <0x00 0x9cb00000 0x00 0x100000>;
|
||||
no-map;
|
||||
};
|
||||
|
||||
mcu_m4fss_memory_region: m4f-memory@9cc00000 {
|
||||
compatible = "shared-dma-pool";
|
||||
reg = <0x00 0x9cc00000 0x00 0xe00000>;
|
||||
no-map;
|
||||
};
|
||||
};
|
||||
|
||||
soc {
|
||||
#address-cells = <2>;
|
||||
#size-cells = <2>;
|
||||
|
||||
mailbox0_cluster0: mailbox-0 {
|
||||
#mbox-cells = <1>;
|
||||
};
|
||||
|
||||
remoteproc@5000000 {
|
||||
compatible = "ti,am64-m4fss";
|
||||
reg = <0x00 0x5000000 0x00 0x30000>,
|
||||
<0x00 0x5040000 0x00 0x10000>;
|
||||
reg-names = "iram", "dram";
|
||||
resets = <&k3_reset 9 1>;
|
||||
firmware-name = "am62-mcu-m4f0_0-fw";
|
||||
mboxes = <&mailbox0_cluster0>, <&mbox_m4_0>;
|
||||
memory-region = <&mcu_m4fss_dma_memory_region>,
|
||||
<&mcu_m4fss_memory_region>;
|
||||
ti,sci = <&dmsc>;
|
||||
ti,sci-dev-id = <9>;
|
||||
ti,sci-proc-ids = <0x18 0xff>;
|
||||
};
|
||||
};
|
||||
@@ -29,3 +29,7 @@
|
||||
&cc_logic_usb_c {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&cc_logic_usb_c_1 {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
@@ -485,6 +485,14 @@
|
||||
pinctrl-0 = <&ptn5150_pins>;
|
||||
};
|
||||
|
||||
cc_logic_usb_c_1: cclogic@1d {
|
||||
compatible = "nxp,ptn5150";
|
||||
reg = <0x1d>;
|
||||
int-gpio = <&gpio1 7 GPIO_ACTIVE_HIGH>;
|
||||
vbus-gpio = <&gpio1 24 GPIO_ACTIVE_HIGH>;
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&ptn5150_pins>;
|
||||
};
|
||||
};
|
||||
|
||||
&i2c3 {
|
||||
|
||||
@@ -272,17 +272,41 @@
|
||||
status = "okay";
|
||||
|
||||
adc1: adc@0 {
|
||||
st,adc-channels = <1 5 13 15>;
|
||||
st,min-sample-time-nsecs = <10000>;
|
||||
assigned-resolution-bits = <16>;
|
||||
status = "okay";
|
||||
channel@1 {
|
||||
reg = <1>;
|
||||
st,min-sample-time-ns = <10000>;
|
||||
assigned-resolution-bits = <16>;
|
||||
};
|
||||
channel@5 {
|
||||
reg = <5>;
|
||||
st,min-sample-time-ns = <10000>;
|
||||
assigned-resolution-bits = <16>;
|
||||
};
|
||||
channel@13 {
|
||||
reg = <13>;
|
||||
st,min-sample-time-ns = <10000>;
|
||||
assigned-resolution-bits = <16>;
|
||||
};
|
||||
channel@15 {
|
||||
reg = <15>;
|
||||
st,min-sample-time-ns = <10000>;
|
||||
assigned-resolution-bits = <16>;
|
||||
};
|
||||
};
|
||||
|
||||
adc2: adc@100 {
|
||||
st,adc-channels = <0 3>;
|
||||
st,min-sample-time-nsecs = <10000>;
|
||||
assigned-resolution-bits = <16>;
|
||||
status = "okay";
|
||||
channel@0 {
|
||||
reg = <0>;
|
||||
st,min-sample-time-ns = <10000>;
|
||||
assigned-resolution-bits = <16>;
|
||||
};
|
||||
channel@3 {
|
||||
reg = <3>;
|
||||
st,min-sample-time-ns = <10000>;
|
||||
assigned-resolution-bits = <16>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -145,3 +145,18 @@ din_spi@0 {
|
||||
spi-max-frequency = <5000000>;
|
||||
};
|
||||
};
|
||||
|
||||
&i2c4 {
|
||||
cc_logic_usb_c_1: cclogic@1d {
|
||||
compatible = "nxp,ptn5150";
|
||||
reg = <0x1d>;
|
||||
int-gpio = <&gpioh 3 GPIO_ACTIVE_LOW>;
|
||||
vbus-supply = <&vbus_otg>;
|
||||
|
||||
port {
|
||||
con_usbotg_hs_ep_1: endpoint {
|
||||
remote-endpoint = <&usbotg_hs_ep>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ CONFIG_BPF_JIT=y
|
||||
CONFIG_PREEMPT_RT=y
|
||||
CONFIG_IKCONFIG=y
|
||||
CONFIG_IKCONFIG_PROC=y
|
||||
CONFIG_BLK_DEV_INITRD=y
|
||||
CONFIG_CGROUPS=y
|
||||
CONFIG_MEMCG=y
|
||||
CONFIG_BLK_CGROUP=y
|
||||
|
||||
@@ -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";
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -171,4 +171,17 @@
|
||||
bosch,mram-cfg = <0x0 128 64 64 64 64 32 32>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
mcu_m4fss: m4fss@5000000 {
|
||||
compatible = "ti,am64-m4fss";
|
||||
reg = <0x00 0x5000000 0x00 0x30000>,
|
||||
<0x00 0x5040000 0x00 0x10000>;
|
||||
reg-names = "iram", "dram";
|
||||
resets = <&k3_reset 9 1>;
|
||||
firmware-name = "am62-mcu-m4f0_0-fw";
|
||||
ti,sci = <&dmsc>;
|
||||
ti,sci-dev-id = <9>;
|
||||
ti,sci-proc-ids = <0x18 0xff>;
|
||||
status = "disabled";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,17 +6,91 @@
|
||||
* 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 {
|
||||
status = "okay";
|
||||
board,variant = "pfc400";
|
||||
};
|
||||
|
||||
&slb9670 {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&main_spi2 {
|
||||
ti,spi-num-cs = <4>;
|
||||
|
||||
spidev@3 {
|
||||
compatible = "rohm,dh2228fv";
|
||||
reg = <3>;
|
||||
spi-max-frequency = <50000000>;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9,21 +9,31 @@
|
||||
&main_pmx0 {
|
||||
main_ksz9477_pins: main-ksz9477-pins {
|
||||
pinctrl-single,pins = <
|
||||
AM62X_IOPAD(0x01a0, PIN_OUTPUT, 7) /* (E18) MCASP0_AXR0.GPIO1_10 nRST-RGMII2 / nRST-ETHSW (BB) */
|
||||
AM62X_IOPAD(0x0034, PIN_INPUT, 7) /* (H21) OSPI0_CSn2.GPIO0_13 nINT-RGMII2 / nINT-ETHSW(BB) */
|
||||
>;
|
||||
};
|
||||
};
|
||||
|
||||
&mcu_pmx0 {
|
||||
mcu_ksz9477_pins: mcu-ksz9477-pins {
|
||||
pinctrl-single,pins = <
|
||||
AM62X_MCU_IOPAD(0x002c, PIN_OUTPUT, 7) /* (C6) WKUP_UART0_CTSn.MCU_GPIO0_11 nRST-RGMII2 / nRST-ETHSW(BB) */
|
||||
>;
|
||||
};
|
||||
};
|
||||
|
||||
&mcu_spi0 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
ksz9477@0 {
|
||||
compatible = "microchip,ksz9477";
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&main_ksz9477_pins>;
|
||||
pinctrl-0 = <&main_ksz9477_pins>, <&mcu_ksz9477_pins>;
|
||||
reg = <0>;
|
||||
spi-max-frequency = <1000000>;
|
||||
|
||||
reset-gpios = <&main_gpio1 10 GPIO_ACTIVE_LOW>;
|
||||
reset-gpios = <&mcu_gpio0 11 GPIO_ACTIVE_LOW>;
|
||||
|
||||
dsa,member = <0 0>;
|
||||
|
||||
@@ -36,7 +46,7 @@
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
label = "ethX3";
|
||||
label = "ethX1";
|
||||
phy-mode = "internal";
|
||||
};
|
||||
|
||||
@@ -48,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";
|
||||
};
|
||||
|
||||
@@ -81,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 {
|
||||
@@ -105,22 +108,22 @@
|
||||
default-state = "off";
|
||||
};
|
||||
|
||||
lb60_9: eb-red@9 {
|
||||
label = "eb-red";
|
||||
lb60_9: em-red@9 {
|
||||
label = "em-red";
|
||||
reg = <9>;
|
||||
color = <LED_COLOR_ID_RED>;
|
||||
default-state = "off";
|
||||
};
|
||||
|
||||
lb60_10: eb-green@10 {
|
||||
label = "eb-green";
|
||||
lb60_10: em-green@10 {
|
||||
label = "em-green";
|
||||
reg = <10>;
|
||||
color = <LED_COLOR_ID_GREEN>;
|
||||
default-state = "off";
|
||||
};
|
||||
|
||||
lb60_11: eb-blue@11 {
|
||||
label = "eb-blue";
|
||||
lb60_11: em-blue@11 {
|
||||
label = "em-blue";
|
||||
reg = <11>;
|
||||
color = <LED_COLOR_ID_BLUE>;
|
||||
default-state = "off";
|
||||
@@ -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";
|
||||
@@ -323,9 +326,9 @@
|
||||
max-brightness = <255>;
|
||||
};
|
||||
|
||||
eb_led: eb-led {
|
||||
em_led: em-led {
|
||||
compatible = "leds-group-multicolor";
|
||||
label = "eb";
|
||||
label = "em";
|
||||
color = <LED_COLOR_ID_RGB>;
|
||||
function = LED_FUNCTION_INDICATOR;
|
||||
default-state = "off";
|
||||
|
||||
@@ -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,11 +170,16 @@
|
||||
};
|
||||
|
||||
&ospi0 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
compatible = "ti,am654-ospi";
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&ospi0_pins_default>;
|
||||
status = "okay";
|
||||
|
||||
/* Setup the clock for 200 MHz (400 MHz Ref / 2) */
|
||||
assigned-clock-rates = <400000000>;
|
||||
|
||||
/* Everspin Tech. EM008LXO
|
||||
* Order-No EM008LXOAB320IS1R
|
||||
*/
|
||||
@@ -195,16 +192,23 @@
|
||||
|
||||
compatible = "jedec,spi-nor";
|
||||
reg = <0x0>;
|
||||
spi-tx-bus-width = <8>;
|
||||
spi-rx-bus-width = <8>;
|
||||
spi-max-frequency = <25000000>;
|
||||
|
||||
cdns,tshsl-ns = <60>;
|
||||
cdns,tsd2d-ns = <60>;
|
||||
cdns,tchsh-ns = <60>;
|
||||
cdns,tslch-ns = <60>;
|
||||
cdns,read-delay = <4>;
|
||||
cdns,phy-mode;
|
||||
/* 50 MHz for Startup */
|
||||
spi-max-frequency = <50000000>;
|
||||
|
||||
/* 200 MHz needs a very precise sampling point.
|
||||
* Start with 4 or 5 since 125MHz already needed 3.
|
||||
*/
|
||||
cdns,read-delay = <5>;
|
||||
|
||||
/* Tighten timings for 200 MHz (T=5ns) */
|
||||
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>;
|
||||
|
||||
partitions {
|
||||
compatible = "fixed-partitions";
|
||||
@@ -213,13 +217,15 @@
|
||||
|
||||
partition@0 {
|
||||
label = "ospi.mram.mem";
|
||||
reg = <0x0 0x800000>;
|
||||
reg = <0x0 0x100000>; /* 1MB total capacity */
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
&main_i2c0 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
status = "okay";
|
||||
|
||||
eeprom_hw_id_wosm: at24c02@50 {
|
||||
@@ -228,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 {
|
||||
@@ -71,6 +71,7 @@
|
||||
ramoops@9ca00000 {
|
||||
compatible = "ramoops";
|
||||
reg = <0x00 0x9ca00000 0x00 0x00100000>;
|
||||
no-map;
|
||||
record-size = <0x8000>;
|
||||
console-size = <0x8000>;
|
||||
ftrace-size = <0x00>;
|
||||
@@ -89,11 +90,48 @@
|
||||
no-map;
|
||||
};
|
||||
|
||||
/* Gap between m4f-memory and r5f-dma-memory: must be reserved
|
||||
* to prevent Linux from allocating pages that are adjacent to
|
||||
* ioremap_wc-mapped M4 regions (ARM64 cache aliasing). */
|
||||
m4f_gap_memory: m4f-gap@9da00000 {
|
||||
reg = <0x00 0x9da00000 0x00 0x100000>;
|
||||
no-map;
|
||||
};
|
||||
|
||||
wkup_r5fss0_core0_dma_memory_region: r5f-dma-memory@9db00000 {
|
||||
compatible = "shared-dma-pool";
|
||||
reg = <0x00 0x9db00000 0x00 0xc00000>;
|
||||
no-map;
|
||||
};
|
||||
|
||||
/* Gap between r5f-dma-memory and tfa */
|
||||
r5f_gap_memory: r5f-gap@9e700000 {
|
||||
reg = <0x00 0x9e700000 0x00 0x80000>;
|
||||
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>;
|
||||
no-map;
|
||||
};
|
||||
|
||||
mcu_m4fss_memory_region: m4f-memory@9cc00000 {
|
||||
compatible = "shared-dma-pool";
|
||||
reg = <0x00 0x9cc00000 0x00 0xe00000>;
|
||||
no-map;
|
||||
};
|
||||
};
|
||||
|
||||
vmain_pd: regulator-0 {
|
||||
@@ -222,8 +260,9 @@
|
||||
pinctrl-single,pins = <
|
||||
AM62X_IOPAD(0x01b0, PIN_OUTPUT, 1) /* (A20) MCASP0_ACLKR.SPI2_CLK */
|
||||
AM62X_IOPAD(0x0194, PIN_INPUT, 1) /* (B19) MCASP0_AXR3.SPI2_D0 */
|
||||
AM62X_IOPAD(0x0198, PIN_INPUT, 1) /* (A19) MCASP0_AXR2.SPI2_D1 */
|
||||
AM62X_IOPAD(0x0198, PIN_OUTPUT, 1) /* (A19) MCASP0_AXR2.SPI2_D1 */
|
||||
AM62X_IOPAD(0x01ac, PIN_OUTPUT, 1) /* (E19) MCASP0_AFSR.SPI2_CS0 */
|
||||
AM62X_IOPAD(0x01a8, PIN_OUTPUT, 1) /* (D20) MCASP0_AFSX.SPI2_CS3 */
|
||||
>;
|
||||
};
|
||||
|
||||
@@ -257,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 */
|
||||
@@ -361,3 +401,11 @@
|
||||
status = "okay";
|
||||
dr_mode = "peripheral";
|
||||
};
|
||||
|
||||
&mcu_m4fss {
|
||||
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.
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
};
|
||||
|
||||
&main_spi2 {
|
||||
SLB9670: SLB9670@0 {
|
||||
slb9670: SLB9670@0 {
|
||||
compatible = "infineon,slb9670", "tis,tpm2-spi", "tcg,tpm_tis-spi";
|
||||
reg = <0>;
|
||||
#gpio-cells = <2>;
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
@@ -150,9 +150,10 @@ CONFIG_NET_ACT_POLICE=m
|
||||
CONFIG_NET_ACT_GACT=m
|
||||
CONFIG_NET_ACT_MIRRED=m
|
||||
CONFIG_NET_ACT_GATE=m
|
||||
CONFIG_NET_SWITCHDEV=y
|
||||
CONFIG_CGROUP_NET_PRIO=y
|
||||
# CONFIG_WIRELESS is not set
|
||||
CONFIG_NFC=y
|
||||
CONFIG_NFC_TAG_ST25DV=y
|
||||
CONFIG_PAGE_POOL_STATS=y
|
||||
CONFIG_DEVTMPFS=y
|
||||
CONFIG_DEVTMPFS_MOUNT=y
|
||||
@@ -258,6 +259,7 @@ CONFIG_SERIAL_8250_DW=y
|
||||
CONFIG_SERIAL_8250_OMAP=y
|
||||
CONFIG_SERIAL_OF_PLATFORM=y
|
||||
# CONFIG_SERIAL_OMAP_MODBUS is not set
|
||||
CONFIG_RPMSG_TTY=y
|
||||
CONFIG_HW_RANDOM=y
|
||||
CONFIG_TCG_TPM=y
|
||||
CONFIG_TCG_TIS=y
|
||||
@@ -339,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
|
||||
@@ -346,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
|
||||
@@ -370,6 +376,14 @@ CONFIG_IOMMU_IO_PGTABLE_ARMV7S=y
|
||||
CONFIG_IOMMU_IO_PGTABLE_DART=y
|
||||
CONFIG_ARM_SMMU=y
|
||||
CONFIG_ARM_SMMU_V3=y
|
||||
CONFIG_REMOTEPROC=y
|
||||
CONFIG_REMOTEPROC_CDEV=y
|
||||
CONFIG_TI_K3_DSP_REMOTEPROC=y
|
||||
CONFIG_TI_K3_M4_REMOTEPROC=y
|
||||
CONFIG_TI_K3_R5_REMOTEPROC=y
|
||||
CONFIG_RPMSG_CHAR=y
|
||||
CONFIG_RPMSG_CTRL=y
|
||||
CONFIG_RPMSG_VIRTIO=y
|
||||
CONFIG_SOC_BRCMSTB=y
|
||||
CONFIG_TI_SCI_PM_DOMAINS=y
|
||||
CONFIG_PM_DEVFREQ=y
|
||||
@@ -401,7 +415,6 @@ CONFIG_NTFS3_FS=m
|
||||
CONFIG_TMPFS=y
|
||||
CONFIG_TMPFS_POSIX_ACL=y
|
||||
CONFIG_HUGETLBFS=y
|
||||
CONFIG_CONFIGFS_FS=y
|
||||
CONFIG_SQUASHFS=y
|
||||
CONFIG_PSTORE=y
|
||||
CONFIG_PSTORE_CONSOLE=y
|
||||
@@ -450,8 +463,6 @@ CONFIG_SYSTEM_TRUSTED_KEYRING=y
|
||||
CONFIG_PACKING=y
|
||||
CONFIG_INDIRECT_PIO=y
|
||||
CONFIG_CRC_CCITT=m
|
||||
CONFIG_CRC_T10DIF=y
|
||||
CONFIG_CRC64_ROCKSOFT=y
|
||||
CONFIG_CRC_ITU_T=y
|
||||
CONFIG_CRC7=y
|
||||
CONFIG_CRC8=y
|
||||
@@ -459,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;
|
||||
@@ -3442,13 +3459,81 @@ static void spi_nor_set_mtd_info(struct spi_nor *nor)
|
||||
mtd->_put_device = spi_nor_put_device;
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_nor_everspin_reboot_fix - Forces Everspin MRAM out of Octal mode
|
||||
* @nor: pointer to 'struct spi_nor'
|
||||
*
|
||||
* This function sends a "blind" Software Reset sequence in Octal-STR (8-8-8).
|
||||
* It is required for warm reboots because the MRAM stays in Octal mode
|
||||
* while the controller restarts in Single-SPI mode.
|
||||
*/
|
||||
/**
|
||||
* spi_nor_everspin_reboot_fix - Blind reset for MRAM Octal-STR escape
|
||||
* @nor: pointer to 'struct spi_nor'
|
||||
*/
|
||||
static void spi_nor_everspin_reboot_fix(struct spi_nor *nor)
|
||||
{
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0x66, 8),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_NO_DATA);
|
||||
int ret;
|
||||
|
||||
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.
|
||||
*/
|
||||
dev_info(nor->dev, "MRAM Reboot-Fix: Sending blind Octal-STR reset (0x66->0x99)...\n");
|
||||
|
||||
/* 1. Reset Enable (0x66) in Octal-STR */
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
|
||||
/* 2. Reset (0x99) in Octal-STR */
|
||||
op.cmd.opcode = 0x99;
|
||||
ret |= spi_mem_exec_op(nor->spimem, &op);
|
||||
|
||||
if (ret) {
|
||||
/*
|
||||
* Note: A failure here is expected if the controller is
|
||||
* not yet ready for 8-lane commands or if the chip
|
||||
* is already in Single-SPI mode.
|
||||
*/
|
||||
dev_dbg(nor->dev, "MRAM Reboot-Fix: Octal reset op returned %d (normal for Single-SPI)\n", ret);
|
||||
}
|
||||
|
||||
/* Essential delay: allow MRAM internal state machine to recover */
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
dev_info(nor->dev, "MRAM Reboot-Fix: Completed. Chip should be in Single-SPI mode now.\n");
|
||||
}
|
||||
|
||||
static int spi_nor_hw_reset(struct spi_nor *nor)
|
||||
{
|
||||
struct gpio_desc *reset;
|
||||
|
||||
reset = devm_gpiod_get_optional(nor->dev, "reset", GPIOD_OUT_LOW);
|
||||
if (IS_ERR_OR_NULL(reset))
|
||||
return PTR_ERR_OR_ZERO(reset);
|
||||
if (IS_ERR(reset))
|
||||
return PTR_ERR(reset);
|
||||
|
||||
if (!reset) {
|
||||
/*
|
||||
* FALLBACK: No dedicated reset-pin found in DT.
|
||||
* Execute the blind Octal-STR reset to recover from warm reboots.
|
||||
*/
|
||||
spi_nor_everspin_reboot_fix(nor);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Experimental delay values by looking at different flash device
|
||||
|
||||
@@ -5,9 +5,394 @@
|
||||
*/
|
||||
|
||||
#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
|
||||
|
||||
#define EVERSPIN_MRAM_SPEED_MHZ 200000000
|
||||
|
||||
#define SPINOR_OP_MT_RD_ANY_REG 0x85 /* Read volatile configuration register */
|
||||
#define SPINOR_OP_MT_WR_ANY_REG 0x81 /* Write volatile configuration register */
|
||||
#define SPINOR_REG_MT_CFR0V 0x00 /* Address for Mode Configuration */
|
||||
#define SPINOR_REG_MT_CFR1V 0x01 /* Address for Dummy Cycle Configuration */
|
||||
#define SPINOR_MT_OCT_STR 0xB7 /* Enable Octal STR mode with DS */
|
||||
#define SPINOR_OP_EV_OCTAL_FAST_READ 0xCB /* Everspin Octal Fast Read (8-8-8) */
|
||||
#define SPINOR_OP_EV_OCTAL_PAGE_PROG 0x82 /* Everspin Octal Page Program (8-8-8) */
|
||||
|
||||
/**
|
||||
* everspin_mram_software_reset - Software Reset in Single-SPI mode
|
||||
*/
|
||||
static int everspin_mram_software_reset(struct spi_nor *nor)
|
||||
{
|
||||
struct spi_mem_op op;
|
||||
int ret;
|
||||
|
||||
/* Software Reset Enable (SPINOR_OP_SRSTEN: 0x66) */
|
||||
op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_SRSTEN, 1),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_NO_DATA);
|
||||
|
||||
ret = spi_nor_write_any_volatile_reg(nor, &op, nor->reg_proto);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Software Reset (SPINOR_OP_SRST: 0x99) */
|
||||
op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_SRST, 1),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_NO_DATA);
|
||||
|
||||
ret = spi_nor_write_any_volatile_reg(nor, &op, nor->reg_proto);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
udelay(100);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* everspin_mram_write_reg - Writes to configuration registers (4-byte addr)
|
||||
*/
|
||||
static int everspin_mram_write_reg(struct spi_nor *nor, u32 addr, u8 val)
|
||||
{
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_MT_WR_ANY_REG, 1),
|
||||
SPI_MEM_OP_ADDR(3, addr, 1),
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1));
|
||||
|
||||
nor->bouncebuf[0] = val;
|
||||
spi_nor_write_enable(nor);
|
||||
|
||||
return spi_nor_write_any_volatile_reg(nor, &op, SNOR_PROTO_1_1_1);
|
||||
}
|
||||
|
||||
/**
|
||||
* everspin_mram_read_reg_octal - Reads a register in 8s-8s-8s mode
|
||||
* Uses Opcode 0x85, 3-byte address, and 8 dummy cycles.
|
||||
*/
|
||||
static int everspin_mram_read_reg_octal(struct spi_nor *nor, u32 addr, u8 *val)
|
||||
{
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_MT_RD_ANY_REG, 8),
|
||||
SPI_MEM_OP_ADDR(3, addr, 8),
|
||||
SPI_MEM_OP_DUMMY(8, 8), /* Fixed 8 dummies for reg read */
|
||||
SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 8));
|
||||
int ret;
|
||||
|
||||
ret = spi_nor_read_any_reg(nor, &op, SNOR_PROTO_8_8_8);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = nor->bouncebuf[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* everspin_mram_write_reg_octal - Writes a register in 8s-8s-8s mode
|
||||
* Uses Opcode 0x81, 3-byte address, and 0 dummy cycles.
|
||||
*/
|
||||
static int everspin_mram_write_reg_octal(struct spi_nor *nor, u32 addr, u8 val)
|
||||
{
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_MT_WR_ANY_REG, 8),
|
||||
SPI_MEM_OP_ADDR(3, addr, 8),
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 8));
|
||||
|
||||
nor->bouncebuf[0] = val;
|
||||
|
||||
/* WREN is required before writing to volatile registers */
|
||||
spi_nor_write_enable(nor);
|
||||
|
||||
return spi_nor_write_any_volatile_reg(nor, &op, SNOR_PROTO_8_8_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* everspin_mram_unlock - Clears Block Protection bits
|
||||
*/
|
||||
static int everspin_mram_unlock(struct spi_nor *nor)
|
||||
{
|
||||
struct spi_mem_op op;
|
||||
|
||||
spi_nor_write_enable(nor);
|
||||
|
||||
nor->bouncebuf[0] = 0x00;
|
||||
op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 1),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1));
|
||||
|
||||
return spi_nor_write_any_volatile_reg(nor, &op, nor->reg_proto);
|
||||
}
|
||||
|
||||
static void everspin_mram_default_init(struct spi_nor *nor)
|
||||
{
|
||||
struct spi_mem_op op;
|
||||
struct spi_mem_op op_ri; /* for read id */
|
||||
int ret;
|
||||
|
||||
/* Do a Software Reset to get sure we are in a clean state.
|
||||
* Not necessary if a hardware reset is already done in NOR layer.
|
||||
*/
|
||||
everspin_mram_software_reset(nor);
|
||||
|
||||
dev_info(nor->dev, "Starting Everspin MRAM initialization (Octal-STR, %d Dummies) ...\n",
|
||||
EVERSPIN_MRAM_DUMMY_CYCLES);
|
||||
|
||||
/* Initial SR1 Check */
|
||||
op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 1),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 1));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/* Configuration: Set Dummy Cycles in CFR1V */
|
||||
everspin_mram_write_reg(nor, SPINOR_REG_MT_CFR1V, EVERSPIN_MRAM_DUMMY_CYCLES);
|
||||
|
||||
/* Verify WEL */
|
||||
ret = spi_nor_read_any_reg(nor, &op, SNOR_PROTO_1_1_1);
|
||||
if (!ret)
|
||||
dev_info(nor->dev, "SR1 after config: 0x%02x (WEL should be 0x00)\n", nor->bouncebuf[0]);
|
||||
|
||||
/* Setup Octal-STR with DS */
|
||||
everspin_mram_write_reg(nor, SPINOR_REG_MT_CFR0V, SPINOR_MT_OCT_STR);
|
||||
|
||||
/* Status Register Read (05h) in Octal STR Mode (8s-0-8s) with 8 Dummies */
|
||||
struct spi_mem_op op_rsr = SPI_MEM_OP(
|
||||
/* Command: Opcode 05h, sent on 8 lanes */
|
||||
SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 8),
|
||||
|
||||
/* Address: None */
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
|
||||
/* EVERSPIN_MRAM_DUMMY_CYCLES cycles, sent on 8 lanes */
|
||||
SPI_MEM_OP_DUMMY(EVERSPIN_MRAM_DUMMY_CYCLES, 8),
|
||||
|
||||
/* Data: 1 byte (Status), received on 8 lanes */
|
||||
SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 8)
|
||||
);
|
||||
|
||||
/* Execute using the framework helper with Octal-STR protocol */
|
||||
ret = spi_nor_read_any_reg(nor, &op_rsr, SNOR_PROTO_8_8_8);
|
||||
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);
|
||||
}
|
||||
|
||||
dev_info(nor->dev, "Going to access Read ID (0x9f) in 8s-0-8s Mode ...\n");
|
||||
|
||||
/* Read ID (0x9f) access in Octal-STR mode (8s-0-8s) with 8 Dummy Bytes */
|
||||
op_ri = (struct spi_mem_op)SPI_MEM_OP(
|
||||
/* Opcode 0x9F, 1 Byte long, sent on 8 lanes */
|
||||
SPI_MEM_OP_CMD(SPINOR_OP_RDID, 8),
|
||||
|
||||
/* No address phase */
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
|
||||
/* EVERSPIN_MRAM_DUMMY_CYCLES Dummy Cycles, sent on 8 lanes */
|
||||
SPI_MEM_OP_DUMMY(EVERSPIN_MRAM_DUMMY_CYCLES, 8),
|
||||
|
||||
/* Read 3 bytes of ID data, received on 8 lanes */
|
||||
SPI_MEM_OP_DATA_IN(3, nor->bouncebuf, 8)
|
||||
);
|
||||
|
||||
/* Protokoll 8-8-8 für 8s-0-8s (Octal STR) */
|
||||
ret = spi_nor_read_any_reg(nor, &op_ri, SNOR_PROTO_8_8_8);
|
||||
if (!ret) {
|
||||
/* Access the 3 bytes from bouncebuf */
|
||||
u8 manufacturer_id = nor->bouncebuf[0];
|
||||
u8 memory_type = nor->bouncebuf[1];
|
||||
u8 capacity = nor->bouncebuf[2];
|
||||
|
||||
/* Log the result in hex format */
|
||||
dev_info(nor->dev, "MRAM INFO: Octal Read ID: %02x %02x %02x.\n",
|
||||
manufacturer_id, memory_type, capacity);
|
||||
} else {
|
||||
dev_info(nor->dev, "Read ID Access in 8s-0-8s mode FAILED!\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* everspin_mram_ready_noop - MRAM is always ready, no polling needed
|
||||
*/
|
||||
static int everspin_mram_ready_noop(struct spi_nor *nor)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* everspin_mram_setup - Finalizes the SPI setup and escalates frequency
|
||||
* @nor: pointer to 'struct spi_nor'
|
||||
* @hwcaps: pointer to 'struct spi_nor_hwcaps'
|
||||
*
|
||||
* This hook is called after default_init/late_init but before the first
|
||||
* data transfer. It is the ideal place to switch from safe scan frequency
|
||||
* to high-performance operational frequency.
|
||||
*/
|
||||
static int everspin_mram_setup(struct spi_nor *nor,
|
||||
const struct spi_nor_hwcaps *hwcaps)
|
||||
{
|
||||
struct spi_nor_flash_parameter *params = nor->params;
|
||||
struct spi_device *spi = nor->spimem->spi;
|
||||
int ret;
|
||||
u8 val = 0;
|
||||
|
||||
/*
|
||||
* Request high speed frequency.
|
||||
*/
|
||||
spi->max_speed_hz = EVERSPIN_MRAM_SPEED_MHZ;
|
||||
|
||||
dev_info(nor->dev, "Escalating speed to %u Hz in setup hook\n",
|
||||
spi->max_speed_hz);
|
||||
|
||||
/* Update Dummy Bytes to serve the higher rate EVERSPIN_MRAM_SPEED_MHZ */
|
||||
nor->read_dummy = EVERSPIN_MRAM_DUMMY_CYCLES_FAST;
|
||||
spi_nor_set_read_settings(¶ms->reads[SNOR_CMD_READ], 0,
|
||||
EVERSPIN_MRAM_DUMMY_CYCLES_FAST,
|
||||
SPINOR_OP_EV_OCTAL_FAST_READ, SNOR_PROTO_8_8_8);
|
||||
everspin_mram_write_reg_octal(nor, SPINOR_REG_MT_CFR1V,
|
||||
EVERSPIN_MRAM_DUMMY_CYCLES_FAST);
|
||||
everspin_mram_read_reg_octal(nor, SPINOR_REG_MT_CFR1V, &val);
|
||||
|
||||
dev_info(nor->dev, "Updating Dummy Bytes to %d (read: %d)\n", nor->read_dummy, val);
|
||||
|
||||
/*
|
||||
* Force the controller driver (cadence-qspi) to re-run
|
||||
* cqspi_config_baudrate_div() and update hardware registers.
|
||||
*/
|
||||
ret = spi_setup(spi);
|
||||
if (ret) {
|
||||
dev_err(nor->dev, "Failed to update SPI setup (%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* everspin_mram_late_init - Final 8-8-8 STR configuration for EM008LXO
|
||||
*/
|
||||
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");
|
||||
|
||||
/* Bypass core hwcaps restrictions.
|
||||
* We need this as a workaround.
|
||||
*/
|
||||
params->hwcaps.mask |= SNOR_HWCAPS_READ | SNOR_HWCAPS_PP;
|
||||
|
||||
/* Global Octal STR Protocol Settings */
|
||||
nor->read_proto = SNOR_PROTO_8_8_8;
|
||||
nor->write_proto = SNOR_PROTO_8_8_8;
|
||||
nor->reg_proto = SNOR_PROTO_8_8_8;
|
||||
|
||||
nor->read_opcode = SPINOR_OP_EV_OCTAL_FAST_READ;
|
||||
/* Set global read dummy cycles */
|
||||
nor->read_dummy = EVERSPIN_MRAM_DUMMY_CYCLES;
|
||||
nor->program_opcode = SPINOR_OP_EV_OCTAL_PAGE_PROG;
|
||||
nor->addr_nbytes = 3;
|
||||
params->addr_nbytes = 3;
|
||||
|
||||
/* Disable WIP Polling (Fixes the 40s Timeout) */
|
||||
params->ready = everspin_mram_ready_noop;
|
||||
|
||||
/* Align Page Size for Controller Stability */
|
||||
/* Cadence OSPI handles 256-byte pages more reliably in Octal mode */
|
||||
params->page_size = 256;
|
||||
|
||||
/* Map Opcodes to standard slots for MTD core */
|
||||
spi_nor_set_read_settings(¶ms->reads[SNOR_CMD_READ], 0,
|
||||
EVERSPIN_MRAM_DUMMY_CYCLES,
|
||||
SPINOR_OP_EV_OCTAL_FAST_READ, SNOR_PROTO_8_8_8);
|
||||
|
||||
spi_nor_set_pp_settings(¶ms->page_programs[SNOR_CMD_PP],
|
||||
SPINOR_OP_EV_OCTAL_PAGE_PROG, SNOR_PROTO_8_8_8);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups everspin_mram_fixups = {
|
||||
.default_init = everspin_mram_default_init,
|
||||
.late_init = everspin_mram_late_init,
|
||||
};
|
||||
|
||||
static const struct flash_info everspin_nor_parts[] = {
|
||||
/* Everspin */
|
||||
{ "mr25h128", CAT25_INFO(16 * 1024, 1, 256, 2) },
|
||||
@@ -19,22 +404,28 @@ static const struct flash_info everspin_nor_parts[] = {
|
||||
static const struct flash_info everspin_mram_parts[] = {
|
||||
/* Everspin */
|
||||
{ "em256lx", INFO(0x6bbb19, 0, 32 * 1024 * 1024, 1)
|
||||
FLAGS(SPI_NOR_NO_ERASE)
|
||||
FLAGS(SPI_NOR_NO_ERASE)
|
||||
.fixups = &everspin_mram_fixups,
|
||||
},
|
||||
{ "em128lx", INFO(0x6bbb18, 0, 16 * 1024 * 1024, 1)
|
||||
FLAGS(SPI_NOR_NO_ERASE)
|
||||
FLAGS(SPI_NOR_NO_ERASE)
|
||||
.fixups = &everspin_mram_fixups,
|
||||
},
|
||||
{ "em064lx", INFO(0x6bbb17, 0, 8 * 1024 * 1024, 1)
|
||||
FLAGS(SPI_NOR_NO_ERASE)
|
||||
FLAGS(SPI_NOR_NO_ERASE)
|
||||
.fixups = &everspin_mram_fixups,
|
||||
},
|
||||
{ "em032lx", INFO(0x6bbb16, 0, 4 * 1024 * 1024, 1)
|
||||
FLAGS(SPI_NOR_NO_ERASE)
|
||||
FLAGS(SPI_NOR_NO_ERASE)
|
||||
.fixups = &everspin_mram_fixups,
|
||||
},
|
||||
{ "em016lx", INFO(0x6bbb15, 0, 2 * 1024 * 1024, 1)
|
||||
FLAGS(SPI_NOR_NO_ERASE)
|
||||
FLAGS(SPI_NOR_NO_ERASE)
|
||||
.fixups = &everspin_mram_fixups,
|
||||
},
|
||||
{ "em008lx", INFO(0x6bbb14, 0, 1 * 1024 * 1024, 1)
|
||||
FLAGS(SPI_NOR_NO_ERASE)
|
||||
FLAGS(SPI_NOR_NO_ERASE)
|
||||
.fixups = &everspin_mram_fixups,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -39,3 +39,10 @@ config NET_DSA_MICROCHIP_KSZ8863_SMI
|
||||
help
|
||||
Select to enable support for registering switches configured through
|
||||
Microchip SMI. It supports the KSZ8863 and KSZ8873 switch.
|
||||
|
||||
config NET_DSA_MICROCHIP_KSZ9477_SYSFS
|
||||
tristate "SYSFS interface for userspace KSZ register access"
|
||||
depends on NET_DSA_MICROCHIP_KSZ_COMMON
|
||||
help
|
||||
Select to enable support for accessing KSZ registers in userspace
|
||||
through the sysfs interface.
|
||||
|
||||
@@ -15,3 +15,4 @@ ksz_switch-objs += ksz_devlink.o
|
||||
obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ9477_I2C) += ksz9477_i2c.o
|
||||
obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ_SPI) += ksz_spi.o
|
||||
obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ8863_SMI) += ksz8863_smi.o
|
||||
obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ9477_SYSFS) += ksz9477_sysfs.o
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "ksz9477_reg.h"
|
||||
#include "ksz_common.h"
|
||||
#include "ksz9477.h"
|
||||
#include "ksz9477_sysfs.h"
|
||||
|
||||
static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set)
|
||||
{
|
||||
@@ -1185,6 +1186,10 @@ int ksz9477_switch_init(struct ksz_device *dev)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
#ifdef CONFIG_NET_DSA_MICROCHIP_KSZ9477_SYSFS
|
||||
ksz_sysfs_register(dev);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* KSZ9477 Sysfs Register Access (Bypassing Regmap Whitelist)
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/regmap.h> // NOTE: Required for direct regmap access
|
||||
|
||||
#include "ksz_common.h"
|
||||
#include "ksz9477_reg.h"
|
||||
#include "ksz9477_sysfs.h"
|
||||
|
||||
#define to_kszdev(d) container_of(d, struct ksz_device, dev)
|
||||
|
||||
struct ksz_device *g_kdev = NULL;
|
||||
|
||||
static ssize_t ksz_read_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
u32 reg, value = 0;
|
||||
int ret, bit;
|
||||
char end;
|
||||
|
||||
if (!g_kdev || !g_kdev->regmap)
|
||||
return -EINVAL;
|
||||
|
||||
ret = sscanf(buf, "%d %x%c", &bit, ®, &end);
|
||||
if (ret < 1) return -EINVAL;
|
||||
|
||||
/*
|
||||
* Use regmap_bulk_read to bypass the kernel's internal address
|
||||
* validation. regmap_read would immediately return -EIO for
|
||||
* 'illegal' addresses.
|
||||
*/
|
||||
switch(bit) {
|
||||
case 8:
|
||||
ret = regmap_bulk_read(g_kdev->regmap[0], reg, (u8*)&value, 1);
|
||||
break;
|
||||
case 16:
|
||||
/* KSZ expects big-endian on the bus, regmap bulk handles that */
|
||||
ret = regmap_bulk_read(g_kdev->regmap[1], reg, (u16*)&value, 1);
|
||||
break;
|
||||
case 32:
|
||||
ret = regmap_bulk_read(g_kdev->regmap[2], reg, &value, 1);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Read failed at 0x%x with error %d\n", reg, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_info(dev, "%d-bit read: reg 0x%04x: 0x%08x.\n", bit, reg, value);
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_WO(ksz_read);
|
||||
|
||||
static ssize_t ksz_write_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
u32 reg, value;
|
||||
int ret, bit;
|
||||
char end;
|
||||
|
||||
if (!g_kdev || !g_kdev->regmap)
|
||||
return -EINVAL;
|
||||
|
||||
ret = sscanf(buf, "%d %x %x%c", &bit, ®, &value, &end);
|
||||
if (ret < 1) return -EINVAL;
|
||||
|
||||
switch(bit) {
|
||||
case 8:
|
||||
ret = regmap_bulk_write(g_kdev->regmap[0], reg, (u8*)&value, 1);
|
||||
break;
|
||||
case 16:
|
||||
ret = regmap_bulk_write(g_kdev->regmap[1], reg, (u16*)&value, 1);
|
||||
break;
|
||||
case 32:
|
||||
ret = regmap_bulk_write(g_kdev->regmap[2], reg, &value, 1);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Write failed at 0x%x\n", reg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_info(dev, "%d-bit write: reg 0x%04x value 0x%08x.\n", bit, reg, value);
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_WO(ksz_write);
|
||||
|
||||
int ksz_sysfs_register(struct ksz_device *kdev)
|
||||
{
|
||||
struct device *dev = kdev->dev;
|
||||
int ret;
|
||||
|
||||
if (!kdev)
|
||||
return -EINVAL;
|
||||
|
||||
ret = device_create_file(dev, &dev_attr_ksz_read);
|
||||
if (ret)
|
||||
dev_err(dev, "Could not create <%s> attribute.\n", "ksz_read");
|
||||
|
||||
ret = device_create_file(dev, &dev_attr_ksz_write);
|
||||
if (ret)
|
||||
dev_err(dev, "Could not create <%s> attribute.\n", "ksz_write");
|
||||
|
||||
g_kdev = kdev;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ksz_sysfs_unregister(struct ksz_device *kdev)
|
||||
{
|
||||
struct device *dev = kdev->dev;
|
||||
|
||||
g_kdev = NULL;
|
||||
|
||||
device_remove_file(dev, &dev_attr_ksz_read);
|
||||
device_remove_file(dev, &dev_attr_ksz_write);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef KSZ9477_SYSFS_H_
|
||||
#define KSZ9477_SYSFS_H_
|
||||
|
||||
int ksz_sysfs_register(struct ksz_device *kdev);
|
||||
void ksz_sysfs_unregister(struct ksz_device *kdev);
|
||||
|
||||
#endif /* KSZ9477_SYSFS_H_ */
|
||||
@@ -447,6 +447,9 @@ static const u16 ksz9477_regs[] = {
|
||||
[S_MULTICAST_CTRL] = 0x0331,
|
||||
[P_XMII_CTRL_0] = 0x0300,
|
||||
[P_XMII_CTRL_1] = 0x0301,
|
||||
[P_PHY_MMD_SETUP] = 0x011A,
|
||||
[P_PHY_MMD_DATA] = 0x011C,
|
||||
[P_PHY_DIGITAL_DEBUG_3] = 0x013C,
|
||||
};
|
||||
|
||||
static const u32 ksz9477_masks[] = {
|
||||
@@ -2955,6 +2958,39 @@ static void ksz_set_gbit(struct ksz_device *dev, int port, bool gbit)
|
||||
ksz_pwrite8(dev, port, regs[P_XMII_CTRL_1], data8);
|
||||
}
|
||||
|
||||
static void ksz_enable_single_led_mode(struct ksz_device *dev, int port)
|
||||
{
|
||||
const u16 *regs = dev->info->regs;
|
||||
u16 data16;
|
||||
|
||||
dev_info(dev->dev, "port-%d: enable single-led mode.\n", port);
|
||||
|
||||
/* Set up register address for MMD */
|
||||
data16 = 0x0002;
|
||||
ksz_pwrite16(dev, port, regs[P_PHY_MMD_SETUP], data16);
|
||||
|
||||
/* Select Register 00h of MMD */
|
||||
data16 = 0x0000;
|
||||
ksz_pwrite16(dev, port, regs[P_PHY_MMD_DATA], data16);
|
||||
|
||||
/* Select register data for MMD */
|
||||
data16 = 0x4002;
|
||||
ksz_pwrite16(dev, port, regs[P_PHY_MMD_SETUP], data16);
|
||||
|
||||
/* Write value 0010h to MMD */
|
||||
data16 = 0x0011;
|
||||
ksz_pwrite16(dev, port, regs[P_PHY_MMD_DATA], data16);
|
||||
|
||||
/* Due to Errata Module 19 this workaround is needed to
|
||||
* enable single-led mode. It has also to be a 32bit
|
||||
* write.
|
||||
*/
|
||||
dev_info(dev->dev, "port-%d: applying errata (Module 19) " \
|
||||
"workaround.\n", port);
|
||||
data16 = 0xfa00;
|
||||
ksz_pwrite16(dev, port, regs[P_PHY_DIGITAL_DEBUG_3], data16);
|
||||
}
|
||||
|
||||
static void ksz_set_100_10mbit(struct ksz_device *dev, int port, int speed)
|
||||
{
|
||||
const u8 *bitval = dev->info->xmii_ctrl0;
|
||||
@@ -3020,6 +3056,9 @@ static void ksz9477_phylink_mac_link_up(struct ksz_device *dev, int port,
|
||||
{
|
||||
struct ksz_port *p;
|
||||
|
||||
if (dev->single_led_mode && port != dev->cpu_port)
|
||||
ksz_enable_single_led_mode(dev, port);
|
||||
|
||||
p = &dev->ports[port];
|
||||
|
||||
/* Internal PHYs */
|
||||
@@ -3744,6 +3783,8 @@ int ksz_switch_register(struct ksz_device *dev)
|
||||
ret = of_property_read_u8(dev->dev->of_node, "microchip,led-mode", &dev->led_mode);
|
||||
if (ret)
|
||||
dev->led_mode = 0;
|
||||
dev->single_led_mode = of_property_read_bool(dev->dev->of_node,
|
||||
"microchip,single-led-mode");
|
||||
}
|
||||
|
||||
ret = dsa_register_switch(dev->ds);
|
||||
|
||||
@@ -150,6 +150,7 @@ struct ksz_device {
|
||||
struct gpio_desc *reset_gpio; /* Optional reset GPIO */
|
||||
bool disable_internal_ldo; /* Disable internal 1.8V LDO */
|
||||
u8 led_mode;
|
||||
bool single_led_mode; /* Enable Single LED Mode */
|
||||
/* chip specific data */
|
||||
u32 chip_id;
|
||||
u8 chip_rev;
|
||||
@@ -232,6 +233,9 @@ enum ksz_regs {
|
||||
S_MULTICAST_CTRL,
|
||||
P_XMII_CTRL_0,
|
||||
P_XMII_CTRL_1,
|
||||
P_PHY_MMD_SETUP,
|
||||
P_PHY_MMD_DATA,
|
||||
P_PHY_DIGITAL_DEBUG_3,
|
||||
};
|
||||
|
||||
enum ksz_masks {
|
||||
|
||||
@@ -70,4 +70,5 @@ source "drivers/nfc/st-nci/Kconfig"
|
||||
source "drivers/nfc/nxp-nci/Kconfig"
|
||||
source "drivers/nfc/s3fwrn5/Kconfig"
|
||||
source "drivers/nfc/st95hf/Kconfig"
|
||||
source "drivers/nfc/tag/Kconfig"
|
||||
endmenu
|
||||
|
||||
@@ -18,3 +18,4 @@ obj-$(CONFIG_NFC_NXP_NCI) += nxp-nci/
|
||||
obj-$(CONFIG_NFC_S3FWRN5) += s3fwrn5/
|
||||
obj-$(CONFIG_NFC_ST95HF) += st95hf/
|
||||
obj-$(CONFIG_NFC_VIRTUAL_NCI) += virtual_ncidev.o
|
||||
obj-y += tag/
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
menu "NFC Tag devices"
|
||||
depends on NFC
|
||||
|
||||
source "drivers/nfc/tag/st25dv/Kconfig"
|
||||
endmenu
|
||||
@@ -0,0 +1,6 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Makefile for nfc tag devices
|
||||
#
|
||||
|
||||
obj-$(CONFIG_NFC_TAG_ST25DV) += st25dv/
|
||||
@@ -0,0 +1,6 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config NFC_TAG_ST25DV
|
||||
tristate "STMicroelectronics ST25DVxxK nfc tag driver"
|
||||
depends on I2C
|
||||
help
|
||||
This is the STMicroelectronics NFC Tag driver for the ST25DVxxK devices.
|
||||
@@ -0,0 +1,7 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Makefile for ST25DV NFC driver
|
||||
#
|
||||
|
||||
# st25dv-y += st25dv.o
|
||||
obj-$(CONFIG_NFC_TAG_ST25DV) += st25dv.o
|
||||
@@ -0,0 +1,443 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2018 Loïc Boban <loic.boban@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
#define DYN_REG_SIZE 0x8
|
||||
#define SYS_MEM_SIZE 0x24
|
||||
#define USER_MEM_SIZE 0x200
|
||||
#define MAILBOX_MEM_SIZE 0x100
|
||||
#define SYS_ADDR 0x57
|
||||
#define USER_ADDR 0x53
|
||||
#define PWD_OFF 0x0900
|
||||
#define DYN_REG_OFF 0x2000
|
||||
#define MAILBOX_OFF 0x2008
|
||||
#define MAX_TRY 10
|
||||
|
||||
#define PWD_REQ_SIZE 0x13
|
||||
#define PWD_SIZE 0x08
|
||||
#define CMD_PRESENT_PWD 0x09
|
||||
#define CMD_WRITE_PWD 0x07
|
||||
#define PWD_CMD_POS 10
|
||||
#define PWD1_POS 2
|
||||
#define PWD2_POS 11
|
||||
|
||||
#define MEM_04K 512
|
||||
#define MEM_16K 2000
|
||||
#define MEM_64K 8000
|
||||
|
||||
static const struct bin_attribute st25dv_p_pwd_attr;
|
||||
|
||||
enum area_type {
|
||||
USER_AREA = 0,
|
||||
SYS_AREA = 1,
|
||||
DYN_REG_AREA = 2,
|
||||
MAILBOX_AREA = 3,
|
||||
};
|
||||
|
||||
/*
|
||||
* The ST25DV EEPROM has two areas: the user area and the system
|
||||
* area to manage read/write protection for the NFC interface and
|
||||
* I2C interface. To drive the system area, a dummy i2c_client is used.
|
||||
*/
|
||||
static int mem_config[] = {MEM_04K, MEM_04K, MEM_16K, MEM_64K};
|
||||
static int area_off[] = {0, 0, DYN_REG_OFF, MAILBOX_OFF};
|
||||
|
||||
/* one struct is used for each area */
|
||||
struct st25dv_data {
|
||||
u8 *data; /* area data */
|
||||
enum area_type type;
|
||||
struct bin_attribute bin_attr;
|
||||
struct i2c_client *client;
|
||||
struct mutex *update_lock; /* protect for concurrent updates */
|
||||
struct st25dv_data *next;
|
||||
};
|
||||
|
||||
/* Addresses to scan */
|
||||
static const unsigned short normal_i2c[] = { USER_ADDR, SYS_ADDR, I2C_CLIENT_END };
|
||||
|
||||
static ssize_t st25dv_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count)
|
||||
{
|
||||
int r_size, nack;
|
||||
struct i2c_client *client = to_i2c_client(kobj_to_dev(kobj));
|
||||
struct st25dv_data *data = i2c_get_clientdata(client);
|
||||
u16 cur_off;
|
||||
|
||||
while (&data->bin_attr != bin_attr)
|
||||
data = data->next;
|
||||
|
||||
off += area_off[data->type];
|
||||
mutex_lock(data->update_lock);
|
||||
for (cur_off = off; cur_off-off < count; cur_off++) {
|
||||
nack = 0;
|
||||
retry_:
|
||||
if (nack > MAX_TRY) {
|
||||
mutex_unlock(data->update_lock);
|
||||
return r_size;
|
||||
}
|
||||
r_size = i2c_smbus_write_byte_data(client, (cur_off >> 8) & 0x0ff, cur_off & 0x0ff);
|
||||
if (r_size < 0) {
|
||||
nack++;
|
||||
udelay(150);
|
||||
goto retry_;
|
||||
}
|
||||
r_size = i2c_smbus_read_byte(client);
|
||||
if (r_size < 0) {
|
||||
nack++;
|
||||
udelay(150);
|
||||
goto retry_;
|
||||
}
|
||||
data->data[cur_off] = r_size;
|
||||
}
|
||||
memcpy(buf, &data->data[off], count);
|
||||
mutex_unlock(data->update_lock);
|
||||
|
||||
dev_dbg(&client->dev, "%zu bytes read\n", count);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* I2C_SMBUS_BLOCK_MAX = 9 page writes
|
||||
* MAX tw = 9 * 5ms
|
||||
* write_block is faster than write single byte but not supported by some adapter
|
||||
*/
|
||||
static ssize_t st25dv_write_block(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count)
|
||||
{
|
||||
int r_size, to_write, not_write;
|
||||
struct i2c_client *client = to_i2c_client(kobj_to_dev(kobj));
|
||||
struct st25dv_data *data = i2c_get_clientdata(client);
|
||||
u16 cur_off, cur_buf_off;
|
||||
u8 nack, tmp[I2C_SMBUS_BLOCK_MAX];
|
||||
|
||||
while (&data->bin_attr != bin_attr)
|
||||
data = data->next;
|
||||
|
||||
off += area_off[data->type];
|
||||
mutex_lock(data->update_lock);
|
||||
memcpy(data->data + off, buf, count);
|
||||
not_write = count;
|
||||
cur_off = off;
|
||||
cur_buf_off = 0;
|
||||
while (not_write) {
|
||||
nack = 0;
|
||||
to_write = not_write > I2C_SMBUS_BLOCK_MAX-2 ? I2C_SMBUS_BLOCK_MAX-2 : not_write;
|
||||
not_write -= to_write;
|
||||
tmp[1] = cur_off;
|
||||
tmp[0] = cur_off >> 8;
|
||||
memcpy(tmp + 2, buf + cur_buf_off, to_write);
|
||||
retry_:
|
||||
if (nack > MAX_TRY) {
|
||||
mutex_unlock(data->update_lock);
|
||||
return r_size;
|
||||
}
|
||||
r_size = i2c_master_send(client, tmp, to_write+2);
|
||||
if (r_size < 0) {
|
||||
nack++;
|
||||
mdelay(5);
|
||||
goto retry_;
|
||||
}
|
||||
mdelay(20);
|
||||
cur_off += to_write;
|
||||
cur_buf_off += to_write;
|
||||
}
|
||||
|
||||
mutex_unlock(data->update_lock);
|
||||
dev_dbg(&client->dev, "%zu bytes written\n", count);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t st25dv_send_pwd_req(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
u8 pwd_req[PWD_REQ_SIZE], cmd, *pwd_ptr1, *pwd_ptr2;
|
||||
int r_size, off_tmp, nack;
|
||||
struct i2c_client *client = to_i2c_client(kobj_to_dev(kobj));
|
||||
struct st25dv_data *data = i2c_get_clientdata(client);
|
||||
|
||||
cmd = bin_attr == &st25dv_p_pwd_attr ? CMD_PRESENT_PWD : CMD_WRITE_PWD;
|
||||
if (count != PWD_SIZE) {
|
||||
dev_err(&client->dev, "send pwd cmd fail: count=%d\n", (int)count);
|
||||
return count;
|
||||
}
|
||||
nack = 0;
|
||||
pwd_req[0] = 0x09;
|
||||
pwd_req[1] = 0x00;
|
||||
pwd_ptr1 = &pwd_req[PWD1_POS];
|
||||
pwd_ptr2 = &pwd_req[PWD2_POS];
|
||||
pwd_req[PWD_CMD_POS] = cmd;
|
||||
for (off_tmp = PWD_SIZE-1; off_tmp >= 0; off_tmp--) {
|
||||
*pwd_ptr1 = buf[off_tmp];
|
||||
*pwd_ptr2 = buf[off_tmp];
|
||||
pwd_ptr1++;
|
||||
pwd_ptr2++;
|
||||
}
|
||||
mutex_lock(data->update_lock);
|
||||
retry_:
|
||||
if (nack > MAX_TRY) {
|
||||
mutex_unlock(data->update_lock);
|
||||
return r_size;
|
||||
}
|
||||
r_size = i2c_master_send(client, pwd_req, PWD_REQ_SIZE);
|
||||
if (r_size < 0) {
|
||||
nack++;
|
||||
mdelay(5);
|
||||
goto retry_;
|
||||
}
|
||||
mutex_unlock(data->update_lock);
|
||||
|
||||
dev_info(&client->dev, "%s password command successful\n",
|
||||
(cmd == CMD_PRESENT_PWD) ? "Presentation" : "Write");
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct bin_attribute st25dv_user_attr = {
|
||||
.attr = { .name = "st25dv_user", .mode = 0666 },
|
||||
.size = USER_MEM_SIZE,
|
||||
.read = st25dv_read,
|
||||
.write = st25dv_write_block,
|
||||
};
|
||||
|
||||
static const struct bin_attribute st25dv_sys_attr = {
|
||||
.attr = { .name = "st25dv_sys", .mode = 0644 },
|
||||
.size = SYS_MEM_SIZE,
|
||||
.read = st25dv_read,
|
||||
.write = st25dv_write_block,
|
||||
};
|
||||
|
||||
static const struct bin_attribute st25dv_dyn_reg_attr = {
|
||||
.attr = { .name = "st25dv_dyn_reg", .mode = 0644 },
|
||||
.size = DYN_REG_SIZE,
|
||||
.read = st25dv_read,
|
||||
.write = st25dv_write_block,
|
||||
};
|
||||
|
||||
static const struct bin_attribute st25dv_mailbox_attr = {
|
||||
.attr = { .name = "st25dv_mailbox", .mode = 0666 },
|
||||
.size = MAILBOX_MEM_SIZE,
|
||||
.read = st25dv_read,
|
||||
.write = st25dv_write_block,
|
||||
};
|
||||
|
||||
static const struct bin_attribute st25dv_w_pwd_attr = {
|
||||
.attr = { .name = "st25dv_write_pwd", .mode = 0200 },
|
||||
.size = PWD_SIZE,
|
||||
.write = st25dv_send_pwd_req,
|
||||
};
|
||||
|
||||
static const struct bin_attribute st25dv_p_pwd_attr = {
|
||||
.attr = { .name = "st25dv_present_pwd", .mode = 0200 },
|
||||
.size = PWD_SIZE,
|
||||
.write = st25dv_send_pwd_req,
|
||||
};
|
||||
|
||||
/* Return 0 if detection is successful, -ENODEV otherwise */
|
||||
static int st25dv_detect(struct i2c_client *client, struct i2c_board_info *info)
|
||||
{
|
||||
struct i2c_adapter *adapter = client->adapter;
|
||||
|
||||
if (!(adapter->class & I2C_CLASS_SPD) && (client->addr != USER_ADDR))
|
||||
return -ENODEV;
|
||||
|
||||
if (!i2c_new_dummy_device(client->adapter, SYS_ADDR))
|
||||
return -ENODEV;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_WORD_DATA)
|
||||
&& !i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
|
||||
dev_err(&client->dev, "ST25DV detected at 0x%02x but lacks required functionality\n",
|
||||
client->addr);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dev_info(&client->dev, "ST25DV EEPROM detected at 0x%02x\n", client->addr);
|
||||
|
||||
strscpy(info->type, "st25dv", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int st25dv_probe(struct i2c_client *client)
|
||||
{
|
||||
const struct i2c_device_id *id = i2c_client_get_device_id(client);
|
||||
struct st25dv_data *data, *sys_data, *dyn_reg_data, *mailbox_data;
|
||||
struct i2c_client *client_sys_area;
|
||||
struct mutex *st25dv_lock;
|
||||
int status;
|
||||
|
||||
/* Dummy client for the system area at addr 0x57 */
|
||||
client_sys_area = i2c_new_dummy_device(client->adapter, SYS_ADDR);
|
||||
if (!client_sys_area) {
|
||||
dev_err(&client->dev, "st25dv sys eeprom not detected\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dev_set_name(&client_sys_area->dev, "st25dv-sys-%d-%04x",
|
||||
i2c_adapter_id(client->adapter), client_sys_area->addr);
|
||||
|
||||
/* Register action to unregister dummy device automatically */
|
||||
status = devm_add_action_or_reset(&client->dev,
|
||||
(void(*)(void *))i2c_unregister_device,
|
||||
client_sys_area);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
/* Allocate lock and data structures */
|
||||
st25dv_lock = devm_kzalloc(&client->dev, sizeof(*st25dv_lock), GFP_KERNEL);
|
||||
data = devm_kmalloc(&client->dev, sizeof(*data), GFP_KERNEL);
|
||||
sys_data = devm_kmalloc(&client_sys_area->dev, sizeof(*sys_data), GFP_KERNEL);
|
||||
dyn_reg_data = devm_kmalloc(&client->dev, sizeof(*dyn_reg_data), GFP_KERNEL);
|
||||
mailbox_data = devm_kmalloc(&client->dev, sizeof(*mailbox_data), GFP_KERNEL);
|
||||
|
||||
if (!st25dv_lock || !data || !sys_data || !dyn_reg_data || !mailbox_data)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(st25dv_lock);
|
||||
|
||||
/* Allocate and initialize buffers with 0xff */
|
||||
data->data = devm_kmalloc(&client->dev, mem_config[id->driver_data], GFP_KERNEL);
|
||||
sys_data->data = devm_kmalloc(&client_sys_area->dev, SYS_MEM_SIZE, GFP_KERNEL);
|
||||
dyn_reg_data->data = devm_kmalloc(&client->dev, DYN_REG_SIZE, GFP_KERNEL);
|
||||
mailbox_data->data = devm_kmalloc(&client->dev, MAILBOX_MEM_SIZE, GFP_KERNEL);
|
||||
|
||||
if (!data->data || !sys_data->data || !dyn_reg_data->data || !mailbox_data->data)
|
||||
return -ENOMEM;
|
||||
|
||||
memset(data->data, 0xff, mem_config[id->driver_data]);
|
||||
memset(sys_data->data, 0xff, SYS_MEM_SIZE);
|
||||
memset(dyn_reg_data->data, 0xff, DYN_REG_SIZE);
|
||||
memset(mailbox_data->data, 0xff, MAILBOX_MEM_SIZE);
|
||||
|
||||
/* Setup User Area */
|
||||
data->client = client;
|
||||
data->update_lock = st25dv_lock;
|
||||
data->next = dyn_reg_data;
|
||||
data->type = USER_AREA;
|
||||
memcpy(&data->bin_attr, &st25dv_user_attr, sizeof(struct bin_attribute));
|
||||
data->bin_attr.size = mem_config[id->driver_data];
|
||||
|
||||
/* Setup Dynamic Registers Area */
|
||||
dyn_reg_data->client = client;
|
||||
dyn_reg_data->update_lock = st25dv_lock;
|
||||
dyn_reg_data->next = mailbox_data;
|
||||
dyn_reg_data->type = DYN_REG_AREA;
|
||||
memcpy(&dyn_reg_data->bin_attr, &st25dv_dyn_reg_attr, sizeof(struct bin_attribute));
|
||||
|
||||
/* Setup Mailbox Area */
|
||||
mailbox_data->client = client;
|
||||
mailbox_data->update_lock = st25dv_lock;
|
||||
mailbox_data->next = sys_data;
|
||||
mailbox_data->type = MAILBOX_AREA;
|
||||
memcpy(&mailbox_data->bin_attr, &st25dv_mailbox_attr, sizeof(struct bin_attribute));
|
||||
|
||||
/* Setup System Area */
|
||||
sys_data->client = client_sys_area;
|
||||
sys_data->update_lock = st25dv_lock;
|
||||
sys_data->next = data;
|
||||
sys_data->type = SYS_AREA;
|
||||
memcpy(&sys_data->bin_attr, &st25dv_sys_attr, sizeof(struct bin_attribute));
|
||||
|
||||
i2c_set_clientdata(client, data);
|
||||
i2c_set_clientdata(client_sys_area, sys_data);
|
||||
|
||||
/* Create sysfs files - labels only for sysfs cleanup */
|
||||
status = sysfs_create_bin_file(&client->dev.kobj, &data->bin_attr);
|
||||
if (status < 0) goto err_sysfs;
|
||||
status = sysfs_create_bin_file(&client_sys_area->dev.kobj, &sys_data->bin_attr);
|
||||
if (status < 0) goto err_sysfs4;
|
||||
status = sysfs_create_bin_file(&client->dev.kobj, &dyn_reg_data->bin_attr);
|
||||
if (status < 0) goto err_sysfs3;
|
||||
status = sysfs_create_bin_file(&client_sys_area->dev.kobj, &st25dv_w_pwd_attr);
|
||||
if (status < 0) goto err_sysfs2;
|
||||
status = sysfs_create_bin_file(&client_sys_area->dev.kobj, &st25dv_p_pwd_attr);
|
||||
if (status < 0) goto err_sysfs1;
|
||||
status = sysfs_create_bin_file(&client->dev.kobj, &mailbox_data->bin_attr);
|
||||
if (status < 0) goto err_sysfs0;
|
||||
|
||||
dev_info(&client->dev, "ST25DV %s detected, %d bytes user memory\n",
|
||||
id->name, mem_config[id->driver_data]);
|
||||
|
||||
return 0;
|
||||
|
||||
err_sysfs0:
|
||||
sysfs_remove_bin_file(&client_sys_area->dev.kobj, &st25dv_p_pwd_attr);
|
||||
err_sysfs1:
|
||||
sysfs_remove_bin_file(&client_sys_area->dev.kobj, &st25dv_w_pwd_attr);
|
||||
err_sysfs2:
|
||||
sysfs_remove_bin_file(&client->dev.kobj, &dyn_reg_data->bin_attr);
|
||||
err_sysfs3:
|
||||
sysfs_remove_bin_file(&client_sys_area->dev.kobj, &sys_data->bin_attr);
|
||||
err_sysfs4:
|
||||
sysfs_remove_bin_file(&client->dev.kobj, &data->bin_attr);
|
||||
err_sysfs:
|
||||
dev_err(&client->dev, "failed to create bin file: %d\n", status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void st25dv_remove(struct i2c_client *client)
|
||||
{
|
||||
struct st25dv_data *data = i2c_get_clientdata(client);
|
||||
struct st25dv_data *tmp = data;
|
||||
|
||||
do {
|
||||
if (tmp->type == SYS_AREA) {
|
||||
sysfs_remove_bin_file(&tmp->client->dev.kobj, &st25dv_p_pwd_attr);
|
||||
sysfs_remove_bin_file(&tmp->client->dev.kobj, &st25dv_w_pwd_attr);
|
||||
}
|
||||
sysfs_remove_bin_file(&tmp->client->dev.kobj, &tmp->bin_attr);
|
||||
tmp = tmp->next;
|
||||
} while (tmp != data);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id st25dv_id[] = {
|
||||
{ "st25dv", 0 },
|
||||
{ "st25dv04k", 1 },
|
||||
{ "st25dv16k", 2 },
|
||||
{ "st25dv64k", 3 },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct i2c_driver st25dv_driver = {
|
||||
.driver = {
|
||||
.name = "st25dv",
|
||||
},
|
||||
.probe = st25dv_probe,
|
||||
.remove = st25dv_remove,
|
||||
.id_table = st25dv_id,
|
||||
|
||||
.class = I2C_CLASS_DDC | I2C_CLASS_SPD,
|
||||
.detect = st25dv_detect,
|
||||
.address_list = normal_i2c,
|
||||
};
|
||||
|
||||
static int __init st25dv_i2c_init_driver(void)
|
||||
{
|
||||
return i2c_add_driver(&st25dv_driver);
|
||||
}
|
||||
|
||||
static void __exit st25dv_i2c_exit_driver(void)
|
||||
{
|
||||
i2c_del_driver(&st25dv_driver);
|
||||
}
|
||||
|
||||
module_init(st25dv_i2c_init_driver);
|
||||
module_exit(st25dv_i2c_exit_driver);
|
||||
|
||||
MODULE_INFO(intree, "y");
|
||||
MODULE_AUTHOR("Loïc Boban <loic.boban@gmail.com>");
|
||||
MODULE_DESCRIPTION("nfc/i2c eeprom st25dv driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -339,6 +339,19 @@ config TI_K3_DSP_REMOTEPROC
|
||||
It's safe to say N here if you're not interested in utilizing
|
||||
the DSP slave processors.
|
||||
|
||||
config TI_K3_M4_REMOTEPROC
|
||||
tristate "TI K3 M4 remoteproc support"
|
||||
depends on ARCH_K3 || COMPILE_TEST
|
||||
select MAILBOX
|
||||
select OMAP2PLUS_MBOX
|
||||
help
|
||||
Say m here to support TI's M4 remote processor subsystems
|
||||
on various TI K3 family of SoCs through the remote processor
|
||||
framework.
|
||||
|
||||
It's safe to say N here if you're not interested in utilizing
|
||||
a remote processor.
|
||||
|
||||
config TI_K3_R5_REMOTEPROC
|
||||
tristate "TI K3 R5 remoteproc support"
|
||||
depends on ARCH_K3
|
||||
|
||||
@@ -37,5 +37,6 @@ obj-$(CONFIG_ST_REMOTEPROC) += st_remoteproc.o
|
||||
obj-$(CONFIG_ST_SLIM_REMOTEPROC) += st_slim_rproc.o
|
||||
obj-$(CONFIG_STM32_RPROC) += stm32_rproc.o
|
||||
obj-$(CONFIG_TI_K3_DSP_REMOTEPROC) += ti_k3_dsp_remoteproc.o
|
||||
obj-$(CONFIG_TI_K3_M4_REMOTEPROC) += ti_k3_m4_remoteproc.o
|
||||
obj-$(CONFIG_TI_K3_R5_REMOTEPROC) += ti_k3_r5_remoteproc.o
|
||||
obj-$(CONFIG_XLNX_R5_REMOTEPROC) += xlnx_r5_remoteproc.o
|
||||
|
||||
@@ -59,7 +59,9 @@ enum omap_rp_mbox_messages {
|
||||
RP_MBOX_SUSPEND_SYSTEM = 0xFFFFFF11,
|
||||
RP_MBOX_SUSPEND_ACK = 0xFFFFFF12,
|
||||
RP_MBOX_SUSPEND_CANCEL = 0xFFFFFF13,
|
||||
RP_MBOX_END_MSG = 0xFFFFFF14,
|
||||
RP_MBOX_SHUTDOWN = 0xFFFFFF14,
|
||||
RP_MBOX_SHUTDOWN_ACK = 0xFFFFFF15,
|
||||
RP_MBOX_END_MSG = 0xFFFFFF16,
|
||||
};
|
||||
|
||||
#endif /* _OMAP_RPMSG_H */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1195,6 +1195,7 @@ static void cqspi_config_baudrate_div(struct cqspi_st *cqspi)
|
||||
const unsigned int ref_clk_hz = cqspi->master_ref_clk_hz;
|
||||
void __iomem *reg_base = cqspi->iobase;
|
||||
u32 reg, div;
|
||||
u32 actual_clk;
|
||||
|
||||
/* Recalculate the baudrate divisor based on QSPI specification. */
|
||||
div = DIV_ROUND_UP(ref_clk_hz, 2 * cqspi->sclk) - 1;
|
||||
@@ -1207,10 +1208,25 @@ static void cqspi_config_baudrate_div(struct cqspi_st *cqspi)
|
||||
cqspi->sclk, ref_clk_hz/((div+1)*2));
|
||||
}
|
||||
|
||||
/* Calculate actual clock for logging purposes */
|
||||
actual_clk = ref_clk_hz / (2 * (div + 1));
|
||||
|
||||
dev_info(&cqspi->pdev->dev,
|
||||
"CQSPI Clock Config: Ref=%u Hz, Target=%u Hz, Divisor=%u, Result=%u Hz\n",
|
||||
ref_clk_hz, cqspi->sclk, div, actual_clk);
|
||||
|
||||
reg = readl(reg_base + CQSPI_REG_CONFIG);
|
||||
|
||||
/* Log old register value for deep debugging */
|
||||
dev_info(&cqspi->pdev->dev, "Old CONFIG_REG: 0x%08x\n", reg);
|
||||
|
||||
reg &= ~(CQSPI_REG_CONFIG_BAUD_MASK << CQSPI_REG_CONFIG_BAUD_LSB);
|
||||
reg |= (div & CQSPI_REG_CONFIG_BAUD_MASK) << CQSPI_REG_CONFIG_BAUD_LSB;
|
||||
|
||||
writel(reg, reg_base + CQSPI_REG_CONFIG);
|
||||
|
||||
dev_info(&cqspi->pdev->dev, "New CONFIG_REG: 0x%08x (BaudDiv field updated)\n",
|
||||
readl(reg_base + CQSPI_REG_CONFIG));
|
||||
}
|
||||
|
||||
static void cqspi_readdata_capture(struct cqspi_st *cqspi,
|
||||
|
||||
@@ -132,7 +132,6 @@ static irqreturn_t kbus_renesas_isr(int irq, void *dev)
|
||||
if (unlikely(kdrvdata->cmdsel)) {
|
||||
if (kdrvdata->kbus_ign_reset_irq) {
|
||||
kdrvdata->kbus_ign_reset_irq = 0;
|
||||
init_waitqueue_head(&kdrvdata->kbus_irq_wq);
|
||||
// trace for command 0xA3 find in libkbusfwload library
|
||||
trace_pxc_kbus(__func__, "kbus-irq: edge falling after command\
|
||||
SCPU_COMMAND_START_UPDATE [0xa3]: ignore irq");
|
||||
|
||||
@@ -1310,9 +1310,11 @@ static int omap2_mcspi_setup(struct spi_device *spi)
|
||||
struct omap2_mcspi_cs *cs = spi->controller_state;
|
||||
|
||||
#ifdef CONFIG_SPI_KBUS_OMAP_EXTENSION
|
||||
if (spi->max_speed_hz < (OMAP2_MCSPI_MAX_FREQ >> 15) ||
|
||||
spi->max_speed_hz > OMAP2_MCSPI_MAX_FREQ)
|
||||
return -EINVAL;
|
||||
if (spi->dev.driver == &kbus_driver.driver) {
|
||||
if (spi->max_speed_hz < (OMAP2_MCSPI_MAX_FREQ >> 15) ||
|
||||
spi->max_speed_hz > OMAP2_MCSPI_MAX_FREQ)
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!cs) {
|
||||
|
||||
+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.11.03
|
||||
-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