mvebu: puzzle-m902: add driver for MCU driving LEDs, fan and buzzer

Backport MFD driver for communicating with the on-board MCU found on
IEI World Puzzle appliances.
Improve the driver to support multiple LEDs, apply a default state and
let MCU take care of blinking if timing is within supported range.
Wire up LEDs and fan for Puzzle M902 in device tree.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
This commit is contained in:
Daniel Golle 2021-12-21 16:27:16 +00:00
parent c46202fb34
commit f0c0b18234
No known key found for this signature in database
GPG Key ID: 5A8F39C31C3217CA
11 changed files with 2462 additions and 1 deletions

View File

@ -42,8 +42,10 @@ CONFIG_HOLES_IN_ZONE=y
CONFIG_HW_RANDOM_OMAP=y
CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000
CONFIG_LEDS_IS31FL319X=y
CONFIG_LEDS_IEI_WT61P803_PUZZLE=y
CONFIG_MARVELL_10G_PHY=y
CONFIG_MDIO_DEVRES=y
CONFIG_MFD_IEI_WT61P803_PUZZLE=y
CONFIG_MFD_SYSCON=y
CONFIG_MMC_SDHCI_XENON=y
CONFIG_MODULES_USE_ELF_RELA=y
@ -73,8 +75,12 @@ CONFIG_POWER_SUPPLY=y
CONFIG_QUEUED_RWLOCKS=y
CONFIG_QUEUED_SPINLOCKS=y
CONFIG_RAS=y
# CONFIG_RAVE_SP_CORE is not set
CONFIG_REGULATOR_GPIO=y
# CONFIG_RODATA_FULL_DEFAULT_ENABLED is not set
CONFIG_SENSORS_IEI_WT61P803_PUZZLE_HWMON=y
CONFIG_SERIAL_DEV_BUS=y
CONFIG_SERIAL_DEV_CTRL_TTYPORT=y
CONFIG_SPARSEMEM=y
CONFIG_SPARSEMEM_EXTREME=y
CONFIG_SPARSEMEM_MANUAL=y

View File

@ -38,7 +38,10 @@
ethernet8 = &cp2_eth2;
spi1 = &cp0_spi0;
spi2 = &cp0_spi1;
serial1 = &cp0_uart0;
led-boot = &led_power;
led-failsafe = &led_info;
led-running = &led_power;
led-upgrade = &led_info;
};
memory@00000000 {
@ -91,6 +94,75 @@
&cp0_uart0 {
status = "okay";
puzzle-mcu {
compatible = "iei,wt61p803-puzzle";
#address-cells = <1>;
#size-cells = <1>;
current-speed = <115200>;
enable-beep;
status = "okay";
leds {
compatible = "iei,wt61p803-puzzle-leds";
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
led@0 {
reg = <0>;
label = "white:network";
active-low;
};
led@1 {
reg = <1>;
label = "green:cloud";
active-low;
};
led_info: led@2 {
reg = <2>;
label = "orange:info";
active-low;
};
led_power: led@3 {
reg = <3>;
label = "yellow:power";
active-low;
default-state = "on";
};
};
hwmon {
compatible = "iei,wt61p803-puzzle-hwmon";
#address-cells = <1>;
#size-cells = <0>;
chassis_fan_group0: fan-group@0 {
#cooling-cells = <2>;
reg = <0x00>;
cooling-levels = <64 102 170 230 250>;
};
};
};
};
&ap_thermal_cpu1 {
trips {
cpu_active: cpu-active {
temperature = <44000>;
hysteresis = <2000>;
type = "active";
};
};
cooling-maps {
fan-map {
trip = <&cpu_active>;
cooling-device = <&chassis_fan_group0 64 THERMAL_NO_LIMIT>;
};
};
};
/* on-board eMMC - U9 */

View File

@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0+ */
#ifndef _LINUX_PUZZLE_H_
#define _LINUX_PUZZLE_H_
struct puzzle;
int puzzle_led(struct puzzle *pz, u8 ledn, u8 ledmode);
int puzzle_fan(struct puzzle *pz, u8 speed);
int puzzle_buzzer(struct puzzle *pz, u8 len);
#endif /* _LINUX_PUZZLE_H_ */

View File

@ -0,0 +1,218 @@
From aa4a0ccc41997f2da172165c92803abace43bd1c Mon Sep 17 00:00:00 2001
From: Luka Kovacic <luka.kovacic () sartura ! hr>
Date: Tue, 24 Aug 2021 12:44:32 +0000
Subject: [PATCH 1/7] dt-bindings: Add IEI vendor prefix and IEI WT61P803
PUZZLE driver bindings
Add the IEI WT61P803 PUZZLE Device Tree bindings for MFD, HWMON and LED
drivers. A new vendor prefix is also added accordingly for
IEI Integration Corp.
Signed-off-by: Luka Kovacic <luka.kovacic@sartura.hr>
Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr>
Cc: Luka Perkov <luka.perkov@sartura.hr>
Cc: Robert Marko <robert.marko@sartura.hr>
---
.../hwmon/iei,wt61p803-puzzle-hwmon.yaml | 53 ++++++++++++
.../leds/iei,wt61p803-puzzle-leds.yaml | 39 +++++++++
.../bindings/mfd/iei,wt61p803-puzzle.yaml | 82 +++++++++++++++++++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
4 files changed, 176 insertions(+)
create mode 100644 Documentation/devicetree/bindings/hwmon/iei,wt61p803-puzzle-hwmon.yaml
create mode 100644 Documentation/devicetree/bindings/leds/iei,wt61p803-puzzle-leds.yaml
create mode 100644 Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/iei,wt61p803-puzzle-hwmon.yaml
@@ -0,0 +1,53 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/hwmon/iei,wt61p803-puzzle-hwmon.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: IEI WT61P803 PUZZLE MCU HWMON module from IEI Integration Corp.
+
+maintainers:
+ - Luka Kovacic <luka.kovacic@sartura.hr>
+
+description: |
+ This module is a part of the IEI WT61P803 PUZZLE MFD device. For more details
+ see Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml.
+
+ The HWMON module is a sub-node of the MCU node in the Device Tree.
+
+properties:
+ compatible:
+ const: iei,wt61p803-puzzle-hwmon
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+patternProperties:
+ "^fan-group@[0-1]$":
+ type: object
+ properties:
+ reg:
+ minimum: 0
+ maximum: 1
+ description:
+ Fan group ID
+
+ cooling-levels:
+ minItems: 1
+ maxItems: 255
+ description:
+ Cooling levels for the fans (PWM value mapping)
+ description: |
+ Properties for each fan group.
+ required:
+ - reg
+
+required:
+ - compatible
+ - "#address-cells"
+ - "#size-cells"
+
+additionalProperties: false
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/iei,wt61p803-puzzle-leds.yaml
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/iei,wt61p803-puzzle-leds.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: IEI WT61P803 PUZZLE MCU LED module from IEI Integration Corp.
+
+maintainers:
+ - Luka Kovacic <luka.kovacic@sartura.hr>
+
+description: |
+ This module is a part of the IEI WT61P803 PUZZLE MFD device. For more details
+ see Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml.
+
+ The LED module is a sub-node of the MCU node in the Device Tree.
+
+properties:
+ compatible:
+ const: iei,wt61p803-puzzle-leds
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+ led@0:
+ type: object
+ $ref: common.yaml
+ description: |
+ Properties for a single LED.
+
+required:
+ - compatible
+ - "#address-cells"
+ - "#size-cells"
+
+additionalProperties: false
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml
@@ -0,0 +1,82 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/iei,wt61p803-puzzle.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: IEI WT61P803 PUZZLE MCU from IEI Integration Corp.
+
+maintainers:
+ - Luka Kovacic <luka.kovacic@sartura.hr>
+
+description: |
+ IEI WT61P803 PUZZLE MCU is embedded in some IEI Puzzle series boards.
+ It's used for controlling system power states, fans, LEDs and temperature
+ sensors.
+
+ For Device Tree bindings of other sub-modules (HWMON, LEDs) refer to the
+ binding documents under the respective subsystem directories.
+
+properties:
+ compatible:
+ const: iei,wt61p803-puzzle
+
+ current-speed:
+ description:
+ Serial bus speed in bps
+ maxItems: 1
+
+ enable-beep: true
+
+ hwmon:
+ $ref: /schemas/hwmon/iei,wt61p803-puzzle-hwmon.yaml
+
+ leds:
+ $ref: /schemas/leds/iei,wt61p803-puzzle-leds.yaml
+
+required:
+ - compatible
+ - current-speed
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/leds/common.h>
+ serial {
+ mcu {
+ compatible = "iei,wt61p803-puzzle";
+ current-speed = <115200>;
+ enable-beep;
+
+ leds {
+ compatible = "iei,wt61p803-puzzle-leds";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led@0 {
+ reg = <0>;
+ function = LED_FUNCTION_POWER;
+ color = <LED_COLOR_ID_BLUE>;
+ };
+ };
+
+ hwmon {
+ compatible = "iei,wt61p803-puzzle-hwmon";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ fan-group@0 {
+ #cooling-cells = <2>;
+ reg = <0x00>;
+ cooling-levels = <64 102 170 230 250>;
+ };
+
+ fan-group@1 {
+ #cooling-cells = <2>;
+ reg = <0x01>;
+ cooling-levels = <64 102 170 230 250>;
+ };
+ };
+ };
+ };
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -475,6 +475,8 @@ patternProperties:
description: IC Plus Corp.
"^idt,.*":
description: Integrated Device Technologies, Inc.
+ "^iei,.*":
+ description: IEI Integration Corp.
"^ifi,.*":
description: Ingenieurburo Fur Ic-Technologie (I/F/I)
"^ilitek,.*":

View File

@ -0,0 +1,469 @@
From e3310a638cd310bfd93dbbc6d2732ab6aea18dd2 Mon Sep 17 00:00:00 2001
From: Luka Kovacic <luka.kovacic () sartura ! hr>
Date: Tue, 24 Aug 2021 12:44:34 +0000
Subject: [PATCH 3/7] drivers: hwmon: Add the IEI WT61P803 PUZZLE HWMON driver
Add the IEI WT61P803 PUZZLE HWMON driver, that handles the fan speed
control via PWM, reading fan speed and reading on-board temperature
sensors.
The driver registers a HWMON device and a simple thermal cooling device to
enable in-kernel fan management.
This driver depends on the IEI WT61P803 PUZZLE MFD driver.
Signed-off-by: Luka Kovacic <luka.kovacic@sartura.hr>
Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr>
Acked-by: Guenter Roeck <linux@roeck-us.net>
Cc: Luka Perkov <luka.perkov@sartura.hr>
Cc: Robert Marko <robert.marko@sartura.hr>
---
drivers/hwmon/Kconfig | 8 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/iei-wt61p803-puzzle-hwmon.c | 413 ++++++++++++++++++++++
3 files changed, 422 insertions(+)
create mode 100644 drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -722,6 +722,14 @@ config SENSORS_IBMPOWERNV
This driver can also be built as a module. If so, the module
will be called ibmpowernv.
+config SENSORS_IEI_WT61P803_PUZZLE_HWMON
+ tristate "IEI WT61P803 PUZZLE MFD HWMON Driver"
+ depends on MFD_IEI_WT61P803_PUZZLE
+ help
+ The IEI WT61P803 PUZZLE MFD HWMON Driver handles reading fan speed
+ and writing fan PWM values. It also supports reading on-board
+ temperature sensors.
+
config SENSORS_IIO_HWMON
tristate "Hwmon driver that uses channels specified via iio maps"
depends on IIO
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -83,6 +83,7 @@ obj-$(CONFIG_SENSORS_HIH6130) += hih6130
obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o
obj-$(CONFIG_SENSORS_I5500) += i5500_temp.o
obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o
+obj-$(CONFIG_SENSORS_IEI_WT61P803_PUZZLE_HWMON) += iei-wt61p803-puzzle-hwmon.o
obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o
obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o
obj-$(CONFIG_SENSORS_IBMPOWERNV)+= ibmpowernv.o
--- /dev/null
+++ b/drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
@@ -0,0 +1,413 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* IEI WT61P803 PUZZLE MCU HWMON Driver
+ *
+ * Copyright (C) 2020 Sartura Ltd.
+ * Author: Luka Kovacic <luka.kovacic@sartura.hr>
+ */
+
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/math64.h>
+#include <linux/mfd/iei-wt61p803-puzzle.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#define IEI_WT61P803_PUZZLE_HWMON_MAX_PWM 2
+#define IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_VAL 255
+
+/**
+ * struct iei_wt61p803_puzzle_thermal_cooling_device - Thermal cooling device instance
+ * @mcu_hwmon: Parent driver struct pointer
+ * @tcdev: Thermal cooling device pointer
+ * @name: Thermal cooling device name
+ * @pwm_channel: Controlled PWM channel (0 or 1)
+ * @cooling_levels: Thermal cooling device cooling levels (DT)
+ */
+struct iei_wt61p803_puzzle_thermal_cooling_device {
+ struct iei_wt61p803_puzzle_hwmon *mcu_hwmon;
+ struct thermal_cooling_device *tcdev;
+ char name[THERMAL_NAME_LENGTH];
+ int pwm_channel;
+ u8 *cooling_levels;
+};
+
+/**
+ * struct iei_wt61p803_puzzle_hwmon - MCU HWMON Driver
+ * @mcu: MCU struct pointer
+ * @response_buffer Global MCU response buffer
+ * @thermal_cooling_dev_present: Per-channel thermal cooling device control indicator
+ * @cdev: Per-channel thermal cooling device private structure
+ */
+struct iei_wt61p803_puzzle_hwmon {
+ struct iei_wt61p803_puzzle *mcu;
+ unsigned char response_buffer[IEI_WT61P803_PUZZLE_BUF_SIZE];
+ bool thermal_cooling_dev_present[IEI_WT61P803_PUZZLE_HWMON_MAX_PWM];
+ struct iei_wt61p803_puzzle_thermal_cooling_device
+ *cdev[IEI_WT61P803_PUZZLE_HWMON_MAX_PWM];
+ struct mutex lock; /* mutex to protect response_buffer array */
+};
+
+#define raw_temp_to_milidegree_celsius(x) (((x) - 0x80) * 1000)
+static int iei_wt61p803_puzzle_read_temp_sensor(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon,
+ int channel, long *value)
+{
+ unsigned char *resp_buf = mcu_hwmon->response_buffer;
+ unsigned char temp_sensor_ntc_cmd[4] = {
+ IEI_WT61P803_PUZZLE_CMD_HEADER_START,
+ IEI_WT61P803_PUZZLE_CMD_TEMP,
+ IEI_WT61P803_PUZZLE_CMD_TEMP_ALL,
+ };
+ size_t reply_size;
+ int ret;
+
+ mutex_lock(&mcu_hwmon->lock);
+ ret = iei_wt61p803_puzzle_write_command(mcu_hwmon->mcu, temp_sensor_ntc_cmd,
+ sizeof(temp_sensor_ntc_cmd), resp_buf,
+ &reply_size);
+ if (ret)
+ goto exit;
+
+ if (reply_size != 7) {
+ ret = -EIO;
+ goto exit;
+ }
+
+ /* Check the number of NTC values */
+ if (resp_buf[3] != '2') {
+ ret = -EIO;
+ goto exit;
+ }
+
+ *value = raw_temp_to_milidegree_celsius(resp_buf[4 + channel]);
+exit:
+ mutex_unlock(&mcu_hwmon->lock);
+ return ret;
+}
+
+#define raw_fan_val_to_rpm(x, y) ((((x) << 8 | (y)) / 2) * 60)
+static int iei_wt61p803_puzzle_read_fan_speed(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon,
+ int channel, long *value)
+{
+ unsigned char *resp_buf = mcu_hwmon->response_buffer;
+ unsigned char fan_speed_cmd[4] = {};
+ size_t reply_size;
+ int ret;
+
+ fan_speed_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START;
+ fan_speed_cmd[1] = IEI_WT61P803_PUZZLE_CMD_FAN;
+ fan_speed_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_RPM(channel);
+
+ mutex_lock(&mcu_hwmon->lock);
+ ret = iei_wt61p803_puzzle_write_command(mcu_hwmon->mcu, fan_speed_cmd,
+ sizeof(fan_speed_cmd), resp_buf,
+ &reply_size);
+ if (ret)
+ goto exit;
+
+ if (reply_size != 7) {
+ ret = -EIO;
+ goto exit;
+ }
+
+ *value = raw_fan_val_to_rpm(resp_buf[3], resp_buf[4]);
+exit:
+ mutex_unlock(&mcu_hwmon->lock);
+ return ret;
+}
+
+static int iei_wt61p803_puzzle_write_pwm_channel(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon,
+ int channel, long pwm_set_val)
+{
+ unsigned char *resp_buf = mcu_hwmon->response_buffer;
+ unsigned char pwm_set_cmd[6] = {};
+ size_t reply_size;
+ int ret;
+
+ pwm_set_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START;
+ pwm_set_cmd[1] = IEI_WT61P803_PUZZLE_CMD_FAN;
+ pwm_set_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM_WRITE;
+ pwm_set_cmd[3] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM(channel);
+ pwm_set_cmd[4] = pwm_set_val;
+
+ mutex_lock(&mcu_hwmon->lock);
+ ret = iei_wt61p803_puzzle_write_command(mcu_hwmon->mcu, pwm_set_cmd,
+ sizeof(pwm_set_cmd), resp_buf,
+ &reply_size);
+ if (ret)
+ goto exit;
+
+ if (reply_size != 3) {
+ ret = -EIO;
+ goto exit;
+ }
+
+ if (!(resp_buf[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START &&
+ resp_buf[1] == IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK &&
+ resp_buf[2] == IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK)) {
+ ret = -EIO;
+ goto exit;
+ }
+exit:
+ mutex_unlock(&mcu_hwmon->lock);
+ return ret;
+}
+
+static int iei_wt61p803_puzzle_read_pwm_channel(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon,
+ int channel, long *value)
+{
+ unsigned char *resp_buf = mcu_hwmon->response_buffer;
+ unsigned char pwm_get_cmd[5] = {};
+ size_t reply_size;
+ int ret;
+
+ pwm_get_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START;
+ pwm_get_cmd[1] = IEI_WT61P803_PUZZLE_CMD_FAN;
+ pwm_get_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM_READ;
+ pwm_get_cmd[3] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM(channel);
+
+ ret = iei_wt61p803_puzzle_write_command(mcu_hwmon->mcu, pwm_get_cmd,
+ sizeof(pwm_get_cmd), resp_buf,
+ &reply_size);
+ if (ret)
+ return ret;
+
+ if (reply_size != 5)
+ return -EIO;
+
+ if (resp_buf[2] != IEI_WT61P803_PUZZLE_CMD_FAN_PWM_READ)
+ return -EIO;
+
+ *value = resp_buf[3];
+
+ return 0;
+}
+
+static int iei_wt61p803_puzzle_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct iei_wt61p803_puzzle_hwmon *mcu_hwmon = dev_get_drvdata(dev->parent);
+
+ switch (type) {
+ case hwmon_pwm:
+ return iei_wt61p803_puzzle_read_pwm_channel(mcu_hwmon, channel, val);
+ case hwmon_fan:
+ return iei_wt61p803_puzzle_read_fan_speed(mcu_hwmon, channel, val);
+ case hwmon_temp:
+ return iei_wt61p803_puzzle_read_temp_sensor(mcu_hwmon, channel, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int iei_wt61p803_puzzle_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct iei_wt61p803_puzzle_hwmon *mcu_hwmon = dev_get_drvdata(dev->parent);
+
+ return iei_wt61p803_puzzle_write_pwm_channel(mcu_hwmon, channel, val);
+}
+
+static umode_t iei_wt61p803_puzzle_is_visible(const void *data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct iei_wt61p803_puzzle_hwmon *mcu_hwmon = data;
+
+ switch (type) {
+ case hwmon_pwm:
+ if (mcu_hwmon->thermal_cooling_dev_present[channel])
+ return 0444;
+ if (attr == hwmon_pwm_input)
+ return 0644;
+ break;
+ case hwmon_fan:
+ if (attr == hwmon_fan_input)
+ return 0444;
+ break;
+ case hwmon_temp:
+ if (attr == hwmon_temp_input)
+ return 0444;
+ break;
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+static const struct hwmon_ops iei_wt61p803_puzzle_hwmon_ops = {
+ .is_visible = iei_wt61p803_puzzle_is_visible,
+ .read = iei_wt61p803_puzzle_read,
+ .write = iei_wt61p803_puzzle_write,
+};
+
+static const struct hwmon_channel_info *iei_wt61p803_puzzle_info[] = {
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT,
+ HWMON_F_INPUT,
+ HWMON_F_INPUT,
+ HWMON_F_INPUT,
+ HWMON_F_INPUT),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT),
+ NULL
+};
+
+static const struct hwmon_chip_info iei_wt61p803_puzzle_chip_info = {
+ .ops = &iei_wt61p803_puzzle_hwmon_ops,
+ .info = iei_wt61p803_puzzle_info,
+};
+
+static int iei_wt61p803_puzzle_get_max_state(struct thermal_cooling_device *tcdev,
+ unsigned long *state)
+{
+ *state = IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_VAL;
+
+ return 0;
+}
+
+static int iei_wt61p803_puzzle_get_cur_state(struct thermal_cooling_device *tcdev,
+ unsigned long *state)
+{
+ struct iei_wt61p803_puzzle_thermal_cooling_device *cdev = tcdev->devdata;
+ struct iei_wt61p803_puzzle_hwmon *mcu_hwmon = cdev->mcu_hwmon;
+ long value;
+ int ret;
+
+ ret = iei_wt61p803_puzzle_read_pwm_channel(mcu_hwmon, cdev->pwm_channel, &value);
+ if (ret)
+ return ret;
+ *state = value;
+ return 0;
+}
+
+static int iei_wt61p803_puzzle_set_cur_state(struct thermal_cooling_device *tcdev,
+ unsigned long state)
+{
+ struct iei_wt61p803_puzzle_thermal_cooling_device *cdev = tcdev->devdata;
+ struct iei_wt61p803_puzzle_hwmon *mcu_hwmon = cdev->mcu_hwmon;
+
+ return iei_wt61p803_puzzle_write_pwm_channel(mcu_hwmon, cdev->pwm_channel, state);
+}
+
+static const struct thermal_cooling_device_ops iei_wt61p803_puzzle_cooling_ops = {
+ .get_max_state = iei_wt61p803_puzzle_get_max_state,
+ .get_cur_state = iei_wt61p803_puzzle_get_cur_state,
+ .set_cur_state = iei_wt61p803_puzzle_set_cur_state,
+};
+
+static int
+iei_wt61p803_puzzle_enable_thermal_cooling_dev(struct device *dev,
+ struct fwnode_handle *child,
+ struct iei_wt61p803_puzzle_hwmon *mcu_hwmon)
+{
+ struct iei_wt61p803_puzzle_thermal_cooling_device *cdev;
+ u32 pwm_channel;
+ u8 num_levels;
+ int ret;
+
+ ret = fwnode_property_read_u32(child, "reg", &pwm_channel);
+ if (ret)
+ return ret;
+
+ mcu_hwmon->thermal_cooling_dev_present[pwm_channel] = true;
+
+ num_levels = fwnode_property_count_u8(child, "cooling-levels");
+ if (!num_levels)
+ return -EINVAL;
+
+ cdev = devm_kzalloc(dev, sizeof(*cdev), GFP_KERNEL);
+ if (!cdev)
+ return -ENOMEM;
+
+ cdev->cooling_levels = devm_kmalloc_array(dev, num_levels, sizeof(u8), GFP_KERNEL);
+ if (!cdev->cooling_levels)
+ return -ENOMEM;
+
+ ret = fwnode_property_read_u8_array(child, "cooling-levels",
+ cdev->cooling_levels,
+ num_levels);
+ if (ret) {
+ dev_err(dev, "Couldn't read property 'cooling-levels'\n");
+ return ret;
+ }
+
+ snprintf(cdev->name, THERMAL_NAME_LENGTH, "wt61p803_puzzle_%d", pwm_channel);
+ cdev->tcdev = devm_thermal_of_cooling_device_register(dev, NULL, cdev->name, cdev,
+ &iei_wt61p803_puzzle_cooling_ops);
+ if (IS_ERR(cdev->tcdev))
+ return PTR_ERR(cdev->tcdev);
+
+ cdev->mcu_hwmon = mcu_hwmon;
+ cdev->pwm_channel = pwm_channel;
+ mcu_hwmon->cdev[pwm_channel] = cdev;
+
+ return 0;
+}
+
+static int iei_wt61p803_puzzle_hwmon_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev->parent);
+ struct iei_wt61p803_puzzle_hwmon *mcu_hwmon;
+ struct fwnode_handle *child;
+ struct device *hwmon_dev;
+ int ret;
+
+ mcu_hwmon = devm_kzalloc(dev, sizeof(*mcu_hwmon), GFP_KERNEL);
+ if (!mcu_hwmon)
+ return -ENOMEM;
+
+ mcu_hwmon->mcu = mcu;
+ platform_set_drvdata(pdev, mcu_hwmon);
+ mutex_init(&mcu_hwmon->lock);
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, "iei_wt61p803_puzzle",
+ mcu_hwmon,
+ &iei_wt61p803_puzzle_chip_info,
+ NULL);
+ if (IS_ERR(hwmon_dev))
+ return PTR_ERR(hwmon_dev);
+
+ /* Control fans via PWM lines via Linux Kernel */
+ if (IS_ENABLED(CONFIG_THERMAL)) {
+ device_for_each_child_node(dev, child) {
+ ret = iei_wt61p803_puzzle_enable_thermal_cooling_dev(dev, child, mcu_hwmon);
+ if (ret) {
+ dev_err(dev, "Enabling the PWM fan failed\n");
+ fwnode_handle_put(child);
+ return ret;
+ }
+ }
+ }
+ return 0;
+}
+
+static const struct of_device_id iei_wt61p803_puzzle_hwmon_id_table[] = {
+ { .compatible = "iei,wt61p803-puzzle-hwmon" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, iei_wt61p803_puzzle_hwmon_id_table);
+
+static struct platform_driver iei_wt61p803_puzzle_hwmon_driver = {
+ .driver = {
+ .name = "iei-wt61p803-puzzle-hwmon",
+ .of_match_table = iei_wt61p803_puzzle_hwmon_id_table,
+ },
+ .probe = iei_wt61p803_puzzle_hwmon_probe,
+};
+
+module_platform_driver(iei_wt61p803_puzzle_hwmon_driver);
+
+MODULE_DESCRIPTION("IEI WT61P803 PUZZLE MCU HWMON Driver");
+MODULE_AUTHOR("Luka Kovacic <luka.kovacic@sartura.hr>");
+MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,207 @@
From f3b44eb69cc561cf05d00506dcec0dd9be003ed8 Mon Sep 17 00:00:00 2001
From: Luka Kovacic <luka.kovacic () sartura ! hr>
Date: Tue, 24 Aug 2021 12:44:35 +0000
Subject: [PATCH 4/7] drivers: leds: Add the IEI WT61P803 PUZZLE LED driver
Add support for the IEI WT61P803 PUZZLE LED driver.
Currently only the front panel power LED is supported,
since it is the only LED on this board wired through the
MCU.
The LED is wired directly to the on-board MCU controller
and is toggled using an MCU command.
Support for more LEDs is going to be added in case more
boards implement this microcontroller, as LEDs use many
different GPIOs.
This driver depends on the IEI WT61P803 PUZZLE MFD driver.
Signed-off-by: Luka Kovacic <luka.kovacic@sartura.hr>
Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr>
Cc: Luka Perkov <luka.perkov@sartura.hr>
Cc: Robert Marko <robert.marko@sartura.hr>
---
drivers/leds/Kconfig | 8 ++
drivers/leds/Makefile | 1 +
drivers/leds/leds-iei-wt61p803-puzzle.c | 147 ++++++++++++++++++++++++
3 files changed, 156 insertions(+)
create mode 100644 drivers/leds/leds-iei-wt61p803-puzzle.c
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -333,6 +333,14 @@ config LEDS_IPAQ_MICRO
Choose this option if you want to use the notification LED on
Compaq/HP iPAQ h3100 and h3600.
+config LEDS_IEI_WT61P803_PUZZLE
+ tristate "LED Support for the IEI WT61P803 PUZZLE MCU"
+ depends on LEDS_CLASS
+ depends on MFD_IEI_WT61P803_PUZZLE
+ help
+ This option enables support for LEDs controlled by the IEI WT61P803
+ M801 MCU.
+
config LEDS_HP6XX
tristate "LED Support for the HP Jornada 6xx"
depends on LEDS_CLASS
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.
obj-$(CONFIG_LEDS_INTEL_SS4200) += leds-ss4200.o
obj-$(CONFIG_LEDS_IP30) += leds-ip30.o
obj-$(CONFIG_LEDS_IPAQ_MICRO) += leds-ipaq-micro.o
+obj-$(CONFIG_LEDS_IEI_WT61P803_PUZZLE) += leds-iei-wt61p803-puzzle.o
obj-$(CONFIG_LEDS_IS31FL319X) += leds-is31fl319x.o
obj-$(CONFIG_LEDS_IS31FL32XX) += leds-is31fl32xx.o
obj-$(CONFIG_LEDS_KTD2692) += leds-ktd2692.o
--- /dev/null
+++ b/drivers/leds/leds-iei-wt61p803-puzzle.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* IEI WT61P803 PUZZLE MCU LED Driver
+ *
+ * Copyright (C) 2020 Sartura Ltd.
+ * Author: Luka Kovacic <luka.kovacic@sartura.hr>
+ */
+
+#include <linux/leds.h>
+#include <linux/mfd/iei-wt61p803-puzzle.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+
+enum iei_wt61p803_puzzle_led_state {
+ IEI_LED_OFF = 0x30,
+ IEI_LED_ON = 0x31,
+ IEI_LED_BLINK_5HZ = 0x32,
+ IEI_LED_BLINK_1HZ = 0x33,
+};
+
+/**
+ * struct iei_wt61p803_puzzle_led - MCU LED Driver
+ * @cdev: LED classdev
+ * @mcu: MCU struct pointer
+ * @response_buffer Global MCU response buffer
+ * @lock: General mutex lock to protect simultaneous R/W access to led_power_state
+ * @led_power_state: State of the front panel power LED
+ */
+struct iei_wt61p803_puzzle_led {
+ struct led_classdev cdev;
+ struct iei_wt61p803_puzzle *mcu;
+ unsigned char response_buffer[IEI_WT61P803_PUZZLE_BUF_SIZE];
+ struct mutex lock; /* mutex to protect led_power_state */
+ int led_power_state;
+};
+
+static inline struct iei_wt61p803_puzzle_led *cdev_to_iei_wt61p803_puzzle_led
+ (struct led_classdev *led_cdev)
+{
+ return container_of(led_cdev, struct iei_wt61p803_puzzle_led, cdev);
+}
+
+static int iei_wt61p803_puzzle_led_brightness_set_blocking(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct iei_wt61p803_puzzle_led *priv = cdev_to_iei_wt61p803_puzzle_led(cdev);
+ unsigned char *resp_buf = priv->response_buffer;
+ unsigned char led_power_cmd[5] = {};
+ size_t reply_size;
+ int ret;
+
+ led_power_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START;
+ led_power_cmd[1] = IEI_WT61P803_PUZZLE_CMD_LED;
+ led_power_cmd[2] = IEI_WT61P803_PUZZLE_CMD_LED_POWER;
+ led_power_cmd[3] = brightness == LED_OFF ? IEI_LED_OFF : IEI_LED_ON;
+
+ ret = iei_wt61p803_puzzle_write_command(priv->mcu, led_power_cmd,
+ sizeof(led_power_cmd),
+ resp_buf,
+ &reply_size);
+ if (ret)
+ return ret;
+
+ if (reply_size != 3)
+ return -EIO;
+
+ if (!(resp_buf[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START &&
+ resp_buf[1] == IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK &&
+ resp_buf[2] == IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK))
+ return -EIO;
+
+ mutex_lock(&priv->lock);
+ priv->led_power_state = brightness;
+ mutex_unlock(&priv->lock);
+
+ return 0;
+}
+
+static enum led_brightness iei_wt61p803_puzzle_led_brightness_get(struct led_classdev *cdev)
+{
+ struct iei_wt61p803_puzzle_led *priv = cdev_to_iei_wt61p803_puzzle_led(cdev);
+ int led_state;
+
+ mutex_lock(&priv->lock);
+ led_state = priv->led_power_state;
+ mutex_unlock(&priv->lock);
+
+ return led_state;
+}
+
+static int iei_wt61p803_puzzle_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev->parent);
+ struct iei_wt61p803_puzzle_led *priv;
+ struct led_init_data init_data = {};
+ struct fwnode_handle *child;
+ int ret;
+
+ if (device_get_child_node_count(dev) != 1)
+ return -EINVAL;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->mcu = mcu;
+ priv->led_power_state = 1;
+ mutex_init(&priv->lock);
+ dev_set_drvdata(dev, priv);
+
+ child = device_get_next_child_node(dev, NULL);
+ init_data.fwnode = child;
+
+ priv->cdev.brightness_set_blocking = iei_wt61p803_puzzle_led_brightness_set_blocking;
+ priv->cdev.brightness_get = iei_wt61p803_puzzle_led_brightness_get;
+ priv->cdev.max_brightness = 1;
+
+ ret = devm_led_classdev_register_ext(dev, &priv->cdev, &init_data);
+ if (ret)
+ dev_err(dev, "Could not register LED\n");
+
+ fwnode_handle_put(child);
+ return ret;
+}
+
+static const struct of_device_id iei_wt61p803_puzzle_led_of_match[] = {
+ { .compatible = "iei,wt61p803-puzzle-leds" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, iei_wt61p803_puzzle_led_of_match);
+
+static struct platform_driver iei_wt61p803_puzzle_led_driver = {
+ .driver = {
+ .name = "iei-wt61p803-puzzle-led",
+ .of_match_table = iei_wt61p803_puzzle_led_of_match,
+ },
+ .probe = iei_wt61p803_puzzle_led_probe,
+};
+module_platform_driver(iei_wt61p803_puzzle_led_driver);
+
+MODULE_DESCRIPTION("IEI WT61P803 PUZZLE front panel LED driver");
+MODULE_AUTHOR("Luka Kovacic <luka.kovacic@sartura.hr>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:leds-iei-wt61p803-puzzle");

View File

@ -0,0 +1,82 @@
From 2fab3b4956c5b2f83c1e1abffc1df39de2933d83 Mon Sep 17 00:00:00 2001
From: Luka Kovacic <luka.kovacic () sartura ! hr>
Date: Tue, 24 Aug 2021 12:44:36 +0000
Subject: [PATCH 5/7] Documentation/ABI: Add iei-wt61p803-puzzle driver sysfs
interface documentation
Add the iei-wt61p803-puzzle driver sysfs interface documentation to allow
monitoring and control of the microcontroller from user space.
Signed-off-by: Luka Kovacic <luka.kovacic@sartura.hr>
Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr>
Cc: Luka Perkov <luka.perkov@sartura.hr>
Cc: Robert Marko <robert.marko@sartura.hr>
---
.../testing/sysfs-driver-iei-wt61p803-puzzle | 61 +++++++++++++++++++
1 file changed, 61 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-driver-iei-wt61p803-puzzle
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-iei-wt61p803-puzzle
@@ -0,0 +1,61 @@
+What: /sys/bus/serial/devices/.../mac_address_*
+Date: September 2020
+Contact: Luka Kovacic <luka.kovacic@sartura.hr>
+Description: (RW) Internal factory assigned MAC address values
+
+What: /sys/bus/serial/devices/.../serial_number
+Date: September 2020
+Contact: Luka Kovacic <luka.kovacic@sartura.hr>
+Description: (RW) Internal factory assigned serial number
+
+What: /sys/bus/serial/devices/.../version
+Date: September 2020
+Contact: Luka Kovacic <luka.kovacic@sartura.hr>
+Description: (RO) Internal MCU firmware version
+
+What: /sys/bus/serial/devices/.../protocol_version
+Date: September 2020
+Contact: Luka Kovacic <luka.kovacic@sartura.hr>
+Description: (RO) Internal MCU communication protocol version
+
+What: /sys/bus/serial/devices/.../power_loss_recovery
+Date: September 2020
+Contact: Luka Kovacic <luka.kovacic@sartura.hr>
+Description: (RW) Host platform power loss recovery settings
+ Value mapping: 0 - Always-On, 1 - Always-Off, 2 - Always-AC, 3 - Always-WA
+
+What: /sys/bus/serial/devices/.../bootloader_mode
+Date: September 2020
+Contact: Luka Kovacic <luka.kovacic@sartura.hr>
+Description: (RO) Internal MCU bootloader mode status
+ Value mapping:
+ 0 - normal mode
+ 1 - bootloader mode
+
+What: /sys/bus/serial/devices/.../power_status
+Date: September 2020
+Contact: Luka Kovacic <luka.kovacic@sartura.hr>
+Description: (RO) Power status indicates the host platform power on method.
+ Value mapping (bitwise list):
+ 0x80 - Null
+ 0x40 - Firmware flag
+ 0x20 - Power loss detection flag (powered off)
+ 0x10 - Power loss detection flag (AC mode)
+ 0x08 - Button power on
+ 0x04 - Wake-on-LAN power on
+ 0x02 - RTC alarm power on
+ 0x01 - AC recover power on
+
+What: /sys/bus/serial/devices/.../build_info
+Date: September 2020
+Contact: Luka Kovacic <luka.kovacic@sartura.hr>
+Description: (RO) Internal MCU firmware build date
+ Format: yyyy/mm/dd hh:mm
+
+What: /sys/bus/serial/devices/.../ac_recovery_status
+Date: September 2020
+Contact: Luka Kovacic <luka.kovacic@sartura.hr>
+Description: (RO) Host platform AC recovery status value
+ Value mapping:
+ 0 - board has not been recovered from power down
+ 1 - board has been recovered from power down

View File

@ -0,0 +1,74 @@
From 0aff3e5923fecc6842473ad07a688d6e2f2c2d55 Mon Sep 17 00:00:00 2001
From: Luka Kovacic <luka.kovacic () sartura ! hr>
Date: Tue, 24 Aug 2021 12:44:37 +0000
Subject: [PATCH 6/7] Documentation/hwmon: Add iei-wt61p803-puzzle hwmon driver
documentation
Add the iei-wt61p803-puzzle driver hwmon driver interface documentation.
Signed-off-by: Luka Kovacic <luka.kovacic@sartura.hr>
Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr>
Cc: Luka Perkov <luka.perkov@sartura.hr>
Cc: Robert Marko <robert.marko@sartura.hr>
---
.../hwmon/iei-wt61p803-puzzle-hwmon.rst | 43 +++++++++++++++++++
Documentation/hwmon/index.rst | 1 +
2 files changed, 44 insertions(+)
create mode 100644 Documentation/hwmon/iei-wt61p803-puzzle-hwmon.rst
--- /dev/null
+++ b/Documentation/hwmon/iei-wt61p803-puzzle-hwmon.rst
@@ -0,0 +1,43 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+Kernel driver iei-wt61p803-puzzle-hwmon
+=======================================
+
+Supported chips:
+ * IEI WT61P803 PUZZLE for IEI Puzzle M801
+
+ Prefix: 'iei-wt61p803-puzzle-hwmon'
+
+Author: Luka Kovacic <luka.kovacic@sartura.hr>
+
+
+Description
+-----------
+
+This driver adds fan and temperature sensor reading for some IEI Puzzle
+series boards.
+
+Sysfs attributes
+----------------
+
+The following attributes are supported:
+
+- IEI WT61P803 PUZZLE for IEI Puzzle M801
+
+/sys files in hwmon subsystem
+-----------------------------
+
+================= == =====================================================
+fan[1-5]_input RO files for fan speed (in RPM)
+pwm[1-2] RW files for fan[1-2] target duty cycle (0..255)
+temp[1-2]_input RO files for temperature sensors, in millidegree Celsius
+================= == =====================================================
+
+/sys files in thermal subsystem
+-------------------------------
+
+================= == =====================================================
+cur_state RW file for current cooling state of the cooling device
+ (0..max_state)
+max_state RO file for maximum cooling state of the cooling device
+================= == =====================================================
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -71,6 +71,7 @@ Hardware Monitoring Kernel Drivers
ibmaem
ibm-cffps
ibmpowernv
+ iei-wt61p803-puzzle-hwmon
ina209
ina2xx
ina3221

View File

@ -0,0 +1,41 @@
From 12479baad28d2a08c6cb9e83471057635fa1635c Mon Sep 17 00:00:00 2001
From: Luka Kovacic <luka.kovacic () sartura ! hr>
Date: Tue, 24 Aug 2021 12:44:38 +0000
Subject: [PATCH 7/7] MAINTAINERS: Add an entry for the IEI WT61P803 PUZZLE
driver
Add an entry for the IEI WT61P803 PUZZLE driver (MFD, HWMON, LED drivers).
Signed-off-by: Luka Kovacic <luka.kovacic@sartura.hr>
Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr>
Cc: Luka Perkov <luka.perkov@sartura.hr>
Cc: Robert Marko <robert.marko@sartura.hr>
---
MAINTAINERS | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8538,6 +8538,22 @@ F: include/net/nl802154.h
F: net/ieee802154/
F: net/mac802154/
+IEI WT61P803 M801 MFD DRIVER
+M: Luka Kovacic <luka.kovacic@sartura.hr>
+M: Luka Perkov <luka.perkov@sartura.hr>
+M: Goran Medic <goran.medic@sartura.hr>
+L: linux-kernel@vger.kernel.org
+S: Maintained
+F: Documentation/ABI/stable/sysfs-driver-iei-wt61p803-puzzle
+F: Documentation/devicetree/bindings/hwmon/iei,wt61p803-puzzle-hwmon.yaml
+F: Documentation/devicetree/bindings/leds/iei,wt61p803-puzzle-leds.yaml
+F: Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml
+F: Documentation/hwmon/iei-wt61p803-puzzle-hwmon.rst
+F: drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
+F: drivers/leds/leds-iei-wt61p803-puzzle.c
+F: drivers/mfd/iei-wt61p803-puzzle.c
+F: include/linux/mfd/iei-wt61p803-puzzle.h
+
IFE PROTOCOL
M: Yotam Gigi <yotam.gi@gmail.com>
M: Jamal Hadi Salim <jhs@mojatatu.com>

View File

@ -0,0 +1,247 @@
--- a/drivers/leds/leds-iei-wt61p803-puzzle.c
+++ b/drivers/leds/leds-iei-wt61p803-puzzle.c
@@ -9,10 +9,13 @@
#include <linux/mfd/iei-wt61p803-puzzle.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>
+#define IEI_LEDS_MAX 4
+
enum iei_wt61p803_puzzle_led_state {
IEI_LED_OFF = 0x30,
IEI_LED_ON = 0x31,
@@ -34,6 +37,9 @@ struct iei_wt61p803_puzzle_led {
unsigned char response_buffer[IEI_WT61P803_PUZZLE_BUF_SIZE];
struct mutex lock; /* mutex to protect led_power_state */
int led_power_state;
+ int id;
+ bool blinking;
+ bool active_low;
};
static inline struct iei_wt61p803_puzzle_led *cdev_to_iei_wt61p803_puzzle_led
@@ -51,10 +57,20 @@ static int iei_wt61p803_puzzle_led_brigh
size_t reply_size;
int ret;
+ mutex_lock(&priv->lock);
+ if (priv->blinking) {
+ if (brightness == LED_OFF)
+ priv->blinking = false;
+ else
+ return 0;
+ }
+ mutex_unlock(&priv->lock);
+
led_power_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START;
led_power_cmd[1] = IEI_WT61P803_PUZZLE_CMD_LED;
- led_power_cmd[2] = IEI_WT61P803_PUZZLE_CMD_LED_POWER;
- led_power_cmd[3] = brightness == LED_OFF ? IEI_LED_OFF : IEI_LED_ON;
+ led_power_cmd[2] = IEI_WT61P803_PUZZLE_CMD_LED_SET(priv->id);
+ led_power_cmd[3] = ((brightness == LED_OFF) ^ priv->active_low) ?
+ IEI_LED_OFF : IEI_LED_ON;
ret = iei_wt61p803_puzzle_write_command(priv->mcu, led_power_cmd,
sizeof(led_power_cmd),
@@ -90,39 +106,164 @@ static enum led_brightness iei_wt61p803_
return led_state;
}
+static int iei_wt61p803_puzzle_led_set_blink(struct led_classdev *cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct iei_wt61p803_puzzle_led *priv = cdev_to_iei_wt61p803_puzzle_led(cdev);
+ unsigned char led_blink_cmd[5] = {};
+ unsigned char resp_buf[IEI_WT61P803_PUZZLE_BUF_SIZE];
+ size_t reply_size;
+ int ret = 0;
+
+ /* set defaults */
+ if (!*delay_on && !*delay_off) {
+ *delay_on = 500;
+ *delay_off = 500;
+ }
+
+ /* minimum delay for soft-driven blinking is 50ms to keep load low */
+ if (*delay_on < 50)
+ *delay_on = 50;
+
+ if (*delay_off < 50)
+ *delay_off = 50;
+
+ if (*delay_on != *delay_off)
+ return -EINVAL;
+
+ /* aggressively offload blinking to hardware, if possible */
+ if (*delay_on < 100) {
+ return -EINVAL;
+ } else if (*delay_on < 200) {
+ *delay_on = 100;
+ *delay_off = 100;
+ } else if (*delay_on <= 500) {
+ *delay_on = 500;
+ *delay_off = 500;
+ } else {
+ return -EINVAL;
+ }
+
+ led_blink_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START;
+ led_blink_cmd[1] = IEI_WT61P803_PUZZLE_CMD_LED;
+ led_blink_cmd[2] = IEI_WT61P803_PUZZLE_CMD_LED_SET(priv->id);
+ led_blink_cmd[3] = (*delay_on == 100)?IEI_LED_BLINK_5HZ:IEI_LED_BLINK_1HZ;
+
+ ret = iei_wt61p803_puzzle_write_command(priv->mcu, led_blink_cmd,
+ sizeof(led_blink_cmd),
+ resp_buf,
+ &reply_size);
+
+ if (ret)
+ return ret;
+
+ if (reply_size != 3)
+ return -EIO;
+
+ if (!(resp_buf[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START &&
+ resp_buf[1] == IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK &&
+ resp_buf[2] == IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK))
+ return -EIO;
+
+ mutex_lock(&priv->lock);
+ priv->blinking = true;
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int iei_wt61p803_puzzle_led_set_dt_default(struct led_classdev *cdev,
+ struct device_node *np)
+{
+ const char *state;
+ int ret = 0;
+
+ state = of_get_property(np, "default-state", NULL);
+ if (state) {
+ if (!strcmp(state, "on")) {
+ ret =
+ iei_wt61p803_puzzle_led_brightness_set_blocking(
+ cdev, cdev->max_brightness);
+ } else {
+ ret = iei_wt61p803_puzzle_led_brightness_set_blocking(
+ cdev, LED_OFF);
+ }
+ }
+
+ return ret;
+}
+
static int iei_wt61p803_puzzle_led_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
+ struct device_node *np = dev_of_node(dev);
+ struct device_node *child;
struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev->parent);
struct iei_wt61p803_puzzle_led *priv;
- struct led_init_data init_data = {};
- struct fwnode_handle *child;
int ret;
+ u32 reg;
- if (device_get_child_node_count(dev) != 1)
+ if (device_get_child_node_count(dev) > IEI_LEDS_MAX)
return -EINVAL;
- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
-
- priv->mcu = mcu;
- priv->led_power_state = 1;
- mutex_init(&priv->lock);
- dev_set_drvdata(dev, priv);
-
- child = device_get_next_child_node(dev, NULL);
- init_data.fwnode = child;
-
- priv->cdev.brightness_set_blocking = iei_wt61p803_puzzle_led_brightness_set_blocking;
- priv->cdev.brightness_get = iei_wt61p803_puzzle_led_brightness_get;
- priv->cdev.max_brightness = 1;
+ for_each_available_child_of_node(np, child) {
+ struct led_init_data init_data = {};
- ret = devm_led_classdev_register_ext(dev, &priv->cdev, &init_data);
- if (ret)
- dev_err(dev, "Could not register LED\n");
+ ret = of_property_read_u32(child, "reg", &reg);
+ if (ret) {
+ dev_err(dev, "Failed to read led 'reg' property\n");
+ goto put_child_node;
+ }
+
+ if (reg > IEI_LEDS_MAX) {
+ dev_err(dev, "Invalid led reg %u\n", reg);
+ ret = -EINVAL;
+ goto put_child_node;
+ }
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ ret = -ENOMEM;
+ goto put_child_node;
+ }
+
+ mutex_init(&priv->lock);
+
+ dev_set_drvdata(dev, priv);
+
+ if (of_property_read_bool(child, "active-low"))
+ priv->active_low = true;
+
+ priv->mcu = mcu;
+ priv->id = reg;
+ priv->led_power_state = 1;
+ priv->blinking = false;
+ init_data.fwnode = of_fwnode_handle(child);
+
+ priv->cdev.brightness_set_blocking = iei_wt61p803_puzzle_led_brightness_set_blocking;
+ priv->cdev.brightness_get = iei_wt61p803_puzzle_led_brightness_get;
+ priv->cdev.blink_set = iei_wt61p803_puzzle_led_set_blink;
+
+ priv->cdev.max_brightness = 1;
+
+ ret = iei_wt61p803_puzzle_led_set_dt_default(&priv->cdev, child);
+ if (ret) {
+ dev_err(dev, "Could apply default from DT\n");
+ goto put_child_node;
+ }
+
+ ret = devm_led_classdev_register_ext(dev, &priv->cdev, &init_data);
+ if (ret) {
+ dev_err(dev, "Could not register LED\n");
+ goto put_child_node;
+ }
+ }
+
+ return ret;
- fwnode_handle_put(child);
+put_child_node:
+ of_node_put(child);
return ret;
}
--- a/include/linux/mfd/iei-wt61p803-puzzle.h
+++ b/include/linux/mfd/iei-wt61p803-puzzle.h
@@ -36,7 +36,7 @@
#define IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_POWER_LOSS 0x41 /* A */
#define IEI_WT61P803_PUZZLE_CMD_LED 0x52 /* R */
-#define IEI_WT61P803_PUZZLE_CMD_LED_POWER 0x31 /* 1 */
+#define IEI_WT61P803_PUZZLE_CMD_LED_SET(n) (0x30 | (n))
#define IEI_WT61P803_PUZZLE_CMD_TEMP 0x54 /* T */
#define IEI_WT61P803_PUZZLE_CMD_TEMP_ALL 0x41 /* A */