mvebu: puzzle-m902: add driver for MCU driving LEDs, fan and buzzer
authorDaniel Golle <daniel@makrotopia.org>
Tue, 21 Dec 2021 16:27:16 +0000 (16:27 +0000)
committerDaniel Golle <daniel@makrotopia.org>
Tue, 21 Dec 2021 16:41:10 +0000 (16:41 +0000)
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@makrotopia.org>
target/linux/mvebu/cortexa72/config-5.10
target/linux/mvebu/files/arch/arm64/boot/dts/marvell/cn9132-puzzle-m902.dts
target/linux/mvebu/files/include/linux/mfd/puzzle.h [new file with mode: 0644]
target/linux/mvebu/patches-5.10/901-dt-bindings-Add-IEI-vendor-prefix-and-IEI-WT61P803-P.patch [new file with mode: 0644]
target/linux/mvebu/patches-5.10/902-drivers-mfd-Add-a-driver-for-IEI-WT61P803-PUZZLE-MCU.patch [new file with mode: 0644]
target/linux/mvebu/patches-5.10/903-drivers-hwmon-Add-the-IEI-WT61P803-PUZZLE-HWMON-driv.patch [new file with mode: 0644]
target/linux/mvebu/patches-5.10/904-drivers-leds-Add-the-IEI-WT61P803-PUZZLE-LED-driver.patch [new file with mode: 0644]
target/linux/mvebu/patches-5.10/905-Documentation-ABI-Add-iei-wt61p803-puzzle-driver-sys.patch [new file with mode: 0644]
target/linux/mvebu/patches-5.10/906-Documentation-hwmon-Add-iei-wt61p803-puzzle-hwmon-dr.patch [new file with mode: 0644]
target/linux/mvebu/patches-5.10/907-MAINTAINERS-Add-an-entry-for-the-IEI-WT61P803-PUZZLE.patch [new file with mode: 0644]
target/linux/mvebu/patches-5.10/910-drivers-leds-wt61p803-puzzle-improvements.patch [new file with mode: 0644]

index 421b732dede7af07edc555d6e1c33a0fb0a3103b..9b431b75ce8cd88f7ce1fe7b623adf01fe7d0a5a 100644 (file)
@@ -42,8 +42,10 @@ CONFIG_HOLES_IN_ZONE=y
 CONFIG_HW_RANDOM_OMAP=y
 CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000
 CONFIG_LEDS_IS31FL319X=y
+CONFIG_LEDS_IEI_WT61P803_PUZZLE=y
 CONFIG_MARVELL_10G_PHY=y
 CONFIG_MDIO_DEVRES=y
+CONFIG_MFD_IEI_WT61P803_PUZZLE=y
 CONFIG_MFD_SYSCON=y
 CONFIG_MMC_SDHCI_XENON=y
 CONFIG_MODULES_USE_ELF_RELA=y
@@ -73,8 +75,12 @@ CONFIG_POWER_SUPPLY=y
 CONFIG_QUEUED_RWLOCKS=y
 CONFIG_QUEUED_SPINLOCKS=y
 CONFIG_RAS=y
+# CONFIG_RAVE_SP_CORE is not set
 CONFIG_REGULATOR_GPIO=y
 # CONFIG_RODATA_FULL_DEFAULT_ENABLED is not set
+CONFIG_SENSORS_IEI_WT61P803_PUZZLE_HWMON=y
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_SERIAL_DEV_CTRL_TTYPORT=y
 CONFIG_SPARSEMEM=y
 CONFIG_SPARSEMEM_EXTREME=y
 CONFIG_SPARSEMEM_MANUAL=y
index dc4e6527f170ba891c1e16e0c89fd0b6d85e191f..8d2d53d03cbf46914770e0a4acbec88d09e72a5f 100644 (file)
                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@00000000 {
 
 &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@0 {
+                               reg = <0>;
+                               label = "white:network";
+                               active-low;
+                       };
+
+                       led@1 {
+                               reg = <1>;
+                               label = "green:cloud";
+                               active-low;
+                       };
+
+                       led_info: led@2 {
+                               reg = <2>;
+                               label = "orange:info";
+                               active-low;
+                       };
+
+                       led_power: led@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@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/files/include/linux/mfd/puzzle.h b/target/linux/mvebu/files/include/linux/mfd/puzzle.h
new file mode 100644 (file)
index 0000000..b0389f8
--- /dev/null
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+
+#ifndef _LINUX_PUZZLE_H_
+#define _LINUX_PUZZLE_H_
+
+struct puzzle;
+int puzzle_led(struct puzzle *pz, u8 ledn, u8 ledmode);
+int puzzle_fan(struct puzzle *pz, u8 speed);
+int puzzle_buzzer(struct puzzle *pz, u8 len);
+
+#endif /* _LINUX_PUZZLE_H_ */
diff --git a/target/linux/mvebu/patches-5.10/901-dt-bindings-Add-IEI-vendor-prefix-and-IEI-WT61P803-P.patch b/target/linux/mvebu/patches-5.10/901-dt-bindings-Add-IEI-vendor-prefix-and-IEI-WT61P803-P.patch
new file mode 100644 (file)
index 0000000..28b2b19
--- /dev/null
@@ -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@sartura.hr>
+Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr>
+Cc: Luka Perkov <luka.perkov@sartura.hr>
+Cc: Robert Marko <robert.marko@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@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@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@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@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@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@0 {
++                    #cooling-cells = <2>;
++                    reg = <0x00>;
++                    cooling-levels = <64 102 170 230 250>;
++                };
++
++                fan-group@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
+@@ -475,6 +475,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.10/902-drivers-mfd-Add-a-driver-for-IEI-WT61P803-PUZZLE-MCU.patch b/target/linux/mvebu/patches-5.10/902-drivers-mfd-Add-a-driver-for-IEI-WT61P803-PUZZLE-MCU.patch
new file mode 100644 (file)
index 0000000..01c2ecc
--- /dev/null
@@ -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@sartura.hr>
+Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr>
+Cc: Luka Perkov <luka.perkov@sartura.hr>
+Cc: Robert Marko <robert.marko@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
+@@ -2155,6 +2155,15 @@ config SGI_MFD_IOC3
+         If you have an SGI Origin, Octane, or a PCI IOC3 card,
+         then say Y. Otherwise say N.
++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 MFD_INTEL_M10_BMC
+       tristate "Intel MAX 10 Board Management Controller"
+       depends on SPI_MASTER
+--- a/drivers/mfd/Makefile
++++ b/drivers/mfd/Makefile
+@@ -237,6 +237,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@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@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@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.10/903-drivers-hwmon-Add-the-IEI-WT61P803-PUZZLE-HWMON-driv.patch b/target/linux/mvebu/patches-5.10/903-drivers-hwmon-Add-the-IEI-WT61P803-PUZZLE-HWMON-driv.patch
new file mode 100644 (file)
index 0000000..684f09c
--- /dev/null
@@ -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@sartura.hr>
+Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr>
+Acked-by: Guenter Roeck <linux@roeck-us.net>
+Cc: Luka Perkov <luka.perkov@sartura.hr>
+Cc: Robert Marko <robert.marko@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
+@@ -722,6 +722,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
+@@ -83,6 +83,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@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@sartura.hr>");
++MODULE_LICENSE("GPL v2");
diff --git a/target/linux/mvebu/patches-5.10/904-drivers-leds-Add-the-IEI-WT61P803-PUZZLE-LED-driver.patch b/target/linux/mvebu/patches-5.10/904-drivers-leds-Add-the-IEI-WT61P803-PUZZLE-LED-driver.patch
new file mode 100644 (file)
index 0000000..37f98b6
--- /dev/null
@@ -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@sartura.hr>
+Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr>
+Cc: Luka Perkov <luka.perkov@sartura.hr>
+Cc: Robert Marko <robert.marko@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
+@@ -333,6 +333,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
+@@ -35,6 +35,7 @@ obj-$(CONFIG_LEDS_HP6XX)             += leds-hp6xx.
+ obj-$(CONFIG_LEDS_INTEL_SS4200)               += leds-ss4200.o
+ obj-$(CONFIG_LEDS_IP30)                       += leds-ip30.o
+ obj-$(CONFIG_LEDS_IPAQ_MICRO)         += leds-ipaq-micro.o
++obj-$(CONFIG_LEDS_IEI_WT61P803_PUZZLE)        += leds-iei-wt61p803-puzzle.o
+ obj-$(CONFIG_LEDS_IS31FL319X)         += leds-is31fl319x.o
+ obj-$(CONFIG_LEDS_IS31FL32XX)         += leds-is31fl32xx.o
+ obj-$(CONFIG_LEDS_KTD2692)            += leds-ktd2692.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@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@sartura.hr>");
++MODULE_LICENSE("GPL v2");
++MODULE_ALIAS("platform:leds-iei-wt61p803-puzzle");
diff --git a/target/linux/mvebu/patches-5.10/905-Documentation-ABI-Add-iei-wt61p803-puzzle-driver-sys.patch b/target/linux/mvebu/patches-5.10/905-Documentation-ABI-Add-iei-wt61p803-puzzle-driver-sys.patch
new file mode 100644 (file)
index 0000000..b1d420e
--- /dev/null
@@ -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@sartura.hr>
+Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr>
+Cc: Luka Perkov <luka.perkov@sartura.hr>
+Cc: Robert Marko <robert.marko@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@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@sartura.hr>
++Description:  (RW) Internal factory assigned serial number
++
++What:         /sys/bus/serial/devices/.../version
++Date:         September 2020
++Contact:      Luka Kovacic <luka.kovacic@sartura.hr>
++Description:  (RO) Internal MCU firmware version
++
++What:         /sys/bus/serial/devices/.../protocol_version
++Date:         September 2020
++Contact:      Luka Kovacic <luka.kovacic@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@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@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@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@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@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.10/906-Documentation-hwmon-Add-iei-wt61p803-puzzle-hwmon-dr.patch b/target/linux/mvebu/patches-5.10/906-Documentation-hwmon-Add-iei-wt61p803-puzzle-hwmon-dr.patch
new file mode 100644 (file)
index 0000000..345d4ca
--- /dev/null
@@ -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@sartura.hr>
+Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr>
+Cc: Luka Perkov <luka.perkov@sartura.hr>
+Cc: Robert Marko <robert.marko@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@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
+@@ -71,6 +71,7 @@ Hardware Monitoring Kernel Drivers
+    ibmaem
+    ibm-cffps
+    ibmpowernv
++   iei-wt61p803-puzzle-hwmon
+    ina209
+    ina2xx
+    ina3221
diff --git a/target/linux/mvebu/patches-5.10/907-MAINTAINERS-Add-an-entry-for-the-IEI-WT61P803-PUZZLE.patch b/target/linux/mvebu/patches-5.10/907-MAINTAINERS-Add-an-entry-for-the-IEI-WT61P803-PUZZLE.patch
new file mode 100644 (file)
index 0000000..577a9b4
--- /dev/null
@@ -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@sartura.hr>
+Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr>
+Cc: Luka Perkov <luka.perkov@sartura.hr>
+Cc: Robert Marko <robert.marko@sartura.hr>
+---
+ MAINTAINERS | 16 ++++++++++++++++
+ 1 file changed, 16 insertions(+)
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -8538,6 +8538,22 @@ F:      include/net/nl802154.h
+ F:    net/ieee802154/
+ F:    net/mac802154/
++IEI WT61P803 M801 MFD DRIVER
++M:    Luka Kovacic <luka.kovacic@sartura.hr>
++M:    Luka Perkov <luka.perkov@sartura.hr>
++M:    Goran Medic <goran.medic@sartura.hr>
++L:    linux-kernel@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@gmail.com>
+ M:    Jamal Hadi Salim <jhs@mojatatu.com>
diff --git a/target/linux/mvebu/patches-5.10/910-drivers-leds-wt61p803-puzzle-improvements.patch b/target/linux/mvebu/patches-5.10/910-drivers-leds-wt61p803-puzzle-improvements.patch
new file mode 100644 (file)
index 0000000..1e065ed
--- /dev/null
@@ -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 */