Skip to contentSkip to conversation

Add devicetree support for max6639

PATCH Superseded Naresh Solanki 2 participants 6 messages Nov 15, 2022

Subsystems (3)

v5 of 8 Superseded Latest is v8 →

lore
Share & tools
lore
#0 lore raw

v5 cover Add devicetree support for max6639

These patches adds devicetree support for MAX6639.

Changes in V5:
- Add pwms support
Changes V4:
- Fix dt error
- update comment
Changes V3:
- correct fan dt property name
- remove unrelevent changes
Changes V2:
- Fix dt schema error.
Changes:
- Add fan-common dt schema.
- add dt-binding support for max6639
- add max6639 specific property

Marcello Sylvester Bauer (1):
dt-bindings: hwmon: Add binding for max6639

Naresh Solanki (2):
dt-bindings: hwmon: fan: Add fan binding to schema
hwmon: (max6639) Change from pdata to dt configuration

 .../devicetree/bindings/hwmon/fan-common.yaml |  42 ++
 .../bindings/hwmon/maxim,max6639.yaml         |  93 ++++
 drivers/hwmon/Kconfig                         |   1 +
 drivers/hwmon/max6639.c                       | 465 +++++++++++++++---
 4 files changed, 527 insertions(+), 74 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/hwmon/fan-common.yaml
 create mode 100644 Documentation/devicetree/bindings/hwmon/maxim,max6639.yaml

base-commit: 0cf46a653bdae56683fece68dc50340f7520e6c4

Signature · 1 line
2.37.3
#1 lore raw

PATCH v5 1/3 dt-bindings: hwmon: fan: Add fan binding to schema

Add common fan properties bindings to a schema.

Bindings for fan controllers can reference the common schema for the fan

child nodes:

patternProperties:

    "^fan@[0-2]":
      type: object
      $ref: fan-common.yaml#
---
 .../devicetree/bindings/hwmon/fan-common.yaml | 42 +++++++++++++++++++
 1 file changed, 42 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/hwmon/fan-common.yaml
Documentation/devicetree/bindings/hwmon/fan-common.yaml new file +42 -0
new file mode 100644
index 000000000000..1954882eed77
@@ -0,0 +1,42 @@
+# SPDX-License-Identifier: GPL-2.0-or-later OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/hwmon/fan-common.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Common fan properties
+
+maintainers:
+  - Naresh Solanki <[email protected]>
+
+properties:
+  max-rpm:
+    description:
+      Max RPM supported by fan.
+    $ref: /schemas/types.yaml#/definitions/uint32
+
+  pulses-per-revolution:
+    description:
+      The number of pulse from fan sensor per revolution.
+    $ref: /schemas/types.yaml#/definitions/uint32
+
+  target-rpm:
+    description:
+      Target RPM the fan should be configured during driver probe.
+    $ref: /schemas/types.yaml#/definitions/uint32
+
+  pwms:
+    description:
+      PWM provider.
+
+  label:
+    description:
+      Optional fan label
+
+  fan-supply:
+    description:
+      Power supply for fan.
+
+additionalProperties: true
+
+...
Signature · 1 line
2.37.3
#2 lore raw

PATCH v5 2/3 dt-bindings: hwmon: Add binding for max6639

In reply to Naresh Solanki

From: Marcello Sylvester Bauer <[email protected]>

Add Devicetree binding documentation for Maxim MAX6639 temperature monitor with PWM fan-speed controller.

---
 .../bindings/hwmon/maxim,max6639.yaml         | 93 +++++++++++++++++++
 1 file changed, 93 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/hwmon/maxim,max6639.yaml
Documentation/devicetree/bindings/hwmon/maxim,max6639.yaml new file +93 -0
new file mode 100644
index 000000000000..da040b11d2ab
@@ -0,0 +1,93 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+
+$id: http://devicetree.org/schemas/hwmon/maxim,max6639.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Maxim max6639
+
+maintainers:
+  - Roland Stigge <[email protected]>
+
+description: |
+  The MAX6639 is a 2-channel temperature monitor with dual, automatic, PWM
+  fan-speed controller.  It monitors its own temperature and one external
+  diode-connected transistor or the temperatures of two external diode-connected
+  transistors, typically available in CPUs, FPGAs, or GPUs.
+
+  Datasheets:
+    https://datasheets.maximintegrated.com/en/ds/MAX6639-MAX6639F.pdf
+
+properties:
+  compatible:
+    enum:
+      - maxim,max6639
+
+  reg:
+    maxItems: 1
+
+  '#address-cells':
+    const: 1
+
+  '#size-cells':
+    const: 0
+
+  '#pwm-cells':
+    const: 3
+
+required:
+  - compatible
+  - reg
+
+patternProperties:
+  "^fan@[0-1]$":
+    type: object
+    description: |
+      Represents the two fans and their specific configuration.
+
+    $ref: fan-common.yaml#
+
+    properties:
+      reg:
+        description: |
+          The fan number.
+        items:
+          minimum: 0
+          maximum: 1
+
+    required:
+      - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        fan1: fan-controller@10 {
+            compatible = "maxim,max6639";
+            reg = <0x10>;
+            #address-cells = <1>;
+            #size-cells = <0>;
+            #pwm-cells = <3>;
+
+            fan@0 {
+                reg = <0x0>;
+                pulses-per-revolution = <2>;
+                max-rpm = <4000>;
+                target-rpm = <1000>;
+                pwms = <&fan1 0 25000 0>;
+            };
+
+            fan@1 {
+                reg = <0x1>;
+                pulses-per-revolution = <2>;
+                max-rpm = <8000>;
+                pwms = <&fan1 1 25000 0>;
+            };
+            };
+    };
+...
Signature · 1 line
2.37.3
#3 lore raw

PATCH v5 3/3 hwmon: (max6639) Change from pdata to dt configuration

In reply to Naresh Solanki

max6639_platform_data is not used by any in-kernel driver and does not address the MAX6639 fans separately.
Move to device tree configuration with explicit properties to configure each fan.

Non-DT platform can still use this module with its default configuration.

---
 drivers/hwmon/Kconfig   |   1 +
 drivers/hwmon/max6639.c | 465 +++++++++++++++++++++++++++++++++-------
 2 files changed, 392 insertions(+), 74 deletions(-)
drivers/hwmon/Kconfig +1 -0
index 5695b266abcf..ad1f6742ca50 100644
@@ -1106,6 +1106,7 @@ config SENSORS_MAX6621
 config SENSORS_MAX6639
 	tristate "Maxim MAX6639 sensor chip"
 	depends on I2C
+	depends on PWM
 	help
 	  If you say yes here you get support for the MAX6639
 	  sensor chips.
drivers/hwmon/max6639.c +391 -74

630 lines not on this page · raw message

Signature · 1 line
2.37.3
#4 lore raw

Re: [PATCH v5 3/3] hwmon: (max6639) Change from pdata to dt configuration

Hello,

Quoted 4 lines · Naresh Solanki

max6639_platform_data is not used by any in-kernel driver and does not address the MAX6639 fans separately.
Move to device tree configuration with explicit properties to configure each fan.

My overall impression is that this patch mixes too much things. IMHO it should be split in (at least)

- Add dt support
- Drop platform support
- Add PWM provider support
- Make use of the PWM API

maybe also add the 2nd PWM in a separate step.

Some more comments inline.

Quoted 27 lines

Non-DT platform can still use this module with its default configuration.

---
 drivers/hwmon/Kconfig   |   1 +
 drivers/hwmon/max6639.c | 465 +++++++++++++++++++++++++++++++++-------
 2 files changed, 392 insertions(+), 74 deletions(-)
drivers/hwmon/Kconfig +1 -0
index 5695b266abcf..ad1f6742ca50 100644
@@ -1106,6 +1106,7 @@ config SENSORS_MAX6621
 config SENSORS_MAX6639
 	tristate "Maxim MAX6639 sensor chip"
 	depends on I2C
+	depends on PWM
 	help
 	  If you say yes here you get support for the MAX6639
 	  sensor chips.
drivers/hwmon/max6639.c +0 -0
index 9b895402c80d..d1ae77e8f72e 100644

drivers/hwmon/max6639.c line 22

Quoted 4 lines
drivers/hwmon/max6639.c +0 -1
@@ -19,7 +19,7 @@
 #include <linux/hwmon-sysfs.h>
 #include <linux/err.h>
 #include <linux/mutex.h>
-#include <linux/platform_data/max6639.h>

This file is now unused and can be dropped.

Quoted 10 lines
drivers/hwmon/max6639.c +2 -1
+#include <linux/pwm.h>
 
 /* Addresses to scan */
 static const unsigned short normal_i2c[] = { 0x2c, 0x2e, 0x2f, I2C_CLIENT_END };
@@ -54,11 +54,20 @@ static const unsigned short normal_i2c[] = { 0x2c, 0x2e, 0x2f, I2C_CLIENT_END };
 #define MAX6639_GCONFIG_PWM_FREQ_HI		0x08
 
 #define MAX6639_FAN_CONFIG1_PWM			0x80
-
+#define MAX6639_REG_FAN_CONFIG2a_PWM_POL	0x02

drivers/hwmon/max6639.c static const unsigned short normal_i2c[] = { 0x2c, 0x2e, 0x2f, I2C_CLIENT_END }; · line 67

Quoted 10 lines
drivers/hwmon/max6639.c +6 -0
 #define MAX6639_FAN_CONFIG3_THERM_FULL_SPEED	0x40
+#define MAX6639_FAN_CONFIG3_FREQ_MASK		0x03
+#define MAX6639_REG_TARGTDUTY_SLOT		120
 
+/* Tach supported range. This internally controls tach frequency */
 static const int rpm_ranges[] = { 2000, 4000, 8000, 16000 };
 
+/* Supported PWM frequency */
+static const unsigned int freq_table[] = { 20, 33, 50, 100, 5000, 8333, 12500,
+					25000 };

I would have put these in a single line, or one line per freq, or at least aligned the last value such that it is at a higher column than the opening {.

drivers/hwmon/max6639.c static const unsigned short normal_i2c[] = { 0x2c, 0x2e, 0x2f, I2C_CLIENT_END }; · line 70

Quoted 3 lines
drivers/hwmon/max6639.c +3 -0
+
+
+

Three empty lines? One or two only should do it.

Quoted 308 lines
drivers/hwmon/max6639.c +126 -67
 #define FAN_FROM_REG(val, rpm_range)	((val) == 0 || (val) == 255 ? \
 				0 : (rpm_ranges[rpm_range] * 30) / (val))
 #define TEMP_LIMIT_TO_REG(val)	clamp_val((val) / 1000, 0, 255)
@@ -76,20 +85,24 @@ struct max6639_data {
 	u16 temp[2];		/* Temperature, in 1/8 C, 0..255 C */
 	bool temp_fault[2];	/* Detected temperature diode failure */
 	u8 fan[2];		/* Register value: TACH count for fans >=30 */
+	struct pwm_device	*pwmd[2]; /* max6639 has two pwm device */
+	u32 target_rpm[2];
+	u32 max_rpm[2];
+
 	u8 status;		/* Detected channel alarms and fan failures */
 
 	/* Register values only written to */
-	u8 pwm[2];		/* Register value: Duty cycle 0..120 */
 	u8 temp_therm[2];	/* THERM Temperature, 0..255 C (->_max) */
 	u8 temp_alert[2];	/* ALERT Temperature, 0..255 C (->_crit) */
 	u8 temp_ot[2];		/* OT Temperature, 0..255 C (->_emergency) */
 
 	/* Register values initialized only once */
-	u8 ppr;			/* Pulses per rotation 0..3 for 1..4 ppr */
-	u8 rpm_range;		/* Index in above rpm_ranges table */
-
+	u8 ppr[2];		/* Pulses per rotation 0..3 for 1..4 ppr */
+	u8 rpm_range[2];	/* Index in above rpm_ranges table */
 	/* Optional regulator for FAN supply */
 	struct regulator *reg;
+	/* max6639 pwm chip */
+	struct pwm_chip chip;
 };
 
 static struct max6639_data *max6639_update_device(struct device *dev)
@@ -280,8 +293,12 @@ static ssize_t pwm_show(struct device *dev, struct device_attribute *dev_attr,
 {
 	struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
 	struct max6639_data *data = dev_get_drvdata(dev);
+	struct pwm_state state;
+
+	pwm_get_state(data->pwmd[attr->index], &state);
+
+	return sprintf(buf, "%d\n", pwm_get_relative_duty_cycle(&state, 255));
 
-	return sprintf(buf, "%d\n", data->pwm[attr->index] * 255 / 120);
 }
 
 static ssize_t pwm_store(struct device *dev,
@@ -290,9 +307,9 @@ static ssize_t pwm_store(struct device *dev,
 {
 	struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
 	struct max6639_data *data = dev_get_drvdata(dev);
-	struct i2c_client *client = data->client;
 	unsigned long val;
 	int res;
+	struct pwm_state state;
 
 	res = kstrtoul(buf, 10, &val);
 	if (res)
@@ -300,12 +317,12 @@ static ssize_t pwm_store(struct device *dev,
 
 	val = clamp_val(val, 0, 255);
 
-	mutex_lock(&data->update_lock);
-	data->pwm[attr->index] = (u8)(val * 120 / 255);
-	i2c_smbus_write_byte_data(client,
-				  MAX6639_REG_TARGTDUTY(attr->index),
-				  data->pwm[attr->index]);
-	mutex_unlock(&data->update_lock);
+	pwm_get_state(data->pwmd[attr->index], &state);
+	pwm_set_relative_duty_cycle(&state, val, 255);
+	res = pwm_apply_state(data->pwmd[attr->index], &state);
+	if (res)
+		return res;
+
 	return count;
 }
 
@@ -319,7 +336,7 @@ static ssize_t fan_input_show(struct device *dev,
 		return PTR_ERR(data);
 
 	return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[attr->index],
-		       data->rpm_range));
+		       data->rpm_range[attr->index]));
 }
 
 static ssize_t alarm_show(struct device *dev,
@@ -386,29 +403,41 @@ static struct attribute *max6639_attrs[] = {
 ATTRIBUTE_GROUPS(max6639);
 
 /*
- *  returns respective index in rpm_ranges table
- *  1 by default on invalid range
+ *  Get respective index in rpm_ranges table
  */
-static int rpm_range_to_reg(int range)
+static int rpm_range_to_index(struct device *dev, u8 *index, int rpm)
 {
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(rpm_ranges); i++) {
-		if (rpm_ranges[i] == range)
-			return i;
+	if (rpm < 0)
+		return -EINVAL;
+
+	/* Set index based on chip support */
+	switch (rpm) {
+	case 0 ... 2000:
+		*index = 0;
+		break;
+	case 2001 ... 4000:
+		*index = 1;
+		break;
+	case 4001 ... 8000:
+		*index = 2;
+		break;
+	case 8001 ... 16000:
+		*index = 3;
+		break;
+	default:
+		/* Use max range for higher RPM */
+		dev_warn(dev,
+		    "RPM higher than supported range. Default to 16000 RPM");
+		*index = 3;
 	}
-
-	return 1; /* default: 4000 RPM */
+	return 0;
 }
 
 static int max6639_init_client(struct i2c_client *client,
 			       struct max6639_data *data)
 {
-	struct max6639_platform_data *max6639_info =
-		dev_get_platdata(&client->dev);
-	int i;
-	int rpm_range = 1; /* default: 4000 RPM */
-	int err;
+	int i, err;
+	struct pwm_state state;
 
 	/* Reset chip to default values, see below for GCONFIG setup */
 	err = i2c_smbus_write_byte_data(client, MAX6639_REG_GCONFIG,
@@ -416,51 +445,29 @@ static int max6639_init_client(struct i2c_client *client,
 	if (err)
 		goto exit;
 
-	/* Fans pulse per revolution is 2 by default */
-	if (max6639_info && max6639_info->ppr > 0 &&
-			max6639_info->ppr < 5)
-		data->ppr = max6639_info->ppr;
-	else
-		data->ppr = 2;
-	data->ppr -= 1;
-
-	if (max6639_info)
-		rpm_range = rpm_range_to_reg(max6639_info->rpm_range);
-	data->rpm_range = rpm_range;
-
 	for (i = 0; i < 2; i++) {
 
 		/* Set Fan pulse per revolution */
-		err = i2c_smbus_write_byte_data(client,
-				MAX6639_REG_FAN_PPR(i),
-				data->ppr << 6);
+		err = i2c_smbus_write_byte_data(client,	MAX6639_REG_FAN_PPR(i),
+						data->ppr[i] << 6);
 		if (err)
 			goto exit;
 
 		/* Fans config PWM, RPM */
 		err = i2c_smbus_write_byte_data(client,
-			MAX6639_REG_FAN_CONFIG1(i),
-			MAX6639_FAN_CONFIG1_PWM | rpm_range);
-		if (err)
-			goto exit;
-
-		/* Fans PWM polarity high by default */
-		if (max6639_info && max6639_info->pwm_polarity == 0)
-			err = i2c_smbus_write_byte_data(client,
-				MAX6639_REG_FAN_CONFIG2a(i), 0x00);
-		else
-			err = i2c_smbus_write_byte_data(client,
-				MAX6639_REG_FAN_CONFIG2a(i), 0x02);
+						MAX6639_REG_FAN_CONFIG1(i),
+						MAX6639_FAN_CONFIG1_PWM |
+						data->rpm_range[i]);
 		if (err)
 			goto exit;
 
 		/*
-		 * /THERM full speed enable,
+		 * /THERM full speed disable,
 		 * PWM frequency 25kHz, see also GCONFIG below
 		 */
 		err = i2c_smbus_write_byte_data(client,
-			MAX6639_REG_FAN_CONFIG3(i),
-			MAX6639_FAN_CONFIG3_THERM_FULL_SPEED | 0x03);
+						MAX6639_REG_FAN_CONFIG3(i),
+						0x03);
 		if (err)
 			goto exit;
 
@@ -469,31 +476,35 @@ static int max6639_init_client(struct i2c_client *client,
 		data->temp_alert[i] = 90;
 		data->temp_ot[i] = 100;
 		err = i2c_smbus_write_byte_data(client,
-				MAX6639_REG_THERM_LIMIT(i),
-				data->temp_therm[i]);
+						MAX6639_REG_THERM_LIMIT(i),
+						data->temp_therm[i]);
 		if (err)
 			goto exit;
 		err = i2c_smbus_write_byte_data(client,
-				MAX6639_REG_ALERT_LIMIT(i),
-				data->temp_alert[i]);
+						MAX6639_REG_ALERT_LIMIT(i),
+						data->temp_alert[i]);
 		if (err)
 			goto exit;
 		err = i2c_smbus_write_byte_data(client,
-				MAX6639_REG_OT_LIMIT(i), data->temp_ot[i]);
+						MAX6639_REG_OT_LIMIT(i),
+						data->temp_ot[i]);
 		if (err)
 			goto exit;
 
-		/* PWM 120/120 (i.e. 100%) */
-		data->pwm[i] = 120;
-		err = i2c_smbus_write_byte_data(client,
-				MAX6639_REG_TARGTDUTY(i), data->pwm[i]);
+		/* Configure PWM controller */
+		pwm_get_state(data->pwmd[i], &state);
+		pwm_set_relative_duty_cycle(&state, data->target_rpm[i],
+					    data->max_rpm[i]);
+		err = pwm_apply_state(data->pwmd[i], &state);
 		if (err)
 			goto exit;
+
 	}
 	/* Start monitoring */
 	err = i2c_smbus_write_byte_data(client, MAX6639_REG_GCONFIG,
 		MAX6639_GCONFIG_DISABLE_TIMEOUT | MAX6639_GCONFIG_CH2_LOCAL |
 		MAX6639_GCONFIG_PWM_FREQ_HI);
+
 exit:
 	return err;
 }
@@ -524,12 +535,276 @@ static void max6639_regulator_disable(void *data)
 	regulator_disable(data);
 }
 
+static int max6639_probe_child_from_dt(struct i2c_client *client,
+				      struct device_node *child,
+				      struct max6639_data *data)
+
+{
+	struct device *dev = &client->dev;
+	u32 i, maxrpm;
+	int val, err;
+
+	err = of_property_read_u32(child, "reg", &i);
+	if (err) {
+		dev_err(dev, "missing reg property of %pOFn\n", child);
+		return err;
+	}
+
+	if (i >= 2) {
+		dev_err(dev, "invalid reg %d of %pOFn\n", i, child);
+		return -EINVAL;
+	}
+
+	err = of_property_read_u32(child, "pulses-per-revolution", &val);
+	if (err) {
+		dev_err(dev, "missing pulses-per-revolution property of %pOFn",
+			child);
+		return err;
+	}
+
+	if (val < 0 || val > 5) {
+		dev_err(dev, "invalid pulses-per-revolution %d of %pOFn\n", val,
+			child);
+		return -EINVAL;
+	}
+	data->ppr[i] = val;
+
+	err = of_property_read_u32(child, "max-rpm", &maxrpm);
+	if (err) {
+		dev_err(dev, "missing max-rpm property of %pOFn\n", child);
+		return err;
+	}
+
+	err = rpm_range_to_index(dev, &data->rpm_range[i], maxrpm);
+	if (err) {
+		dev_err(dev, "invalid max-rpm %d of %pOFn\n", maxrpm, child);
+		return err;
+	}
+	data->max_rpm[i] = maxrpm;
+
+	err = of_property_read_u32(child, "target-rpm", &val);
+	/* Use provided target RPM else default to maxrpm */
+	if (!err)
+		data->target_rpm[i] = val;
+	else
+		data->target_rpm[i] = maxrpm;
+
+	/* Get pwms property for PWM control */
+	data->pwmd[i] = devm_fwnode_pwm_get(dev, &child->fwnode, NULL);
+

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 604

Quoted 10 lines
drivers/hwmon/max6639.c +10 -0
+	if (!IS_ERR(data->pwmd[i]))
+		return 0;
+
+	if (PTR_ERR(data->pwmd[i]) == -EPROBE_DEFER)
+		return PTR_ERR(data->pwmd[i]);
+
+	dev_dbg(dev, "Using chip default PWM");
+	data->pwmd[i] = pwm_request_from_chip(&data->chip, i, NULL);
+	if (!IS_ERR(data->pwmd[i]))
+		return 0;

Are these PWMs usuable at all for a consumer other than the driver itself? If not I'd doubt the added value of this patch.

Quoted 33 lines
drivers/hwmon/max6639.c +33 -0
+	dev_dbg(dev, "Failed to configure pwm for fan %d", i);
+	return PTR_ERR_OR_ZERO(data->pwmd[i]);
+}
+
+static int max6639_probe_from_dt(struct i2c_client *client,
+				 struct max6639_data *data)
+{
+	struct device *dev = &client->dev;
+	const struct device_node *np = dev->of_node;
+	struct device_node *child;
+	int err;
+
+	/* Compatible with non-DT platforms */
+	if (!np)
+		return 0;
+
+	for_each_child_of_node(np, child) {
+		if (strcmp(child->name, "fan"))
+			continue;
+
+		err = max6639_probe_child_from_dt(client, child, data);
+		if (err) {
+			of_node_put(child);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static struct max6639_data *to_max6639_pwm(struct pwm_chip *chip)
+{
+	return container_of(chip, struct max6639_data, chip);

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 648

Quoted 10 lines
drivers/hwmon/max6639.c +10 -0
+}
+
+static void max6639_pwm_get_state(struct pwm_chip *chip,
+				  struct pwm_device *pwm,
+				  struct pwm_state *state)
+{
+
+	struct max6639_data *data = to_max6639_pwm(chip);
+	struct i2c_client *client = data->client;
+	int value, i = pwm->hwpwm, x;

Please use pwm->hwpwm directly instead of i, this makes the code IMHO a bit more readable.

Quoted 17 lines
drivers/hwmon/max6639.c +17 -0
+	unsigned int freq;
+
+	mutex_lock(&data->update_lock);
+
+	value = i2c_smbus_read_byte_data(client, MAX6639_REG_FAN_CONFIG1(i));
+	if (value < 0)
+		goto abort;
+
+	if (value & MAX6639_FAN_CONFIG1_PWM) {
+		state->enabled = true;
+
+		/* Determine frequency from respective registers */
+		value = i2c_smbus_read_byte_data(client,
+						 MAX6639_REG_FAN_CONFIG3(i));
+		if (value < 0)
+			goto abort;
+		x = value & MAX6639_FAN_CONFIG3_FREQ_MASK;

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 675

Quoted 10 lines
drivers/hwmon/max6639.c +10 -0
+
+		value = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG);
+		if (value < 0)
+			goto abort;
+		if (value & MAX6639_GCONFIG_PWM_FREQ_HI)
+			x |= 0x4;
+		x &= 0x7;
+		freq = freq_table[x];
+
+		state->period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, freq);

both NSEC_PER_SEC and freq fit into an unsigned int, so you can use the (cheaper) DIV_ROUND_UP here.

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 683

Quoted 8 lines
drivers/hwmon/max6639.c +8 -0
+
+		value = i2c_smbus_read_byte_data(client,
+						 MAX6639_REG_TARGTDUTY(i));
+		if (value < 0)
+			goto abort;
+		/* max6639 supports 120 slots only */
+		state->duty_cycle = DIV_ROUND_UP_ULL(value * state->period,
+						     120);

s/120/MAX6639_REG_TARGTDUTY_SLOT/ ?

value * state->period might overflow. Use something like mul_u64_u32_div. (Hmm, there doesn't seem to be a variant for unsigned * unsigned / unsigned?)

You're loosing precision here. Consider freq = 8333 and TARGTDUTY = 97 then you have state->period = 120005 and calulate duty_cycle as:

	DIV_ROUND_UP_ULL(97 * 120005, 120) = 97005

. The exact value is (I guess)

	97 * NSEC_PER_SEC / (8333 * 120) = 97003.880
Quoted 10 lines
drivers/hwmon/max6639.c +10 -0
+		value = i2c_smbus_read_byte_data(client,
+						 MAX6639_REG_FAN_CONFIG2a(i));
+		if (value < 0)
+			goto abort;
+		value &= MAX6639_REG_FAN_CONFIG2a_PWM_POL;
+		state->polarity = (value != 0);
+	} else
+		state->enabled = false;
+
+abort:

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 704

Quoted 10 lines
drivers/hwmon/max6639.c +10 -0
+	mutex_unlock(&data->update_lock);
+
+}
+
+static int max6639_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+			     const struct pwm_state *state)
+{
+	struct max6639_data *data = to_max6639_pwm(chip);
+	struct i2c_client *client = data->client;
+	int value = 0, i = pwm->hwpwm, x;

Please use pwm->hwpwm directly instead of i, this makes the code IMHO a bit more readable.

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 708

Quoted 4 lines
drivers/hwmon/max6639.c +4 -0
+	unsigned int freq;
+	struct pwm_state cstate;
+
+	pwm_get_state(pwm, &cstate);

Please don't use pwm API functions in PWM callbacks. Accessing pwm->state is fine here.

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 718

Quoted 9 lines
drivers/hwmon/max6639.c +9 -0
+	mutex_lock(&data->update_lock);
+
+	if (state->period != cstate.period) {
+		/* Configure frequency */
+		freq = DIV_ROUND_UP_ULL(NSEC_PER_SEC, state->period);
+		/* Chip supports limited number of frequency */
+		for (x = 0; x < sizeof(freq_table); x++)
+			if (freq < freq_table[x])
+				break;

For state->period = 80000 we get:

	freq = 12500

and then x = 7 is picked while x = 6 would be an exact match.

I think you just need

	if (freq <= freq_table[x])
Quoted 10 lines
drivers/hwmon/max6639.c +10 -0
+		value = i2c_smbus_read_byte_data(client,
+						 MAX6639_REG_FAN_CONFIG3(i));
+		if (value < 0)
+			goto abort;
+		value &= ~MAX6639_FAN_CONFIG3_FREQ_MASK;
+		value |= (x & MAX6639_FAN_CONFIG3_FREQ_MASK);
+		value = i2c_smbus_write_byte_data(client,
+						  MAX6639_REG_FAN_CONFIG3(i),
+						  value);
+

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 739

Quoted 10 lines
drivers/hwmon/max6639.c +10 -0
+		value = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG);
+		if (value < 0)
+			goto abort;
+
+		if ((value & MAX6639_GCONFIG_PWM_FREQ_HI) && (x >> 2))
+			value &= ~MAX6639_GCONFIG_PWM_FREQ_HI;
+		else
+			value |= MAX6639_GCONFIG_PWM_FREQ_HI;
+		value = i2c_smbus_write_byte_data(client, MAX6639_REG_GCONFIG,
+						  value);

Why does it depend on MAX6639_GCONFIG_PWM_FREQ_HI being already set if you need to set it?

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 745

Quoted 6 lines
drivers/hwmon/max6639.c +6 -0
+		if (value < 0)
+			goto abort;
+	}
+
+	/* Configure dutycycle */
+	if (state->duty_cycle != cstate.duty_cycle) {

That check is wrong if you go from

	.duty_cycle = 50000; .period = 200000

to

	.duty_cycle = 50000; .period = 100000

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 748

Quoted 3 lines
drivers/hwmon/max6639.c +3 -0
+		value = DIV_ROUND_UP_ULL(
+				state->duty_cycle * MAX6639_REG_TARGTDUTY_SLOT,
+				state->period);

Please round down here. (And test with PWM_DEBUG which might have told you given the right tests.) Also please use the real period to determine the register value. (Otherwise for a request with

	.duty_cycle = 40000; .period = 150000

where a real period of 120004.8 ns is picked you get MAX6639_REG_TARGTDUTY_SLOT = 32 which results in a duty cycle of 32001.28 ns, while with MAX6639_REG_TARGTDUTY_SLOT = 39 you'd get 39001.56 ns.

Quoted 16 lines
drivers/hwmon/max6639.c +16 -0
+		value = i2c_smbus_write_byte_data(client,
+						  MAX6639_REG_TARGTDUTY(i),
+						  value);
+		if (value < 0)
+			goto abort;
+	}
+
+	/* Configure polarity */
+	if (state->polarity != cstate.polarity) {
+		value = i2c_smbus_read_byte_data(client,
+						 MAX6639_REG_FAN_CONFIG2a(i));
+		if (value < 0)
+			goto abort;
+		if (state->polarity == PWM_POLARITY_NORMAL)
+			value |= MAX6639_REG_FAN_CONFIG2a_PWM_POL;
+		else

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 774

Quoted 10 lines
drivers/hwmon/max6639.c +10 -0
+			value &= ~MAX6639_REG_FAN_CONFIG2a_PWM_POL;
+		value = i2c_smbus_write_byte_data(client,
+						  MAX6639_REG_FAN_CONFIG2a(i),
+						  value);
+		if (value < 0)
+			goto abort;
+	}
+
+	if (state->enabled == cstate.enabled)
+		goto abort;

Please use goto only for error handling.

Quoted 38 lines
drivers/hwmon/max6639.c +28 -1
+
+	value = i2c_smbus_read_byte_data(client, MAX6639_REG_FAN_CONFIG1(i));
+	if (value < 0)
+		goto abort;
+	if (state->enabled)
+		value |= MAX6639_FAN_CONFIG1_PWM;
+	else
+		value &= ~MAX6639_FAN_CONFIG1_PWM;
+
+	value = i2c_smbus_write_byte_data(client, MAX6639_REG_FAN_CONFIG1(i),
+					  value);
+	if (value < 0)
+		goto abort;
+	value = 0;
+
+abort:
+	mutex_unlock(&data->update_lock);
+
+	return value;
+}
+
+static const struct pwm_ops max6639_pwm_ops = {
+	.apply = max6639_pwm_apply,
+	.get_state = max6639_pwm_get_state,
+	.owner = THIS_MODULE,
+};
+
 static int max6639_probe(struct i2c_client *client)
 {
 	struct device *dev = &client->dev;
 	struct max6639_data *data;
 	struct device *hwmon_dev;
-	int err;
+	int err, i;
 
 	data = devm_kzalloc(dev, sizeof(struct max6639_data), GFP_KERNEL);
 	if (!data)

drivers/hwmon/max6639.c static int max6639_probe(struct i2c_client *client) · line 819

Quoted 8 lines
drivers/hwmon/max6639.c +5 -0
@@ -537,11 +812,26 @@ static int max6639_probe(struct i2c_client *client)
 
 	data->client = client;
 
+	/* Add PWM controller of max6639 */
+	data->chip.dev = dev;
+	data->chip.ops = &max6639_pwm_ops;
+	data->chip.npwm = 2;
+	data->chip.of_pwm_n_cells = 3;

Please drop this, of_pwmchip_add() overwrites it anyhow.

drivers/hwmon/max6639.c static int max6639_probe(struct i2c_client *client) · line 823

Quoted 4 lines
drivers/hwmon/max6639.c +4 -0
+
+	err = devm_pwmchip_add(dev, &data->chip);
+	if (err < 0) {
+		dev_err(dev, "failed to add PWM chip %d\n", err);

dev_err_probe please.

drivers/hwmon/max6639.c static int max6639_probe(struct i2c_client *client) · line 832

Quoted 12 lines
drivers/hwmon/max6639.c +7 -3
+		return err;
+	}
+
 	data->reg = devm_regulator_get_optional(dev, "fan");
 	if (IS_ERR(data->reg)) {
-		if (PTR_ERR(data->reg) != -ENODEV)
-			return PTR_ERR(data->reg);
-
+		if (PTR_ERR(data->reg) != -ENODEV) {
+			err = (int)PTR_ERR(data->reg);
+			dev_warn(dev, "Failed looking up fan supply: %d\n",
+				 err);

unrelated change. Also dev_probe_err is better suited here.

Quoted 29 lines
drivers/hwmon/max6639.c +18 -0
+			return err;
+		}
 		data->reg = NULL;
 	} else {
 		/* Spin up fans */
@@ -560,6 +850,22 @@ static int max6639_probe(struct i2c_client *client)
 
 	mutex_init(&data->update_lock);
 
+	/* Below are defaults leter overridden by DT properties */
+	for (i = 0; i < 2; i++) {
+		/* 4000 RPM */
+		data->rpm_range[i] = 1;
+		data->ppr[i] = 2;
+		/* Max. temp. 80C/90C/100C */
+		data->temp_therm[i] = 80;
+		data->temp_alert[i] = 90;
+		data->temp_ot[i] = 100;
+	}
+
+	/* Probe from DT to get configuration */
+	err = max6639_probe_from_dt(client, data);
+	if (err)
+		return err;
+
 	/* Initialize the max6639 chip */
 	err = max6639_init_client(client, data);
 	if (err < 0)

drivers/hwmon/max6639.c static int max6639_probe(struct i2c_client *client) · line 880

Quoted 4 lines
drivers/hwmon/max6639.c +1 -0
@@ -571,6 +877,7 @@ static int max6639_probe(struct i2c_client *client)
 	return PTR_ERR_OR_ZERO(hwmon_dev);
 }
 
+#if IS_ENABLED(CONFIG_PM_SLEEP)

unrelated change

Quoted 12 lines
drivers/hwmon/max6639.c +1 -0
 static int max6639_suspend(struct device *dev)
 {
 	struct i2c_client *client = to_i2c_client(dev);
@@ -608,6 +915,7 @@ static int max6639_resume(struct device *dev)
 	return i2c_smbus_write_byte_data(client,
 			MAX6639_REG_GCONFIG, ret & ~MAX6639_GCONFIG_STANDBY);
 }
+#endif
 
 static const struct i2c_device_id max6639_id[] = {
 	{"max6639", 0},

drivers/hwmon/max6639.c static const struct i2c_device_id max6639_id[] = { · line 927

Quoted 5 lines
drivers/hwmon/max6639.c +1 -1
@@ -616,13 +924,22 @@ static const struct i2c_device_id max6639_id[] = {
 
 MODULE_DEVICE_TABLE(i2c, max6639_id);
 
-static DEFINE_SIMPLE_DEV_PM_OPS(max6639_pm_ops, max6639_suspend, max6639_resume);
+#ifdef CONFIG_OF

Note that ACPI also uses of_match_table.

Quoted 8 lines
drivers/hwmon/max6639.c +8 -0
+static const struct of_device_id maxim_of_platform_match[] = {
+	{.compatible = "maxim,max6639"},
+	{},
+};
+MODULE_DEVICE_TABLE(of, maxim_of_platform_match);
+#endif
+
+static SIMPLE_DEV_PM_OPS(max6639_pm_ops, max6639_suspend, max6639_resume);

drivers/hwmon/max6639.c static const struct i2c_device_id max6639_id[] = { · line 945

Quoted 10 lines
drivers/hwmon/max6639.c +1 -0
 
 static struct i2c_driver max6639_driver = {
 	.class = I2C_CLASS_HWMON,
 	.driver = {
 		   .name = "max6639",
 		   .pm = pm_sleep_ptr(&max6639_pm_ops),
+		   .of_match_table = of_match_ptr(maxim_of_platform_match),
 		   },
 	.probe_new = max6639_probe,
 	.id_table = max6639_id,

Best regards
Uwe

Signature · 2 lines
Pengutronix e.K.                           | Uwe Kleine-König            |
Industrial Linux Solutions                 | https://www.pengutronix.de/ |
#5 lore raw

Re: [PATCH v5 3/3] hwmon: (max6639) Change from pdata to dt configuration

Hi Uwe,

Quoted 45 lines · Uwe Kleine-König

Hello,

Quoted 4 lines · Naresh Solanki

max6639_platform_data is not used by any in-kernel driver and does not address the MAX6639 fans separately.
Move to device tree configuration with explicit properties to configure each fan.

My overall impression is that this patch mixes too much things. IMHO it should be split in (at least)

- Add dt support
- Drop platform support
- Add PWM provider support
- Make use of the PWM API

maybe also add the 2nd PWM in a separate step.

Some more comments inline.

Quoted 25 lines

Non-DT platform can still use this module with its default configuration.

---
  drivers/hwmon/Kconfig   |   1 +
  drivers/hwmon/max6639.c | 465 +++++++++++++++++++++++++++++++++-------
  2 files changed, 392 insertions(+), 74 deletions(-)
drivers/hwmon/Kconfig +1 -0
index 5695b266abcf..ad1f6742ca50 100644
@@ -1106,6 +1106,7 @@ config SENSORS_MAX6621
  config SENSORS_MAX6639
  	tristate "Maxim MAX6639 sensor chip"
  	depends on I2C
+	depends on PWM
  	help
  	  If you say yes here you get support for the MAX6639
  	  sensor chips.
drivers/hwmon/max6639.c +0 -1
index 9b895402c80d..d1ae77e8f72e 100644
--- a/drivers/hwmon/max6639.c
Quoted 8 lines

drivers/hwmon/max6639.c line 22

Quoted 6 lines

+++ b/drivers/hwmon/max6639.c

drivers/hwmon/max6639.c +0 -1
@@ -19,7 +19,7 @@
  #include <linux/hwmon-sysfs.h>
  #include <linux/err.h>
  #include <linux/mutex.h>
-#include <linux/platform_data/max6639.h>

This file is now unused and can be dropped.

Sure in next version.

Quoted 18 lines
Quoted 18 lines
drivers/hwmon/max6639.c +5 -1
+#include <linux/pwm.h>
 
  /* Addresses to scan */
  static const unsigned short normal_i2c[] = { 0x2c, 0x2e, 0x2f, I2C_CLIENT_END };
@@ -54,11 +54,20 @@ static const unsigned short normal_i2c[] = { 0x2c, 0x2e, 0x2f, I2C_CLIENT_END };
  #define MAX6639_GCONFIG_PWM_FREQ_HI		0x08
 
  #define MAX6639_FAN_CONFIG1_PWM			0x80
-
+#define MAX6639_REG_FAN_CONFIG2a_PWM_POL	0x02
  #define MAX6639_FAN_CONFIG3_THERM_FULL_SPEED	0x40
+#define MAX6639_FAN_CONFIG3_FREQ_MASK		0x03
+#define MAX6639_REG_TARGTDUTY_SLOT		120
 
+/* Tach supported range. This internally controls tach frequency */
  static const int rpm_ranges[] = { 2000, 4000, 8000, 16000 };
 
Quoted 7 lines

drivers/hwmon/max6639.c static const unsigned short normal_i2c[] = { 0x2c, 0x2e, 0x2f, I2C_CLIENT_END }; · line 67

Quoted 3 lines
drivers/hwmon/max6639.c +3 -0
+/* Supported PWM frequency */
+static const unsigned int freq_table[] = { 20, 33, 50, 100, 5000, 8333, 12500,
+					25000 };

I would have put these in a single line, or one line per freq, or at least aligned the last value such that it is at a higher column than the opening {.

Sure will align.

Quoted 6 lines

drivers/hwmon/max6639.c static const unsigned short normal_i2c[] = { 0x2c, 0x2e, 0x2f, I2C_CLIENT_END }; · line 70

Quoted 4 lines
drivers/hwmon/max6639.c +3 -0
+
+
+

Three empty lines? One or two only should do it.

Oh. Will correct this in next version.

Quoted 314 lines
Quoted 314 lines
drivers/hwmon/max6639.c +131 -67
  #define FAN_FROM_REG(val, rpm_range)	((val) == 0 || (val) == 255 ? \
  				0 : (rpm_ranges[rpm_range] * 30) / (val))
  #define TEMP_LIMIT_TO_REG(val)	clamp_val((val) / 1000, 0, 255)
@@ -76,20 +85,24 @@ struct max6639_data {
  	u16 temp[2];		/* Temperature, in 1/8 C, 0..255 C */
  	bool temp_fault[2];	/* Detected temperature diode failure */
  	u8 fan[2];		/* Register value: TACH count for fans >=30 */
+	struct pwm_device	*pwmd[2]; /* max6639 has two pwm device */
+	u32 target_rpm[2];
+	u32 max_rpm[2];
+
  	u8 status;		/* Detected channel alarms and fan failures */
 
  	/* Register values only written to */
-	u8 pwm[2];		/* Register value: Duty cycle 0..120 */
  	u8 temp_therm[2];	/* THERM Temperature, 0..255 C (->_max) */
  	u8 temp_alert[2];	/* ALERT Temperature, 0..255 C (->_crit) */
  	u8 temp_ot[2];		/* OT Temperature, 0..255 C (->_emergency) */
 
  	/* Register values initialized only once */
-	u8 ppr;			/* Pulses per rotation 0..3 for 1..4 ppr */
-	u8 rpm_range;		/* Index in above rpm_ranges table */
-
+	u8 ppr[2];		/* Pulses per rotation 0..3 for 1..4 ppr */
+	u8 rpm_range[2];	/* Index in above rpm_ranges table */
  	/* Optional regulator for FAN supply */
  	struct regulator *reg;
+	/* max6639 pwm chip */
+	struct pwm_chip chip;
  };
 
  static struct max6639_data *max6639_update_device(struct device *dev)
@@ -280,8 +293,12 @@ static ssize_t pwm_show(struct device *dev, struct device_attribute *dev_attr,
  {
  	struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
  	struct max6639_data *data = dev_get_drvdata(dev);
+	struct pwm_state state;
+
+	pwm_get_state(data->pwmd[attr->index], &state);
+
+	return sprintf(buf, "%d\n", pwm_get_relative_duty_cycle(&state, 255));
 
-	return sprintf(buf, "%d\n", data->pwm[attr->index] * 255 / 120);
  }
 
  static ssize_t pwm_store(struct device *dev,
@@ -290,9 +307,9 @@ static ssize_t pwm_store(struct device *dev,
  {
  	struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
  	struct max6639_data *data = dev_get_drvdata(dev);
-	struct i2c_client *client = data->client;
  	unsigned long val;
  	int res;
+	struct pwm_state state;
 
  	res = kstrtoul(buf, 10, &val);
  	if (res)
@@ -300,12 +317,12 @@ static ssize_t pwm_store(struct device *dev,
 
  	val = clamp_val(val, 0, 255);
 
-	mutex_lock(&data->update_lock);
-	data->pwm[attr->index] = (u8)(val * 120 / 255);
-	i2c_smbus_write_byte_data(client,
-				  MAX6639_REG_TARGTDUTY(attr->index),
-				  data->pwm[attr->index]);
-	mutex_unlock(&data->update_lock);
+	pwm_get_state(data->pwmd[attr->index], &state);
+	pwm_set_relative_duty_cycle(&state, val, 255);
+	res = pwm_apply_state(data->pwmd[attr->index], &state);
+	if (res)
+		return res;
+
  	return count;
  }
 
@@ -319,7 +336,7 @@ static ssize_t fan_input_show(struct device *dev,
  		return PTR_ERR(data);
 
  	return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[attr->index],
-		       data->rpm_range));
+		       data->rpm_range[attr->index]));
  }
 
  static ssize_t alarm_show(struct device *dev,
@@ -386,29 +403,41 @@ static struct attribute *max6639_attrs[] = {
  ATTRIBUTE_GROUPS(max6639);
 
  /*
- *  returns respective index in rpm_ranges table
- *  1 by default on invalid range
+ *  Get respective index in rpm_ranges table
   */
-static int rpm_range_to_reg(int range)
+static int rpm_range_to_index(struct device *dev, u8 *index, int rpm)
  {
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(rpm_ranges); i++) {
-		if (rpm_ranges[i] == range)
-			return i;
+	if (rpm < 0)
+		return -EINVAL;
+
+	/* Set index based on chip support */
+	switch (rpm) {
+	case 0 ... 2000:
+		*index = 0;
+		break;
+	case 2001 ... 4000:
+		*index = 1;
+		break;
+	case 4001 ... 8000:
+		*index = 2;
+		break;
+	case 8001 ... 16000:
+		*index = 3;
+		break;
+	default:
+		/* Use max range for higher RPM */
+		dev_warn(dev,
+		    "RPM higher than supported range. Default to 16000 RPM");
+		*index = 3;
  	}
-
-	return 1; /* default: 4000 RPM */
+	return 0;
  }
 
  static int max6639_init_client(struct i2c_client *client,
  			       struct max6639_data *data)
  {
-	struct max6639_platform_data *max6639_info =
-		dev_get_platdata(&client->dev);
-	int i;
-	int rpm_range = 1; /* default: 4000 RPM */
-	int err;
+	int i, err;
+	struct pwm_state state;
 
  	/* Reset chip to default values, see below for GCONFIG setup */
  	err = i2c_smbus_write_byte_data(client, MAX6639_REG_GCONFIG,
@@ -416,51 +445,29 @@ static int max6639_init_client(struct i2c_client *client,
  	if (err)
  		goto exit;
 
-	/* Fans pulse per revolution is 2 by default */
-	if (max6639_info && max6639_info->ppr > 0 &&
-			max6639_info->ppr < 5)
-		data->ppr = max6639_info->ppr;
-	else
-		data->ppr = 2;
-	data->ppr -= 1;
-
-	if (max6639_info)
-		rpm_range = rpm_range_to_reg(max6639_info->rpm_range);
-	data->rpm_range = rpm_range;
-
  	for (i = 0; i < 2; i++) {
 
  		/* Set Fan pulse per revolution */
-		err = i2c_smbus_write_byte_data(client,
-				MAX6639_REG_FAN_PPR(i),
-				data->ppr << 6);
+		err = i2c_smbus_write_byte_data(client,	MAX6639_REG_FAN_PPR(i),
+						data->ppr[i] << 6);
  		if (err)
  			goto exit;
 
  		/* Fans config PWM, RPM */
  		err = i2c_smbus_write_byte_data(client,
-			MAX6639_REG_FAN_CONFIG1(i),
-			MAX6639_FAN_CONFIG1_PWM | rpm_range);
-		if (err)
-			goto exit;
-
-		/* Fans PWM polarity high by default */
-		if (max6639_info && max6639_info->pwm_polarity == 0)
-			err = i2c_smbus_write_byte_data(client,
-				MAX6639_REG_FAN_CONFIG2a(i), 0x00);
-		else
-			err = i2c_smbus_write_byte_data(client,
-				MAX6639_REG_FAN_CONFIG2a(i), 0x02);
+						MAX6639_REG_FAN_CONFIG1(i),
+						MAX6639_FAN_CONFIG1_PWM |
+						data->rpm_range[i]);
  		if (err)
  			goto exit;
 
  		/*
-		 * /THERM full speed enable,
+		 * /THERM full speed disable,
  		 * PWM frequency 25kHz, see also GCONFIG below
  		 */
  		err = i2c_smbus_write_byte_data(client,
-			MAX6639_REG_FAN_CONFIG3(i),
-			MAX6639_FAN_CONFIG3_THERM_FULL_SPEED | 0x03);
+						MAX6639_REG_FAN_CONFIG3(i),
+						0x03);
  		if (err)
  			goto exit;
 
@@ -469,31 +476,35 @@ static int max6639_init_client(struct i2c_client *client,
  		data->temp_alert[i] = 90;
  		data->temp_ot[i] = 100;
  		err = i2c_smbus_write_byte_data(client,
-				MAX6639_REG_THERM_LIMIT(i),
-				data->temp_therm[i]);
+						MAX6639_REG_THERM_LIMIT(i),
+						data->temp_therm[i]);
  		if (err)
  			goto exit;
  		err = i2c_smbus_write_byte_data(client,
-				MAX6639_REG_ALERT_LIMIT(i),
-				data->temp_alert[i]);
+						MAX6639_REG_ALERT_LIMIT(i),
+						data->temp_alert[i]);
  		if (err)
  			goto exit;
  		err = i2c_smbus_write_byte_data(client,
-				MAX6639_REG_OT_LIMIT(i), data->temp_ot[i]);
+						MAX6639_REG_OT_LIMIT(i),
+						data->temp_ot[i]);
  		if (err)
  			goto exit;
 
-		/* PWM 120/120 (i.e. 100%) */
-		data->pwm[i] = 120;
-		err = i2c_smbus_write_byte_data(client,
-				MAX6639_REG_TARGTDUTY(i), data->pwm[i]);
+		/* Configure PWM controller */
+		pwm_get_state(data->pwmd[i], &state);
+		pwm_set_relative_duty_cycle(&state, data->target_rpm[i],
+					    data->max_rpm[i]);
+		err = pwm_apply_state(data->pwmd[i], &state);
  		if (err)
  			goto exit;
+
  	}
  	/* Start monitoring */
  	err = i2c_smbus_write_byte_data(client, MAX6639_REG_GCONFIG,
  		MAX6639_GCONFIG_DISABLE_TIMEOUT | MAX6639_GCONFIG_CH2_LOCAL |
  		MAX6639_GCONFIG_PWM_FREQ_HI);
+
  exit:
  	return err;
  }
@@ -524,12 +535,276 @@ static void max6639_regulator_disable(void *data)
  	regulator_disable(data);
  }
 
+static int max6639_probe_child_from_dt(struct i2c_client *client,
+				      struct device_node *child,
+				      struct max6639_data *data)
+
+{
+	struct device *dev = &client->dev;
+	u32 i, maxrpm;
+	int val, err;
+
+	err = of_property_read_u32(child, "reg", &i);
+	if (err) {
+		dev_err(dev, "missing reg property of %pOFn\n", child);
+		return err;
+	}
+
+	if (i >= 2) {
+		dev_err(dev, "invalid reg %d of %pOFn\n", i, child);
+		return -EINVAL;
+	}
+
+	err = of_property_read_u32(child, "pulses-per-revolution", &val);
+	if (err) {
+		dev_err(dev, "missing pulses-per-revolution property of %pOFn",
+			child);
+		return err;
+	}
+
+	if (val < 0 || val > 5) {
+		dev_err(dev, "invalid pulses-per-revolution %d of %pOFn\n", val,
+			child);
+		return -EINVAL;
+	}
+	data->ppr[i] = val;
+
+	err = of_property_read_u32(child, "max-rpm", &maxrpm);
+	if (err) {
+		dev_err(dev, "missing max-rpm property of %pOFn\n", child);
+		return err;
+	}
+
+	err = rpm_range_to_index(dev, &data->rpm_range[i], maxrpm);
+	if (err) {
+		dev_err(dev, "invalid max-rpm %d of %pOFn\n", maxrpm, child);
+		return err;
+	}
+	data->max_rpm[i] = maxrpm;
+
+	err = of_property_read_u32(child, "target-rpm", &val);
+	/* Use provided target RPM else default to maxrpm */
+	if (!err)
+		data->target_rpm[i] = val;
+	else
+		data->target_rpm[i] = maxrpm;
+
+	/* Get pwms property for PWM control */
+	data->pwmd[i] = devm_fwnode_pwm_get(dev, &child->fwnode, NULL);
+
+	if (!IS_ERR(data->pwmd[i]))
+		return 0;
+
+	if (PTR_ERR(data->pwmd[i]) == -EPROBE_DEFER)
+		return PTR_ERR(data->pwmd[i]);
Quoted 8 lines

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 604

Quoted 5 lines
drivers/hwmon/max6639.c +5 -0
+
+	dev_dbg(dev, "Using chip default PWM");
+	data->pwmd[i] = pwm_request_from_chip(&data->chip, i, NULL);
+	if (!IS_ERR(data->pwmd[i]))
+		return 0;

Are these PWMs usuable at all for a consumer other than the driver itself? If not I'd doubt the added value of this patch.

That depends on DT. For testing purpose, I specified different pwm handle & those pwm unreference in DT where available for use via sysfs export otherwise the resource was busy.

Quoted 39 lines
Quoted 39 lines
drivers/hwmon/max6639.c +38 -0
+	dev_dbg(dev, "Failed to configure pwm for fan %d", i);
+	return PTR_ERR_OR_ZERO(data->pwmd[i]);
+}
+
+static int max6639_probe_from_dt(struct i2c_client *client,
+				 struct max6639_data *data)
+{
+	struct device *dev = &client->dev;
+	const struct device_node *np = dev->of_node;
+	struct device_node *child;
+	int err;
+
+	/* Compatible with non-DT platforms */
+	if (!np)
+		return 0;
+
+	for_each_child_of_node(np, child) {
+		if (strcmp(child->name, "fan"))
+			continue;
+
+		err = max6639_probe_child_from_dt(client, child, data);
+		if (err) {
+			of_node_put(child);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static struct max6639_data *to_max6639_pwm(struct pwm_chip *chip)
+{
+	return container_of(chip, struct max6639_data, chip);
+}
+
+static void max6639_pwm_get_state(struct pwm_chip *chip,
+				  struct pwm_device *pwm,
+				  struct pwm_state *state)
Quoted 8 lines

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 648

Quoted 5 lines
drivers/hwmon/max6639.c +5 -0
+{
+
+	struct max6639_data *data = to_max6639_pwm(chip);
+	struct i2c_client *client = data->client;
+	int value, i = pwm->hwpwm, x;

Please use pwm->hwpwm directly instead of i, this makes the code IMHO a bit more readable.

I used variable 'i' to make it look short. Can switch to pwm->hwpwm

Quoted 23 lines
Quoted 23 lines
drivers/hwmon/max6639.c +22 -0
+	unsigned int freq;
+
+	mutex_lock(&data->update_lock);
+
+	value = i2c_smbus_read_byte_data(client, MAX6639_REG_FAN_CONFIG1(i));
+	if (value < 0)
+		goto abort;
+
+	if (value & MAX6639_FAN_CONFIG1_PWM) {
+		state->enabled = true;
+
+		/* Determine frequency from respective registers */
+		value = i2c_smbus_read_byte_data(client,
+						 MAX6639_REG_FAN_CONFIG3(i));
+		if (value < 0)
+			goto abort;
+		x = value & MAX6639_FAN_CONFIG3_FREQ_MASK;
+
+		value = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG);
+		if (value < 0)
+			goto abort;
+		if (value & MAX6639_GCONFIG_PWM_FREQ_HI)
Quoted 8 lines

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 675

Quoted 5 lines
drivers/hwmon/max6639.c +5 -0
+			x |= 0x4;
+		x &= 0x7;
+		freq = freq_table[x];
+
+		state->period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, freq);

both NSEC_PER_SEC and freq fit into an unsigned int, so you can use the (cheaper) DIV_ROUND_UP here.

Sure. will update in next version

Quoted 12 lines

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 683

Quoted 9 lines
drivers/hwmon/max6639.c +8 -0
+
+		value = i2c_smbus_read_byte_data(client,
+						 MAX6639_REG_TARGTDUTY(i));
+		if (value < 0)
+			goto abort;
+		/* max6639 supports 120 slots only */
+		state->duty_cycle = DIV_ROUND_UP_ULL(value * state->period,
+						     120);

s/120/MAX6639_REG_TARGTDUTY_SLOT/ ?

Quoted 3 lines

value * state->period might overflow. Use something like mul_u64_u32_div. (Hmm, there doesn't seem to be a variant for unsigned * unsigned / unsigned?)

Yeah that seems better. Will update in next version

Quoted 10 lines

You're loosing precision here. Consider freq = 8333 and TARGTDUTY = 97 then you have state->period = 120005 and calulate duty_cycle as:

	DIV_ROUND_UP_ULL(97 * 120005, 120) = 97005

. The exact value is (I guess)

	97 * NSEC_PER_SEC / (8333 * 120) = 97003.880

For this chip, it may not matter as it supports only 120 slots.

Quoted 15 lines
Quoted 15 lines
drivers/hwmon/max6639.c +15 -0
+		value = i2c_smbus_read_byte_data(client,
+						 MAX6639_REG_FAN_CONFIG2a(i));
+		if (value < 0)
+			goto abort;
+		value &= MAX6639_REG_FAN_CONFIG2a_PWM_POL;
+		state->polarity = (value != 0);
+	} else
+		state->enabled = false;
+
+abort:
+	mutex_unlock(&data->update_lock);
+
+}
+
+static int max6639_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
Quoted 8 lines

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 704

Quoted 5 lines
drivers/hwmon/max6639.c +5 -0
+			     const struct pwm_state *state)
+{
+	struct max6639_data *data = to_max6639_pwm(chip);
+	struct i2c_client *client = data->client;
+	int value = 0, i = pwm->hwpwm, x;

Please use pwm->hwpwm directly instead of i, this makes the code IMHO a bit more readable.

I used variable 'i' to make it look short. Can switch to pwm->hwpwm

Quoted 8 lines

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 708

Quoted 5 lines
drivers/hwmon/max6639.c +4 -0
+	unsigned int freq;
+	struct pwm_state cstate;
+
+	pwm_get_state(pwm, &cstate);

Please don't use pwm API functions in PWM callbacks. Accessing pwm->state is fine here.

Sure.

Quoted 13 lines

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 718

Quoted 10 lines
drivers/hwmon/max6639.c +9 -0
+	mutex_lock(&data->update_lock);
+
+	if (state->period != cstate.period) {
+		/* Configure frequency */
+		freq = DIV_ROUND_UP_ULL(NSEC_PER_SEC, state->period);
+		/* Chip supports limited number of frequency */
+		for (x = 0; x < sizeof(freq_table); x++)
+			if (freq < freq_table[x])
+				break;

For state->period = 80000 we get:

Quoted 8 lines
	freq = 12500

and then x = 7 is picked while x = 6 would be an exact match.

I think you just need

	if (freq <= freq_table[x])

Yes. Will update in next revision.

Quoted 15 lines
Quoted 15 lines
drivers/hwmon/max6639.c +15 -0
+		value = i2c_smbus_read_byte_data(client,
+						 MAX6639_REG_FAN_CONFIG3(i));
+		if (value < 0)
+			goto abort;
+		value &= ~MAX6639_FAN_CONFIG3_FREQ_MASK;
+		value |= (x & MAX6639_FAN_CONFIG3_FREQ_MASK);
+		value = i2c_smbus_write_byte_data(client,
+						  MAX6639_REG_FAN_CONFIG3(i),
+						  value);
+
+		value = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG);
+		if (value < 0)
+			goto abort;
+
+		if ((value & MAX6639_GCONFIG_PWM_FREQ_HI) && (x >> 2))
Quoted 9 lines

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 739

Quoted 5 lines
drivers/hwmon/max6639.c +5 -0
+			value &= ~MAX6639_GCONFIG_PWM_FREQ_HI;
+		else
+			value |= MAX6639_GCONFIG_PWM_FREQ_HI;
+		value = i2c_smbus_write_byte_data(client, MAX6639_REG_GCONFIG,
+						  value);

Why does it depend on MAX6639_GCONFIG_PWM_FREQ_HI being already set if you need to set it?

Yes. Will update in next version

Quoted 9 lines

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 745

Quoted 6 lines
drivers/hwmon/max6639.c +6 -0
+		if (value < 0)
+			goto abort;
+	}
+
+	/* Configure dutycycle */
+	if (state->duty_cycle != cstate.duty_cycle) {

That check is wrong if you go from

Quoted 7 lines
	.duty_cycle = 50000; .period = 200000

to

	.duty_cycle = 50000; .period = 100000

Yes. Will update in next revision.

Quoted 8 lines

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 748

Quoted 3 lines
drivers/hwmon/max6639.c +3 -0
+		value = DIV_ROUND_UP_ULL(
+				state->duty_cycle * MAX6639_REG_TARGTDUTY_SLOT,
+				state->period);

Please round down here. (And test with PWM_DEBUG which might have told you given the right tests.) Also please use the real period to determine the register value. (Otherwise for a request with

Quoted 7 lines
	.duty_cycle = 40000; .period = 150000

where a real period of 120004.8 ns is picked you get MAX6639_REG_TARGTDUTY_SLOT = 32 which results in a duty cycle of 32001.28 ns, while with MAX6639_REG_TARGTDUTY_SLOT = 39 you'd get 39001.56 ns.

Sure will update to round down.

Quoted 69 lines
Quoted 16 lines
drivers/hwmon/max6639.c +16 -0
+		value = i2c_smbus_write_byte_data(client,
+						  MAX6639_REG_TARGTDUTY(i),
+						  value);
+		if (value < 0)
+			goto abort;
+	}
+
+	/* Configure polarity */
+	if (state->polarity != cstate.polarity) {
+		value = i2c_smbus_read_byte_data(client,
+						 MAX6639_REG_FAN_CONFIG2a(i));
+		if (value < 0)
+			goto abort;
+		if (state->polarity == PWM_POLARITY_NORMAL)
+			value |= MAX6639_REG_FAN_CONFIG2a_PWM_POL;
+		else

drivers/hwmon/max6639.c static void max6639_regulator_disable(void *data) · line 774

Quoted 10 lines
drivers/hwmon/max6639.c +10 -0
+			value &= ~MAX6639_REG_FAN_CONFIG2a_PWM_POL;
+		value = i2c_smbus_write_byte_data(client,
+						  MAX6639_REG_FAN_CONFIG2a(i),
+						  value);
+		if (value < 0)
+			goto abort;
+	}
+
+	if (state->enabled == cstate.enabled)
+		goto abort;

Please use goto only for error handling.

Quoted 41 lines
Quoted 1 line

+

drivers/hwmon/max6639.c +27 -1
+	value = i2c_smbus_read_byte_data(client, MAX6639_REG_FAN_CONFIG1(i));
+	if (value < 0)
+		goto abort;
+	if (state->enabled)
+		value |= MAX6639_FAN_CONFIG1_PWM;
+	else
+		value &= ~MAX6639_FAN_CONFIG1_PWM;
+
+	value = i2c_smbus_write_byte_data(client, MAX6639_REG_FAN_CONFIG1(i),
+					  value);
+	if (value < 0)
+		goto abort;
+	value = 0;
+
+abort:
+	mutex_unlock(&data->update_lock);
+
+	return value;
+}
+
+static const struct pwm_ops max6639_pwm_ops = {
+	.apply = max6639_pwm_apply,
+	.get_state = max6639_pwm_get_state,
+	.owner = THIS_MODULE,
+};
+
  static int max6639_probe(struct i2c_client *client)
  {
  	struct device *dev = &client->dev;
  	struct max6639_data *data;
  	struct device *hwmon_dev;
-	int err;
+	int err, i;
 
  	data = devm_kzalloc(dev, sizeof(struct max6639_data), GFP_KERNEL);
  	if (!data)
@@ -537,11 +812,26 @@ static int max6639_probe(struct i2c_client *client)
 
  	data->client = client;
 
Quoted 8 lines

drivers/hwmon/max6639.c static int max6639_probe(struct i2c_client *client) · line 819

Quoted 5 lines
drivers/hwmon/max6639.c +5 -0
+	/* Add PWM controller of max6639 */
+	data->chip.dev = dev;
+	data->chip.ops = &max6639_pwm_ops;
+	data->chip.npwm = 2;
+	data->chip.of_pwm_n_cells = 3;

Please drop this, of_pwmchip_add() overwrites it anyhow.

Sure.

Quoted 7 lines

drivers/hwmon/max6639.c static int max6639_probe(struct i2c_client *client) · line 823

Quoted 4 lines
drivers/hwmon/max6639.c +4 -0
+
+	err = devm_pwmchip_add(dev, &data->chip);
+	if (err < 0) {
+		dev_err(dev, "failed to add PWM chip %d\n", err);

dev_err_probe please.

Sure.

Quoted 6 lines
Quoted 6 lines
drivers/hwmon/max6639.c +3 -1
+		return err;
+	}
+
  	data->reg = devm_regulator_get_optional(dev, "fan");
  	if (IS_ERR(data->reg)) {
-		if (PTR_ERR(data->reg) != -ENODEV)
Quoted 9 lines

drivers/hwmon/max6639.c static int max6639_probe(struct i2c_client *client) · line 832

Quoted 6 lines
drivers/hwmon/max6639.c +4 -2
-			return PTR_ERR(data->reg);
-
+		if (PTR_ERR(data->reg) != -ENODEV) {
+			err = (int)PTR_ERR(data->reg);
+			dev_warn(dev, "Failed looking up fan supply: %d\n",
+				 err);

unrelated change. Also dev_probe_err is better suited here.

Sure.

Quoted 32 lines
Quoted 32 lines
drivers/hwmon/max6639.c +18 -0
+			return err;
+		}
  		data->reg = NULL;
  	} else {
  		/* Spin up fans */
@@ -560,6 +850,22 @@ static int max6639_probe(struct i2c_client *client)
 
  	mutex_init(&data->update_lock);
 
+	/* Below are defaults leter overridden by DT properties */
+	for (i = 0; i < 2; i++) {
+		/* 4000 RPM */
+		data->rpm_range[i] = 1;
+		data->ppr[i] = 2;
+		/* Max. temp. 80C/90C/100C */
+		data->temp_therm[i] = 80;
+		data->temp_alert[i] = 90;
+		data->temp_ot[i] = 100;
+	}
+
+	/* Probe from DT to get configuration */
+	err = max6639_probe_from_dt(client, data);
+	if (err)
+		return err;
+
  	/* Initialize the max6639 chip */
  	err = max6639_init_client(client, data);
  	if (err < 0)
@@ -571,6 +877,7 @@ static int max6639_probe(struct i2c_client *client)
  	return PTR_ERR_OR_ZERO(hwmon_dev);
  }
 
Quoted 4 lines

drivers/hwmon/max6639.c static int max6639_probe(struct i2c_client *client) · line 880

Quoted 1 line
drivers/hwmon/max6639.c +1 -0
+#if IS_ENABLED(CONFIG_PM_SLEEP)

unrelated change

Have put this to avoid error when CONFIG_PM_SLEEP is disabled.

Quoted 29 lines
Quoted 12 lines
drivers/hwmon/max6639.c +1 -0
  static int max6639_suspend(struct device *dev)
  {
  	struct i2c_client *client = to_i2c_client(dev);
@@ -608,6 +915,7 @@ static int max6639_resume(struct device *dev)
  	return i2c_smbus_write_byte_data(client,
  			MAX6639_REG_GCONFIG, ret & ~MAX6639_GCONFIG_STANDBY);
  }
+#endif
 
  static const struct i2c_device_id max6639_id[] = {
  	{"max6639", 0},

drivers/hwmon/max6639.c static const struct i2c_device_id max6639_id[] = { · line 927

Quoted 5 lines
drivers/hwmon/max6639.c +1 -1
@@ -616,13 +924,22 @@ static const struct i2c_device_id max6639_id[] = {
 
  MODULE_DEVICE_TABLE(i2c, max6639_id);
 
-static DEFINE_SIMPLE_DEV_PM_OPS(max6639_pm_ops, max6639_suspend, max6639_resume);
+#ifdef CONFIG_OF

Note that ACPI also uses of_match_table.

Quoted 9 lines
drivers/hwmon/max6639.c +8 -0
+static const struct of_device_id maxim_of_platform_match[] = {
+	{.compatible = "maxim,max6639"},
+	{},
+};
+MODULE_DEVICE_TABLE(of, maxim_of_platform_match);
+#endif
+
+static SIMPLE_DEV_PM_OPS(max6639_pm_ops, max6639_suspend, max6639_resume);
 
Quoted 13 lines

drivers/hwmon/max6639.c static const struct i2c_device_id max6639_id[] = { · line 945

Quoted 9 lines
drivers/hwmon/max6639.c +1 -0
  static struct i2c_driver max6639_driver = {
  	.class = I2C_CLASS_HWMON,
  	.driver = {
  		   .name = "max6639",
  		   .pm = pm_sleep_ptr(&max6639_pm_ops),
+		   .of_match_table = of_match_ptr(maxim_of_platform_match),
  		   },
  	.probe_new = max6639_probe,
  	.id_table = max6639_id,

Best regards
Uwe

Regards,
Naresh