* [PATCH 1/4] hwmon: emc2305: Add support for PWM frequency, polarity and output
2025-06-03 11:31 [PATCH 0/4] Add OF support for Microchip emc2305 fan controller florin.leotescu
@ 2025-06-03 11:31 ` florin.leotescu
2025-06-16 22:16 ` Guenter Roeck
2025-06-03 11:31 ` [PATCH 2/4] hwmon: emc2305: Configure PWM channels based on DT properties florin.leotescu
` (2 subsequent siblings)
3 siblings, 1 reply; 9+ messages in thread
From: florin.leotescu @ 2025-06-03 11:31 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck, Florin Leotescu, linux-hwmon,
linux-kernel
Cc: viorel.suman, carlos.song, daniel.baluta, linux-arm-kernel, imx,
festevam
From: Florin Leotescu <florin.leotescu@nxp.com>
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>
---
drivers/hwmon/emc2305.c | 6 ++++++
include/linux/platform_data/emc2305.h | 6 ++++++
2 files changed, 12 insertions(+)
diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c
index 234c54956a4b..8fc4fcf8a063 100644
--- 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];
};
diff --git a/include/linux/platform_data/emc2305.h b/include/linux/platform_data/emc2305.h
index 54d672dd6f7d..76043a97f975 100644
--- 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
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 2/4] hwmon: emc2305: Configure PWM channels based on DT properties
2025-06-03 11:31 [PATCH 0/4] Add OF support for Microchip emc2305 fan controller florin.leotescu
2025-06-03 11:31 ` [PATCH 1/4] hwmon: emc2305: Add support for PWM frequency, polarity and output florin.leotescu
@ 2025-06-03 11:31 ` florin.leotescu
2025-06-16 22:16 ` Guenter Roeck
2025-06-03 11:31 ` [PATCH 3/4] hwmon: emc2305: Enable PWM polarity and output configuration florin.leotescu
2025-06-03 11:31 ` [PATCH 4/4] hwmon: emc2305: Set initial PWM minimum value during probe based on thermal state florin.leotescu
3 siblings, 1 reply; 9+ messages in thread
From: florin.leotescu @ 2025-06-03 11:31 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck, Florin Leotescu, linux-hwmon,
linux-kernel
Cc: viorel.suman, carlos.song, daniel.baluta, linux-arm-kernel, imx,
festevam
From: Florin Leotescu <florin.leotescu@nxp.com>
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>
---
drivers/hwmon/emc2305.c | 151 ++++++++++++++++++++++++++++++++++------
1 file changed, 129 insertions(+), 22 deletions(-)
diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c
index 8fc4fcf8a063..0228511f4753 100644
--- 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 *dev, long val, int channel)
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 device *dev, int idx)
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);
@@ -332,10 +340,10 @@ static int emc2305_set_tz(struct device *dev)
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;
}
@@ -517,15 +525,85 @@ static int emc2305_identify(struct device *dev)
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;
@@ -545,22 +623,40 @@ static int emc2305_probe(struct i2c_client *client)
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;
@@ -574,9 +670,20 @@ static int emc2305_probe(struct i2c_client *client)
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++) {
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 3/4] hwmon: emc2305: Enable PWM polarity and output configuration
2025-06-03 11:31 [PATCH 0/4] Add OF support for Microchip emc2305 fan controller florin.leotescu
2025-06-03 11:31 ` [PATCH 1/4] hwmon: emc2305: Add support for PWM frequency, polarity and output florin.leotescu
2025-06-03 11:31 ` [PATCH 2/4] hwmon: emc2305: Configure PWM channels based on DT properties florin.leotescu
@ 2025-06-03 11:31 ` florin.leotescu
2025-06-16 22:17 ` Guenter Roeck
2025-06-03 11:31 ` [PATCH 4/4] hwmon: emc2305: Set initial PWM minimum value during probe based on thermal state florin.leotescu
3 siblings, 1 reply; 9+ messages in thread
From: florin.leotescu @ 2025-06-03 11:31 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck, Florin Leotescu, linux-hwmon,
linux-kernel
Cc: viorel.suman, carlos.song, daniel.baluta, linux-arm-kernel, imx,
festevam
From: Florin Leotescu <florin.leotescu@nxp.com>
Enable configuration of PWM polarity and PWM output config
based Device Tree properties.
Signed-off-by: Florin Leotescu <florin.leotescu@nxp.com>
---
drivers/hwmon/emc2305.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c
index 0228511f4753..db65c3177f29 100644
--- 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))
@@ -686,6 +690,16 @@ static int emc2305_probe(struct i2c_client *client)
}
}
+ 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]);
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 4/4] hwmon: emc2305: Set initial PWM minimum value during probe based on thermal state
2025-06-03 11:31 [PATCH 0/4] Add OF support for Microchip emc2305 fan controller florin.leotescu
` (2 preceding siblings ...)
2025-06-03 11:31 ` [PATCH 3/4] hwmon: emc2305: Enable PWM polarity and output configuration florin.leotescu
@ 2025-06-03 11:31 ` florin.leotescu
2025-06-16 22:17 ` Guenter Roeck
3 siblings, 1 reply; 9+ messages in thread
From: florin.leotescu @ 2025-06-03 11:31 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck, Florin Leotescu, linux-hwmon,
linux-kernel
Cc: viorel.suman, carlos.song, daniel.baluta, linux-arm-kernel, imx,
festevam
From: Florin Leotescu <florin.leotescu@nxp.com>
Prevent the PWM value from being set to minimum when thermal zone
temperature exceeds any trip point during driver probe. Otherwise, the
PWM fan speed will remains at minimum speed and not respond to
temperature changes.
Signed-off-by: Florin Leotescu <florin.leotescu@nxp.com>
---
drivers/hwmon/emc2305.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c
index db65c3177f29..60809289f816 100644
--- a/drivers/hwmon/emc2305.c
+++ b/drivers/hwmon/emc2305.c
@@ -317,6 +317,12 @@ static int emc2305_set_single_tz(struct device *dev, struct device_node *fan_nod
dev_err(dev, "Failed to register cooling device %s\n", emc2305_fan_name[idx]);
return PTR_ERR(data->cdev_data[cdev_idx].cdev);
}
+
+ if (data->cdev_data[cdev_idx].cur_state > 0)
+ /* Update pwm when temperature is above trips */
+ pwm = EMC2305_PWM_STATE2DUTY(data->cdev_data[cdev_idx].cur_state,
+ data->max_state, EMC2305_FAN_MAX);
+
/* Set minimal PWM speed. */
if (data->pwm_separate) {
ret = emc2305_set_pwm(dev, pwm, cdev_idx);
@@ -330,10 +336,10 @@ static int emc2305_set_single_tz(struct device *dev, struct device_node *fan_nod
}
}
data->cdev_data[cdev_idx].cur_state =
- EMC2305_PWM_DUTY2STATE(data->pwm_min[cdev_idx], data->max_state,
+ EMC2305_PWM_DUTY2STATE(pwm, data->max_state,
EMC2305_FAN_MAX);
data->cdev_data[cdev_idx].last_hwmon_state =
- EMC2305_PWM_DUTY2STATE(data->pwm_min[cdev_idx], data->max_state,
+ EMC2305_PWM_DUTY2STATE(pwm, data->max_state,
EMC2305_FAN_MAX);
return 0;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread