All of lore.kernel.org
 help / color / mirror / Atom feed
From: Troy Mitchell <troy.mitchell@linux.dev>
To: Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	 Conor Dooley <conor+dt@kernel.org>,
	Jean Delvare <jdelvare@suse.com>,
	 Guenter Roeck <linux@roeck-us.net>
Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	 linux-hwmon@vger.kernel.org,
	Troy Mitchell <troy.mitchell@linux.dev>
Subject: [PATCH 3/3] hwmon: (ctf2301) Add support for CTF2301
Date: Tue, 16 Sep 2025 12:46:46 +0800	[thread overview]
Message-ID: <20250916-ctl2301-v1-3-97e7c84f2c47@linux.dev> (raw)
In-Reply-To: <20250916-ctl2301-v1-0-97e7c84f2c47@linux.dev>

This commit introduces driver for the Sensylink CTF2301
system-level thermal management solution chip.

Currently, the driver does NOT support the Auto-Temp mode of the PWM
fan controller, which provides closed-loop automatic fan speed control
based on temperature.

Now this driver supports:
  - Reading local temperature.
  - Reading remote temperature.
  - Controlling the PWM fan output in Direct-DCY mode (direct duty cycle control).
  - Monitoring fan speed via the TACH input (RPM measurement).

Signed-off-by: Troy Mitchell <troy.mitchell@linux.dev>
---
 drivers/hwmon/Kconfig   |  11 ++
 drivers/hwmon/Makefile  |   1 +
 drivers/hwmon/ctf2301.c | 326 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 338 insertions(+)

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 9d28fcf7cd2a6f9e2f54694a717bd85ff4047b46..2120d891e549795c3f3416d08f71916af714f6b6 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -537,6 +537,17 @@ config SENSORS_CROS_EC
 	  This driver can also be built as a module. If so, the module
 	  will be called cros_ec_hwmon.
 
+config SENSORS_CTF2301
+	tristate "Sensylink CTF2301"
+	depends on I2C
+	select REGMAP
+	help
+	  If you say yes here you get support for Sensylink CTF2301
+	  sensor chip.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called ctf2301.
+
 config SENSORS_DRIVETEMP
 	tristate "Hard disk drives with temperature sensors"
 	depends on SCSI && ATA
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index cd8bc4752b4dbf015c6eb46157626f4e8f87dfae..12f2894ce8d5fbfd942409f6c43d78fbdece57b4 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -65,6 +65,7 @@ obj-$(CONFIG_SENSORS_CORETEMP)	+= coretemp.o
 obj-$(CONFIG_SENSORS_CORSAIR_CPRO) += corsair-cpro.o
 obj-$(CONFIG_SENSORS_CORSAIR_PSU) += corsair-psu.o
 obj-$(CONFIG_SENSORS_CROS_EC)	+= cros_ec_hwmon.o
+obj-$(CONFIG_SENSORS_CTF2301)	+= ctf2301.o
 obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
 obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o
 obj-$(CONFIG_SENSORS_DELL_SMM)	+= dell-smm-hwmon.o
diff --git a/drivers/hwmon/ctf2301.c b/drivers/hwmon/ctf2301.c
new file mode 100644
index 0000000000000000000000000000000000000000..2fea4d195519ea34c1d4bf67456098b225d4d13c
--- /dev/null
+++ b/drivers/hwmon/ctf2301.c
@@ -0,0 +1,326 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for CTF2301 system-level thermal management solution chip
+ * Datasheet: https://www.sensylink.com/upload/1/net.sensylink.portal/1689557281035.pdf
+ *
+ * Copyright (C) 2025 Troy Mitchell <troy.mitchell@linux.dev>
+ */
+
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+#define PWM_PARENT_CLOCK			360000
+
+#define CTF2301_LOCAL_TEMP_MSB			0x00
+#define CTF2301_RMT_TEMP_MSB			0x01
+#define CTF2301_ALERT_STATUS			0x02
+#define CTF2301_GLOBAL_CFG			0x03
+#define CTF2301_RMT_TEMP_LSB			0x10
+#define CTF2301_LOCAL_TEMP_LSB			0x15
+#define CTF2301_ALERT_MASK			0x16
+#define	CTF2301_ENHANCED_CFG			0x45
+#define CTF2301_TACH_COUNT_LSB			0x46
+#define CTF2301_TACH_COUNT_MSB			0x47
+#define CTF2301_PWM_AND_TACH_CFG		0x4a
+#define CTF2301_PWM_VALUE			0x4c
+#define CTF2301_PWM_FREQ			0x4d
+#define CTF2301_RMT_DIODE_TEMP_FILTER		0xbf
+
+/* remote diode fault alarm */
+#define ALERT_STATUS_RDFA			BIT(2)
+
+/* alert interrupts enable  */
+#define GLOBAL_CFG_ALERT_MASK			BIT(7)
+/* tach input enable  */
+#define GLOBAL_CFG_TACH_SEL			BIT(2)
+
+/* local high temperature alarm mask */
+#define ALERT_MASK_LHAM				BIT(6)
+/* remote high temperature alarm mask */
+#define ALERT_MASK_RHAM				BIT(4)
+/* remote low temperature alarm mask */
+#define ALERT_MASK_RLAM				BIT(3)
+/* remote t_crit alarm mask */
+#define ALERT_MASK_RCAM				BIT(1)
+/* tachometer alarm mask */
+#define ALERT_MASK_TCHAM			BIT(0)
+
+#define ALERT_MASK_ALL				(ALERT_MASK_LHAM | ALERT_MASK_RHAM | \
+						ALERT_MASK_RLAM | ALERT_MASK_RCAM | \
+						ALERT_MASK_TCHAM)
+
+/* enables signed format for high and t_crit setpoints */
+#define ENHANGCED_CFG_USF			BIT(3)
+
+/* PWM Programming enable */
+#define PWM_AND_TACH_CFG_PWPGM			BIT(5)
+
+#define PWM_DEFAULT_FREQ_CODE			0x17
+
+
+struct ctf2301 {
+	struct i2c_client *client;
+
+	struct regmap *regmap;
+
+	unsigned int pwm_freq_code;
+};
+
+static int ctf2301_read_temp(struct device *dev, u32 attr, int channel, long *val)
+{
+	int regval[2], raw, err, flag = 1, shift = 4, scale = 625;
+	struct ctf2301 *ctf2301 = dev_get_drvdata(dev);
+	unsigned int reg_msb = CTF2301_LOCAL_TEMP_MSB,
+		     reg_lsb = CTF2301_LOCAL_TEMP_LSB;
+
+	switch (attr) {
+	case hwmon_temp_input:
+		if (channel != 0 && channel != 1)
+			return -EOPNOTSUPP;
+
+		if (channel == 1) {
+			err = regmap_read(ctf2301->regmap, CTF2301_ALERT_STATUS, regval);
+			if (err)
+				return err;
+
+			if (regval[0] & ALERT_STATUS_RDFA)
+				return -ENODEV;
+
+			shift = 5;
+			scale = 1250;
+			reg_msb = CTF2301_RMT_TEMP_MSB;
+			reg_lsb = CTF2301_RMT_TEMP_LSB;
+		}
+
+		err = regmap_read(ctf2301->regmap, reg_msb, regval);
+		if (err)
+			return err;
+
+		err = regmap_read(ctf2301->regmap, reg_lsb, regval + 1);
+		if (err)
+			return err;
+
+		dev_err(dev, "local temp: lsb->0x%x, msb->0x%x", regval[1], regval[0]);
+
+		raw = (s16)((regval[0] << 8) | regval[1]);
+
+		raw >>= shift;
+
+		*val = raw * scale * flag;
+
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static int ctf2301_read_fan(struct device *dev, u32 attr, long *val)
+{
+	struct ctf2301 *ctf2301 = dev_get_drvdata(dev);
+	int regval[2], err, speed;
+
+	switch (attr) {
+	case hwmon_fan_input:
+		err = regmap_read(ctf2301->regmap, CTF2301_TACH_COUNT_MSB, regval);
+		if (err)
+			return err;
+
+		err = regmap_read(ctf2301->regmap, CTF2301_TACH_COUNT_LSB, regval + 1);
+		if (err)
+			return err;
+
+		speed = (regval[0] << 8) | regval[1];
+
+		*val = (unsigned int)(1 * (5400000 / speed));
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static int ctf2301_write_pwm(struct device *dev, u32 attr, long val)
+{
+	struct ctf2301 *ctf2301 = dev_get_drvdata(dev);
+	int err, map_val;
+
+	dev_err(dev, "write pwm: %d", attr);
+
+	switch (attr) {
+	case hwmon_pwm_input:
+		map_val = (val * ctf2301->pwm_freq_code * 2) / 255;
+		dev_err(dev, "val:%ld, map_val: %d", val, map_val);
+		err = regmap_write(ctf2301->regmap, CTF2301_PWM_VALUE, map_val);
+		if (err)
+			return err;
+		break;
+	case hwmon_pwm_freq:
+		ctf2301->pwm_freq_code = DIV_ROUND_UP(PWM_PARENT_CLOCK, val) / 2;
+		dev_err(dev, "pwm_freq_code: %d", ctf2301->pwm_freq_code);
+		err = regmap_write(ctf2301->regmap, CTF2301_PWM_FREQ, ctf2301->pwm_freq_code);
+		if (err)
+			return err;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static umode_t ctf2301_is_visible(const void *drvdata,
+				 enum hwmon_sensor_types type,
+				 u32 attr, int channel)
+{
+	switch (type) {
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_input:
+			return 0444;
+		default:
+			return 0;
+		}
+	case hwmon_fan:
+		switch (attr) {
+		case hwmon_fan_input:
+			return 0444;
+		default:
+			return 0;
+		}
+	case hwmon_pwm:
+		switch (attr) {
+		case hwmon_pwm_input:
+		case hwmon_pwm_freq:
+			return 0644;
+		default:
+			return 0;
+		}
+	default:
+		return 0;
+	}
+}
+
+static int ctf2301_read(struct device *dev, enum hwmon_sensor_types type,
+		       u32 attr, int channel, long *val)
+{
+	switch (type) {
+	case hwmon_temp:
+		return ctf2301_read_temp(dev, attr, channel, val);
+	case hwmon_fan:
+		return ctf2301_read_fan(dev, attr, val);
+	default:
+		return -EOPNOTSUPP;
+	}
+	return 0;
+}
+
+static int ctf2301_write(struct device *dev, enum hwmon_sensor_types type,
+			 u32 attr, int channel, long val)
+{
+	switch (type) {
+	case hwmon_pwm:
+		return ctf2301_write_pwm(dev, attr, val);
+	default:
+		return -EOPNOTSUPP;
+	}
+	return 0;
+}
+
+static const struct hwmon_channel_info * const ctf2301_info[] = {
+	HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT, HWMON_T_INPUT),
+	HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_FREQ),
+	HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
+	NULL
+};
+
+static const struct hwmon_ops ctf2301_hwmon_ops = {
+	.is_visible = ctf2301_is_visible,
+	.read = ctf2301_read,
+	.write = ctf2301_write
+};
+
+static const struct hwmon_chip_info ctf2301_chip_info = {
+	.ops = &ctf2301_hwmon_ops,
+	.info = ctf2301_info,
+};
+
+static const struct regmap_config ctf2301_regmap_config = {
+	.max_register = CTF2301_RMT_DIODE_TEMP_FILTER,
+	.reg_bits = 8,
+	.val_bits = 8,
+};
+
+static int ctf2301_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct device *hwmon_dev;
+	struct ctf2301 *ctf2301;
+	int err;
+
+	ctf2301 = devm_kzalloc(dev, sizeof(*ctf2301), GFP_KERNEL);
+	if (!ctf2301)
+		return -ENOMEM;
+	ctf2301->client = client;
+
+	ctf2301->regmap = devm_regmap_init_i2c(client, &ctf2301_regmap_config);
+	if (IS_ERR(ctf2301->regmap))
+		return dev_err_probe(dev, PTR_ERR(ctf2301->regmap),
+				     "failed to allocate register map");
+
+	err = regmap_write(ctf2301->regmap, CTF2301_GLOBAL_CFG,
+			   GLOBAL_CFG_ALERT_MASK | GLOBAL_CFG_TACH_SEL);
+	if (err)
+		return dev_err_probe(dev, err,
+				     "failed to write CTF2301_GLOBAL_CFG register");
+
+	/*err = regmap_write(ctf2301->regmap, CTF2301_ALERT_MASK, ALERT_MASK_ALL);*/
+	/*if (err)*/
+		/*return dev_err_probe(dev, err,*/
+				     /*"failed to write CTF2301_ALERT_MASK");*/
+
+	err = regmap_write(ctf2301->regmap, CTF2301_ENHANCED_CFG, ENHANGCED_CFG_USF);
+	if (err)
+		return dev_err_probe(dev, err,
+				     "failed to write CTF2301_ENHANCED_CFG");
+
+	err = regmap_write(ctf2301->regmap, CTF2301_PWM_AND_TACH_CFG, PWM_AND_TACH_CFG_PWPGM);
+	if (err)
+		return dev_err_probe(dev, err,
+				     "failed to write CTF2301_PWM_AND_TACH_CFG");
+
+	ctf2301->pwm_freq_code = PWM_DEFAULT_FREQ_CODE;
+
+	hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, ctf2301,
+							 &ctf2301_chip_info,
+							 NULL);
+	if (IS_ERR(hwmon_dev))
+		return dev_err_probe(dev, PTR_ERR(hwmon_dev),
+				     "failed to register hwmon device");
+
+	return 0;
+}
+
+static const struct of_device_id ctf2301_of_match[] = {
+	{ .compatible = "sensylink,ctf2301", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ctf2301_of_match);
+
+static struct i2c_driver ctf2301_driver = {
+	.driver = {
+		.name	= "ctf2301",
+		.of_match_table = of_match_ptr(ctf2301_of_match),
+	},
+	.probe		= ctf2301_probe,
+};
+module_i2c_driver(ctf2301_driver);
+
+MODULE_AUTHOR("Troy Mitchell <troy.mitchell@linux.dev>");
+MODULE_DESCRIPTION("ctf2301 driver");
+MODULE_LICENSE("GPL");

-- 
2.51.0


  parent reply	other threads:[~2025-09-16  4:48 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-09-16  4:46 [PATCH 0/3] hwmon: (ctf2301) Add support for CTF2301 Troy Mitchell
2025-09-16  4:46 ` [PATCH 1/3] dt-bindings: vendor-prefixes: Add Sensylink Troy Mitchell
2025-09-24 14:41   ` Guenter Roeck
2025-09-26  1:17     ` Troy Mitchell
2025-09-16  4:46 ` [PATCH 2/3] dt-bindings: Add CTF2301 devicetree bindings Troy Mitchell
2025-09-16 13:25   ` Rob Herring (Arm)
2025-09-16 13:52   ` Rob Herring
2025-09-17  6:40     ` Troy Mitchell
2025-09-16  4:46 ` Troy Mitchell [this message]
2025-09-16  5:02   ` [PATCH 3/3] hwmon: (ctf2301) Add support for CTF2301 Troy Mitchell
2025-09-24 15:43   ` Guenter Roeck
2025-09-26  1:32     ` Troy Mitchell
2025-09-26  3:57       ` Guenter Roeck
2025-09-26 13:21         ` Troy Mitchell
2025-09-26 13:33           ` 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=20250916-ctl2301-v1-3-97e7c84f2c47@linux.dev \
    --to=troy.mitchell@linux.dev \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=jdelvare@suse.com \
    --cc=krzk+dt@kernel.org \
    --cc=linux-hwmon@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@roeck-us.net \
    --cc=robh@kernel.org \
    /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.