[openwrt/openwrt] mvebu: puzzle-m902: add driver for MCU driving LEDs, fan and buzzer

LEDE Commits lede-commits at lists.infradead.org
Sat Jan 1 14:31:22 PST 2022


dangole pushed a commit to openwrt/openwrt.git, branch openwrt-21.02:
https://git.openwrt.org/99a1e882970f02d3713b3d67022af012b782a5f4

commit 99a1e882970f02d3713b3d67022af012b782a5f4
Author: Daniel Golle <daniel at makrotopia.org>
AuthorDate: Tue Dec 21 16:27:16 2021 +0000

    mvebu: puzzle-m902: add driver for MCU driving LEDs, fan and buzzer
    
    Backport MFD driver for communicating with the on-board MCU found on
    IEI World Puzzle appliances.
    Improve the driver to support multiple LEDs, apply a default state and
    let MCU take care of blinking if timing is within supported range.
    Wire up LEDs and fan for Puzzle M902 in device tree.
    
    Signed-off-by: Daniel Golle <daniel at makrotopia.org>
    (cherry picked from commit f0c0b18234418c6ed6d35fcf1c6e5b0cbdceed49
    with commit 962c58558010bd302793ac24284c4f9db8fe287f squashed)
---
 target/linux/mvebu/cortexa72/config-5.4            |    6 +
 .../arm64/boot/dts/marvell/cn9132-puzzle-m902.dts  |   74 +-
 ...-Add-IEI-vendor-prefix-and-IEI-WT61P803-P.patch |  218 +++++
 ...-Add-a-driver-for-IEI-WT61P803-PUZZLE-MCU.patch | 1034 ++++++++++++++++++++
 ...on-Add-the-IEI-WT61P803-PUZZLE-HWMON-driv.patch |  469 +++++++++
 ...ds-Add-the-IEI-WT61P803-PUZZLE-LED-driver.patch |  207 ++++
 ...on-ABI-Add-iei-wt61p803-puzzle-driver-sys.patch |   82 ++
 ...on-hwmon-Add-iei-wt61p803-puzzle-hwmon-dr.patch |   74 ++
 ...-Add-an-entry-for-the-IEI-WT61P803-PUZZLE.patch |   41 +
 ...drivers-leds-wt61p803-puzzle-improvements.patch |  247 +++++
 10 files changed, 2451 insertions(+), 1 deletion(-)

diff --git a/target/linux/mvebu/cortexa72/config-5.4 b/target/linux/mvebu/cortexa72/config-5.4
index 5727ae5918..106724dc3b 100644
--- a/target/linux/mvebu/cortexa72/config-5.4
+++ b/target/linux/mvebu/cortexa72/config-5.4
@@ -130,7 +130,9 @@ CONFIG_INLINE_WRITE_LOCK_IRQ=y
 CONFIG_INLINE_WRITE_LOCK_IRQSAVE=y
 CONFIG_INLINE_WRITE_UNLOCK_BH=y
 CONFIG_INLINE_WRITE_UNLOCK_IRQRESTORE=y
+CONFIG_LEDS_IEI_WT61P803_PUZZLE=y
 CONFIG_MARVELL_10G_PHY=y
+CONFIG_MFD_IEI_WT61P803_PUZZLE=y
 CONFIG_MFD_SYSCON=y
 CONFIG_MMC_SDHCI_XENON=y
 CONFIG_MODULES_USE_ELF_RELA=y
@@ -161,7 +163,11 @@ CONFIG_QUEUED_RWLOCKS=y
 CONFIG_QUEUED_SPINLOCKS=y
 # CONFIG_RANDOMIZE_BASE is not set
 CONFIG_RAS=y
+# CONFIG_RAVE_SP_CORE is not set
 CONFIG_REGULATOR_GPIO=y
+CONFIG_SENSORS_IEI_WT61P803_PUZZLE_HWMON=y
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_SERIAL_DEV_CTRL_TTYPORT=y
 # CONFIG_SERIAL_AMBA_PL011 is not set
 CONFIG_SPARSEMEM=y
 CONFIG_SPARSEMEM_EXTREME=y
diff --git a/target/linux/mvebu/files/arch/arm64/boot/dts/marvell/cn9132-puzzle-m902.dts b/target/linux/mvebu/files/arch/arm64/boot/dts/marvell/cn9132-puzzle-m902.dts
index dc4e6527f1..8d2d53d03c 100644
--- a/target/linux/mvebu/files/arch/arm64/boot/dts/marvell/cn9132-puzzle-m902.dts
+++ b/target/linux/mvebu/files/arch/arm64/boot/dts/marvell/cn9132-puzzle-m902.dts
@@ -38,7 +38,10 @@
 		ethernet8 = &cp2_eth2;
 		spi1 = &cp0_spi0;
 		spi2 = &cp0_spi1;
-		serial1 = &cp0_uart0;
+		led-boot = &led_power;
+		led-failsafe = &led_info;
+		led-running = &led_power;
+		led-upgrade = &led_info;
 	};
 
 	memory at 00000000 {
@@ -91,6 +94,75 @@
 
 &cp0_uart0 {
 	status = "okay";
+
+	puzzle-mcu {
+		compatible = "iei,wt61p803-puzzle";
+		#address-cells = <1>;
+		#size-cells = <1>;
+		current-speed = <115200>;
+		enable-beep;
+		status = "okay";
+
+		leds {
+			compatible = "iei,wt61p803-puzzle-leds";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			led at 0 {
+				reg = <0>;
+				label = "white:network";
+				active-low;
+			};
+
+			led at 1 {
+				reg = <1>;
+				label = "green:cloud";
+				active-low;
+			};
+
+			led_info: led at 2 {
+				reg = <2>;
+				label = "orange:info";
+				active-low;
+			};
+
+			led_power: led at 3 {
+				reg = <3>;
+				label = "yellow:power";
+				active-low;
+				default-state = "on";
+			};
+		};
+
+		hwmon {
+			compatible = "iei,wt61p803-puzzle-hwmon";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			chassis_fan_group0: fan-group at 0 {
+				#cooling-cells = <2>;
+				reg = <0x00>;
+				cooling-levels = <64 102 170 230 250>;
+			};
+		};
+	};
+};
+
+&ap_thermal_cpu1 {
+	trips {
+		cpu_active: cpu-active {
+			temperature = <44000>;
+			hysteresis = <2000>;
+			type = "active";
+		};
+	};
+	cooling-maps {
+		fan-map {
+			trip = <&cpu_active>;
+			cooling-device = <&chassis_fan_group0 64 THERMAL_NO_LIMIT>;
+		};
+	};
 };
 
 /* on-board eMMC - U9 */
diff --git a/target/linux/mvebu/patches-5.4/901-dt-bindings-Add-IEI-vendor-prefix-and-IEI-WT61P803-P.patch b/target/linux/mvebu/patches-5.4/901-dt-bindings-Add-IEI-vendor-prefix-and-IEI-WT61P803-P.patch
new file mode 100644
index 0000000000..fb9ea43e4b
--- /dev/null
+++ b/target/linux/mvebu/patches-5.4/901-dt-bindings-Add-IEI-vendor-prefix-and-IEI-WT61P803-P.patch
@@ -0,0 +1,218 @@
+From aa4a0ccc41997f2da172165c92803abace43bd1c Mon Sep 17 00:00:00 2001
+From: Luka Kovacic <luka.kovacic () sartura ! hr>
+Date: Tue, 24 Aug 2021 12:44:32 +0000
+Subject: [PATCH 1/7] dt-bindings: Add IEI vendor prefix and IEI WT61P803
+ PUZZLE driver bindings
+
+Add the IEI WT61P803 PUZZLE Device Tree bindings for MFD, HWMON and LED
+drivers. A new vendor prefix is also added accordingly for
+IEI Integration Corp.
+
+Signed-off-by: Luka Kovacic <luka.kovacic at sartura.hr>
+Signed-off-by: Pavo Banicevic <pavo.banicevic at sartura.hr>
+Cc: Luka Perkov <luka.perkov at sartura.hr>
+Cc: Robert Marko <robert.marko at sartura.hr>
+---
+ .../hwmon/iei,wt61p803-puzzle-hwmon.yaml      | 53 ++++++++++++
+ .../leds/iei,wt61p803-puzzle-leds.yaml        | 39 +++++++++
+ .../bindings/mfd/iei,wt61p803-puzzle.yaml     | 82 +++++++++++++++++++
+ .../devicetree/bindings/vendor-prefixes.yaml  |  2 +
+ 4 files changed, 176 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/hwmon/iei,wt61p803-puzzle-hwmon.yaml
+ create mode 100644 Documentation/devicetree/bindings/leds/iei,wt61p803-puzzle-leds.yaml
+ create mode 100644 Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/hwmon/iei,wt61p803-puzzle-hwmon.yaml
+@@ -0,0 +1,53 @@
++# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/hwmon/iei,wt61p803-puzzle-hwmon.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: IEI WT61P803 PUZZLE MCU HWMON module from IEI Integration Corp.
++
++maintainers:
++  - Luka Kovacic <luka.kovacic at sartura.hr>
++
++description: |
++  This module is a part of the IEI WT61P803 PUZZLE MFD device. For more details
++  see Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml.
++
++  The HWMON module is a sub-node of the MCU node in the Device Tree.
++
++properties:
++  compatible:
++    const: iei,wt61p803-puzzle-hwmon
++
++  "#address-cells":
++    const: 1
++
++  "#size-cells":
++    const: 0
++
++patternProperties:
++  "^fan-group@[0-1]$":
++    type: object
++    properties:
++      reg:
++        minimum: 0
++        maximum: 1
++        description:
++          Fan group ID
++
++      cooling-levels:
++        minItems: 1
++        maxItems: 255
++        description:
++          Cooling levels for the fans (PWM value mapping)
++    description: |
++      Properties for each fan group.
++    required:
++      - reg
++
++required:
++  - compatible
++  - "#address-cells"
++  - "#size-cells"
++
++additionalProperties: false
+--- /dev/null
++++ b/Documentation/devicetree/bindings/leds/iei,wt61p803-puzzle-leds.yaml
+@@ -0,0 +1,39 @@
++# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/leds/iei,wt61p803-puzzle-leds.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: IEI WT61P803 PUZZLE MCU LED module from IEI Integration Corp.
++
++maintainers:
++  - Luka Kovacic <luka.kovacic at sartura.hr>
++
++description: |
++  This module is a part of the IEI WT61P803 PUZZLE MFD device. For more details
++  see Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml.
++
++  The LED module is a sub-node of the MCU node in the Device Tree.
++
++properties:
++  compatible:
++    const: iei,wt61p803-puzzle-leds
++
++  "#address-cells":
++    const: 1
++
++  "#size-cells":
++    const: 0
++
++  led at 0:
++    type: object
++    $ref: common.yaml
++    description: |
++      Properties for a single LED.
++
++required:
++  - compatible
++  - "#address-cells"
++  - "#size-cells"
++
++additionalProperties: false
+--- /dev/null
++++ b/Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml
+@@ -0,0 +1,82 @@
++# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/mfd/iei,wt61p803-puzzle.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: IEI WT61P803 PUZZLE MCU from IEI Integration Corp.
++
++maintainers:
++  - Luka Kovacic <luka.kovacic at sartura.hr>
++
++description: |
++  IEI WT61P803 PUZZLE MCU is embedded in some IEI Puzzle series boards.
++  It's used for controlling system power states, fans, LEDs and temperature
++  sensors.
++
++  For Device Tree bindings of other sub-modules (HWMON, LEDs) refer to the
++  binding documents under the respective subsystem directories.
++
++properties:
++  compatible:
++    const: iei,wt61p803-puzzle
++
++  current-speed:
++    description:
++      Serial bus speed in bps
++    maxItems: 1
++
++  enable-beep: true
++
++  hwmon:
++    $ref: /schemas/hwmon/iei,wt61p803-puzzle-hwmon.yaml
++
++  leds:
++    $ref: /schemas/leds/iei,wt61p803-puzzle-leds.yaml
++
++required:
++  - compatible
++  - current-speed
++
++additionalProperties: false
++
++examples:
++  - |
++    #include <dt-bindings/leds/common.h>
++    serial {
++        mcu {
++            compatible = "iei,wt61p803-puzzle";
++            current-speed = <115200>;
++            enable-beep;
++
++            leds {
++                compatible = "iei,wt61p803-puzzle-leds";
++                #address-cells = <1>;
++                #size-cells = <0>;
++
++                led at 0 {
++                    reg = <0>;
++                    function = LED_FUNCTION_POWER;
++                    color = <LED_COLOR_ID_BLUE>;
++                };
++            };
++
++            hwmon {
++                compatible = "iei,wt61p803-puzzle-hwmon";
++                #address-cells = <1>;
++                #size-cells = <0>;
++
++                fan-group at 0 {
++                    #cooling-cells = <2>;
++                    reg = <0x00>;
++                    cooling-levels = <64 102 170 230 250>;
++                };
++
++                fan-group at 1 {
++                    #cooling-cells = <2>;
++                    reg = <0x01>;
++                    cooling-levels = <64 102 170 230 250>;
++                };
++            };
++        };
++    };
+--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
++++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
+@@ -423,6 +423,8 @@ patternProperties:
+     description: IC Plus Corp.
+   "^idt,.*":
+     description: Integrated Device Technologies, Inc.
++  "^iei,.*":
++    description: IEI Integration Corp.
+   "^ifi,.*":
+     description: Ingenieurburo Fur Ic-Technologie (I/F/I)
+   "^ilitek,.*":
diff --git a/target/linux/mvebu/patches-5.4/902-drivers-mfd-Add-a-driver-for-IEI-WT61P803-PUZZLE-MCU.patch b/target/linux/mvebu/patches-5.4/902-drivers-mfd-Add-a-driver-for-IEI-WT61P803-PUZZLE-MCU.patch
new file mode 100644
index 0000000000..194feee569
--- /dev/null
+++ b/target/linux/mvebu/patches-5.4/902-drivers-mfd-Add-a-driver-for-IEI-WT61P803-PUZZLE-MCU.patch
@@ -0,0 +1,1034 @@
+From 692cfa85272dd12995b427c0a7a585ced5d54f32 Mon Sep 17 00:00:00 2001
+From: Luka Kovacic <luka.kovacic () sartura ! hr>
+Date: Tue, 24 Aug 2021 12:44:33 +0000
+Subject: [PATCH 2/7] drivers: mfd: Add a driver for IEI WT61P803 PUZZLE MCU
+
+Add a driver for the IEI WT61P803 PUZZLE microcontroller, used in some
+IEI Puzzle series devices. The microcontroller controls system power,
+temperature sensors, fans and LEDs.
+
+This driver implements the core functionality for device communication
+over the system serial (serdev bus). It handles MCU messages and the
+internal MCU properties. Some properties can be managed over sysfs.
+
+Signed-off-by: Luka Kovacic <luka.kovacic at sartura.hr>
+Signed-off-by: Pavo Banicevic <pavo.banicevic at sartura.hr>
+Cc: Luka Perkov <luka.perkov at sartura.hr>
+Cc: Robert Marko <robert.marko at sartura.hr>
+---
+ drivers/mfd/Kconfig                     |   8 +
+ drivers/mfd/Makefile                    |   1 +
+ drivers/mfd/iei-wt61p803-puzzle.c       | 908 ++++++++++++++++++++++++
+ include/linux/mfd/iei-wt61p803-puzzle.h |  66 ++
+ 4 files changed, 983 insertions(+)
+ create mode 100644 drivers/mfd/iei-wt61p803-puzzle.c
+ create mode 100644 include/linux/mfd/iei-wt61p803-puzzle.h
+
+--- a/drivers/mfd/Kconfig
++++ b/drivers/mfd/Kconfig
+@@ -531,6 +531,15 @@ config LPC_SCH
+ 	  LPC bridge function of the Intel SCH provides support for
+ 	  System Management Bus and General Purpose I/O.
+ 
++config MFD_IEI_WT61P803_PUZZLE
++	tristate "IEI WT61P803 PUZZLE MCU driver"
++	depends on SERIAL_DEV_BUS
++	select MFD_CORE
++	help
++	  IEI WT61P803 PUZZLE is a system power management microcontroller
++	  used for fan control, temperature sensor reading, LED control
++	  and system identification.
++
+ config INTEL_SOC_PMIC
+ 	bool "Support for Crystal Cove PMIC"
+ 	depends on ACPI && HAS_IOMEM && I2C=y && GPIOLIB && COMMON_CLK
+--- a/drivers/mfd/Makefile
++++ b/drivers/mfd/Makefile
+@@ -232,6 +232,7 @@ obj-$(CONFIG_MFD_HI655X_PMIC)   += hi655
+ obj-$(CONFIG_MFD_DLN2)		+= dln2.o
+ obj-$(CONFIG_MFD_RT5033)	+= rt5033.o
+ obj-$(CONFIG_MFD_SKY81452)	+= sky81452.o
++obj-$(CONFIG_MFD_IEI_WT61P803_PUZZLE)	+= iei-wt61p803-puzzle.o
+ 
+ intel-soc-pmic-objs		:= intel_soc_pmic_core.o intel_soc_pmic_crc.o
+ obj-$(CONFIG_INTEL_SOC_PMIC)	+= intel-soc-pmic.o
+--- /dev/null
++++ b/drivers/mfd/iei-wt61p803-puzzle.c
+@@ -0,0 +1,908 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* IEI WT61P803 PUZZLE MCU Driver
++ * System management microcontroller for fan control, temperature sensor reading,
++ * LED control and system identification on IEI Puzzle series ARM-based appliances.
++ *
++ * Copyright (C) 2020 Sartura Ltd.
++ * Author: Luka Kovacic <luka.kovacic at sartura.hr>
++ */
++
++#include <linux/atomic.h>
++#include <linux/delay.h>
++#include <linux/export.h>
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/mfd/core.h>
++#include <linux/mfd/iei-wt61p803-puzzle.h>
++#include <linux/mod_devicetable.h>
++#include <linux/module.h>
++#include <linux/of_platform.h>
++#include <linux/property.h>
++#include <linux/sched.h>
++#include <linux/serdev.h>
++#include <linux/slab.h>
++#include <linux/sysfs.h>
++#include <asm/unaligned.h>
++
++/* start, payload and XOR checksum at end */
++#define IEI_WT61P803_PUZZLE_MAX_COMMAND_LENGTH	(1 + 20 + 1)
++#define IEI_WT61P803_PUZZLE_RESP_BUF_SIZE	512
++
++#define IEI_WT61P803_PUZZLE_MAC_LENGTH			17
++#define IEI_WT61P803_PUZZLE_SN_LENGTH			36
++#define IEI_WT61P803_PUZZLE_VERSION_LENGTH		 6
++#define IEI_WT61P803_PUZZLE_BUILD_INFO_LENGTH		16
++#define IEI_WT61P803_PUZZLE_PROTOCOL_VERSION_LENGTH	 8
++#define IEI_WT61P803_PUZZLE_NB_MAC			 8
++
++/* Use HZ as a timeout value throughout the driver */
++#define IEI_WT61P803_PUZZLE_GENERAL_TIMEOUT HZ
++
++enum iei_wt61p803_puzzle_attribute_type {
++	IEI_WT61P803_PUZZLE_VERSION,
++	IEI_WT61P803_PUZZLE_BUILD_INFO,
++	IEI_WT61P803_PUZZLE_BOOTLOADER_MODE,
++	IEI_WT61P803_PUZZLE_PROTOCOL_VERSION,
++	IEI_WT61P803_PUZZLE_SERIAL_NUMBER,
++	IEI_WT61P803_PUZZLE_MAC_ADDRESS,
++	IEI_WT61P803_PUZZLE_AC_RECOVERY_STATUS,
++	IEI_WT61P803_PUZZLE_POWER_LOSS_RECOVERY,
++	IEI_WT61P803_PUZZLE_POWER_STATUS,
++};
++
++struct iei_wt61p803_puzzle_device_attribute {
++	struct device_attribute dev_attr;
++	enum iei_wt61p803_puzzle_attribute_type type;
++	u8 index;
++};
++
++/**
++ * struct iei_wt61p803_puzzle_mcu_status - MCU flags state
++ * @ac_recovery_status_flag:	AC Recovery Status Flag
++ * @power_loss_recovery:	System recovery after power loss
++ * @power_status:		System Power-on Method
++ */
++struct iei_wt61p803_puzzle_mcu_status {
++	u8 ac_recovery_status_flag;
++	u8 power_loss_recovery;
++	u8 power_status;
++};
++
++/**
++ * struct iei_wt61p803_puzzle_reply - MCU reply
++ * @size:	Size of the MCU reply
++ * @data:	Full MCU reply buffer
++ * @state:	Current state of the packet
++ * @received:	Was the response fullfilled
++ */
++struct iei_wt61p803_puzzle_reply {
++	size_t size;
++	unsigned char data[IEI_WT61P803_PUZZLE_RESP_BUF_SIZE];
++	struct completion received;
++};
++
++/**
++ * struct iei_wt61p803_puzzle_mcu_version - MCU version status
++ * @version:		Primary firmware version
++ * @build_info:		Build date and time
++ * @bootloader_mode:	Status of the MCU operation
++ * @protocol_version:	MCU communication protocol version
++ * @serial_number:	Device factory serial number
++ * @mac_address:	Device factory MAC addresses
++ *
++ * Last element of arrays is reserved for '\0'.
++ */
++struct iei_wt61p803_puzzle_mcu_version {
++	char version[IEI_WT61P803_PUZZLE_VERSION_LENGTH + 1];
++	char build_info[IEI_WT61P803_PUZZLE_BUILD_INFO_LENGTH + 1];
++	bool bootloader_mode;
++	char protocol_version[IEI_WT61P803_PUZZLE_PROTOCOL_VERSION_LENGTH + 1];
++	char serial_number[IEI_WT61P803_PUZZLE_SN_LENGTH + 1];
++	char mac_address[IEI_WT61P803_PUZZLE_NB_MAC][IEI_WT61P803_PUZZLE_MAC_LENGTH + 1];
++};
++
++/**
++ * struct iei_wt61p803_puzzle - IEI WT61P803 PUZZLE MCU Driver
++ * @serdev:		Pointer to underlying serdev device
++ * @dev:		Pointer to underlying dev device
++ * @reply_lock:		Reply mutex lock
++ * @reply:		Pointer to the iei_wt61p803_puzzle_reply struct
++ * @version:		MCU version related data
++ * @status:		MCU status related data
++ * @response_buffer	Command response buffer allocation
++ * @lock		General member mutex lock
++ */
++struct iei_wt61p803_puzzle {
++	struct serdev_device *serdev;
++	struct device *dev;
++	struct mutex reply_lock; /* lock to prevent multiple firmware calls */
++	struct iei_wt61p803_puzzle_reply *reply;
++	struct iei_wt61p803_puzzle_mcu_version version;
++	struct iei_wt61p803_puzzle_mcu_status status;
++	unsigned char response_buffer[IEI_WT61P803_PUZZLE_BUF_SIZE];
++	struct mutex lock; /* lock to protect response buffer */
++};
++
++static unsigned char iei_wt61p803_puzzle_checksum(unsigned char *buf, size_t len)
++{
++	unsigned char checksum = 0;
++	size_t i;
++
++	for (i = 0; i < len; i++)
++		checksum ^= buf[i];
++	return checksum;
++}
++
++static int iei_wt61p803_puzzle_process_resp(struct iei_wt61p803_puzzle *mcu,
++					    const unsigned char *raw_resp_data, size_t size)
++{
++	unsigned char checksum;
++
++	/* Check the incoming frame header */
++	if (!(raw_resp_data[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START ||
++	      raw_resp_data[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START_OTHER ||
++	     (raw_resp_data[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM &&
++	      raw_resp_data[1] == IEI_WT61P803_PUZZLE_CMD_EEPROM_READ))) {
++		if (mcu->reply->size + size >= sizeof(mcu->reply->data))
++			return -EIO;
++
++		/* Append the frame to existing data */
++		memcpy(mcu->reply->data + mcu->reply->size, raw_resp_data, size);
++		mcu->reply->size += size;
++	} else {
++		if (size >= sizeof(mcu->reply->data))
++			return -EIO;
++
++		/* Start processing a new frame */
++		memcpy(mcu->reply->data, raw_resp_data, size);
++		mcu->reply->size = size;
++	}
++
++	checksum = iei_wt61p803_puzzle_checksum(mcu->reply->data, mcu->reply->size - 1);
++	if (checksum != mcu->reply->data[mcu->reply->size - 1]) {
++		/* The checksum isn't matched yet, wait for new frames */
++		return size;
++	}
++
++	/* Received all the data */
++	complete(&mcu->reply->received);
++
++	return size;
++}
++
++static int iei_wt61p803_puzzle_recv_buf(struct serdev_device *serdev,
++					const unsigned char *data, size_t size)
++{
++	struct iei_wt61p803_puzzle *mcu = serdev_device_get_drvdata(serdev);
++	int ret;
++
++	ret = iei_wt61p803_puzzle_process_resp(mcu, data, size);
++	/* Return the number of processed bytes if function returns error,
++	 * discard the remaining incoming data, since the frame this data
++	 * belongs to is broken anyway
++	 */
++	if (ret < 0)
++		return size;
++
++	return ret;
++}
++
++static const struct serdev_device_ops iei_wt61p803_puzzle_serdev_device_ops = {
++	.receive_buf  = iei_wt61p803_puzzle_recv_buf,
++	.write_wakeup = serdev_device_write_wakeup,
++};
++
++/**
++ * iei_wt61p803_puzzle_write_command_watchdog() - Watchdog of the normal cmd
++ * @mcu: Pointer to the iei_wt61p803_puzzle core MFD struct
++ * @cmd: Pointer to the char array to send (size should be content + 1 (xor))
++ * @size: Size of the cmd char array
++ * @reply_data: Pointer to the reply/response data array (should be allocated)
++ * @reply_size: Pointer to size_t (size of reply_data)
++ * @retry_count: Number of times to retry sending the command to the MCU
++ */
++int iei_wt61p803_puzzle_write_command_watchdog(struct iei_wt61p803_puzzle *mcu,
++					       unsigned char *cmd, size_t size,
++					       unsigned char *reply_data,
++					       size_t *reply_size, int retry_count)
++{
++	struct device *dev = &mcu->serdev->dev;
++	int ret, i;
++
++	for (i = 0; i < retry_count; i++) {
++		ret = iei_wt61p803_puzzle_write_command(mcu, cmd, size,
++							reply_data, reply_size);
++		if (ret != -ETIMEDOUT)
++			return ret;
++	}
++
++	dev_err(dev, "Command response timed out. Retries: %d\n", retry_count);
++
++	return -ETIMEDOUT;
++}
++EXPORT_SYMBOL_GPL(iei_wt61p803_puzzle_write_command_watchdog);
++
++/**
++ * iei_wt61p803_puzzle_write_command() - Send a structured command to the MCU
++ * @mcu: Pointer to the iei_wt61p803_puzzle core MFD struct
++ * @cmd: Pointer to the char array to send (size should be content + 1 (xor))
++ * @size: Size of the cmd char array
++ * @reply_data: Pointer to the reply/response data array (should be allocated)
++ *
++ * Sends a structured command to the MCU.
++ */
++int iei_wt61p803_puzzle_write_command(struct iei_wt61p803_puzzle *mcu,
++				      unsigned char *cmd, size_t size,
++				      unsigned char *reply_data,
++				      size_t *reply_size)
++{
++	struct device *dev = &mcu->serdev->dev;
++	int ret;
++
++	if (size <= 1 || size > IEI_WT61P803_PUZZLE_MAX_COMMAND_LENGTH)
++		return -EINVAL;
++
++	mutex_lock(&mcu->reply_lock);
++
++	cmd[size - 1] = iei_wt61p803_puzzle_checksum(cmd, size - 1);
++
++	/* Initialize reply struct */
++	reinit_completion(&mcu->reply->received);
++	mcu->reply->size = 0;
++	usleep_range(2000, 10000);
++	serdev_device_write_flush(mcu->serdev);
++	ret = serdev_device_write_buf(mcu->serdev, cmd, size);
++	if (ret < 0)
++		goto exit;
++
++	serdev_device_wait_until_sent(mcu->serdev, IEI_WT61P803_PUZZLE_GENERAL_TIMEOUT);
++	ret = wait_for_completion_timeout(&mcu->reply->received,
++					  IEI_WT61P803_PUZZLE_GENERAL_TIMEOUT);
++	if (ret == 0) {
++		dev_err(dev, "Command reply receive timeout\n");
++		ret = -ETIMEDOUT;
++		goto exit;
++	}
++
++	*reply_size = mcu->reply->size;
++	/* Copy the received data, as it will not be available after a new frame is received */
++	memcpy(reply_data, mcu->reply->data, mcu->reply->size);
++	ret = 0;
++exit:
++	mutex_unlock(&mcu->reply_lock);
++	return ret;
++}
++EXPORT_SYMBOL_GPL(iei_wt61p803_puzzle_write_command);
++
++static int iei_wt61p803_puzzle_buzzer(struct iei_wt61p803_puzzle *mcu, bool long_beep)
++{
++	unsigned char *resp_buf = mcu->response_buffer;
++	unsigned char buzzer_cmd[4] = {};
++	size_t reply_size;
++	int ret;
++
++	buzzer_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START;
++	buzzer_cmd[1] = IEI_WT61P803_PUZZLE_CMD_FUNCTION_SINGLE;
++	buzzer_cmd[2] = long_beep ? '3' : '2'; /* Buzzer 1.5 / 0.5 second beep */
++
++	mutex_lock(&mcu->lock);
++	ret = iei_wt61p803_puzzle_write_command(mcu, buzzer_cmd, sizeof(buzzer_cmd),
++						resp_buf, &reply_size);
++	if (ret)
++		goto exit;
++
++	if (reply_size != 3) {
++		ret = -EIO;
++		goto exit;
++	}
++
++	if (!(resp_buf[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START &&
++	      resp_buf[1] == IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK &&
++	      resp_buf[2] == IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK)) {
++		ret = -EPROTO;
++		goto exit;
++	}
++exit:
++	mutex_unlock(&mcu->lock);
++	return ret;
++}
++
++static int iei_wt61p803_puzzle_get_version(struct iei_wt61p803_puzzle *mcu)
++{
++	unsigned char version_cmd[3] = {
++		IEI_WT61P803_PUZZLE_CMD_HEADER_START_OTHER,
++		IEI_WT61P803_PUZZLE_CMD_OTHER_VERSION,
++	};
++	unsigned char build_info_cmd[3] = {
++		IEI_WT61P803_PUZZLE_CMD_HEADER_START_OTHER,
++		IEI_WT61P803_PUZZLE_CMD_OTHER_BUILD,
++	};
++	unsigned char bootloader_mode_cmd[3] = {
++		IEI_WT61P803_PUZZLE_CMD_HEADER_START_OTHER,
++		IEI_WT61P803_PUZZLE_CMD_OTHER_BOOTLOADER_MODE,
++	};
++	unsigned char protocol_version_cmd[3] = {
++		IEI_WT61P803_PUZZLE_CMD_HEADER_START_OTHER,
++		IEI_WT61P803_PUZZLE_CMD_OTHER_PROTOCOL_VERSION,
++	};
++	unsigned char *rb = mcu->response_buffer;
++	size_t reply_size;
++	int ret;
++
++	mutex_lock(&mcu->lock);
++
++	ret = iei_wt61p803_puzzle_write_command(mcu, version_cmd, sizeof(version_cmd),
++						rb, &reply_size);
++	if (ret)
++		goto err;
++	if (reply_size < 7) {
++		ret = -EIO;
++		goto err;
++	}
++	sprintf(mcu->version.version, "v%c.%.3s", rb[2], &rb[3]);
++
++	ret = iei_wt61p803_puzzle_write_command(mcu, build_info_cmd,
++						sizeof(build_info_cmd),	rb,
++						&reply_size);
++	if (ret)
++		goto err;
++	if (reply_size < 15) {
++		ret = -EIO;
++		goto err;
++	}
++	sprintf(mcu->version.build_info, "%c%c/%c%c/%.4s %c%c:%c%c",
++		rb[8], rb[9], rb[6], rb[7], &rb[2], rb[10], rb[11],
++		rb[12], rb[13]);
++
++	ret = iei_wt61p803_puzzle_write_command(mcu, bootloader_mode_cmd,
++						sizeof(bootloader_mode_cmd), rb,
++						&reply_size);
++	if (ret)
++		goto err;
++	if (reply_size < 4) {
++		ret = -EIO;
++		goto err;
++	}
++	if (rb[2] == IEI_WT61P803_PUZZLE_CMD_OTHER_MODE_APPS)
++		mcu->version.bootloader_mode = false;
++	else if (rb[2] == IEI_WT61P803_PUZZLE_CMD_OTHER_MODE_BOOTLOADER)
++		mcu->version.bootloader_mode = true;
++
++	ret = iei_wt61p803_puzzle_write_command(mcu, protocol_version_cmd,
++						sizeof(protocol_version_cmd), rb,
++						&reply_size);
++	if (ret)
++		goto err;
++	if (reply_size < 9) {
++		ret = -EIO;
++		goto err;
++	}
++	sprintf(mcu->version.protocol_version, "v%c.%c%c%c%c%c",
++		rb[7], rb[6], rb[5], rb[4], rb[3], rb[2]);
++err:
++	mutex_unlock(&mcu->lock);
++	return ret;
++}
++
++static int iei_wt61p803_puzzle_get_mcu_status(struct iei_wt61p803_puzzle *mcu)
++{
++	unsigned char mcu_status_cmd[5] = {
++		IEI_WT61P803_PUZZLE_CMD_HEADER_START,
++		IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER,
++		IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_STATUS,
++		IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_STATUS,
++	};
++	unsigned char *resp_buf = mcu->response_buffer;
++	size_t reply_size;
++	int ret;
++
++	mutex_lock(&mcu->lock);
++	ret = iei_wt61p803_puzzle_write_command(mcu, mcu_status_cmd, sizeof(mcu_status_cmd),
++						resp_buf, &reply_size);
++	if (ret)
++		goto exit;
++	if (reply_size < 20) {
++		ret = -EIO;
++		goto exit;
++	}
++
++	/* Response format:
++	 * (IDX	RESPONSE)
++	 * 0	@
++	 * 1	O
++	 * 2	S
++	 * 3	S
++	 * ...
++	 * 5	AC Recovery Status Flag
++	 * ...
++	 * 10	Power Loss Recovery
++	 * ...
++	 * 19	Power Status (system power on method)
++	 * 20	XOR checksum
++	 */
++	if (resp_buf[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START &&
++	    resp_buf[1] == IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER &&
++	    resp_buf[2] == IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_STATUS &&
++	    resp_buf[3] == IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_STATUS) {
++		mcu->status.ac_recovery_status_flag = resp_buf[5];
++		mcu->status.power_loss_recovery = resp_buf[10];
++		mcu->status.power_status = resp_buf[19];
++	}
++exit:
++	mutex_unlock(&mcu->lock);
++	return ret;
++}
++
++static int iei_wt61p803_puzzle_get_serial_number(struct iei_wt61p803_puzzle *mcu)
++{
++	unsigned char *resp_buf = mcu->response_buffer;
++	unsigned char serial_number_cmd[5] = {
++		IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
++		IEI_WT61P803_PUZZLE_CMD_EEPROM_READ,
++		0x00,	/* EEPROM read address */
++		0x24,	/* Data length */
++	};
++	size_t reply_size;
++	int ret;
++
++	mutex_lock(&mcu->lock);
++	ret = iei_wt61p803_puzzle_write_command(mcu, serial_number_cmd,
++						sizeof(serial_number_cmd),
++						resp_buf, &reply_size);
++	if (ret)
++		goto err;
++
++	if (reply_size < IEI_WT61P803_PUZZLE_SN_LENGTH + 4) {
++		ret = -EIO;
++		goto err;
++	}
++
++	sprintf(mcu->version.serial_number, "%.*s",
++		IEI_WT61P803_PUZZLE_SN_LENGTH, resp_buf + 4);
++err:
++	mutex_unlock(&mcu->lock);
++	return ret;
++}
++
++static int iei_wt61p803_puzzle_write_serial_number(struct iei_wt61p803_puzzle *mcu,
++						   unsigned char serial_number[36])
++{
++	unsigned char *resp_buf = mcu->response_buffer;
++	unsigned char serial_number_header[4] = {
++		IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
++		IEI_WT61P803_PUZZLE_CMD_EEPROM_WRITE,
++		0x00,	/* EEPROM write address */
++		0xC,	/* Data length */
++	};
++	unsigned char serial_number_cmd[4 + 12 + 1]; /* header, serial number, XOR checksum */
++	int ret, sn_counter;
++	size_t reply_size;
++
++	/* The MCU can only handle 22 byte messages, send the S/N in 12 byte chunks */
++	mutex_lock(&mcu->lock);
++	for (sn_counter = 0; sn_counter < 3; sn_counter++) {
++		serial_number_header[2] = 0x0 + 0xC * sn_counter;
++
++		memcpy(serial_number_cmd, serial_number_header, sizeof(serial_number_header));
++		memcpy(serial_number_cmd + sizeof(serial_number_header),
++		       serial_number + 0xC * sn_counter, 0xC);
++
++		ret = iei_wt61p803_puzzle_write_command(mcu, serial_number_cmd,
++							sizeof(serial_number_cmd),
++							resp_buf, &reply_size);
++		if (ret)
++			goto err;
++		if (reply_size != 3) {
++			ret = -EIO;
++			goto err;
++		}
++		if (!(resp_buf[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START &&
++		      resp_buf[1] == IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK &&
++		      resp_buf[2] == IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK)) {
++			ret = -EPROTO;
++			goto err;
++		}
++	}
++
++	sprintf(mcu->version.serial_number, "%.*s",
++		IEI_WT61P803_PUZZLE_SN_LENGTH, serial_number);
++err:
++	mutex_unlock(&mcu->lock);
++	return ret;
++}
++
++static int iei_wt61p803_puzzle_get_mac_address(struct iei_wt61p803_puzzle *mcu, int index)
++{
++	unsigned char *resp_buf = mcu->response_buffer;
++	unsigned char mac_address_cmd[5] = {
++		IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
++		IEI_WT61P803_PUZZLE_CMD_EEPROM_READ,
++		0x00,	/* EEPROM read address */
++		0x11,	/* Data length */
++	};
++	size_t reply_size;
++	int ret;
++
++	mutex_lock(&mcu->lock);
++	mac_address_cmd[2] = 0x24 + 0x11 * index;
++
++	ret = iei_wt61p803_puzzle_write_command(mcu, mac_address_cmd,
++						sizeof(mac_address_cmd),
++						resp_buf, &reply_size);
++	if (ret)
++		goto err;
++
++	if (reply_size < 22) {
++		ret = -EIO;
++		goto err;
++	}
++
++	sprintf(mcu->version.mac_address[index], "%.*s",
++		IEI_WT61P803_PUZZLE_MAC_LENGTH, resp_buf + 4);
++err:
++	mutex_unlock(&mcu->lock);
++	return ret;
++}
++
++static int
++iei_wt61p803_puzzle_write_mac_address(struct iei_wt61p803_puzzle *mcu,
++				      unsigned char mac_address[IEI_WT61P803_PUZZLE_MAC_LENGTH],
++				      int mac_address_idx)
++{
++	unsigned char mac_address_cmd[4 + IEI_WT61P803_PUZZLE_MAC_LENGTH + 1];
++	unsigned char *resp_buf = mcu->response_buffer;
++	unsigned char mac_address_header[4] = {
++		IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
++		IEI_WT61P803_PUZZLE_CMD_EEPROM_WRITE,
++		0x00,	/* EEPROM write address */
++		0x11,	/* Data length */
++	};
++	size_t reply_size;
++	int ret;
++
++	if (mac_address_idx < 0 || mac_address_idx >= IEI_WT61P803_PUZZLE_NB_MAC)
++		return -EINVAL;
++
++	mac_address_header[2] = 0x24 + 0x11 * mac_address_idx;
++
++	/* Concat mac_address_header, mac_address to mac_address_cmd */
++	memcpy(mac_address_cmd, mac_address_header, sizeof(mac_address_header));
++	memcpy(mac_address_cmd + sizeof(mac_address_header), mac_address,
++	       IEI_WT61P803_PUZZLE_MAC_LENGTH);
++
++	mutex_lock(&mcu->lock);
++	ret = iei_wt61p803_puzzle_write_command(mcu, mac_address_cmd,
++						sizeof(mac_address_cmd),
++						resp_buf, &reply_size);
++	if (ret)
++		goto err;
++	if (reply_size != 3) {
++		ret = -EIO;
++		goto err;
++	}
++	if (!(resp_buf[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START &&
++	      resp_buf[1] == IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK &&
++	      resp_buf[2] == IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK)) {
++		ret = -EPROTO;
++		goto err;
++	}
++
++	sprintf(mcu->version.mac_address[mac_address_idx], "%.*s",
++		IEI_WT61P803_PUZZLE_MAC_LENGTH, mac_address);
++err:
++	mutex_unlock(&mcu->lock);
++	return ret;
++}
++
++static int iei_wt61p803_puzzle_write_power_loss_recovery(struct iei_wt61p803_puzzle *mcu,
++							 int power_loss_recovery_action)
++{
++	unsigned char *resp_buf = mcu->response_buffer;
++	unsigned char power_loss_recovery_cmd[5] = {};
++	size_t reply_size;
++	int ret;
++
++	if (power_loss_recovery_action < 0 || power_loss_recovery_action > 4)
++		return -EINVAL;
++
++	power_loss_recovery_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START;
++	power_loss_recovery_cmd[1] = IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER;
++	power_loss_recovery_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_POWER_LOSS;
++	power_loss_recovery_cmd[3] = hex_asc[power_loss_recovery_action];
++
++	mutex_lock(&mcu->lock);
++	ret = iei_wt61p803_puzzle_write_command(mcu, power_loss_recovery_cmd,
++						sizeof(power_loss_recovery_cmd),
++						resp_buf, &reply_size);
++	if (ret)
++		goto exit;
++	mcu->status.power_loss_recovery = power_loss_recovery_action;
++exit:
++	mutex_unlock(&mcu->lock);
++	return ret;
++}
++
++#define to_puzzle_dev_attr(_attr) \
++	container_of(_attr, struct iei_wt61p803_puzzle_device_attribute, dev_attr)
++
++static ssize_t show_output(struct device *dev,
++			   struct device_attribute *attr, char *buf)
++{
++	struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev);
++	struct iei_wt61p803_puzzle_device_attribute *pattr = to_puzzle_dev_attr(attr);
++	int ret;
++
++	switch (pattr->type) {
++	case IEI_WT61P803_PUZZLE_VERSION:
++		return scnprintf(buf, PAGE_SIZE, "%s\n", mcu->version.version);
++	case IEI_WT61P803_PUZZLE_BUILD_INFO:
++		return scnprintf(buf, PAGE_SIZE, "%s\n", mcu->version.build_info);
++	case IEI_WT61P803_PUZZLE_BOOTLOADER_MODE:
++		return scnprintf(buf, PAGE_SIZE, "%d\n", mcu->version.bootloader_mode);
++	case IEI_WT61P803_PUZZLE_PROTOCOL_VERSION:
++		return scnprintf(buf, PAGE_SIZE, "%s\n", mcu->version.protocol_version);
++	case IEI_WT61P803_PUZZLE_SERIAL_NUMBER:
++		ret = iei_wt61p803_puzzle_get_serial_number(mcu);
++		if (!ret)
++			ret = scnprintf(buf, PAGE_SIZE, "%s\n", mcu->version.serial_number);
++		else
++			ret = 0;
++		return ret;
++	case IEI_WT61P803_PUZZLE_MAC_ADDRESS:
++		ret = iei_wt61p803_puzzle_get_mac_address(mcu, pattr->index);
++		if (!ret)
++			ret = scnprintf(buf, PAGE_SIZE, "%s\n",
++					mcu->version.mac_address[pattr->index]);
++		else
++			ret = 0;
++		return ret;
++	case IEI_WT61P803_PUZZLE_AC_RECOVERY_STATUS:
++	case IEI_WT61P803_PUZZLE_POWER_LOSS_RECOVERY:
++	case IEI_WT61P803_PUZZLE_POWER_STATUS:
++		ret = iei_wt61p803_puzzle_get_mcu_status(mcu);
++		if (ret)
++			return ret;
++
++		mutex_lock(&mcu->lock);
++		switch (pattr->type) {
++		case IEI_WT61P803_PUZZLE_AC_RECOVERY_STATUS:
++			ret = scnprintf(buf, PAGE_SIZE, "%x\n",
++					mcu->status.ac_recovery_status_flag);
++			break;
++		case IEI_WT61P803_PUZZLE_POWER_LOSS_RECOVERY:
++			ret = scnprintf(buf, PAGE_SIZE, "%x\n", mcu->status.power_loss_recovery);
++			break;
++		case IEI_WT61P803_PUZZLE_POWER_STATUS:
++			ret = scnprintf(buf, PAGE_SIZE, "%x\n", mcu->status.power_status);
++			break;
++		default:
++			ret = 0;
++			break;
++		}
++		mutex_unlock(&mcu->lock);
++		return ret;
++	default:
++		return 0;
++	}
++
++	return 0;
++}
++
++static ssize_t store_output(struct device *dev,
++			    struct device_attribute *attr,
++			    const char *buf, size_t len)
++{
++	unsigned char serial_number[IEI_WT61P803_PUZZLE_SN_LENGTH];
++	unsigned char mac_address[IEI_WT61P803_PUZZLE_MAC_LENGTH];
++	struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev);
++	struct iei_wt61p803_puzzle_device_attribute *pattr = to_puzzle_dev_attr(attr);
++	int power_loss_recovery_action = 0;
++	int ret;
++
++	switch (pattr->type) {
++	case IEI_WT61P803_PUZZLE_SERIAL_NUMBER:
++		if (len != (size_t)(IEI_WT61P803_PUZZLE_SN_LENGTH + 1))
++			return -EINVAL;
++		memcpy(serial_number, buf, sizeof(serial_number));
++		ret = iei_wt61p803_puzzle_write_serial_number(mcu, serial_number);
++		if (ret)
++			return ret;
++		return len;
++	case IEI_WT61P803_PUZZLE_MAC_ADDRESS:
++		if (len != (size_t)(IEI_WT61P803_PUZZLE_MAC_LENGTH + 1))
++			return -EINVAL;
++
++		memcpy(mac_address, buf, sizeof(mac_address));
++
++		if (strlen(attr->attr.name) != 13)
++			return -EIO;
++
++		ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, pattr->index);
++		if (ret)
++			return ret;
++		return len;
++	case IEI_WT61P803_PUZZLE_POWER_LOSS_RECOVERY:
++		ret = kstrtoint(buf, 10, &power_loss_recovery_action);
++		if (ret)
++			return ret;
++		ret = iei_wt61p803_puzzle_write_power_loss_recovery(mcu,
++								    power_loss_recovery_action);
++		if (ret)
++			return ret;
++		return len;
++	default:
++		return -EINVAL;
++	}
++
++	return 0;
++}
++
++#define IEI_WT61P803_PUZZLE_ATTR(_name, _mode, _show, _store, _type, _index) \
++	struct iei_wt61p803_puzzle_device_attribute dev_attr_##_name = \
++		{ .dev_attr	= __ATTR(_name, _mode, _show, _store), \
++		  .type		= _type, \
++		  .index	= _index }
++
++#define IEI_WT61P803_PUZZLE_ATTR_RO(_name, _type, _id) \
++	IEI_WT61P803_PUZZLE_ATTR(_name, 0444, show_output, NULL, _type, _id)
++
++#define IEI_WT61P803_PUZZLE_ATTR_RW(_name, _type, _id) \
++	IEI_WT61P803_PUZZLE_ATTR(_name, 0644, show_output, store_output, _type, _id)
++
++static IEI_WT61P803_PUZZLE_ATTR_RO(version, IEI_WT61P803_PUZZLE_VERSION, 0);
++static IEI_WT61P803_PUZZLE_ATTR_RO(build_info, IEI_WT61P803_PUZZLE_BUILD_INFO, 0);
++static IEI_WT61P803_PUZZLE_ATTR_RO(bootloader_mode, IEI_WT61P803_PUZZLE_BOOTLOADER_MODE, 0);
++static IEI_WT61P803_PUZZLE_ATTR_RO(protocol_version, IEI_WT61P803_PUZZLE_PROTOCOL_VERSION, 0);
++static IEI_WT61P803_PUZZLE_ATTR_RW(serial_number, IEI_WT61P803_PUZZLE_SERIAL_NUMBER, 0);
++static IEI_WT61P803_PUZZLE_ATTR_RW(mac_address_0, IEI_WT61P803_PUZZLE_MAC_ADDRESS, 0);
++static IEI_WT61P803_PUZZLE_ATTR_RW(mac_address_1, IEI_WT61P803_PUZZLE_MAC_ADDRESS, 1);
++static IEI_WT61P803_PUZZLE_ATTR_RW(mac_address_2, IEI_WT61P803_PUZZLE_MAC_ADDRESS, 2);
++static IEI_WT61P803_PUZZLE_ATTR_RW(mac_address_3, IEI_WT61P803_PUZZLE_MAC_ADDRESS, 3);
++static IEI_WT61P803_PUZZLE_ATTR_RW(mac_address_4, IEI_WT61P803_PUZZLE_MAC_ADDRESS, 4);
++static IEI_WT61P803_PUZZLE_ATTR_RW(mac_address_5, IEI_WT61P803_PUZZLE_MAC_ADDRESS, 5);
++static IEI_WT61P803_PUZZLE_ATTR_RW(mac_address_6, IEI_WT61P803_PUZZLE_MAC_ADDRESS, 6);
++static IEI_WT61P803_PUZZLE_ATTR_RW(mac_address_7, IEI_WT61P803_PUZZLE_MAC_ADDRESS, 7);
++static IEI_WT61P803_PUZZLE_ATTR_RO(ac_recovery_status, IEI_WT61P803_PUZZLE_AC_RECOVERY_STATUS, 0);
++static IEI_WT61P803_PUZZLE_ATTR_RW(power_loss_recovery, IEI_WT61P803_PUZZLE_POWER_LOSS_RECOVERY, 0);
++static IEI_WT61P803_PUZZLE_ATTR_RO(power_status, IEI_WT61P803_PUZZLE_POWER_STATUS, 0);
++
++static struct attribute *iei_wt61p803_puzzle_attrs[] = {
++	&dev_attr_version.dev_attr.attr,
++	&dev_attr_build_info.dev_attr.attr,
++	&dev_attr_bootloader_mode.dev_attr.attr,
++	&dev_attr_protocol_version.dev_attr.attr,
++	&dev_attr_serial_number.dev_attr.attr,
++	&dev_attr_mac_address_0.dev_attr.attr,
++	&dev_attr_mac_address_1.dev_attr.attr,
++	&dev_attr_mac_address_2.dev_attr.attr,
++	&dev_attr_mac_address_3.dev_attr.attr,
++	&dev_attr_mac_address_4.dev_attr.attr,
++	&dev_attr_mac_address_5.dev_attr.attr,
++	&dev_attr_mac_address_6.dev_attr.attr,
++	&dev_attr_mac_address_7.dev_attr.attr,
++	&dev_attr_ac_recovery_status.dev_attr.attr,
++	&dev_attr_power_loss_recovery.dev_attr.attr,
++	&dev_attr_power_status.dev_attr.attr,
++	NULL
++};
++ATTRIBUTE_GROUPS(iei_wt61p803_puzzle);
++
++static int iei_wt61p803_puzzle_sysfs_create(struct device *dev,
++					    struct iei_wt61p803_puzzle *mcu)
++{
++	int ret;
++
++	ret = sysfs_create_groups(&mcu->dev->kobj, iei_wt61p803_puzzle_groups);
++	if (ret)
++		mfd_remove_devices(mcu->dev);
++
++	return ret;
++}
++
++static int iei_wt61p803_puzzle_sysfs_remove(struct device *dev,
++					    struct iei_wt61p803_puzzle *mcu)
++{
++	/* Remove sysfs groups */
++	sysfs_remove_groups(&mcu->dev->kobj, iei_wt61p803_puzzle_groups);
++	mfd_remove_devices(mcu->dev);
++
++	return 0;
++}
++
++static int iei_wt61p803_puzzle_probe(struct serdev_device *serdev)
++{
++	struct device *dev = &serdev->dev;
++	struct iei_wt61p803_puzzle *mcu;
++	u32 baud;
++	int ret;
++
++	/* Read the baud rate from 'current-speed', because the MCU supports different rates */
++	if (device_property_read_u32(dev, "current-speed", &baud)) {
++		dev_err(dev,
++			"'current-speed' is not specified in device node\n");
++		return -EINVAL;
++	}
++	dev_dbg(dev, "Driver baud rate: %d\n", baud);
++
++	/* Allocate the memory */
++	mcu = devm_kzalloc(dev, sizeof(*mcu), GFP_KERNEL);
++	if (!mcu)
++		return -ENOMEM;
++
++	mcu->reply = devm_kzalloc(dev, sizeof(*mcu->reply), GFP_KERNEL);
++	if (!mcu->reply)
++		return -ENOMEM;
++
++	/* Initialize device struct data */
++	mcu->serdev = serdev;
++	mcu->dev = dev;
++	init_completion(&mcu->reply->received);
++	mutex_init(&mcu->reply_lock);
++	mutex_init(&mcu->lock);
++
++	/* Setup UART interface */
++	serdev_device_set_drvdata(serdev, mcu);
++	serdev_device_set_client_ops(serdev, &iei_wt61p803_puzzle_serdev_device_ops);
++	ret = devm_serdev_device_open(dev, serdev);
++	if (ret)
++		return ret;
++	serdev_device_set_baudrate(serdev, baud);
++	serdev_device_set_flow_control(serdev, false);
++	ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
++	if (ret) {
++		dev_err(dev, "Failed to set parity\n");
++		return ret;
++	}
++
++	ret = iei_wt61p803_puzzle_get_version(mcu);
++	if (ret)
++		return ret;
++
++	dev_dbg(dev, "MCU version: %s\n", mcu->version.version);
++	dev_dbg(dev, "MCU firmware build info: %s\n", mcu->version.build_info);
++	dev_dbg(dev, "MCU in bootloader mode: %s\n",
++		mcu->version.bootloader_mode ? "true" : "false");
++	dev_dbg(dev, "MCU protocol version: %s\n", mcu->version.protocol_version);
++
++	if (device_property_read_bool(dev, "enable-beep")) {
++		ret = iei_wt61p803_puzzle_buzzer(mcu, false);
++		if (ret)
++			return ret;
++	}
++
++	ret = iei_wt61p803_puzzle_sysfs_create(dev, mcu);
++	if (ret)
++		return ret;
++
++	return devm_of_platform_populate(dev);
++}
++
++static void iei_wt61p803_puzzle_remove(struct serdev_device *serdev)
++{
++	struct device *dev = &serdev->dev;
++	struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev);
++
++	iei_wt61p803_puzzle_sysfs_remove(dev, mcu);
++}
++
++static const struct of_device_id iei_wt61p803_puzzle_dt_ids[] = {
++	{ .compatible = "iei,wt61p803-puzzle" },
++	{ }
++};
++
++MODULE_DEVICE_TABLE(of, iei_wt61p803_puzzle_dt_ids);
++
++static struct serdev_device_driver iei_wt61p803_puzzle_drv = {
++	.probe			= iei_wt61p803_puzzle_probe,
++	.remove			= iei_wt61p803_puzzle_remove,
++	.driver = {
++		.name		= "iei-wt61p803-puzzle",
++		.of_match_table	= iei_wt61p803_puzzle_dt_ids,
++	},
++};
++
++module_serdev_device_driver(iei_wt61p803_puzzle_drv);
++
++MODULE_LICENSE("GPL v2");
++MODULE_AUTHOR("Luka Kovacic <luka.kovacic at sartura.hr>");
++MODULE_DESCRIPTION("IEI WT61P803 PUZZLE MCU Driver");
+--- /dev/null
++++ b/include/linux/mfd/iei-wt61p803-puzzle.h
+@@ -0,0 +1,66 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* IEI WT61P803 PUZZLE MCU Driver
++ * System management microcontroller for fan control, temperature sensor reading,
++ * LED control and system identification on IEI Puzzle series ARM-based appliances.
++ *
++ * Copyright (C) 2020 Sartura Ltd.
++ * Author: Luka Kovacic <luka.kovacic at sartura.hr>
++ */
++
++#ifndef _MFD_IEI_WT61P803_PUZZLE_H_
++#define _MFD_IEI_WT61P803_PUZZLE_H_
++
++#define IEI_WT61P803_PUZZLE_BUF_SIZE 512
++
++/* Command magic numbers */
++#define IEI_WT61P803_PUZZLE_CMD_HEADER_START		0x40 /* @ */
++#define IEI_WT61P803_PUZZLE_CMD_HEADER_START_OTHER	0x25 /* % */
++#define IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM		0xF7
++
++#define IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK		0x30 /* 0 */
++#define IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK	0x70
++
++#define IEI_WT61P803_PUZZLE_CMD_EEPROM_READ		0xA1
++#define IEI_WT61P803_PUZZLE_CMD_EEPROM_WRITE		0xA0
++
++#define IEI_WT61P803_PUZZLE_CMD_OTHER_VERSION		0x56 /* V */
++#define IEI_WT61P803_PUZZLE_CMD_OTHER_BUILD		0x42 /* B */
++#define IEI_WT61P803_PUZZLE_CMD_OTHER_BOOTLOADER_MODE	0x4D /* M */
++#define IEI_WT61P803_PUZZLE_CMD_OTHER_MODE_BOOTLOADER	0x30
++#define IEI_WT61P803_PUZZLE_CMD_OTHER_MODE_APPS		0x31
++#define IEI_WT61P803_PUZZLE_CMD_OTHER_PROTOCOL_VERSION	0x50 /* P */
++
++#define IEI_WT61P803_PUZZLE_CMD_FUNCTION_SINGLE		0x43 /* C */
++#define IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER		0x4F /* O */
++#define IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_STATUS	0x53 /* S */
++#define IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_POWER_LOSS 0x41 /* A */
++
++#define IEI_WT61P803_PUZZLE_CMD_LED			0x52 /* R */
++#define IEI_WT61P803_PUZZLE_CMD_LED_POWER		0x31 /* 1 */
++
++#define IEI_WT61P803_PUZZLE_CMD_TEMP			0x54 /* T */
++#define IEI_WT61P803_PUZZLE_CMD_TEMP_ALL		0x41 /* A */
++
++#define IEI_WT61P803_PUZZLE_CMD_FAN			0x46 /* F */
++#define IEI_WT61P803_PUZZLE_CMD_FAN_PWM_READ		0x5A /* Z */
++#define IEI_WT61P803_PUZZLE_CMD_FAN_PWM_WRITE		0x57 /* W */
++#define IEI_WT61P803_PUZZLE_CMD_FAN_PWM_BASE		0x30
++#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM_BASE		0x41 /* A */
++
++#define IEI_WT61P803_PUZZLE_CMD_FAN_PWM(x) (IEI_WT61P803_PUZZLE_CMD_FAN_PWM_BASE + (x)) /* 0 - 1 */
++#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM(x) (IEI_WT61P803_PUZZLE_CMD_FAN_RPM_BASE + (x)) /* 0 - 5 */
++
++struct iei_wt61p803_puzzle_mcu_version;
++struct iei_wt61p803_puzzle_reply;
++struct iei_wt61p803_puzzle;
++
++int iei_wt61p803_puzzle_write_command_watchdog(struct iei_wt61p803_puzzle *mcu,
++					       unsigned char *cmd, size_t size,
++					       unsigned char *reply_data, size_t *reply_size,
++					       int retry_count);
++
++int iei_wt61p803_puzzle_write_command(struct iei_wt61p803_puzzle *mcu,
++				      unsigned char *cmd, size_t size,
++				      unsigned char *reply_data, size_t *reply_size);
++
++#endif /* _MFD_IEI_WT61P803_PUZZLE_H_ */
diff --git a/target/linux/mvebu/patches-5.4/903-drivers-hwmon-Add-the-IEI-WT61P803-PUZZLE-HWMON-driv.patch b/target/linux/mvebu/patches-5.4/903-drivers-hwmon-Add-the-IEI-WT61P803-PUZZLE-HWMON-driv.patch
new file mode 100644
index 0000000000..550ce6387a
--- /dev/null
+++ b/target/linux/mvebu/patches-5.4/903-drivers-hwmon-Add-the-IEI-WT61P803-PUZZLE-HWMON-driv.patch
@@ -0,0 +1,469 @@
+From e3310a638cd310bfd93dbbc6d2732ab6aea18dd2 Mon Sep 17 00:00:00 2001
+From: Luka Kovacic <luka.kovacic () sartura ! hr>
+Date: Tue, 24 Aug 2021 12:44:34 +0000
+Subject: [PATCH 3/7] drivers: hwmon: Add the IEI WT61P803 PUZZLE HWMON driver
+
+Add the IEI WT61P803 PUZZLE HWMON driver, that handles the fan speed
+control via PWM, reading fan speed and reading on-board temperature
+sensors.
+
+The driver registers a HWMON device and a simple thermal cooling device to
+enable in-kernel fan management.
+
+This driver depends on the IEI WT61P803 PUZZLE MFD driver.
+
+Signed-off-by: Luka Kovacic <luka.kovacic at sartura.hr>
+Signed-off-by: Pavo Banicevic <pavo.banicevic at sartura.hr>
+Acked-by: Guenter Roeck <linux at roeck-us.net>
+Cc: Luka Perkov <luka.perkov at sartura.hr>
+Cc: Robert Marko <robert.marko at sartura.hr>
+---
+ drivers/hwmon/Kconfig                     |   8 +
+ drivers/hwmon/Makefile                    |   1 +
+ drivers/hwmon/iei-wt61p803-puzzle-hwmon.c | 413 ++++++++++++++++++++++
+ 3 files changed, 422 insertions(+)
+ create mode 100644 drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
+
+--- a/drivers/hwmon/Kconfig
++++ b/drivers/hwmon/Kconfig
+@@ -639,6 +639,14 @@ config SENSORS_IBMPOWERNV
+ 	  This driver can also be built as a module. If so, the module
+ 	  will be called ibmpowernv.
+ 
++config SENSORS_IEI_WT61P803_PUZZLE_HWMON
++	tristate "IEI WT61P803 PUZZLE MFD HWMON Driver"
++	depends on MFD_IEI_WT61P803_PUZZLE
++	help
++	  The IEI WT61P803 PUZZLE MFD HWMON Driver handles reading fan speed
++	  and writing fan PWM values. It also supports reading on-board
++	  temperature sensors.
++
+ config SENSORS_IIO_HWMON
+ 	tristate "Hwmon driver that uses channels specified via iio maps"
+ 	depends on IIO
+--- a/drivers/hwmon/Makefile
++++ b/drivers/hwmon/Makefile
+@@ -77,6 +77,7 @@ obj-$(CONFIG_SENSORS_HIH6130)	+= hih6130
+ obj-$(CONFIG_SENSORS_ULTRA45)	+= ultra45_env.o
+ obj-$(CONFIG_SENSORS_I5500)	+= i5500_temp.o
+ obj-$(CONFIG_SENSORS_I5K_AMB)	+= i5k_amb.o
++obj-$(CONFIG_SENSORS_IEI_WT61P803_PUZZLE_HWMON) += iei-wt61p803-puzzle-hwmon.o
+ obj-$(CONFIG_SENSORS_IBMAEM)	+= ibmaem.o
+ obj-$(CONFIG_SENSORS_IBMPEX)	+= ibmpex.o
+ obj-$(CONFIG_SENSORS_IBMPOWERNV)+= ibmpowernv.o
+--- /dev/null
++++ b/drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
+@@ -0,0 +1,413 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* IEI WT61P803 PUZZLE MCU HWMON Driver
++ *
++ * Copyright (C) 2020 Sartura Ltd.
++ * Author: Luka Kovacic <luka.kovacic at sartura.hr>
++ */
++
++#include <linux/err.h>
++#include <linux/hwmon.h>
++#include <linux/interrupt.h>
++#include <linux/irq.h>
++#include <linux/math64.h>
++#include <linux/mfd/iei-wt61p803-puzzle.h>
++#include <linux/mod_devicetable.h>
++#include <linux/module.h>
++#include <linux/platform_device.h>
++#include <linux/property.h>
++#include <linux/slab.h>
++#include <linux/thermal.h>
++
++#define IEI_WT61P803_PUZZLE_HWMON_MAX_PWM	2
++#define IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_VAL	255
++
++/**
++ * struct iei_wt61p803_puzzle_thermal_cooling_device - Thermal cooling device instance
++ * @mcu_hwmon:		Parent driver struct pointer
++ * @tcdev:		Thermal cooling device pointer
++ * @name:		Thermal cooling device name
++ * @pwm_channel:	Controlled PWM channel (0 or 1)
++ * @cooling_levels:	Thermal cooling device cooling levels (DT)
++ */
++struct iei_wt61p803_puzzle_thermal_cooling_device {
++	struct iei_wt61p803_puzzle_hwmon *mcu_hwmon;
++	struct thermal_cooling_device *tcdev;
++	char name[THERMAL_NAME_LENGTH];
++	int pwm_channel;
++	u8 *cooling_levels;
++};
++
++/**
++ * struct iei_wt61p803_puzzle_hwmon - MCU HWMON Driver
++ * @mcu:				MCU struct pointer
++ * @response_buffer			Global MCU response buffer
++ * @thermal_cooling_dev_present:	Per-channel thermal cooling device control indicator
++ * @cdev:				Per-channel thermal cooling device private structure
++ */
++struct iei_wt61p803_puzzle_hwmon {
++	struct iei_wt61p803_puzzle *mcu;
++	unsigned char response_buffer[IEI_WT61P803_PUZZLE_BUF_SIZE];
++	bool thermal_cooling_dev_present[IEI_WT61P803_PUZZLE_HWMON_MAX_PWM];
++	struct iei_wt61p803_puzzle_thermal_cooling_device
++		*cdev[IEI_WT61P803_PUZZLE_HWMON_MAX_PWM];
++	struct mutex lock; /* mutex to protect response_buffer array */
++};
++
++#define raw_temp_to_milidegree_celsius(x) (((x) - 0x80) * 1000)
++static int iei_wt61p803_puzzle_read_temp_sensor(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon,
++						int channel, long *value)
++{
++	unsigned char *resp_buf = mcu_hwmon->response_buffer;
++	unsigned char temp_sensor_ntc_cmd[4] = {
++		IEI_WT61P803_PUZZLE_CMD_HEADER_START,
++		IEI_WT61P803_PUZZLE_CMD_TEMP,
++		IEI_WT61P803_PUZZLE_CMD_TEMP_ALL,
++	};
++	size_t reply_size;
++	int ret;
++
++	mutex_lock(&mcu_hwmon->lock);
++	ret = iei_wt61p803_puzzle_write_command(mcu_hwmon->mcu, temp_sensor_ntc_cmd,
++						sizeof(temp_sensor_ntc_cmd), resp_buf,
++						&reply_size);
++	if (ret)
++		goto exit;
++
++	if (reply_size != 7) {
++		ret = -EIO;
++		goto exit;
++	}
++
++	/* Check the number of NTC values */
++	if (resp_buf[3] != '2') {
++		ret = -EIO;
++		goto exit;
++	}
++
++	*value = raw_temp_to_milidegree_celsius(resp_buf[4 + channel]);
++exit:
++	mutex_unlock(&mcu_hwmon->lock);
++	return ret;
++}
++
++#define raw_fan_val_to_rpm(x, y) ((((x) << 8 | (y)) / 2) * 60)
++static int iei_wt61p803_puzzle_read_fan_speed(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon,
++					      int channel, long *value)
++{
++	unsigned char *resp_buf = mcu_hwmon->response_buffer;
++	unsigned char fan_speed_cmd[4] = {};
++	size_t reply_size;
++	int ret;
++
++	fan_speed_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START;
++	fan_speed_cmd[1] = IEI_WT61P803_PUZZLE_CMD_FAN;
++	fan_speed_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_RPM(channel);
++
++	mutex_lock(&mcu_hwmon->lock);
++	ret = iei_wt61p803_puzzle_write_command(mcu_hwmon->mcu, fan_speed_cmd,
++						sizeof(fan_speed_cmd), resp_buf,
++						&reply_size);
++	if (ret)
++		goto exit;
++
++	if (reply_size != 7) {
++		ret = -EIO;
++		goto exit;
++	}
++
++	*value = raw_fan_val_to_rpm(resp_buf[3], resp_buf[4]);
++exit:
++	mutex_unlock(&mcu_hwmon->lock);
++	return ret;
++}
++
++static int iei_wt61p803_puzzle_write_pwm_channel(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon,
++						 int channel, long pwm_set_val)
++{
++	unsigned char *resp_buf = mcu_hwmon->response_buffer;
++	unsigned char pwm_set_cmd[6] = {};
++	size_t reply_size;
++	int ret;
++
++	pwm_set_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START;
++	pwm_set_cmd[1] = IEI_WT61P803_PUZZLE_CMD_FAN;
++	pwm_set_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM_WRITE;
++	pwm_set_cmd[3] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM(channel);
++	pwm_set_cmd[4] = pwm_set_val;
++
++	mutex_lock(&mcu_hwmon->lock);
++	ret = iei_wt61p803_puzzle_write_command(mcu_hwmon->mcu, pwm_set_cmd,
++						sizeof(pwm_set_cmd), resp_buf,
++						&reply_size);
++	if (ret)
++		goto exit;
++
++	if (reply_size != 3) {
++		ret = -EIO;
++		goto exit;
++	}
++
++	if (!(resp_buf[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START &&
++	      resp_buf[1] == IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK &&
++	      resp_buf[2] == IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK)) {
++		ret = -EIO;
++		goto exit;
++	}
++exit:
++	mutex_unlock(&mcu_hwmon->lock);
++	return ret;
++}
++
++static int iei_wt61p803_puzzle_read_pwm_channel(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon,
++						int channel, long *value)
++{
++	unsigned char *resp_buf = mcu_hwmon->response_buffer;
++	unsigned char pwm_get_cmd[5] = {};
++	size_t reply_size;
++	int ret;
++
++	pwm_get_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START;
++	pwm_get_cmd[1] = IEI_WT61P803_PUZZLE_CMD_FAN;
++	pwm_get_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM_READ;
++	pwm_get_cmd[3] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM(channel);
++
++	ret = iei_wt61p803_puzzle_write_command(mcu_hwmon->mcu, pwm_get_cmd,
++						sizeof(pwm_get_cmd), resp_buf,
++						&reply_size);
++	if (ret)
++		return ret;
++
++	if (reply_size != 5)
++		return -EIO;
++
++	if (resp_buf[2] != IEI_WT61P803_PUZZLE_CMD_FAN_PWM_READ)
++		return -EIO;
++
++	*value = resp_buf[3];
++
++	return 0;
++}
++
++static int iei_wt61p803_puzzle_read(struct device *dev, enum hwmon_sensor_types type,
++				    u32 attr, int channel, long *val)
++{
++	struct iei_wt61p803_puzzle_hwmon *mcu_hwmon = dev_get_drvdata(dev->parent);
++
++	switch (type) {
++	case hwmon_pwm:
++		return iei_wt61p803_puzzle_read_pwm_channel(mcu_hwmon, channel, val);
++	case hwmon_fan:
++		return iei_wt61p803_puzzle_read_fan_speed(mcu_hwmon, channel, val);
++	case hwmon_temp:
++		return iei_wt61p803_puzzle_read_temp_sensor(mcu_hwmon, channel, val);
++	default:
++		return -EINVAL;
++	}
++}
++
++static int iei_wt61p803_puzzle_write(struct device *dev, enum hwmon_sensor_types type,
++				     u32 attr, int channel, long val)
++{
++	struct iei_wt61p803_puzzle_hwmon *mcu_hwmon = dev_get_drvdata(dev->parent);
++
++	return iei_wt61p803_puzzle_write_pwm_channel(mcu_hwmon, channel, val);
++}
++
++static umode_t iei_wt61p803_puzzle_is_visible(const void *data, enum hwmon_sensor_types type,
++					      u32 attr, int channel)
++{
++	const struct iei_wt61p803_puzzle_hwmon *mcu_hwmon = data;
++
++	switch (type) {
++	case hwmon_pwm:
++		if (mcu_hwmon->thermal_cooling_dev_present[channel])
++			return 0444;
++		if (attr == hwmon_pwm_input)
++			return 0644;
++		break;
++	case hwmon_fan:
++		if (attr == hwmon_fan_input)
++			return 0444;
++		break;
++	case hwmon_temp:
++		if (attr == hwmon_temp_input)
++			return 0444;
++		break;
++	default:
++		return 0;
++	}
++
++	return 0;
++}
++
++static const struct hwmon_ops iei_wt61p803_puzzle_hwmon_ops = {
++	.is_visible = iei_wt61p803_puzzle_is_visible,
++	.read = iei_wt61p803_puzzle_read,
++	.write = iei_wt61p803_puzzle_write,
++};
++
++static const struct hwmon_channel_info *iei_wt61p803_puzzle_info[] = {
++	HWMON_CHANNEL_INFO(pwm,
++			   HWMON_PWM_INPUT,
++			   HWMON_PWM_INPUT),
++	HWMON_CHANNEL_INFO(fan,
++			   HWMON_F_INPUT,
++			   HWMON_F_INPUT,
++			   HWMON_F_INPUT,
++			   HWMON_F_INPUT,
++			   HWMON_F_INPUT),
++	HWMON_CHANNEL_INFO(temp,
++			   HWMON_T_INPUT,
++			   HWMON_T_INPUT),
++	NULL
++};
++
++static const struct hwmon_chip_info iei_wt61p803_puzzle_chip_info = {
++	.ops = &iei_wt61p803_puzzle_hwmon_ops,
++	.info = iei_wt61p803_puzzle_info,
++};
++
++static int iei_wt61p803_puzzle_get_max_state(struct thermal_cooling_device *tcdev,
++					     unsigned long *state)
++{
++	*state = IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_VAL;
++
++	return 0;
++}
++
++static int iei_wt61p803_puzzle_get_cur_state(struct thermal_cooling_device *tcdev,
++					     unsigned long *state)
++{
++	struct iei_wt61p803_puzzle_thermal_cooling_device *cdev = tcdev->devdata;
++	struct iei_wt61p803_puzzle_hwmon *mcu_hwmon = cdev->mcu_hwmon;
++	long value;
++	int ret;
++
++	ret = iei_wt61p803_puzzle_read_pwm_channel(mcu_hwmon, cdev->pwm_channel, &value);
++	if (ret)
++		return ret;
++	*state = value;
++	return 0;
++}
++
++static int iei_wt61p803_puzzle_set_cur_state(struct thermal_cooling_device *tcdev,
++					     unsigned long state)
++{
++	struct iei_wt61p803_puzzle_thermal_cooling_device *cdev = tcdev->devdata;
++	struct iei_wt61p803_puzzle_hwmon *mcu_hwmon = cdev->mcu_hwmon;
++
++	return iei_wt61p803_puzzle_write_pwm_channel(mcu_hwmon, cdev->pwm_channel, state);
++}
++
++static const struct thermal_cooling_device_ops iei_wt61p803_puzzle_cooling_ops = {
++	.get_max_state = iei_wt61p803_puzzle_get_max_state,
++	.get_cur_state = iei_wt61p803_puzzle_get_cur_state,
++	.set_cur_state = iei_wt61p803_puzzle_set_cur_state,
++};
++
++static int
++iei_wt61p803_puzzle_enable_thermal_cooling_dev(struct device *dev,
++					       struct fwnode_handle *child,
++					       struct iei_wt61p803_puzzle_hwmon *mcu_hwmon)
++{
++	struct iei_wt61p803_puzzle_thermal_cooling_device *cdev;
++	u32 pwm_channel;
++	u8 num_levels;
++	int ret;
++
++	ret = fwnode_property_read_u32(child, "reg", &pwm_channel);
++	if (ret)
++		return ret;
++
++	mcu_hwmon->thermal_cooling_dev_present[pwm_channel] = true;
++
++	num_levels = fwnode_property_count_u8(child, "cooling-levels");
++	if (!num_levels)
++		return -EINVAL;
++
++	cdev = devm_kzalloc(dev, sizeof(*cdev), GFP_KERNEL);
++	if (!cdev)
++		return -ENOMEM;
++
++	cdev->cooling_levels = devm_kmalloc_array(dev, num_levels, sizeof(u8), GFP_KERNEL);
++	if (!cdev->cooling_levels)
++		return -ENOMEM;
++
++	ret = fwnode_property_read_u8_array(child, "cooling-levels",
++					    cdev->cooling_levels,
++					    num_levels);
++	if (ret) {
++		dev_err(dev, "Couldn't read property 'cooling-levels'\n");
++		return ret;
++	}
++
++	snprintf(cdev->name, THERMAL_NAME_LENGTH, "wt61p803_puzzle_%d", pwm_channel);
++	cdev->tcdev = devm_thermal_of_cooling_device_register(dev, NULL, cdev->name, cdev,
++							      &iei_wt61p803_puzzle_cooling_ops);
++	if (IS_ERR(cdev->tcdev))
++		return PTR_ERR(cdev->tcdev);
++
++	cdev->mcu_hwmon = mcu_hwmon;
++	cdev->pwm_channel = pwm_channel;
++	mcu_hwmon->cdev[pwm_channel] = cdev;
++
++	return 0;
++}
++
++static int iei_wt61p803_puzzle_hwmon_probe(struct platform_device *pdev)
++{
++	struct device *dev = &pdev->dev;
++	struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev->parent);
++	struct iei_wt61p803_puzzle_hwmon *mcu_hwmon;
++	struct fwnode_handle *child;
++	struct device *hwmon_dev;
++	int ret;
++
++	mcu_hwmon = devm_kzalloc(dev, sizeof(*mcu_hwmon), GFP_KERNEL);
++	if (!mcu_hwmon)
++		return -ENOMEM;
++
++	mcu_hwmon->mcu = mcu;
++	platform_set_drvdata(pdev, mcu_hwmon);
++	mutex_init(&mcu_hwmon->lock);
++
++	hwmon_dev = devm_hwmon_device_register_with_info(dev, "iei_wt61p803_puzzle",
++							 mcu_hwmon,
++							 &iei_wt61p803_puzzle_chip_info,
++							 NULL);
++	if (IS_ERR(hwmon_dev))
++		return PTR_ERR(hwmon_dev);
++
++	/* Control fans via PWM lines via Linux Kernel */
++	if (IS_ENABLED(CONFIG_THERMAL)) {
++		device_for_each_child_node(dev, child) {
++			ret = iei_wt61p803_puzzle_enable_thermal_cooling_dev(dev, child, mcu_hwmon);
++			if (ret) {
++				dev_err(dev, "Enabling the PWM fan failed\n");
++				fwnode_handle_put(child);
++				return ret;
++			}
++		}
++	}
++	return 0;
++}
++
++static const struct of_device_id iei_wt61p803_puzzle_hwmon_id_table[] = {
++	{ .compatible = "iei,wt61p803-puzzle-hwmon" },
++	{}
++};
++MODULE_DEVICE_TABLE(of, iei_wt61p803_puzzle_hwmon_id_table);
++
++static struct platform_driver iei_wt61p803_puzzle_hwmon_driver = {
++	.driver = {
++		.name = "iei-wt61p803-puzzle-hwmon",
++		.of_match_table = iei_wt61p803_puzzle_hwmon_id_table,
++	},
++	.probe = iei_wt61p803_puzzle_hwmon_probe,
++};
++
++module_platform_driver(iei_wt61p803_puzzle_hwmon_driver);
++
++MODULE_DESCRIPTION("IEI WT61P803 PUZZLE MCU HWMON Driver");
++MODULE_AUTHOR("Luka Kovacic <luka.kovacic at sartura.hr>");
++MODULE_LICENSE("GPL v2");
diff --git a/target/linux/mvebu/patches-5.4/904-drivers-leds-Add-the-IEI-WT61P803-PUZZLE-LED-driver.patch b/target/linux/mvebu/patches-5.4/904-drivers-leds-Add-the-IEI-WT61P803-PUZZLE-LED-driver.patch
new file mode 100644
index 0000000000..55224a2a20
--- /dev/null
+++ b/target/linux/mvebu/patches-5.4/904-drivers-leds-Add-the-IEI-WT61P803-PUZZLE-LED-driver.patch
@@ -0,0 +1,207 @@
+From f3b44eb69cc561cf05d00506dcec0dd9be003ed8 Mon Sep 17 00:00:00 2001
+From: Luka Kovacic <luka.kovacic () sartura ! hr>
+Date: Tue, 24 Aug 2021 12:44:35 +0000
+Subject: [PATCH 4/7] drivers: leds: Add the IEI WT61P803 PUZZLE LED driver
+
+Add support for the IEI WT61P803 PUZZLE LED driver.
+Currently only the front panel power LED is supported,
+since it is the only LED on this board wired through the
+MCU.
+
+The LED is wired directly to the on-board MCU controller
+and is toggled using an MCU command.
+
+Support for more LEDs is going to be added in case more
+boards implement this microcontroller, as LEDs use many
+different GPIOs.
+
+This driver depends on the IEI WT61P803 PUZZLE MFD driver.
+
+Signed-off-by: Luka Kovacic <luka.kovacic at sartura.hr>
+Signed-off-by: Pavo Banicevic <pavo.banicevic at sartura.hr>
+Cc: Luka Perkov <luka.perkov at sartura.hr>
+Cc: Robert Marko <robert.marko at sartura.hr>
+---
+ drivers/leds/Kconfig                    |   8 ++
+ drivers/leds/Makefile                   |   1 +
+ drivers/leds/leds-iei-wt61p803-puzzle.c | 147 ++++++++++++++++++++++++
+ 3 files changed, 156 insertions(+)
+ create mode 100644 drivers/leds/leds-iei-wt61p803-puzzle.c
+
+--- a/drivers/leds/Kconfig
++++ b/drivers/leds/Kconfig
+@@ -278,6 +278,14 @@ config LEDS_IPAQ_MICRO
+ 	  Choose this option if you want to use the notification LED on
+ 	  Compaq/HP iPAQ h3100 and h3600.
+ 
++config LEDS_IEI_WT61P803_PUZZLE
++	tristate "LED Support for the IEI WT61P803 PUZZLE MCU"
++	depends on LEDS_CLASS
++	depends on MFD_IEI_WT61P803_PUZZLE
++	help
++	  This option enables support for LEDs controlled by the IEI WT61P803
++	  M801 MCU.
++
+ config LEDS_HP6XX
+ 	tristate "LED Support for the HP Jornada 6xx"
+ 	depends on LEDS_CLASS
+--- a/drivers/leds/Makefile
++++ b/drivers/leds/Makefile
+@@ -43,6 +43,7 @@ obj-$(CONFIG_LEDS_LP8860)		+= leds-lp886
+ obj-$(CONFIG_LEDS_TCA6507)		+= leds-tca6507.o
+ obj-$(CONFIG_LEDS_TLC591XX)		+= leds-tlc591xx.o
+ obj-$(CONFIG_LEDS_CLEVO_MAIL)		+= leds-clevo-mail.o
++obj-$(CONFIG_LEDS_IEI_WT61P803_PUZZLE)	+= leds-iei-wt61p803-puzzle.o
+ obj-$(CONFIG_LEDS_IPAQ_MICRO)		+= leds-ipaq-micro.o
+ obj-$(CONFIG_LEDS_HP6XX)		+= leds-hp6xx.o
+ obj-$(CONFIG_LEDS_OT200)		+= leds-ot200.o
+--- /dev/null
++++ b/drivers/leds/leds-iei-wt61p803-puzzle.c
+@@ -0,0 +1,147 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* IEI WT61P803 PUZZLE MCU LED Driver
++ *
++ * Copyright (C) 2020 Sartura Ltd.
++ * Author: Luka Kovacic <luka.kovacic at sartura.hr>
++ */
++
++#include <linux/leds.h>
++#include <linux/mfd/iei-wt61p803-puzzle.h>
++#include <linux/mod_devicetable.h>
++#include <linux/module.h>
++#include <linux/platform_device.h>
++#include <linux/property.h>
++#include <linux/slab.h>
++
++enum iei_wt61p803_puzzle_led_state {
++	IEI_LED_OFF = 0x30,
++	IEI_LED_ON = 0x31,
++	IEI_LED_BLINK_5HZ = 0x32,
++	IEI_LED_BLINK_1HZ = 0x33,
++};
++
++/**
++ * struct iei_wt61p803_puzzle_led - MCU LED Driver
++ * @cdev:		LED classdev
++ * @mcu:		MCU struct pointer
++ * @response_buffer	Global MCU response buffer
++ * @lock:		General mutex lock to protect simultaneous R/W access to led_power_state
++ * @led_power_state:	State of the front panel power LED
++ */
++struct iei_wt61p803_puzzle_led {
++	struct led_classdev cdev;
++	struct iei_wt61p803_puzzle *mcu;
++	unsigned char response_buffer[IEI_WT61P803_PUZZLE_BUF_SIZE];
++	struct mutex lock; /* mutex to protect led_power_state */
++	int led_power_state;
++};
++
++static inline struct iei_wt61p803_puzzle_led *cdev_to_iei_wt61p803_puzzle_led
++	(struct led_classdev *led_cdev)
++{
++	return container_of(led_cdev, struct iei_wt61p803_puzzle_led, cdev);
++}
++
++static int iei_wt61p803_puzzle_led_brightness_set_blocking(struct led_classdev *cdev,
++							   enum led_brightness brightness)
++{
++	struct iei_wt61p803_puzzle_led *priv = cdev_to_iei_wt61p803_puzzle_led(cdev);
++	unsigned char *resp_buf = priv->response_buffer;
++	unsigned char led_power_cmd[5] = {};
++	size_t reply_size;
++	int ret;
++
++	led_power_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START;
++	led_power_cmd[1] = IEI_WT61P803_PUZZLE_CMD_LED;
++	led_power_cmd[2] = IEI_WT61P803_PUZZLE_CMD_LED_POWER;
++	led_power_cmd[3] = brightness == LED_OFF ? IEI_LED_OFF : IEI_LED_ON;
++
++	ret = iei_wt61p803_puzzle_write_command(priv->mcu, led_power_cmd,
++						sizeof(led_power_cmd),
++						resp_buf,
++						&reply_size);
++	if (ret)
++		return ret;
++
++	if (reply_size != 3)
++		return -EIO;
++
++	if (!(resp_buf[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START &&
++	      resp_buf[1] == IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK &&
++	      resp_buf[2] == IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK))
++		return -EIO;
++
++	mutex_lock(&priv->lock);
++	priv->led_power_state = brightness;
++	mutex_unlock(&priv->lock);
++
++	return 0;
++}
++
++static enum led_brightness iei_wt61p803_puzzle_led_brightness_get(struct led_classdev *cdev)
++{
++	struct iei_wt61p803_puzzle_led *priv = cdev_to_iei_wt61p803_puzzle_led(cdev);
++	int led_state;
++
++	mutex_lock(&priv->lock);
++	led_state = priv->led_power_state;
++	mutex_unlock(&priv->lock);
++
++	return led_state;
++}
++
++static int iei_wt61p803_puzzle_led_probe(struct platform_device *pdev)
++{
++	struct device *dev = &pdev->dev;
++	struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev->parent);
++	struct iei_wt61p803_puzzle_led *priv;
++	struct led_init_data init_data = {};
++	struct fwnode_handle *child;
++	int ret;
++
++	if (device_get_child_node_count(dev) != 1)
++		return -EINVAL;
++
++	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++	if (!priv)
++		return -ENOMEM;
++
++	priv->mcu = mcu;
++	priv->led_power_state = 1;
++	mutex_init(&priv->lock);
++	dev_set_drvdata(dev, priv);
++
++	child = device_get_next_child_node(dev, NULL);
++	init_data.fwnode = child;
++
++	priv->cdev.brightness_set_blocking = iei_wt61p803_puzzle_led_brightness_set_blocking;
++	priv->cdev.brightness_get = iei_wt61p803_puzzle_led_brightness_get;
++	priv->cdev.max_brightness = 1;
++
++	ret = devm_led_classdev_register_ext(dev, &priv->cdev, &init_data);
++	if (ret)
++		dev_err(dev, "Could not register LED\n");
++
++	fwnode_handle_put(child);
++	return ret;
++}
++
++static const struct of_device_id iei_wt61p803_puzzle_led_of_match[] = {
++	{ .compatible = "iei,wt61p803-puzzle-leds" },
++	{ }
++};
++MODULE_DEVICE_TABLE(of, iei_wt61p803_puzzle_led_of_match);
++
++static struct platform_driver iei_wt61p803_puzzle_led_driver = {
++	.driver = {
++		.name = "iei-wt61p803-puzzle-led",
++		.of_match_table = iei_wt61p803_puzzle_led_of_match,
++	},
++	.probe = iei_wt61p803_puzzle_led_probe,
++};
++module_platform_driver(iei_wt61p803_puzzle_led_driver);
++
++MODULE_DESCRIPTION("IEI WT61P803 PUZZLE front panel LED driver");
++MODULE_AUTHOR("Luka Kovacic <luka.kovacic at sartura.hr>");
++MODULE_LICENSE("GPL v2");
++MODULE_ALIAS("platform:leds-iei-wt61p803-puzzle");
diff --git a/target/linux/mvebu/patches-5.4/905-Documentation-ABI-Add-iei-wt61p803-puzzle-driver-sys.patch b/target/linux/mvebu/patches-5.4/905-Documentation-ABI-Add-iei-wt61p803-puzzle-driver-sys.patch
new file mode 100644
index 0000000000..b1d420ef0a
--- /dev/null
+++ b/target/linux/mvebu/patches-5.4/905-Documentation-ABI-Add-iei-wt61p803-puzzle-driver-sys.patch
@@ -0,0 +1,82 @@
+From 2fab3b4956c5b2f83c1e1abffc1df39de2933d83 Mon Sep 17 00:00:00 2001
+From: Luka Kovacic <luka.kovacic () sartura ! hr>
+Date: Tue, 24 Aug 2021 12:44:36 +0000
+Subject: [PATCH 5/7] Documentation/ABI: Add iei-wt61p803-puzzle driver sysfs
+ interface documentation
+
+Add the iei-wt61p803-puzzle driver sysfs interface documentation to allow
+monitoring and control of the microcontroller from user space.
+
+Signed-off-by: Luka Kovacic <luka.kovacic at sartura.hr>
+Signed-off-by: Pavo Banicevic <pavo.banicevic at sartura.hr>
+Cc: Luka Perkov <luka.perkov at sartura.hr>
+Cc: Robert Marko <robert.marko at sartura.hr>
+---
+ .../testing/sysfs-driver-iei-wt61p803-puzzle  | 61 +++++++++++++++++++
+ 1 file changed, 61 insertions(+)
+ create mode 100644 Documentation/ABI/testing/sysfs-driver-iei-wt61p803-puzzle
+
+--- /dev/null
++++ b/Documentation/ABI/testing/sysfs-driver-iei-wt61p803-puzzle
+@@ -0,0 +1,61 @@
++What:		/sys/bus/serial/devices/.../mac_address_*
++Date:		September 2020
++Contact:	Luka Kovacic <luka.kovacic at sartura.hr>
++Description:	(RW) Internal factory assigned MAC address values
++
++What:		/sys/bus/serial/devices/.../serial_number
++Date:		September 2020
++Contact:	Luka Kovacic <luka.kovacic at sartura.hr>
++Description:	(RW) Internal factory assigned serial number
++
++What:		/sys/bus/serial/devices/.../version
++Date:		September 2020
++Contact:	Luka Kovacic <luka.kovacic at sartura.hr>
++Description:	(RO) Internal MCU firmware version
++
++What:		/sys/bus/serial/devices/.../protocol_version
++Date:		September 2020
++Contact:	Luka Kovacic <luka.kovacic at sartura.hr>
++Description:	(RO) Internal MCU communication protocol version
++
++What:		/sys/bus/serial/devices/.../power_loss_recovery
++Date:		September 2020
++Contact:	Luka Kovacic <luka.kovacic at sartura.hr>
++Description:	(RW) Host platform power loss recovery settings
++		Value mapping: 0 - Always-On, 1 - Always-Off, 2 - Always-AC, 3 - Always-WA
++
++What:		/sys/bus/serial/devices/.../bootloader_mode
++Date:		September 2020
++Contact:	Luka Kovacic <luka.kovacic at sartura.hr>
++Description:	(RO) Internal MCU bootloader mode status
++		Value mapping:
++		0 - normal mode
++		1 - bootloader mode
++
++What:		/sys/bus/serial/devices/.../power_status
++Date:		September 2020
++Contact:	Luka Kovacic <luka.kovacic at sartura.hr>
++Description:	(RO) Power status indicates the host platform power on method.
++		Value mapping (bitwise list):
++		0x80 - Null
++		0x40 - Firmware flag
++		0x20 - Power loss detection flag (powered off)
++		0x10 - Power loss detection flag (AC mode)
++		0x08 - Button power on
++		0x04 - Wake-on-LAN power on
++		0x02 - RTC alarm power on
++		0x01 - AC recover power on
++
++What:		/sys/bus/serial/devices/.../build_info
++Date:		September 2020
++Contact:	Luka Kovacic <luka.kovacic at sartura.hr>
++Description:	(RO) Internal MCU firmware build date
++		Format: yyyy/mm/dd hh:mm
++
++What:		/sys/bus/serial/devices/.../ac_recovery_status
++Date:		September 2020
++Contact:	Luka Kovacic <luka.kovacic at sartura.hr>
++Description:	(RO) Host platform AC recovery status value
++		Value mapping:
++		0 - board has not been recovered from power down
++		1 - board has been recovered from power down
diff --git a/target/linux/mvebu/patches-5.4/906-Documentation-hwmon-Add-iei-wt61p803-puzzle-hwmon-dr.patch b/target/linux/mvebu/patches-5.4/906-Documentation-hwmon-Add-iei-wt61p803-puzzle-hwmon-dr.patch
new file mode 100644
index 0000000000..aaac99ab5d
--- /dev/null
+++ b/target/linux/mvebu/patches-5.4/906-Documentation-hwmon-Add-iei-wt61p803-puzzle-hwmon-dr.patch
@@ -0,0 +1,74 @@
+From 0aff3e5923fecc6842473ad07a688d6e2f2c2d55 Mon Sep 17 00:00:00 2001
+From: Luka Kovacic <luka.kovacic () sartura ! hr>
+Date: Tue, 24 Aug 2021 12:44:37 +0000
+Subject: [PATCH 6/7] Documentation/hwmon: Add iei-wt61p803-puzzle hwmon driver
+ documentation
+
+Add the iei-wt61p803-puzzle driver hwmon driver interface documentation.
+
+Signed-off-by: Luka Kovacic <luka.kovacic at sartura.hr>
+Signed-off-by: Pavo Banicevic <pavo.banicevic at sartura.hr>
+Cc: Luka Perkov <luka.perkov at sartura.hr>
+Cc: Robert Marko <robert.marko at sartura.hr>
+---
+ .../hwmon/iei-wt61p803-puzzle-hwmon.rst       | 43 +++++++++++++++++++
+ Documentation/hwmon/index.rst                 |  1 +
+ 2 files changed, 44 insertions(+)
+ create mode 100644 Documentation/hwmon/iei-wt61p803-puzzle-hwmon.rst
+
+--- /dev/null
++++ b/Documentation/hwmon/iei-wt61p803-puzzle-hwmon.rst
+@@ -0,0 +1,43 @@
++.. SPDX-License-Identifier: GPL-2.0-only
++
++Kernel driver iei-wt61p803-puzzle-hwmon
++=======================================
++
++Supported chips:
++ * IEI WT61P803 PUZZLE for IEI Puzzle M801
++
++   Prefix: 'iei-wt61p803-puzzle-hwmon'
++
++Author: Luka Kovacic <luka.kovacic at sartura.hr>
++
++
++Description
++-----------
++
++This driver adds fan and temperature sensor reading for some IEI Puzzle
++series boards.
++
++Sysfs attributes
++----------------
++
++The following attributes are supported:
++
++- IEI WT61P803 PUZZLE for IEI Puzzle M801
++
++/sys files in hwmon subsystem
++-----------------------------
++
++================= == =====================================================
++fan[1-5]_input    RO files for fan speed (in RPM)
++pwm[1-2]          RW files for fan[1-2] target duty cycle (0..255)
++temp[1-2]_input   RO files for temperature sensors, in millidegree Celsius
++================= == =====================================================
++
++/sys files in thermal subsystem
++-------------------------------
++
++================= == =====================================================
++cur_state         RW file for current cooling state of the cooling device
++                     (0..max_state)
++max_state         RO file for maximum cooling state of the cooling device
++================= == =====================================================
+--- a/Documentation/hwmon/index.rst
++++ b/Documentation/hwmon/index.rst
+@@ -62,6 +62,7 @@ Hardware Monitoring Kernel Drivers
+    ibmaem
+    ibm-cffps
+    ibmpowernv
++   iei-wt61p803-puzzle-hwmon
+    ina209
+    ina2xx
+    ina3221
diff --git a/target/linux/mvebu/patches-5.4/907-MAINTAINERS-Add-an-entry-for-the-IEI-WT61P803-PUZZLE.patch b/target/linux/mvebu/patches-5.4/907-MAINTAINERS-Add-an-entry-for-the-IEI-WT61P803-PUZZLE.patch
new file mode 100644
index 0000000000..830cf33ce4
--- /dev/null
+++ b/target/linux/mvebu/patches-5.4/907-MAINTAINERS-Add-an-entry-for-the-IEI-WT61P803-PUZZLE.patch
@@ -0,0 +1,41 @@
+From 12479baad28d2a08c6cb9e83471057635fa1635c Mon Sep 17 00:00:00 2001
+From: Luka Kovacic <luka.kovacic () sartura ! hr>
+Date: Tue, 24 Aug 2021 12:44:38 +0000
+Subject: [PATCH 7/7] MAINTAINERS: Add an entry for the IEI WT61P803 PUZZLE
+ driver
+
+Add an entry for the IEI WT61P803 PUZZLE driver (MFD, HWMON, LED drivers).
+
+Signed-off-by: Luka Kovacic <luka.kovacic at sartura.hr>
+Signed-off-by: Pavo Banicevic <pavo.banicevic at sartura.hr>
+Cc: Luka Perkov <luka.perkov at sartura.hr>
+Cc: Robert Marko <robert.marko at sartura.hr>
+---
+ MAINTAINERS | 16 ++++++++++++++++
+ 1 file changed, 16 insertions(+)
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -7929,6 +7929,22 @@ F:	include/net/cfg802154.h
+ F:	include/net/ieee802154_netdev.h
+ F:	Documentation/networking/ieee802154.rst
+ 
++IEI WT61P803 M801 MFD DRIVER
++M:	Luka Kovacic <luka.kovacic at sartura.hr>
++M:	Luka Perkov <luka.perkov at sartura.hr>
++M:	Goran Medic <goran.medic at sartura.hr>
++L:	linux-kernel at vger.kernel.org
++S:	Maintained
++F:	Documentation/ABI/stable/sysfs-driver-iei-wt61p803-puzzle
++F:	Documentation/devicetree/bindings/hwmon/iei,wt61p803-puzzle-hwmon.yaml
++F:	Documentation/devicetree/bindings/leds/iei,wt61p803-puzzle-leds.yaml
++F:	Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml
++F:	Documentation/hwmon/iei-wt61p803-puzzle-hwmon.rst
++F:	drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
++F:	drivers/leds/leds-iei-wt61p803-puzzle.c
++F:	drivers/mfd/iei-wt61p803-puzzle.c
++F:	include/linux/mfd/iei-wt61p803-puzzle.h
++
+ IFE PROTOCOL
+ M:	Yotam Gigi <yotam.gi at gmail.com>
+ M:	Jamal Hadi Salim <jhs at mojatatu.com>
diff --git a/target/linux/mvebu/patches-5.4/910-drivers-leds-wt61p803-puzzle-improvements.patch b/target/linux/mvebu/patches-5.4/910-drivers-leds-wt61p803-puzzle-improvements.patch
new file mode 100644
index 0000000000..1e065ed6e7
--- /dev/null
+++ b/target/linux/mvebu/patches-5.4/910-drivers-leds-wt61p803-puzzle-improvements.patch
@@ -0,0 +1,247 @@
+--- a/drivers/leds/leds-iei-wt61p803-puzzle.c
++++ b/drivers/leds/leds-iei-wt61p803-puzzle.c
+@@ -9,10 +9,13 @@
+ #include <linux/mfd/iei-wt61p803-puzzle.h>
+ #include <linux/mod_devicetable.h>
+ #include <linux/module.h>
++#include <linux/of.h>
+ #include <linux/platform_device.h>
+ #include <linux/property.h>
+ #include <linux/slab.h>
+ 
++#define IEI_LEDS_MAX		4
++
+ enum iei_wt61p803_puzzle_led_state {
+ 	IEI_LED_OFF = 0x30,
+ 	IEI_LED_ON = 0x31,
+@@ -34,6 +37,9 @@ struct iei_wt61p803_puzzle_led {
+ 	unsigned char response_buffer[IEI_WT61P803_PUZZLE_BUF_SIZE];
+ 	struct mutex lock; /* mutex to protect led_power_state */
+ 	int led_power_state;
++	int id;
++	bool blinking;
++	bool active_low;
+ };
+ 
+ static inline struct iei_wt61p803_puzzle_led *cdev_to_iei_wt61p803_puzzle_led
+@@ -51,10 +57,20 @@ static int iei_wt61p803_puzzle_led_brigh
+ 	size_t reply_size;
+ 	int ret;
+ 
++	mutex_lock(&priv->lock);
++	if (priv->blinking) {
++		if (brightness == LED_OFF)
++			priv->blinking = false;
++		else
++			return 0;
++	}
++	mutex_unlock(&priv->lock);
++
+ 	led_power_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START;
+ 	led_power_cmd[1] = IEI_WT61P803_PUZZLE_CMD_LED;
+-	led_power_cmd[2] = IEI_WT61P803_PUZZLE_CMD_LED_POWER;
+-	led_power_cmd[3] = brightness == LED_OFF ? IEI_LED_OFF : IEI_LED_ON;
++	led_power_cmd[2] = IEI_WT61P803_PUZZLE_CMD_LED_SET(priv->id);
++	led_power_cmd[3] = ((brightness == LED_OFF) ^ priv->active_low) ?
++				IEI_LED_OFF : IEI_LED_ON;
+ 
+ 	ret = iei_wt61p803_puzzle_write_command(priv->mcu, led_power_cmd,
+ 						sizeof(led_power_cmd),
+@@ -90,39 +106,164 @@ static enum led_brightness iei_wt61p803_
+ 	return led_state;
+ }
+ 
++static int iei_wt61p803_puzzle_led_set_blink(struct led_classdev *cdev,
++					     unsigned long *delay_on,
++					     unsigned long *delay_off)
++{
++	struct iei_wt61p803_puzzle_led *priv = cdev_to_iei_wt61p803_puzzle_led(cdev);
++	unsigned char led_blink_cmd[5] = {};
++	unsigned char resp_buf[IEI_WT61P803_PUZZLE_BUF_SIZE];
++	size_t reply_size;
++	int ret = 0;
++
++	/* set defaults */
++	if (!*delay_on && !*delay_off) {
++		*delay_on = 500;
++		*delay_off = 500;
++	}
++
++	/* minimum delay for soft-driven blinking is 50ms to keep load low */
++	if (*delay_on < 50)
++		*delay_on = 50;
++
++	if (*delay_off < 50)
++		*delay_off = 50;
++
++	if (*delay_on != *delay_off)
++		return -EINVAL;
++
++	/* aggressively offload blinking to hardware, if possible */
++	if (*delay_on < 100) {
++		return -EINVAL;
++	} else if (*delay_on < 200) {
++		*delay_on = 100;
++		*delay_off = 100;
++	} else if (*delay_on <= 500) {
++		*delay_on = 500;
++		*delay_off = 500;
++	} else {
++		return -EINVAL;
++	}
++
++	led_blink_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START;
++	led_blink_cmd[1] = IEI_WT61P803_PUZZLE_CMD_LED;
++	led_blink_cmd[2] = IEI_WT61P803_PUZZLE_CMD_LED_SET(priv->id);
++	led_blink_cmd[3] = (*delay_on == 100)?IEI_LED_BLINK_5HZ:IEI_LED_BLINK_1HZ;
++
++	ret = iei_wt61p803_puzzle_write_command(priv->mcu, led_blink_cmd,
++						sizeof(led_blink_cmd),
++						resp_buf,
++						&reply_size);
++
++	if (ret)
++		return ret;
++
++	if (reply_size != 3)
++		return -EIO;
++
++	if (!(resp_buf[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START &&
++	      resp_buf[1] == IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK &&
++	      resp_buf[2] == IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK))
++		return -EIO;
++
++	mutex_lock(&priv->lock);
++	priv->blinking = true;
++	mutex_unlock(&priv->lock);
++
++	return ret;
++}
++
++static int iei_wt61p803_puzzle_led_set_dt_default(struct led_classdev *cdev,
++				     struct device_node *np)
++{
++	const char *state;
++	int ret = 0;
++
++	state = of_get_property(np, "default-state", NULL);
++	if (state) {
++		if (!strcmp(state, "on")) {
++			ret =
++			iei_wt61p803_puzzle_led_brightness_set_blocking(
++				cdev, cdev->max_brightness);
++		} else  {
++			ret = iei_wt61p803_puzzle_led_brightness_set_blocking(
++				cdev, LED_OFF);
++		}
++	}
++
++	return ret;
++}
++
+ static int iei_wt61p803_puzzle_led_probe(struct platform_device *pdev)
+ {
+ 	struct device *dev = &pdev->dev;
++	struct device_node *np = dev_of_node(dev);
++	struct device_node *child;
+ 	struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev->parent);
+ 	struct iei_wt61p803_puzzle_led *priv;
+-	struct led_init_data init_data = {};
+-	struct fwnode_handle *child;
+ 	int ret;
++	u32 reg;
+ 
+-	if (device_get_child_node_count(dev) != 1)
++	if (device_get_child_node_count(dev) > IEI_LEDS_MAX)
+ 		return -EINVAL;
+ 
+-	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+-	if (!priv)
+-		return -ENOMEM;
+-
+-	priv->mcu = mcu;
+-	priv->led_power_state = 1;
+-	mutex_init(&priv->lock);
+-	dev_set_drvdata(dev, priv);
+-
+-	child = device_get_next_child_node(dev, NULL);
+-	init_data.fwnode = child;
+-
+-	priv->cdev.brightness_set_blocking = iei_wt61p803_puzzle_led_brightness_set_blocking;
+-	priv->cdev.brightness_get = iei_wt61p803_puzzle_led_brightness_get;
+-	priv->cdev.max_brightness = 1;
++	for_each_available_child_of_node(np, child) {
++		struct led_init_data init_data = {};
+ 
+-	ret = devm_led_classdev_register_ext(dev, &priv->cdev, &init_data);
+-	if (ret)
+-		dev_err(dev, "Could not register LED\n");
++		ret = of_property_read_u32(child, "reg", &reg);
++		if (ret) {
++			dev_err(dev, "Failed to read led 'reg' property\n");
++			goto put_child_node;
++		}
++
++		if (reg > IEI_LEDS_MAX) {
++			dev_err(dev, "Invalid led reg %u\n", reg);
++			ret = -EINVAL;
++			goto put_child_node;
++		}
++
++		priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++		if (!priv) {
++			ret = -ENOMEM;
++			goto put_child_node;
++		}
++
++		mutex_init(&priv->lock);
++
++		dev_set_drvdata(dev, priv);
++
++		if (of_property_read_bool(child, "active-low"))
++			priv->active_low = true;
++
++		priv->mcu = mcu;
++		priv->id = reg;
++		priv->led_power_state = 1;
++		priv->blinking = false;
++		init_data.fwnode = of_fwnode_handle(child);
++
++		priv->cdev.brightness_set_blocking = iei_wt61p803_puzzle_led_brightness_set_blocking;
++		priv->cdev.brightness_get = iei_wt61p803_puzzle_led_brightness_get;
++		priv->cdev.blink_set = iei_wt61p803_puzzle_led_set_blink;
++
++		priv->cdev.max_brightness = 1;
++
++		ret = iei_wt61p803_puzzle_led_set_dt_default(&priv->cdev, child);
++		if (ret) {
++			dev_err(dev, "Could apply default from DT\n");
++			goto put_child_node;
++		}
++
++		ret = devm_led_classdev_register_ext(dev, &priv->cdev, &init_data);
++		if (ret) {
++			dev_err(dev, "Could not register LED\n");
++			goto put_child_node;
++		}
++	}
++
++	return ret;
+ 
+-	fwnode_handle_put(child);
++put_child_node:
++	of_node_put(child);
+ 	return ret;
+ }
+ 
+--- a/include/linux/mfd/iei-wt61p803-puzzle.h
++++ b/include/linux/mfd/iei-wt61p803-puzzle.h
+@@ -36,7 +36,7 @@
+ #define IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_POWER_LOSS 0x41 /* A */
+ 
+ #define IEI_WT61P803_PUZZLE_CMD_LED			0x52 /* R */
+-#define IEI_WT61P803_PUZZLE_CMD_LED_POWER		0x31 /* 1 */
++#define IEI_WT61P803_PUZZLE_CMD_LED_SET(n)		(0x30 | (n))
+ 
+ #define IEI_WT61P803_PUZZLE_CMD_TEMP			0x54 /* T */
+ #define IEI_WT61P803_PUZZLE_CMD_TEMP_ALL		0x41 /* A */



More information about the lede-commits mailing list