]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
mvebu: move wt61p803 drivers to files
authorRosen Penev <rosenp@gmail.com>
Sun, 5 Jul 2026 22:24:18 +0000 (15:24 -0700)
committerJonas Jelonek <jelonek.jonas@gmail.com>
Tue, 7 Jul 2026 20:58:56 +0000 (22:58 +0200)
It seems that this was last attempted to be upstreamed in 2021. No
progress since then. Move to files as with many other local drivers.

Signed-off-by: Rosen Penev <rosenp@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/24094
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
target/linux/mvebu/files/drivers/hwmon/iei-wt61p803-puzzle-hwmon.c [new file with mode: 0644]
target/linux/mvebu/files/drivers/leds/leds-iei-wt61p803-puzzle.c [new file with mode: 0644]
target/linux/mvebu/files/drivers/mfd/iei-wt61p803-puzzle.c [new file with mode: 0644]
target/linux/mvebu/files/include/linux/mfd/iei-wt61p803-puzzle.h [new file with mode: 0644]
target/linux/mvebu/patches-6.18/902-drivers-mfd-Add-a-driver-for-IEI-WT61P803-PUZZLE-MCU.patch
target/linux/mvebu/patches-6.18/903-drivers-hwmon-Add-the-IEI-WT61P803-PUZZLE-HWMON-driv.patch
target/linux/mvebu/patches-6.18/904-drivers-leds-Add-the-IEI-WT61P803-PUZZLE-LED-driver.patch
target/linux/mvebu/patches-6.18/910-drivers-leds-wt61p803-puzzle-improvements.patch [deleted file]
target/linux/mvebu/patches-6.18/911-drivers-leds-wt61p803-puzzle-mcu-retry.patch [deleted file]
target/linux/mvebu/patches-6.18/912-drivers-hwmon-wt61p803-puzzle-thermal-zone.patch [deleted file]

diff --git a/target/linux/mvebu/files/drivers/hwmon/iei-wt61p803-puzzle-hwmon.c b/target/linux/mvebu/files/drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
new file mode 100644 (file)
index 0000000..788ef31
--- /dev/null
@@ -0,0 +1,443 @@
+// 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)
+ * @cur_level:         Current cooling level
+ * @num_levels:        Number of cooling levels
+ * @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;
+       int cur_level;
+       u8 num_levels;
+       u32 cooling_levels[] __counted_by(num_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(chip, HWMON_C_REGISTER_TZ),
+       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)
+{
+       struct iei_wt61p803_puzzle_thermal_cooling_device *cdev = tcdev->devdata;
+
+       if (!cdev)
+               return -EINVAL;
+
+       *state = cdev->num_levels - 1;
+       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;
+
+       if (!cdev)
+               return -EINVAL;
+
+       if (cdev->cur_level < 0)
+               return -EAGAIN;
+
+       *state = cdev->cur_level;
+       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;
+       u8 pwm_level;
+
+       if (!cdev)
+               return -EINVAL;
+
+       if (state >= cdev->num_levels)
+               return -EINVAL;
+
+       if (state == cdev->cur_level)
+               return 0;
+
+       cdev->cur_level = state;
+       pwm_level = cdev->cooling_levels[state];
+
+       return iei_wt61p803_puzzle_write_pwm_channel(cdev->mcu_hwmon, cdev->pwm_channel, pwm_level);
+}
+
+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 i, 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_u32(child, "cooling-levels");
+       if (!num_levels)
+               return -EINVAL;
+
+       cdev = devm_kzalloc(dev, struct_size(cdev, cooling_levels, num_levels), GFP_KERNEL);
+       if (!cdev)
+               return -ENOMEM;
+
+       cdev->num_levels = num_levels;
+
+       ret = fwnode_property_read_u32_array(child, "cooling-levels",
+                                            cdev->cooling_levels,
+                                            num_levels);
+       if (ret) {
+               dev_err(dev, "Couldn't read property 'cooling-levels'\n");
+               return ret;
+       }
+
+       for (i = 0; i < num_levels; i++) {
+               if (cdev->cooling_levels[i] >
+                   IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_VAL) {
+                       dev_err(dev, "iei_wt61p803_fan state[%d]:%d > %d\n", i,
+                               cdev->cooling_levels[i],
+                               IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_VAL);
+                       return -EINVAL;
+               }
+       }
+
+       cdev->mcu_hwmon = mcu_hwmon;
+       cdev->pwm_channel = pwm_channel;
+       cdev->cur_level = -1;
+       mcu_hwmon->cdev[pwm_channel] = cdev;
+
+       snprintf(cdev->name, THERMAL_NAME_LENGTH, "wt61p803_puzzle_%d", pwm_channel);
+       cdev->tcdev = devm_thermal_of_cooling_device_register(dev, to_of_node(child), cdev->name,
+                                                             cdev, &iei_wt61p803_puzzle_cooling_ops);
+       if (IS_ERR(cdev->tcdev))
+               return PTR_ERR(cdev->tcdev);
+
+       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 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);
+       ret = devm_mutex_init(dev, &mcu_hwmon->lock);
+       if (ret)
+               return ret;
+
+       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_scoped(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");
+                               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/files/drivers/leds/leds-iei-wt61p803-puzzle.c b/target/linux/mvebu/files/drivers/leds/leds-iei-wt61p803-puzzle.c
new file mode 100644 (file)
index 0000000..a38db9a
--- /dev/null
@@ -0,0 +1,290 @@
+// 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/of.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#define IEI_LEDS_MAX           4
+
+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 */
+       struct work_struct work;
+       int led_power_state;
+       int id;
+       u8 blinking;
+       bool active_low;
+};
+
+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;
+
+       if (priv->blinking) {
+               if (brightness == LED_OFF)
+                       priv->blinking = 0;
+               else
+                       return 0;
+       }
+
+       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_SET(priv->id);
+       led_power_cmd[3] = ((brightness == LED_OFF) ^ priv->active_low) ?
+                               IEI_LED_OFF : priv->blinking?priv->blinking: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 void iei_wt61p803_puzzle_led_apply_blink(struct work_struct *work)
+{
+       struct iei_wt61p803_puzzle_led *priv = container_of(work, struct iei_wt61p803_puzzle_led, work);
+       unsigned char led_blink_cmd[5] = {};
+       unsigned char resp_buf[IEI_WT61P803_PUZZLE_BUF_SIZE];
+       size_t reply_size;
+
+       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] = priv->blinking;
+
+       iei_wt61p803_puzzle_write_command(priv->mcu, led_blink_cmd,
+                                         sizeof(led_blink_cmd),
+                                         resp_buf,
+                                         &reply_size);
+
+       return;
+}
+
+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);
+       u8 blink_mode = 0;
+       int ret = 0;
+
+       /* set defaults */
+       if (!*delay_on && !*delay_off) {
+               *delay_on = 500;
+               *delay_off = 500;
+       }
+
+       /* minimum delay for soft-driven blinking is 100ms to keep load low */
+       if (*delay_on < 100)
+               *delay_on = 100;
+
+       if (*delay_off < 100)
+               *delay_off = 100;
+
+       /* offload blinking to hardware, if possible */
+       if (*delay_on != *delay_off) {
+               ret = -EINVAL;
+       } else if (*delay_on == 100) {
+               blink_mode = IEI_LED_BLINK_5HZ;
+               *delay_on = 100;
+               *delay_off = 100;
+       } else if (*delay_on <= 500) {
+               blink_mode = IEI_LED_BLINK_1HZ;
+               *delay_on = 500;
+               *delay_off = 500;
+       } else {
+               ret = -EINVAL;
+       }
+
+       mutex_lock(&priv->lock);
+       priv->blinking = blink_mode;
+       mutex_unlock(&priv->lock);
+
+       if (blink_mode)
+               schedule_work(&priv->work);
+
+       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 iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev->parent);
+       struct iei_wt61p803_puzzle_led *priv;
+       int ret;
+       u32 reg;
+
+       if (device_get_child_node_count(dev) > IEI_LEDS_MAX)
+               return -EINVAL;
+
+       for_each_available_child_of_node_scoped(np, child) {
+               struct led_init_data init_data = {};
+
+               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;
+               }
+
+               ret = devm_mutex_init(dev, &priv->lock);
+               if (ret)
+                       goto put_child_node;
+
+               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 = 0;
+               init_data.fwnode = of_fwnode_handle(of_node_get(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;
+
+               INIT_WORK(&priv->work, iei_wt61p803_puzzle_led_apply_blink);
+
+               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;
+
+put_child_node:
+       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/files/drivers/mfd/iei-wt61p803-puzzle.c b/target/linux/mvebu/files/drivers/mfd/iei-wt61p803-puzzle.c
new file mode 100644 (file)
index 0000000..960a254
--- /dev/null
@@ -0,0 +1,928 @@
+// 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 <linux/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 size_t iei_wt61p803_puzzle_process_resp(struct iei_wt61p803_puzzle *mcu,
+                                           const u8 *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 size_t iei_wt61p803_puzzle_recv_buf(struct serdev_device *serdev,
+                                       const u8 *data, size_t size)
+{
+       struct iei_wt61p803_puzzle *mcu = serdev_device_get_drvdata(serdev);
+       size_t ret;
+
+       print_hex_dump_debug("puzzle-mcu rx: ", DUMP_PREFIX_NONE,
+                            16, 1, data, size, false);
+
+       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;
+       int retries;
+
+       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);
+
+       print_hex_dump_debug("puzzle-mcu tx: ", DUMP_PREFIX_NONE,
+                            16, 1, cmd, size, false);
+
+       retries = 3;
+       /* Initialize reply struct */
+       while (retries) {
+               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);
+               retries--;
+               if (ret == 0) {
+                       if (retries == 0) {
+                               dev_err(dev, "Command reply receive timeout\n");
+                               ret = -ETIMEDOUT;
+                               goto exit;
+                       }
+               }
+               else {
+                       if (mcu->reply->data[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START &&
+                               mcu->reply->data[1] == IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK &&
+                               mcu->reply->data[2] == IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK) {
+                               break;
+                       }
+               }
+       }
+       *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;
+       }
+
+       memcpy(mcu->version.serial_number, resp_buf + 4, IEI_WT61P803_PUZZLE_SN_LENGTH);
+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;
+               }
+       }
+
+       memcpy(mcu->version.serial_number, serial_number, IEI_WT61P803_PUZZLE_SN_LENGTH);
+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;
+       }
+
+       memcpy(mcu->version.mac_address[index], resp_buf + 4, IEI_WT61P803_PUZZLE_MAC_LENGTH);
+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;
+       }
+
+       memcpy(mcu->version.mac_address[mac_address_idx], mac_address,
+               IEI_WT61P803_PUZZLE_MAC_LENGTH);
+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);
+       ret = devm_mutex_init(dev, &mcu->reply_lock);
+       if (ret)
+               return ret;
+       ret = devm_mutex_init(dev, &mcu->lock);
+       if (ret)
+               return ret;
+
+       /* 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");
diff --git a/target/linux/mvebu/files/include/linux/mfd/iei-wt61p803-puzzle.h b/target/linux/mvebu/files/include/linux/mfd/iei-wt61p803-puzzle.h
new file mode 100644 (file)
index 0000000..cbdfc27
--- /dev/null
@@ -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_SET(n)             (0x30 | (n))
+
+#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_ */
index 7d35626637c91bce1b5461eb6a49b89a896f3ebf..bed5fa51e0986b3694967f13dfd876f78654b433 100644 (file)
@@ -52,984 +52,3 @@ Cc: Robert Marko <robert.marko@sartura.hr>
  
  obj-$(CONFIG_INTEL_SOC_PMIC)          += intel_soc_pmic_crc.o
  obj-$(CONFIG_INTEL_SOC_PMIC_BXTWC)    += intel_soc_pmic_bxtwc.o
---- /dev/null
-+++ b/drivers/mfd/iei-wt61p803-puzzle.c
-@@ -0,0 +1,909 @@
-+// 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 <linux/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 size_t iei_wt61p803_puzzle_process_resp(struct iei_wt61p803_puzzle *mcu,
-+                                          const u8 *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 size_t iei_wt61p803_puzzle_recv_buf(struct serdev_device *serdev,
-+                                      const u8 *data, size_t size)
-+{
-+      struct iei_wt61p803_puzzle *mcu = serdev_device_get_drvdata(serdev);
-+      size_t 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;
-+      }
-+
-+      memcpy(mcu->version.serial_number, resp_buf + 4, IEI_WT61P803_PUZZLE_SN_LENGTH);
-+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;
-+              }
-+      }
-+
-+      memcpy(mcu->version.serial_number, serial_number, IEI_WT61P803_PUZZLE_SN_LENGTH);
-+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;
-+      }
-+
-+      memcpy(mcu->version.mac_address[index], resp_buf + 4, IEI_WT61P803_PUZZLE_MAC_LENGTH);
-+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;
-+      }
-+
-+      memcpy(mcu->version.mac_address[mac_address_idx], mac_address,
-+              IEI_WT61P803_PUZZLE_MAC_LENGTH);
-+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);
-+      ret = devm_mutex_init(dev, &mcu->reply_lock);
-+      if (ret)
-+              return ret;
-+      ret = devm_mutex_init(dev, &mcu->lock);
-+      if (ret)
-+              return ret;
-+
-+      /* 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_ */
index 9e69779c0d310fa2b9187b4df46c986ca2274446..87fbd8ed74cd1ef8f97ca2de8f7cb7ef37a7cafe 100644 (file)
@@ -51,448 +51,3 @@ Cc: Robert Marko <robert.marko@sartura.hr>
  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,442 @@
-+// 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)
-+ * @cur_level:                Current cooling level
-+ * @num_levels:       Number of cooling levels
-+ * @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;
-+      int cur_level;
-+      u8 num_levels;
-+      u32 cooling_levels[] __counted_by(num_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)
-+{
-+      struct iei_wt61p803_puzzle_thermal_cooling_device *cdev = tcdev->devdata;
-+
-+      if (!cdev)
-+              return -EINVAL;
-+
-+      *state = cdev->num_levels - 1;
-+      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;
-+
-+      if (!cdev)
-+              return -EINVAL;
-+
-+      if (cdev->cur_level < 0)
-+              return -EAGAIN;
-+
-+      *state = cdev->cur_level;
-+      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;
-+      u8 pwm_level;
-+
-+      if (!cdev)
-+              return -EINVAL;
-+
-+      if (state >= cdev->num_levels)
-+              return -EINVAL;
-+
-+      if (state == cdev->cur_level)
-+              return 0;
-+
-+      cdev->cur_level = state;
-+      pwm_level = cdev->cooling_levels[state];
-+
-+      return iei_wt61p803_puzzle_write_pwm_channel(cdev->mcu_hwmon, cdev->pwm_channel, pwm_level);
-+}
-+
-+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 i, 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_u32(child, "cooling-levels");
-+      if (!num_levels)
-+              return -EINVAL;
-+
-+      cdev = devm_kzalloc(dev, struct_size(cdev, cooling_levels, num_levels), GFP_KERNEL);
-+      if (!cdev)
-+              return -ENOMEM;
-+
-+      cdev->num_levels = num_levels;
-+
-+      ret = fwnode_property_read_u32_array(child, "cooling-levels",
-+                                           cdev->cooling_levels,
-+                                           num_levels);
-+      if (ret) {
-+              dev_err(dev, "Couldn't read property 'cooling-levels'\n");
-+              return ret;
-+      }
-+
-+      for (i = 0; i < num_levels; i++) {
-+              if (cdev->cooling_levels[i] >
-+                  IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_VAL) {
-+                      dev_err(dev, "iei_wt61p803_fan state[%d]:%d > %d\n", i,
-+                              cdev->cooling_levels[i],
-+                              IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_VAL);
-+                      return -EINVAL;
-+              }
-+      }
-+
-+      cdev->mcu_hwmon = mcu_hwmon;
-+      cdev->pwm_channel = pwm_channel;
-+      cdev->cur_level = -1;
-+      mcu_hwmon->cdev[pwm_channel] = cdev;
-+
-+      snprintf(cdev->name, THERMAL_NAME_LENGTH, "wt61p803_puzzle_%d", pwm_channel);
-+      cdev->tcdev = devm_thermal_of_cooling_device_register(dev, to_of_node(child), cdev->name,
-+                                                            cdev, &iei_wt61p803_puzzle_cooling_ops);
-+      if (IS_ERR(cdev->tcdev))
-+              return PTR_ERR(cdev->tcdev);
-+
-+      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 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);
-+      ret = devm_mutex_init(dev, &mcu_hwmon->lock);
-+      if (ret)
-+              return ret;
-+
-+      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_scoped(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");
-+                              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");
index 3186562678ea4fb5f9e1694a31e409fd9d322adc..540d0d90d11dc04f3d3855f2a34ff0c6133ab84d 100644 (file)
@@ -55,153 +55,3 @@ Cc: Robert Marko <robert.marko@sartura.hr>
  obj-$(CONFIG_LEDS_IS31FL319X)         += leds-is31fl319x.o
  obj-$(CONFIG_LEDS_IS31FL32XX)         += leds-is31fl32xx.o
  obj-$(CONFIG_LEDS_LM3530)             += leds-lm3530.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-6.18/910-drivers-leds-wt61p803-puzzle-improvements.patch b/target/linux/mvebu/patches-6.18/910-drivers-leds-wt61p803-puzzle-improvements.patch
deleted file mode 100644 (file)
index 7535ecf..0000000
+++ /dev/null
@@ -1,271 +0,0 @@
---- a/drivers/leds/leds-iei-wt61p803-puzzle.c
-+++ b/drivers/leds/leds-iei-wt61p803-puzzle.c
-@@ -9,9 +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>
-+#include <linux/workqueue.h>
-+
-+#define IEI_LEDS_MAX          4
- enum iei_wt61p803_puzzle_led_state {
-       IEI_LED_OFF = 0x30,
-@@ -33,7 +37,11 @@ struct iei_wt61p803_puzzle_led {
-       struct iei_wt61p803_puzzle *mcu;
-       unsigned char response_buffer[IEI_WT61P803_PUZZLE_BUF_SIZE];
-       struct mutex lock; /* mutex to protect led_power_state */
-+      struct work_struct work;
-       int led_power_state;
-+      int id;
-+      u8 blinking;
-+      bool active_low;
- };
- static inline struct iei_wt61p803_puzzle_led *cdev_to_iei_wt61p803_puzzle_led
-@@ -51,10 +59,18 @@ static int iei_wt61p803_puzzle_led_brigh
-       size_t reply_size;
-       int ret;
-+      if (priv->blinking) {
-+              if (brightness == LED_OFF)
-+                      priv->blinking = 0;
-+              else
-+                      return 0;
-+      }
-+
-       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 : priv->blinking?priv->blinking:IEI_LED_ON;
-       ret = iei_wt61p803_puzzle_write_command(priv->mcu, led_power_cmd,
-                                               sizeof(led_power_cmd),
-@@ -90,39 +106,166 @@ static enum led_brightness iei_wt61p803_
-       return led_state;
- }
-+static void iei_wt61p803_puzzle_led_apply_blink(struct work_struct *work)
-+{
-+      struct iei_wt61p803_puzzle_led *priv = container_of(work, struct iei_wt61p803_puzzle_led, work);
-+      unsigned char led_blink_cmd[5] = {};
-+      unsigned char resp_buf[IEI_WT61P803_PUZZLE_BUF_SIZE];
-+      size_t reply_size;
-+
-+      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] = priv->blinking;
-+
-+      iei_wt61p803_puzzle_write_command(priv->mcu, led_blink_cmd,
-+                                        sizeof(led_blink_cmd),
-+                                        resp_buf,
-+                                        &reply_size);
-+
-+      return;
-+}
-+
-+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);
-+      u8 blink_mode = 0;
-+      int ret = 0;
-+
-+      /* set defaults */
-+      if (!*delay_on && !*delay_off) {
-+              *delay_on = 500;
-+              *delay_off = 500;
-+      }
-+
-+      /* minimum delay for soft-driven blinking is 100ms to keep load low */
-+      if (*delay_on < 100)
-+              *delay_on = 100;
-+
-+      if (*delay_off < 100)
-+              *delay_off = 100;
-+
-+      /* offload blinking to hardware, if possible */
-+      if (*delay_on != *delay_off) {
-+              ret = -EINVAL;
-+      } else if (*delay_on == 100) {
-+              blink_mode = IEI_LED_BLINK_5HZ;
-+              *delay_on = 100;
-+              *delay_off = 100;
-+      } else if (*delay_on <= 500) {
-+              blink_mode = IEI_LED_BLINK_1HZ;
-+              *delay_on = 500;
-+              *delay_off = 500;
-+      } else {
-+              ret = -EINVAL;
-+      }
-+
-+      mutex_lock(&priv->lock);
-+      priv->blinking = blink_mode;
-+      mutex_unlock(&priv->lock);
-+
-+      if (blink_mode)
-+              schedule_work(&priv->work);
-+
-+      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 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_scoped(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;
-+              }
-+
-+              ret = devm_mutex_init(dev, &priv->lock);
-+              if (ret)
-+                      goto put_child_node;
-+
-+              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 = 0;
-+              init_data.fwnode = of_fwnode_handle(of_node_get(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;
-+
-+              INIT_WORK(&priv->work, iei_wt61p803_puzzle_led_apply_blink);
-+
-+              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:
-       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 */
---- a/drivers/mfd/iei-wt61p803-puzzle.c
-+++ b/drivers/mfd/iei-wt61p803-puzzle.c
-@@ -176,6 +176,9 @@ static size_t iei_wt61p803_puzzle_recv_b
-       struct iei_wt61p803_puzzle *mcu = serdev_device_get_drvdata(serdev);
-       size_t ret;
-+      print_hex_dump_debug("puzzle-mcu rx: ", DUMP_PREFIX_NONE,
-+                           16, 1, data, size, false);
-+
-       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
-@@ -246,6 +249,9 @@ int iei_wt61p803_puzzle_write_command(st
-       cmd[size - 1] = iei_wt61p803_puzzle_checksum(cmd, size - 1);
-+      print_hex_dump_debug("puzzle-mcu tx: ", DUMP_PREFIX_NONE,
-+                           16, 1, cmd, size, false);
-+
-       /* Initialize reply struct */
-       reinit_completion(&mcu->reply->received);
-       mcu->reply->size = 0;
diff --git a/target/linux/mvebu/patches-6.18/911-drivers-leds-wt61p803-puzzle-mcu-retry.patch b/target/linux/mvebu/patches-6.18/911-drivers-leds-wt61p803-puzzle-mcu-retry.patch
deleted file mode 100644 (file)
index 2f0b178..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
---- a/drivers/mfd/iei-wt61p803-puzzle.c
-+++ b/drivers/mfd/iei-wt61p803-puzzle.c
-@@ -241,6 +241,7 @@ int iei_wt61p803_puzzle_write_command(st
- {
-       struct device *dev = &mcu->serdev->dev;
-       int ret;
-+      int retries;
-       if (size <= 1 || size > IEI_WT61P803_PUZZLE_MAX_COMMAND_LENGTH)
-               return -EINVAL;
-@@ -252,24 +253,36 @@ int iei_wt61p803_puzzle_write_command(st
-       print_hex_dump_debug("puzzle-mcu tx: ", DUMP_PREFIX_NONE,
-                            16, 1, cmd, size, false);
-+      retries = 3;
-       /* 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;
-+      while (retries) {
-+              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);
-+              retries--;
-+              if (ret == 0) {
-+                      if (retries == 0) {
-+                              dev_err(dev, "Command reply receive timeout\n");
-+                              ret = -ETIMEDOUT;
-+                              goto exit;
-+                      }
-+              }
-+              else {
-+                      if (mcu->reply->data[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START &&
-+                              mcu->reply->data[1] == IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK &&
-+                              mcu->reply->data[2] == IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK) {
-+                              break;
-+                      }
-+              }
-       }
--
-       *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);
diff --git a/target/linux/mvebu/patches-6.18/912-drivers-hwmon-wt61p803-puzzle-thermal-zone.patch b/target/linux/mvebu/patches-6.18/912-drivers-hwmon-wt61p803-puzzle-thermal-zone.patch
deleted file mode 100644 (file)
index 4633c03..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
---- a/drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
-+++ b/drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
-@@ -251,6 +251,7 @@ static const struct hwmon_ops iei_wt61p8
- };
- static const struct hwmon_channel_info *iei_wt61p803_puzzle_info[] = {
-+      HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
-       HWMON_CHANNEL_INFO(pwm,
-                          HWMON_PWM_INPUT,
-                          HWMON_PWM_INPUT),