]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
generic: 6.12: backport EMC2305 changes
authorRobert Marko <robimarko@gmail.com>
Wed, 15 Apr 2026 10:58:58 +0000 (12:58 +0200)
committerRobert Marko <robimarko@gmail.com>
Sat, 18 Apr 2026 10:46:31 +0000 (12:46 +0200)
Backport the Microchip EMC2305 driver changes up to current linux-next.
These are required in order to actually be able to use the driver with
DTS support to drive fans as cooling devices.

Replace the bcm27xx RPi patch with the one from RPi kernel 7.0 that was
already adapted to all of these backports.

Link: https://github.com/openwrt/openwrt/pull/22942
Signed-off-by: Robert Marko <robimarko@gmail.com>
target/linux/bcm27xx/patches-6.12/950-0284-hwmon-emc2305-fixups-for-driver-submitted-to-mailing.patch
target/linux/generic/backport-6.12/830-01-v6.15-hwmon-emc2305-Add-OF-support.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/830-02-v6.15-hwmon-emc2305-Use-devm_thermal_of_cooling_device_reg.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/830-03-v6.17-hwmon-emc2305-Add-support-for-PWM-frequency-polarity.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/830-04-v6.17-hwmon-emc2305-Configure-PWM-channels-based-on-DT-pro.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/830-05-v6.17-hwmon-emc2305-Enable-PWM-polarity-and-output-configu.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/830-06-v6.19-hwmon-emc2305-fix-double-put-in-emc2305_probe_childs.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/830-07-v6.19-hwmon-emc2305-fix-device-node-refcount-leak-in-error.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/830-08-v7.0-hwmon-emc2305-Simplify-with-scoped-for-each-OF-child.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/830-09-v7.0-hwmon-emc2305-Fix-a-resource-leak-in-emc2305_of_pars.patch [new file with mode: 0644]

index ef86c0ee60ef7d1db6d20442a02dacd57db3d891..23de440f6b4cf6c17ef6ab50bf9735919a3886c4 100644 (file)
@@ -1,4 +1,4 @@
-From 38798884cb3ccd05b2a82299db865aeb7e7c059b Mon Sep 17 00:00:00 2001
+From 936b6af1d55981c2f5306ec3c06432e676e1a534 Mon Sep 17 00:00:00 2001
 From: Phil Elwell <phil@raspberrypi.com>
 Date: Thu, 5 May 2022 15:46:07 +0100
 Subject: [PATCH] hwmon: emc2305: fixups for driver submitted to mailing lists
@@ -33,59 +33,29 @@ As this is all downstream only, revert to u8 to match 5.15.
 
 Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
 ---
- drivers/hwmon/emc2305.c | 95 +++++++++++++++++++++++++++++++++++++----
- 1 file changed, 87 insertions(+), 8 deletions(-)
+ drivers/hwmon/emc2305.c | 106 ++++++++++++++++++++++++++++++++++++++--
+ 1 file changed, 103 insertions(+), 3 deletions(-)
 
 --- a/drivers/hwmon/emc2305.c
 +++ b/drivers/hwmon/emc2305.c
-@@ -12,12 +12,13 @@
- #include <linux/platform_data/emc2305.h>
- #include <linux/thermal.h>
+@@ -15,6 +15,8 @@
+ #include <linux/of_device.h>
+ #include <linux/util_macros.h>
  
 +#define EMC2305_REG_FAN_STATUS                0x24
 +#define EMC2305_REG_FAN_STALL_STATUS  0x25
  #define EMC2305_REG_DRIVE_FAIL_STATUS 0x27
  #define EMC2305_REG_VENDOR            0xfe
  #define EMC2305_FAN_MAX                       0xff
- #define EMC2305_FAN_MIN                       0x00
- #define EMC2305_FAN_MAX_STATE         10
--#define EMC2305_DEVICE                        0x34
- #define EMC2305_VENDOR                        0x5d
- #define EMC2305_REG_PRODUCT_ID                0xfd
- #define EMC2305_TACH_REGS_UNUSE_BITS  3
-@@ -36,6 +37,7 @@
- #define EMC2305_RPM_FACTOR            3932160
- #define EMC2305_REG_FAN_DRIVE(n)      (0x30 + 0x10 * (n))
-+#define EMC2305_REG_FAN_CFG(n)                (0x32 + 0x10 * (n))
- #define EMC2305_REG_FAN_MIN_DRIVE(n)  (0x38 + 0x10 * (n))
- #define EMC2305_REG_FAN_TACH(n)               (0x3e + 0x10 * (n))
-@@ -55,6 +57,15 @@ static const struct i2c_device_id emc230
- };
- MODULE_DEVICE_TABLE(i2c, emc2305_ids);
-+static const struct of_device_id emc2305_dt_ids[] = {
-+      { .compatible = "microchip,emc2305" },
-+      { .compatible = "microchip,emc2303" },
-+      { .compatible = "microchip,emc2302" },
-+      { .compatible = "microchip,emc2301" },
-+      { }
-+};
-+MODULE_DEVICE_TABLE(of, emc2305_dt_ids);
-+
- /**
-  * struct emc2305_cdev_data - device-specific cooling device state
-  * @cdev: cooling device
-@@ -100,6 +111,7 @@ struct emc2305_data {
-       u8 pwm_num;
+@@ -117,6 +119,7 @@ struct emc2305_data {
+       u8 pwm_polarity_mask;
        bool pwm_separate;
        u8 pwm_min[EMC2305_PWM_MAX];
 +      u8 pwm_max;
+       u16 pwm_freq[EMC2305_PWM_MAX];
        struct emc2305_cdev_data cdev_data[EMC2305_PWM_MAX];
  };
-@@ -272,7 +284,7 @@ static int emc2305_set_pwm(struct device
+@@ -288,7 +291,7 @@ static int emc2305_set_pwm(struct device
        struct i2c_client *client = data->client;
        int ret;
  
@@ -94,7 +64,7 @@ Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
                return -EINVAL;
  
        ret = i2c_smbus_write_byte_data(client, EMC2305_REG_FAN_DRIVE(channel), val);
-@@ -283,6 +295,49 @@ static int emc2305_set_pwm(struct device
+@@ -299,6 +302,49 @@ static int emc2305_set_pwm(struct device
        return 0;
  }
  
@@ -141,22 +111,19 @@ Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
 +      return 0;
 +}
 +
- static int emc2305_set_single_tz(struct device *dev, int idx)
+ static int emc2305_set_single_tz(struct device *dev, struct device_node *fan_node, int idx)
  {
        struct emc2305_data *data = dev_get_drvdata(dev);
-@@ -292,9 +347,17 @@ static int emc2305_set_single_tz(struct
+@@ -308,10 +354,16 @@ static int emc2305_set_single_tz(struct
        cdev_idx = (idx) ? idx - 1 : 0;
        pwm = data->pwm_min[cdev_idx];
  
 -      data->cdev_data[cdev_idx].cdev =
--              thermal_cooling_device_register(emc2305_fan_name[idx], data,
--                                              &emc2305_cooling_ops);
 +      if (dev->of_node)
 +              data->cdev_data[cdev_idx].cdev =
-+                      devm_thermal_of_cooling_device_register(dev, dev->of_node,
-+                                                              emc2305_fan_name[idx],
-+                                                              data,
-+                                                              &emc2305_cooling_ops);
+               devm_thermal_of_cooling_device_register(dev, fan_node,
+                                                       emc2305_fan_name[idx], data,
+                                                       &emc2305_cooling_ops);
 +      else
 +              data->cdev_data[cdev_idx].cdev =
 +                      thermal_cooling_device_register(emc2305_fan_name[idx],
@@ -165,26 +132,60 @@ Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
  
        if (IS_ERR(data->cdev_data[cdev_idx].cdev)) {
                dev_err(dev, "Failed to register cooling device %s\n", emc2305_fan_name[idx]);
-@@ -353,9 +416,11 @@ static void emc2305_unset_tz(struct devi
-       int i;
+@@ -344,6 +396,19 @@ static int emc2305_set_single_tz(struct
+       return 0;
+ }
  
-       /* Unregister cooling device. */
--      for (i = 0; i < EMC2305_PWM_MAX; i++)
--              if (data->cdev_data[i].cdev)
--                      thermal_cooling_device_unregister(data->cdev_data[i].cdev);
++static void emc2305_unset_tz(struct device *dev)
++{
++      struct emc2305_data *data = dev_get_drvdata(dev);
++      int i;
++
++      /* Unregister cooling device. */
 +      if (!dev->of_node) {
 +              for (i = 0; i < EMC2305_PWM_MAX; i++)
 +                      if (data->cdev_data[i].cdev)
 +                              thermal_cooling_device_unregister(data->cdev_data[i].cdev);
 +      }
++}
++
+ static int emc2305_set_tz(struct device *dev)
+ {
+       struct emc2305_data *data = dev_get_drvdata(dev);
+@@ -355,9 +420,13 @@ static int emc2305_set_tz(struct device
+       for (i = 0; i < data->pwm_num; i++) {
+               ret = emc2305_set_single_tz(dev, dev->of_node, i + 1);
+               if (ret)
+-                      return ret;
++                      goto thermal_cooling_device_register_fail;
+       }
+       return 0;
++
++thermal_cooling_device_register_fail:
++      emc2305_unset_tz(dev);
++      return ret;
  }
  
  static umode_t
-@@ -577,11 +642,18 @@ static int emc2305_probe(struct i2c_clie
-               data->pwm_separate = pdata->pwm_separate;
-               for (i = 0; i < EMC2305_PWM_MAX; i++)
-                       data->pwm_min[i] = pdata->pwm_min[i];
-+              data->pwm_max = EMC2305_FAN_MAX;
+@@ -655,6 +724,7 @@ static int emc2305_probe(struct i2c_clie
+                               data->pwm_min[i] = pdata->pwm_min[i];
+                               data->pwm_freq[i] = pdata->pwm_freq[i];
+                       }
++                      data->pwm_max = EMC2305_FAN_MAX;
+               } else {
+                       data->max_state = EMC2305_FAN_MAX_STATE;
+                       data->pwm_separate = false;
+@@ -664,12 +734,24 @@ static int emc2305_probe(struct i2c_clie
+                               data->pwm_min[i] = EMC2305_FAN_MIN;
+                               data->pwm_freq[i] = base_freq_table[3];
+                       }
++                      data->pwm_max = EMC2305_FAN_MAX;
++                      if (dev->of_node) {
++                              ret = emc2305_get_tz_of(dev);
++                              if (ret < 0)
++                                      return ret;
++                      }
+               }
        } else {
                data->max_state = EMC2305_FAN_MAX_STATE;
                data->pwm_separate = false;
@@ -199,7 +200,7 @@ Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
        }
  
        data->hwmon_dev = devm_hwmon_device_register_with_info(dev, "emc2305", data,
-@@ -602,6 +674,12 @@ static int emc2305_probe(struct i2c_clie
+@@ -711,21 +793,39 @@ static int emc2305_probe(struct i2c_clie
                        return ret;
        }
  
@@ -212,11 +213,30 @@ Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
        return 0;
  }
  
-@@ -616,6 +694,7 @@ static void emc2305_remove(struct i2c_cl
+ static const struct of_device_id of_emc2305_match_table[] = {
+       { .compatible = "microchip,emc2305", },
++      { .compatible = "microchip,emc2303", },
++      { .compatible = "microchip,emc2302", },
++      { .compatible = "microchip,emc2301", },
+       {},
+ };
+ MODULE_DEVICE_TABLE(of, of_emc2305_match_table);
++static void emc2305_remove(struct i2c_client *client)
++{
++      struct device *dev = &client->dev;
++
++      if (IS_REACHABLE(CONFIG_THERMAL))
++              emc2305_unset_tz(dev);
++}
++
  static struct i2c_driver emc2305_driver = {
        .driver = {
                .name = "emc2305",
-+              .of_match_table = emc2305_dt_ids,
+               .of_match_table = of_emc2305_match_table,
        },
        .probe = emc2305_probe,
-       .remove   = emc2305_remove,
++      .remove   = emc2305_remove,
+       .id_table = emc2305_ids,
+ };
diff --git a/target/linux/generic/backport-6.12/830-01-v6.15-hwmon-emc2305-Add-OF-support.patch b/target/linux/generic/backport-6.12/830-01-v6.15-hwmon-emc2305-Add-OF-support.patch
new file mode 100644 (file)
index 0000000..44bf97f
--- /dev/null
@@ -0,0 +1,34 @@
+From 882bd6de1a5b4488dc0747d74420af34d419fd99 Mon Sep 17 00:00:00 2001
+From: Florin Leotescu <florin.leotescu@nxp.com>
+Date: Fri, 21 Mar 2025 16:33:07 +0200
+Subject: [PATCH] hwmon: emc2305: Add OF support
+
+Introduce OF support for Microchip emc2305 pwm fan controller.
+
+Signed-off-by: Florin Leotescu <florin.leotescu@nxp.com>
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Link: https://lore.kernel.org/r/20250321143308.4008623-3-florin.leotescu@oss.nxp.com
+Signed-off-by: Guenter Roeck <linux@roeck-us.net>
+---
+ drivers/hwmon/emc2305.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+--- a/drivers/hwmon/emc2305.c
++++ b/drivers/hwmon/emc2305.c
+@@ -613,9 +613,16 @@ static void emc2305_remove(struct i2c_cl
+               emc2305_unset_tz(dev);
+ }
++static const struct of_device_id of_emc2305_match_table[] = {
++      { .compatible = "microchip,emc2305", },
++      {},
++};
++MODULE_DEVICE_TABLE(of, of_emc2305_match_table);
++
+ static struct i2c_driver emc2305_driver = {
+       .driver = {
+               .name = "emc2305",
++              .of_match_table = of_emc2305_match_table,
+       },
+       .probe = emc2305_probe,
+       .remove   = emc2305_remove,
diff --git a/target/linux/generic/backport-6.12/830-02-v6.15-hwmon-emc2305-Use-devm_thermal_of_cooling_device_reg.patch b/target/linux/generic/backport-6.12/830-02-v6.15-hwmon-emc2305-Use-devm_thermal_of_cooling_device_reg.patch
new file mode 100644 (file)
index 0000000..d33eae5
--- /dev/null
@@ -0,0 +1,90 @@
+From 2115cbeec8a3ccc69e3b7ecdf97b4472b0829cfc Mon Sep 17 00:00:00 2001
+From: Florin Leotescu <florin.leotescu@nxp.com>
+Date: Fri, 21 Mar 2025 16:33:08 +0200
+Subject: [PATCH] hwmon: emc2305: Use devm_thermal_of_cooling_device_register
+
+Prepare the emc2305 driver to use configuration from Device Tree nodes.
+Switch to devm_thermal_of_cooling_device_register to simplify the
+cleanup procedure, allowing the removal of emc2305_unset_tz and
+emc2305_remove, which are no longer needed.
+
+Signed-off-by: Florin Leotescu <florin.leotescu@nxp.com>
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Link: https://lore.kernel.org/r/20250321143308.4008623-4-florin.leotescu@oss.nxp.com
+Signed-off-by: Guenter Roeck <linux@roeck-us.net>
+---
+ drivers/hwmon/emc2305.c | 33 ++++-----------------------------
+ 1 file changed, 4 insertions(+), 29 deletions(-)
+
+--- a/drivers/hwmon/emc2305.c
++++ b/drivers/hwmon/emc2305.c
+@@ -112,8 +112,6 @@ static char *emc2305_fan_name[] = {
+       "emc2305_fan5",
+ };
+-static void emc2305_unset_tz(struct device *dev);
+-
+ static int emc2305_get_max_channel(const struct emc2305_data *data)
+ {
+       return data->pwm_num;
+@@ -293,8 +291,9 @@ static int emc2305_set_single_tz(struct
+       pwm = data->pwm_min[cdev_idx];
+       data->cdev_data[cdev_idx].cdev =
+-              thermal_cooling_device_register(emc2305_fan_name[idx], data,
+-                                              &emc2305_cooling_ops);
++              devm_thermal_of_cooling_device_register(dev, dev->of_node,
++                                                      emc2305_fan_name[idx], data,
++                                                      &emc2305_cooling_ops);
+       if (IS_ERR(data->cdev_data[cdev_idx].cdev)) {
+               dev_err(dev, "Failed to register cooling device %s\n", emc2305_fan_name[idx]);
+@@ -338,24 +337,9 @@ static int emc2305_set_tz(struct device
+       for (i = 0; i < data->pwm_num; i++) {
+               ret = emc2305_set_single_tz(dev, i + 1);
+               if (ret)
+-                      goto thermal_cooling_device_register_fail;
++                      return ret;
+       }
+       return 0;
+-
+-thermal_cooling_device_register_fail:
+-      emc2305_unset_tz(dev);
+-      return ret;
+-}
+-
+-static void emc2305_unset_tz(struct device *dev)
+-{
+-      struct emc2305_data *data = dev_get_drvdata(dev);
+-      int i;
+-
+-      /* Unregister cooling device. */
+-      for (i = 0; i < EMC2305_PWM_MAX; i++)
+-              if (data->cdev_data[i].cdev)
+-                      thermal_cooling_device_unregister(data->cdev_data[i].cdev);
+ }
+ static umode_t
+@@ -605,14 +589,6 @@ static int emc2305_probe(struct i2c_clie
+       return 0;
+ }
+-static void emc2305_remove(struct i2c_client *client)
+-{
+-      struct device *dev = &client->dev;
+-
+-      if (IS_REACHABLE(CONFIG_THERMAL))
+-              emc2305_unset_tz(dev);
+-}
+-
+ static const struct of_device_id of_emc2305_match_table[] = {
+       { .compatible = "microchip,emc2305", },
+       {},
+@@ -625,7 +601,6 @@ static struct i2c_driver emc2305_driver
+               .of_match_table = of_emc2305_match_table,
+       },
+       .probe = emc2305_probe,
+-      .remove   = emc2305_remove,
+       .id_table = emc2305_ids,
+ };
diff --git a/target/linux/generic/backport-6.12/830-03-v6.17-hwmon-emc2305-Add-support-for-PWM-frequency-polarity.patch b/target/linux/generic/backport-6.12/830-03-v6.17-hwmon-emc2305-Add-support-for-PWM-frequency-polarity.patch
new file mode 100644 (file)
index 0000000..c758258
--- /dev/null
@@ -0,0 +1,66 @@
+From 7114b74d99a3cd588da4ecb6011858c06f8408a1 Mon Sep 17 00:00:00 2001
+From: Florin Leotescu <florin.leotescu@nxp.com>
+Date: Tue, 3 Jun 2025 14:31:22 +0300
+Subject: [PATCH 1/3] hwmon: (emc2305) Add support for PWM frequency, polarity
+ and output
+
+Add three new attributes to the driver data structures to support
+configuration of PWM frequency, PWM polarity and PWM output config.
+
+Signed-off-by: Florin Leotescu <florin.leotescu@nxp.com>
+Link: https://lore.kernel.org/r/20250603113125.3175103-2-florin.leotescu@oss.nxp.com
+Signed-off-by: Guenter Roeck <linux@roeck-us.net>
+---
+ drivers/hwmon/emc2305.c               | 6 ++++++
+ include/linux/platform_data/emc2305.h | 6 ++++++
+ 2 files changed, 12 insertions(+)
+
+--- a/drivers/hwmon/emc2305.c
++++ b/drivers/hwmon/emc2305.c
+@@ -89,8 +89,11 @@ struct emc2305_cdev_data {
+  * @hwmon_dev: hwmon device
+  * @max_state: maximum cooling state of the cooling device
+  * @pwm_num: number of PWM channels
++ * @pwm_output_mask: PWM output mask
++ * @pwm_polarity_mask: PWM polarity mask
+  * @pwm_separate: separate PWM settings for every channel
+  * @pwm_min: array of minimum PWM per channel
++ * @pwm_freq: array of PWM frequency per channel
+  * @cdev_data: array of cooling devices data
+  */
+ struct emc2305_data {
+@@ -98,8 +101,11 @@ struct emc2305_data {
+       struct device *hwmon_dev;
+       u8 max_state;
+       u8 pwm_num;
++      u8 pwm_output_mask;
++      u8 pwm_polarity_mask;
+       bool pwm_separate;
+       u8 pwm_min[EMC2305_PWM_MAX];
++      u16 pwm_freq[EMC2305_PWM_MAX];
+       struct emc2305_cdev_data cdev_data[EMC2305_PWM_MAX];
+ };
+--- a/include/linux/platform_data/emc2305.h
++++ b/include/linux/platform_data/emc2305.h
+@@ -9,14 +9,20 @@
+  * struct emc2305_platform_data - EMC2305 driver platform data
+  * @max_state: maximum cooling state of the cooling device;
+  * @pwm_num: number of active channels;
++ * @pwm_output_mask: PWM output mask
++ * @pwm_polarity_mask: PWM polarity mask
+  * @pwm_separate: separate PWM settings for every channel;
+  * @pwm_min: array of minimum PWM per channel;
++ * @pwm_freq: array of PWM frequency per channel
+  */
+ struct emc2305_platform_data {
+       u8 max_state;
+       u8 pwm_num;
++      u8 pwm_output_mask;
++      u8 pwm_polarity_mask;
+       bool pwm_separate;
+       u8 pwm_min[EMC2305_PWM_MAX];
++      u16 pwm_freq[EMC2305_PWM_MAX];
+ };
+ #endif
diff --git a/target/linux/generic/backport-6.12/830-04-v6.17-hwmon-emc2305-Configure-PWM-channels-based-on-DT-pro.patch b/target/linux/generic/backport-6.12/830-04-v6.17-hwmon-emc2305-Configure-PWM-channels-based-on-DT-pro.patch
new file mode 100644 (file)
index 0000000..2b6481a
--- /dev/null
@@ -0,0 +1,244 @@
+From 2ed4db7a1d07b349b50e890dee3d0f245230d254 Mon Sep 17 00:00:00 2001
+From: Florin Leotescu <florin.leotescu@nxp.com>
+Date: Tue, 3 Jun 2025 14:31:23 +0300
+Subject: [PATCH 2/3] hwmon: (emc2305) Configure PWM channels based on DT
+ properties
+
+Add support for configuring each PWM channel using Device Tree (DT)
+properties by parsing the 'pwms' phandle arguments.
+
+Signed-off-by: Florin Leotescu <florin.leotescu@nxp.com>
+Link: https://lore.kernel.org/r/20250603113125.3175103-3-florin.leotescu@oss.nxp.com
+Signed-off-by: Guenter Roeck <linux@roeck-us.net>
+---
+ drivers/hwmon/emc2305.c | 151 ++++++++++++++++++++++++++++++++++------
+ 1 file changed, 129 insertions(+), 22 deletions(-)
+
+--- a/drivers/hwmon/emc2305.c
++++ b/drivers/hwmon/emc2305.c
+@@ -11,6 +11,9 @@
+ #include <linux/module.h>
+ #include <linux/platform_data/emc2305.h>
+ #include <linux/thermal.h>
++#include <linux/pwm.h>
++#include <linux/of_device.h>
++#include <linux/util_macros.h>
+ #define EMC2305_REG_DRIVE_FAIL_STATUS 0x27
+ #define EMC2305_REG_VENDOR            0xfe
+@@ -23,6 +26,8 @@
+ #define EMC2305_TACH_REGS_UNUSE_BITS  3
+ #define EMC2305_TACH_CNT_MULTIPLIER   0x02
+ #define EMC2305_TACH_RANGE_MIN                480
++#define EMC2305_DEFAULT_OUTPUT                0x0
++#define EMC2305_DEFAULT_POLARITY      0x0
+ #define EMC2305_PWM_DUTY2STATE(duty, max_state, pwm_max) \
+       DIV_ROUND_CLOSEST((duty) * (max_state), (pwm_max))
+@@ -39,6 +44,9 @@
+ #define EMC2305_REG_FAN_MIN_DRIVE(n)  (0x38 + 0x10 * (n))
+ #define EMC2305_REG_FAN_TACH(n)               (0x3e + 0x10 * (n))
++/* Supported base PWM frequencies */
++static const unsigned int base_freq_table[] = { 2441, 4882, 19530, 26000 };
++
+ enum emc230x_product_id {
+       EMC2305 = 0x34,
+       EMC2303 = 0x35,
+@@ -287,7 +295,7 @@ static int emc2305_set_pwm(struct device
+       return 0;
+ }
+-static int emc2305_set_single_tz(struct device *dev, int idx)
++static int emc2305_set_single_tz(struct device *dev, struct device_node *fan_node, int idx)
+ {
+       struct emc2305_data *data = dev_get_drvdata(dev);
+       long pwm;
+@@ -297,7 +305,7 @@ static int emc2305_set_single_tz(struct
+       pwm = data->pwm_min[cdev_idx];
+       data->cdev_data[cdev_idx].cdev =
+-              devm_thermal_of_cooling_device_register(dev, dev->of_node,
++              devm_thermal_of_cooling_device_register(dev, fan_node,
+                                                       emc2305_fan_name[idx], data,
+                                                       &emc2305_cooling_ops);
+@@ -338,10 +346,10 @@ static int emc2305_set_tz(struct device
+       int i, ret;
+       if (!data->pwm_separate)
+-              return emc2305_set_single_tz(dev, 0);
++              return emc2305_set_single_tz(dev, dev->of_node, 0);
+       for (i = 0; i < data->pwm_num; i++) {
+-              ret = emc2305_set_single_tz(dev, i + 1);
++              ret = emc2305_set_single_tz(dev, dev->of_node, i + 1);
+               if (ret)
+                       return ret;
+       }
+@@ -523,15 +531,85 @@ static int emc2305_identify(struct devic
+       return 0;
+ }
++static int emc2305_of_parse_pwm_child(struct device *dev,
++                                    struct device_node *child,
++                                    struct emc2305_data *data)
++{     u32 ch;
++      int ret;
++      struct of_phandle_args args;
++
++      ret = of_property_read_u32(child, "reg", &ch);
++      if (ret) {
++              dev_err(dev, "missing reg property of %pOFn\n", child);
++              return ret;
++      }
++
++      ret = of_parse_phandle_with_args(child, "pwms", "#pwm-cells", 0, &args);
++
++      if (ret)
++              return ret;
++
++      if (args.args_count > 0) {
++              data->pwm_freq[ch] = find_closest(args.args[0], base_freq_table,
++                                                ARRAY_SIZE(base_freq_table));
++      } else {
++              data->pwm_freq[ch] = base_freq_table[3];
++      }
++
++      if (args.args_count > 1) {
++              if (args.args[1] == PWM_POLARITY_NORMAL || args.args[1] == PWM_POLARITY_INVERSED)
++                      data->pwm_polarity_mask |= args.args[1] << ch;
++              else
++                      dev_err(dev, "Wrong PWM polarity config provided: %d\n", args.args[0]);
++      } else {
++              data->pwm_polarity_mask |= PWM_POLARITY_NORMAL << ch;
++      }
++
++      if (args.args_count > 2) {
++              if (args.args[2] == EMC2305_PUSH_PULL || args.args[2] <= EMC2305_OPEN_DRAIN)
++                      data->pwm_output_mask |= args.args[2] << ch;
++              else
++                      dev_err(dev, "Wrong PWM output config provided: %d\n", args.args[1]);
++      } else {
++              data->pwm_output_mask |= EMC2305_OPEN_DRAIN << ch;
++      }
++
++      return 0;
++}
++
++static int emc2305_probe_childs_from_dt(struct device *dev)
++{
++      struct emc2305_data *data = dev_get_drvdata(dev);
++      struct device_node *child;
++      int ret, count = 0;
++
++      data->pwm_output_mask = 0x0;
++      data->pwm_polarity_mask = 0x0;
++
++      for_each_child_of_node(dev->of_node, child) {
++              if (of_property_present(child, "reg")) {
++                      ret = emc2305_of_parse_pwm_child(dev, child, data);
++                      if (ret) {
++                              of_node_put(child);
++                              continue;
++                      }
++                      count++;
++              }
++      }
++      return count;
++}
++
+ static int emc2305_probe(struct i2c_client *client)
+ {
+       struct i2c_adapter *adapter = client->adapter;
+       struct device *dev = &client->dev;
++      struct device_node *child;
+       struct emc2305_data *data;
+       struct emc2305_platform_data *pdata;
+       int vendor;
+       int ret;
+       int i;
++      int pwm_childs;
+       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA))
+               return -ENODEV;
+@@ -551,22 +629,40 @@ static int emc2305_probe(struct i2c_clie
+       if (ret)
+               return ret;
++      pwm_childs = emc2305_probe_childs_from_dt(dev);
++
+       pdata = dev_get_platdata(&client->dev);
+-      if (pdata) {
+-              if (!pdata->max_state || pdata->max_state > EMC2305_FAN_MAX_STATE)
+-                      return -EINVAL;
+-              data->max_state = pdata->max_state;
+-              /*
+-               * Validate a number of active PWM channels. Note that
+-               * configured number can be less than the actual maximum
+-               * supported by the device.
+-               */
+-              if (!pdata->pwm_num || pdata->pwm_num > EMC2305_PWM_MAX)
+-                      return -EINVAL;
+-              data->pwm_num = pdata->pwm_num;
+-              data->pwm_separate = pdata->pwm_separate;
+-              for (i = 0; i < EMC2305_PWM_MAX; i++)
+-                      data->pwm_min[i] = pdata->pwm_min[i];
++
++      if (!pwm_childs) {
++              if (pdata) {
++                      if (!pdata->max_state || pdata->max_state > EMC2305_FAN_MAX_STATE)
++                              return -EINVAL;
++                      data->max_state = pdata->max_state;
++                      /*
++                       * Validate a number of active PWM channels. Note that
++                       * configured number can be less than the actual maximum
++                       * supported by the device.
++                       */
++                      if (!pdata->pwm_num || pdata->pwm_num > EMC2305_PWM_MAX)
++                              return -EINVAL;
++                      data->pwm_num = pdata->pwm_num;
++                      data->pwm_output_mask = pdata->pwm_output_mask;
++                      data->pwm_polarity_mask = pdata->pwm_polarity_mask;
++                      data->pwm_separate = pdata->pwm_separate;
++                      for (i = 0; i < EMC2305_PWM_MAX; i++) {
++                              data->pwm_min[i] = pdata->pwm_min[i];
++                              data->pwm_freq[i] = pdata->pwm_freq[i];
++                      }
++              } else {
++                      data->max_state = EMC2305_FAN_MAX_STATE;
++                      data->pwm_separate = false;
++                      data->pwm_output_mask = EMC2305_DEFAULT_OUTPUT;
++                      data->pwm_polarity_mask = EMC2305_DEFAULT_POLARITY;
++                      for (i = 0; i < EMC2305_PWM_MAX; i++) {
++                              data->pwm_min[i] = EMC2305_FAN_MIN;
++                              data->pwm_freq[i] = base_freq_table[3];
++                      }
++              }
+       } else {
+               data->max_state = EMC2305_FAN_MAX_STATE;
+               data->pwm_separate = false;
+@@ -580,9 +676,20 @@ static int emc2305_probe(struct i2c_clie
+               return PTR_ERR(data->hwmon_dev);
+       if (IS_REACHABLE(CONFIG_THERMAL)) {
+-              ret = emc2305_set_tz(dev);
+-              if (ret != 0)
+-                      return ret;
++              /* Parse and check for the available PWM child nodes */
++              if (pwm_childs > 0) {
++                      i = 0;
++                      for_each_child_of_node(dev->of_node, child) {
++                              ret = emc2305_set_single_tz(dev, child, i);
++                              if (ret != 0)
++                                      return ret;
++                              i++;
++                      }
++              } else {
++                      ret = emc2305_set_tz(dev);
++                      if (ret != 0)
++                              return ret;
++              }
+       }
+       for (i = 0; i < data->pwm_num; i++) {
diff --git a/target/linux/generic/backport-6.12/830-05-v6.17-hwmon-emc2305-Enable-PWM-polarity-and-output-configu.patch b/target/linux/generic/backport-6.12/830-05-v6.17-hwmon-emc2305-Enable-PWM-polarity-and-output-configu.patch
new file mode 100644 (file)
index 0000000..96b435f
--- /dev/null
@@ -0,0 +1,46 @@
+From ef8b1b4eb702cdd56807c0430b511f94b2af8e66 Mon Sep 17 00:00:00 2001
+From: Florin Leotescu <florin.leotescu@nxp.com>
+Date: Tue, 3 Jun 2025 14:31:24 +0300
+Subject: [PATCH 3/3] hwmon: (emc2305) Enable PWM polarity and output
+ configuration
+
+Enable configuration of PWM polarity and PWM output config
+based Device Tree properties.
+
+Signed-off-by: Florin Leotescu <florin.leotescu@nxp.com>
+Link: https://lore.kernel.org/r/20250603113125.3175103-4-florin.leotescu@oss.nxp.com
+Signed-off-by: Guenter Roeck <linux@roeck-us.net>
+---
+ drivers/hwmon/emc2305.c | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+--- a/drivers/hwmon/emc2305.c
++++ b/drivers/hwmon/emc2305.c
+@@ -28,6 +28,10 @@
+ #define EMC2305_TACH_RANGE_MIN                480
+ #define EMC2305_DEFAULT_OUTPUT                0x0
+ #define EMC2305_DEFAULT_POLARITY      0x0
++#define EMC2305_REG_POLARITY          0x2a
++#define EMC2305_REG_DRIVE_PWM_OUT     0x2b
++#define EMC2305_OPEN_DRAIN            0x0
++#define EMC2305_PUSH_PULL             0x1
+ #define EMC2305_PWM_DUTY2STATE(duty, max_state, pwm_max) \
+       DIV_ROUND_CLOSEST((duty) * (max_state), (pwm_max))
+@@ -692,6 +696,16 @@ static int emc2305_probe(struct i2c_clie
+               }
+       }
++      ret = i2c_smbus_write_byte_data(client, EMC2305_REG_DRIVE_PWM_OUT,
++                                      data->pwm_output_mask);
++      if (ret < 0)
++              dev_err(dev, "Failed to configure pwm output, using default\n");
++
++      ret = i2c_smbus_write_byte_data(client, EMC2305_REG_POLARITY,
++                                      data->pwm_polarity_mask);
++      if (ret < 0)
++              dev_err(dev, "Failed to configure pwm polarity, using default\n");
++
+       for (i = 0; i < data->pwm_num; i++) {
+               ret = i2c_smbus_write_byte_data(client, EMC2305_REG_FAN_MIN_DRIVE(i),
+                                               data->pwm_min[i]);
diff --git a/target/linux/generic/backport-6.12/830-06-v6.19-hwmon-emc2305-fix-double-put-in-emc2305_probe_childs.patch b/target/linux/generic/backport-6.12/830-06-v6.19-hwmon-emc2305-fix-double-put-in-emc2305_probe_childs.patch
new file mode 100644 (file)
index 0000000..098ba84
--- /dev/null
@@ -0,0 +1,32 @@
+From 541dfb49dcb80c2509e030842de77adfb77820f5 Mon Sep 17 00:00:00 2001
+From: Pei Xiao <xiaopei01@kylinos.cn>
+Date: Fri, 5 Dec 2025 10:02:41 +0800
+Subject: [PATCH 1/2] hwmon: (emc2305) fix double put in
+ emc2305_probe_childs_from_dt
+
+./drivers/hwmon/emc2305.c:597:4-15: ERROR: probable double put
+
+Device node iterators put the previous value of the index variable, so an
+explicit put causes a double put.
+
+Signed-off-by: Pei Xiao <xiaopei01@kylinos.cn>
+Link: https://lore.kernel.org/r/tencent_CD373F952BE48697C949E39CB5EB77841D06@qq.com
+Signed-off-by: Guenter Roeck <linux@roeck-us.net>
+---
+ drivers/hwmon/emc2305.c | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+--- a/drivers/hwmon/emc2305.c
++++ b/drivers/hwmon/emc2305.c
+@@ -593,10 +593,8 @@ static int emc2305_probe_childs_from_dt(
+       for_each_child_of_node(dev->of_node, child) {
+               if (of_property_present(child, "reg")) {
+                       ret = emc2305_of_parse_pwm_child(dev, child, data);
+-                      if (ret) {
+-                              of_node_put(child);
++                      if (ret)
+                               continue;
+-                      }
+                       count++;
+               }
+       }
diff --git a/target/linux/generic/backport-6.12/830-07-v6.19-hwmon-emc2305-fix-device-node-refcount-leak-in-error.patch b/target/linux/generic/backport-6.12/830-07-v6.19-hwmon-emc2305-fix-device-node-refcount-leak-in-error.patch
new file mode 100644 (file)
index 0000000..0503913
--- /dev/null
@@ -0,0 +1,37 @@
+From 4910da6b36b122db50a27fabf6ab7f8611b60bf8 Mon Sep 17 00:00:00 2001
+From: Pei Xiao <xiaopei01@kylinos.cn>
+Date: Fri, 5 Dec 2025 11:15:13 +0800
+Subject: [PATCH 2/2] hwmon: (emc2305) fix device node refcount leak in error
+ path
+
+The for_each_child_of_node() macro automatically manages device node
+reference counts during normal iteration. However, when breaking out
+of the loop early with return, the current iteration's node is not
+automatically released, leading to a reference count leak.
+
+Fix this by adding of_node_put(child) before returning from the loop
+when emc2305_set_single_tz() fails.
+
+This issue could lead to memory leaks over multiple probe cycles.
+
+Signed-off-by: Pei Xiao <xiaopei01@kylinos.cn>
+Link: https://lore.kernel.org/r/tencent_5CDC08544C901D5ECA270573D5AEE3117108@qq.com
+Signed-off-by: Guenter Roeck <linux@roeck-us.net>
+---
+ drivers/hwmon/emc2305.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+--- a/drivers/hwmon/emc2305.c
++++ b/drivers/hwmon/emc2305.c
+@@ -683,8 +683,10 @@ static int emc2305_probe(struct i2c_clie
+                       i = 0;
+                       for_each_child_of_node(dev->of_node, child) {
+                               ret = emc2305_set_single_tz(dev, child, i);
+-                              if (ret != 0)
++                              if (ret != 0) {
++                                      of_node_put(child);
+                                       return ret;
++                              }
+                               i++;
+                       }
+               } else {
diff --git a/target/linux/generic/backport-6.12/830-08-v7.0-hwmon-emc2305-Simplify-with-scoped-for-each-OF-child.patch b/target/linux/generic/backport-6.12/830-08-v7.0-hwmon-emc2305-Simplify-with-scoped-for-each-OF-child.patch
new file mode 100644 (file)
index 0000000..cae5a5c
--- /dev/null
@@ -0,0 +1,40 @@
+From bfd0103be5d85751fc8b7c6c8c7553d71022efe0 Mon Sep 17 00:00:00 2001
+From: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
+Date: Wed, 24 Dec 2025 12:07:03 +0100
+Subject: [PATCH] hwmon: (emc2305) Simplify with scoped for each OF child loop
+
+Use scoped for-each loop when iterating over device nodes to make code a
+bit simpler.
+
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
+Link: https://lore.kernel.org/r/20251224110702.61746-4-krzysztof.kozlowski@oss.qualcomm.com
+Signed-off-by: Guenter Roeck <linux@roeck-us.net>
+---
+ drivers/hwmon/emc2305.c | 7 ++-----
+ 1 file changed, 2 insertions(+), 5 deletions(-)
+
+--- a/drivers/hwmon/emc2305.c
++++ b/drivers/hwmon/emc2305.c
+@@ -605,7 +605,6 @@ static int emc2305_probe(struct i2c_clie
+ {
+       struct i2c_adapter *adapter = client->adapter;
+       struct device *dev = &client->dev;
+-      struct device_node *child;
+       struct emc2305_data *data;
+       struct emc2305_platform_data *pdata;
+       int vendor;
+@@ -681,12 +680,10 @@ static int emc2305_probe(struct i2c_clie
+               /* Parse and check for the available PWM child nodes */
+               if (pwm_childs > 0) {
+                       i = 0;
+-                      for_each_child_of_node(dev->of_node, child) {
++                      for_each_child_of_node_scoped(dev->of_node, child) {
+                               ret = emc2305_set_single_tz(dev, child, i);
+-                              if (ret != 0) {
+-                                      of_node_put(child);
++                              if (ret != 0)
+                                       return ret;
+-                              }
+                               i++;
+                       }
+               } else {
diff --git a/target/linux/generic/backport-6.12/830-09-v7.0-hwmon-emc2305-Fix-a-resource-leak-in-emc2305_of_pars.patch b/target/linux/generic/backport-6.12/830-09-v7.0-hwmon-emc2305-Fix-a-resource-leak-in-emc2305_of_pars.patch
new file mode 100644 (file)
index 0000000..7b19b8e
--- /dev/null
@@ -0,0 +1,28 @@
+From 2954ce672b7623478c1cfeb69e6a6e4042a3656e Mon Sep 17 00:00:00 2001
+From: Felix Gu <gu_0233@qq.com>
+Date: Thu, 15 Jan 2026 21:51:48 +0800
+Subject: [PATCH] hwmon: (emc2305) Fix a resource leak in
+ emc2305_of_parse_pwm_child
+
+When calling of_parse_phandle_with_args(), the caller is responsible
+to call of_node_put() to release the reference of device node.
+In emc2305_of_parse_pwm_child, it does not release the reference,
+causing a resource leak.
+
+Signed-off-by: Felix Gu <gu_0233@qq.com>
+Link: https://lore.kernel.org/r/tencent_738BA80BBF28F3440301EEE6F9E470165105@qq.com
+Signed-off-by: Guenter Roeck <linux@roeck-us.net>
+---
+ drivers/hwmon/emc2305.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/hwmon/emc2305.c
++++ b/drivers/hwmon/emc2305.c
+@@ -578,6 +578,7 @@ static int emc2305_of_parse_pwm_child(st
+               data->pwm_output_mask |= EMC2305_OPEN_DRAIN << ch;
+       }
++      of_node_put(args.np);
+       return 0;
+ }