This patchset adds support for the iEi Puzzle-M801 1U Rackmount Network
Appliance and for the iEi WT61P803 PUZZLE microcontroller, which enables
some board specific features like fan and LED control, system power
management and temperature sensor reading.
The platform is based on the popular Marvell Armada 8040 SoC and supports
up to 16 GB of DDR4 2400 MHz ECC RAM.
It has a PCIe x16 slot (x2 lanes only) and an M.2 type B slot.
External chassis ports:
2x 10 GbE SFP+
4x 1 GbE (Marvell 88E1512P)
2x USB 3.0
1x RJ45 serial port
All notable board components are supported in this patchset.
Changes for v2:
- Use LAAs for local-mac-address and match reg values
- Code styling changes
- Error handling moved to the end of the function
- Define all magic numbers in the main header file
- Convert the driver to make it OF independent
- Refactor hwmon to use devm_hwmon_device_register_with_info()
- Reduce the number of mutex locks
- Allocate memory once for the response buffer
- Reduce managed memory allocations
Luka Kovacic (7):
dt-bindings: Add iEi vendor prefix and iEi WT61P803 PUZZLE driver
bindings
drivers: mfd: Add a driver for iEi WT61P803 PUZZLE MCU
drivers: hwmon: Add the iEi WT61P803 PUZZLE HWMON driver
drivers: leds: Add the iEi WT61P803 PUZZLE LED driver
Documentation/ABI: Add iei-wt61p803-puzzle driver sysfs interface
documentation
MAINTAINERS: Add an entry for the iEi WT61P803 PUZZLE driver
arm64: dts: marvell: Add a device tree for the iEi Puzzle-M801 board
.../stable/sysfs-driver-iei-wt61p803-puzzle | 65 +
.../hwmon/iei,wt61p803-puzzle-hwmon.yaml | 41 +
.../leds/iei,wt61p803-puzzle-leds.yaml | 48 +
.../bindings/mfd/iei,wt61p803-puzzle.yaml | 82 ++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
MAINTAINERS | 13 +
arch/arm64/boot/dts/marvell/Makefile | 1 +
.../dts/marvell/armada-8040-puzzle-m801.dts | 519 ++++++++
drivers/hwmon/Kconfig | 8 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/iei-wt61p803-puzzle-hwmon.c | 511 ++++++++
drivers/leds/Kconfig | 8 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-iei-wt61p803-puzzle.c | 174 +++
drivers/mfd/Kconfig | 8 +
drivers/mfd/Makefile | 1 +
drivers/mfd/iei-wt61p803-puzzle.c | 1069 +++++++++++++++++
include/linux/mfd/iei-wt61p803-puzzle.h | 69 ++
18 files changed, 2621 insertions(+)
create mode 100644 Documentation/ABI/stable/sysfs-driver-iei-wt61p803-puzzle
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
create mode 100644 arch/arm64/boot/dts/marvell/armada-8040-puzzle-m801.dts
create mode 100644 drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
create mode 100644 drivers/leds/leds-iei-wt61p803-puzzle.c
create mode 100644 drivers/mfd/iei-wt61p803-puzzle.c
create mode 100644 include/linux/mfd/iei-wt61p803-puzzle.h
--
2.26.2
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 <[email protected]>
Cc: Luka Perkov <[email protected]>
Cc: Robert Marko <[email protected]>
---
.../hwmon/iei,wt61p803-puzzle-hwmon.yaml | 41 ++++++++++
.../leds/iei,wt61p803-puzzle-leds.yaml | 48 +++++++++++
.../bindings/mfd/iei,wt61p803-puzzle.yaml | 82 +++++++++++++++++++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
4 files changed, 173 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
diff --git a/Documentation/devicetree/bindings/hwmon/iei,wt61p803-puzzle-hwmon.yaml b/Documentation/devicetree/bindings/hwmon/iei,wt61p803-puzzle-hwmon.yaml
new file mode 100644
index 000000000000..37f0030df237
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/iei,wt61p803-puzzle-hwmon.yaml
@@ -0,0 +1,41 @@
+# 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 <[email protected]>
+
+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
+
+patternProperties:
+ "^fan-group@[0-1]$":
+ type: object
+ properties:
+ reg:
+ minimum: 0
+ maximum: 1
+ description:
+ Fan group ID
+ cooling-levels:
+ maxItems: 255
+ description:
+ Cooling levels for the fans (PWM value mapping)
+ description: |
+ Properties for each fan group.
+ required:
+ - reg
+
+required:
+ - compatible
diff --git a/Documentation/devicetree/bindings/leds/iei,wt61p803-puzzle-leds.yaml b/Documentation/devicetree/bindings/leds/iei,wt61p803-puzzle-leds.yaml
new file mode 100644
index 000000000000..502d97630ecc
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/iei,wt61p803-puzzle-leds.yaml
@@ -0,0 +1,48 @@
+# 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 <[email protected]>
+
+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
+
+patternProperties:
+ "^led@0$":
+ type: object
+ description: |
+ Properties for a single LED.
+
+ properties:
+ reg:
+ description:
+ Index of the LED. Only one LED is supported at the moment.
+ minimum: 0
+ maximum: 0
+
+ label: true
+
+ linux,default-trigger: true
+
+required:
+ - compatible
+ - "#address-cells"
+ - "#size-cells"
diff --git a/Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml b/Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml
new file mode 100644
index 000000000000..38846c758372
--- /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 <[email protected]>
+
+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
+
+ iei-wt61p803-hwmon:
+ $ref: ../hwmon/iei,wt61p803-puzzle-hwmon.yaml
+
+ leds:
+ $ref: ../leds/iei,wt61p803-puzzle-leds.yaml
+
+required:
+ - compatible
+ - current-speed
+
+examples:
+ - |
+ #include <dt-bindings/leds/common.h>
+ serial {
+ status = "okay";
+ 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>;
+ color = <LED_COLOR_ID_BLUE>;
+ label = "front-power-led";
+ };
+ };
+
+ iei-wt61p803-puzzle-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>;
+ };
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 63996ab03521..5f2595f0b2ad 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -467,6 +467,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,.*":
--
2.26.2
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 <[email protected]>
Cc: Luka Perkov <[email protected]>
Cc: Robert Marko <[email protected]>
---
drivers/hwmon/Kconfig | 8 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/iei-wt61p803-puzzle-hwmon.c | 511 ++++++++++++++++++++++
3 files changed, 520 insertions(+)
create mode 100644 drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 8dc28b26916e..ff279df9bf40 100644
--- 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
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index a8f4b35b136b..b0afb2d6896f 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -83,6 +83,7 @@ obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o
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
diff --git a/drivers/hwmon/iei-wt61p803-puzzle-hwmon.c b/drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
new file mode 100644
index 000000000000..2691b943936b
--- /dev/null
+++ b/drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
@@ -0,0 +1,511 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* iEi WT61P803 PUZZLE MCU HWMON Driver
+ *
+ * Copyright (C) 2020 Sartura Ltd.
+ * Author: Luka Kovacic <[email protected]>
+ */
+
+#include <linux/err.h>
+#include <linux/hwmon-sysfs.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_TEMP_NUM 2
+#define IEI_WT61P803_PUZZLE_HWMON_MAX_FAN_NUM 5
+#define IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_NUM 2
+#define IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_VAL 255
+
+/**
+ * struct iei_wt61p803_puzzle_thermal_cooling_device - Thermal cooling device instance
+ *
+ * @mcu_hwmon: MCU HWMON struct pointer
+ * @tcdev: Thermal cooling device pointer
+ * @name: Thermal cooling device name
+ * @pwm_channel: PWM channel (0 or 1)
+ * @cooling_levels: Thermal cooling device cooling levels
+ */
+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
+ * @lock General member lock
+ * @response_buffer Global MCU response buffer allocation
+ * @temp_sensor_val: Temperature sensor values
+ * @fan_speed_val: FAN speed (RPM) values
+ * @pwm_val: PWM values (0-255)
+ * @thermal_cooling_dev_present: Per-channel thermal cooling device control
+ * @cdev: Per-channel thermal cooling device private structure
+ */
+struct iei_wt61p803_puzzle_hwmon {
+ struct iei_wt61p803_puzzle *mcu;
+ struct mutex lock;
+ unsigned char *response_buffer;
+ int temp_sensor_val[IEI_WT61P803_PUZZLE_HWMON_MAX_TEMP_NUM];
+ int fan_speed_val[IEI_WT61P803_PUZZLE_HWMON_MAX_FAN_NUM];
+ int pwm_val[IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_NUM];
+ bool thermal_cooling_dev_present[IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_NUM];
+ struct iei_wt61p803_puzzle_thermal_cooling_device
+ *cdev[IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_NUM];
+};
+
+#define raw_temp_to_milidegree_celsius(x) ((int)((x - 0x80)*1000))
+static int iei_wt61p803_puzzle_read_temp_sensor
+(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon, int channel, int *value)
+{
+ int ret;
+ size_t reply_size = 0;
+ 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
+ };
+
+ if (channel > 1 && channel < 0)
+ return -EINVAL;
+
+ 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) {
+ /* Check the number of NTC values (should be 0x32/'2') */
+ if (resp_buf[3] == 0x32) {
+ /* Write values to the struct */
+ mcu_hwmon->temp_sensor_val[0] =
+ raw_temp_to_milidegree_celsius(resp_buf[4]);
+ mcu_hwmon->temp_sensor_val[1] =
+ raw_temp_to_milidegree_celsius(resp_buf[5]);
+ }
+
+ }
+ *value = mcu_hwmon->temp_sensor_val[channel];
+ mutex_unlock(&mcu_hwmon->lock);
+
+ return ret;
+}
+
+#define raw_fan_val_to_rpm(x, y) ((int)(((x)<<8|(y))/2)*60)
+static int iei_wt61p803_puzzle_read_fan_speed
+(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon, int channel, int *value)
+{
+ int ret;
+ size_t reply_size = 0;
+ unsigned char *resp_buf = mcu_hwmon->response_buffer;
+ unsigned char fan_speed_cmd[4] = {
+ IEI_WT61P803_PUZZLE_CMD_HEADER_START,
+ IEI_WT61P803_PUZZLE_CMD_FAN,
+ IEI_WT61P803_PUZZLE_CMD_FAN_RPM_0
+ };
+
+ switch (channel) {
+ case 0:
+ fan_speed_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_RPM_0;
+ break;
+ case 1:
+ fan_speed_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_RPM_1;
+ break;
+ case 2:
+ fan_speed_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_RPM_2;
+ break;
+ case 3:
+ fan_speed_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_RPM_3;
+ break;
+ case 4:
+ fan_speed_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_RPM_4;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ 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)
+ mcu_hwmon->fan_speed_val[channel] = raw_fan_val_to_rpm(resp_buf[3],
+ resp_buf[4]);
+
+ *value = mcu_hwmon->fan_speed_val[channel];
+ mutex_unlock(&mcu_hwmon->lock);
+
+ return 0;
+}
+
+static int iei_wt61p803_puzzle_write_pwm_channel
+(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon, int channel, long pwm_set_val)
+{
+ int ret;
+ size_t reply_size = 0;
+ unsigned char *resp_buf = mcu_hwmon->response_buffer;
+ unsigned char pwm_set_cmd[6] = {
+ IEI_WT61P803_PUZZLE_CMD_HEADER_START,
+ IEI_WT61P803_PUZZLE_CMD_FAN,
+ IEI_WT61P803_PUZZLE_CMD_FAN_PWM_WRITE,
+ IEI_WT61P803_PUZZLE_CMD_FAN_PWM_0,
+ 0x00
+ };
+
+ switch (channel) {
+ case 0:
+ pwm_set_cmd[3] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM_0;
+ break;
+ case 1:
+ pwm_set_cmd[3] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM_1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (pwm_set_val < 0 || pwm_set_val > IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_VAL)
+ return -EINVAL;
+
+ /* Add the PWM value to the command */
+ pwm_set_cmd[4] = (char)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) {
+ /* Store the PWM value */
+ 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)
+ mcu_hwmon->pwm_val[channel] = (int)pwm_set_val;
+ }
+ mutex_unlock(&mcu_hwmon->lock);
+
+ return 0;
+}
+
+static int iei_wt61p803_puzzle_read_pwm_channel
+(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon, int channel, int *value)
+{
+ int ret;
+ size_t reply_size = 0;
+ unsigned char *resp_buf = mcu_hwmon->response_buffer;
+ unsigned char pwm_get_cmd[5] = {
+ IEI_WT61P803_PUZZLE_CMD_HEADER_START,
+ IEI_WT61P803_PUZZLE_CMD_FAN,
+ IEI_WT61P803_PUZZLE_CMD_FAN_PWM_READ,
+ IEI_WT61P803_PUZZLE_CMD_FAN_PWM_0
+ };
+
+ switch (channel) {
+ case 0:
+ pwm_get_cmd[3] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM_0;
+ break;
+ case 1:
+ pwm_get_cmd[3] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM_1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mutex_lock(&mcu_hwmon->lock);
+ ret = iei_wt61p803_puzzle_write_command(mcu_hwmon->mcu, pwm_get_cmd,
+ sizeof(pwm_get_cmd), resp_buf, &reply_size);
+ if (!ret) {
+ /* Store the PWM value */
+ if (resp_buf[2] == IEI_WT61P803_PUZZLE_CMD_FAN_PWM_READ)
+ mcu_hwmon->pwm_val[channel] = (int)resp_buf[3];
+ }
+ *value = mcu_hwmon->pwm_val[channel];
+ mutex_unlock(&mcu_hwmon->lock);
+
+ 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);
+ int ret, value;
+
+ switch (type) {
+ case hwmon_pwm:
+ if (attr != hwmon_pwm_input)
+ return -ENODEV;
+ ret = iei_wt61p803_puzzle_read_pwm_channel(mcu_hwmon, channel, &value);
+ if (ret)
+ return ret;
+ *val = (long)value;
+ return ret;
+ case hwmon_fan:
+ if (attr != hwmon_fan_input)
+ return -ENODEV;
+ ret = iei_wt61p803_puzzle_read_fan_speed(mcu_hwmon, channel, &value);
+ if (ret)
+ return ret;
+ *val = (long)value;
+ return ret;
+ case hwmon_temp:
+ if (attr != hwmon_temp_input)
+ return -ENODEV;
+ ret = iei_wt61p803_puzzle_read_temp_sensor(mcu_hwmon, channel, &value);
+ if (ret)
+ return ret;
+ *val = (long)value;
+ return ret;
+ default:
+ return -ENODEV;
+ }
+}
+
+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);
+
+ switch (type) {
+ case hwmon_pwm:
+ if (attr != hwmon_pwm_input)
+ return -ENODEV;
+ if (mcu_hwmon->thermal_cooling_dev_present[channel]) {
+ /*
+ * The Thermal Framework has already claimed this specific PWM
+ * channel.
+ */
+ return -EBUSY;
+ }
+ return iei_wt61p803_puzzle_write_pwm_channel(mcu_hwmon, channel, val);
+ default:
+ return -ENODEV;
+ }
+}
+
+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:
+ switch (attr) {
+ case hwmon_pwm_input:
+ if (mcu_hwmon->thermal_cooling_dev_present[channel])
+ return 0444;
+ return 0644;
+ default:
+ return 0;
+ }
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_input:
+ return 0444;
+ default:
+ return 0;
+ }
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_input:
+ return 0444;
+ default:
+ return 0;
+ }
+ default:
+ 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;
+
+ int ret, value;
+
+ if (!mcu_hwmon)
+ return -EINVAL;
+
+ ret = iei_wt61p803_puzzle_read_pwm_channel(mcu_hwmon,
+ cdev->pwm_channel, &value);
+ if (ret)
+ return ret;
+
+ *state = (unsigned long)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;
+
+ if (!mcu_hwmon)
+ return -EINVAL;
+
+ 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)
+{
+ u32 pwm_channel;
+ int ret, num_levels;
+
+ struct iei_wt61p803_puzzle_thermal_cooling_device *cdev;
+
+ ret = fwnode_property_read_u32(child, "reg", &pwm_channel);
+ if (ret)
+ return ret;
+
+ mutex_lock(&mcu_hwmon->lock);
+ mcu_hwmon->thermal_cooling_dev_present[pwm_channel] = true;
+ mutex_unlock(&mcu_hwmon->lock);
+
+ num_levels = fwnode_property_read_u8_array(child, "cooling-levels", NULL, 0);
+ if (num_levels > 0) {
+ cdev = devm_kzalloc(dev, sizeof(*cdev), GFP_KERNEL);
+ if (!cdev)
+ return -ENOMEM;
+
+ cdev->cooling_levels = devm_kzalloc(dev, num_levels, 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'");
+ return ret;
+ }
+
+ snprintf(cdev->name, THERMAL_NAME_LENGTH, "iei_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;
+
+ mutex_lock(&mcu_hwmon->lock);
+ mcu_hwmon->cdev[pwm_channel] = cdev;
+ mutex_unlock(&mcu_hwmon->lock);
+ }
+ return 0;
+}
+
+static int iei_wt61p803_puzzle_hwmon_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct fwnode_handle *child;
+ struct iei_wt61p803_puzzle_hwmon *mcu_hwmon;
+ struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev->parent);
+ struct device *hwmon_dev;
+ int ret;
+
+ mcu_hwmon = devm_kzalloc(dev, sizeof(*mcu_hwmon), GFP_KERNEL);
+ if (!mcu_hwmon)
+ return -ENOMEM;
+
+ mcu_hwmon->response_buffer = devm_kzalloc(dev,
+ IEI_WT61P803_PUZZLE_BUF_SIZE, GFP_KERNEL);
+ if (!mcu_hwmon->response_buffer)
+ return -ENOMEM;
+
+ mcu_hwmon->mcu = mcu;
+ mutex_init(&mcu_hwmon->lock);
+ platform_set_drvdata(pdev, mcu_hwmon);
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev,
+ "iei_wt61p803_puzzle",
+ mcu_hwmon,
+ &iei_wt61p803_puzzle_chip_info,
+ NULL);
+
+ /* 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 <[email protected]>");
+MODULE_LICENSE("GPL");
--
2.26.2
Add a driver for the iEi WT61P803 PUZZLE microcontroller, used in some
iEi Puzzle series devices. The microcontroller controls system power,
temperature sensors, fans and LEDs.
This driver implements the core functionality for device communication
over the system serial (serdev bus). It handles MCU messages and the
internal MCU properties. Some properties can be managed over sysfs.
Signed-off-by: Luka Kovacic <[email protected]>
Cc: Luka Perkov <[email protected]>
Cc: Robert Marko <[email protected]>
---
drivers/mfd/Kconfig | 8 +
drivers/mfd/Makefile | 1 +
drivers/mfd/iei-wt61p803-puzzle.c | 1069 +++++++++++++++++++++++
include/linux/mfd/iei-wt61p803-puzzle.h | 69 ++
4 files changed, 1147 insertions(+)
create mode 100644 drivers/mfd/iei-wt61p803-puzzle.c
create mode 100644 include/linux/mfd/iei-wt61p803-puzzle.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 33df0837ab41..b1588845894e 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2118,5 +2118,13 @@ config SGI_MFD_IOC3
If you have an SGI Origin, Octane, or a PCI IOC3 card,
then say Y. Otherwise say N.
+config MFD_IEI_WT61P803_PUZZLE
+ tristate "iEi WT61P803 PUZZLE MCU driver"
+ depends on SERIAL_DEV_BUS
+ help
+ iEi WT61P803 PUZZLE is a system power management microcontroller
+ used for fan control, temperature sensor reading, LED control
+ and system identification.
+
endmenu
endif
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index a60e5f835283..33b88023a68d 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -236,6 +236,7 @@ obj-$(CONFIG_MFD_HI655X_PMIC) += hi655x-pmic.o
obj-$(CONFIG_MFD_DLN2) += dln2.o
obj-$(CONFIG_MFD_RT5033) += rt5033.o
obj-$(CONFIG_MFD_SKY81452) += sky81452.o
+obj-$(CONFIG_MFD_IEI_WT61P803_PUZZLE) += iei-wt61p803-puzzle.o
intel-soc-pmic-objs := intel_soc_pmic_core.o intel_soc_pmic_crc.o
obj-$(CONFIG_INTEL_SOC_PMIC) += intel-soc-pmic.o
diff --git a/drivers/mfd/iei-wt61p803-puzzle.c b/drivers/mfd/iei-wt61p803-puzzle.c
new file mode 100644
index 000000000000..5cba010ac9b9
--- /dev/null
+++ b/drivers/mfd/iei-wt61p803-puzzle.c
@@ -0,0 +1,1069 @@
+// 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 <[email protected]>
+ */
+
+#include <asm/unaligned.h>
+#include <linux/atomic.h>
+#include <linux/delay.h>
+#include <linux/delay.h>
+#include <linux/export.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mfd/iei-wt61p803-puzzle.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/property.h>
+#include <linux/sched.h>
+#include <linux/serdev.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+
+#define IEI_WT61P803_PUZZLE_MAX_COMMAND_LENGTH (20 + 2)
+#define IEI_WT61P803_PUZZLE_RESP_BUF_SIZE 512
+
+/* Use HZ as a timeout value throughout the driver */
+#define IEI_WT61P803_PUZZLE_GENERAL_TIMEOUT HZ
+
+/**
+ * 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;
+};
+
+/**
+ * enum iei_wt61p803_puzzle_reply_state - State of the reply
+ * @FRAME_OK: The frame was completely processed/received
+ * @FRAME_PROCESSING: First bytes were received, but the frame isn't complete
+ * @FRAME_STRUCT_EMPTY: The frame struct is empty, no data was received
+ * @FRAME_TIMEOUT: The frame processing timed out, communication failed
+ *
+ * Describes the general state of the frame that is currently being received.
+ */
+enum iei_wt61p803_puzzle_reply_state {
+ FRAME_OK = 0x00,
+ FRAME_PROCESSING = 0x01,
+ FRAME_STRUCT_EMPTY = 0xFF,
+ FRAME_TIMEOUT = 0xFE
+};
+
+/**
+ * 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;
+ u8 state;
+ 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
+ */
+struct iei_wt61p803_puzzle_mcu_version {
+ const char *version;
+ const char *build_info;
+ bool bootloader_mode;
+ const char *protocol_version;
+ const char *serial_number;
+ const char *mac_address[8];
+};
+
+/**
+ * struct iei_wt61p803_puzzle - iEi WT61P803 PUZZLE MCU Driver
+ *
+ * @serdev: Pointer to underlying serdev device
+ * @kobj: Pointer to kobject (sysfs)
+ * @reply_lock: Reply mutex lock
+ * @bus_lock: Bus 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 kobject *kobj;
+ struct mutex reply_lock;
+ struct mutex bus_lock;
+ struct iei_wt61p803_puzzle_reply *reply;
+ struct iei_wt61p803_puzzle_mcu_version version;
+ struct iei_wt61p803_puzzle_mcu_status status;
+ unsigned char *response_buffer;
+ struct mutex lock;
+};
+
+unsigned char iei_wt61p803_puzzle_checksum(unsigned char *buf, size_t len)
+{
+ unsigned char checksum = 0;
+ unsigned int i;
+
+ for (i = 0; i < len; i++)
+ checksum ^= buf[i];
+
+ return checksum;
+}
+
+static int iei_wt61p803_puzzle_process_resp(struct iei_wt61p803_puzzle *mcu,
+ unsigned char *raw_resp_data, size_t size)
+{
+ struct device *dev = &mcu->serdev->dev;
+
+ unsigned char checksum;
+
+ mutex_lock(&mcu->reply_lock);
+
+ /* 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))) {
+
+ /* Frame header is not correct, check whether to append */
+ if (mcu->reply->state != FRAME_PROCESSING) {
+ dev_err(dev, "Invalid frame header and state (0x%x)", mcu->reply->state);
+ mutex_unlock(&mcu->reply_lock);
+ return -EIO;
+ }
+
+ /* Append the frame to existing data */
+ memcpy(mcu->reply->data+mcu->reply->size, raw_resp_data, size);
+ mcu->reply->size += size;
+ } else {
+ /* Start processing a new frame */
+ memcpy(mcu->reply->data, raw_resp_data, size);
+ mcu->reply->size = size;
+ mcu->reply->state = FRAME_PROCESSING;
+ }
+
+ 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 */
+ mutex_unlock(&mcu->reply_lock);
+ return (int)size;
+ }
+
+ /* Received all the data */
+ mcu->reply->state = FRAME_OK;
+ complete(&mcu->reply->received);
+
+ mutex_unlock(&mcu->reply_lock);
+
+ return (int)size;
+}
+
+static int iei_wt61p803_puzzle_recv_buf(struct serdev_device *serdev,
+ const unsigned char *data, size_t size)
+{
+ struct iei_wt61p803_puzzle *mcu = serdev_device_get_drvdata(serdev);
+ int ret;
+
+ ret = iei_wt61p803_puzzle_process_resp(mcu, (unsigned char *)data, size);
+
+ /* Return the number of processed bytes if function returns error */
+ if (ret < 0)
+ return (int)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, "%s: Command response timed out. Retries: %d", __func__,
+ 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 len = (int)size;
+
+ if (size > IEI_WT61P803_PUZZLE_MAX_COMMAND_LENGTH)
+ return -EINVAL;
+
+ cmd[len - 1] = iei_wt61p803_puzzle_checksum(cmd, size);
+
+ mutex_lock(&mcu->bus_lock);
+ mutex_lock(&mcu->reply_lock);
+
+ if (!mcu->reply) {
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ /* Initialize reply struct */
+ reinit_completion(&mcu->reply->received);
+ mcu->reply->state = FRAME_STRUCT_EMPTY;
+ mcu->reply->size = 0;
+ mutex_unlock(&mcu->reply_lock);
+
+ ret = serdev_device_write(mcu->serdev, cmd, len, IEI_WT61P803_PUZZLE_GENERAL_TIMEOUT);
+
+ if (ret < 0) {
+ mutex_unlock(&mcu->bus_lock);
+ return ret;
+ }
+
+ if (!wait_for_completion_timeout(&mcu->reply->received,
+ IEI_WT61P803_PUZZLE_GENERAL_TIMEOUT)) {
+ dev_err(dev, "Command reply receive timeout\n");
+ mutex_lock(&mcu->reply_lock);
+ reinit_completion(&mcu->reply->received);
+ mcu->reply->state = FRAME_TIMEOUT;
+
+ ret = -ETIMEDOUT;
+ goto exit;
+ }
+
+ mutex_lock(&mcu->reply_lock);
+
+ if (!mcu->reply) {
+ ret = -EFAULT;
+ 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);
+ mutex_unlock(&mcu->bus_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iei_wt61p803_puzzle_write_command);
+
+int iei_wt61p803_puzzle_buzzer(struct iei_wt61p803_puzzle *mcu, bool long_beep)
+{
+ unsigned char buzzer_short_cmd[4] = {
+ IEI_WT61P803_PUZZLE_CMD_HEADER_START,
+ IEI_WT61P803_PUZZLE_CMD_FUNCTION_SINGLE,
+ '2'
+ }; /* Buzzer 0.5 sec */
+ unsigned char buzzer_long_cmd[4] = {
+ IEI_WT61P803_PUZZLE_CMD_HEADER_START,
+ IEI_WT61P803_PUZZLE_CMD_FUNCTION_SINGLE,
+ '3'
+ }; /* Buzzer 1.5 sec */
+ unsigned char *resp_buf = mcu->response_buffer;
+ size_t reply_size = 0;
+ int ret;
+
+ mutex_lock(&mcu->lock);
+ ret = iei_wt61p803_puzzle_write_command(mcu,
+ long_beep ? buzzer_long_cmd : buzzer_short_cmd, 4,
+ 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;
+}
+
+int iei_wt61p803_puzzle_get_version(struct iei_wt61p803_puzzle *mcu)
+{
+ struct device *dev = &mcu->serdev->dev;
+ 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 = 0;
+ 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;
+ }
+ mcu->version.version = devm_kasprintf(dev, GFP_KERNEL, "v%c.%c%c%c",
+ rb[2], rb[3], rb[4], rb[5]);
+
+ 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;
+ }
+ mcu->version.build_info = devm_kasprintf(dev, GFP_KERNEL,
+ "%c%c/%c%c/%c%c%c%c %c%c:%c%c",
+ rb[8], rb[9], rb[6], rb[7], rb[2],
+ rb[3], rb[4], rb[5], 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;
+ }
+ mcu->version.protocol_version = devm_kasprintf(dev, GFP_KERNEL,
+ "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;
+}
+
+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 = 0;
+ 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;
+}
+
+int iei_wt61p803_puzzle_get_serial_number(struct iei_wt61p803_puzzle *mcu)
+{
+ struct device *dev = &mcu->serdev->dev;
+ unsigned char serial_number_cmd[5] = {
+ IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
+ IEI_WT61P803_PUZZLE_CMD_EEPROM_READ,
+ 0x00,
+ 0x24
+ };
+ unsigned char *resp_buf = mcu->response_buffer;
+ size_t reply_size = 0;
+ 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;
+
+ mcu->version.serial_number = devm_kasprintf(dev, GFP_KERNEL, "%.*s",
+ (int)reply_size - 5, resp_buf + 4);
+err:
+ mutex_unlock(&mcu->lock);
+ return ret;
+
+}
+
+int iei_wt61p803_puzzle_write_serial_number(struct iei_wt61p803_puzzle *mcu,
+ unsigned char serial_number[36])
+{
+ struct device *dev = &mcu->serdev->dev;
+ unsigned char serial_number_header[4] = {
+ IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
+ IEI_WT61P803_PUZZLE_CMD_EEPROM_WRITE,
+ 0x00,
+ 0xC
+ };
+ unsigned char *resp_buf = mcu->response_buffer;
+ unsigned char serial_number_cmd[4+12+1]; /* header, serial number chunk, XOR checksum */
+ int ret, sn_counter;
+ size_t reply_size = 0;
+
+ /* The MCU can only handle 22 byte messages, send the S/N in 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, 4);
+ memcpy(serial_number_cmd + 4, serial_number + (0xC) * sn_counter, 0xC);
+
+ serial_number_cmd[sizeof(serial_number_cmd) - 1] = 0;
+
+ 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;
+ }
+ }
+
+ mcu->version.serial_number = devm_kasprintf(dev, GFP_KERNEL, "%.*s",
+ 36, serial_number);
+err:
+ mutex_unlock(&mcu->lock);
+ return ret;
+}
+
+int iei_wt61p803_puzzle_get_mac_addresses(struct iei_wt61p803_puzzle *mcu)
+{
+ struct device *dev = &mcu->serdev->dev;
+ unsigned char mac_address_cmd[5] = {
+ IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
+ IEI_WT61P803_PUZZLE_CMD_EEPROM_READ,
+ 0x00,
+ 0x11
+ };
+ unsigned char *resp_buf = mcu->response_buffer;
+ int ret, mac_counter;
+ size_t reply_size = 0;
+
+ mutex_lock(&mcu->lock);
+ for (mac_counter = 0; mac_counter < 8; mac_counter++) {
+ mac_address_cmd[2] = 0x24 + (0x11) * mac_counter;
+ mac_address_cmd[4] = 0x00;
+
+ ret = iei_wt61p803_puzzle_write_command(mcu, mac_address_cmd,
+ sizeof(mac_address_cmd), resp_buf, &reply_size);
+ if (ret)
+ continue;
+
+ if (reply_size < 22) {
+ ret = -EIO;
+ goto err;
+ }
+
+ mcu->version.mac_address[mac_counter] = devm_kasprintf(dev,
+ GFP_KERNEL, "%.*s", (int)reply_size - 5,
+ resp_buf + 4);
+ }
+err:
+ mutex_unlock(&mcu->lock);
+ return ret;
+}
+
+int iei_wt61p803_puzzle_write_mac_address(struct iei_wt61p803_puzzle *mcu,
+ unsigned char mac_address[17], int mac_address_idx)
+{
+ struct device *dev = &mcu->serdev->dev;
+ unsigned char mac_address_header[4] = {
+ IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
+ IEI_WT61P803_PUZZLE_CMD_EEPROM_WRITE,
+ 0x00,
+ 0x11
+ };
+ unsigned char mac_address_cmd[4+17+1]; /* header, MAC address, XOR checksum*/
+ unsigned char *resp_buf = mcu->response_buffer;
+ size_t reply_size = 0;
+ int ret;
+
+ if (!(mac_address_idx < 8))
+ 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, 4);
+ memcpy(mac_address_cmd + 4, mac_address, 17);
+
+ mac_address_cmd[sizeof(mac_address_cmd) - 1] = 0;
+
+ 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;
+ }
+
+ mcu->version.mac_address[mac_address_idx] = devm_kasprintf(dev,
+ GFP_KERNEL, "%.*s", 17, mac_address);
+err:
+ mutex_unlock(&mcu->lock);
+ return ret;
+}
+
+int iei_wt61p803_puzzle_write_power_loss_recovery(struct iei_wt61p803_puzzle *mcu,
+ int power_loss_recovery_action)
+{
+ unsigned char power_loss_recovery_cmd[5] = {
+ IEI_WT61P803_PUZZLE_CMD_HEADER_START,
+ IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER,
+ IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_POWER_LOSS,
+ '0'
+ };
+ unsigned char *resp_buf = mcu->response_buffer;
+ size_t reply_size = 0;
+ unsigned char cmd_buf[2];
+ int ret;
+
+ if (power_loss_recovery_action < 0 || power_loss_recovery_action > 4)
+ return -EINVAL;
+
+ ret = snprintf(cmd_buf, sizeof(cmd_buf), "%d", power_loss_recovery_action);
+ if (ret < 0)
+ return ret;
+
+ /* Modify the command with the action index */
+ power_loss_recovery_cmd[3] = cmd_buf[0];
+
+ 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 sysfs_container(dev) \
+ (container_of((dev)->kobj.parent, struct device, kobj))
+
+static ssize_t version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct device *dev_container = sysfs_container(dev);
+ struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
+
+ return sprintf(buf, "%s\n", mcu->version.version);
+}
+static DEVICE_ATTR_RO(version);
+
+static ssize_t build_info_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct device *dev_container = sysfs_container(dev);
+ struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
+
+ return sprintf(buf, "%s\n", mcu->version.build_info);
+}
+static DEVICE_ATTR_RO(build_info);
+
+static ssize_t bootloader_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct device *dev_container = sysfs_container(dev);
+ struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
+
+ return sprintf(buf, "%d\n", mcu->version.bootloader_mode);
+}
+static DEVICE_ATTR_RO(bootloader_mode);
+
+static ssize_t protocol_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct device *dev_container = sysfs_container(dev);
+ struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
+
+ return sprintf(buf, "%s\n", mcu->version.protocol_version);
+}
+static DEVICE_ATTR_RO(protocol_version);
+
+static ssize_t serial_number_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct device *dev_container = sysfs_container(dev);
+ struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
+ int ret;
+
+ mutex_lock(&mcu->lock);
+ ret = sprintf(buf, "%s\n", mcu->version.serial_number);
+ mutex_unlock(&mcu->lock);
+
+ return ret;
+}
+static ssize_t serial_number_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct device *dev_container = sysfs_container(dev);
+ struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
+ unsigned char serial_number[36];
+ int ret;
+
+ /* Check whether the serial number is 36 characters long + null */
+ if ((int)count != 36 + 1)
+ return -EINVAL;
+
+ /* Copy 36 characters from the buffer to serial_number */
+ memcpy(serial_number, (unsigned char *)buf, 36);
+
+ ret = iei_wt61p803_puzzle_write_serial_number(mcu, serial_number);
+ if (ret)
+ return ret;
+
+ return count;
+}
+static DEVICE_ATTR_RW(serial_number);
+
+static ssize_t mac_address_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct device *dev_container = sysfs_container(dev);
+ struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
+ int ret;
+
+ mutex_lock(&mcu->lock);
+ if (!strcmp(attr->attr.name, "mac_address_0"))
+ ret = sprintf(buf, "%s\n", mcu->version.mac_address[0]);
+ else if (!strcmp(attr->attr.name, "mac_address_1"))
+ ret = sprintf(buf, "%s\n", mcu->version.mac_address[1]);
+ else if (!strcmp(attr->attr.name, "mac_address_2"))
+ ret = sprintf(buf, "%s\n", mcu->version.mac_address[2]);
+ else if (!strcmp(attr->attr.name, "mac_address_3"))
+ ret = sprintf(buf, "%s\n", mcu->version.mac_address[3]);
+ else if (!strcmp(attr->attr.name, "mac_address_4"))
+ ret = sprintf(buf, "%s\n", mcu->version.mac_address[4]);
+ else if (!strcmp(attr->attr.name, "mac_address_5"))
+ ret = sprintf(buf, "%s\n", mcu->version.mac_address[5]);
+ else if (!strcmp(attr->attr.name, "mac_address_6"))
+ ret = sprintf(buf, "%s\n", mcu->version.mac_address[6]);
+ else if (!strcmp(attr->attr.name, "mac_address_7"))
+ ret = sprintf(buf, "%s\n", mcu->version.mac_address[7]);
+ else
+ ret = sprintf(buf, "\n");
+ mutex_unlock(&mcu->lock);
+
+ return ret;
+}
+static ssize_t mac_address_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct device *dev_container = sysfs_container(dev);
+ struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
+ unsigned char mac_address[17];
+ int ret;
+
+ if ((int)count != 17 + 1)
+ return -EINVAL;
+
+ memcpy(mac_address, (unsigned char *)buf, 17);
+
+ if (!strcmp(attr->attr.name, "mac_address_0"))
+ ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 0);
+ else if (!strcmp(attr->attr.name, "mac_address_1"))
+ ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 1);
+ else if (!strcmp(attr->attr.name, "mac_address_2"))
+ ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 2);
+ else if (!strcmp(attr->attr.name, "mac_address_3"))
+ ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 3);
+ else if (!strcmp(attr->attr.name, "mac_address_4"))
+ ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 4);
+ else if (!strcmp(attr->attr.name, "mac_address_5"))
+ ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 5);
+ else if (!strcmp(attr->attr.name, "mac_address_6"))
+ ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 6);
+ else if (!strcmp(attr->attr.name, "mac_address_7"))
+ ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 7);
+ if (ret)
+ return ret;
+
+ return count;
+}
+static DEVICE_ATTR(mac_address_0, 0644, mac_address_show, mac_address_store);
+static DEVICE_ATTR(mac_address_1, 0644, mac_address_show, mac_address_store);
+static DEVICE_ATTR(mac_address_2, 0644, mac_address_show, mac_address_store);
+static DEVICE_ATTR(mac_address_3, 0644, mac_address_show, mac_address_store);
+static DEVICE_ATTR(mac_address_4, 0644, mac_address_show, mac_address_store);
+static DEVICE_ATTR(mac_address_5, 0644, mac_address_show, mac_address_store);
+static DEVICE_ATTR(mac_address_6, 0644, mac_address_show, mac_address_store);
+static DEVICE_ATTR(mac_address_7, 0644, mac_address_show, mac_address_store);
+
+static ssize_t ac_recovery_status_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct device *dev_container = sysfs_container(dev);
+ struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
+
+ int ret;
+
+ ret = iei_wt61p803_puzzle_get_mcu_status(mcu);
+ if (ret)
+ return ret;
+
+ mutex_lock(&mcu->lock);
+ ret = sprintf(buf, "%x\n", mcu->status.ac_recovery_status_flag);
+ mutex_unlock(&mcu->lock);
+
+ return ret;
+}
+static DEVICE_ATTR_RO(ac_recovery_status);
+
+static ssize_t power_loss_recovery_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct device *dev_container = sysfs_container(dev);
+ struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
+
+ int ret;
+
+ ret = iei_wt61p803_puzzle_get_mcu_status(mcu);
+ if (ret)
+ return ret;
+
+ mutex_lock(&mcu->lock);
+ ret = sprintf(buf, "%x\n", mcu->status.power_loss_recovery);
+ mutex_unlock(&mcu->lock);
+
+ return ret;
+}
+static ssize_t power_loss_recovery_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct device *dev_container = sysfs_container(dev);
+ struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
+
+ int ret;
+ long power_loss_recovery_action = 0;
+
+ ret = kstrtol(buf, 10, &power_loss_recovery_action);
+ if (ret)
+ return ret;
+
+ ret = iei_wt61p803_puzzle_write_power_loss_recovery(mcu,
+ (int)power_loss_recovery_action);
+ if (ret)
+ return ret;
+
+ return count;
+}
+static DEVICE_ATTR_RW(power_loss_recovery);
+
+static ssize_t power_status_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct device *dev_container = sysfs_container(dev);
+ struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
+
+ int ret;
+
+ ret = iei_wt61p803_puzzle_get_mcu_status(mcu);
+ if (ret)
+ return ret;
+
+ mutex_lock(&mcu->lock);
+ ret = sprintf(buf, "%x\n", mcu->status.power_status);
+ mutex_unlock(&mcu->lock);
+
+ return ret;
+}
+static DEVICE_ATTR_RO(power_status);
+
+static struct attribute *iei_wt61p803_puzzle_attrs[] = {
+ &dev_attr_version.attr,
+ &dev_attr_build_info.attr,
+ &dev_attr_bootloader_mode.attr,
+ &dev_attr_protocol_version.attr,
+ &dev_attr_serial_number.attr,
+ &dev_attr_mac_address_0.attr,
+ &dev_attr_mac_address_1.attr,
+ &dev_attr_mac_address_2.attr,
+ &dev_attr_mac_address_3.attr,
+ &dev_attr_mac_address_4.attr,
+ &dev_attr_mac_address_5.attr,
+ &dev_attr_mac_address_6.attr,
+ &dev_attr_mac_address_7.attr,
+ &dev_attr_ac_recovery_status.attr,
+ &dev_attr_power_loss_recovery.attr,
+ &dev_attr_power_status.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(iei_wt61p803_puzzle);
+
+static int iei_wt61p803_puzzle_sysfs_create(struct device *dev,
+ struct iei_wt61p803_puzzle *mcu)
+{
+ int ret;
+
+ mcu->kobj = kobject_create_and_add("iei_wt61p803_puzzle_core", &dev->kobj);
+ if (!mcu->kobj)
+ return -ENOMEM;
+
+ ret = sysfs_create_groups(mcu->kobj, iei_wt61p803_puzzle_groups);
+ if (ret) {
+ kobject_del(mcu->kobj);
+ kobject_put(mcu->kobj);
+ mcu->kobj = NULL;
+ }
+
+ return ret;
+}
+
+static int iei_wt61p803_puzzle_sysfs_remove(struct device *dev,
+ struct iei_wt61p803_puzzle *mcu)
+{
+ /* Remove sysfs groups */
+ sysfs_remove_groups(mcu->kobj, iei_wt61p803_puzzle_groups);
+
+ /* Remove the kobject */
+ kobject_del(mcu->kobj);
+ kobject_put(mcu->kobj);
+ mcu->kobj = NULL;
+
+ 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_info(dev, "Driver baud rate: %d", 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;
+
+ mcu->reply->data = devm_kzalloc(dev, IEI_WT61P803_PUZZLE_RESP_BUF_SIZE,
+ GFP_KERNEL);
+ if (!mcu->reply->data)
+ return -ENOMEM;
+
+ mcu->response_buffer = devm_kzalloc(dev, IEI_WT61P803_PUZZLE_BUF_SIZE,
+ GFP_KERNEL);
+ if (!mcu->response_buffer)
+ return -ENOMEM;
+
+ /* Initialize device struct data */
+ mcu->serdev = serdev;
+ init_completion(&mcu->reply->received);
+ mutex_init(&mcu->reply_lock);
+ mutex_init(&mcu->bus_lock);
+ mutex_init(&mcu->lock);
+
+ /* Setup UART interface */
+ serdev_device_set_drvdata(serdev, mcu);
+ serdev_device_set_client_ops(serdev, &iei_wt61p803_puzzle_serdev_device_ops);
+ ret = devm_serdev_device_open(dev, serdev);
+ if (ret)
+ return ret;
+ serdev_device_set_baudrate(serdev, baud);
+ serdev_device_set_flow_control(serdev, false);
+ ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
+ if (ret) {
+ dev_err(dev, "Failed to set parity");
+ return ret;
+ }
+
+ ret = iei_wt61p803_puzzle_get_version(mcu);
+ if (ret)
+ return ret;
+
+ ret = iei_wt61p803_puzzle_get_mac_addresses(mcu);
+ if (ret)
+ return ret;
+
+ ret = iei_wt61p803_puzzle_get_serial_number(mcu);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "MCU version: %s", mcu->version.version);
+ dev_info(dev, "MCU firmware build info: %s", mcu->version.build_info);
+ dev_info(dev, "MCU in bootloader mode: %s",
+ mcu->version.bootloader_mode ? "true" : "false");
+ dev_info(dev, "MCU protocol version: %s", 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);
+
+ 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");
+MODULE_AUTHOR("Luka Kovacic <[email protected]>");
+MODULE_DESCRIPTION("iEi WT61P803 PUZZLE MCU Driver");
diff --git a/include/linux/mfd/iei-wt61p803-puzzle.h b/include/linux/mfd/iei-wt61p803-puzzle.h
new file mode 100644
index 000000000000..633ceb1d00e3
--- /dev/null
+++ b/include/linux/mfd/iei-wt61p803-puzzle.h
@@ -0,0 +1,69 @@
+/* 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 <[email protected]>
+ */
+
+#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_0 0x30
+#define IEI_WT61P803_PUZZLE_CMD_FAN_PWM_1 0x31
+#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_RPM_0 0x41 /* A */
+#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM_1 0x42 /* B */
+#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM_2 0x43 /* C */
+#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM_3 0x44 /* D */
+#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM_4 0x45 /* E */
+#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM_5 0x46 /* F */
+
+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_ */
--
2.26.2
Add support for the iEi WT61P803 PUZZLE LED driver.
Currently only the front panel power LED is supported.
This driver depends on the iEi WT61P803 PUZZLE MFD driver.
Signed-off-by: Luka Kovacic <[email protected]>
Cc: Luka Perkov <[email protected]>
Cc: Robert Marko <[email protected]>
---
drivers/leds/Kconfig | 8 ++
drivers/leds/Makefile | 1 +
drivers/leds/leds-iei-wt61p803-puzzle.c | 174 ++++++++++++++++++++++++
3 files changed, 183 insertions(+)
create mode 100644 drivers/leds/leds-iei-wt61p803-puzzle.c
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 1c181df24eae..8a25fb753dec 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -332,6 +332,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
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index c2c7d7ade0d0..cd362437fefd 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o
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
diff --git a/drivers/leds/leds-iei-wt61p803-puzzle.c b/drivers/leds/leds-iei-wt61p803-puzzle.c
new file mode 100644
index 000000000000..b9a977575a23
--- /dev/null
+++ b/drivers/leds/leds-iei-wt61p803-puzzle.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* iEi WT61P803 PUZZLE MCU LED Driver
+ *
+ * Copyright (C) 2020 Sartura Ltd.
+ * Author: Luka Kovacic <[email protected]>
+ */
+
+#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
+ *
+ * @mcu: MCU struct pointer
+ * @response_buffer Global MCU response buffer allocation
+ * @lock: General mutex lock for LED operations
+ * @led_power_state: State of the front panel power LED
+ */
+struct iei_wt61p803_puzzle_led {
+ struct iei_wt61p803_puzzle *mcu;
+ unsigned char *response_buffer;
+ struct mutex lock;
+ int led_power_state;
+};
+
+static inline struct iei_wt61p803_puzzle_led *cdev_to_iei_wt61p803_puzzle_led
+ (struct led_classdev *led_cdev)
+{
+ return dev_get_drvdata(led_cdev->dev->parent);
+}
+
+static int iei_wt61p803_puzzle_led_brightness_set_blocking(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct iei_wt61p803_puzzle_led *mcu_led =
+ cdev_to_iei_wt61p803_puzzle_led(cdev);
+ unsigned char led_power_cmd[5] = {
+ IEI_WT61P803_PUZZLE_CMD_HEADER_START,
+ IEI_WT61P803_PUZZLE_CMD_LED,
+ IEI_WT61P803_PUZZLE_CMD_LED_POWER,
+ (char)IEI_LED_OFF
+ };
+ unsigned char *resp_buf = mcu_led->response_buffer;
+ size_t reply_size;
+
+ mutex_lock(&mcu_led->lock);
+ if (brightness == LED_OFF) {
+ led_power_cmd[3] = (char)IEI_LED_OFF;
+ mcu_led->led_power_state = LED_OFF;
+ } else {
+ led_power_cmd[3] = (char)IEI_LED_ON;
+ mcu_led->led_power_state = LED_ON;
+ }
+ mutex_unlock(&mcu_led->lock);
+
+ return iei_wt61p803_puzzle_write_command(mcu_led->mcu, led_power_cmd,
+ sizeof(led_power_cmd), resp_buf, &reply_size);
+}
+
+static enum led_brightness
+iei_wt61p803_puzzle_led_brightness_get(struct led_classdev *cdev)
+{
+ struct iei_wt61p803_puzzle_led *mcu_led =
+ cdev_to_iei_wt61p803_puzzle_led(cdev);
+ int led_state;
+
+ mutex_lock(&mcu_led->lock);
+ led_state = mcu_led->led_power_state;
+ mutex_unlock(&mcu_led->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 *mcu_led;
+ struct fwnode_handle *child;
+ const char *label;
+ int ret;
+
+ mcu_led = devm_kzalloc(dev, sizeof(*mcu_led), GFP_KERNEL);
+ if (!mcu_led)
+ return -ENOMEM;
+
+ mcu_led->response_buffer = devm_kzalloc(dev,
+ IEI_WT61P803_PUZZLE_BUF_SIZE, GFP_KERNEL);
+ if (!mcu_led->response_buffer)
+ return -ENOMEM;
+
+ mcu_led->mcu = mcu;
+ mcu_led->led_power_state = 1;
+ mutex_init(&mcu_led->lock);
+ dev_set_drvdata(dev, mcu_led);
+
+ device_for_each_child_node(dev, child) {
+ struct led_classdev *led;
+ u32 reg;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led) {
+ ret = -ENOMEM;
+ goto err_child_node;
+ }
+
+ ret = fwnode_property_read_u32(child, "reg", ®);
+ if (ret || reg > 1) {
+ dev_err(dev, "Could not register 'reg' (%lu)\n", (unsigned long)reg);
+ ret = -EINVAL;
+ goto err_child_node;
+ }
+
+ if (fwnode_property_read_string(child, "label", &label)) {
+ led->name = "iei-wt61p803-puzzle-led::";
+ } else {
+ led->name = devm_kasprintf(dev, GFP_KERNEL,
+ "iei-wt61p803-puzzle-led:%s", label);
+ if (!led->name) {
+ ret = -ENOMEM;
+ goto err_child_node;
+ }
+ }
+
+ fwnode_property_read_string(child, "linux,default-trigger",
+ &led->default_trigger);
+
+ led->brightness_set_blocking = iei_wt61p803_puzzle_led_brightness_set_blocking;
+ led->brightness_get = iei_wt61p803_puzzle_led_brightness_get;
+ led->max_brightness = 1;
+
+ ret = devm_led_classdev_register(dev, led);
+ if (ret) {
+ dev_err(dev, "Could not register %s\n", led->name);
+ goto err_child_node;
+ }
+ }
+ return 0;
+err_child_node:
+ 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 <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:leds-iei-wt61p803-puzzle");
--
2.26.2
Add initial support for the iEi Puzzle-M801 1U Rackmount Network
Appliance board.
The board is based on the quad-core Marvell Armada 8040 SoC and supports
up to 16 GB of DDR4 2400 MHz ECC RAM. It has a PCIe x16 slot (x2 lanes
only) and an M.2 type B slot.
Main system hardware:
2x USB 3.0
4x Gigabit Ethernet
2x SFP+
1x SATA 3.0
1x M.2 type B
1x RJ45 UART
1x SPI flash
1x iEi WT61P803 PUZZLE Microcontroller
1x EPSON RX8010 RTC (used instead of the integrated Marvell RTC controller)
6x SFP+ LED
1x HDD LED
All of the hardware listed above is supported and tested in this port.
Signed-off-by: Luka Kovacic <[email protected]>
Cc: Luka Perkov <[email protected]>
Cc: Robert Marko <[email protected]>
---
arch/arm64/boot/dts/marvell/Makefile | 1 +
.../dts/marvell/armada-8040-puzzle-m801.dts | 519 ++++++++++++++++++
2 files changed, 520 insertions(+)
create mode 100644 arch/arm64/boot/dts/marvell/armada-8040-puzzle-m801.dts
diff --git a/arch/arm64/boot/dts/marvell/Makefile b/arch/arm64/boot/dts/marvell/Makefile
index 3e5f2e7a040c..e413c3261792 100644
--- a/arch/arm64/boot/dts/marvell/Makefile
+++ b/arch/arm64/boot/dts/marvell/Makefile
@@ -12,6 +12,7 @@ dtb-$(CONFIG_ARCH_MVEBU) += armada-8040-clearfog-gt-8k.dtb
dtb-$(CONFIG_ARCH_MVEBU) += armada-8040-db.dtb
dtb-$(CONFIG_ARCH_MVEBU) += armada-8040-mcbin.dtb
dtb-$(CONFIG_ARCH_MVEBU) += armada-8040-mcbin-singleshot.dtb
+dtb-$(CONFIG_ARCH_MVEBU) += armada-8040-puzzle-m801.dtb
dtb-$(CONFIG_ARCH_MVEBU) += armada-8080-db.dtb
dtb-$(CONFIG_ARCH_MVEBU) += cn9130-db.dtb
dtb-$(CONFIG_ARCH_MVEBU) += cn9131-db.dtb
diff --git a/arch/arm64/boot/dts/marvell/armada-8040-puzzle-m801.dts b/arch/arm64/boot/dts/marvell/armada-8040-puzzle-m801.dts
new file mode 100644
index 000000000000..a0d9f1ffa18f
--- /dev/null
+++ b/arch/arm64/boot/dts/marvell/armada-8040-puzzle-m801.dts
@@ -0,0 +1,519 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright (C) 2016 Marvell Technology Group Ltd.
+ * Copyright (C) 2020 Sartura Ltd.
+ *
+ * Device Tree file for iEi Puzzle-M801
+ */
+
+#include "armada-8040.dtsi"
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/leds/common.h>
+
+/ {
+ model = "iEi-Puzzle-M801";
+ compatible = "marvell,armada8040", "marvell,armada-ap806-quad", "marvell,armada-ap806";
+
+ aliases {
+ ethernet0 = &cp0_eth0;
+ ethernet1 = &cp1_eth0;
+ ethernet2 = &cp0_eth1;
+ ethernet3 = &cp0_eth2;
+ ethernet4 = &cp1_eth1;
+ ethernet5 = &cp1_eth2;
+ };
+
+ chosen {
+ stdout-path = "serial0:115200n8";
+ };
+
+ memory@0 {
+ device_type = "memory";
+ reg = <0x0 0x0 0x0 0x80000000>;
+ };
+
+ /* Regulator labels correspond with schematics */
+ v_3_3: regulator-3-3v {
+ compatible = "regulator-fixed";
+ regulator-name = "v_3_3";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-always-on;
+ status = "okay";
+ };
+
+ v_5v0_usb3_hst_vbus: regulator-usb3-vbus0 {
+ compatible = "regulator-fixed";
+ enable-active-high;
+ gpio = <&cp0_gpio2 15 GPIO_ACTIVE_HIGH>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&cp0_xhci_vbus_pins>;
+ regulator-name = "v_5v0_usb3_hst_vbus";
+ regulator-min-microvolt = <5000000>;
+ regulator-max-microvolt = <5000000>;
+ status = "okay";
+ };
+
+ v_vddo_h: regulator-1-8v {
+ compatible = "regulator-fixed";
+ regulator-name = "v_vddo_h";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-always-on;
+ status = "okay";
+ };
+
+ sfp_cp0_eth0: sfp-cp0-eth0 {
+ compatible = "sff,sfp";
+ i2c-bus = <&sfpplus0_i2c>;
+ los-gpio = <&sfpplus_gpio 11 GPIO_ACTIVE_HIGH>;
+ mod-def0-gpio = <&sfpplus_gpio 10 GPIO_ACTIVE_LOW>;
+ tx-disable-gpio = <&sfpplus_gpio 9 GPIO_ACTIVE_HIGH>;
+ tx-fault-gpio = <&sfpplus_gpio 8 GPIO_ACTIVE_HIGH>;
+ maximum-power-milliwatt = <3000>;
+ };
+
+ sfp_cp1_eth0: sfp-cp1-eth0 {
+ compatible = "sff,sfp";
+ i2c-bus = <&sfpplus1_i2c>;
+ los-gpio = <&sfpplus_gpio 3 GPIO_ACTIVE_HIGH>;
+ mod-def0-gpio = <&sfpplus_gpio 2 GPIO_ACTIVE_LOW>;
+ tx-disable-gpio = <&sfpplus_gpio 1 GPIO_ACTIVE_HIGH>;
+ tx-fault-gpio = <&sfpplus_gpio 0 GPIO_ACTIVE_HIGH>;
+ maximum-power-milliwatt = <3000>;
+ };
+
+ leds {
+ compatible = "gpio-leds";
+ status = "okay";
+ pinctrl-0 = <&cp0_sfpplus_led_pins &cp1_sfpplus_led_pins>;
+ pinctrl-names = "default";
+
+ led0 {
+ function = LED_FUNCTION_STATUS;
+ label = "p2_act";
+ gpios = <&cp1_gpio1 6 GPIO_ACTIVE_LOW>;
+ };
+
+ led1 {
+ function = LED_FUNCTION_STATUS;
+ label = "p1_act";
+ gpios = <&cp1_gpio1 14 GPIO_ACTIVE_LOW>;
+ };
+
+ led2 {
+ function = LED_FUNCTION_STATUS;
+ label = "p2_10g";
+ gpios = <&cp1_gpio1 7 GPIO_ACTIVE_LOW>;
+ };
+
+ led3 {
+ function = LED_FUNCTION_STATUS;
+ label = "p2_1g";
+ gpios = <&cp1_gpio1 8 GPIO_ACTIVE_LOW>;
+ };
+
+ led4 {
+ function = LED_FUNCTION_STATUS;
+ label = "p1_10g";
+ gpios = <&cp1_gpio1 10 GPIO_ACTIVE_LOW>;
+ };
+
+ led5 {
+ function = LED_FUNCTION_STATUS;
+ label = "p1_1g";
+ gpios = <&cp1_gpio1 31 GPIO_ACTIVE_LOW>;
+ };
+
+ led6 {
+ function = LED_FUNCTION_STATUS;
+ linux,default-trigger = "disk-activity";
+ label = "front-hdd-led";
+ gpios = <&cp0_gpio2 22 GPIO_ACTIVE_HIGH>;
+ };
+
+ };
+};
+
+&ap_sdhci0 {
+ bus-width = <8>;
+ /*
+ * Not stable in HS modes - phy needs "more calibration", so add
+ * the "slow-mode" and disable SDR104, SDR50 and DDR50 modes.
+ */
+ marvell,xenon-phy-slow-mode;
+ no-1-8-v;
+ no-sd;
+ no-sdio;
+ non-removable;
+ status = "okay";
+ vqmmc-supply = <&v_vddo_h>;
+};
+
+&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>,
+ <&chassis_fan_group1 64 THERMAL_NO_LIMIT>;
+ };
+ };
+};
+
+&i2c0 {
+ clock-frequency = <100000>;
+ status = "okay";
+
+ rtc@32 {
+ compatible = "epson,rx8010";
+ reg = <0x32>;
+ };
+};
+
+&spi0 {
+ status = "okay";
+ spi-flash@0 {
+ #address-cells = <0x1>;
+ #size-cells = <0x1>;
+ compatible = "jedec,spi-nor";
+ reg = <0x0>;
+ spi-max-frequency = <20000000>;
+ partition@u-boot {
+ label = "u-boot";
+ reg = <0x00000000 0x001f0000>;
+ };
+ partition@u-boot-env {
+ label = "u-boot-env";
+ reg = <0x001f0000 0x00010000>;
+ };
+ partition@ubi1 {
+ label = "ubi1";
+ reg = <0x00200000 0x03f00000>;
+ };
+ partition@ubi2 {
+ label = "ubi2";
+ reg = <0x04100000 0x03f00000>;
+ };
+ };
+};
+
+&uart0 {
+ status = "okay";
+ pinctrl-0 = <&uart0_pins>;
+ pinctrl-names = "default";
+};
+
+&uart1 {
+ status = "okay";
+ /* iEi WT61P803 PUZZLE MCU Controller */
+ 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>;
+ color = <LED_COLOR_ID_BLUE>;
+ label = "front-power-led";
+ };
+ };
+
+ iei-wt61p803-puzzle-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>;
+ };
+
+ chassis_fan_group1:fan-group@1 {
+ #cooling-cells = <2>;
+ reg = <0x01>;
+ cooling-levels = <64 102 170 230 250>;
+ };
+ };
+ };
+};
+
+&cp0_rtc {
+ status = "disabled";
+};
+
+&cp0_i2c0 {
+ clock-frequency = <100000>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&cp0_i2c0_pins>;
+ status = "okay";
+
+ sfpplus_gpio: gpio@21 {
+ compatible = "nxp,pca9555";
+ reg = <0x21>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ };
+
+ eeprom@54 {
+ compatible = "atmel,24c04";
+ reg = <0x54>;
+ };
+};
+
+&cp0_i2c1 {
+ clock-frequency = <100000>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&cp0_i2c1_pins>;
+ status = "okay";
+
+ i2c-switch@70 {
+ compatible = "nxp,pca9544";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x70>;
+
+ sfpplus0_i2c: i2c@0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0>;
+ };
+
+ sfpplus1_i2c: i2c@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <1>;
+ };
+ };
+};
+
+&cp0_uart1 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&cp0_uart1_pins>;
+ status = "okay";
+};
+
+&cp0_mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ status = "okay";
+
+ ge_phy2: ethernet-phy@0 {
+ reg = <0>;
+ };
+
+ ge_phy3: ethernet-phy@1 {
+ reg = <1>;
+ };
+};
+
+&cp0_pcie0 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&cp0_pcie_pins>;
+ num-lanes = <1>;
+ num-viewport = <8>;
+ reset-gpios = <&cp0_gpio2 20 GPIO_ACTIVE_LOW>;
+ ranges = <0x82000000 0x0 0xc0000000 0x0 0xc0000000 0x0 0x20000000>;
+ phys = <&cp0_comphy0 0>;
+ phy-names = "cp0-pcie0-x1-phy";
+ status = "okay";
+};
+
+&cp0_pinctrl {
+ cp0_ge_mdio_pins: ge-mdio-pins {
+ marvell,pins = "mpp32", "mpp34";
+ marvell,function = "ge";
+ };
+ cp0_i2c1_pins: i2c1-pins {
+ marvell,pins = "mpp35", "mpp36";
+ marvell,function = "i2c1";
+ };
+ cp0_i2c0_pins: i2c0-pins {
+ marvell,pins = "mpp37", "mpp38";
+ marvell,function = "i2c0";
+ };
+ cp0_uart1_pins: uart1-pins {
+ marvell,pins = "mpp40", "mpp41";
+ marvell,function = "uart1";
+ };
+ cp0_xhci_vbus_pins: xhci0-vbus-pins {
+ marvell,pins = "mpp47";
+ marvell,function = "gpio";
+ };
+ cp0_pcie_pins: pcie-pins {
+ marvell,pins = "mpp52";
+ marvell,function = "gpio";
+ };
+ cp0_sdhci_pins: sdhci-pins {
+ marvell,pins = "mpp55", "mpp56", "mpp57", "mpp58", "mpp59",
+ "mpp60", "mpp61";
+ marvell,function = "sdio";
+ };
+ cp0_sfpplus_led_pins: sfpplus-led-pins {
+ marvell,pins = "mpp54";
+ marvell,function = "gpio";
+ };
+};
+
+&cp0_ethernet {
+ status = "okay";
+};
+
+&cp0_eth0 {
+ status = "okay";
+ phy-mode = "10gbase-r";
+ phys = <&cp0_comphy4 0>;
+ local-mac-address = [ae 00 00 00 ff 00];
+ sfp = <&sfp_cp0_eth0>;
+ managed = "in-band-status";
+};
+
+&cp0_eth1 {
+ status = "okay";
+ phy = <&ge_phy2>;
+ phy-mode = "sgmii";
+ local-mac-address = [ae 00 00 00 ff 01];
+ phys = <&cp0_comphy3 1>;
+};
+
+&cp0_eth2 {
+ status = "okay";
+ phy-mode = "sgmii";
+ phys = <&cp0_comphy1 2>;
+ local-mac-address = [ae 00 00 00 ff 02];
+ phy = <&ge_phy3>;
+};
+
+&cp0_sata0 {
+ status = "okay";
+
+ sata-port@0 {
+ phys = <&cp0_comphy2 0>;
+ phy-names = "cp0-sata0-0-phy";
+ };
+
+ sata-port@1 {
+ phys = <&cp0_comphy5 1>;
+ phy-names = "cp0-sata0-1-phy";
+ };
+};
+
+&cp0_sdhci0 {
+ broken-cd;
+ bus-width = <4>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&cp0_sdhci_pins>;
+ status = "okay";
+ vqmmc-supply = <&v_3_3>;
+};
+
+&cp0_usb3_0 {
+ status = "okay";
+};
+
+&cp0_usb3_1 {
+ status = "okay";
+};
+
+&cp1_i2c0 {
+ clock-frequency = <100000>;
+ status = "disabled";
+};
+
+&cp1_i2c1 {
+ clock-frequency = <100000>;
+ status = "disabled";
+};
+
+&cp1_rtc {
+ status = "disabled";
+};
+
+&cp1_ethernet {
+ status = "okay";
+};
+
+&cp1_eth0 {
+ status = "okay";
+ phy-mode = "10gbase-r";
+ phys = <&cp1_comphy4 0>;
+ local-mac-address = [ae 00 00 00 ff 03];
+ sfp = <&sfp_cp1_eth0>;
+ managed = "in-band-status";
+};
+
+&cp1_eth1 {
+ status = "okay";
+ phy = <&ge_phy4>;
+ phy-mode = "sgmii";
+ local-mac-address = [ae 00 00 00 ff 04];
+ phys = <&cp1_comphy3 1>;
+};
+
+&cp1_eth2 {
+ status = "okay";
+ phy-mode = "sgmii";
+ local-mac-address = [ae 00 00 00 ff 05];
+ phys = <&cp1_comphy5 2>;
+ phy = <&ge_phy5>;
+};
+
+&cp1_pinctrl {
+ cp1_sfpplus_led_pins: sfpplus-led-pins {
+ marvell,pins = "mpp6", "mpp7", "mpp8", "mpp10", "mpp14", "mpp31";
+ marvell,function = "gpio";
+ };
+};
+
+&cp1_uart0 {
+ status = "disabled";
+};
+
+&cp1_comphy2 {
+ cp1_usbh0_con: connector {
+ compatible = "usb-a-connector";
+ phy-supply = <&v_5v0_usb3_hst_vbus>;
+ };
+};
+
+&cp1_usb3_0 {
+ phys = <&cp1_comphy2 0>;
+ phy-names = "cp1-usb3h0-comphy";
+ status = "okay";
+};
+
+&cp1_mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ status = "okay";
+
+ ge_phy4: ethernet-phy@1 {
+ reg = <1>;
+ };
+ ge_phy5: ethernet-phy@0 {
+ reg = <0>;
+ };
+};
+
+&cp1_pcie0 {
+ num-lanes = <2>;
+ phys = <&cp1_comphy0 0>, <&cp1_comphy1 0>;
+ phy-names = "cp1-pcie0-x2-lane0-phy", "cp1-pcie0-x2-lane1-phy";
+ status = "okay";
+};
--
2.26.2
Add an entry for the iEi WT61P803 PUZZLE driver (MFD, HWMON, LED drivers).
Signed-off-by: Luka Kovacic <[email protected]>
Cc: Luka Perkov <[email protected]>
Cc: Robert Marko <[email protected]>
---
MAINTAINERS | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index d746519253c3..6feacd8ad422 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8460,6 +8460,19 @@ F: include/net/nl802154.h
F: net/ieee802154/
F: net/mac802154/
+IEI WT61P803 M801 MFD DRIVER
+M: Luka Kovacic <[email protected]>
+L: [email protected]
+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: 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 <[email protected]>
M: Jamal Hadi Salim <[email protected]>
--
2.26.2
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 <[email protected]>
Cc: Luka Perkov <[email protected]>
Cc: Robert Marko <[email protected]>
---
.../stable/sysfs-driver-iei-wt61p803-puzzle | 65 +++++++++++++++++++
1 file changed, 65 insertions(+)
create mode 100644 Documentation/ABI/stable/sysfs-driver-iei-wt61p803-puzzle
diff --git a/Documentation/ABI/stable/sysfs-driver-iei-wt61p803-puzzle b/Documentation/ABI/stable/sysfs-driver-iei-wt61p803-puzzle
new file mode 100644
index 000000000000..36fca70d66ef
--- /dev/null
+++ b/Documentation/ABI/stable/sysfs-driver-iei-wt61p803-puzzle
@@ -0,0 +1,65 @@
+What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/mac_address_*
+Date: September 2020
+Contact: Luka Kovacic <[email protected]>
+Description: Read the internal iEi WT61P803 PUZZLE MCU MAC address values.
+ These are factory assigned and can be changed.
+
+What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/serial_number
+Date: September 2020
+Contact: Luka Kovacic <[email protected]>
+Description: Read the internal iEi WT61P803 PUZZLE MCU serial number.
+ This value is factory assigned and can be changed.
+
+What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/version
+Date: September 2020
+Contact: Luka Kovacic <[email protected]>
+Description: Read the internal iEi WT61P803 PUZZLE MCU version.
+ This value is read only.
+
+What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/protocol_version
+Date: September 2020
+Contact: Luka Kovacic <[email protected]>
+Description: Read the internal iEi WT61P803 PUZZLE MCU protocol version.
+ This value is read only.
+
+What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/power_loss_recovery
+Date: September 2020
+Contact: Luka Kovacic <[email protected]>
+Description: Read the iEi WT61P803 PUZZLE MCU power loss recovery value.
+ This value is read write.
+ Value mapping: 0 - Always-On, 1 - Always-Off, 2 - Always-AC, 3 - Always-WA
+
+What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/bootloader_mode
+Date: September 2020
+Contact: Luka Kovacic <[email protected]>
+Description: Read whether the MCU is in bootloader mode.
+ This value is read only.
+
+What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/power_status
+Date: September 2020
+Contact: Luka Kovacic <[email protected]>
+Description: Read the iEi WT61P803 PUZZLE MCU power status. Power status indicates
+ the power on method.
+ This value is read only.
+ 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 - WOL power on
+ 0x02 - RTC alarm power on
+ 0x01 - AC recover power on
+
+What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/build_info
+Date: September 2020
+Contact: Luka Kovacic <[email protected]>
+Description: Read the iEi WT61P803 PUZZLE MCU firmware build date.
+ This value is read only.
+ Format: yyyy/mm/dd hh:mm
+
+What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/ac_recovery_status
+Date: September 2020
+Contact: Luka Kovacic <[email protected]>
+Description: Read the iEi WT61P803 PUZZLE MCU AC recovery status.
+ This value is read only.
--
2.26.2
On Sat, Sep 26, 2020 at 03:55:14PM +0200, Luka Kovacic wrote:
> Add initial support for the iEi Puzzle-M801 1U Rackmount Network
> Appliance board.
>
> The board is based on the quad-core Marvell Armada 8040 SoC and supports
> up to 16 GB of DDR4 2400 MHz ECC RAM. It has a PCIe x16 slot (x2 lanes
> only) and an M.2 type B slot.
>
> Main system hardware:
> 2x USB 3.0
> 4x Gigabit Ethernet
> 2x SFP+
> 1x SATA 3.0
> 1x M.2 type B
> 1x RJ45 UART
> 1x SPI flash
> 1x iEi WT61P803 PUZZLE Microcontroller
> 1x EPSON RX8010 RTC (used instead of the integrated Marvell RTC controller)
> 6x SFP+ LED
> 1x HDD LED
>
> All of the hardware listed above is supported and tested in this port.
>
> Signed-off-by: Luka Kovacic <[email protected]>
> Cc: Luka Perkov <[email protected]>
> Cc: Robert Marko <[email protected]>
I don't know this Marvell SoC too well, but what i see looks O.K.
Reviewed-by: Andrew Lunn <[email protected]>
To get this merged, you probably need to break this patchset up and
send the DT part to Gregory. It could be Lee will take the rest if
there are ACKed by from the LED and HWMON maintainer.
Andrew
On Sat, 26 Sep 2020 15:55:14 +0200
Luka Kovacic <[email protected]> wrote:
> + leds {
> + compatible = "gpio-leds";
> + status = "okay";
> + pinctrl-0 = <&cp0_sfpplus_led_pins &cp1_sfpplus_led_pins>;
> + pinctrl-names = "default";
> +
> + led0 {
> + function = LED_FUNCTION_STATUS;
> + label = "p2_act";
> + gpios = <&cp1_gpio1 6 GPIO_ACTIVE_LOW>;
> + };
There should be a dash in LED node name, please pass this dts via
dt_binding_check
led-0 {
...
};
Also why not add the `color` property to the LED? This is DTS for a
specific device, right?
`label` is obsolete. The LED subsystem creates a name in form
[device:]color:function
If this LED should blink for activity on port 2 (is this an ethernet
port?), the function should be LED_FUNCTION_LAN and function-enumerator
should be <2> (or function should be LED_FUNCTION_ACTIVITY, depending
on how the LED subsystem goes forward with this, but certainly not
LED_FUNCTION_STATUS), and trigger-sources should be set to point to the
ethernet port.
Luka, are you willing to change this once we solve this API properly
in LED subsystem?
> + led6 {
> + function = LED_FUNCTION_STATUS;
> + linux,default-trigger = "disk-activity";
> + label = "front-hdd-led";
> + gpios = <&cp0_gpio2 22 GPIO_ACTIVE_HIGH>;
> + };
led-6. LED_FUNCTION_DISK. `label` deprecated.
> + leds {
> + compatible = "iei,wt61p803-puzzle-leds";
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + led@0 {
> + reg = <0>;
> + color = <LED_COLOR_ID_BLUE>;
> + label = "front-power-led";
> + };
Again, `label` is deprecated. Rather use function =
<LED_FUNCTION_POWER>;
Marek
On Sat, 26 Sep 2020 15:55:08 +0200
Luka Kovacic <[email protected]> wrote:
> diff --git a/Documentation/devicetree/bindings/leds/iei,wt61p803-puzzle-leds.yaml b/Documentation/devicetree/bindings/leds/iei,wt61p803-puzzle-leds.yaml
> new file mode 100644
> index 000000000000..502d97630ecc
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/leds/iei,wt61p803-puzzle-leds.yaml
> @@ -0,0 +1,48 @@
> +# 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 <[email protected]>
> +
> +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
> +
> +patternProperties:
> + "^led@0$":
> + type: object
Here should be a ref to LED common.yaml:
$ref: common.yaml#
> + description: |
> + Properties for a single LED.
> +
> + properties:
> + reg:
> + description:
> + Index of the LED. Only one LED is supported at the moment.
> + minimum: 0
> + maximum: 0
> +
> + label: true
> +
> + linux,default-trigger: true
> +
label is obsolete, linux,default-trigger as well.
> +required:
> + - compatible
> + - "#address-cells"
> + - "#size-cells"
> diff --git a/Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml b/Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml
> new file mode 100644
> index 000000000000..38846c758372
> --- /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 <[email protected]>
> +
> +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
What does this mean? Is this connected via uart? Why not name it
`baud`? I realize that `current-speed` is used in other device trees
(and `baud` as well), but what "current" is this? I don't suppose this
means electric current :)
Have you passed these via dt_binding_check?
Marek
On Sat, 26 Sep 2020 15:55:11 +0200
Luka Kovacic <[email protected]> wrote:
> Add support for the iEi WT61P803 PUZZLE LED driver.
> Currently only the front panel power LED is supported.
>
> This driver depends on the iEi WT61P803 PUZZLE MFD driver.
>
> Signed-off-by: Luka Kovacic <[email protected]>
> Cc: Luka Perkov <[email protected]>
> Cc: Robert Marko <[email protected]>
> ---
> drivers/leds/Kconfig | 8 ++
> drivers/leds/Makefile | 1 +
> drivers/leds/leds-iei-wt61p803-puzzle.c | 174 ++++++++++++++++++++++++
> 3 files changed, 183 insertions(+)
> create mode 100644 drivers/leds/leds-iei-wt61p803-puzzle.c
>
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index 1c181df24eae..8a25fb753dec 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -332,6 +332,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
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index c2c7d7ade0d0..cd362437fefd 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -34,6 +34,7 @@ obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o
> 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
> diff --git a/drivers/leds/leds-iei-wt61p803-puzzle.c b/drivers/leds/leds-iei-wt61p803-puzzle.c
> new file mode 100644
> index 000000000000..b9a977575a23
> --- /dev/null
> +++ b/drivers/leds/leds-iei-wt61p803-puzzle.c
> @@ -0,0 +1,174 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* iEi WT61P803 PUZZLE MCU LED Driver
> + *
> + * Copyright (C) 2020 Sartura Ltd.
> + * Author: Luka Kovacic <[email protected]>
> + */
> +
> +#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
> + *
> + * @mcu: MCU struct pointer
> + * @response_buffer Global MCU response buffer allocation
> + * @lock: General mutex lock for LED operations
> + * @led_power_state: State of the front panel power LED
> + */
> +struct iei_wt61p803_puzzle_led {
> + struct iei_wt61p803_puzzle *mcu;
> + unsigned char *response_buffer;
> + struct mutex lock;
> + int led_power_state;
> +};
> +
> +static inline struct iei_wt61p803_puzzle_led *cdev_to_iei_wt61p803_puzzle_led
> + (struct led_classdev *led_cdev)
> +{
> + return dev_get_drvdata(led_cdev->dev->parent);
> +}
> +
> +static int iei_wt61p803_puzzle_led_brightness_set_blocking(struct led_classdev *cdev,
> + enum led_brightness brightness)
> +{
> + struct iei_wt61p803_puzzle_led *mcu_led =
> + cdev_to_iei_wt61p803_puzzle_led(cdev);
> + unsigned char led_power_cmd[5] = {
> + IEI_WT61P803_PUZZLE_CMD_HEADER_START,
> + IEI_WT61P803_PUZZLE_CMD_LED,
> + IEI_WT61P803_PUZZLE_CMD_LED_POWER,
> + (char)IEI_LED_OFF
> + };
> + unsigned char *resp_buf = mcu_led->response_buffer;
> + size_t reply_size;
> +
> + mutex_lock(&mcu_led->lock);
> + if (brightness == LED_OFF) {
> + led_power_cmd[3] = (char)IEI_LED_OFF;
> + mcu_led->led_power_state = LED_OFF;
> + } else {
> + led_power_cmd[3] = (char)IEI_LED_ON;
> + mcu_led->led_power_state = LED_ON;
> + }
> + mutex_unlock(&mcu_led->lock);
> +
> + return iei_wt61p803_puzzle_write_command(mcu_led->mcu, led_power_cmd,
> + sizeof(led_power_cmd), resp_buf, &reply_size);
> +}
> +
> +static enum led_brightness
> +iei_wt61p803_puzzle_led_brightness_get(struct led_classdev *cdev)
> +{
> + struct iei_wt61p803_puzzle_led *mcu_led =
> + cdev_to_iei_wt61p803_puzzle_led(cdev);
> + int led_state;
> +
> + mutex_lock(&mcu_led->lock);
> + led_state = mcu_led->led_power_state;
> + mutex_unlock(&mcu_led->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 *mcu_led;
> + struct fwnode_handle *child;
> + const char *label;
> + int ret;
> +
> + mcu_led = devm_kzalloc(dev, sizeof(*mcu_led), GFP_KERNEL);
> + if (!mcu_led)
> + return -ENOMEM;
> +
> + mcu_led->response_buffer = devm_kzalloc(dev,
> + IEI_WT61P803_PUZZLE_BUF_SIZE, GFP_KERNEL);
> + if (!mcu_led->response_buffer)
> + return -ENOMEM;
> +
> + mcu_led->mcu = mcu;
> + mcu_led->led_power_state = 1;
> + mutex_init(&mcu_led->lock);
> + dev_set_drvdata(dev, mcu_led);
> +
> + device_for_each_child_node(dev, child) {
> + struct led_classdev *led;
> + u32 reg;
> +
> + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
Avoid multiple allocations.
Please look
at
https://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds.git/commit/?h=for-next&id=4351dceba0ff929f667867f9bb69210f08f61717
To avoid multiple allocations, please use flexible array members.
Does this controller support multiple LEDs? The device tree you
provided only defines one.
If it supports multiple LED:
Rename the mcu_led to mcu_leds, or chip, or (semantically best in this
driver) priv.
Add member
struct led_classdev leds[];
to that structure.
Then allocate by
count = device_get_child_node_count(dev);
priv = devm_kzalloc(dev, struct_size(priv, leds, count), GFP_KERNEL);
If the device supports only one LED, just put
struct led_classdev cdev;
to the private structure of this driver. And don't use
device_for_each_child_node, just check whether there is exactly one
child node (device_get_child_node_count), get it via
child = device_get_next_child_node(dev, NULL);
and after registering the LED
fwnode_handle_put(child);
This was done in:
https://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds.git/commit/?h=for-next&id=e92e7e8aa066904def9a5d584ece7fb6ae512dbd
> + if (!led) {
> + ret = -ENOMEM;
> + goto err_child_node;
> + }
> +
> + ret = fwnode_property_read_u32(child, "reg", ®);
> + if (ret || reg > 1) {
> + dev_err(dev, "Could not register 'reg' (%lu)\n", (unsigned long)reg);
> + ret = -EINVAL;
> + goto err_child_node;
> + }
> +
> + if (fwnode_property_read_string(child, "label", &label)) {
> + led->name = "iei-wt61p803-puzzle-led::";
> + } else {
> + led->name = devm_kasprintf(dev, GFP_KERNEL,
> + "iei-wt61p803-puzzle-led:%s", label);
> + if (!led->name) {
> + ret = -ENOMEM;
> + goto err_child_node;
> + }
> + }
Parsing of label is done by LED core if you use
devm_led_classdev_register_ext. Also, label is obsolete. The LED name
should be composed from color, function and device.
Also please dont pass devicename "iei-wt61p803-puzzle-led" here. We
want to make the LED subsystem derive the device name somehow, and
afterwards we would need to change this. Also the devicename should
refer to the device the LED is triggering to (ie. if the LED is set in
devicetree to trigger on activity on eth0, the devicename should be
eth0 or something, not the name of this driver).
Just remove this code and let devm_led_classdev_register_ext do its
thing.
> +
> + fwnode_property_read_string(child, "linux,default-trigger",
> + &led->default_trigger);
> +
Parsing of linux,default-trigger is done by LED core if you use
devm_led_classdev_register_ext.
> + led->brightness_set_blocking = iei_wt61p803_puzzle_led_brightness_set_blocking;
> + led->brightness_get = iei_wt61p803_puzzle_led_brightness_get;
> + led->max_brightness = 1;
> +
> + ret = devm_led_classdev_register(dev, led);
Please use extended LED registration API, with
devm_led_classdev_register_ext. Pass init_data with fwnode member set
to child.
> + if (ret) {
> + dev_err(dev, "Could not register %s\n", led->name);
> + goto err_child_node;
> + }
> + }
> + return 0;
> +err_child_node:
> + 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 <[email protected]>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:leds-iei-wt61p803-puzzle");
Marek
On Sat, 26 Sep 2020 15:55:12 +0200
Luka Kovacic <[email protected]> wrote:
> 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 <[email protected]>
> Cc: Luka Perkov <[email protected]>
> Cc: Robert Marko <[email protected]>
> ---
> .../stable/sysfs-driver-iei-wt61p803-puzzle | 65 +++++++++++++++++++
> 1 file changed, 65 insertions(+)
> create mode 100644 Documentation/ABI/stable/sysfs-driver-iei-wt61p803-puzzle
>
> diff --git a/Documentation/ABI/stable/sysfs-driver-iei-wt61p803-puzzle b/Documentation/ABI/stable/sysfs-driver-iei-wt61p803-puzzle
> new file mode 100644
> index 000000000000..36fca70d66ef
> --- /dev/null
> +++ b/Documentation/ABI/stable/sysfs-driver-iei-wt61p803-puzzle
I think this should go to testing, not stable. It should go to stable
only after it is stable for some time.
> @@ -0,0 +1,65 @@
> +What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/mac_address_*
> +Date: September 2020
> +Contact: Luka Kovacic <[email protected]>
> +Description: Read the internal iEi WT61P803 PUZZLE MCU MAC address values.
> + These are factory assigned and can be changed.
> +
> +What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/serial_number
> +Date: September 2020
> +Contact: Luka Kovacic <[email protected]>
> +Description: Read the internal iEi WT61P803 PUZZLE MCU serial number.
> + This value is factory assigned and can be changed.
> +
Please use (RO) and (RW) prefixes before the Description, instead of
writing "This value is read only", i.e.:
Description: (RO) Internal ... serial number.
JFI: Why can these values be changed? Shouldn't they be burned into OTP?
Marek
> +What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/version
> +Date: September 2020
> +Contact: Luka Kovacic <[email protected]>
> +Description: Read the internal iEi WT61P803 PUZZLE MCU version.
> + This value is read only.
> +
> +What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/protocol_version
> +Date: September 2020
> +Contact: Luka Kovacic <[email protected]>
> +Description: Read the internal iEi WT61P803 PUZZLE MCU protocol version.
> + This value is read only.
> +
> +What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/power_loss_recovery
> +Date: September 2020
> +Contact: Luka Kovacic <[email protected]>
> +Description: Read the iEi WT61P803 PUZZLE MCU power loss recovery value.
> + This value is read write.
> + Value mapping: 0 - Always-On, 1 - Always-Off, 2 - Always-AC, 3 - Always-WA
> +
> +What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/bootloader_mode
> +Date: September 2020
> +Contact: Luka Kovacic <[email protected]>
> +Description: Read whether the MCU is in bootloader mode.
> + This value is read only.
> +
> +What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/power_status
> +Date: September 2020
> +Contact: Luka Kovacic <[email protected]>
> +Description: Read the iEi WT61P803 PUZZLE MCU power status. Power status indicates
> + the power on method.
> + This value is read only.
> + 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 - WOL power on
> + 0x02 - RTC alarm power on
> + 0x01 - AC recover power on
> +
> +What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/build_info
> +Date: September 2020
> +Contact: Luka Kovacic <[email protected]>
> +Description: Read the iEi WT61P803 PUZZLE MCU firmware build date.
> + This value is read only.
> + Format: yyyy/mm/dd hh:mm
> +
> +What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/ac_recovery_status
> +Date: September 2020
> +Contact: Luka Kovacic <[email protected]>
> +Description: Read the iEi WT61P803 PUZZLE MCU AC recovery status.
> + This value is read only.
Signed-off-by: kernel test robot <[email protected]>
---
iei-wt61p803-puzzle.c | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/drivers/mfd/iei-wt61p803-puzzle.c b/drivers/mfd/iei-wt61p803-puzzle.c
index 5cba010ac9b963..f803222fc8e56f 100644
--- a/drivers/mfd/iei-wt61p803-puzzle.c
+++ b/drivers/mfd/iei-wt61p803-puzzle.c
@@ -118,7 +118,7 @@ struct iei_wt61p803_puzzle {
struct mutex lock;
};
-unsigned char iei_wt61p803_puzzle_checksum(unsigned char *buf, size_t len)
+static unsigned char iei_wt61p803_puzzle_checksum(unsigned char *buf, size_t len)
{
unsigned char checksum = 0;
unsigned int i;
@@ -302,7 +302,7 @@ int iei_wt61p803_puzzle_write_command(struct iei_wt61p803_puzzle *mcu,
}
EXPORT_SYMBOL_GPL(iei_wt61p803_puzzle_write_command);
-int iei_wt61p803_puzzle_buzzer(struct iei_wt61p803_puzzle *mcu, bool long_beep)
+static int iei_wt61p803_puzzle_buzzer(struct iei_wt61p803_puzzle *mcu, bool long_beep)
{
unsigned char buzzer_short_cmd[4] = {
IEI_WT61P803_PUZZLE_CMD_HEADER_START,
@@ -341,7 +341,7 @@ int iei_wt61p803_puzzle_buzzer(struct iei_wt61p803_puzzle *mcu, bool long_beep)
return ret;
}
-int iei_wt61p803_puzzle_get_version(struct iei_wt61p803_puzzle *mcu)
+static int iei_wt61p803_puzzle_get_version(struct iei_wt61p803_puzzle *mcu)
{
struct device *dev = &mcu->serdev->dev;
unsigned char version_cmd[3] = {
@@ -420,7 +420,7 @@ int iei_wt61p803_puzzle_get_version(struct iei_wt61p803_puzzle *mcu)
return ret;
}
-int iei_wt61p803_puzzle_get_mcu_status(struct iei_wt61p803_puzzle *mcu)
+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,
@@ -469,7 +469,7 @@ int iei_wt61p803_puzzle_get_mcu_status(struct iei_wt61p803_puzzle *mcu)
return ret;
}
-int iei_wt61p803_puzzle_get_serial_number(struct iei_wt61p803_puzzle *mcu)
+static int iei_wt61p803_puzzle_get_serial_number(struct iei_wt61p803_puzzle *mcu)
{
struct device *dev = &mcu->serdev->dev;
unsigned char serial_number_cmd[5] = {
@@ -496,7 +496,7 @@ int iei_wt61p803_puzzle_get_serial_number(struct iei_wt61p803_puzzle *mcu)
}
-int iei_wt61p803_puzzle_write_serial_number(struct iei_wt61p803_puzzle *mcu,
+static int iei_wt61p803_puzzle_write_serial_number(struct iei_wt61p803_puzzle *mcu,
unsigned char serial_number[36])
{
struct device *dev = &mcu->serdev->dev;
@@ -544,7 +544,7 @@ int iei_wt61p803_puzzle_write_serial_number(struct iei_wt61p803_puzzle *mcu,
return ret;
}
-int iei_wt61p803_puzzle_get_mac_addresses(struct iei_wt61p803_puzzle *mcu)
+static int iei_wt61p803_puzzle_get_mac_addresses(struct iei_wt61p803_puzzle *mcu)
{
struct device *dev = &mcu->serdev->dev;
unsigned char mac_address_cmd[5] = {
@@ -581,7 +581,7 @@ int iei_wt61p803_puzzle_get_mac_addresses(struct iei_wt61p803_puzzle *mcu)
return ret;
}
-int iei_wt61p803_puzzle_write_mac_address(struct iei_wt61p803_puzzle *mcu,
+static int iei_wt61p803_puzzle_write_mac_address(struct iei_wt61p803_puzzle *mcu,
unsigned char mac_address[17], int mac_address_idx)
{
struct device *dev = &mcu->serdev->dev;
@@ -630,7 +630,7 @@ int iei_wt61p803_puzzle_write_mac_address(struct iei_wt61p803_puzzle *mcu,
return ret;
}
-int iei_wt61p803_puzzle_write_power_loss_recovery(struct iei_wt61p803_puzzle *mcu,
+static int iei_wt61p803_puzzle_write_power_loss_recovery(struct iei_wt61p803_puzzle *mcu,
int power_loss_recovery_action)
{
unsigned char power_loss_recovery_cmd[5] = {
On Sat, 26 Sep 2020 15:55:09 +0200
Luka Kovacic <[email protected]> wrote:
> Add a driver for the iEi WT61P803 PUZZLE microcontroller, used in some
> iEi Puzzle series devices. The microcontroller controls system power,
> temperature sensors, fans and LEDs.
>
> This driver implements the core functionality for device communication
> over the system serial (serdev bus). It handles MCU messages and the
> internal MCU properties. Some properties can be managed over sysfs.
>
Hi Luka,
this patch produces some checkpatch warnings, some of them are
reasonable.
My thought below:
> Signed-off-by: Luka Kovacic <[email protected]>
> Cc: Luka Perkov <[email protected]>
> Cc: Robert Marko <[email protected]>
> ---
> drivers/mfd/Kconfig | 8 +
> drivers/mfd/Makefile | 1 +
> drivers/mfd/iei-wt61p803-puzzle.c | 1069 +++++++++++++++++++++++
> include/linux/mfd/iei-wt61p803-puzzle.h | 69 ++
> 4 files changed, 1147 insertions(+)
> create mode 100644 drivers/mfd/iei-wt61p803-puzzle.c
> create mode 100644 include/linux/mfd/iei-wt61p803-puzzle.h
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 33df0837ab41..b1588845894e 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -2118,5 +2118,13 @@ config SGI_MFD_IOC3
> If you have an SGI Origin, Octane, or a PCI IOC3 card,
> then say Y. Otherwise say N.
>
> +config MFD_IEI_WT61P803_PUZZLE
> + tristate "iEi WT61P803 PUZZLE MCU driver"
> + depends on SERIAL_DEV_BUS
> + help
> + iEi WT61P803 PUZZLE is a system power management microcontroller
> + used for fan control, temperature sensor reading, LED control
> + and system identification.
> +
> endmenu
> endif
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index a60e5f835283..33b88023a68d 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -236,6 +236,7 @@ obj-$(CONFIG_MFD_HI655X_PMIC) += hi655x-pmic.o
> obj-$(CONFIG_MFD_DLN2) += dln2.o
> obj-$(CONFIG_MFD_RT5033) += rt5033.o
> obj-$(CONFIG_MFD_SKY81452) += sky81452.o
> +obj-$(CONFIG_MFD_IEI_WT61P803_PUZZLE) += iei-wt61p803-puzzle.o
>
> intel-soc-pmic-objs := intel_soc_pmic_core.o intel_soc_pmic_crc.o
> obj-$(CONFIG_INTEL_SOC_PMIC) += intel-soc-pmic.o
> diff --git a/drivers/mfd/iei-wt61p803-puzzle.c b/drivers/mfd/iei-wt61p803-puzzle.c
> new file mode 100644
> index 000000000000..5cba010ac9b9
> --- /dev/null
> +++ b/drivers/mfd/iei-wt61p803-puzzle.c
> @@ -0,0 +1,1069 @@
> +// 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 <[email protected]>
> + */
> +
> +#include <asm/unaligned.h>
> +#include <linux/atomic.h>
> +#include <linux/delay.h>
> +#include <linux/delay.h>
> +#include <linux/export.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/iei-wt61p803-puzzle.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/property.h>
> +#include <linux/sched.h>
> +#include <linux/serdev.h>
> +#include <linux/slab.h>
> +#include <linux/sysfs.h>
> +
> +#define IEI_WT61P803_PUZZLE_MAX_COMMAND_LENGTH (20 + 2)
> +#define IEI_WT61P803_PUZZLE_RESP_BUF_SIZE 512
> +
> +/* Use HZ as a timeout value throughout the driver */
> +#define IEI_WT61P803_PUZZLE_GENERAL_TIMEOUT HZ
> +
> +/**
> + * 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;
> +};
> +
> +/**
> + * enum iei_wt61p803_puzzle_reply_state - State of the reply
> + * @FRAME_OK: The frame was completely processed/received
> + * @FRAME_PROCESSING: First bytes were received, but the frame isn't complete
> + * @FRAME_STRUCT_EMPTY: The frame struct is empty, no data was received
> + * @FRAME_TIMEOUT: The frame processing timed out, communication failed
> + *
> + * Describes the general state of the frame that is currently being received.
> + */
> +enum iei_wt61p803_puzzle_reply_state {
> + FRAME_OK = 0x00,
> + FRAME_PROCESSING = 0x01,
> + FRAME_STRUCT_EMPTY = 0xFF,
> + FRAME_TIMEOUT = 0xFE
> +};
> +
> +/**
> + * 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;
> + u8 state;
> + 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
> + */
> +struct iei_wt61p803_puzzle_mcu_version {
> + const char *version;
> + const char *build_info;
> + bool bootloader_mode;
> + const char *protocol_version;
> + const char *serial_number;
> + const char *mac_address[8];
You use devm_kasprintf below for these, but such variables have limited
and often small length. The mac_address array could for example be:
char mac_address[8][18];
Can serial number be longer than 32 bytes? What about the other members?
I believe it would be better, to avoid unnecesarry allocations, to
change this.
> +};
> +
> +/**
> + * struct iei_wt61p803_puzzle - iEi WT61P803 PUZZLE MCU Driver
> + *
> + * @serdev: Pointer to underlying serdev device
> + * @kobj: Pointer to kobject (sysfs)
> + * @reply_lock: Reply mutex lock
> + * @bus_lock: Bus 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 kobject *kobj;
> + struct mutex reply_lock;
> + struct mutex bus_lock;
> + struct iei_wt61p803_puzzle_reply *reply;
> + struct iei_wt61p803_puzzle_mcu_version version;
> + struct iei_wt61p803_puzzle_mcu_status status;
> + unsigned char *response_buffer;
> + struct mutex lock;
> +};
> +
> +unsigned char iei_wt61p803_puzzle_checksum(unsigned char *buf, size_t len)
> +{
> + unsigned char checksum = 0;
> + unsigned int i;
> +
> + for (i = 0; i < len; i++)
> + checksum ^= buf[i];
> +
> + return checksum;
> +}
This function should be static.
And maybe for the whole driver: s/unsigned char/u8/
> +
> +static int iei_wt61p803_puzzle_process_resp(struct iei_wt61p803_puzzle *mcu,
> + unsigned char *raw_resp_data, size_t size)
> +{
> + struct device *dev = &mcu->serdev->dev;
> +
> + unsigned char checksum;
Empty line between declarations?
> +
> + mutex_lock(&mcu->reply_lock);
> +
> + /* 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))) {
> +
> + /* Frame header is not correct, check whether to append */
> + if (mcu->reply->state != FRAME_PROCESSING) {
> + dev_err(dev, "Invalid frame header and state (0x%x)", mcu->reply->state);
> + mutex_unlock(&mcu->reply_lock);
> + return -EIO;
> + }
> +
> + /* Append the frame to existing data */
> + memcpy(mcu->reply->data+mcu->reply->size, raw_resp_data, size);
> + mcu->reply->size += size;
> + } else {
> + /* Start processing a new frame */
> + memcpy(mcu->reply->data, raw_resp_data, size);
> + mcu->reply->size = size;
> + mcu->reply->state = FRAME_PROCESSING;
> + }
> +
> + 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 */
> + mutex_unlock(&mcu->reply_lock);
> + return (int)size;
> + }
> +
> + /* Received all the data */
> + mcu->reply->state = FRAME_OK;
> + complete(&mcu->reply->received);
> +
> + mutex_unlock(&mcu->reply_lock);
> +
> + return (int)size;
> +}
> +
> +static int iei_wt61p803_puzzle_recv_buf(struct serdev_device *serdev,
> + const unsigned char *data, size_t size)
> +{
> + struct iei_wt61p803_puzzle *mcu = serdev_device_get_drvdata(serdev);
> + int ret;
> +
> + ret = iei_wt61p803_puzzle_process_resp(mcu, (unsigned char *)data, size);
> +
> + /* Return the number of processed bytes if function returns error */
> + if (ret < 0)
> + return (int)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, "%s: Command response timed out. Retries: %d", __func__,
> + retry_count);
The indentation here is weird, I would put the retry_count at the
position one to the right after dev_err(. But this is not important.
> +
> + 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 len = (int)size;
Some kernel maintainers like reverse christmas tree order of variable
declaration. I find it better to read. If you would like to try:
struct device *dev = &mcu->serdev->dev;
int len = (int)size;
int ret;
> +
> + if (size > IEI_WT61P803_PUZZLE_MAX_COMMAND_LENGTH)
> + return -EINVAL;
> +
> + cmd[len - 1] = iei_wt61p803_puzzle_checksum(cmd, size);
> +
> + mutex_lock(&mcu->bus_lock);
> + mutex_lock(&mcu->reply_lock);
> +
> + if (!mcu->reply) {
> + ret = -EFAULT;
> + goto exit;
> + }
> +
> + /* Initialize reply struct */
> + reinit_completion(&mcu->reply->received);
> + mcu->reply->state = FRAME_STRUCT_EMPTY;
> + mcu->reply->size = 0;
> + mutex_unlock(&mcu->reply_lock);
> +
> + ret = serdev_device_write(mcu->serdev, cmd, len, IEI_WT61P803_PUZZLE_GENERAL_TIMEOUT);
> +
> + if (ret < 0) {
> + mutex_unlock(&mcu->bus_lock);
> + return ret;
> + }
> +
> + if (!wait_for_completion_timeout(&mcu->reply->received,
> + IEI_WT61P803_PUZZLE_GENERAL_TIMEOUT)) {
> + dev_err(dev, "Command reply receive timeout\n");
> + mutex_lock(&mcu->reply_lock);
> + reinit_completion(&mcu->reply->received);
> + mcu->reply->state = FRAME_TIMEOUT;
> +
> + ret = -ETIMEDOUT;
> + goto exit;
> + }
> +
> + mutex_lock(&mcu->reply_lock);
> +
> + if (!mcu->reply) {
> + ret = -EFAULT;
> + 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);
> + mutex_unlock(&mcu->bus_lock);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(iei_wt61p803_puzzle_write_command);
> +
> +int iei_wt61p803_puzzle_buzzer(struct iei_wt61p803_puzzle *mcu, bool long_beep)
> +{
> + unsigned char buzzer_short_cmd[4] = {
> + IEI_WT61P803_PUZZLE_CMD_HEADER_START,
> + IEI_WT61P803_PUZZLE_CMD_FUNCTION_SINGLE,
> + '2'
> + }; /* Buzzer 0.5 sec */
> + unsigned char buzzer_long_cmd[4] = {
> + IEI_WT61P803_PUZZLE_CMD_HEADER_START,
> + IEI_WT61P803_PUZZLE_CMD_FUNCTION_SINGLE,
> + '3'
> + }; /* Buzzer 1.5 sec */
> + unsigned char *resp_buf = mcu->response_buffer;
> + size_t reply_size = 0;
> + int ret;
> +
> + mutex_lock(&mcu->lock);
> + ret = iei_wt61p803_puzzle_write_command(mcu,
> + long_beep ? buzzer_long_cmd : buzzer_short_cmd, 4,
> + 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)) {
Again weird indentation here, I would do
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;
> +}
> +
> +int iei_wt61p803_puzzle_get_version(struct iei_wt61p803_puzzle *mcu)
> +{
> + struct device *dev = &mcu->serdev->dev;
> + 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 = 0;
> + 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;
> + }
> + mcu->version.version = devm_kasprintf(dev, GFP_KERNEL, "v%c.%c%c%c",
> + rb[2], rb[3], rb[4], rb[5]);
> +
> + 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;
> + }
> + mcu->version.build_info = devm_kasprintf(dev, GFP_KERNEL,
> + "%c%c/%c%c/%c%c%c%c %c%c:%c%c",
> + rb[8], rb[9], rb[6], rb[7], rb[2],
> + rb[3], rb[4], rb[5], 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;
> + }
> + mcu->version.protocol_version = devm_kasprintf(dev, GFP_KERNEL,
> + "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;
> +}
> +
> +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 = 0;
> + 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;
> +}
> +
> +int iei_wt61p803_puzzle_get_serial_number(struct iei_wt61p803_puzzle *mcu)
> +{
> + struct device *dev = &mcu->serdev->dev;
> + unsigned char serial_number_cmd[5] = {
> + IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
> + IEI_WT61P803_PUZZLE_CMD_EEPROM_READ,
> + 0x00,
> + 0x24
> + };
> + unsigned char *resp_buf = mcu->response_buffer;
> + size_t reply_size = 0;
> + 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;
> +
> + mcu->version.serial_number = devm_kasprintf(dev, GFP_KERNEL, "%.*s",
> + (int)reply_size - 5, resp_buf + 4);
> +err:
> + mutex_unlock(&mcu->lock);
> + return ret;
> +
> +}
> +
> +int iei_wt61p803_puzzle_write_serial_number(struct iei_wt61p803_puzzle *mcu,
> + unsigned char serial_number[36])
> +{
> + struct device *dev = &mcu->serdev->dev;
> + unsigned char serial_number_header[4] = {
> + IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
> + IEI_WT61P803_PUZZLE_CMD_EEPROM_WRITE,
> + 0x00,
> + 0xC
> + };
> + unsigned char *resp_buf = mcu->response_buffer;
> + unsigned char serial_number_cmd[4+12+1]; /* header, serial number chunk, XOR checksum */
> + int ret, sn_counter;
> + size_t reply_size = 0;
> +
> + /* The MCU can only handle 22 byte messages, send the S/N in 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, 4);
> + memcpy(serial_number_cmd + 4, serial_number + (0xC) * sn_counter, 0xC);
> +
> + serial_number_cmd[sizeof(serial_number_cmd) - 1] = 0;
> +
> + 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;
> + }
> + }
> +
> + mcu->version.serial_number = devm_kasprintf(dev, GFP_KERNEL, "%.*s",
> + 36, serial_number);
> +err:
> + mutex_unlock(&mcu->lock);
> + return ret;
> +}
> +
> +int iei_wt61p803_puzzle_get_mac_addresses(struct iei_wt61p803_puzzle *mcu)
> +{
> + struct device *dev = &mcu->serdev->dev;
> + unsigned char mac_address_cmd[5] = {
> + IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
> + IEI_WT61P803_PUZZLE_CMD_EEPROM_READ,
> + 0x00,
> + 0x11
> + };
> + unsigned char *resp_buf = mcu->response_buffer;
> + int ret, mac_counter;
> + size_t reply_size = 0;
> +
> + mutex_lock(&mcu->lock);
> + for (mac_counter = 0; mac_counter < 8; mac_counter++) {
> + mac_address_cmd[2] = 0x24 + (0x11) * mac_counter;
> + mac_address_cmd[4] = 0x00;
> +
> + ret = iei_wt61p803_puzzle_write_command(mcu, mac_address_cmd,
> + sizeof(mac_address_cmd), resp_buf, &reply_size);
> + if (ret)
> + continue;
> +
> + if (reply_size < 22) {
> + ret = -EIO;
> + goto err;
> + }
> +
> + mcu->version.mac_address[mac_counter] = devm_kasprintf(dev,
> + GFP_KERNEL, "%.*s", (int)reply_size - 5,
> + resp_buf + 4);
> + }
> +err:
> + mutex_unlock(&mcu->lock);
> + return ret;
> +}
> +
> +int iei_wt61p803_puzzle_write_mac_address(struct iei_wt61p803_puzzle *mcu,
> + unsigned char mac_address[17], int mac_address_idx)
> +{
> + struct device *dev = &mcu->serdev->dev;
> + unsigned char mac_address_header[4] = {
> + IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
> + IEI_WT61P803_PUZZLE_CMD_EEPROM_WRITE,
> + 0x00,
> + 0x11
> + };
> + unsigned char mac_address_cmd[4+17+1]; /* header, MAC address, XOR checksum*/
> + unsigned char *resp_buf = mcu->response_buffer;
> + size_t reply_size = 0;
> + int ret;
> +
> + if (!(mac_address_idx < 8))
> + 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, 4);
> + memcpy(mac_address_cmd + 4, mac_address, 17);
> +
> + mac_address_cmd[sizeof(mac_address_cmd) - 1] = 0;
> +
> + 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;
> + }
> +
> + mcu->version.mac_address[mac_address_idx] = devm_kasprintf(dev,
> + GFP_KERNEL, "%.*s", 17, mac_address);
> +err:
> + mutex_unlock(&mcu->lock);
> + return ret;
> +}
> +
> +int iei_wt61p803_puzzle_write_power_loss_recovery(struct iei_wt61p803_puzzle *mcu,
> + int power_loss_recovery_action)
> +{
> + unsigned char power_loss_recovery_cmd[5] = {
> + IEI_WT61P803_PUZZLE_CMD_HEADER_START,
> + IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER,
> + IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_POWER_LOSS,
> + '0'
> + };
> + unsigned char *resp_buf = mcu->response_buffer;
> + size_t reply_size = 0;
> + unsigned char cmd_buf[2];
> + int ret;
> +
> + if (power_loss_recovery_action < 0 || power_loss_recovery_action > 4)
> + return -EINVAL;
> +
> + ret = snprintf(cmd_buf, sizeof(cmd_buf), "%d", power_loss_recovery_action);
> + if (ret < 0)
> + return ret;
> +
> + /* Modify the command with the action index */
> + power_loss_recovery_cmd[3] = cmd_buf[0];
> +
> + 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 sysfs_container(dev) \
> + (container_of((dev)->kobj.parent, struct device, kobj))
> +
> +static ssize_t version_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct device *dev_container = sysfs_container(dev);
> + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> +
> + return sprintf(buf, "%s\n", mcu->version.version);
> +}
> +static DEVICE_ATTR_RO(version);
> +
> +static ssize_t build_info_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct device *dev_container = sysfs_container(dev);
> + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> +
> + return sprintf(buf, "%s\n", mcu->version.build_info);
> +}
> +static DEVICE_ATTR_RO(build_info);
> +
> +static ssize_t bootloader_mode_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct device *dev_container = sysfs_container(dev);
> + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> +
> + return sprintf(buf, "%d\n", mcu->version.bootloader_mode);
> +}
> +static DEVICE_ATTR_RO(bootloader_mode);
> +
> +static ssize_t protocol_version_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct device *dev_container = sysfs_container(dev);
> + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> +
> + return sprintf(buf, "%s\n", mcu->version.protocol_version);
> +}
> +static DEVICE_ATTR_RO(protocol_version);
> +
> +static ssize_t serial_number_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct device *dev_container = sysfs_container(dev);
> + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> + int ret;
> +
> + mutex_lock(&mcu->lock);
> + ret = sprintf(buf, "%s\n", mcu->version.serial_number);
> + mutex_unlock(&mcu->lock);
> +
> + return ret;
> +}
> +static ssize_t serial_number_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct device *dev_container = sysfs_container(dev);
> + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> + unsigned char serial_number[36];
> + int ret;
> +
> + /* Check whether the serial number is 36 characters long + null */
> + if ((int)count != 36 + 1)
> + return -EINVAL;
> +
> + /* Copy 36 characters from the buffer to serial_number */
> + memcpy(serial_number, (unsigned char *)buf, 36);
> +
> + ret = iei_wt61p803_puzzle_write_serial_number(mcu, serial_number);
> + if (ret)
> + return ret;
> +
> + return count;
> +}
> +static DEVICE_ATTR_RW(serial_number);
> +
> +static ssize_t mac_address_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct device *dev_container = sysfs_container(dev);
> + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> + int ret;
> +
> + mutex_lock(&mcu->lock);
> + if (!strcmp(attr->attr.name, "mac_address_0"))
> + ret = sprintf(buf, "%s\n", mcu->version.mac_address[0]);
> + else if (!strcmp(attr->attr.name, "mac_address_1"))
> + ret = sprintf(buf, "%s\n", mcu->version.mac_address[1]);
> + else if (!strcmp(attr->attr.name, "mac_address_2"))
> + ret = sprintf(buf, "%s\n", mcu->version.mac_address[2]);
> + else if (!strcmp(attr->attr.name, "mac_address_3"))
> + ret = sprintf(buf, "%s\n", mcu->version.mac_address[3]);
> + else if (!strcmp(attr->attr.name, "mac_address_4"))
> + ret = sprintf(buf, "%s\n", mcu->version.mac_address[4]);
> + else if (!strcmp(attr->attr.name, "mac_address_5"))
> + ret = sprintf(buf, "%s\n", mcu->version.mac_address[5]);
> + else if (!strcmp(attr->attr.name, "mac_address_6"))
> + ret = sprintf(buf, "%s\n", mcu->version.mac_address[6]);
> + else if (!strcmp(attr->attr.name, "mac_address_7"))
> + ret = sprintf(buf, "%s\n", mcu->version.mac_address[7]);
> + else
> + ret = sprintf(buf, "\n");
WHYYYYY?
int idx;
if (strlen(attr->attr.name) != 13)
return -EIO;
idx = attr->attr.name[12] - '0';
if (idx < 0 || idx > 7)
return -EIO;
ret = sprintf(buf, "%s\n", mcu->version.mac_address[idx]);
> + mutex_unlock(&mcu->lock);
> +
> + return ret;
> +}
> +static ssize_t mac_address_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct device *dev_container = sysfs_container(dev);
> + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> + unsigned char mac_address[17];
> + int ret;
> +
> + if ((int)count != 17 + 1)
> + return -EINVAL;
> +
> + memcpy(mac_address, (unsigned char *)buf, 17);
> +
> + if (!strcmp(attr->attr.name, "mac_address_0"))
> + ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 0);
> + else if (!strcmp(attr->attr.name, "mac_address_1"))
> + ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 1);
> + else if (!strcmp(attr->attr.name, "mac_address_2"))
> + ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 2);
> + else if (!strcmp(attr->attr.name, "mac_address_3"))
> + ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 3);
> + else if (!strcmp(attr->attr.name, "mac_address_4"))
> + ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 4);
> + else if (!strcmp(attr->attr.name, "mac_address_5"))
> + ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 5);
> + else if (!strcmp(attr->attr.name, "mac_address_6"))
> + ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 6);
> + else if (!strcmp(attr->attr.name, "mac_address_7"))
> + ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 7);
> + if (ret)
> + return ret;
> +
Similar to above.
> + return count;
> +}
> +static DEVICE_ATTR(mac_address_0, 0644, mac_address_show, mac_address_store);
> +static DEVICE_ATTR(mac_address_1, 0644, mac_address_show, mac_address_store);
> +static DEVICE_ATTR(mac_address_2, 0644, mac_address_show, mac_address_store);
> +static DEVICE_ATTR(mac_address_3, 0644, mac_address_show, mac_address_store);
> +static DEVICE_ATTR(mac_address_4, 0644, mac_address_show, mac_address_store);
> +static DEVICE_ATTR(mac_address_5, 0644, mac_address_show, mac_address_store);
> +static DEVICE_ATTR(mac_address_6, 0644, mac_address_show, mac_address_store);
> +static DEVICE_ATTR(mac_address_7, 0644, mac_address_show, mac_address_store);
> +
> +static ssize_t ac_recovery_status_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct device *dev_container = sysfs_container(dev);
> + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> +
> + int ret;
> +
> + ret = iei_wt61p803_puzzle_get_mcu_status(mcu);
> + if (ret)
> + return ret;
> +
> + mutex_lock(&mcu->lock);
> + ret = sprintf(buf, "%x\n", mcu->status.ac_recovery_status_flag);
> + mutex_unlock(&mcu->lock);
> +
> + return ret;
> +}
> +static DEVICE_ATTR_RO(ac_recovery_status);
> +
> +static ssize_t power_loss_recovery_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct device *dev_container = sysfs_container(dev);
> + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> +
> + int ret;
> +
> + ret = iei_wt61p803_puzzle_get_mcu_status(mcu);
> + if (ret)
> + return ret;
> +
> + mutex_lock(&mcu->lock);
> + ret = sprintf(buf, "%x\n", mcu->status.power_loss_recovery);
> + mutex_unlock(&mcu->lock);
> +
> + return ret;
> +}
> +static ssize_t power_loss_recovery_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct device *dev_container = sysfs_container(dev);
> + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> +
> + int ret;
> + long power_loss_recovery_action = 0;
> +
> + ret = kstrtol(buf, 10, &power_loss_recovery_action);
> + if (ret)
> + return ret;
> +
> + ret = iei_wt61p803_puzzle_write_power_loss_recovery(mcu,
> + (int)power_loss_recovery_action);
> + if (ret)
> + return ret;
> +
> + return count;
> +}
> +static DEVICE_ATTR_RW(power_loss_recovery);
> +
> +static ssize_t power_status_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct device *dev_container = sysfs_container(dev);
> + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> +
> + int ret;
> +
> + ret = iei_wt61p803_puzzle_get_mcu_status(mcu);
> + if (ret)
> + return ret;
> +
> + mutex_lock(&mcu->lock);
> + ret = sprintf(buf, "%x\n", mcu->status.power_status);
> + mutex_unlock(&mcu->lock);
> +
> + return ret;
> +}
> +static DEVICE_ATTR_RO(power_status);
> +
> +static struct attribute *iei_wt61p803_puzzle_attrs[] = {
> + &dev_attr_version.attr,
> + &dev_attr_build_info.attr,
> + &dev_attr_bootloader_mode.attr,
> + &dev_attr_protocol_version.attr,
> + &dev_attr_serial_number.attr,
> + &dev_attr_mac_address_0.attr,
> + &dev_attr_mac_address_1.attr,
> + &dev_attr_mac_address_2.attr,
> + &dev_attr_mac_address_3.attr,
> + &dev_attr_mac_address_4.attr,
> + &dev_attr_mac_address_5.attr,
> + &dev_attr_mac_address_6.attr,
> + &dev_attr_mac_address_7.attr,
> + &dev_attr_ac_recovery_status.attr,
> + &dev_attr_power_loss_recovery.attr,
> + &dev_attr_power_status.attr,
> + NULL
> +};
> +ATTRIBUTE_GROUPS(iei_wt61p803_puzzle);
> +
> +static int iei_wt61p803_puzzle_sysfs_create(struct device *dev,
> + struct iei_wt61p803_puzzle *mcu)
> +{
> + int ret;
> +
> + mcu->kobj = kobject_create_and_add("iei_wt61p803_puzzle_core", &dev->kobj);
> + if (!mcu->kobj)
> + return -ENOMEM;
> +
> + ret = sysfs_create_groups(mcu->kobj, iei_wt61p803_puzzle_groups);
> + if (ret) {
> + kobject_del(mcu->kobj);
> + kobject_put(mcu->kobj);
> + mcu->kobj = NULL;
> + }
> +
> + return ret;
> +}
> +
> +static int iei_wt61p803_puzzle_sysfs_remove(struct device *dev,
> + struct iei_wt61p803_puzzle *mcu)
> +{
> + /* Remove sysfs groups */
> + sysfs_remove_groups(mcu->kobj, iei_wt61p803_puzzle_groups);
> +
> + /* Remove the kobject */
> + kobject_del(mcu->kobj);
> + kobject_put(mcu->kobj);
> + mcu->kobj = NULL;
> +
> + 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_info(dev, "Driver baud rate: %d", 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;
> +
> + mcu->reply->data = devm_kzalloc(dev, IEI_WT61P803_PUZZLE_RESP_BUF_SIZE,
> + GFP_KERNEL);
> + if (!mcu->reply->data)
> + return -ENOMEM;
> +
> + mcu->response_buffer = devm_kzalloc(dev, IEI_WT61P803_PUZZLE_BUF_SIZE,
> + GFP_KERNEL);
> + if (!mcu->response_buffer)
> + return -ENOMEM;
> +
> + /* Initialize device struct data */
> + mcu->serdev = serdev;
> + init_completion(&mcu->reply->received);
> + mutex_init(&mcu->reply_lock);
> + mutex_init(&mcu->bus_lock);
> + mutex_init(&mcu->lock);
> +
> + /* Setup UART interface */
> + serdev_device_set_drvdata(serdev, mcu);
> + serdev_device_set_client_ops(serdev, &iei_wt61p803_puzzle_serdev_device_ops);
> + ret = devm_serdev_device_open(dev, serdev);
> + if (ret)
> + return ret;
> + serdev_device_set_baudrate(serdev, baud);
> + serdev_device_set_flow_control(serdev, false);
> + ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
> + if (ret) {
> + dev_err(dev, "Failed to set parity");
> + return ret;
> + }
> +
> + ret = iei_wt61p803_puzzle_get_version(mcu);
> + if (ret)
> + return ret;
> +
> + ret = iei_wt61p803_puzzle_get_mac_addresses(mcu);
> + if (ret)
> + return ret;
> +
> + ret = iei_wt61p803_puzzle_get_serial_number(mcu);
> + if (ret)
> + return ret;
> +
> + dev_info(dev, "MCU version: %s", mcu->version.version);
> + dev_info(dev, "MCU firmware build info: %s", mcu->version.build_info);
> + dev_info(dev, "MCU in bootloader mode: %s",
> + mcu->version.bootloader_mode ? "true" : "false");
> + dev_info(dev, "MCU protocol version: %s", 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);
> +
> + 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");
> +MODULE_AUTHOR("Luka Kovacic <[email protected]>");
> +MODULE_DESCRIPTION("iEi WT61P803 PUZZLE MCU Driver");
> diff --git a/include/linux/mfd/iei-wt61p803-puzzle.h b/include/linux/mfd/iei-wt61p803-puzzle.h
> new file mode 100644
> index 000000000000..633ceb1d00e3
> --- /dev/null
> +++ b/include/linux/mfd/iei-wt61p803-puzzle.h
> @@ -0,0 +1,69 @@
> +/* 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 <[email protected]>
> + */
> +
> +#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_0 0x30
> +#define IEI_WT61P803_PUZZLE_CMD_FAN_PWM_1 0x31
> +#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_RPM_0 0x41 /* A */
> +#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM_1 0x42 /* B */
> +#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM_2 0x43 /* C */
> +#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM_3 0x44 /* D */
> +#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM_4 0x45 /* E */
> +#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM_5 0x46 /* F */
> +
> +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_ */
Marek
Hi Luka,
Thank you for the patch! Perhaps something to improve:
[auto build test WARNING on hwmon/hwmon-next]
[also build test WARNING on lee-mfd/for-mfd-next pavel-linux-leds/for-next linus/master v5.9-rc6 next-20200925]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]
url: https://github.com/0day-ci/linux/commits/Luka-Kovacic/Add-support-for-the-iEi-Puzzle-M801-board/20200926-215756
base: https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
config: nios2-allyesconfig (attached as .config)
compiler: nios2-linux-gcc (GCC) 9.3.0
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# https://github.com/0day-ci/linux/commit/a453994a7710920ee06d179274597737ff37af27
git remote add linux-review https://github.com/0day-ci/linux
git fetch --no-tags linux-review Luka-Kovacic/Add-support-for-the-iEi-Puzzle-M801-board/20200926-215756
git checkout a453994a7710920ee06d179274597737ff37af27
# save the attached .config to linux build tree
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross ARCH=nios2
If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <[email protected]>
All warnings (new ones prefixed by >>):
drivers/hwmon/iei-wt61p803-puzzle-hwmon.c: In function 'iei_wt61p803_puzzle_hwmon_probe':
>> drivers/hwmon/iei-wt61p803-puzzle-hwmon.c:457:17: warning: variable 'hwmon_dev' set but not used [-Wunused-but-set-variable]
457 | struct device *hwmon_dev;
| ^~~~~~~~~
vim +/hwmon_dev +457 drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
450
451 static int iei_wt61p803_puzzle_hwmon_probe(struct platform_device *pdev)
452 {
453 struct device *dev = &pdev->dev;
454 struct fwnode_handle *child;
455 struct iei_wt61p803_puzzle_hwmon *mcu_hwmon;
456 struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev->parent);
> 457 struct device *hwmon_dev;
458 int ret;
459
460 mcu_hwmon = devm_kzalloc(dev, sizeof(*mcu_hwmon), GFP_KERNEL);
461 if (!mcu_hwmon)
462 return -ENOMEM;
463
464 mcu_hwmon->response_buffer = devm_kzalloc(dev,
465 IEI_WT61P803_PUZZLE_BUF_SIZE, GFP_KERNEL);
466 if (!mcu_hwmon->response_buffer)
467 return -ENOMEM;
468
469 mcu_hwmon->mcu = mcu;
470 mutex_init(&mcu_hwmon->lock);
471 platform_set_drvdata(pdev, mcu_hwmon);
472
473 hwmon_dev = devm_hwmon_device_register_with_info(dev,
474 "iei_wt61p803_puzzle",
475 mcu_hwmon,
476 &iei_wt61p803_puzzle_chip_info,
477 NULL);
478
479 /* Control fans via PWM lines via Linux Kernel */
480 if (IS_ENABLED(CONFIG_THERMAL)) {
481 device_for_each_child_node(dev, child) {
482 ret = iei_wt61p803_puzzle_enable_thermal_cooling_dev(dev, child, mcu_hwmon);
483 if (ret) {
484 dev_err(dev, "Enabling the PWM fan failed\n");
485 fwnode_handle_put(child);
486 return ret;
487 }
488 }
489 }
490 return 0;
491 }
492
---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/[email protected]
Hello Andrew and Marek,
I will break the new patchset up and also add Gregory to the DT conversation.
Should I exclude this patch from this patchset or can I just add him to Cc?
First six LEDs are used to indicate port status and activity on the SFP+ ports.
Certainly, I will change this once the API is solved. There are currently many
similar boards with no real solution for the network LED triggers.
I'll add the color and correct the function properties for the LEDs.
Kind regards,
Luka
On Sat, Sep 26, 2020 at 7:50 PM Marek Behun <[email protected]> wrote:
>
> On Sat, 26 Sep 2020 15:55:14 +0200
> Luka Kovacic <[email protected]> wrote:
>
> > + leds {
> > + compatible = "gpio-leds";
> > + status = "okay";
> > + pinctrl-0 = <&cp0_sfpplus_led_pins &cp1_sfpplus_led_pins>;
> > + pinctrl-names = "default";
> > +
> > + led0 {
> > + function = LED_FUNCTION_STATUS;
> > + label = "p2_act";
> > + gpios = <&cp1_gpio1 6 GPIO_ACTIVE_LOW>;
> > + };
>
> There should be a dash in LED node name, please pass this dts via
> dt_binding_check
> led-0 {
> ...
> };
>
> Also why not add the `color` property to the LED? This is DTS for a
> specific device, right?
> `label` is obsolete. The LED subsystem creates a name in form
> [device:]color:function
> If this LED should blink for activity on port 2 (is this an ethernet
> port?), the function should be LED_FUNCTION_LAN and function-enumerator
> should be <2> (or function should be LED_FUNCTION_ACTIVITY, depending
> on how the LED subsystem goes forward with this, but certainly not
> LED_FUNCTION_STATUS), and trigger-sources should be set to point to the
> ethernet port.
>
> Luka, are you willing to change this once we solve this API properly
> in LED subsystem?
>
>
>
> > + led6 {
> > + function = LED_FUNCTION_STATUS;
> > + linux,default-trigger = "disk-activity";
> > + label = "front-hdd-led";
> > + gpios = <&cp0_gpio2 22 GPIO_ACTIVE_HIGH>;
> > + };
>
> led-6. LED_FUNCTION_DISK. `label` deprecated.
>
> > + leds {
> > + compatible = "iei,wt61p803-puzzle-leds";
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + led@0 {
> > + reg = <0>;
> > + color = <LED_COLOR_ID_BLUE>;
> > + label = "front-power-led";
> > + };
>
> Again, `label` is deprecated. Rather use function =
> <LED_FUNCTION_POWER>;
>
> Marek
Hi,
The microcontroller on this platform is currently only physically wired to one
LED, but should support multiple LEDs in software (this could possibly be
used in future platforms).
Thus I'd like to keep the support for multiple LEDs prepared. As I am not able
to test multiple LEDs functionality, I will just keep the framework prepared.
Also I'll use the devm_led_classdev_register_ext() API, thanks for
letting me know.
Kind regards,
Luka
On Sat, Sep 26, 2020 at 8:09 PM Marek Behun <[email protected]> wrote:
>
> On Sat, 26 Sep 2020 15:55:11 +0200
> Luka Kovacic <[email protected]> wrote:
>
> > Add support for the iEi WT61P803 PUZZLE LED driver.
> > Currently only the front panel power LED is supported.
> >
> > This driver depends on the iEi WT61P803 PUZZLE MFD driver.
> >
> > Signed-off-by: Luka Kovacic <[email protected]>
> > Cc: Luka Perkov <[email protected]>
> > Cc: Robert Marko <[email protected]>
> > ---
> > drivers/leds/Kconfig | 8 ++
> > drivers/leds/Makefile | 1 +
> > drivers/leds/leds-iei-wt61p803-puzzle.c | 174 ++++++++++++++++++++++++
> > 3 files changed, 183 insertions(+)
> > create mode 100644 drivers/leds/leds-iei-wt61p803-puzzle.c
> >
> > diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> > index 1c181df24eae..8a25fb753dec 100644
> > --- a/drivers/leds/Kconfig
> > +++ b/drivers/leds/Kconfig
> > @@ -332,6 +332,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
> > diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> > index c2c7d7ade0d0..cd362437fefd 100644
> > --- a/drivers/leds/Makefile
> > +++ b/drivers/leds/Makefile
> > @@ -34,6 +34,7 @@ obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o
> > 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
> > diff --git a/drivers/leds/leds-iei-wt61p803-puzzle.c b/drivers/leds/leds-iei-wt61p803-puzzle.c
> > new file mode 100644
> > index 000000000000..b9a977575a23
> > --- /dev/null
> > +++ b/drivers/leds/leds-iei-wt61p803-puzzle.c
> > @@ -0,0 +1,174 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/* iEi WT61P803 PUZZLE MCU LED Driver
> > + *
> > + * Copyright (C) 2020 Sartura Ltd.
> > + * Author: Luka Kovacic <[email protected]>
> > + */
> > +
> > +#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
> > + *
> > + * @mcu: MCU struct pointer
> > + * @response_buffer Global MCU response buffer allocation
> > + * @lock: General mutex lock for LED operations
> > + * @led_power_state: State of the front panel power LED
> > + */
> > +struct iei_wt61p803_puzzle_led {
> > + struct iei_wt61p803_puzzle *mcu;
> > + unsigned char *response_buffer;
> > + struct mutex lock;
> > + int led_power_state;
> > +};
> > +
> > +static inline struct iei_wt61p803_puzzle_led *cdev_to_iei_wt61p803_puzzle_led
> > + (struct led_classdev *led_cdev)
> > +{
> > + return dev_get_drvdata(led_cdev->dev->parent);
> > +}
> > +
> > +static int iei_wt61p803_puzzle_led_brightness_set_blocking(struct led_classdev *cdev,
> > + enum led_brightness brightness)
> > +{
> > + struct iei_wt61p803_puzzle_led *mcu_led =
> > + cdev_to_iei_wt61p803_puzzle_led(cdev);
> > + unsigned char led_power_cmd[5] = {
> > + IEI_WT61P803_PUZZLE_CMD_HEADER_START,
> > + IEI_WT61P803_PUZZLE_CMD_LED,
> > + IEI_WT61P803_PUZZLE_CMD_LED_POWER,
> > + (char)IEI_LED_OFF
> > + };
> > + unsigned char *resp_buf = mcu_led->response_buffer;
> > + size_t reply_size;
> > +
> > + mutex_lock(&mcu_led->lock);
> > + if (brightness == LED_OFF) {
> > + led_power_cmd[3] = (char)IEI_LED_OFF;
> > + mcu_led->led_power_state = LED_OFF;
> > + } else {
> > + led_power_cmd[3] = (char)IEI_LED_ON;
> > + mcu_led->led_power_state = LED_ON;
> > + }
> > + mutex_unlock(&mcu_led->lock);
> > +
> > + return iei_wt61p803_puzzle_write_command(mcu_led->mcu, led_power_cmd,
> > + sizeof(led_power_cmd), resp_buf, &reply_size);
> > +}
> > +
> > +static enum led_brightness
> > +iei_wt61p803_puzzle_led_brightness_get(struct led_classdev *cdev)
> > +{
> > + struct iei_wt61p803_puzzle_led *mcu_led =
> > + cdev_to_iei_wt61p803_puzzle_led(cdev);
> > + int led_state;
> > +
> > + mutex_lock(&mcu_led->lock);
> > + led_state = mcu_led->led_power_state;
> > + mutex_unlock(&mcu_led->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 *mcu_led;
> > + struct fwnode_handle *child;
> > + const char *label;
> > + int ret;
> > +
> > + mcu_led = devm_kzalloc(dev, sizeof(*mcu_led), GFP_KERNEL);
> > + if (!mcu_led)
> > + return -ENOMEM;
> > +
> > + mcu_led->response_buffer = devm_kzalloc(dev,
> > + IEI_WT61P803_PUZZLE_BUF_SIZE, GFP_KERNEL);
> > + if (!mcu_led->response_buffer)
> > + return -ENOMEM;
> > +
> > + mcu_led->mcu = mcu;
> > + mcu_led->led_power_state = 1;
> > + mutex_init(&mcu_led->lock);
> > + dev_set_drvdata(dev, mcu_led);
> > +
> > + device_for_each_child_node(dev, child) {
> > + struct led_classdev *led;
> > + u32 reg;
> > +
> > + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
>
> Avoid multiple allocations.
>
> Please look
> at
> https://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds.git/commit/?h=for-next&id=4351dceba0ff929f667867f9bb69210f08f61717
>
> To avoid multiple allocations, please use flexible array members.
>
> Does this controller support multiple LEDs? The device tree you
> provided only defines one.
>
> If it supports multiple LED:
> Rename the mcu_led to mcu_leds, or chip, or (semantically best in this
> driver) priv.
> Add member
> struct led_classdev leds[];
> to that structure.
> Then allocate by
> count = device_get_child_node_count(dev);
> priv = devm_kzalloc(dev, struct_size(priv, leds, count), GFP_KERNEL);
>
> If the device supports only one LED, just put
> struct led_classdev cdev;
> to the private structure of this driver. And don't use
> device_for_each_child_node, just check whether there is exactly one
> child node (device_get_child_node_count), get it via
> child = device_get_next_child_node(dev, NULL);
> and after registering the LED
> fwnode_handle_put(child);
> This was done in:
> https://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds.git/commit/?h=for-next&id=e92e7e8aa066904def9a5d584ece7fb6ae512dbd
>
> > + if (!led) {
> > + ret = -ENOMEM;
> > + goto err_child_node;
> > + }
> > +
> > + ret = fwnode_property_read_u32(child, "reg", ®);
> > + if (ret || reg > 1) {
> > + dev_err(dev, "Could not register 'reg' (%lu)\n", (unsigned long)reg);
> > + ret = -EINVAL;
> > + goto err_child_node;
> > + }
> > +
> > + if (fwnode_property_read_string(child, "label", &label)) {
> > + led->name = "iei-wt61p803-puzzle-led::";
> > + } else {
> > + led->name = devm_kasprintf(dev, GFP_KERNEL,
> > + "iei-wt61p803-puzzle-led:%s", label);
> > + if (!led->name) {
> > + ret = -ENOMEM;
> > + goto err_child_node;
> > + }
> > + }
>
> Parsing of label is done by LED core if you use
> devm_led_classdev_register_ext. Also, label is obsolete. The LED name
> should be composed from color, function and device.
> Also please dont pass devicename "iei-wt61p803-puzzle-led" here. We
> want to make the LED subsystem derive the device name somehow, and
> afterwards we would need to change this. Also the devicename should
> refer to the device the LED is triggering to (ie. if the LED is set in
> devicetree to trigger on activity on eth0, the devicename should be
> eth0 or something, not the name of this driver).
>
> Just remove this code and let devm_led_classdev_register_ext do its
> thing.
>
> > +
> > + fwnode_property_read_string(child, "linux,default-trigger",
> > + &led->default_trigger);
> > +
>
> Parsing of linux,default-trigger is done by LED core if you use
> devm_led_classdev_register_ext.
>
> > + led->brightness_set_blocking = iei_wt61p803_puzzle_led_brightness_set_blocking;
> > + led->brightness_get = iei_wt61p803_puzzle_led_brightness_get;
> > + led->max_brightness = 1;
> > +
> > + ret = devm_led_classdev_register(dev, led);
>
> Please use extended LED registration API, with
> devm_led_classdev_register_ext. Pass init_data with fwnode member set
> to child.
>
> > + if (ret) {
> > + dev_err(dev, "Could not register %s\n", led->name);
> > + goto err_child_node;
> > + }
> > + }
> > + return 0;
> > +err_child_node:
> > + 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 <[email protected]>");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:leds-iei-wt61p803-puzzle");
>
> Marek
I agree, I'll move this over to testing.
These values are stored in the microcontroller EEPROM and can be modified.
Some test units might be shipped with unpopulated MAC address and S/N values
so I exposed this functionality to enable the user to use some
internal value there.
Kind regards,
Luka
On Sat, Sep 26, 2020 at 8:25 PM Marek Behun <[email protected]> wrote:
>
> On Sat, 26 Sep 2020 15:55:12 +0200
> Luka Kovacic <[email protected]> wrote:
>
> > 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 <[email protected]>
> > Cc: Luka Perkov <[email protected]>
> > Cc: Robert Marko <[email protected]>
> > ---
> > .../stable/sysfs-driver-iei-wt61p803-puzzle | 65 +++++++++++++++++++
> > 1 file changed, 65 insertions(+)
> > create mode 100644 Documentation/ABI/stable/sysfs-driver-iei-wt61p803-puzzle
> >
> > diff --git a/Documentation/ABI/stable/sysfs-driver-iei-wt61p803-puzzle b/Documentation/ABI/stable/sysfs-driver-iei-wt61p803-puzzle
> > new file mode 100644
> > index 000000000000..36fca70d66ef
> > --- /dev/null
> > +++ b/Documentation/ABI/stable/sysfs-driver-iei-wt61p803-puzzle
>
> I think this should go to testing, not stable. It should go to stable
> only after it is stable for some time.
>
> > @@ -0,0 +1,65 @@
> > +What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/mac_address_*
> > +Date: September 2020
> > +Contact: Luka Kovacic <[email protected]>
> > +Description: Read the internal iEi WT61P803 PUZZLE MCU MAC address values.
> > + These are factory assigned and can be changed.
> > +
> > +What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/serial_number
> > +Date: September 2020
> > +Contact: Luka Kovacic <[email protected]>
> > +Description: Read the internal iEi WT61P803 PUZZLE MCU serial number.
> > + This value is factory assigned and can be changed.
> > +
>
> Please use (RO) and (RW) prefixes before the Description, instead of
> writing "This value is read only", i.e.:
> Description: (RO) Internal ... serial number.
>
> JFI: Why can these values be changed? Shouldn't they be burned into OTP?
>
> Marek
>
> > +What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/version
> > +Date: September 2020
> > +Contact: Luka Kovacic <[email protected]>
> > +Description: Read the internal iEi WT61P803 PUZZLE MCU version.
> > + This value is read only.
> > +
> > +What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/protocol_version
> > +Date: September 2020
> > +Contact: Luka Kovacic <[email protected]>
> > +Description: Read the internal iEi WT61P803 PUZZLE MCU protocol version.
> > + This value is read only.
> > +
> > +What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/power_loss_recovery
> > +Date: September 2020
> > +Contact: Luka Kovacic <[email protected]>
> > +Description: Read the iEi WT61P803 PUZZLE MCU power loss recovery value.
> > + This value is read write.
> > + Value mapping: 0 - Always-On, 1 - Always-Off, 2 - Always-AC, 3 - Always-WA
> > +
> > +What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/bootloader_mode
> > +Date: September 2020
> > +Contact: Luka Kovacic <[email protected]>
> > +Description: Read whether the MCU is in bootloader mode.
> > + This value is read only.
> > +
> > +What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/power_status
> > +Date: September 2020
> > +Contact: Luka Kovacic <[email protected]>
> > +Description: Read the iEi WT61P803 PUZZLE MCU power status. Power status indicates
> > + the power on method.
> > + This value is read only.
> > + 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 - WOL power on
> > + 0x02 - RTC alarm power on
> > + 0x01 - AC recover power on
> > +
> > +What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/build_info
> > +Date: September 2020
> > +Contact: Luka Kovacic <[email protected]>
> > +Description: Read the iEi WT61P803 PUZZLE MCU firmware build date.
> > + This value is read only.
> > + Format: yyyy/mm/dd hh:mm
> > +
> > +What: /sys/bus/serial/devices/.../iei_wt61p803_puzzle_core/ac_recovery_status
> > +Date: September 2020
> > +Contact: Luka Kovacic <[email protected]>
> > +Description: Read the iEi WT61P803 PUZZLE MCU AC recovery status.
> > + This value is read only.
>
Hi Marek,
Thanks for going through the patch.
Ok, the MAC addresses and the serial number always have the same length, so
I'll use a fixed array.
I can also apply the reverse christmas tree ordering to all three
drivers, I agree it
looks better.
Kind regards,
Luka
On Sat, Sep 26, 2020 at 9:28 PM Marek Behun <[email protected]> wrote:
>
> On Sat, 26 Sep 2020 15:55:09 +0200
> Luka Kovacic <[email protected]> wrote:
>
> > Add a driver for the iEi WT61P803 PUZZLE microcontroller, used in some
> > iEi Puzzle series devices. The microcontroller controls system power,
> > temperature sensors, fans and LEDs.
> >
> > This driver implements the core functionality for device communication
> > over the system serial (serdev bus). It handles MCU messages and the
> > internal MCU properties. Some properties can be managed over sysfs.
> >
>
> Hi Luka,
>
> this patch produces some checkpatch warnings, some of them are
> reasonable.
>
> My thought below:
>
> > Signed-off-by: Luka Kovacic <[email protected]>
> > Cc: Luka Perkov <[email protected]>
> > Cc: Robert Marko <[email protected]>
> > ---
> > drivers/mfd/Kconfig | 8 +
> > drivers/mfd/Makefile | 1 +
> > drivers/mfd/iei-wt61p803-puzzle.c | 1069 +++++++++++++++++++++++
> > include/linux/mfd/iei-wt61p803-puzzle.h | 69 ++
> > 4 files changed, 1147 insertions(+)
> > create mode 100644 drivers/mfd/iei-wt61p803-puzzle.c
> > create mode 100644 include/linux/mfd/iei-wt61p803-puzzle.h
> >
> > diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> > index 33df0837ab41..b1588845894e 100644
> > --- a/drivers/mfd/Kconfig
> > +++ b/drivers/mfd/Kconfig
> > @@ -2118,5 +2118,13 @@ config SGI_MFD_IOC3
> > If you have an SGI Origin, Octane, or a PCI IOC3 card,
> > then say Y. Otherwise say N.
> >
> > +config MFD_IEI_WT61P803_PUZZLE
> > + tristate "iEi WT61P803 PUZZLE MCU driver"
> > + depends on SERIAL_DEV_BUS
> > + help
> > + iEi WT61P803 PUZZLE is a system power management microcontroller
> > + used for fan control, temperature sensor reading, LED control
> > + and system identification.
> > +
> > endmenu
> > endif
> > diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> > index a60e5f835283..33b88023a68d 100644
> > --- a/drivers/mfd/Makefile
> > +++ b/drivers/mfd/Makefile
> > @@ -236,6 +236,7 @@ obj-$(CONFIG_MFD_HI655X_PMIC) += hi655x-pmic.o
> > obj-$(CONFIG_MFD_DLN2) += dln2.o
> > obj-$(CONFIG_MFD_RT5033) += rt5033.o
> > obj-$(CONFIG_MFD_SKY81452) += sky81452.o
> > +obj-$(CONFIG_MFD_IEI_WT61P803_PUZZLE) += iei-wt61p803-puzzle.o
> >
> > intel-soc-pmic-objs := intel_soc_pmic_core.o intel_soc_pmic_crc.o
> > obj-$(CONFIG_INTEL_SOC_PMIC) += intel-soc-pmic.o
> > diff --git a/drivers/mfd/iei-wt61p803-puzzle.c b/drivers/mfd/iei-wt61p803-puzzle.c
> > new file mode 100644
> > index 000000000000..5cba010ac9b9
> > --- /dev/null
> > +++ b/drivers/mfd/iei-wt61p803-puzzle.c
> > @@ -0,0 +1,1069 @@
> > +// 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 <[email protected]>
> > + */
> > +
> > +#include <asm/unaligned.h>
> > +#include <linux/atomic.h>
> > +#include <linux/delay.h>
> > +#include <linux/delay.h>
> > +#include <linux/export.h>
> > +#include <linux/init.h>
> > +#include <linux/kernel.h>
> > +#include <linux/mfd/iei-wt61p803-puzzle.h>
> > +#include <linux/mod_devicetable.h>
> > +#include <linux/module.h>
> > +#include <linux/of_device.h>
> > +#include <linux/property.h>
> > +#include <linux/sched.h>
> > +#include <linux/serdev.h>
> > +#include <linux/slab.h>
> > +#include <linux/sysfs.h>
> > +
> > +#define IEI_WT61P803_PUZZLE_MAX_COMMAND_LENGTH (20 + 2)
> > +#define IEI_WT61P803_PUZZLE_RESP_BUF_SIZE 512
> > +
> > +/* Use HZ as a timeout value throughout the driver */
> > +#define IEI_WT61P803_PUZZLE_GENERAL_TIMEOUT HZ
> > +
> > +/**
> > + * 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;
> > +};
> > +
> > +/**
> > + * enum iei_wt61p803_puzzle_reply_state - State of the reply
> > + * @FRAME_OK: The frame was completely processed/received
> > + * @FRAME_PROCESSING: First bytes were received, but the frame isn't complete
> > + * @FRAME_STRUCT_EMPTY: The frame struct is empty, no data was received
> > + * @FRAME_TIMEOUT: The frame processing timed out, communication failed
> > + *
> > + * Describes the general state of the frame that is currently being received.
> > + */
> > +enum iei_wt61p803_puzzle_reply_state {
> > + FRAME_OK = 0x00,
> > + FRAME_PROCESSING = 0x01,
> > + FRAME_STRUCT_EMPTY = 0xFF,
> > + FRAME_TIMEOUT = 0xFE
> > +};
> > +
> > +/**
> > + * 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;
> > + u8 state;
> > + 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
> > + */
> > +struct iei_wt61p803_puzzle_mcu_version {
> > + const char *version;
> > + const char *build_info;
> > + bool bootloader_mode;
> > + const char *protocol_version;
> > + const char *serial_number;
> > + const char *mac_address[8];
>
> You use devm_kasprintf below for these, but such variables have limited
> and often small length. The mac_address array could for example be:
> char mac_address[8][18];
> Can serial number be longer than 32 bytes? What about the other members?
> I believe it would be better, to avoid unnecesarry allocations, to
> change this.
>
> > +};
> > +
> > +/**
> > + * struct iei_wt61p803_puzzle - iEi WT61P803 PUZZLE MCU Driver
> > + *
> > + * @serdev: Pointer to underlying serdev device
> > + * @kobj: Pointer to kobject (sysfs)
> > + * @reply_lock: Reply mutex lock
> > + * @bus_lock: Bus 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 kobject *kobj;
> > + struct mutex reply_lock;
> > + struct mutex bus_lock;
> > + struct iei_wt61p803_puzzle_reply *reply;
> > + struct iei_wt61p803_puzzle_mcu_version version;
> > + struct iei_wt61p803_puzzle_mcu_status status;
> > + unsigned char *response_buffer;
> > + struct mutex lock;
> > +};
> > +
> > +unsigned char iei_wt61p803_puzzle_checksum(unsigned char *buf, size_t len)
> > +{
> > + unsigned char checksum = 0;
> > + unsigned int i;
> > +
> > + for (i = 0; i < len; i++)
> > + checksum ^= buf[i];
> > +
> > + return checksum;
> > +}
>
> This function should be static.
> And maybe for the whole driver: s/unsigned char/u8/
>
> > +
> > +static int iei_wt61p803_puzzle_process_resp(struct iei_wt61p803_puzzle *mcu,
> > + unsigned char *raw_resp_data, size_t size)
> > +{
> > + struct device *dev = &mcu->serdev->dev;
> > +
> > + unsigned char checksum;
>
> Empty line between declarations?
>
> > +
> > + mutex_lock(&mcu->reply_lock);
> > +
> > + /* 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))) {
> > +
> > + /* Frame header is not correct, check whether to append */
> > + if (mcu->reply->state != FRAME_PROCESSING) {
> > + dev_err(dev, "Invalid frame header and state (0x%x)", mcu->reply->state);
> > + mutex_unlock(&mcu->reply_lock);
> > + return -EIO;
> > + }
> > +
> > + /* Append the frame to existing data */
> > + memcpy(mcu->reply->data+mcu->reply->size, raw_resp_data, size);
> > + mcu->reply->size += size;
> > + } else {
> > + /* Start processing a new frame */
> > + memcpy(mcu->reply->data, raw_resp_data, size);
> > + mcu->reply->size = size;
> > + mcu->reply->state = FRAME_PROCESSING;
> > + }
> > +
> > + 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 */
> > + mutex_unlock(&mcu->reply_lock);
> > + return (int)size;
> > + }
> > +
> > + /* Received all the data */
> > + mcu->reply->state = FRAME_OK;
> > + complete(&mcu->reply->received);
> > +
> > + mutex_unlock(&mcu->reply_lock);
> > +
> > + return (int)size;
> > +}
> > +
> > +static int iei_wt61p803_puzzle_recv_buf(struct serdev_device *serdev,
> > + const unsigned char *data, size_t size)
> > +{
> > + struct iei_wt61p803_puzzle *mcu = serdev_device_get_drvdata(serdev);
> > + int ret;
> > +
> > + ret = iei_wt61p803_puzzle_process_resp(mcu, (unsigned char *)data, size);
> > +
> > + /* Return the number of processed bytes if function returns error */
> > + if (ret < 0)
> > + return (int)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, "%s: Command response timed out. Retries: %d", __func__,
> > + retry_count);
>
> The indentation here is weird, I would put the retry_count at the
> position one to the right after dev_err(. But this is not important.
>
> > +
> > + 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 len = (int)size;
>
> Some kernel maintainers like reverse christmas tree order of variable
> declaration. I find it better to read. If you would like to try:
> struct device *dev = &mcu->serdev->dev;
> int len = (int)size;
> int ret;
>
> > +
> > + if (size > IEI_WT61P803_PUZZLE_MAX_COMMAND_LENGTH)
> > + return -EINVAL;
> > +
> > + cmd[len - 1] = iei_wt61p803_puzzle_checksum(cmd, size);
> > +
> > + mutex_lock(&mcu->bus_lock);
> > + mutex_lock(&mcu->reply_lock);
> > +
> > + if (!mcu->reply) {
> > + ret = -EFAULT;
> > + goto exit;
> > + }
> > +
> > + /* Initialize reply struct */
> > + reinit_completion(&mcu->reply->received);
> > + mcu->reply->state = FRAME_STRUCT_EMPTY;
> > + mcu->reply->size = 0;
> > + mutex_unlock(&mcu->reply_lock);
> > +
> > + ret = serdev_device_write(mcu->serdev, cmd, len, IEI_WT61P803_PUZZLE_GENERAL_TIMEOUT);
> > +
> > + if (ret < 0) {
> > + mutex_unlock(&mcu->bus_lock);
> > + return ret;
> > + }
> > +
> > + if (!wait_for_completion_timeout(&mcu->reply->received,
> > + IEI_WT61P803_PUZZLE_GENERAL_TIMEOUT)) {
> > + dev_err(dev, "Command reply receive timeout\n");
> > + mutex_lock(&mcu->reply_lock);
> > + reinit_completion(&mcu->reply->received);
> > + mcu->reply->state = FRAME_TIMEOUT;
> > +
> > + ret = -ETIMEDOUT;
> > + goto exit;
> > + }
> > +
> > + mutex_lock(&mcu->reply_lock);
> > +
> > + if (!mcu->reply) {
> > + ret = -EFAULT;
> > + 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);
> > + mutex_unlock(&mcu->bus_lock);
> > + return ret;
> > +}
> > +EXPORT_SYMBOL_GPL(iei_wt61p803_puzzle_write_command);
> > +
> > +int iei_wt61p803_puzzle_buzzer(struct iei_wt61p803_puzzle *mcu, bool long_beep)
> > +{
> > + unsigned char buzzer_short_cmd[4] = {
> > + IEI_WT61P803_PUZZLE_CMD_HEADER_START,
> > + IEI_WT61P803_PUZZLE_CMD_FUNCTION_SINGLE,
> > + '2'
> > + }; /* Buzzer 0.5 sec */
> > + unsigned char buzzer_long_cmd[4] = {
> > + IEI_WT61P803_PUZZLE_CMD_HEADER_START,
> > + IEI_WT61P803_PUZZLE_CMD_FUNCTION_SINGLE,
> > + '3'
> > + }; /* Buzzer 1.5 sec */
> > + unsigned char *resp_buf = mcu->response_buffer;
> > + size_t reply_size = 0;
> > + int ret;
> > +
> > + mutex_lock(&mcu->lock);
> > + ret = iei_wt61p803_puzzle_write_command(mcu,
> > + long_beep ? buzzer_long_cmd : buzzer_short_cmd, 4,
> > + 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)) {
>
> Again weird indentation here, I would do
>
> 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;
> > +}
> > +
> > +int iei_wt61p803_puzzle_get_version(struct iei_wt61p803_puzzle *mcu)
> > +{
> > + struct device *dev = &mcu->serdev->dev;
> > + 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 = 0;
> > + 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;
> > + }
> > + mcu->version.version = devm_kasprintf(dev, GFP_KERNEL, "v%c.%c%c%c",
> > + rb[2], rb[3], rb[4], rb[5]);
> > +
> > + 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;
> > + }
> > + mcu->version.build_info = devm_kasprintf(dev, GFP_KERNEL,
> > + "%c%c/%c%c/%c%c%c%c %c%c:%c%c",
> > + rb[8], rb[9], rb[6], rb[7], rb[2],
> > + rb[3], rb[4], rb[5], 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;
> > + }
> > + mcu->version.protocol_version = devm_kasprintf(dev, GFP_KERNEL,
> > + "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;
> > +}
> > +
> > +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 = 0;
> > + 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;
> > +}
> > +
> > +int iei_wt61p803_puzzle_get_serial_number(struct iei_wt61p803_puzzle *mcu)
> > +{
> > + struct device *dev = &mcu->serdev->dev;
> > + unsigned char serial_number_cmd[5] = {
> > + IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
> > + IEI_WT61P803_PUZZLE_CMD_EEPROM_READ,
> > + 0x00,
> > + 0x24
> > + };
> > + unsigned char *resp_buf = mcu->response_buffer;
> > + size_t reply_size = 0;
> > + 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;
> > +
> > + mcu->version.serial_number = devm_kasprintf(dev, GFP_KERNEL, "%.*s",
> > + (int)reply_size - 5, resp_buf + 4);
> > +err:
> > + mutex_unlock(&mcu->lock);
> > + return ret;
> > +
> > +}
> > +
> > +int iei_wt61p803_puzzle_write_serial_number(struct iei_wt61p803_puzzle *mcu,
> > + unsigned char serial_number[36])
> > +{
> > + struct device *dev = &mcu->serdev->dev;
> > + unsigned char serial_number_header[4] = {
> > + IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
> > + IEI_WT61P803_PUZZLE_CMD_EEPROM_WRITE,
> > + 0x00,
> > + 0xC
> > + };
> > + unsigned char *resp_buf = mcu->response_buffer;
> > + unsigned char serial_number_cmd[4+12+1]; /* header, serial number chunk, XOR checksum */
> > + int ret, sn_counter;
> > + size_t reply_size = 0;
> > +
> > + /* The MCU can only handle 22 byte messages, send the S/N in 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, 4);
> > + memcpy(serial_number_cmd + 4, serial_number + (0xC) * sn_counter, 0xC);
> > +
> > + serial_number_cmd[sizeof(serial_number_cmd) - 1] = 0;
> > +
> > + 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;
> > + }
> > + }
> > +
> > + mcu->version.serial_number = devm_kasprintf(dev, GFP_KERNEL, "%.*s",
> > + 36, serial_number);
> > +err:
> > + mutex_unlock(&mcu->lock);
> > + return ret;
> > +}
> > +
> > +int iei_wt61p803_puzzle_get_mac_addresses(struct iei_wt61p803_puzzle *mcu)
> > +{
> > + struct device *dev = &mcu->serdev->dev;
> > + unsigned char mac_address_cmd[5] = {
> > + IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
> > + IEI_WT61P803_PUZZLE_CMD_EEPROM_READ,
> > + 0x00,
> > + 0x11
> > + };
> > + unsigned char *resp_buf = mcu->response_buffer;
> > + int ret, mac_counter;
> > + size_t reply_size = 0;
> > +
> > + mutex_lock(&mcu->lock);
> > + for (mac_counter = 0; mac_counter < 8; mac_counter++) {
> > + mac_address_cmd[2] = 0x24 + (0x11) * mac_counter;
> > + mac_address_cmd[4] = 0x00;
> > +
> > + ret = iei_wt61p803_puzzle_write_command(mcu, mac_address_cmd,
> > + sizeof(mac_address_cmd), resp_buf, &reply_size);
> > + if (ret)
> > + continue;
> > +
> > + if (reply_size < 22) {
> > + ret = -EIO;
> > + goto err;
> > + }
> > +
> > + mcu->version.mac_address[mac_counter] = devm_kasprintf(dev,
> > + GFP_KERNEL, "%.*s", (int)reply_size - 5,
> > + resp_buf + 4);
> > + }
> > +err:
> > + mutex_unlock(&mcu->lock);
> > + return ret;
> > +}
> > +
> > +int iei_wt61p803_puzzle_write_mac_address(struct iei_wt61p803_puzzle *mcu,
> > + unsigned char mac_address[17], int mac_address_idx)
> > +{
> > + struct device *dev = &mcu->serdev->dev;
> > + unsigned char mac_address_header[4] = {
> > + IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
> > + IEI_WT61P803_PUZZLE_CMD_EEPROM_WRITE,
> > + 0x00,
> > + 0x11
> > + };
> > + unsigned char mac_address_cmd[4+17+1]; /* header, MAC address, XOR checksum*/
> > + unsigned char *resp_buf = mcu->response_buffer;
> > + size_t reply_size = 0;
> > + int ret;
> > +
> > + if (!(mac_address_idx < 8))
> > + 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, 4);
> > + memcpy(mac_address_cmd + 4, mac_address, 17);
> > +
> > + mac_address_cmd[sizeof(mac_address_cmd) - 1] = 0;
> > +
> > + 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;
> > + }
> > +
> > + mcu->version.mac_address[mac_address_idx] = devm_kasprintf(dev,
> > + GFP_KERNEL, "%.*s", 17, mac_address);
> > +err:
> > + mutex_unlock(&mcu->lock);
> > + return ret;
> > +}
> > +
> > +int iei_wt61p803_puzzle_write_power_loss_recovery(struct iei_wt61p803_puzzle *mcu,
> > + int power_loss_recovery_action)
> > +{
> > + unsigned char power_loss_recovery_cmd[5] = {
> > + IEI_WT61P803_PUZZLE_CMD_HEADER_START,
> > + IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER,
> > + IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_POWER_LOSS,
> > + '0'
> > + };
> > + unsigned char *resp_buf = mcu->response_buffer;
> > + size_t reply_size = 0;
> > + unsigned char cmd_buf[2];
> > + int ret;
> > +
> > + if (power_loss_recovery_action < 0 || power_loss_recovery_action > 4)
> > + return -EINVAL;
> > +
> > + ret = snprintf(cmd_buf, sizeof(cmd_buf), "%d", power_loss_recovery_action);
> > + if (ret < 0)
> > + return ret;
> > +
> > + /* Modify the command with the action index */
> > + power_loss_recovery_cmd[3] = cmd_buf[0];
> > +
> > + 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 sysfs_container(dev) \
> > + (container_of((dev)->kobj.parent, struct device, kobj))
> > +
> > +static ssize_t version_show(struct device *dev,
> > + struct device_attribute *attr, char *buf)
> > +{
> > + struct device *dev_container = sysfs_container(dev);
> > + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> > +
> > + return sprintf(buf, "%s\n", mcu->version.version);
> > +}
> > +static DEVICE_ATTR_RO(version);
> > +
> > +static ssize_t build_info_show(struct device *dev,
> > + struct device_attribute *attr, char *buf)
> > +{
> > + struct device *dev_container = sysfs_container(dev);
> > + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> > +
> > + return sprintf(buf, "%s\n", mcu->version.build_info);
> > +}
> > +static DEVICE_ATTR_RO(build_info);
> > +
> > +static ssize_t bootloader_mode_show(struct device *dev,
> > + struct device_attribute *attr, char *buf)
> > +{
> > + struct device *dev_container = sysfs_container(dev);
> > + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> > +
> > + return sprintf(buf, "%d\n", mcu->version.bootloader_mode);
> > +}
> > +static DEVICE_ATTR_RO(bootloader_mode);
> > +
> > +static ssize_t protocol_version_show(struct device *dev,
> > + struct device_attribute *attr, char *buf)
> > +{
> > + struct device *dev_container = sysfs_container(dev);
> > + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> > +
> > + return sprintf(buf, "%s\n", mcu->version.protocol_version);
> > +}
> > +static DEVICE_ATTR_RO(protocol_version);
> > +
> > +static ssize_t serial_number_show(struct device *dev,
> > + struct device_attribute *attr, char *buf)
> > +{
> > + struct device *dev_container = sysfs_container(dev);
> > + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> > + int ret;
> > +
> > + mutex_lock(&mcu->lock);
> > + ret = sprintf(buf, "%s\n", mcu->version.serial_number);
> > + mutex_unlock(&mcu->lock);
> > +
> > + return ret;
> > +}
> > +static ssize_t serial_number_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + struct device *dev_container = sysfs_container(dev);
> > + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> > + unsigned char serial_number[36];
> > + int ret;
> > +
> > + /* Check whether the serial number is 36 characters long + null */
> > + if ((int)count != 36 + 1)
> > + return -EINVAL;
> > +
> > + /* Copy 36 characters from the buffer to serial_number */
> > + memcpy(serial_number, (unsigned char *)buf, 36);
> > +
> > + ret = iei_wt61p803_puzzle_write_serial_number(mcu, serial_number);
> > + if (ret)
> > + return ret;
> > +
> > + return count;
> > +}
> > +static DEVICE_ATTR_RW(serial_number);
> > +
> > +static ssize_t mac_address_show(struct device *dev,
> > + struct device_attribute *attr, char *buf)
> > +{
> > + struct device *dev_container = sysfs_container(dev);
> > + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> > + int ret;
> > +
> > + mutex_lock(&mcu->lock);
> > + if (!strcmp(attr->attr.name, "mac_address_0"))
> > + ret = sprintf(buf, "%s\n", mcu->version.mac_address[0]);
> > + else if (!strcmp(attr->attr.name, "mac_address_1"))
> > + ret = sprintf(buf, "%s\n", mcu->version.mac_address[1]);
> > + else if (!strcmp(attr->attr.name, "mac_address_2"))
> > + ret = sprintf(buf, "%s\n", mcu->version.mac_address[2]);
> > + else if (!strcmp(attr->attr.name, "mac_address_3"))
> > + ret = sprintf(buf, "%s\n", mcu->version.mac_address[3]);
> > + else if (!strcmp(attr->attr.name, "mac_address_4"))
> > + ret = sprintf(buf, "%s\n", mcu->version.mac_address[4]);
> > + else if (!strcmp(attr->attr.name, "mac_address_5"))
> > + ret = sprintf(buf, "%s\n", mcu->version.mac_address[5]);
> > + else if (!strcmp(attr->attr.name, "mac_address_6"))
> > + ret = sprintf(buf, "%s\n", mcu->version.mac_address[6]);
> > + else if (!strcmp(attr->attr.name, "mac_address_7"))
> > + ret = sprintf(buf, "%s\n", mcu->version.mac_address[7]);
> > + else
> > + ret = sprintf(buf, "\n");
>
> WHYYYYY?
>
> int idx;
>
> if (strlen(attr->attr.name) != 13)
> return -EIO;
>
> idx = attr->attr.name[12] - '0';
> if (idx < 0 || idx > 7)
> return -EIO;
> ret = sprintf(buf, "%s\n", mcu->version.mac_address[idx]);
>
> > + mutex_unlock(&mcu->lock);
> > +
> > + return ret;
> > +}
> > +static ssize_t mac_address_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + struct device *dev_container = sysfs_container(dev);
> > + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> > + unsigned char mac_address[17];
> > + int ret;
> > +
> > + if ((int)count != 17 + 1)
> > + return -EINVAL;
> > +
> > + memcpy(mac_address, (unsigned char *)buf, 17);
> > +
> > + if (!strcmp(attr->attr.name, "mac_address_0"))
> > + ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 0);
> > + else if (!strcmp(attr->attr.name, "mac_address_1"))
> > + ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 1);
> > + else if (!strcmp(attr->attr.name, "mac_address_2"))
> > + ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 2);
> > + else if (!strcmp(attr->attr.name, "mac_address_3"))
> > + ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 3);
> > + else if (!strcmp(attr->attr.name, "mac_address_4"))
> > + ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 4);
> > + else if (!strcmp(attr->attr.name, "mac_address_5"))
> > + ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 5);
> > + else if (!strcmp(attr->attr.name, "mac_address_6"))
> > + ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 6);
> > + else if (!strcmp(attr->attr.name, "mac_address_7"))
> > + ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, 7);
> > + if (ret)
> > + return ret;
> > +
>
> Similar to above.
>
> > + return count;
> > +}
> > +static DEVICE_ATTR(mac_address_0, 0644, mac_address_show, mac_address_store);
> > +static DEVICE_ATTR(mac_address_1, 0644, mac_address_show, mac_address_store);
> > +static DEVICE_ATTR(mac_address_2, 0644, mac_address_show, mac_address_store);
> > +static DEVICE_ATTR(mac_address_3, 0644, mac_address_show, mac_address_store);
> > +static DEVICE_ATTR(mac_address_4, 0644, mac_address_show, mac_address_store);
> > +static DEVICE_ATTR(mac_address_5, 0644, mac_address_show, mac_address_store);
> > +static DEVICE_ATTR(mac_address_6, 0644, mac_address_show, mac_address_store);
> > +static DEVICE_ATTR(mac_address_7, 0644, mac_address_show, mac_address_store);
> > +
> > +static ssize_t ac_recovery_status_show(struct device *dev,
> > + struct device_attribute *attr, char *buf)
> > +{
> > + struct device *dev_container = sysfs_container(dev);
> > + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> > +
> > + int ret;
> > +
> > + ret = iei_wt61p803_puzzle_get_mcu_status(mcu);
> > + if (ret)
> > + return ret;
> > +
> > + mutex_lock(&mcu->lock);
> > + ret = sprintf(buf, "%x\n", mcu->status.ac_recovery_status_flag);
> > + mutex_unlock(&mcu->lock);
> > +
> > + return ret;
> > +}
> > +static DEVICE_ATTR_RO(ac_recovery_status);
> > +
> > +static ssize_t power_loss_recovery_show(struct device *dev,
> > + struct device_attribute *attr, char *buf)
> > +{
> > + struct device *dev_container = sysfs_container(dev);
> > + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> > +
> > + int ret;
> > +
> > + ret = iei_wt61p803_puzzle_get_mcu_status(mcu);
> > + if (ret)
> > + return ret;
> > +
> > + mutex_lock(&mcu->lock);
> > + ret = sprintf(buf, "%x\n", mcu->status.power_loss_recovery);
> > + mutex_unlock(&mcu->lock);
> > +
> > + return ret;
> > +}
> > +static ssize_t power_loss_recovery_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + struct device *dev_container = sysfs_container(dev);
> > + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> > +
> > + int ret;
> > + long power_loss_recovery_action = 0;
> > +
> > + ret = kstrtol(buf, 10, &power_loss_recovery_action);
> > + if (ret)
> > + return ret;
> > +
> > + ret = iei_wt61p803_puzzle_write_power_loss_recovery(mcu,
> > + (int)power_loss_recovery_action);
> > + if (ret)
> > + return ret;
> > +
> > + return count;
> > +}
> > +static DEVICE_ATTR_RW(power_loss_recovery);
> > +
> > +static ssize_t power_status_show(struct device *dev,
> > + struct device_attribute *attr, char *buf)
> > +{
> > + struct device *dev_container = sysfs_container(dev);
> > + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev_container);
> > +
> > + int ret;
> > +
> > + ret = iei_wt61p803_puzzle_get_mcu_status(mcu);
> > + if (ret)
> > + return ret;
> > +
> > + mutex_lock(&mcu->lock);
> > + ret = sprintf(buf, "%x\n", mcu->status.power_status);
> > + mutex_unlock(&mcu->lock);
> > +
> > + return ret;
> > +}
> > +static DEVICE_ATTR_RO(power_status);
> > +
> > +static struct attribute *iei_wt61p803_puzzle_attrs[] = {
> > + &dev_attr_version.attr,
> > + &dev_attr_build_info.attr,
> > + &dev_attr_bootloader_mode.attr,
> > + &dev_attr_protocol_version.attr,
> > + &dev_attr_serial_number.attr,
> > + &dev_attr_mac_address_0.attr,
> > + &dev_attr_mac_address_1.attr,
> > + &dev_attr_mac_address_2.attr,
> > + &dev_attr_mac_address_3.attr,
> > + &dev_attr_mac_address_4.attr,
> > + &dev_attr_mac_address_5.attr,
> > + &dev_attr_mac_address_6.attr,
> > + &dev_attr_mac_address_7.attr,
> > + &dev_attr_ac_recovery_status.attr,
> > + &dev_attr_power_loss_recovery.attr,
> > + &dev_attr_power_status.attr,
> > + NULL
> > +};
> > +ATTRIBUTE_GROUPS(iei_wt61p803_puzzle);
> > +
> > +static int iei_wt61p803_puzzle_sysfs_create(struct device *dev,
> > + struct iei_wt61p803_puzzle *mcu)
> > +{
> > + int ret;
> > +
> > + mcu->kobj = kobject_create_and_add("iei_wt61p803_puzzle_core", &dev->kobj);
> > + if (!mcu->kobj)
> > + return -ENOMEM;
> > +
> > + ret = sysfs_create_groups(mcu->kobj, iei_wt61p803_puzzle_groups);
> > + if (ret) {
> > + kobject_del(mcu->kobj);
> > + kobject_put(mcu->kobj);
> > + mcu->kobj = NULL;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static int iei_wt61p803_puzzle_sysfs_remove(struct device *dev,
> > + struct iei_wt61p803_puzzle *mcu)
> > +{
> > + /* Remove sysfs groups */
> > + sysfs_remove_groups(mcu->kobj, iei_wt61p803_puzzle_groups);
> > +
> > + /* Remove the kobject */
> > + kobject_del(mcu->kobj);
> > + kobject_put(mcu->kobj);
> > + mcu->kobj = NULL;
> > +
> > + 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_info(dev, "Driver baud rate: %d", 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;
> > +
> > + mcu->reply->data = devm_kzalloc(dev, IEI_WT61P803_PUZZLE_RESP_BUF_SIZE,
> > + GFP_KERNEL);
> > + if (!mcu->reply->data)
> > + return -ENOMEM;
> > +
> > + mcu->response_buffer = devm_kzalloc(dev, IEI_WT61P803_PUZZLE_BUF_SIZE,
> > + GFP_KERNEL);
> > + if (!mcu->response_buffer)
> > + return -ENOMEM;
> > +
> > + /* Initialize device struct data */
> > + mcu->serdev = serdev;
> > + init_completion(&mcu->reply->received);
> > + mutex_init(&mcu->reply_lock);
> > + mutex_init(&mcu->bus_lock);
> > + mutex_init(&mcu->lock);
> > +
> > + /* Setup UART interface */
> > + serdev_device_set_drvdata(serdev, mcu);
> > + serdev_device_set_client_ops(serdev, &iei_wt61p803_puzzle_serdev_device_ops);
> > + ret = devm_serdev_device_open(dev, serdev);
> > + if (ret)
> > + return ret;
> > + serdev_device_set_baudrate(serdev, baud);
> > + serdev_device_set_flow_control(serdev, false);
> > + ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
> > + if (ret) {
> > + dev_err(dev, "Failed to set parity");
> > + return ret;
> > + }
> > +
> > + ret = iei_wt61p803_puzzle_get_version(mcu);
> > + if (ret)
> > + return ret;
> > +
> > + ret = iei_wt61p803_puzzle_get_mac_addresses(mcu);
> > + if (ret)
> > + return ret;
> > +
> > + ret = iei_wt61p803_puzzle_get_serial_number(mcu);
> > + if (ret)
> > + return ret;
> > +
> > + dev_info(dev, "MCU version: %s", mcu->version.version);
> > + dev_info(dev, "MCU firmware build info: %s", mcu->version.build_info);
> > + dev_info(dev, "MCU in bootloader mode: %s",
> > + mcu->version.bootloader_mode ? "true" : "false");
> > + dev_info(dev, "MCU protocol version: %s", 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);
> > +
> > + 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");
> > +MODULE_AUTHOR("Luka Kovacic <[email protected]>");
> > +MODULE_DESCRIPTION("iEi WT61P803 PUZZLE MCU Driver");
> > diff --git a/include/linux/mfd/iei-wt61p803-puzzle.h b/include/linux/mfd/iei-wt61p803-puzzle.h
> > new file mode 100644
> > index 000000000000..633ceb1d00e3
> > --- /dev/null
> > +++ b/include/linux/mfd/iei-wt61p803-puzzle.h
> > @@ -0,0 +1,69 @@
> > +/* 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 <[email protected]>
> > + */
> > +
> > +#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_0 0x30
> > +#define IEI_WT61P803_PUZZLE_CMD_FAN_PWM_1 0x31
> > +#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_RPM_0 0x41 /* A */
> > +#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM_1 0x42 /* B */
> > +#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM_2 0x43 /* C */
> > +#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM_3 0x44 /* D */
> > +#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM_4 0x45 /* E */
> > +#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM_5 0x46 /* F */
> > +
> > +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_ */
>
> Marek
On Sat, Sep 26, 2020 at 03:55:10PM +0200, Luka Kovacic wrote:
> 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 <[email protected]>
> Cc: Luka Perkov <[email protected]>
> Cc: Robert Marko <[email protected]>
> ---
> drivers/hwmon/Kconfig | 8 +
> drivers/hwmon/Makefile | 1 +
> drivers/hwmon/iei-wt61p803-puzzle-hwmon.c | 511 ++++++++++++++++++++++
> 3 files changed, 520 insertions(+)
> create mode 100644 drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
>
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 8dc28b26916e..ff279df9bf40 100644
> --- 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
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index a8f4b35b136b..b0afb2d6896f 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -83,6 +83,7 @@ obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o
> 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
> diff --git a/drivers/hwmon/iei-wt61p803-puzzle-hwmon.c b/drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
> new file mode 100644
> index 000000000000..2691b943936b
> --- /dev/null
> +++ b/drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
> @@ -0,0 +1,511 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* iEi WT61P803 PUZZLE MCU HWMON Driver
> + *
> + * Copyright (C) 2020 Sartura Ltd.
> + * Author: Luka Kovacic <[email protected]>
> + */
> +
> +#include <linux/err.h>
> +#include <linux/hwmon-sysfs.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_TEMP_NUM 2
> +#define IEI_WT61P803_PUZZLE_HWMON_MAX_FAN_NUM 5
> +#define IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_NUM 2
> +#define IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_VAL 255
> +
> +/**
> + * struct iei_wt61p803_puzzle_thermal_cooling_device - Thermal cooling device instance
> + *
> + * @mcu_hwmon: MCU HWMON struct pointer
> + * @tcdev: Thermal cooling device pointer
> + * @name: Thermal cooling device name
> + * @pwm_channel: PWM channel (0 or 1)
> + * @cooling_levels: Thermal cooling device cooling levels
> + */
> +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
> + * @lock General member lock
> + * @response_buffer Global MCU response buffer allocation
> + * @temp_sensor_val: Temperature sensor values
> + * @fan_speed_val: FAN speed (RPM) values
> + * @pwm_val: PWM values (0-255)
> + * @thermal_cooling_dev_present: Per-channel thermal cooling device control
> + * @cdev: Per-channel thermal cooling device private structure
> + */
> +struct iei_wt61p803_puzzle_hwmon {
> + struct iei_wt61p803_puzzle *mcu;
> + struct mutex lock;
> + unsigned char *response_buffer;
> + int temp_sensor_val[IEI_WT61P803_PUZZLE_HWMON_MAX_TEMP_NUM];
> + int fan_speed_val[IEI_WT61P803_PUZZLE_HWMON_MAX_FAN_NUM];
> + int pwm_val[IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_NUM];
> + bool thermal_cooling_dev_present[IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_NUM];
> + struct iei_wt61p803_puzzle_thermal_cooling_device
> + *cdev[IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_NUM];
> +};
> +
> +#define raw_temp_to_milidegree_celsius(x) ((int)((x - 0x80)*1000))
Spaces around '*', please, and (x). checkpatch --strict will tell about it.
> +static int iei_wt61p803_puzzle_read_temp_sensor
> +(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon, int channel, int *value)
Odd multi-line alignment. Please use either
static int
iei_wt61p803_puzzle_read_temp_sensor(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon, int channel,
int *value)
or
static int iei_wt61p803_puzzle_read_temp_sensor(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon,
int channel, int *value)
There are lots of those in this driver. Please run
checkpatch --strict and fix what it reports.
> +{
> + int ret;
> + size_t reply_size = 0;
> + 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
> + };
> +
> + if (channel > 1 && channel < 0)
> + return -EINVAL;
Unnecessary range check.
> +
> + 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 < 0)
goto unlock;
> + if (!ret) {
> + /* Check the number of NTC values (should be 0x32/'2') */
> + if (resp_buf[3] == 0x32) {
> + /* Write values to the struct */
> + mcu_hwmon->temp_sensor_val[0] =
> + raw_temp_to_milidegree_celsius(resp_buf[4]);
> + mcu_hwmon->temp_sensor_val[1] =
> + raw_temp_to_milidegree_celsius(resp_buf[5]);
What is the point of storing the return values in mcu_hwmon->temp_sensor_val[] ?
> + }
> +
Unnecessary empty line. checkpatch --strict reports this.
> + }
> + *value = mcu_hwmon->temp_sensor_val[channel];
unlock:
> + mutex_unlock(&mcu_hwmon->lock);
> +
> + return ret;
> +}
> +
> +#define raw_fan_val_to_rpm(x, y) ((int)(((x)<<8|(y))/2)*60)
> +static int iei_wt61p803_puzzle_read_fan_speed
> +(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon, int channel, int *value)
> +{
> + int ret;
> + size_t reply_size = 0;
> + unsigned char *resp_buf = mcu_hwmon->response_buffer;
> + unsigned char fan_speed_cmd[4] = {
> + IEI_WT61P803_PUZZLE_CMD_HEADER_START,
> + IEI_WT61P803_PUZZLE_CMD_FAN,
> + IEI_WT61P803_PUZZLE_CMD_FAN_RPM_0
> + };
> +
> + switch (channel) {
> + case 0:
> + fan_speed_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_RPM_0;
> + break;
> + case 1:
> + fan_speed_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_RPM_1;
> + break;
> + case 2:
> + fan_speed_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_RPM_2;
> + break;
> + case 3:
> + fan_speed_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_RPM_3;
> + break;
> + case 4:
> + fan_speed_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_RPM_4;
> + break;
> + default:
> + return -EINVAL;
> + }
This would be much simpler written as
static const u8 fan_speed_cmds[] = {
IEI_WT61P803_PUZZLE_CMD_FAN_RPM_0,
IEI_WT61P803_PUZZLE_CMD_FAN_RPM_1,
IEI_WT61P803_PUZZLE_CMD_FAN_RPM_2,
IEI_WT61P803_PUZZLE_CMD_FAN_RPM_3,
IEI_WT61P803_PUZZLE_CMD_FAN_RPM_4
};
...
fan_speed_cmd[2] = fan_speed_cmds[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 < 0)
goto unlock;
> + if (!ret)
> + mcu_hwmon->fan_speed_val[channel] = raw_fan_val_to_rpm(resp_buf[3],
> + resp_buf[4]);
> +
> + *value = mcu_hwmon->fan_speed_val[channel];
What exactly is the point of storing the result in
mcu_hwmon->fan_speed_val[channel] ?
I won't comment about similar code again, but please drop any such
unnecessary arrays.
> + mutex_unlock(&mcu_hwmon->lock);
> +
> + return 0;
> +}
> +
> +static int iei_wt61p803_puzzle_write_pwm_channel
> +(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon, int channel, long pwm_set_val)
> +{
> + int ret;
> + size_t reply_size = 0;
> + unsigned char *resp_buf = mcu_hwmon->response_buffer;
> + unsigned char pwm_set_cmd[6] = {
> + IEI_WT61P803_PUZZLE_CMD_HEADER_START,
> + IEI_WT61P803_PUZZLE_CMD_FAN,
> + IEI_WT61P803_PUZZLE_CMD_FAN_PWM_WRITE,
> + IEI_WT61P803_PUZZLE_CMD_FAN_PWM_0,
> + 0x00
> + };
> +
> + switch (channel) {
> + case 0:
> + pwm_set_cmd[3] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM_0;
> + break;
> + case 1:
> + pwm_set_cmd[3] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM_1;
> + break;
> + default:
> + return -EINVAL;
> + }
Same as above - it would be much simpler to have IEI_WT61P803_PUZZLE_CMD_FAN_PWM_0
and IEI_WT61P803_PUZZLE_CMD_FAN_PWM_1 in an array and get it from there.
The range check is unnecessary.
> +
> + if (pwm_set_val < 0 || pwm_set_val > IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_VAL)
> + return -EINVAL;
> +
> + /* Add the PWM value to the command */
> + pwm_set_cmd[4] = (char)pwm_set_val;
I think this typecast is quite unnecessary. Besides, it is wrong, since the
value is an unsigned char.
> +
> + 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 < 0)
goto unlock;
> + if (!ret) {
> + /* Store the PWM value */
What for ?
Ah yes, I think I finally get it: All this odd handling is to be able to ignore
errors. But that is not acceptable. If there is an error, report it to the user.
You can't really claim no error to the user when the value wasn't stored.
> + 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)
> + mcu_hwmon->pwm_val[channel] = (int)pwm_set_val;
> + }
> + mutex_unlock(&mcu_hwmon->lock);
> +
> + return 0;
> +}
> +
> +static int iei_wt61p803_puzzle_read_pwm_channel
> +(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon, int channel, int *value)
> +{
> + int ret;
> + size_t reply_size = 0;
> + unsigned char *resp_buf = mcu_hwmon->response_buffer;
> + unsigned char pwm_get_cmd[5] = {
> + IEI_WT61P803_PUZZLE_CMD_HEADER_START,
> + IEI_WT61P803_PUZZLE_CMD_FAN,
> + IEI_WT61P803_PUZZLE_CMD_FAN_PWM_READ,
> + IEI_WT61P803_PUZZLE_CMD_FAN_PWM_0
> + };
> +
> + switch (channel) {
> + case 0:
> + pwm_get_cmd[3] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM_0;
> + break;
> + case 1:
> + pwm_get_cmd[3] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM_1;
> + break;
> + default:
> + return -EINVAL;
> + }
Same comments as before.
> +
> + mutex_lock(&mcu_hwmon->lock);
> + ret = iei_wt61p803_puzzle_write_command(mcu_hwmon->mcu, pwm_get_cmd,
> + sizeof(pwm_get_cmd), resp_buf, &reply_size);
> + if (!ret) {
> + /* Store the PWM value */
> + if (resp_buf[2] == IEI_WT61P803_PUZZLE_CMD_FAN_PWM_READ)
> + mcu_hwmon->pwm_val[channel] = (int)resp_buf[3];
> + }
Same comments as before.
> + *value = mcu_hwmon->pwm_val[channel];
> + mutex_unlock(&mcu_hwmon->lock);
> +
> + 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);
> + int ret, value;
> +
> + switch (type) {
> + case hwmon_pwm:
> + if (attr != hwmon_pwm_input)
> + return -ENODEV;
> + ret = iei_wt61p803_puzzle_read_pwm_channel(mcu_hwmon, channel, &value);
> + if (ret)
> + return ret;
> + *val = (long)value;
> + return ret;
> + case hwmon_fan:
> + if (attr != hwmon_fan_input)
> + return -ENODEV;
> + ret = iei_wt61p803_puzzle_read_fan_speed(mcu_hwmon, channel, &value);
> + if (ret)
> + return ret;
> + *val = (long)value;
Unncecssary typecast. Plase check all typecasts and keep only those which
are really needed (if there are any).
> + return ret;
ret is 0 here.
> + case hwmon_temp:
> + if (attr != hwmon_temp_input)
> + return -ENODEV;
> + ret = iei_wt61p803_puzzle_read_temp_sensor(mcu_hwmon, channel, &value);
> + if (ret)
> + return ret;
> + *val = (long)value;
> + return ret;
ret is 0 here. That is sprinkled through the code. Please
replace with "return 0;" everywhere.
> + default:
> + return -ENODEV;
> + }
> +}
> +
> +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);
> +
> + switch (type) {
> + case hwmon_pwm:
> + if (attr != hwmon_pwm_input)
> + return -ENODEV;
> + if (mcu_hwmon->thermal_cooling_dev_present[channel]) {
> + /*
> + * The Thermal Framework has already claimed this specific PWM
> + * channel.
> + */
> + return -EBUSY;
> + }
This won't happen (the attribute is read-only in this case), and the check is
therefore unnecessary and just adds confusion.
> + return iei_wt61p803_puzzle_write_pwm_channel(mcu_hwmon, channel, val);
> + default:
> + return -ENODEV;
> + }
Unless there is a plan to make other types writable, this switch statement
is unnecessary. Only pwm attributes are writeable, after all, so the code
won't be called for anything else.
> +}
> +
> +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:
> + switch (attr) {
> + case hwmon_pwm_input:
> + if (mcu_hwmon->thermal_cooling_dev_present[channel])
> + return 0444;
> + return 0644;
> + default:
> + return 0;
> + }
> + case hwmon_fan:
> + switch (attr) {
> + case hwmon_fan_input:
> + return 0444;
> + default:
> + return 0;
> + }
> + case hwmon_temp:
> + switch (attr) {
> + case hwmon_temp_input:
> + return 0444;
> + default:
> + return 0;
> + }
> + default:
> + 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;
> +
> + int ret, value;
> +
> + if (!mcu_hwmon)
> + return -EINVAL;
> +
> + ret = iei_wt61p803_puzzle_read_pwm_channel(mcu_hwmon,
> + cdev->pwm_channel, &value);
> + if (ret)
> + return ret;
> +
> + *state = (unsigned long)value;
> +
> + return 0;
> +}
Missing empty line. checkpatch --strict reports this.
> +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;
> +
> + if (!mcu_hwmon)
> + return -EINVAL;
> +
> + 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)
> +{
> + u32 pwm_channel;
> + int ret, num_levels;
> +
> + struct iei_wt61p803_puzzle_thermal_cooling_device *cdev;
> +
> + ret = fwnode_property_read_u32(child, "reg", &pwm_channel);
> + if (ret)
> + return ret;
> +
> + mutex_lock(&mcu_hwmon->lock);
> + mcu_hwmon->thermal_cooling_dev_present[pwm_channel] = true;
> + mutex_unlock(&mcu_hwmon->lock);
> +
> + num_levels = fwnode_property_read_u8_array(child, "cooling-levels", NULL, 0);
> + if (num_levels > 0) {
> + cdev = devm_kzalloc(dev, sizeof(*cdev), GFP_KERNEL);
> + if (!cdev)
> + return -ENOMEM;
> +
> + cdev->cooling_levels = devm_kzalloc(dev, num_levels, 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'");
> + return ret;
> + }
> +
> + snprintf(cdev->name, THERMAL_NAME_LENGTH, "iei_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;
> +
> + mutex_lock(&mcu_hwmon->lock);
> + mcu_hwmon->cdev[pwm_channel] = cdev;
> + mutex_unlock(&mcu_hwmon->lock);
> + }
> + return 0;
> +}
> +
> +static int iei_wt61p803_puzzle_hwmon_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct fwnode_handle *child;
> + struct iei_wt61p803_puzzle_hwmon *mcu_hwmon;
> + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev->parent);
> + struct device *hwmon_dev;
> + int ret;
> +
> + mcu_hwmon = devm_kzalloc(dev, sizeof(*mcu_hwmon), GFP_KERNEL);
> + if (!mcu_hwmon)
> + return -ENOMEM;
> +
> + mcu_hwmon->response_buffer = devm_kzalloc(dev,
> + IEI_WT61P803_PUZZLE_BUF_SIZE, GFP_KERNEL);
> + if (!mcu_hwmon->response_buffer)
> + return -ENOMEM;
> +
> + mcu_hwmon->mcu = mcu;
> + mutex_init(&mcu_hwmon->lock);
> + platform_set_drvdata(pdev, mcu_hwmon);
> +
> + hwmon_dev = devm_hwmon_device_register_with_info(dev,
> + "iei_wt61p803_puzzle",
> + mcu_hwmon,
> + &iei_wt61p803_puzzle_chip_info,
> + NULL);
> +
> + /* 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 <[email protected]>");
> +MODULE_LICENSE("GPL");
> --
> 2.26.2
>
On Wed, Sep 30, 2020 at 10:31 PM Guenter Roeck <[email protected]> wrote:
>
> On Sat, Sep 26, 2020 at 03:55:10PM +0200, Luka Kovacic wrote:
> > 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 <[email protected]>
> > Cc: Luka Perkov <[email protected]>
> > Cc: Robert Marko <[email protected]>
> > ---
> > drivers/hwmon/Kconfig | 8 +
> > drivers/hwmon/Makefile | 1 +
> > drivers/hwmon/iei-wt61p803-puzzle-hwmon.c | 511 ++++++++++++++++++++++
> > 3 files changed, 520 insertions(+)
> > create mode 100644 drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
> >
> > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> > index 8dc28b26916e..ff279df9bf40 100644
> > --- 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
> > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> > index a8f4b35b136b..b0afb2d6896f 100644
> > --- a/drivers/hwmon/Makefile
> > +++ b/drivers/hwmon/Makefile
> > @@ -83,6 +83,7 @@ obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o
> > 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
> > diff --git a/drivers/hwmon/iei-wt61p803-puzzle-hwmon.c b/drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
> > new file mode 100644
> > index 000000000000..2691b943936b
> > --- /dev/null
> > +++ b/drivers/hwmon/iei-wt61p803-puzzle-hwmon.c
> > @@ -0,0 +1,511 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/* iEi WT61P803 PUZZLE MCU HWMON Driver
> > + *
> > + * Copyright (C) 2020 Sartura Ltd.
> > + * Author: Luka Kovacic <[email protected]>
> > + */
> > +
> > +#include <linux/err.h>
> > +#include <linux/hwmon-sysfs.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_TEMP_NUM 2
> > +#define IEI_WT61P803_PUZZLE_HWMON_MAX_FAN_NUM 5
> > +#define IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_NUM 2
> > +#define IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_VAL 255
> > +
> > +/**
> > + * struct iei_wt61p803_puzzle_thermal_cooling_device - Thermal cooling device instance
> > + *
> > + * @mcu_hwmon: MCU HWMON struct pointer
> > + * @tcdev: Thermal cooling device pointer
> > + * @name: Thermal cooling device name
> > + * @pwm_channel: PWM channel (0 or 1)
> > + * @cooling_levels: Thermal cooling device cooling levels
> > + */
> > +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
> > + * @lock General member lock
> > + * @response_buffer Global MCU response buffer allocation
> > + * @temp_sensor_val: Temperature sensor values
> > + * @fan_speed_val: FAN speed (RPM) values
> > + * @pwm_val: PWM values (0-255)
> > + * @thermal_cooling_dev_present: Per-channel thermal cooling device control
> > + * @cdev: Per-channel thermal cooling device private structure
> > + */
> > +struct iei_wt61p803_puzzle_hwmon {
> > + struct iei_wt61p803_puzzle *mcu;
> > + struct mutex lock;
> > + unsigned char *response_buffer;
> > + int temp_sensor_val[IEI_WT61P803_PUZZLE_HWMON_MAX_TEMP_NUM];
> > + int fan_speed_val[IEI_WT61P803_PUZZLE_HWMON_MAX_FAN_NUM];
> > + int pwm_val[IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_NUM];
> > + bool thermal_cooling_dev_present[IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_NUM];
> > + struct iei_wt61p803_puzzle_thermal_cooling_device
> > + *cdev[IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_NUM];
> > +};
> > +
> > +#define raw_temp_to_milidegree_celsius(x) ((int)((x - 0x80)*1000))
>
> Spaces around '*', please, and (x). checkpatch --strict will tell about it.
>
> > +static int iei_wt61p803_puzzle_read_temp_sensor
> > +(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon, int channel, int *value)
>
> Odd multi-line alignment. Please use either
>
> static int
> iei_wt61p803_puzzle_read_temp_sensor(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon, int channel,
> int *value)
>
> or
>
> static int iei_wt61p803_puzzle_read_temp_sensor(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon,
> int channel, int *value)
>
> There are lots of those in this driver. Please run
> checkpatch --strict and fix what it reports.
>
> > +{
> > + int ret;
> > + size_t reply_size = 0;
> > + 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
> > + };
> > +
> > + if (channel > 1 && channel < 0)
> > + return -EINVAL;
>
> Unnecessary range check.
>
> > +
> > + 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 < 0)
> goto unlock;
>
> > + if (!ret) {
> > + /* Check the number of NTC values (should be 0x32/'2') */
> > + if (resp_buf[3] == 0x32) {
> > + /* Write values to the struct */
> > + mcu_hwmon->temp_sensor_val[0] =
> > + raw_temp_to_milidegree_celsius(resp_buf[4]);
> > + mcu_hwmon->temp_sensor_val[1] =
> > + raw_temp_to_milidegree_celsius(resp_buf[5]);
>
> What is the point of storing the return values in mcu_hwmon->temp_sensor_val[] ?
>
> > + }
> > +
>
> Unnecessary empty line. checkpatch --strict reports this.
>
> > + }
> > + *value = mcu_hwmon->temp_sensor_val[channel];
>
> unlock:
>
> > + mutex_unlock(&mcu_hwmon->lock);
> > +
> > + return ret;
> > +}
> > +
> > +#define raw_fan_val_to_rpm(x, y) ((int)(((x)<<8|(y))/2)*60)
> > +static int iei_wt61p803_puzzle_read_fan_speed
> > +(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon, int channel, int *value)
> > +{
> > + int ret;
> > + size_t reply_size = 0;
> > + unsigned char *resp_buf = mcu_hwmon->response_buffer;
> > + unsigned char fan_speed_cmd[4] = {
> > + IEI_WT61P803_PUZZLE_CMD_HEADER_START,
> > + IEI_WT61P803_PUZZLE_CMD_FAN,
> > + IEI_WT61P803_PUZZLE_CMD_FAN_RPM_0
> > + };
> > +
> > + switch (channel) {
> > + case 0:
> > + fan_speed_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_RPM_0;
> > + break;
> > + case 1:
> > + fan_speed_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_RPM_1;
> > + break;
> > + case 2:
> > + fan_speed_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_RPM_2;
> > + break;
> > + case 3:
> > + fan_speed_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_RPM_3;
> > + break;
> > + case 4:
> > + fan_speed_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_RPM_4;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
>
> This would be much simpler written as
>
> static const u8 fan_speed_cmds[] = {
> IEI_WT61P803_PUZZLE_CMD_FAN_RPM_0,
> IEI_WT61P803_PUZZLE_CMD_FAN_RPM_1,
> IEI_WT61P803_PUZZLE_CMD_FAN_RPM_2,
> IEI_WT61P803_PUZZLE_CMD_FAN_RPM_3,
> IEI_WT61P803_PUZZLE_CMD_FAN_RPM_4
> };
> ...
>
> fan_speed_cmd[2] = fan_speed_cmds[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 < 0)
> goto unlock;
>
> > + if (!ret)
> > + mcu_hwmon->fan_speed_val[channel] = raw_fan_val_to_rpm(resp_buf[3],
> > + resp_buf[4]);
> > +
> > + *value = mcu_hwmon->fan_speed_val[channel];
>
> What exactly is the point of storing the result in
> mcu_hwmon->fan_speed_val[channel] ?
>
> I won't comment about similar code again, but please drop any such
> unnecessary arrays.
>
> > + mutex_unlock(&mcu_hwmon->lock);
> > +
> > + return 0;
> > +}
> > +
> > +static int iei_wt61p803_puzzle_write_pwm_channel
> > +(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon, int channel, long pwm_set_val)
> > +{
> > + int ret;
> > + size_t reply_size = 0;
> > + unsigned char *resp_buf = mcu_hwmon->response_buffer;
> > + unsigned char pwm_set_cmd[6] = {
> > + IEI_WT61P803_PUZZLE_CMD_HEADER_START,
> > + IEI_WT61P803_PUZZLE_CMD_FAN,
> > + IEI_WT61P803_PUZZLE_CMD_FAN_PWM_WRITE,
> > + IEI_WT61P803_PUZZLE_CMD_FAN_PWM_0,
> > + 0x00
> > + };
> > +
> > + switch (channel) {
> > + case 0:
> > + pwm_set_cmd[3] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM_0;
> > + break;
> > + case 1:
> > + pwm_set_cmd[3] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM_1;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
>
> Same as above - it would be much simpler to have IEI_WT61P803_PUZZLE_CMD_FAN_PWM_0
> and IEI_WT61P803_PUZZLE_CMD_FAN_PWM_1 in an array and get it from there.
> The range check is unnecessary.
>
> > +
> > + if (pwm_set_val < 0 || pwm_set_val > IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_VAL)
> > + return -EINVAL;
> > +
> > + /* Add the PWM value to the command */
> > + pwm_set_cmd[4] = (char)pwm_set_val;
>
> I think this typecast is quite unnecessary. Besides, it is wrong, since the
> value is an unsigned char.
>
> > +
> > + 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 < 0)
> goto unlock;
>
> > + if (!ret) {
> > + /* Store the PWM value */
>
> What for ?
>
> Ah yes, I think I finally get it: All this odd handling is to be able to ignore
> errors. But that is not acceptable. If there is an error, report it to the user.
> You can't really claim no error to the user when the value wasn't stored.
>
> > + 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)
> > + mcu_hwmon->pwm_val[channel] = (int)pwm_set_val;
> > + }
> > + mutex_unlock(&mcu_hwmon->lock);
> > +
> > + return 0;
> > +}
> > +
> > +static int iei_wt61p803_puzzle_read_pwm_channel
> > +(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon, int channel, int *value)
> > +{
> > + int ret;
> > + size_t reply_size = 0;
> > + unsigned char *resp_buf = mcu_hwmon->response_buffer;
> > + unsigned char pwm_get_cmd[5] = {
> > + IEI_WT61P803_PUZZLE_CMD_HEADER_START,
> > + IEI_WT61P803_PUZZLE_CMD_FAN,
> > + IEI_WT61P803_PUZZLE_CMD_FAN_PWM_READ,
> > + IEI_WT61P803_PUZZLE_CMD_FAN_PWM_0
> > + };
> > +
> > + switch (channel) {
> > + case 0:
> > + pwm_get_cmd[3] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM_0;
> > + break;
> > + case 1:
> > + pwm_get_cmd[3] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM_1;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
>
> Same comments as before.
>
> > +
> > + mutex_lock(&mcu_hwmon->lock);
> > + ret = iei_wt61p803_puzzle_write_command(mcu_hwmon->mcu, pwm_get_cmd,
> > + sizeof(pwm_get_cmd), resp_buf, &reply_size);
> > + if (!ret) {
> > + /* Store the PWM value */
> > + if (resp_buf[2] == IEI_WT61P803_PUZZLE_CMD_FAN_PWM_READ)
> > + mcu_hwmon->pwm_val[channel] = (int)resp_buf[3];
> > + }
>
> Same comments as before.
>
> > + *value = mcu_hwmon->pwm_val[channel];
> > + mutex_unlock(&mcu_hwmon->lock);
> > +
> > + 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);
> > + int ret, value;
> > +
> > + switch (type) {
> > + case hwmon_pwm:
> > + if (attr != hwmon_pwm_input)
> > + return -ENODEV;
> > + ret = iei_wt61p803_puzzle_read_pwm_channel(mcu_hwmon, channel, &value);
> > + if (ret)
> > + return ret;
> > + *val = (long)value;
> > + return ret;
> > + case hwmon_fan:
> > + if (attr != hwmon_fan_input)
> > + return -ENODEV;
> > + ret = iei_wt61p803_puzzle_read_fan_speed(mcu_hwmon, channel, &value);
> > + if (ret)
> > + return ret;
> > + *val = (long)value;
>
> Unncecssary typecast. Plase check all typecasts and keep only those which
> are really needed (if there are any).
>
> > + return ret;
>
> ret is 0 here.
>
> > + case hwmon_temp:
> > + if (attr != hwmon_temp_input)
> > + return -ENODEV;
> > + ret = iei_wt61p803_puzzle_read_temp_sensor(mcu_hwmon, channel, &value);
> > + if (ret)
> > + return ret;
> > + *val = (long)value;
> > + return ret;
>
> ret is 0 here. That is sprinkled through the code. Please
> replace with "return 0;" everywhere.
>
> > + default:
> > + return -ENODEV;
> > + }
> > +}
> > +
> > +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);
> > +
> > + switch (type) {
> > + case hwmon_pwm:
> > + if (attr != hwmon_pwm_input)
> > + return -ENODEV;
> > + if (mcu_hwmon->thermal_cooling_dev_present[channel]) {
> > + /*
> > + * The Thermal Framework has already claimed this specific PWM
> > + * channel.
> > + */
> > + return -EBUSY;
> > + }
> This won't happen (the attribute is read-only in this case), and the check is
> therefore unnecessary and just adds confusion.
>
> > + return iei_wt61p803_puzzle_write_pwm_channel(mcu_hwmon, channel, val);
> > + default:
> > + return -ENODEV;
> > + }
>
> Unless there is a plan to make other types writable, this switch statement
> is unnecessary. Only pwm attributes are writeable, after all, so the code
> won't be called for anything else.
>
> > +}
> > +
> > +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:
> > + switch (attr) {
> > + case hwmon_pwm_input:
> > + if (mcu_hwmon->thermal_cooling_dev_present[channel])
> > + return 0444;
> > + return 0644;
> > + default:
> > + return 0;
> > + }
> > + case hwmon_fan:
> > + switch (attr) {
> > + case hwmon_fan_input:
> > + return 0444;
> > + default:
> > + return 0;
> > + }
> > + case hwmon_temp:
> > + switch (attr) {
> > + case hwmon_temp_input:
> > + return 0444;
> > + default:
> > + return 0;
> > + }
> > + default:
> > + 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;
> > +
> > + int ret, value;
> > +
> > + if (!mcu_hwmon)
> > + return -EINVAL;
> > +
> > + ret = iei_wt61p803_puzzle_read_pwm_channel(mcu_hwmon,
> > + cdev->pwm_channel, &value);
> > + if (ret)
> > + return ret;
> > +
> > + *state = (unsigned long)value;
> > +
> > + return 0;
> > +}
>
> Missing empty line. checkpatch --strict reports this.
>
> > +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;
> > +
> > + if (!mcu_hwmon)
> > + return -EINVAL;
> > +
> > + 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)
> > +{
> > + u32 pwm_channel;
> > + int ret, num_levels;
> > +
> > + struct iei_wt61p803_puzzle_thermal_cooling_device *cdev;
> > +
> > + ret = fwnode_property_read_u32(child, "reg", &pwm_channel);
> > + if (ret)
> > + return ret;
> > +
> > + mutex_lock(&mcu_hwmon->lock);
> > + mcu_hwmon->thermal_cooling_dev_present[pwm_channel] = true;
> > + mutex_unlock(&mcu_hwmon->lock);
> > +
> > + num_levels = fwnode_property_read_u8_array(child, "cooling-levels", NULL, 0);
> > + if (num_levels > 0) {
> > + cdev = devm_kzalloc(dev, sizeof(*cdev), GFP_KERNEL);
> > + if (!cdev)
> > + return -ENOMEM;
> > +
> > + cdev->cooling_levels = devm_kzalloc(dev, num_levels, 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'");
> > + return ret;
> > + }
> > +
> > + snprintf(cdev->name, THERMAL_NAME_LENGTH, "iei_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;
> > +
> > + mutex_lock(&mcu_hwmon->lock);
> > + mcu_hwmon->cdev[pwm_channel] = cdev;
> > + mutex_unlock(&mcu_hwmon->lock);
> > + }
> > + return 0;
> > +}
> > +
> > +static int iei_wt61p803_puzzle_hwmon_probe(struct platform_device *pdev)
> > +{
> > + struct device *dev = &pdev->dev;
> > + struct fwnode_handle *child;
> > + struct iei_wt61p803_puzzle_hwmon *mcu_hwmon;
> > + struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev->parent);
> > + struct device *hwmon_dev;
> > + int ret;
> > +
> > + mcu_hwmon = devm_kzalloc(dev, sizeof(*mcu_hwmon), GFP_KERNEL);
> > + if (!mcu_hwmon)
> > + return -ENOMEM;
> > +
> > + mcu_hwmon->response_buffer = devm_kzalloc(dev,
> > + IEI_WT61P803_PUZZLE_BUF_SIZE, GFP_KERNEL);
> > + if (!mcu_hwmon->response_buffer)
> > + return -ENOMEM;
> > +
> > + mcu_hwmon->mcu = mcu;
> > + mutex_init(&mcu_hwmon->lock);
> > + platform_set_drvdata(pdev, mcu_hwmon);
> > +
> > + hwmon_dev = devm_hwmon_device_register_with_info(dev,
> > + "iei_wt61p803_puzzle",
> > + mcu_hwmon,
> > + &iei_wt61p803_puzzle_chip_info,
> > + NULL);
> > +
> > + /* 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 <[email protected]>");
> > +MODULE_LICENSE("GPL");
> > --
> > 2.26.2
> >
Hello Guenter,
Thanks for reviewing the patch.
I'll resolve the code styling issues, fix the error handling, and
other issues you
have pointed out.
Kind regards,
Luka