Skip to contentSkip to conversation

Add Khadas MCU and fan control support for Khadas Edge 2

PATCH Superseded [email protected] 2 participants 14 messages Jun 26, 2025

Subsystems (5)

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

cover Add Khadas MCU and fan control support for Khadas Edge 2

From: Muhammed Efe Cetin <[email protected]>

This patch series adds support for the Khadas MCU and fan control for Khadas Edge 2 board.

The first patch updates devicetree bindings for new cooling-levels property.
The second mfd patch removes unused nvmem code.
The third patch adds the Khadas Edge 2 registers to the mfd driver.
The fourth patch adds support for Khadas Edge 2's new fan control registers to MCU thermal driver and the last one adds the Khadas Edge 2 device tree node for the MCU and fan control. And the final patch adds the Khadas Edge 2 device tree node for the MCU and fan control.

Muhammed Efe Cetin (5):
dt-bindings: mfd: khadas-mcu: add cooling-levels property
mfd: khadas-mcu: drop unused nvmem code
mfd: khadas-mcu: add Khadas Edge 2 registers
thermal: khadas_mcu_fan: add support for Khadas Edge 2
arm64: dts: rockchip: add Khadas MCU and fan control nodes

 .../devicetree/bindings/mfd/khadas,mcu.yaml   |  9 ++-
 .../dts/rockchip/rk3588s-khadas-edge2.dts     | 59 ++++++++++++++
 drivers/mfd/khadas-mcu.c                      | 19 ++---
 drivers/thermal/khadas_mcu_fan.c              | 76 ++++++++++++++++++-
 include/linux/mfd/khadas-mcu.h                | 25 ++++++
 5 files changed, 170 insertions(+), 18 deletions(-)
Signature · 1 line
2.49.0
#1 lore raw

PATCH 1/5 dt-bindings: mfd: khadas-mcu: add cooling-levels property

From: Muhammed Efe Cetin <[email protected]>

Add fan controlling step property which is used by Khadas Edge 2.

---
 Documentation/devicetree/bindings/mfd/khadas,mcu.yaml | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)
Documentation/devicetree/bindings/mfd/khadas,mcu.yaml +8 -1
index 084960fd5..f69643aae 100644
@@ -11,7 +11,7 @@ maintainers:
 
 description: |
   Khadas embeds a microcontroller on their VIM and Edge boards adding some
-  system feature as PWM Fan control (for VIM2 rev14 or VIM3), User memory
+  system feature as PWM Fan control (for VIM2 rev14, VIM3, Edge2), User memory
   storage, IR/Key resume control, system power LED control and more.
 
 properties:
@@ -22,6 +22,13 @@ properties:
   "#cooling-cells": # Only needed for boards having FAN control feature
     const: 2
 
+  cooling-levels:
+    description:
+      Max speed of PWM fan, from 0 to 100. This property is necessary for Khadas Edge 2.
+    $ref: /schemas/types.yaml#/definitions/uint32-array
+    items:
+      maximum: 100
+
   reg:
     maxItems: 1
 
Signature · 1 line
2.49.0
#2 lore raw

Re: [PATCH 1/5] dt-bindings: mfd: khadas-mcu: add cooling-levels property

Quoted 23 lines · On 26/06/2025 16:04, [email protected] wrote:

From: Muhammed Efe Cetin <[email protected]>

Add fan controlling step property which is used by Khadas Edge 2.

---
  Documentation/devicetree/bindings/mfd/khadas,mcu.yaml | 9 ++++++++-
  1 file changed, 8 insertions(+), 1 deletion(-)
Documentation/devicetree/bindings/mfd/khadas,mcu.yaml +1 -1
index 084960fd5..f69643aae 100644
@@ -11,7 +11,7 @@ maintainers:
 
  description: |
    Khadas embeds a microcontroller on their VIM and Edge boards adding some
-  system feature as PWM Fan control (for VIM2 rev14 or VIM3), User memory
+  system feature as PWM Fan control (for VIM2 rev14, VIM3, Edge2), User memory
    storage, IR/Key resume control, system power LED control and more.
 
  properties:

Documentation/devicetree/bindings/mfd/khadas,mcu.yaml properties: · line 30

Quoted 9 lines
Documentation/devicetree/bindings/mfd/khadas,mcu.yaml +6 -0
@@ -22,6 +22,13 @@ properties:
    "#cooling-cells": # Only needed for boards having FAN control feature
      const: 2
 
+  cooling-levels:
+    description:
+      Max speed of PWM fan, from 0 to 100. This property is necessary for Khadas Edge 2.
+    $ref: /schemas/types.yaml#/definitions/uint32-array
+    items:
+      maximum: 100

The cooling level can be detected dynamically by reading the MCU register, so just set the max_level to 100 when edge2 is detected, and drop this property.

Neil

Quoted 4 lines
Documentation/devicetree/bindings/mfd/khadas,mcu.yaml +1 -0
+
    reg:
      maxItems: 1
 
#3 lore raw

PATCH 2/5 mfd: khadas-mcu: drop unused nvmem code

In reply to [email protected]

From: Muhammed Efe Cetin <[email protected]>

Drop "khadas-mcu-user-mem" code since it is not used anywhere.

---
 drivers/mfd/khadas-mcu.c | 11 -----------
 1 file changed, 11 deletions(-)
drivers/mfd/khadas-mcu.c +0 -11
index ba981a788..ca4bd6cf5 100644
@@ -80,10 +80,6 @@ static struct mfd_cell khadas_mcu_fan_cells[] = {
 	{ .name = "khadas-mcu-fan-ctrl", },
 };
 
-static struct mfd_cell khadas_mcu_cells[] = {
-	{ .name = "khadas-mcu-user-mem", },
-};
-
 static int khadas_mcu_probe(struct i2c_client *client)
 {
 	struct device *dev = &client->dev;
@@ -105,13 +101,6 @@ static int khadas_mcu_probe(struct i2c_client *client)
 		return ret;
 	}
 
-	ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
-				   khadas_mcu_cells,
-				   ARRAY_SIZE(khadas_mcu_cells),
-				   NULL, 0, NULL);
-	if (ret)
-		return ret;
-
 	if (of_property_present(dev->of_node, "#cooling-cells"))
 		return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
 					    khadas_mcu_fan_cells,
Signature · 1 line
2.49.0
maintainer Reviewed-by on v1 2/5
#4 lore raw

Re: [PATCH 2/5] mfd: khadas-mcu: drop unused nvmem code

Quoted 28 lines · On 26/06/2025 16:04, [email protected] wrote:

From: Muhammed Efe Cetin <[email protected]>

Drop "khadas-mcu-user-mem" code since it is not used anywhere.

---
  drivers/mfd/khadas-mcu.c | 11 -----------
  1 file changed, 11 deletions(-)
drivers/mfd/khadas-mcu.c +0 -4
index ba981a788..ca4bd6cf5 100644
@@ -80,10 +80,6 @@ static struct mfd_cell khadas_mcu_fan_cells[] = {
  	{ .name = "khadas-mcu-fan-ctrl", },
  };
 
-static struct mfd_cell khadas_mcu_cells[] = {
-	{ .name = "khadas-mcu-user-mem", },
-};
-
  static int khadas_mcu_probe(struct i2c_client *client)
  {
  	struct device *dev = &client->dev;
@@ -105,13 +101,6 @@ static int khadas_mcu_probe(struct i2c_client *client)
  		return ret;
  	}
 

drivers/mfd/khadas-mcu.c static int khadas_mcu_probe(struct i2c_client *client) · line 106

Quoted 10 lines
drivers/mfd/khadas-mcu.c +0 -7
-	ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
-				   khadas_mcu_cells,
-				   ARRAY_SIZE(khadas_mcu_cells),
-				   NULL, 0, NULL);
-	if (ret)
-		return ret;
-
  	if (of_property_present(dev->of_node, "#cooling-cells"))
  		return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
  					    khadas_mcu_fan_cells,
#5 lore raw

PATCH 3/5 mfd: khadas-mcu: add Khadas Edge 2 registers

In reply to [email protected]

From: Muhammed Efe Cetin <[email protected]>

Add Khadas Edge 2 MCU registers to khadas-mcu.h and adjust some properties. Also, i've added some registers with V2 suffix,
as same addresses were already exist for previous boards.

---
 drivers/mfd/khadas-mcu.c       |  8 ++++++--
 include/linux/mfd/khadas-mcu.h | 25 +++++++++++++++++++++++++
 2 files changed, 31 insertions(+), 2 deletions(-)
drivers/mfd/khadas-mcu.c +6 -2
index ca4bd6cf5..199ed4925 100644
@@ -26,6 +26,10 @@ static bool khadas_mcu_reg_volatile(struct device *dev, unsigned int reg)
 	case KHADAS_MCU_CHECK_USER_PASSWD_REG:
 	case KHADAS_MCU_WOL_INIT_START_REG:
 	case KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG:
+	case KHADAS_MCU_LED_ON_RAM_REG:
+	case KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG_V2:
+	case KHADAS_MCU_WDT_EN_REG:
+	case KHADAS_MCU_SYS_RST_REG:
 		return true;
 	default:
 		return false;
@@ -69,14 +73,14 @@ static const struct regmap_config khadas_mcu_regmap_config = {
 	.reg_bits	= 8,
 	.reg_stride	= 1,
 	.val_bits	= 8,
-	.max_register	= KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG,
+	.max_register	= KHADAS_MCU_SYS_RST_REG,
 	.volatile_reg	= khadas_mcu_reg_volatile,
 	.writeable_reg	= khadas_mcu_reg_writeable,
 	.cache_type	= REGCACHE_MAPLE,
 };
 
 static struct mfd_cell khadas_mcu_fan_cells[] = {
-	/* VIM1/2 Rev13+ and VIM3 only */
+	/* VIM1/2 Rev13+, VIM3 and Edge2 only */
 	{ .name = "khadas-mcu-fan-ctrl", },
 };
 
include/linux/mfd/khadas-mcu.h +25 -0
index a99ba2ed0..de570e159 100644
@@ -35,26 +35,45 @@
 #define KHADAS_MCU_FACTORY_TEST_REG		0x16 /* R */
 #define KHADAS_MCU_BOOT_MODE_REG		0x20 /* RW */
 #define KHADAS_MCU_BOOT_EN_WOL_REG		0x21 /* RW */
+#define KHADAS_MCU_BOOT_EN_DCIN_REG_V2		0x21 /* RW */
 #define KHADAS_MCU_BOOT_EN_RTC_REG		0x22 /* RW */
 #define KHADAS_MCU_BOOT_EN_EXP_REG		0x23 /* RW */
+#define KHADAS_MCU_LED_MODE_ON_REG_V2		0x23 /* RW */
+#define KHADAS_MCU_LED_MODE_OFF_REG_V2		0x24 /* RW */
 #define KHADAS_MCU_BOOT_EN_IR_REG		0x24 /* RW */
 #define KHADAS_MCU_BOOT_EN_DCIN_REG		0x25 /* RW */
+#define KHADAS_MCU_RGB_ON_R_REG			0x25 /* RW */
+#define KHADAS_MCU_RGB_ON_G_REG			0x26 /* RW */
 #define KHADAS_MCU_BOOT_EN_KEY_REG		0x26 /* RW */
+#define KHADAS_MCU_RGB_ON_B_REG			0x27 /* RW */
 #define KHADAS_MCU_KEY_MODE_REG			0x27 /* RW */
+#define KHADAS_MCU_RGB_OFF_R_REG		0x28 /* RW */
 #define KHADAS_MCU_LED_MODE_ON_REG		0x28 /* RW */
+#define KHADAS_MCU_RGB_OFF_G_REG		0x29 /* RW */
 #define KHADAS_MCU_LED_MODE_OFF_REG		0x29 /* RW */
+#define KHADAS_MCU_RGB_OFF_B_REG		0x2a /* RW */
+#define KHADAS_MCU_REST_CONF_REG		0x2c /* WO */
 #define KHADAS_MCU_SHUTDOWN_NORMAL_REG		0x2c /* RW */
 #define KHADAS_MCU_MAC_SWITCH_REG		0x2d /* RW */
 #define KHADAS_MCU_MCU_SLEEP_MODE_REG		0x2e /* RW */
+#define KHADAS_MCU_BOOT_EN_IR_REG_V2		0x2f /* RW */
 #define KHADAS_MCU_IR_CODE1_0_REG		0x2f /* RW */
 #define KHADAS_MCU_IR_CODE1_1_REG		0x30 /* RW */
+#define KHADAS_MCU_IR1_CUST1_REG		0x30 /* RW */
 #define KHADAS_MCU_IR_CODE1_2_REG		0x31 /* RW */
+#define KHADAS_MCU_IR1_CUST2_REG		0x31 /* RW */
 #define KHADAS_MCU_IR_CODE1_3_REG		0x32 /* RW */
+#define KHADAS_MCU_IR1_ORDER1_REG		0x32 /* RW */
 #define KHADAS_MCU_USB_PCIE_SWITCH_REG		0x33 /* RW */
+#define KHADAS_MCU_IR1_ORDER2_REG		0x33 /* RW */
+#define KHADAS_MCU_IR2_CUST1_REG		0x34 /* RW */
 #define KHADAS_MCU_IR_CODE2_0_REG		0x34 /* RW */
 #define KHADAS_MCU_IR_CODE2_1_REG		0x35 /* RW */
+#define KHADAS_MCU_IR2_CUST2_REG		0x35 /* RW */
 #define KHADAS_MCU_IR_CODE2_2_REG		0x36 /* RW */
+#define KHADAS_MCU_IR2_ORDER1_REG		0x36 /* RW */
 #define KHADAS_MCU_IR_CODE2_3_REG		0x37 /* RW */
+#define KHADAS_MCU_IR2_ORDER2_REG		0x36 /* RW */
 #define KHADAS_MCU_PASSWD_USER_0_REG		0x40 /* RW */
 #define KHADAS_MCU_PASSWD_USER_1_REG		0x41 /* RW */
 #define KHADAS_MCU_PASSWD_USER_2_REG		0x42 /* RW */
@@ -69,6 +88,11 @@
 #define KHADAS_MCU_SHUTDOWN_NORMAL_STATUS_REG	0x86 /* RO */
 #define KHADAS_MCU_WOL_INIT_START_REG		0x87 /* WO */
 #define KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG	0x88 /* WO */
+#define KHADAS_MCU_LED_ON_RAM_REG		0x89 /* WO */
+#define KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG_V2	0x8A /* WO */
+#define KHADAS_MCU_WDT_EN_REG			0x8B /* WO */
+#define KHADAS_MCU_SYS_RST_REG			0x91 /* WO */
+
 
 enum {
 	KHADAS_BOARD_VIM1 = 0x1,
@@ -76,6 +100,7 @@ enum {
 	KHADAS_BOARD_VIM3,
 	KHADAS_BOARD_EDGE = 0x11,
 	KHADAS_BOARD_EDGE_V,
+	KHADAS_BOARD_EDGE2,
 };
 
 /**
Signature · 1 line
2.49.0
maintainer Reviewed-by on v1 3/5
#6 lore raw

Re: [PATCH 3/5] mfd: khadas-mcu: add Khadas Edge 2 registers

Quoted 107 lines · On 26/06/2025 16:04, [email protected] wrote:

From: Muhammed Efe Cetin <[email protected]>

Add Khadas Edge 2 MCU registers to khadas-mcu.h and adjust some properties. Also, i've added some registers with V2 suffix,
as same addresses were already exist for previous boards.

---
  drivers/mfd/khadas-mcu.c       |  8 ++++++--
  include/linux/mfd/khadas-mcu.h | 25 +++++++++++++++++++++++++
  2 files changed, 31 insertions(+), 2 deletions(-)
drivers/mfd/khadas-mcu.c +6 -2
index ca4bd6cf5..199ed4925 100644
@@ -26,6 +26,10 @@ static bool khadas_mcu_reg_volatile(struct device *dev, unsigned int reg)
  	case KHADAS_MCU_CHECK_USER_PASSWD_REG:
  	case KHADAS_MCU_WOL_INIT_START_REG:
  	case KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG:
+	case KHADAS_MCU_LED_ON_RAM_REG:
+	case KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG_V2:
+	case KHADAS_MCU_WDT_EN_REG:
+	case KHADAS_MCU_SYS_RST_REG:
  		return true;
  	default:
  		return false;
@@ -69,14 +73,14 @@ static const struct regmap_config khadas_mcu_regmap_config = {
  	.reg_bits	= 8,
  	.reg_stride	= 1,
  	.val_bits	= 8,
-	.max_register	= KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG,
+	.max_register	= KHADAS_MCU_SYS_RST_REG,
  	.volatile_reg	= khadas_mcu_reg_volatile,
  	.writeable_reg	= khadas_mcu_reg_writeable,
  	.cache_type	= REGCACHE_MAPLE,
  };
 
  static struct mfd_cell khadas_mcu_fan_cells[] = {
-	/* VIM1/2 Rev13+ and VIM3 only */
+	/* VIM1/2 Rev13+, VIM3 and Edge2 only */
  	{ .name = "khadas-mcu-fan-ctrl", },
  };
 
include/linux/mfd/khadas-mcu.h +24 -0
index a99ba2ed0..de570e159 100644
@@ -35,26 +35,45 @@
  #define KHADAS_MCU_FACTORY_TEST_REG		0x16 /* R */
  #define KHADAS_MCU_BOOT_MODE_REG		0x20 /* RW */
  #define KHADAS_MCU_BOOT_EN_WOL_REG		0x21 /* RW */
+#define KHADAS_MCU_BOOT_EN_DCIN_REG_V2		0x21 /* RW */
  #define KHADAS_MCU_BOOT_EN_RTC_REG		0x22 /* RW */
  #define KHADAS_MCU_BOOT_EN_EXP_REG		0x23 /* RW */
+#define KHADAS_MCU_LED_MODE_ON_REG_V2		0x23 /* RW */
+#define KHADAS_MCU_LED_MODE_OFF_REG_V2		0x24 /* RW */
  #define KHADAS_MCU_BOOT_EN_IR_REG		0x24 /* RW */
  #define KHADAS_MCU_BOOT_EN_DCIN_REG		0x25 /* RW */
+#define KHADAS_MCU_RGB_ON_R_REG			0x25 /* RW */
+#define KHADAS_MCU_RGB_ON_G_REG			0x26 /* RW */
  #define KHADAS_MCU_BOOT_EN_KEY_REG		0x26 /* RW */
+#define KHADAS_MCU_RGB_ON_B_REG			0x27 /* RW */
  #define KHADAS_MCU_KEY_MODE_REG			0x27 /* RW */
+#define KHADAS_MCU_RGB_OFF_R_REG		0x28 /* RW */
  #define KHADAS_MCU_LED_MODE_ON_REG		0x28 /* RW */
+#define KHADAS_MCU_RGB_OFF_G_REG		0x29 /* RW */
  #define KHADAS_MCU_LED_MODE_OFF_REG		0x29 /* RW */
+#define KHADAS_MCU_RGB_OFF_B_REG		0x2a /* RW */
+#define KHADAS_MCU_REST_CONF_REG		0x2c /* WO */
  #define KHADAS_MCU_SHUTDOWN_NORMAL_REG		0x2c /* RW */
  #define KHADAS_MCU_MAC_SWITCH_REG		0x2d /* RW */
  #define KHADAS_MCU_MCU_SLEEP_MODE_REG		0x2e /* RW */
+#define KHADAS_MCU_BOOT_EN_IR_REG_V2		0x2f /* RW */
  #define KHADAS_MCU_IR_CODE1_0_REG		0x2f /* RW */
  #define KHADAS_MCU_IR_CODE1_1_REG		0x30 /* RW */
+#define KHADAS_MCU_IR1_CUST1_REG		0x30 /* RW */
  #define KHADAS_MCU_IR_CODE1_2_REG		0x31 /* RW */
+#define KHADAS_MCU_IR1_CUST2_REG		0x31 /* RW */
  #define KHADAS_MCU_IR_CODE1_3_REG		0x32 /* RW */
+#define KHADAS_MCU_IR1_ORDER1_REG		0x32 /* RW */
  #define KHADAS_MCU_USB_PCIE_SWITCH_REG		0x33 /* RW */
+#define KHADAS_MCU_IR1_ORDER2_REG		0x33 /* RW */
+#define KHADAS_MCU_IR2_CUST1_REG		0x34 /* RW */
  #define KHADAS_MCU_IR_CODE2_0_REG		0x34 /* RW */
  #define KHADAS_MCU_IR_CODE2_1_REG		0x35 /* RW */
+#define KHADAS_MCU_IR2_CUST2_REG		0x35 /* RW */
  #define KHADAS_MCU_IR_CODE2_2_REG		0x36 /* RW */
+#define KHADAS_MCU_IR2_ORDER1_REG		0x36 /* RW */
  #define KHADAS_MCU_IR_CODE2_3_REG		0x37 /* RW */
+#define KHADAS_MCU_IR2_ORDER2_REG		0x36 /* RW */
  #define KHADAS_MCU_PASSWD_USER_0_REG		0x40 /* RW */
  #define KHADAS_MCU_PASSWD_USER_1_REG		0x41 /* RW */
  #define KHADAS_MCU_PASSWD_USER_2_REG		0x42 /* RW */
@@ -69,6 +88,11 @@
  #define KHADAS_MCU_SHUTDOWN_NORMAL_STATUS_REG	0x86 /* RO */
  #define KHADAS_MCU_WOL_INIT_START_REG		0x87 /* WO */
  #define KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG	0x88 /* WO */
+#define KHADAS_MCU_LED_ON_RAM_REG		0x89 /* WO */
+#define KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG_V2	0x8A /* WO */
+#define KHADAS_MCU_WDT_EN_REG			0x8B /* WO */
+#define KHADAS_MCU_SYS_RST_REG			0x91 /* WO */
+
 
  enum {
  	KHADAS_BOARD_VIM1 = 0x1,

include/linux/mfd/khadas-mcu.h enum { · line 106

Quoted 7 lines
include/linux/mfd/khadas-mcu.h +1 -0
@@ -76,6 +100,7 @@ enum {
  	KHADAS_BOARD_VIM3,
  	KHADAS_BOARD_EDGE = 0x11,
  	KHADAS_BOARD_EDGE_V,
+	KHADAS_BOARD_EDGE2,
  };
 
  /**
#7 lore raw

PATCH 4/5 thermal: khadas_mcu_fan: add support for Khadas Edge 2

In reply to [email protected]

From: Muhammed Efe Cetin <[email protected]>

Fan control on the Khadas Edge 2 is controlled with the 0x8A register,
using percentage values from 0 to 100, whereas there are only 3 constant steps in previous Khadas boards. Therefore, i added a new cooling-levels property, similar to the one used in the pwm-fan driver. The original behavior can still be used when the cooling-levels property is not specified, ensuring that the new functionality does not break old boards.

---
 drivers/thermal/khadas_mcu_fan.c | 76 ++++++++++++++++++++++++++++++--
 1 file changed, 72 insertions(+), 4 deletions(-)
drivers/thermal/khadas_mcu_fan.c +72 -4
index d35e5313b..504e7d254 100644
@@ -15,10 +15,16 @@
 #include <linux/thermal.h>
 
 #define MAX_LEVEL 3
+#define MAX_SPEED 0x64
 
 struct khadas_mcu_fan_ctx {
 	struct khadas_mcu *mcu;
 	unsigned int level;
+
+	unsigned int fan_max_level;
+	unsigned int fan_register;
+	unsigned int *fan_cooling_levels;
+
 	struct thermal_cooling_device *cdev;
 };
 
@@ -26,9 +32,21 @@ static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx,
 				    unsigned int level)
 {
 	int ret;
+	unsigned int write_level = level;
+
+	if (level > ctx->fan_max_level)
+		return -EINVAL;
+
+	if (ctx->fan_cooling_levels != NULL) {
+		write_level = ctx->fan_cooling_levels[level];
+
+		if (write_level > MAX_SPEED)
+			return -EINVAL;
+	}
+
+	ret = regmap_write(ctx->mcu->regmap, ctx->fan_register,
+			   write_level);
 
-	ret = regmap_write(ctx->mcu->regmap, KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG,
-			   level);
 	if (ret)
 		return ret;
 
@@ -40,7 +58,9 @@ static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx,
 static int khadas_mcu_fan_get_max_state(struct thermal_cooling_device *cdev,
 					unsigned long *state)
 {
-	*state = MAX_LEVEL;
+	struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
+
+	*state = ctx->fan_max_level;
 
 	return 0;
 }
@@ -61,7 +81,7 @@ khadas_mcu_fan_set_cur_state(struct thermal_cooling_device *cdev,
 {
 	struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
 
-	if (state > MAX_LEVEL)
+	if (state > ctx->fan_max_level)
 		return -EINVAL;
 
 	if (state == ctx->level)
@@ -76,6 +96,47 @@ static const struct thermal_cooling_device_ops khadas_mcu_fan_cooling_ops = {
 	.set_cur_state = khadas_mcu_fan_set_cur_state,
 };
 
+static int khadas_mcu_fan_get_cooling_data_edge2(struct khadas_mcu_fan_ctx *ctx, struct device *dev)
+{
+	struct device_node *np = ctx->mcu->dev->of_node;
+	int num, i, ret;
+
+	if (!of_property_present(np, "cooling-levels"))
+		return 0;
+
+	ret = of_property_count_u32_elems(np, "cooling-levels");
+	if (ret <= 0) {
+		dev_err(dev, "Wrong data!\n");
+		return ret ? : -EINVAL;
+	}
+
+	num = ret;
+	ctx->fan_cooling_levels = devm_kcalloc(dev, num, sizeof(u32),
+						   GFP_KERNEL);
+	if (!ctx->fan_cooling_levels)
+		return -ENOMEM;
+
+	ret = of_property_read_u32_array(np, "cooling-levels",
+					 ctx->fan_cooling_levels, num);
+	if (ret) {
+		dev_err(dev, "Property 'cooling-levels' cannot be read!\n");
+		return ret;
+	}
+
+	for (i = 0; i < num; i++) {
+		if (ctx->fan_cooling_levels[i] > MAX_SPEED) {
+			dev_err(dev, "MCU fan state[%d]:%d > %d\n", i,
+				ctx->fan_cooling_levels[i], MAX_SPEED);
+			return -EINVAL;
+		}
+	}
+
+	ctx->fan_max_level = num - 1;
+	ctx->fan_register = KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG_V2;
+
+	return 0;
+}
+
 static int khadas_mcu_fan_probe(struct platform_device *pdev)
 {
 	struct khadas_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
@@ -90,6 +151,13 @@ static int khadas_mcu_fan_probe(struct platform_device *pdev)
 	ctx->mcu = mcu;
 	platform_set_drvdata(pdev, ctx);
 
+	ctx->fan_max_level = MAX_LEVEL;
+	ctx->fan_register = KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG;
+
+	ret = khadas_mcu_fan_get_cooling_data_edge2(ctx, dev);
+	if (ret)
+		return ret;
+
 	cdev = devm_thermal_of_cooling_device_register(dev->parent,
 			dev->parent->of_node, "khadas-mcu-fan", ctx,
 			&khadas_mcu_fan_cooling_ops);
Signature · 1 line
2.49.0
#8 lore raw

Re: [PATCH 4/5] thermal: khadas_mcu_fan: add support for Khadas Edge 2

Quoted 7 lines · On 26/06/2025 16:04, [email protected] wrote:

From: Muhammed Efe Cetin <[email protected]>

Fan control on the Khadas Edge 2 is controlled with the 0x8A register,
using percentage values from 0 to 100, whereas there are only 3 constant steps in previous Khadas boards. Therefore, i added a new cooling-levels property, similar to the one used in the pwm-fan driver. The original behavior can still be used when the cooling-levels property is not specified, ensuring that the new functionality does not break old boards.

Thanks for the explanation, but would would you like to change that ? The MCU can accept any value between 0 and 99, so why change the levels from DT ?

Neil

Quoted 133 lines
---
  drivers/thermal/khadas_mcu_fan.c | 76 ++++++++++++++++++++++++++++++--
  1 file changed, 72 insertions(+), 4 deletions(-)
drivers/thermal/khadas_mcu_fan.c +72 -4
index d35e5313b..504e7d254 100644
@@ -15,10 +15,16 @@
  #include <linux/thermal.h>
 
  #define MAX_LEVEL 3
+#define MAX_SPEED 0x64
 
  struct khadas_mcu_fan_ctx {
  	struct khadas_mcu *mcu;
  	unsigned int level;
+
+	unsigned int fan_max_level;
+	unsigned int fan_register;
+	unsigned int *fan_cooling_levels;
+
  	struct thermal_cooling_device *cdev;
  };
 
@@ -26,9 +32,21 @@ static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx,
  				    unsigned int level)
  {
  	int ret;
+	unsigned int write_level = level;
+
+	if (level > ctx->fan_max_level)
+		return -EINVAL;
+
+	if (ctx->fan_cooling_levels != NULL) {
+		write_level = ctx->fan_cooling_levels[level];
+
+		if (write_level > MAX_SPEED)
+			return -EINVAL;
+	}
+
+	ret = regmap_write(ctx->mcu->regmap, ctx->fan_register,
+			   write_level);
 
-	ret = regmap_write(ctx->mcu->regmap, KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG,
-			   level);
  	if (ret)
  		return ret;
 
@@ -40,7 +58,9 @@ static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx,
  static int khadas_mcu_fan_get_max_state(struct thermal_cooling_device *cdev,
  					unsigned long *state)
  {
-	*state = MAX_LEVEL;
+	struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
+
+	*state = ctx->fan_max_level;
 
  	return 0;
  }
@@ -61,7 +81,7 @@ khadas_mcu_fan_set_cur_state(struct thermal_cooling_device *cdev,
  {
  	struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
 
-	if (state > MAX_LEVEL)
+	if (state > ctx->fan_max_level)
  		return -EINVAL;
 
  	if (state == ctx->level)
@@ -76,6 +96,47 @@ static const struct thermal_cooling_device_ops khadas_mcu_fan_cooling_ops = {
  	.set_cur_state = khadas_mcu_fan_set_cur_state,
  };
 
+static int khadas_mcu_fan_get_cooling_data_edge2(struct khadas_mcu_fan_ctx *ctx, struct device *dev)
+{
+	struct device_node *np = ctx->mcu->dev->of_node;
+	int num, i, ret;
+
+	if (!of_property_present(np, "cooling-levels"))
+		return 0;
+
+	ret = of_property_count_u32_elems(np, "cooling-levels");
+	if (ret <= 0) {
+		dev_err(dev, "Wrong data!\n");
+		return ret ? : -EINVAL;
+	}
+
+	num = ret;
+	ctx->fan_cooling_levels = devm_kcalloc(dev, num, sizeof(u32),
+						   GFP_KERNEL);
+	if (!ctx->fan_cooling_levels)
+		return -ENOMEM;
+
+	ret = of_property_read_u32_array(np, "cooling-levels",
+					 ctx->fan_cooling_levels, num);
+	if (ret) {
+		dev_err(dev, "Property 'cooling-levels' cannot be read!\n");
+		return ret;
+	}
+
+	for (i = 0; i < num; i++) {
+		if (ctx->fan_cooling_levels[i] > MAX_SPEED) {
+			dev_err(dev, "MCU fan state[%d]:%d > %d\n", i,
+				ctx->fan_cooling_levels[i], MAX_SPEED);
+			return -EINVAL;
+		}
+	}
+
+	ctx->fan_max_level = num - 1;
+	ctx->fan_register = KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG_V2;
+
+	return 0;
+}
+
  static int khadas_mcu_fan_probe(struct platform_device *pdev)
  {
  	struct khadas_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
@@ -90,6 +151,13 @@ static int khadas_mcu_fan_probe(struct platform_device *pdev)
  	ctx->mcu = mcu;
  	platform_set_drvdata(pdev, ctx);
 
+	ctx->fan_max_level = MAX_LEVEL;
+	ctx->fan_register = KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG;
+
+	ret = khadas_mcu_fan_get_cooling_data_edge2(ctx, dev);
+	if (ret)
+		return ret;
+
  	cdev = devm_thermal_of_cooling_device_register(dev->parent,
  			dev->parent->of_node, "khadas-mcu-fan", ctx,
  			&khadas_mcu_fan_cooling_ops);
#9 lore raw

Re: [PATCH 4/5] thermal: khadas_mcu_fan: add support for Khadas Edge 2

Quoted 9 lines · On 6/26/25 17:11, [email protected] wrote:
Quoted 7 lines · On 26/06/2025 16:04, [email protected] wrote:

From: Muhammed Efe Cetin <[email protected]>

Fan control on the Khadas Edge 2 is controlled with the 0x8A register,
using percentage values from 0 to 100, whereas there are only 3 constant steps in previous Khadas boards. Therefore, i added a new cooling-levels property, similar to the one used in the pwm-fan driver. The original behavior can still be used when the cooling-levels property is not specified, ensuring that the new functionality does not break old boards.

Quoted 4 lines

Thanks for the explanation, but would would you like to change that ? The MCU can accept any value between 0 and 99, so why change the levels from DT ?

Neil

Thanks for the review. Therefore, you say just add values between 0-100 to cooling-device instead of remapping them using cooling-levels property?

What would be the best practise of detecting whether the board is Khadas Edge 2? Adding new bool property, reading model propety from devicetree etc.

Best regards.

Quoted 125 lines
Quoted 124 lines
---
  drivers/thermal/khadas_mcu_fan.c | 76 ++++++++++++++++++++++++++++++--
  1 file changed, 72 insertions(+), 4 deletions(-)
drivers/thermal/khadas_mcu_fan.c +6 -0
index d35e5313b..504e7d254 100644
@@ -15,10 +15,16 @@
  #include <linux/thermal.h>
    #define MAX_LEVEL 3
+#define MAX_SPEED 0x64
    struct khadas_mcu_fan_ctx {
      struct khadas_mcu *mcu;
      unsigned int level;
+
+    unsigned int fan_max_level;
+    unsigned int fan_register;
+    unsigned int *fan_cooling_levels;
+
      struct thermal_cooling_device *cdev;
  };
  @@ -26,9 +32,21 @@ static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx,
                      unsigned int level)
  {
      int ret;
drivers/thermal/khadas_mcu_fan.c +64 -2
+    unsigned int write_level = level;
+
+    if (level > ctx->fan_max_level)
+        return -EINVAL;
+
+    if (ctx->fan_cooling_levels != NULL) {
+        write_level = ctx->fan_cooling_levels[level];
+
+        if (write_level > MAX_SPEED)
+            return -EINVAL;
+    }
+
+    ret = regmap_write(ctx->mcu->regmap, ctx->fan_register,
+               write_level);
  -    ret = regmap_write(ctx->mcu->regmap, KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG,
-               level);
      if (ret)
          return ret;
  @@ -40,7 +58,9 @@ static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx,
  static int khadas_mcu_fan_get_max_state(struct thermal_cooling_device *cdev,
                      unsigned long *state)
  {
-    *state = MAX_LEVEL;
+    struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
+
+    *state = ctx->fan_max_level;
        return 0;
  }
@@ -61,7 +81,7 @@ khadas_mcu_fan_set_cur_state(struct thermal_cooling_device *cdev,
  {
      struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
  -    if (state > MAX_LEVEL)
+    if (state > ctx->fan_max_level)
          return -EINVAL;
        if (state == ctx->level)
@@ -76,6 +96,47 @@ static const struct thermal_cooling_device_ops khadas_mcu_fan_cooling_ops = {
      .set_cur_state = khadas_mcu_fan_set_cur_state,
  };
  +static int khadas_mcu_fan_get_cooling_data_edge2(struct khadas_mcu_fan_ctx *ctx, struct device *dev)
+{
+    struct device_node *np = ctx->mcu->dev->of_node;
+    int num, i, ret;
+
+    if (!of_property_present(np, "cooling-levels"))
+        return 0;
+
+    ret = of_property_count_u32_elems(np, "cooling-levels");
+    if (ret <= 0) {
+        dev_err(dev, "Wrong data!\n");
+        return ret ? : -EINVAL;
+    }
+
+    num = ret;
+    ctx->fan_cooling_levels = devm_kcalloc(dev, num, sizeof(u32),
+                           GFP_KERNEL);
+    if (!ctx->fan_cooling_levels)
+        return -ENOMEM;
+
+    ret = of_property_read_u32_array(np, "cooling-levels",
+                     ctx->fan_cooling_levels, num);
+    if (ret) {
+        dev_err(dev, "Property 'cooling-levels' cannot be read!\n");
+        return ret;
+    }
+
+    for (i = 0; i < num; i++) {
+        if (ctx->fan_cooling_levels[i] > MAX_SPEED) {
+            dev_err(dev, "MCU fan state[%d]:%d > %d\n", i,
+                ctx->fan_cooling_levels[i], MAX_SPEED);
+            return -EINVAL;
+        }
+    }
+
+    ctx->fan_max_level = num - 1;
+    ctx->fan_register = KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG_V2;
+
+    return 0;
+}
+
  static int khadas_mcu_fan_probe(struct platform_device *pdev)
  {
      struct khadas_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
@@ -90,6 +151,13 @@ static int khadas_mcu_fan_probe(struct platform_device *pdev)
      ctx->mcu = mcu;
      platform_set_drvdata(pdev, ctx);
  +    ctx->fan_max_level = MAX_LEVEL;
+    ctx->fan_register = KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG;
+
+    ret = khadas_mcu_fan_get_cooling_data_edge2(ctx, dev);
+    if (ret)
+        return ret;
+
      cdev = devm_thermal_of_cooling_device_register(dev->parent,
              dev->parent->of_node, "khadas-mcu-fan", ctx,
              &khadas_mcu_fan_cooling_ops); 
#10 lore raw

Re: [PATCH 4/5] thermal: khadas_mcu_fan: add support for Khadas Edge 2

Quoted 10 lines · On 26/06/2025 16:36, [email protected] wrote:
Quoted 9 lines · On 6/26/25 17:11, [email protected] wrote:
Quoted 7 lines · On 26/06/2025 16:04, [email protected] wrote:

From: Muhammed Efe Cetin <[email protected]>

Fan control on the Khadas Edge 2 is controlled with the 0x8A register,
using percentage values from 0 to 100, whereas there are only 3 constant steps in previous Khadas boards. Therefore, i added a new cooling-levels property, similar to the one used in the pwm-fan driver. The original behavior can still be used when the cooling-levels property is not specified, ensuring that the new functionality does not break old boards.

Quoted 8 lines
Quoted 4 lines

Thanks for the explanation, but would would you like to change that ? The MCU can accept any value between 0 and 99, so why change the levels from DT ?

Neil

Thanks for the review. Therefore, you say just add values between 0-100 to cooling-device instead of remapping them using cooling-levels property?

What would be the best practise of detecting whether the board is Khadas Edge 2? Adding new bool property, reading model propety from devicetree etc.

The register DEVICE_NO should be set at 0x12 for Edge V, 0x11 for Edge 1. I don't have the number for Edge 2, perhaps you can read it ?

Neil

Quoted 129 lines

Best regards.

Quoted 125 lines
Quoted 124 lines
---
   drivers/thermal/khadas_mcu_fan.c | 76 ++++++++++++++++++++++++++++++--
   1 file changed, 72 insertions(+), 4 deletions(-)
drivers/thermal/khadas_mcu_fan.c +6 -0
index d35e5313b..504e7d254 100644
@@ -15,10 +15,16 @@
   #include <linux/thermal.h>
     #define MAX_LEVEL 3
+#define MAX_SPEED 0x64
     struct khadas_mcu_fan_ctx {
       struct khadas_mcu *mcu;
       unsigned int level;
+
+    unsigned int fan_max_level;
+    unsigned int fan_register;
+    unsigned int *fan_cooling_levels;
+
       struct thermal_cooling_device *cdev;
   };
   @@ -26,9 +32,21 @@ static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx,
                       unsigned int level)
   {
       int ret;
drivers/thermal/khadas_mcu_fan.c +64 -2
+    unsigned int write_level = level;
+
+    if (level > ctx->fan_max_level)
+        return -EINVAL;
+
+    if (ctx->fan_cooling_levels != NULL) {
+        write_level = ctx->fan_cooling_levels[level];
+
+        if (write_level > MAX_SPEED)
+            return -EINVAL;
+    }
+
+    ret = regmap_write(ctx->mcu->regmap, ctx->fan_register,
+               write_level);
   -    ret = regmap_write(ctx->mcu->regmap, KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG,
-               level);
       if (ret)
           return ret;
   @@ -40,7 +58,9 @@ static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx,
   static int khadas_mcu_fan_get_max_state(struct thermal_cooling_device *cdev,
                       unsigned long *state)
   {
-    *state = MAX_LEVEL;
+    struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
+
+    *state = ctx->fan_max_level;
         return 0;
   }
@@ -61,7 +81,7 @@ khadas_mcu_fan_set_cur_state(struct thermal_cooling_device *cdev,
   {
       struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
   -    if (state > MAX_LEVEL)
+    if (state > ctx->fan_max_level)
           return -EINVAL;
         if (state == ctx->level)
@@ -76,6 +96,47 @@ static const struct thermal_cooling_device_ops khadas_mcu_fan_cooling_ops = {
       .set_cur_state = khadas_mcu_fan_set_cur_state,
   };
   +static int khadas_mcu_fan_get_cooling_data_edge2(struct khadas_mcu_fan_ctx *ctx, struct device *dev)
+{
+    struct device_node *np = ctx->mcu->dev->of_node;
+    int num, i, ret;
+
+    if (!of_property_present(np, "cooling-levels"))
+        return 0;
+
+    ret = of_property_count_u32_elems(np, "cooling-levels");
+    if (ret <= 0) {
+        dev_err(dev, "Wrong data!\n");
+        return ret ? : -EINVAL;
+    }
+
+    num = ret;
+    ctx->fan_cooling_levels = devm_kcalloc(dev, num, sizeof(u32),
+                           GFP_KERNEL);
+    if (!ctx->fan_cooling_levels)
+        return -ENOMEM;
+
+    ret = of_property_read_u32_array(np, "cooling-levels",
+                     ctx->fan_cooling_levels, num);
+    if (ret) {
+        dev_err(dev, "Property 'cooling-levels' cannot be read!\n");
+        return ret;
+    }
+
+    for (i = 0; i < num; i++) {
+        if (ctx->fan_cooling_levels[i] > MAX_SPEED) {
+            dev_err(dev, "MCU fan state[%d]:%d > %d\n", i,
+                ctx->fan_cooling_levels[i], MAX_SPEED);
+            return -EINVAL;
+        }
+    }
+
+    ctx->fan_max_level = num - 1;
+    ctx->fan_register = KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG_V2;
+
+    return 0;
+}
+
   static int khadas_mcu_fan_probe(struct platform_device *pdev)
   {
       struct khadas_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
@@ -90,6 +151,13 @@ static int khadas_mcu_fan_probe(struct platform_device *pdev)
       ctx->mcu = mcu;
       platform_set_drvdata(pdev, ctx);
   +    ctx->fan_max_level = MAX_LEVEL;
+    ctx->fan_register = KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG;
+
+    ret = khadas_mcu_fan_get_cooling_data_edge2(ctx, dev);
+    if (ret)
+        return ret;
+
       cdev = devm_thermal_of_cooling_device_register(dev->parent,
               dev->parent->of_node, "khadas-mcu-fan", ctx,
               &khadas_mcu_fan_cooling_ops);
#11 lore raw

Re: [PATCH 4/5] thermal: khadas_mcu_fan: add support for Khadas Edge 2

Quoted 16 lines · On 6/26/25 17:46, [email protected] wrote:
Quoted 15 lines · On 26/06/2025 16:36, [email protected] wrote:
Quoted 13 lines · On 6/26/25 17:11, [email protected] wrote:
Quoted 7 lines · On 26/06/2025 16:04, [email protected] wrote:

From: Muhammed Efe Cetin <[email protected]>

Fan control on the Khadas Edge 2 is controlled with the 0x8A register,
using percentage values from 0 to 100, whereas there are only 3 constant steps in previous Khadas boards. Therefore, i added a new cooling-levels property, similar to the one used in the pwm-fan driver. The original behavior can still be used when the cooling-levels property is not specified, ensuring that the new functionality does not break old boards.

Thanks for the explanation, but would would you like to change that ? The MCU can accept any value between 0 and 99, so why change the levels from DT ?

Neil

Quoted 7 lines
Quoted 3 lines

Thanks for the review. Therefore, you say just add values between 0-100 to cooling-device instead of remapping them using cooling-levels property?

What would be the best practise of detecting whether the board is Khadas Edge 2? Adding new bool property, reading model propety from devicetree etc.

The register DEVICE_NO should be set at 0x12 for Edge V, 0x11 for Edge 1. I don't have the number for Edge 2, perhaps you can read it ?

Neil

The DEVICE_NO register is not available for Khadas Edge 2. Here are the registers [0].

[0] https://dl.khadas.com/products/edge2/tools/mcu/edge2-mcu-register-description-en-v1.0-230520.pdf

Quoted 131 lines
Quoted 130 lines

Best regards.

Quoted 125 lines
Quoted 124 lines
---
   drivers/thermal/khadas_mcu_fan.c | 76 ++++++++++++++++++++++++++++++--
   1 file changed, 72 insertions(+), 4 deletions(-)
drivers/thermal/khadas_mcu_fan.c +6 -0
index d35e5313b..504e7d254 100644
@@ -15,10 +15,16 @@
   #include <linux/thermal.h>
     #define MAX_LEVEL 3
+#define MAX_SPEED 0x64
     struct khadas_mcu_fan_ctx {
       struct khadas_mcu *mcu;
       unsigned int level;
+
+    unsigned int fan_max_level;
+    unsigned int fan_register;
+    unsigned int *fan_cooling_levels;
+
       struct thermal_cooling_device *cdev;
   };
   @@ -26,9 +32,21 @@ static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx,
                       unsigned int level)
   {
       int ret;
drivers/thermal/khadas_mcu_fan.c +64 -2
+    unsigned int write_level = level;
+
+    if (level > ctx->fan_max_level)
+        return -EINVAL;
+
+    if (ctx->fan_cooling_levels != NULL) {
+        write_level = ctx->fan_cooling_levels[level];
+
+        if (write_level > MAX_SPEED)
+            return -EINVAL;
+    }
+
+    ret = regmap_write(ctx->mcu->regmap, ctx->fan_register,
+               write_level);
   -    ret = regmap_write(ctx->mcu->regmap, KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG,
-               level);
       if (ret)
           return ret;
   @@ -40,7 +58,9 @@ static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx,
   static int khadas_mcu_fan_get_max_state(struct thermal_cooling_device *cdev,
                       unsigned long *state)
   {
-    *state = MAX_LEVEL;
+    struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
+
+    *state = ctx->fan_max_level;
         return 0;
   }
@@ -61,7 +81,7 @@ khadas_mcu_fan_set_cur_state(struct thermal_cooling_device *cdev,
   {
       struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
   -    if (state > MAX_LEVEL)
+    if (state > ctx->fan_max_level)
           return -EINVAL;
         if (state == ctx->level)
@@ -76,6 +96,47 @@ static const struct thermal_cooling_device_ops khadas_mcu_fan_cooling_ops = {
       .set_cur_state = khadas_mcu_fan_set_cur_state,
   };
   +static int khadas_mcu_fan_get_cooling_data_edge2(struct khadas_mcu_fan_ctx *ctx, struct device *dev)
+{
+    struct device_node *np = ctx->mcu->dev->of_node;
+    int num, i, ret;
+
+    if (!of_property_present(np, "cooling-levels"))
+        return 0;
+
+    ret = of_property_count_u32_elems(np, "cooling-levels");
+    if (ret <= 0) {
+        dev_err(dev, "Wrong data!\n");
+        return ret ? : -EINVAL;
+    }
+
+    num = ret;
+    ctx->fan_cooling_levels = devm_kcalloc(dev, num, sizeof(u32),
+                           GFP_KERNEL);
+    if (!ctx->fan_cooling_levels)
+        return -ENOMEM;
+
+    ret = of_property_read_u32_array(np, "cooling-levels",
+                     ctx->fan_cooling_levels, num);
+    if (ret) {
+        dev_err(dev, "Property 'cooling-levels' cannot be read!\n");
+        return ret;
+    }
+
+    for (i = 0; i < num; i++) {
+        if (ctx->fan_cooling_levels[i] > MAX_SPEED) {
+            dev_err(dev, "MCU fan state[%d]:%d > %d\n", i,
+                ctx->fan_cooling_levels[i], MAX_SPEED);
+            return -EINVAL;
+        }
+    }
+
+    ctx->fan_max_level = num - 1;
+    ctx->fan_register = KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG_V2;
+
+    return 0;
+}
+
   static int khadas_mcu_fan_probe(struct platform_device *pdev)
   {
       struct khadas_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
@@ -90,6 +151,13 @@ static int khadas_mcu_fan_probe(struct platform_device *pdev)
       ctx->mcu = mcu;
       platform_set_drvdata(pdev, ctx);
   +    ctx->fan_max_level = MAX_LEVEL;
+    ctx->fan_register = KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG;
+
+    ret = khadas_mcu_fan_get_cooling_data_edge2(ctx, dev);
+    if (ret)
+        return ret;
+
       cdev = devm_thermal_of_cooling_device_register(dev->parent,
               dev->parent->of_node, "khadas-mcu-fan", ctx,
               &khadas_mcu_fan_cooling_ops); 
#12 lore raw

Re: [PATCH 4/5] thermal: khadas_mcu_fan: add support for Khadas Edge 2

Quoted 21 lines · Muhammed Efe Cetin
Quoted 20 lines · On 6/26/25 17:46, [email protected] wrote:
Quoted 18 lines · On 26/06/2025 16:36, [email protected] wrote:
Quoted 9 lines · On 6/26/25 17:11, [email protected] wrote:
Quoted 7 lines · On 26/06/2025 16:04, [email protected] wrote:

From: Muhammed Efe Cetin <[email protected]>

Fan control on the Khadas Edge 2 is controlled with the 0x8A register,
using percentage values from 0 to 100, whereas there are only 3 constant steps in previous Khadas boards. Therefore, i added a new cooling-levels property, similar to the one used in the pwm-fan driver. The original behavior can still be used when the cooling-levels property is not specified, ensuring that the new functionality does not break old boards.

Quoted 4 lines

Thanks for the explanation, but would would you like to change that ? The MCU can accept any value between 0 and 99, so why change the levels from DT ?

Neil

Thanks for the review. Therefore, you say just add values between 0-100 to cooling-device instead of remapping them using cooling-levels property?

What would be the best practise of detecting whether the board is Khadas Edge 2? Adding new bool property, reading model propety from devicetree etc.

Quoted 7 lines
Quoted 3 lines

The register DEVICE_NO should be set at 0x12 for Edge V, 0x11 for Edge 1. I don't have the number for Edge 2, perhaps you can read it ?

Neil

The DEVICE_NO register is not available for Khadas Edge 2. Here are the registers [0].

[0] https://dl.khadas.com/products/edge2/tools/mcu/edge2-mcu-register-description-en-v1.0-230520.pdf

OK so it's basically completely different.

In this case introduce a separate compatible since we can't detect it dynamically, and just add the registers with EDGE2 prefix.
But keep the khadas-mcu-fan-ctrl, it will work if you pass the device type detected via the new compatible.

Neil

Quoted 133 lines
Quoted 132 lines
Quoted 131 lines

Best regards.

Quoted 125 lines
Quoted 124 lines
---
    drivers/thermal/khadas_mcu_fan.c | 76 ++++++++++++++++++++++++++++++--
    1 file changed, 72 insertions(+), 4 deletions(-)
drivers/thermal/khadas_mcu_fan.c +6 -0
index d35e5313b..504e7d254 100644
@@ -15,10 +15,16 @@
    #include <linux/thermal.h>
      #define MAX_LEVEL 3
+#define MAX_SPEED 0x64
      struct khadas_mcu_fan_ctx {
        struct khadas_mcu *mcu;
        unsigned int level;
+
+    unsigned int fan_max_level;
+    unsigned int fan_register;
+    unsigned int *fan_cooling_levels;
+
        struct thermal_cooling_device *cdev;
    };
    @@ -26,9 +32,21 @@ static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx,
                        unsigned int level)
    {
        int ret;
drivers/thermal/khadas_mcu_fan.c +64 -2
+    unsigned int write_level = level;
+
+    if (level > ctx->fan_max_level)
+        return -EINVAL;
+
+    if (ctx->fan_cooling_levels != NULL) {
+        write_level = ctx->fan_cooling_levels[level];
+
+        if (write_level > MAX_SPEED)
+            return -EINVAL;
+    }
+
+    ret = regmap_write(ctx->mcu->regmap, ctx->fan_register,
+               write_level);
    -    ret = regmap_write(ctx->mcu->regmap, KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG,
-               level);
        if (ret)
            return ret;
    @@ -40,7 +58,9 @@ static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx,
    static int khadas_mcu_fan_get_max_state(struct thermal_cooling_device *cdev,
                        unsigned long *state)
    {
-    *state = MAX_LEVEL;
+    struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
+
+    *state = ctx->fan_max_level;
          return 0;
    }
@@ -61,7 +81,7 @@ khadas_mcu_fan_set_cur_state(struct thermal_cooling_device *cdev,
    {
        struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
    -    if (state > MAX_LEVEL)
+    if (state > ctx->fan_max_level)
            return -EINVAL;
          if (state == ctx->level)
@@ -76,6 +96,47 @@ static const struct thermal_cooling_device_ops khadas_mcu_fan_cooling_ops = {
        .set_cur_state = khadas_mcu_fan_set_cur_state,
    };
    +static int khadas_mcu_fan_get_cooling_data_edge2(struct khadas_mcu_fan_ctx *ctx, struct device *dev)
+{
+    struct device_node *np = ctx->mcu->dev->of_node;
+    int num, i, ret;
+
+    if (!of_property_present(np, "cooling-levels"))
+        return 0;
+
+    ret = of_property_count_u32_elems(np, "cooling-levels");
+    if (ret <= 0) {
+        dev_err(dev, "Wrong data!\n");
+        return ret ? : -EINVAL;
+    }
+
+    num = ret;
+    ctx->fan_cooling_levels = devm_kcalloc(dev, num, sizeof(u32),
+                           GFP_KERNEL);
+    if (!ctx->fan_cooling_levels)
+        return -ENOMEM;
+
+    ret = of_property_read_u32_array(np, "cooling-levels",
+                     ctx->fan_cooling_levels, num);
+    if (ret) {
+        dev_err(dev, "Property 'cooling-levels' cannot be read!\n");
+        return ret;
+    }
+
+    for (i = 0; i < num; i++) {
+        if (ctx->fan_cooling_levels[i] > MAX_SPEED) {
+            dev_err(dev, "MCU fan state[%d]:%d > %d\n", i,
+                ctx->fan_cooling_levels[i], MAX_SPEED);
+            return -EINVAL;
+        }
+    }
+
+    ctx->fan_max_level = num - 1;
+    ctx->fan_register = KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG_V2;
+
+    return 0;
+}
+
    static int khadas_mcu_fan_probe(struct platform_device *pdev)
    {
        struct khadas_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
@@ -90,6 +151,13 @@ static int khadas_mcu_fan_probe(struct platform_device *pdev)
        ctx->mcu = mcu;
        platform_set_drvdata(pdev, ctx);
    +    ctx->fan_max_level = MAX_LEVEL;
+    ctx->fan_register = KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG;
+
+    ret = khadas_mcu_fan_get_cooling_data_edge2(ctx, dev);
+    if (ret)
+        return ret;
+
        cdev = devm_thermal_of_cooling_device_register(dev->parent,
                dev->parent->of_node, "khadas-mcu-fan", ctx,
                &khadas_mcu_fan_cooling_ops);
#13 lore raw

PATCH 5/5 arm64: dts: rockchip: add Khadas MCU and fan control nodes

In reply to [email protected]

From: Muhammed Efe Cetin <[email protected]>

Add Khadas MCU fan control to Khadas Edge 2 with 4 fan control levels.

---
 .../dts/rockchip/rk3588s-khadas-edge2.dts     | 59 +++++++++++++++++++
 1 file changed, 59 insertions(+)
arch/arm64/boot/dts/rockchip/rk3588s-khadas-edge2.dts +59 -0
index 2c22abaf4..60766c896 100644
@@ -271,12 +271,71 @@ hym8563: rtc@51 {
 		clock-output-names = "hym8563";
 		wakeup-source;
 	};
+
+	khadas_mcu: system-controller@18 {
+		compatible = "khadas,mcu";
+		reg = <0x18>;
+		cooling-levels = <0 50 72 100>;
+		#cooling-cells = <2>;
+	};
 };
 
 &i2s5_8ch {
 	status = "okay";
 };
 
+&package_thermal {
+	polling-delay = <2000>;
+
+	trips {
+		package_fan0: package-fan0 {
+			temperature = <50000>;
+			hysteresis = <5000>;
+			type = "active";
+		};
+
+		package_fan1: package-fan1 {
+			temperature = <60000>;
+			hysteresis = <5000>;
+			type = "active";
+		};
+
+		package_fan2: package-fan2 {
+			temperature = <65000>;
+			hysteresis = <5000>;
+			type = "active";
+		};
+
+		package_fan3: package-fan3 {
+			temperature = <75000>;
+			hysteresis = <5000>;
+			type = "active";
+		};
+	};
+
+	cooling-maps {
+		map0 {
+			trip = <&package_fan0>;
+			cooling-device = <&khadas_mcu THERMAL_NO_LIMIT 1>;
+		};
+
+		map1 {
+			trip = <&package_fan1>;
+			cooling-device = <&khadas_mcu 1 2>;
+		};
+
+		map2 {
+			trip = <&package_fan2>;
+			cooling-device = <&khadas_mcu 2 3>;
+		};
+
+		map3 {
+			trip = <&package_fan3>;
+			cooling-device = <&khadas_mcu 3 THERMAL_NO_LIMIT>;
+		};
+	};
+};
+
 &pd_gpu {
 	domain-supply = <&vdd_gpu_s0>;
 };
Signature · 1 line
2.49.0