All of lore.kernel.org
 help / color / mirror / Atom feed
From: Naresh Solanki <naresh.solanki@9elements.com>
To: Guenter Roeck <linux@roeck-us.net>,
	krzysztof.kozlowski+dt@linaro.org,
	u.kleine-koenig@pengutronix.de, Jean Delvare <jdelvare@suse.com>
Cc: Naresh Solanki <naresh.solanki@9elements.com>,
	linux-hwmon@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-pwm@vger.kernel.org
Subject: [PATCH 2/4] hwmon: (max6639) : Utilise pwm subsystem
Date: Tue, 16 Apr 2024 22:47:15 +0530	[thread overview]
Message-ID: <20240416171720.2875916-2-naresh.solanki@9elements.com> (raw)
In-Reply-To: <20240416171720.2875916-1-naresh.solanki@9elements.com>

Utilise pwm subsystem for fan pwm handling

Signed-off-by: Naresh Solanki <naresh.solanki@9elements.com>
---
 drivers/hwmon/Kconfig   |   1 +
 drivers/hwmon/max6639.c | 200 ++++++++++++++++++++++++++++++++++++++--
 2 files changed, 191 insertions(+), 10 deletions(-)

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 257ec5360e35..c9cc74f8c807 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1224,6 +1224,7 @@ config SENSORS_MAX6639
 	tristate "Maxim MAX6639 sensor chip"
 	depends on I2C
 	select REGMAP_I2C
+	depends on PWM
 	help
 	  If you say yes here you get support for the MAX6639
 	  sensor chips.
diff --git a/drivers/hwmon/max6639.c b/drivers/hwmon/max6639.c
index 1af93fc53cb5..f37fdd161154 100644
--- a/drivers/hwmon/max6639.c
+++ b/drivers/hwmon/max6639.c
@@ -20,6 +20,7 @@
 #include <linux/err.h>
 #include <linux/mutex.h>
 #include <linux/platform_data/max6639.h>
+#include <linux/pwm.h>
 #include <linux/regmap.h>
 
 /* Addresses to scan */
@@ -55,6 +56,9 @@ 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_FREQ_MASK		0x03
+#define MAX6639_REG_TARGTDUTY_SLOT		120
 
 #define MAX6639_FAN_CONFIG3_THERM_FULL_SPEED	0x40
 
@@ -62,6 +66,10 @@ static const unsigned short normal_i2c[] = { 0x2c, 0x2e, 0x2f, I2C_CLIENT_END };
 
 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 };
+
 #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)
@@ -93,6 +101,9 @@ struct max6639_data {
 
 	/* Optional regulator for FAN supply */
 	struct regulator *reg;
+	/* max6639 pwm chip */
+	struct pwm_chip chip;
+	struct pwm_device *pwmd[MAX6639_NDEV]; /* max6639 has two pwm device */
 };
 
 static struct max6639_data *max6639_update_device(struct device *dev)
@@ -271,8 +282,11 @@ 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", data->pwm[attr->index] * 255 / 120);
+	return sprintf(buf, "%d\n", pwm_get_relative_duty_cycle(&state, 255));
 }
 
 static ssize_t pwm_store(struct device *dev,
@@ -281,6 +295,7 @@ 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 pwm_state state;
 	unsigned long val;
 	int res;
 
@@ -290,10 +305,10 @@ 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);
-	regmap_write(data->regmap, 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);
+	pwm_apply_state(data->pwmd[attr->index], &state);
+
 	return count;
 }
 
@@ -373,6 +388,158 @@ static struct attribute *max6639_attrs[] = {
 };
 ATTRIBUTE_GROUPS(max6639);
 
+static struct max6639_data *to_max6639_pwm(struct pwm_chip *chip)
+{
+	return container_of(chip, struct max6639_data, chip);
+}
+
+static int max6639_pwm_get_state(struct pwm_chip *chip,
+				 struct pwm_device *pwm,
+				 struct pwm_state *state)
+{
+	struct max6639_data *data = to_max6639_pwm(chip);
+	int value, i = pwm->hwpwm, x, err;
+	unsigned int freq;
+
+	mutex_lock(&data->update_lock);
+
+	err = regmap_read(data->regmap, MAX6639_REG_FAN_CONFIG1(i), &value);
+	if (err < 0)
+		goto abort;
+
+	if (value & MAX6639_FAN_CONFIG1_PWM) {
+		state->enabled = true;
+
+		/* Determine frequency from respective registers */
+		err = regmap_read(data->regmap, MAX6639_REG_FAN_CONFIG3(i), &value);
+		if (err < 0)
+			goto abort;
+		x = value & MAX6639_FAN_CONFIG3_FREQ_MASK;
+
+		err = regmap_read(data->regmap, MAX6639_REG_GCONFIG, &value);
+		if (err < 0)
+			goto abort;
+		if (value & MAX6639_GCONFIG_PWM_FREQ_HI)
+			x |= 0x4;
+		x &= 0x7;
+		freq = freq_table[x];
+
+		state->period = DIV_ROUND_UP(NSEC_PER_SEC, freq);
+
+		err = regmap_read(data->regmap, MAX6639_REG_TARGTDUTY(i), &value);
+		if (err < 0)
+			goto abort;
+		/* max6639 supports 120 slots only */
+		state->duty_cycle = mul_u64_u32_div(state->period, value, 120);
+
+		err = regmap_read(data->regmap, MAX6639_REG_FAN_CONFIG2a(i), &value);
+		if (err < 0)
+			goto abort;
+		value &= MAX6639_REG_FAN_CONFIG2a_PWM_POL;
+		state->polarity = (value != 0);
+	} else {
+		state->enabled = false;
+	}
+
+abort:
+	mutex_unlock(&data->update_lock);
+	return value;
+}
+
+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);
+	int value, i = pwm->hwpwm, x, err;
+	unsigned int freq;
+	struct pwm_state cstate;
+
+	cstate = pwm->state;
+
+	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;
+
+		err = regmap_read(data->regmap, MAX6639_REG_FAN_CONFIG3(i), &value);
+		if (err < 0)
+			goto abort;
+
+		value &= ~MAX6639_FAN_CONFIG3_FREQ_MASK;
+		value |= (x & MAX6639_FAN_CONFIG3_FREQ_MASK);
+		err = regmap_write(data->regmap, MAX6639_REG_FAN_CONFIG3(i), value);
+		if (err < 0)
+			goto abort;
+
+		err = regmap_read(data->regmap, MAX6639_REG_GCONFIG, &value);
+		if (err < 0)
+			goto abort;
+
+		if (x >> 2)
+			value &= ~MAX6639_GCONFIG_PWM_FREQ_HI;
+		else
+			value |= MAX6639_GCONFIG_PWM_FREQ_HI;
+		err = regmap_write(data->regmap, MAX6639_REG_GCONFIG, value);
+		if (err < 0)
+			goto abort;
+	}
+
+	/* Configure dutycycle */
+	if (state->duty_cycle != cstate.duty_cycle ||
+	    state->period != cstate.period) {
+		value = DIV_ROUND_DOWN_ULL(state->duty_cycle * MAX6639_REG_TARGTDUTY_SLOT,
+					   state->period);
+		err = regmap_write(data->regmap, MAX6639_REG_TARGTDUTY(i), value);
+		if (err < 0)
+			goto abort;
+	}
+
+	/* Configure polarity */
+	if (state->polarity != cstate.polarity) {
+		err = regmap_read(data->regmap, MAX6639_REG_FAN_CONFIG2a(i), &value);
+		if (err < 0)
+			goto abort;
+		if (state->polarity == PWM_POLARITY_NORMAL)
+			value |= MAX6639_REG_FAN_CONFIG2a_PWM_POL;
+		else
+			value &= ~MAX6639_REG_FAN_CONFIG2a_PWM_POL;
+		err = regmap_write(data->regmap, MAX6639_REG_FAN_CONFIG2a(i), value);
+		if (err < 0)
+			goto abort;
+	}
+
+	if (state->enabled != cstate.enabled) {
+		err = regmap_read(data->regmap, MAX6639_REG_FAN_CONFIG1(i), &value);
+		if (err < 0)
+			goto abort;
+		if (state->enabled)
+			value |= MAX6639_FAN_CONFIG1_PWM;
+		else
+			value &= ~MAX6639_FAN_CONFIG1_PWM;
+
+		err = regmap_write(data->regmap, MAX6639_REG_FAN_CONFIG1(i), value);
+		if (err < 0)
+			goto abort;
+	}
+	value = 0;
+
+abort:
+	mutex_unlock(&data->update_lock);
+
+	return err;
+}
+
+static const struct pwm_ops max6639_pwm_ops = {
+	.apply = max6639_pwm_apply,
+	.get_state = max6639_pwm_get_state,
+};
+
 /*
  *  returns respective index in rpm_ranges table
  *  1 by default on invalid range
@@ -396,6 +563,7 @@ static int max6639_init_client(struct i2c_client *client,
 		dev_get_platdata(&client->dev);
 	int i;
 	int rpm_range = 1; /* default: 4000 RPM */
+	struct pwm_state state;
 	int err;
 
 	/* Reset chip to default values, see below for GCONFIG setup */
@@ -459,11 +627,15 @@ static int max6639_init_client(struct i2c_client *client,
 		if (err)
 			goto exit;
 
-		/* PWM 120/120 (i.e. 100%) */
-		data->pwm[i] = 120;
-		err = regmap_write(data->regmap, MAX6639_REG_TARGTDUTY(i), data->pwm[i]);
-		if (err)
-			goto exit;
+		dev_dbg(&client->dev, "Using chip default PWM");
+		data->pwmd[i] = pwm_request_from_chip(&data->chip, i, NULL);
+		if (IS_ERR(data->pwmd[i]))
+			return PTR_ERR(data->pwmd[i]);
+		pwm_get_state(data->pwmd[i], &state);
+		state.period = DIV_ROUND_UP(NSEC_PER_SEC, 25000);
+		state.polarity = PWM_POLARITY_NORMAL;
+		pwm_set_relative_duty_cycle(&state, 0, 255);
+		pwm_apply_state(data->pwmd[i], &state);
 	}
 	/* Start monitoring */
 	err = regmap_write(data->regmap, MAX6639_REG_GCONFIG,
@@ -540,6 +712,14 @@ static int max6639_probe(struct i2c_client *client)
 				     PTR_ERR(data->regmap),
 				     "regmap initialization failed\n");
 
+	/* Add PWM controller of max6639 */
+	data->chip.dev = dev;
+	data->chip.ops = &max6639_pwm_ops;
+	data->chip.npwm = MAX6639_NDEV;
+	err = devm_pwmchip_add(dev, &data->chip);
+	if (err < 0)
+		return dev_err_probe(dev, err, "failed to add PWM chip\n");
+
 	data->reg = devm_regulator_get_optional(dev, "fan");
 	if (IS_ERR(data->reg)) {
 		if (PTR_ERR(data->reg) != -ENODEV)
-- 
2.42.0


  reply	other threads:[~2024-04-16 17:17 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-04-16 17:17 [PATCH 1/4] hwmon (max6639): Use regmap Naresh Solanki
2024-04-16 17:17 ` Naresh Solanki [this message]
2024-04-16 21:22   ` [PATCH 2/4] hwmon: (max6639) : Utilise pwm subsystem Guenter Roeck
2024-04-22 10:39     ` Naresh Solanki
2024-04-22 12:37       ` Guenter Roeck
2024-05-06 10:05         ` Naresh Solanki
2024-05-06 13:49           ` Guenter Roeck
2024-04-17  7:04   ` Uwe Kleine-König
2024-04-16 17:17 ` [PATCH 3/4] hwmon: (max6639) : Add hwmon init using info Naresh Solanki
2024-04-16 17:17 ` [PATCH 4/4] hwmon (max6639): Remove hwmon init with group Naresh Solanki
2024-04-16 21:23   ` Guenter Roeck
2024-04-16 21:25 ` [PATCH 1/4] hwmon (max6639): Use regmap Guenter Roeck
2024-04-22 10:36   ` Naresh Solanki
2024-04-22 16:02     ` Guenter Roeck
2024-04-25  9:50       ` Naresh Solanki
2024-04-28 17:18         ` Guenter Roeck
2024-04-29  8:19           ` Naresh Solanki
2024-04-29 13:49             ` Guenter Roeck

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20240416171720.2875916-2-naresh.solanki@9elements.com \
    --to=naresh.solanki@9elements.com \
    --cc=jdelvare@suse.com \
    --cc=krzysztof.kozlowski+dt@linaro.org \
    --cc=linux-hwmon@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pwm@vger.kernel.org \
    --cc=linux@roeck-us.net \
    --cc=u.kleine-koenig@pengutronix.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.