--- /dev/null
+// 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");
--- /dev/null
+// 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", ®);
+ 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");
--- /dev/null
+// 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");
--- /dev/null
+/* 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_ */
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_ */
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");
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");
+++ /dev/null
---- 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", ®);
-+ 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;
+++ /dev/null
---- 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);
+++ /dev/null
---- 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),