linux-hwmon.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] hwmon: Driver for Sensylink CTF2304
@ 2023-03-25  3:21 Il Han
  2023-03-25 13:20 ` Guenter Roeck
  0 siblings, 1 reply; 5+ messages in thread
From: Il Han @ 2023-03-25  3:21 UTC (permalink / raw)
  To: Guenter Roeck, Jean Delvare; +Cc: linux-hwmon, Il Han

From: Il Han <corone.il.han@gmail.com>

The driver supports the Sensylink CTF2304.

Signed-off-by: Il Han <corone.il.han@gmail.com>
---
 Documentation/hwmon/ctf2304.rst |  41 +++
 drivers/hwmon/Kconfig           |  10 +
 drivers/hwmon/Makefile          |   1 +
 drivers/hwmon/ctf2304.c         | 554 ++++++++++++++++++++++++++++++++
 4 files changed, 606 insertions(+)
 create mode 100644 Documentation/hwmon/ctf2304.rst
 create mode 100644 drivers/hwmon/ctf2304.c

diff --git a/Documentation/hwmon/ctf2304.rst b/Documentation/hwmon/ctf2304.rst
new file mode 100644
index 000000000000..d0e9ef9ea52e
--- /dev/null
+++ b/Documentation/hwmon/ctf2304.rst
@@ -0,0 +1,41 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Kernel driver ctf2304
+=====================
+
+Supported chips:
+
+  * Sensylink CTF2304
+
+    Prefix: 'ctf2304'
+
+    Addresses scanned: -
+
+    Datasheet:
+
+Author: Il Han <corone.il.han@gmail.com>
+
+
+Description
+-----------
+
+This driver implements support for the Sensylink CTF2304 chip.
+
+The CTF2304 controls the speeds of up to four fans using four independent
+PWM outputs with local and remote temperature and remote voltage sensing.
+
+
+Sysfs entries
+-------------
+
+================== === =======================================================
+fan[1-4]_input     RO  fan tachometer speed in RPM
+fan[1-4]_target    RW  desired fan speed in RPM
+fan[1-4]_div       RW  sets the RPM range of the fan.
+pwm[1-4]_enable    RW  regulator mode,
+                       0=auto temperature mode, 1=manual mode, 2=rpm mode
+pwm[1-4]           RW  read: current pwm duty cycle,
+                       write: target pwm duty cycle (0-255)
+in[1-8]_input      RO  measured output voltage
+temp[1-9]_input    RO  measured temperature
+================== === =======================================================
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 5b3b76477b0e..da9fbb0f8af3 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -474,6 +474,16 @@ config SENSORS_CORSAIR_PSU
 	  This driver can also be built as a module. If so, the module
 	  will be called corsair-psu.
 
+config SENSORS_CTF2304
+	tristate "Sensylink CTF2304 sensor chip"
+	depends on I2C
+	help
+	  If you say yes here you get support for PWM and Fan Controller
+	  with temperature and voltage sensing.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called ctf2304.
+
 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 88712b5031c8..3742b52f032d 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -60,6 +60,7 @@ obj-$(CONFIG_SENSORS_BT1_PVT)	+= bt1-pvt.o
 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_CTF2304)	+= ctf2304.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/ctf2304.c b/drivers/hwmon/ctf2304.c
new file mode 100644
index 000000000000..5788740a57e5
--- /dev/null
+++ b/drivers/hwmon/ctf2304.c
@@ -0,0 +1,554 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ctf2304.c - Part of lm_sensors, Linux kernel modules for hardware
+ *             monitoring.
+ *
+ * (C) 2023 by Il Han <corone.il.han@gmail.com>
+ */
+
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+/* CTF2304 registers */
+#define CTF2304_REG_LOCAL_TEMP		0x00
+#define CTF2304_REG_REMOTE_CHANNEL(ch)	(0x01 + (ch))
+#define CTF2304_REG_TACH_COUNT(ch)	(0x09 + (ch))
+#define CTF2304_REG_FAN_CONFIG1		0x10
+#define CTF2304_REG_FAN_CONFIG2		0x11
+#define CTF2304_REG_FAN_RPM_CTRL	0x18
+#define CTF2304_REG_PWMOUT(ch)		(0x40 + (ch))
+#define CTF2304_REG_TARGET_COUNT(ch)	(0x44 + (ch))
+
+/* Fan Configure1 register bits */
+#define CTF2304_FAN_CFG1_TRANGE		0x0400
+#define CTF2304_FAN_CFG1_MODE_MASK	(0x7)
+#define CTF2304_FAN_CFG1_MODE_SHIFT	7
+
+/* Fan Configure2 register bits */
+#define CTF2304_FAN_CFG2_MODE_MASK(ch)	(0x6 << (ch) * 4)
+#define CTF2304_FAN_CFG2_MODE_SHIFT(ch)	(1 + (ch) * 4)
+#define CTF2304_FAN_CFG2_DCY_MODE	0
+#define CTF2304_FAN_CFG2_RPM_MODE	1
+#define CTF2304_FAN_CFG2_TEMP_MODE	2
+#define CTF2304_FAN_CFG2_MAX_MODE	3
+
+/* Fan RPM CTRL register bits */
+#define CTF2304_FAN_DIV_MASK(ch)	(0x6 << (ch) * 4)
+#define CTF2304_FAN_DIV_SHIFT(ch)	(1 + (ch) * 4)
+
+#define CTF2304_VCC			3300
+
+#define FAN_RPM_MIN			480
+#define FAN_RPM_MAX			1966080
+
+#define FAN_COUNT_REG_MAX		0xFFF
+
+#define TEMP_FROM_REG(reg)		(((reg) * 1000) >> 8)
+#define VOLT_FROM_REG(reg, fs)		((((reg) >> 4) * (fs)) >> 12)
+#define DIV_FROM_REG(reg)		(1 << (reg))
+#define DIV_TO_REG(div)			((div == 8) ? 0x3 : \
+					 (div == 4) ? 0x2 : \
+					 (div == 1) ? 0x0 : 0x1)
+#define RPM_FROM_REG(reg)		(((reg) >> 4) ? \
+					 ((32768 * 60) / ((reg) >> 4)) : \
+					 FAN_RPM_MAX)
+#define RPM_TO_REG(rpm)			((rpm) ? \
+					 ((32768 * 60) / (rpm)) : \
+					 FAN_COUNT_REG_MAX)
+
+#define NR_CHANNEL			8
+#define NR_FAN_CHANNEL			4
+
+/*
+ * Client data (each client gets its own)
+ */
+struct ctf2304_data {
+	struct i2c_client *client;
+	struct mutex update_lock;
+	char valid; /* zero until following fields are valid */
+	unsigned long last_updated; /* in jiffies */
+
+	/* register values */
+	u16 local_temp;
+	u16 remote_channel[NR_CHANNEL];
+	u16 tach[NR_FAN_CHANNEL];
+	u16 fan_config1;
+	u16 fan_config2;
+	u16 fan_rpm_ctrl;
+	u16 pwm[NR_FAN_CHANNEL];
+	u16 target_count[NR_FAN_CHANNEL];
+};
+
+static struct ctf2304_data *ctf2304_update_device(struct device *dev)
+{
+	struct ctf2304_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	int i;
+	int rv;
+
+	mutex_lock(&data->update_lock);
+
+	if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
+		rv = i2c_smbus_read_word_swapped(client,
+				CTF2304_REG_LOCAL_TEMP);
+		if (rv >= 0)
+			data->local_temp = rv;
+
+		for (i = 0; i < NR_CHANNEL; i++) {
+			rv = i2c_smbus_read_word_swapped(client,
+					CTF2304_REG_REMOTE_CHANNEL(i));
+			if (rv >= 0)
+				data->remote_channel[i] = rv;
+		}
+
+		rv = i2c_smbus_read_word_swapped(client,
+				CTF2304_REG_FAN_CONFIG1);
+		if (rv >= 0)
+			data->fan_config1 = rv;
+		rv = i2c_smbus_read_word_swapped(client,
+				CTF2304_REG_FAN_CONFIG2);
+		if (rv >= 0)
+			data->fan_config2 = rv;
+		rv = i2c_smbus_read_word_swapped(client,
+				CTF2304_REG_FAN_RPM_CTRL);
+		if (rv >= 0)
+			data->fan_rpm_ctrl = rv;
+
+		for (i = 0; i < NR_FAN_CHANNEL; i++) {
+			rv = i2c_smbus_read_word_swapped(client,
+					CTF2304_REG_TACH_COUNT(i));
+			if (rv >= 0)
+				data->tach[i] = rv;
+			rv = i2c_smbus_read_word_swapped(client,
+					CTF2304_REG_PWMOUT(i));
+			if (rv >= 0)
+				data->pwm[i] = rv;
+			rv = i2c_smbus_read_word_swapped(client,
+					CTF2304_REG_TARGET_COUNT(i));
+			if (rv >= 0)
+				data->target_count[i] = rv;
+		}
+
+		data->last_updated = jiffies;
+		data->valid = true;
+	}
+
+	mutex_unlock(&data->update_lock);
+
+	return data;
+}
+
+static int register_to_temp(u16 reg, u16 config)
+{
+	if (config & CTF2304_FAN_CFG1_TRANGE)
+		return TEMP_FROM_REG(reg & 0x7FF0) + ((reg >> 15) ? -64000:0);
+	else
+		return TEMP_FROM_REG(reg);
+}
+
+static ssize_t show_temp(struct device *dev,
+			 struct device_attribute *devattr, char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct ctf2304_data *data = ctf2304_update_device(dev);
+	u16 reg;
+
+	if (attr->index == 0)
+		reg = data->local_temp;
+	else
+		reg = data->remote_channel[attr->index-1];
+
+	return sprintf(buf, "%d\n", register_to_temp(reg, data->fan_config1));
+}
+
+static int get_full_scale(u16 config)
+{
+	int full_scale;
+	u8 bits;
+
+	bits = (config >> CTF2304_FAN_CFG1_MODE_SHIFT)
+	       & CTF2304_FAN_CFG1_MODE_MASK;
+
+	if (bits == 0x0)
+		full_scale = 2560;
+	else if (bits == 0x1)
+		full_scale = CTF2304_VCC;
+	else if (bits == 0x2)
+		full_scale = 4096;
+	else if (bits == 0x3)
+		full_scale = 2048;
+	else if (bits == 0x4)
+		full_scale = 1024;
+	else if (bits == 0x5)
+		full_scale = 512;
+	else
+		full_scale = 256;
+
+	return full_scale;
+}
+
+static int register_to_volt(u16 reg, u16 config)
+{
+	int full_scale;
+
+	full_scale = get_full_scale(config);
+
+	return VOLT_FROM_REG(reg, full_scale);
+}
+
+static ssize_t show_voltage(struct device *dev,
+			    struct device_attribute *devattr, char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct ctf2304_data *data = ctf2304_update_device(dev);
+	int voltage;
+
+	voltage = register_to_volt(data->remote_channel[attr->index],
+			data->fan_config1);
+
+	return sprintf(buf, "%d\n", voltage);
+}
+
+static ssize_t get_fan(struct device *dev,
+		       struct device_attribute *devattr, char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct ctf2304_data *data = ctf2304_update_device(dev);
+	int rpm;
+
+	rpm = RPM_FROM_REG(data->tach[attr->index]);
+
+	return sprintf(buf, "%d\n", rpm);
+}
+
+static ssize_t get_fan_target(struct device *dev,
+			      struct device_attribute *devattr, char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct ctf2304_data *data = ctf2304_update_device(dev);
+	int rpm;
+
+	rpm = RPM_FROM_REG(data->target_count[attr->index]);
+
+	return sprintf(buf, "%d\n", rpm);
+}
+
+static ssize_t set_fan_target(struct device *dev,
+			      struct device_attribute *devattr,
+			      const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct ctf2304_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	unsigned long rpm;
+	int target_count;
+	int err;
+
+	err = kstrtoul(buf, 10, &rpm);
+	if (err)
+		return err;
+
+	rpm = clamp_val(rpm, FAN_RPM_MIN, FAN_RPM_MAX);
+	target_count = RPM_TO_REG(rpm);
+	target_count = clamp_val(target_count, 0x1, 0xFFF);
+
+	mutex_lock(&data->update_lock);
+
+	data->target_count[attr->index] = target_count << 4;
+	i2c_smbus_write_word_swapped(client,
+			CTF2304_REG_TARGET_COUNT(attr->index),
+			data->target_count[attr->index]);
+
+	mutex_unlock(&data->update_lock);
+
+	return count;
+}
+
+static ssize_t show_fan_div(struct device *dev,
+			    struct device_attribute *devattr, char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct ctf2304_data *data = ctf2304_update_device(dev);
+	u8 bits;
+
+	bits = (data->fan_rpm_ctrl & CTF2304_FAN_DIV_MASK(attr->index))
+	       >> CTF2304_FAN_DIV_SHIFT(attr->index);
+
+	return sprintf(buf, "%d\n", DIV_FROM_REG(bits));
+}
+
+static ssize_t set_fan_div(struct device *dev,
+			   struct device_attribute *devattr,
+			   const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct ctf2304_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	unsigned long div;
+	int err;
+
+	err = kstrtoul(buf, 10, &div);
+	if (err)
+		return err;
+
+	mutex_lock(&data->update_lock);
+
+	data->fan_rpm_ctrl = (data->fan_rpm_ctrl
+			      & ~CTF2304_FAN_DIV_MASK(attr->index))
+			     | (DIV_TO_REG(div)
+				<< CTF2304_FAN_DIV_SHIFT(attr->index));
+
+	i2c_smbus_write_word_swapped(client,
+			CTF2304_REG_FAN_RPM_CTRL, data->fan_rpm_ctrl);
+
+	mutex_unlock(&data->update_lock);
+
+	return count;
+}
+
+static ssize_t get_pwm(struct device *dev,
+		       struct device_attribute *devattr, char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct ctf2304_data *data = ctf2304_update_device(dev);
+	int pwm;
+
+	pwm = data->pwm[attr->index] >> 8;
+
+	return sprintf(buf, "%d\n", pwm);
+}
+
+static ssize_t set_pwm(struct device *dev,
+		       struct device_attribute *devattr,
+		       const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct ctf2304_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	unsigned long pwm;
+	int err;
+
+	err = kstrtoul(buf, 10, &pwm);
+	if (err)
+		return err;
+
+	pwm = clamp_val(pwm, 0, 255);
+
+	mutex_lock(&data->update_lock);
+
+	data->pwm[attr->index] = (data->pwm[attr->index] & 0xFF) | (pwm << 8);
+	i2c_smbus_write_word_swapped(client,
+			CTF2304_REG_PWMOUT(attr->index),
+			data->pwm[attr->index]);
+
+	mutex_unlock(&data->update_lock);
+
+	return count;
+}
+
+static ssize_t get_pwm_enable(struct device *dev,
+			      struct device_attribute *devattr, char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct ctf2304_data *data = ctf2304_update_device(dev);
+	int config;
+	int mode;
+
+	config = (data->fan_config2 & CTF2304_FAN_CFG2_MODE_MASK(attr->index))
+		 >> CTF2304_FAN_CFG2_MODE_SHIFT(attr->index);
+	if (config == CTF2304_FAN_CFG2_RPM_MODE)
+		mode = 2;
+	else if (config == CTF2304_FAN_CFG2_DCY_MODE)
+		mode = 1;
+	else
+		mode = 0;
+
+	return sprintf(buf, "%d\n", mode);
+}
+
+static ssize_t set_pwm_enable(struct device *dev,
+			      struct device_attribute *devattr,
+			      const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct ctf2304_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	unsigned long mode;
+	int err;
+
+	err = kstrtoul(buf, 10, &mode);
+	if (err)
+		return err;
+
+	mutex_lock(&data->update_lock);
+
+	switch (mode) {
+	case 0:
+		data->fan_config2 =
+			(data->fan_config2
+			 & ~CTF2304_FAN_CFG2_MODE_MASK(attr->index))
+			| (CTF2304_FAN_CFG2_TEMP_MODE
+			   << CTF2304_FAN_CFG2_MODE_SHIFT(attr->index));
+		break;
+	case 1:
+		data->fan_config2 =
+			(data->fan_config2
+			 & ~CTF2304_FAN_CFG2_MODE_MASK(attr->index))
+			| (CTF2304_FAN_CFG2_DCY_MODE
+			   << CTF2304_FAN_CFG2_MODE_SHIFT(attr->index));
+		break;
+	case 2:
+		data->fan_config2 =
+			(data->fan_config2
+			 & ~CTF2304_FAN_CFG2_MODE_MASK(attr->index))
+			| (CTF2304_FAN_CFG2_RPM_MODE
+			   << CTF2304_FAN_CFG2_MODE_SHIFT(attr->index));
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	i2c_smbus_write_word_swapped(client,
+			CTF2304_REG_FAN_CONFIG2, data->fan_config2);
+
+	mutex_unlock(&data->update_lock);
+
+	return count;
+}
+
+static SENSOR_DEVICE_ATTR(temp1_input, 0444, show_temp, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_input, 0444, show_temp, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp3_input, 0444, show_temp, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp4_input, 0444, show_temp, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp5_input, 0444, show_temp, NULL, 4);
+static SENSOR_DEVICE_ATTR(temp6_input, 0444, show_temp, NULL, 5);
+static SENSOR_DEVICE_ATTR(temp7_input, 0444, show_temp, NULL, 6);
+static SENSOR_DEVICE_ATTR(temp8_input, 0444, show_temp, NULL, 7);
+static SENSOR_DEVICE_ATTR(temp9_input, 0444, show_temp, NULL, 8);
+static SENSOR_DEVICE_ATTR(in1_input, 0444, show_voltage, NULL, 0);
+static SENSOR_DEVICE_ATTR(in2_input, 0444, show_voltage, NULL, 1);
+static SENSOR_DEVICE_ATTR(in3_input, 0444, show_voltage, NULL, 2);
+static SENSOR_DEVICE_ATTR(in4_input, 0444, show_voltage, NULL, 3);
+static SENSOR_DEVICE_ATTR(in5_input, 0444, show_voltage, NULL, 4);
+static SENSOR_DEVICE_ATTR(in6_input, 0444, show_voltage, NULL, 5);
+static SENSOR_DEVICE_ATTR(in7_input, 0444, show_voltage, NULL, 6);
+static SENSOR_DEVICE_ATTR(in8_input, 0444, show_voltage, NULL, 7);
+static SENSOR_DEVICE_ATTR(fan1_input, 0444, get_fan, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan2_input, 0444, get_fan, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan3_input, 0444, get_fan, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan4_input, 0444, get_fan, NULL, 3);
+static SENSOR_DEVICE_ATTR(fan1_target, 0644,
+		get_fan_target, set_fan_target, 0);
+static SENSOR_DEVICE_ATTR(fan2_target, 0644,
+		get_fan_target, set_fan_target, 1);
+static SENSOR_DEVICE_ATTR(fan3_target, 0644,
+		get_fan_target, set_fan_target, 2);
+static SENSOR_DEVICE_ATTR(fan4_target, 0644,
+		get_fan_target, set_fan_target, 3);
+static SENSOR_DEVICE_ATTR(fan1_div, 0644, show_fan_div, set_fan_div, 0);
+static SENSOR_DEVICE_ATTR(fan2_div, 0644, show_fan_div, set_fan_div, 1);
+static SENSOR_DEVICE_ATTR(fan3_div, 0644, show_fan_div, set_fan_div, 2);
+static SENSOR_DEVICE_ATTR(fan4_div, 0644, show_fan_div, set_fan_div, 3);
+static SENSOR_DEVICE_ATTR(pwm1, 0644, get_pwm, set_pwm, 0);
+static SENSOR_DEVICE_ATTR(pwm2, 0644, get_pwm, set_pwm, 1);
+static SENSOR_DEVICE_ATTR(pwm3, 0644, get_pwm, set_pwm, 2);
+static SENSOR_DEVICE_ATTR(pwm4, 0644, get_pwm, set_pwm, 3);
+static SENSOR_DEVICE_ATTR(pwm1_enable, 0644,
+		get_pwm_enable, set_pwm_enable, 0);
+static SENSOR_DEVICE_ATTR(pwm2_enable, 0644,
+		get_pwm_enable, set_pwm_enable, 1);
+static SENSOR_DEVICE_ATTR(pwm3_enable, 0644,
+		get_pwm_enable, set_pwm_enable, 2);
+static SENSOR_DEVICE_ATTR(pwm4_enable, 0644,
+		get_pwm_enable, set_pwm_enable, 3);
+
+static struct attribute *ctf2304_attrs[] = {
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp2_input.dev_attr.attr,
+	&sensor_dev_attr_temp3_input.dev_attr.attr,
+	&sensor_dev_attr_temp4_input.dev_attr.attr,
+	&sensor_dev_attr_temp5_input.dev_attr.attr,
+	&sensor_dev_attr_temp6_input.dev_attr.attr,
+	&sensor_dev_attr_temp7_input.dev_attr.attr,
+	&sensor_dev_attr_temp8_input.dev_attr.attr,
+	&sensor_dev_attr_temp9_input.dev_attr.attr,
+	&sensor_dev_attr_in1_input.dev_attr.attr,
+	&sensor_dev_attr_in2_input.dev_attr.attr,
+	&sensor_dev_attr_in3_input.dev_attr.attr,
+	&sensor_dev_attr_in4_input.dev_attr.attr,
+	&sensor_dev_attr_in5_input.dev_attr.attr,
+	&sensor_dev_attr_in6_input.dev_attr.attr,
+	&sensor_dev_attr_in7_input.dev_attr.attr,
+	&sensor_dev_attr_in8_input.dev_attr.attr,
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
+	&sensor_dev_attr_fan2_input.dev_attr.attr,
+	&sensor_dev_attr_fan3_input.dev_attr.attr,
+	&sensor_dev_attr_fan4_input.dev_attr.attr,
+	&sensor_dev_attr_fan1_target.dev_attr.attr,
+	&sensor_dev_attr_fan2_target.dev_attr.attr,
+	&sensor_dev_attr_fan3_target.dev_attr.attr,
+	&sensor_dev_attr_fan4_target.dev_attr.attr,
+	&sensor_dev_attr_fan1_div.dev_attr.attr,
+	&sensor_dev_attr_fan2_div.dev_attr.attr,
+	&sensor_dev_attr_fan3_div.dev_attr.attr,
+	&sensor_dev_attr_fan4_div.dev_attr.attr,
+	&sensor_dev_attr_pwm1.dev_attr.attr,
+	&sensor_dev_attr_pwm2.dev_attr.attr,
+	&sensor_dev_attr_pwm3.dev_attr.attr,
+	&sensor_dev_attr_pwm4.dev_attr.attr,
+	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm2_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm3_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm4_enable.dev_attr.attr,
+	NULL
+};
+
+ATTRIBUTE_GROUPS(ctf2304);
+
+static int ctf2304_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	struct device *dev = &client->dev;
+	struct ctf2304_data *data;
+	struct device *hwmon_dev;
+
+	data = devm_kzalloc(dev, sizeof(struct ctf2304_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->client = client;
+	mutex_init(&data->update_lock);
+
+	hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
+							   data,
+							   ctf2304_groups);
+
+	return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct i2c_device_id ctf2304_id[] = {
+	{ "ctf2304", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ctf2304_id);
+
+static struct i2c_driver ctf2304_driver = {
+	.class		= I2C_CLASS_HWMON,
+	.probe		= ctf2304_probe,
+	.driver = {
+		.name	= "ctf2304",
+	},
+	.id_table	= ctf2304_id,
+};
+
+module_i2c_driver(ctf2304_driver);
+
+MODULE_AUTHOR("Il Han <corone.il.han@gmail.com>");
+MODULE_DESCRIPTION("CTF2304 sensor driver");
+MODULE_LICENSE("GPL");
-- 
2.26.2


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH] hwmon: Driver for Sensylink CTF2304
  2023-03-25  3:21 [PATCH] hwmon: Driver for Sensylink CTF2304 Il Han
@ 2023-03-25 13:20 ` Guenter Roeck
  0 siblings, 0 replies; 5+ messages in thread
From: Guenter Roeck @ 2023-03-25 13:20 UTC (permalink / raw)
  To: Il Han; +Cc: Jean Delvare, linux-hwmon

On Sat, Mar 25, 2023 at 12:21:14PM +0900, Il Han wrote:
> From: Il Han <corone.il.han@gmail.com>
> 
> The driver supports the Sensylink CTF2304.
> 
> Signed-off-by: Il Han <corone.il.han@gmail.com>
> ---
>  Documentation/hwmon/ctf2304.rst |  41 +++

Needs to be added to Documentation/hwmon/index.rst.

>  drivers/hwmon/Kconfig           |  10 +
>  drivers/hwmon/Makefile          |   1 +
>  drivers/hwmon/ctf2304.c         | 554 ++++++++++++++++++++++++++++++++
>  4 files changed, 606 insertions(+)
>  create mode 100644 Documentation/hwmon/ctf2304.rst
>  create mode 100644 drivers/hwmon/ctf2304.c
> 
> diff --git a/Documentation/hwmon/ctf2304.rst b/Documentation/hwmon/ctf2304.rst
> new file mode 100644
> index 000000000000..d0e9ef9ea52e
> --- /dev/null
> +++ b/Documentation/hwmon/ctf2304.rst
> @@ -0,0 +1,41 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +Kernel driver ctf2304
> +=====================
> +
> +Supported chips:
> +
> +  * Sensylink CTF2304
> +
> +    Prefix: 'ctf2304'
> +
> +    Addresses scanned: -
> +
> +    Datasheet:
> +
> +Author: Il Han <corone.il.han@gmail.com>
> +
> +
> +Description
> +-----------
> +
> +This driver implements support for the Sensylink CTF2304 chip.
> +
> +The CTF2304 controls the speeds of up to four fans using four independent
> +PWM outputs with local and remote temperature and remote voltage sensing.
> +
> +
> +Sysfs entries
> +-------------
> +
> +================== === =======================================================
> +fan[1-4]_input     RO  fan tachometer speed in RPM
> +fan[1-4]_target    RW  desired fan speed in RPM
> +fan[1-4]_div       RW  sets the RPM range of the fan.
> +pwm[1-4]_enable    RW  regulator mode,
> +                       0=auto temperature mode, 1=manual mode, 2=rpm mode
> +pwm[1-4]           RW  read: current pwm duty cycle,
> +                       write: target pwm duty cycle (0-255)
> +in[1-8]_input      RO  measured output voltage
> +temp[1-9]_input    RO  measured temperature
> +================== === =======================================================
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 5b3b76477b0e..da9fbb0f8af3 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -474,6 +474,16 @@ config SENSORS_CORSAIR_PSU
>  	  This driver can also be built as a module. If so, the module
>  	  will be called corsair-psu.
>  
> +config SENSORS_CTF2304
> +	tristate "Sensylink CTF2304 sensor chip"
> +	depends on I2C
> +	help
> +	  If you say yes here you get support for PWM and Fan Controller
> +	  with temperature and voltage sensing.
> +
> +	  This driver can also be built as a module. If so, the module
> +	  will be called ctf2304.
> +
>  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 88712b5031c8..3742b52f032d 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -60,6 +60,7 @@ obj-$(CONFIG_SENSORS_BT1_PVT)	+= bt1-pvt.o
>  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_CTF2304)	+= ctf2304.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/ctf2304.c b/drivers/hwmon/ctf2304.c
> new file mode 100644
> index 000000000000..5788740a57e5
> --- /dev/null
> +++ b/drivers/hwmon/ctf2304.c
> @@ -0,0 +1,554 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * ctf2304.c - Part of lm_sensors, Linux kernel modules for hardware
> + *             monitoring.
> + *
> + * (C) 2023 by Il Han <corone.il.han@gmail.com>
> + */
> +
> +#include <linux/err.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/jiffies.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +
> +/* CTF2304 registers */
> +#define CTF2304_REG_LOCAL_TEMP		0x00
> +#define CTF2304_REG_REMOTE_CHANNEL(ch)	(0x01 + (ch))
> +#define CTF2304_REG_TACH_COUNT(ch)	(0x09 + (ch))
> +#define CTF2304_REG_FAN_CONFIG1		0x10
> +#define CTF2304_REG_FAN_CONFIG2		0x11
> +#define CTF2304_REG_FAN_RPM_CTRL	0x18
> +#define CTF2304_REG_PWMOUT(ch)		(0x40 + (ch))
> +#define CTF2304_REG_TARGET_COUNT(ch)	(0x44 + (ch))
> +
> +/* Fan Configure1 register bits */
> +#define CTF2304_FAN_CFG1_TRANGE		0x0400
> +#define CTF2304_FAN_CFG1_MODE_MASK	(0x7)
> +#define CTF2304_FAN_CFG1_MODE_SHIFT	7
> +
> +/* Fan Configure2 register bits */
> +#define CTF2304_FAN_CFG2_MODE_MASK(ch)	(0x6 << (ch) * 4)
> +#define CTF2304_FAN_CFG2_MODE_SHIFT(ch)	(1 + (ch) * 4)
> +#define CTF2304_FAN_CFG2_DCY_MODE	0
> +#define CTF2304_FAN_CFG2_RPM_MODE	1
> +#define CTF2304_FAN_CFG2_TEMP_MODE	2
> +#define CTF2304_FAN_CFG2_MAX_MODE	3
> +
> +/* Fan RPM CTRL register bits */
> +#define CTF2304_FAN_DIV_MASK(ch)	(0x6 << (ch) * 4)
> +#define CTF2304_FAN_DIV_SHIFT(ch)	(1 + (ch) * 4)
> +
> +#define CTF2304_VCC			3300
> +
> +#define FAN_RPM_MIN			480
> +#define FAN_RPM_MAX			1966080
> +
> +#define FAN_COUNT_REG_MAX		0xFFF
> +
> +#define TEMP_FROM_REG(reg)		(((reg) * 1000) >> 8)
> +#define VOLT_FROM_REG(reg, fs)		((((reg) >> 4) * (fs)) >> 12)
> +#define DIV_FROM_REG(reg)		(1 << (reg))
> +#define DIV_TO_REG(div)			((div == 8) ? 0x3 : \
> +					 (div == 4) ? 0x2 : \
> +					 (div == 1) ? 0x0 : 0x1)
> +#define RPM_FROM_REG(reg)		(((reg) >> 4) ? \
> +					 ((32768 * 60) / ((reg) >> 4)) : \
> +					 FAN_RPM_MAX)
> +#define RPM_TO_REG(rpm)			((rpm) ? \
> +					 ((32768 * 60) / (rpm)) : \
> +					 FAN_COUNT_REG_MAX)
> +
> +#define NR_CHANNEL			8
> +#define NR_FAN_CHANNEL			4
> +
> +/*
> + * Client data (each client gets its own)
> + */
> +struct ctf2304_data {
> +	struct i2c_client *client;
> +	struct mutex update_lock;
> +	char valid; /* zero until following fields are valid */
> +	unsigned long last_updated; /* in jiffies */
> +
> +	/* register values */
> +	u16 local_temp;
> +	u16 remote_channel[NR_CHANNEL];
> +	u16 tach[NR_FAN_CHANNEL];
> +	u16 fan_config1;
> +	u16 fan_config2;
> +	u16 fan_rpm_ctrl;
> +	u16 pwm[NR_FAN_CHANNEL];
> +	u16 target_count[NR_FAN_CHANNEL];
> +};
> +
> +static struct ctf2304_data *ctf2304_update_device(struct device *dev)
> +{
> +	struct ctf2304_data *data = dev_get_drvdata(dev);
> +	struct i2c_client *client = data->client;
> +	int i;
> +	int rv;
> +
> +	mutex_lock(&data->update_lock);
> +
> +	if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
> +		rv = i2c_smbus_read_word_swapped(client,
> +				CTF2304_REG_LOCAL_TEMP);
> +		if (rv >= 0)
> +			data->local_temp = rv;
> +
> +		for (i = 0; i < NR_CHANNEL; i++) {
> +			rv = i2c_smbus_read_word_swapped(client,
> +					CTF2304_REG_REMOTE_CHANNEL(i));
> +			if (rv >= 0)
> +				data->remote_channel[i] = rv;
> +		}
> +
> +		rv = i2c_smbus_read_word_swapped(client,
> +				CTF2304_REG_FAN_CONFIG1);
> +		if (rv >= 0)
> +			data->fan_config1 = rv;
> +		rv = i2c_smbus_read_word_swapped(client,
> +				CTF2304_REG_FAN_CONFIG2);
> +		if (rv >= 0)
> +			data->fan_config2 = rv;
> +		rv = i2c_smbus_read_word_swapped(client,
> +				CTF2304_REG_FAN_RPM_CTRL);
> +		if (rv >= 0)
> +			data->fan_rpm_ctrl = rv;
> +
> +		for (i = 0; i < NR_FAN_CHANNEL; i++) {
> +			rv = i2c_smbus_read_word_swapped(client,
> +					CTF2304_REG_TACH_COUNT(i));
> +			if (rv >= 0)
> +				data->tach[i] = rv;
> +			rv = i2c_smbus_read_word_swapped(client,
> +					CTF2304_REG_PWMOUT(i));
> +			if (rv >= 0)
> +				data->pwm[i] = rv;
> +			rv = i2c_smbus_read_word_swapped(client,
> +					CTF2304_REG_TARGET_COUNT(i));
> +			if (rv >= 0)
> +				data->target_count[i] = rv;
> +		}
> +
> +		data->last_updated = jiffies;
> +		data->valid = true;
> +	}
> +
> +	mutex_unlock(&data->update_lock);
> +
> +	return data;
> +}
> +
> +static int register_to_temp(u16 reg, u16 config)
> +{
> +	if (config & CTF2304_FAN_CFG1_TRANGE)
> +		return TEMP_FROM_REG(reg & 0x7FF0) + ((reg >> 15) ? -64000:0);
> +	else
> +		return TEMP_FROM_REG(reg);
> +}
> +
> +static ssize_t show_temp(struct device *dev,
> +			 struct device_attribute *devattr, char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct ctf2304_data *data = ctf2304_update_device(dev);
> +	u16 reg;
> +
> +	if (attr->index == 0)
> +		reg = data->local_temp;
> +	else
> +		reg = data->remote_channel[attr->index-1];
> +
> +	return sprintf(buf, "%d\n", register_to_temp(reg, data->fan_config1));
> +}
> +
> +static int get_full_scale(u16 config)
> +{
> +	int full_scale;
> +	u8 bits;
> +
> +	bits = (config >> CTF2304_FAN_CFG1_MODE_SHIFT)
> +	       & CTF2304_FAN_CFG1_MODE_MASK;
> +
> +	if (bits == 0x0)
> +		full_scale = 2560;
> +	else if (bits == 0x1)
> +		full_scale = CTF2304_VCC;
> +	else if (bits == 0x2)
> +		full_scale = 4096;
> +	else if (bits == 0x3)
> +		full_scale = 2048;
> +	else if (bits == 0x4)
> +		full_scale = 1024;
> +	else if (bits == 0x5)
> +		full_scale = 512;
> +	else
> +		full_scale = 256;
> +
> +	return full_scale;
> +}
> +
> +static int register_to_volt(u16 reg, u16 config)
> +{
> +	int full_scale;
> +
> +	full_scale = get_full_scale(config);
> +
> +	return VOLT_FROM_REG(reg, full_scale);
> +}
> +
> +static ssize_t show_voltage(struct device *dev,
> +			    struct device_attribute *devattr, char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct ctf2304_data *data = ctf2304_update_device(dev);
> +	int voltage;
> +
> +	voltage = register_to_volt(data->remote_channel[attr->index],
> +			data->fan_config1);
> +
> +	return sprintf(buf, "%d\n", voltage);
> +}
> +
> +static ssize_t get_fan(struct device *dev,
> +		       struct device_attribute *devattr, char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct ctf2304_data *data = ctf2304_update_device(dev);
> +	int rpm;
> +
> +	rpm = RPM_FROM_REG(data->tach[attr->index]);
> +
> +	return sprintf(buf, "%d\n", rpm);
> +}
> +
> +static ssize_t get_fan_target(struct device *dev,
> +			      struct device_attribute *devattr, char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct ctf2304_data *data = ctf2304_update_device(dev);
> +	int rpm;
> +
> +	rpm = RPM_FROM_REG(data->target_count[attr->index]);
> +
> +	return sprintf(buf, "%d\n", rpm);
> +}
> +
> +static ssize_t set_fan_target(struct device *dev,
> +			      struct device_attribute *devattr,
> +			      const char *buf, size_t count)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct ctf2304_data *data = dev_get_drvdata(dev);
> +	struct i2c_client *client = data->client;
> +	unsigned long rpm;
> +	int target_count;
> +	int err;
> +
> +	err = kstrtoul(buf, 10, &rpm);
> +	if (err)
> +		return err;
> +
> +	rpm = clamp_val(rpm, FAN_RPM_MIN, FAN_RPM_MAX);
> +	target_count = RPM_TO_REG(rpm);
> +	target_count = clamp_val(target_count, 0x1, 0xFFF);
> +
> +	mutex_lock(&data->update_lock);
> +
> +	data->target_count[attr->index] = target_count << 4;
> +	i2c_smbus_write_word_swapped(client,
> +			CTF2304_REG_TARGET_COUNT(attr->index),
> +			data->target_count[attr->index]);
> +
> +	mutex_unlock(&data->update_lock);
> +
> +	return count;
> +}
> +
> +static ssize_t show_fan_div(struct device *dev,
> +			    struct device_attribute *devattr, char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct ctf2304_data *data = ctf2304_update_device(dev);
> +	u8 bits;
> +
> +	bits = (data->fan_rpm_ctrl & CTF2304_FAN_DIV_MASK(attr->index))
> +	       >> CTF2304_FAN_DIV_SHIFT(attr->index);
> +
> +	return sprintf(buf, "%d\n", DIV_FROM_REG(bits));
> +}
> +
> +static ssize_t set_fan_div(struct device *dev,
> +			   struct device_attribute *devattr,
> +			   const char *buf, size_t count)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct ctf2304_data *data = dev_get_drvdata(dev);
> +	struct i2c_client *client = data->client;
> +	unsigned long div;
> +	int err;
> +
> +	err = kstrtoul(buf, 10, &div);
> +	if (err)
> +		return err;
> +
> +	mutex_lock(&data->update_lock);
> +
> +	data->fan_rpm_ctrl = (data->fan_rpm_ctrl
> +			      & ~CTF2304_FAN_DIV_MASK(attr->index))
> +			     | (DIV_TO_REG(div)
> +				<< CTF2304_FAN_DIV_SHIFT(attr->index));
> +
> +	i2c_smbus_write_word_swapped(client,
> +			CTF2304_REG_FAN_RPM_CTRL, data->fan_rpm_ctrl);
> +
> +	mutex_unlock(&data->update_lock);
> +
> +	return count;
> +}
> +
> +static ssize_t get_pwm(struct device *dev,
> +		       struct device_attribute *devattr, char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct ctf2304_data *data = ctf2304_update_device(dev);
> +	int pwm;
> +
> +	pwm = data->pwm[attr->index] >> 8;
> +
> +	return sprintf(buf, "%d\n", pwm);
> +}
> +
> +static ssize_t set_pwm(struct device *dev,
> +		       struct device_attribute *devattr,
> +		       const char *buf, size_t count)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct ctf2304_data *data = dev_get_drvdata(dev);
> +	struct i2c_client *client = data->client;
> +	unsigned long pwm;
> +	int err;
> +
> +	err = kstrtoul(buf, 10, &pwm);
> +	if (err)
> +		return err;
> +
> +	pwm = clamp_val(pwm, 0, 255);
> +
> +	mutex_lock(&data->update_lock);
> +
> +	data->pwm[attr->index] = (data->pwm[attr->index] & 0xFF) | (pwm << 8);
> +	i2c_smbus_write_word_swapped(client,
> +			CTF2304_REG_PWMOUT(attr->index),
> +			data->pwm[attr->index]);
> +
> +	mutex_unlock(&data->update_lock);
> +
> +	return count;
> +}
> +
> +static ssize_t get_pwm_enable(struct device *dev,
> +			      struct device_attribute *devattr, char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct ctf2304_data *data = ctf2304_update_device(dev);
> +	int config;
> +	int mode;
> +
> +	config = (data->fan_config2 & CTF2304_FAN_CFG2_MODE_MASK(attr->index))
> +		 >> CTF2304_FAN_CFG2_MODE_SHIFT(attr->index);
> +	if (config == CTF2304_FAN_CFG2_RPM_MODE)
> +		mode = 2;
> +	else if (config == CTF2304_FAN_CFG2_DCY_MODE)
> +		mode = 1;
> +	else
> +		mode = 0;
> +
> +	return sprintf(buf, "%d\n", mode);
> +}
> +
> +static ssize_t set_pwm_enable(struct device *dev,
> +			      struct device_attribute *devattr,
> +			      const char *buf, size_t count)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct ctf2304_data *data = dev_get_drvdata(dev);
> +	struct i2c_client *client = data->client;
> +	unsigned long mode;
> +	int err;
> +
> +	err = kstrtoul(buf, 10, &mode);
> +	if (err)
> +		return err;
> +
> +	mutex_lock(&data->update_lock);
> +
> +	switch (mode) {
> +	case 0:
> +		data->fan_config2 =
> +			(data->fan_config2
> +			 & ~CTF2304_FAN_CFG2_MODE_MASK(attr->index))
> +			| (CTF2304_FAN_CFG2_TEMP_MODE
> +			   << CTF2304_FAN_CFG2_MODE_SHIFT(attr->index));
> +		break;
> +	case 1:
> +		data->fan_config2 =
> +			(data->fan_config2
> +			 & ~CTF2304_FAN_CFG2_MODE_MASK(attr->index))
> +			| (CTF2304_FAN_CFG2_DCY_MODE
> +			   << CTF2304_FAN_CFG2_MODE_SHIFT(attr->index));
> +		break;
> +	case 2:
> +		data->fan_config2 =
> +			(data->fan_config2
> +			 & ~CTF2304_FAN_CFG2_MODE_MASK(attr->index))
> +			| (CTF2304_FAN_CFG2_RPM_MODE
> +			   << CTF2304_FAN_CFG2_MODE_SHIFT(attr->index));
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	i2c_smbus_write_word_swapped(client,
> +			CTF2304_REG_FAN_CONFIG2, data->fan_config2);
> +
> +	mutex_unlock(&data->update_lock);
> +
> +	return count;
> +}
> +
> +static SENSOR_DEVICE_ATTR(temp1_input, 0444, show_temp, NULL, 0);
> +static SENSOR_DEVICE_ATTR(temp2_input, 0444, show_temp, NULL, 1);
> +static SENSOR_DEVICE_ATTR(temp3_input, 0444, show_temp, NULL, 2);
> +static SENSOR_DEVICE_ATTR(temp4_input, 0444, show_temp, NULL, 3);
> +static SENSOR_DEVICE_ATTR(temp5_input, 0444, show_temp, NULL, 4);
> +static SENSOR_DEVICE_ATTR(temp6_input, 0444, show_temp, NULL, 5);
> +static SENSOR_DEVICE_ATTR(temp7_input, 0444, show_temp, NULL, 6);
> +static SENSOR_DEVICE_ATTR(temp8_input, 0444, show_temp, NULL, 7);
> +static SENSOR_DEVICE_ATTR(temp9_input, 0444, show_temp, NULL, 8);
> +static SENSOR_DEVICE_ATTR(in1_input, 0444, show_voltage, NULL, 0);
> +static SENSOR_DEVICE_ATTR(in2_input, 0444, show_voltage, NULL, 1);
> +static SENSOR_DEVICE_ATTR(in3_input, 0444, show_voltage, NULL, 2);
> +static SENSOR_DEVICE_ATTR(in4_input, 0444, show_voltage, NULL, 3);
> +static SENSOR_DEVICE_ATTR(in5_input, 0444, show_voltage, NULL, 4);
> +static SENSOR_DEVICE_ATTR(in6_input, 0444, show_voltage, NULL, 5);
> +static SENSOR_DEVICE_ATTR(in7_input, 0444, show_voltage, NULL, 6);
> +static SENSOR_DEVICE_ATTR(in8_input, 0444, show_voltage, NULL, 7);
> +static SENSOR_DEVICE_ATTR(fan1_input, 0444, get_fan, NULL, 0);
> +static SENSOR_DEVICE_ATTR(fan2_input, 0444, get_fan, NULL, 1);
> +static SENSOR_DEVICE_ATTR(fan3_input, 0444, get_fan, NULL, 2);
> +static SENSOR_DEVICE_ATTR(fan4_input, 0444, get_fan, NULL, 3);
> +static SENSOR_DEVICE_ATTR(fan1_target, 0644,
> +		get_fan_target, set_fan_target, 0);
> +static SENSOR_DEVICE_ATTR(fan2_target, 0644,
> +		get_fan_target, set_fan_target, 1);
> +static SENSOR_DEVICE_ATTR(fan3_target, 0644,
> +		get_fan_target, set_fan_target, 2);
> +static SENSOR_DEVICE_ATTR(fan4_target, 0644,
> +		get_fan_target, set_fan_target, 3);
> +static SENSOR_DEVICE_ATTR(fan1_div, 0644, show_fan_div, set_fan_div, 0);
> +static SENSOR_DEVICE_ATTR(fan2_div, 0644, show_fan_div, set_fan_div, 1);
> +static SENSOR_DEVICE_ATTR(fan3_div, 0644, show_fan_div, set_fan_div, 2);
> +static SENSOR_DEVICE_ATTR(fan4_div, 0644, show_fan_div, set_fan_div, 3);
> +static SENSOR_DEVICE_ATTR(pwm1, 0644, get_pwm, set_pwm, 0);
> +static SENSOR_DEVICE_ATTR(pwm2, 0644, get_pwm, set_pwm, 1);
> +static SENSOR_DEVICE_ATTR(pwm3, 0644, get_pwm, set_pwm, 2);
> +static SENSOR_DEVICE_ATTR(pwm4, 0644, get_pwm, set_pwm, 3);
> +static SENSOR_DEVICE_ATTR(pwm1_enable, 0644,
> +		get_pwm_enable, set_pwm_enable, 0);
> +static SENSOR_DEVICE_ATTR(pwm2_enable, 0644,
> +		get_pwm_enable, set_pwm_enable, 1);
> +static SENSOR_DEVICE_ATTR(pwm3_enable, 0644,
> +		get_pwm_enable, set_pwm_enable, 2);
> +static SENSOR_DEVICE_ATTR(pwm4_enable, 0644,
> +		get_pwm_enable, set_pwm_enable, 3);
> +
> +static struct attribute *ctf2304_attrs[] = {
> +	&sensor_dev_attr_temp1_input.dev_attr.attr,
> +	&sensor_dev_attr_temp2_input.dev_attr.attr,
> +	&sensor_dev_attr_temp3_input.dev_attr.attr,
> +	&sensor_dev_attr_temp4_input.dev_attr.attr,
> +	&sensor_dev_attr_temp5_input.dev_attr.attr,
> +	&sensor_dev_attr_temp6_input.dev_attr.attr,
> +	&sensor_dev_attr_temp7_input.dev_attr.attr,
> +	&sensor_dev_attr_temp8_input.dev_attr.attr,
> +	&sensor_dev_attr_temp9_input.dev_attr.attr,
> +	&sensor_dev_attr_in1_input.dev_attr.attr,
> +	&sensor_dev_attr_in2_input.dev_attr.attr,
> +	&sensor_dev_attr_in3_input.dev_attr.attr,
> +	&sensor_dev_attr_in4_input.dev_attr.attr,
> +	&sensor_dev_attr_in5_input.dev_attr.attr,
> +	&sensor_dev_attr_in6_input.dev_attr.attr,
> +	&sensor_dev_attr_in7_input.dev_attr.attr,
> +	&sensor_dev_attr_in8_input.dev_attr.attr,
> +	&sensor_dev_attr_fan1_input.dev_attr.attr,
> +	&sensor_dev_attr_fan2_input.dev_attr.attr,
> +	&sensor_dev_attr_fan3_input.dev_attr.attr,
> +	&sensor_dev_attr_fan4_input.dev_attr.attr,
> +	&sensor_dev_attr_fan1_target.dev_attr.attr,
> +	&sensor_dev_attr_fan2_target.dev_attr.attr,
> +	&sensor_dev_attr_fan3_target.dev_attr.attr,
> +	&sensor_dev_attr_fan4_target.dev_attr.attr,
> +	&sensor_dev_attr_fan1_div.dev_attr.attr,
> +	&sensor_dev_attr_fan2_div.dev_attr.attr,
> +	&sensor_dev_attr_fan3_div.dev_attr.attr,
> +	&sensor_dev_attr_fan4_div.dev_attr.attr,
> +	&sensor_dev_attr_pwm1.dev_attr.attr,
> +	&sensor_dev_attr_pwm2.dev_attr.attr,
> +	&sensor_dev_attr_pwm3.dev_attr.attr,
> +	&sensor_dev_attr_pwm4.dev_attr.attr,
> +	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
> +	&sensor_dev_attr_pwm2_enable.dev_attr.attr,
> +	&sensor_dev_attr_pwm3_enable.dev_attr.attr,
> +	&sensor_dev_attr_pwm4_enable.dev_attr.attr,
> +	NULL
> +};
> +
> +ATTRIBUTE_GROUPS(ctf2304);
> +
> +static int ctf2304_probe(struct i2c_client *client,
> +			 const struct i2c_device_id *id)
> +{
> +	struct device *dev = &client->dev;
> +	struct ctf2304_data *data;
> +	struct device *hwmon_dev;
> +
> +	data = devm_kzalloc(dev, sizeof(struct ctf2304_data), GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	data->client = client;
> +	mutex_init(&data->update_lock);
> +
> +	hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
> +							   data,
> +							   ctf2304_groups);
> +

Documentation/hwmon/submitting-patches.rst:

* Use devm_hwmon_device_register_with_info() or, if your driver needs a remove
  function, hwmon_device_register_with_info() to register your driver with the
  hwmon subsystem. Try using devm_add_action() instead of a remove function if
  possible. Do not use any of the deprecated registration functions.

include/linux/hwmon.h:

/*
 * hwmon_device_register_with_groups() and
 * devm_hwmon_device_register_with_groups() are deprecated.
 */

Please rewrite to use [devm_]devm_hwmon_device_register_with_info().

Guenter

> +	return PTR_ERR_OR_ZERO(hwmon_dev);
> +}
> +
> +static const struct i2c_device_id ctf2304_id[] = {
> +	{ "ctf2304", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, ctf2304_id);
> +
> +static struct i2c_driver ctf2304_driver = {
> +	.class		= I2C_CLASS_HWMON,
> +	.probe		= ctf2304_probe,
> +	.driver = {
> +		.name	= "ctf2304",
> +	},
> +	.id_table	= ctf2304_id,
> +};
> +
> +module_i2c_driver(ctf2304_driver);
> +
> +MODULE_AUTHOR("Il Han <corone.il.han@gmail.com>");
> +MODULE_DESCRIPTION("CTF2304 sensor driver");
> +MODULE_LICENSE("GPL");
> -- 
> 2.26.2
> 

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] hwmon: Driver for Sensylink CTF2304
       [not found]   ` <CADrkgWLUV2FbL0hrobQWUi4jsbZenVOXErtuRH_FQ9ZBwJsjHw@mail.gmail.com>
@ 2023-04-05 13:49     ` Guenter Roeck
  0 siblings, 0 replies; 5+ messages in thread
From: Guenter Roeck @ 2023-04-05 13:49 UTC (permalink / raw)
  To: Il Han; +Cc: Jean Delvare, linux-hwmon

On 4/5/23 06:32, Il Han wrote:
> 2023년 4월 1일 (토) 오후 11:54, Guenter Roeck <linux@roeck-us.net <mailto:linux@roeck-us.net>>님이 작성:
> 
>     On Fri, Mar 31, 2023 at 10:34:37PM +0900, Il Han wrote:
>      > The driver supports the Sensylink CTF2304.
>      >
> 
>     Please version your patches, and provide change logs. I am not inclined
>     to compare this version with the previous version to find out what changed
>     besides the API conversion, and I am not inclined to dig through my e-mail
>     history to figure out how many versions of this patch have been submitted.
> 
>     Please consult
>     Documentation/process/submitting-patches.rst
>     Documentation/process/submit-checklist.rst
>     Documentation/hwmon/submitting-patches.rst
> 
>     and follow the guidance in those documents.
> 
>     Thanks,
>     Guenter
> 
> 
> Sorry, I made this patch newly.
> I didn't add it to the previous one.

That is not the point. It is still (at least) v2, and the change log
may have been "rewrote from scratch to implement new hwmon API" or similar.
Besides, I doubt that you rewrote the documentation.

I don't want having to deal with v3, v4, v5, each submitted as "new" with
the same claim that it was rewritten from scratch.

Guenter

> So you don't have to compare them
> Please disregard the previous one.
> I will add the next patch to this next time.
>  > Thanks,
> Il Han
> 
>      > Signed-off-by: Il Han <corone.il.han@gmail.com <mailto:corone.il.han@gmail.com>>
>      > ---
>      >  Documentation/hwmon/ctf2304.rst |  41 +++
>      >  Documentation/hwmon/index.rst   |   1 +
>      >  drivers/hwmon/Kconfig           |  10 +
>      >  drivers/hwmon/Makefile          |   1 +
>      >  drivers/hwmon/ctf2304.c         | 522 ++++++++++++++++++++++++++++++++
>      >  5 files changed, 575 insertions(+)
>      >  create mode 100644 Documentation/hwmon/ctf2304.rst
>      >  create mode 100644 drivers/hwmon/ctf2304.c
>      >
>      > diff --git a/Documentation/hwmon/ctf2304.rst b/Documentation/hwmon/ctf2304.rst
>      > new file mode 100644
>      > index 000000000000..e1584524d612
>      > --- /dev/null
>      > +++ b/Documentation/hwmon/ctf2304.rst
>      > @@ -0,0 +1,41 @@
>      > +.. SPDX-License-Identifier: GPL-2.0-or-later
>      > +
>      > +Kernel driver ctf2304
>      > +=====================
>      > +
>      > +Supported chips:
>      > +
>      > +  * Sensylink CTF2304
>      > +
>      > +    Prefix: 'ctf2304'
>      > +
>      > +    Addresses scanned: -
>      > +
>      > +    Datasheet:
>      > +
>      > +Author: Il Han <corone.il.han@gmail.com <mailto:corone.il.han@gmail.com>>
>      > +
>      > +
>      > +Description
>      > +-----------
>      > +
>      > +This driver implements support for the Sensylink CTF2304 chip.
>      > +
>      > +The CTF2304 controls the speeds of up to four fans using four independent
>      > +PWM outputs with local and remote temperature and remote voltage sensing.
>      > +
>      > +
>      > +Sysfs entries
>      > +-------------
>      > +
>      > +================== === =======================================================
>      > +fan[1-4]_input     RO  fan tachometer speed in RPM
>      > +fan[1-4]_target    RW  desired fan speed in RPM
>      > +fan[1-4]_div       RW  sets the RPM range of the fan
>      > +pwm[1-4]_enable    RW  regulator mode,
>      > +                       0=auto temperature mode, 1=manual mode, 2=rpm mode
>      > +pwm[1-4]           RW  read: current pwm duty cycle,
>      > +                       write: target pwm duty cycle (0-255)
>      > +in[0-7]_input      RO  measured output voltage
>      > +temp[1-9]_input    RO  measured temperature
>      > +================== === =======================================================
>      > diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
>      > index f1fe75f596a5..a74cd43a3916 100644
>      > --- a/Documentation/hwmon/index.rst
>      > +++ b/Documentation/hwmon/index.rst
>      > @@ -54,6 +54,7 @@ Hardware Monitoring Kernel Drivers
>      >     coretemp
>      >     corsair-cpro
>      >     corsair-psu
>      > +   ctf2304
>      >     da9052
>      >     da9055
>      >     dell-smm-hwmon
>      > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
>      > index 5b3b76477b0e..da9fbb0f8af3 100644
>      > --- a/drivers/hwmon/Kconfig
>      > +++ b/drivers/hwmon/Kconfig
>      > @@ -474,6 +474,16 @@ config SENSORS_CORSAIR_PSU
>      >         This driver can also be built as a module. If so, the module
>      >         will be called corsair-psu.
>      >
>      > +config SENSORS_CTF2304
>      > +     tristate "Sensylink CTF2304 sensor chip"
>      > +     depends on I2C
>      > +     help
>      > +       If you say yes here you get support for PWM and Fan Controller
>      > +       with temperature and voltage sensing.
>      > +
>      > +       This driver can also be built as a module. If so, the module
>      > +       will be called ctf2304.
>      > +
>      >  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 88712b5031c8..3742b52f032d 100644
>      > --- a/drivers/hwmon/Makefile
>      > +++ b/drivers/hwmon/Makefile
>      > @@ -60,6 +60,7 @@ obj-$(CONFIG_SENSORS_BT1_PVT)       += bt1-pvt.o
>      >  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_CTF2304)        += ctf2304.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/ctf2304.c b/drivers/hwmon/ctf2304.c
>      > new file mode 100644
>      > index 000000000000..102c41957219
>      > --- /dev/null
>      > +++ b/drivers/hwmon/ctf2304.c
>      > @@ -0,0 +1,522 @@
>      > +// SPDX-License-Identifier: GPL-2.0-or-later
>      > +/*
>      > + * ctf2304.c - Part of lm_sensors, Linux kernel modules for hardware
>      > + *             monitoring.
>      > + *
>      > + * (C) 2023 by Il Han <corone.il.han@gmail.com <mailto:corone.il.han@gmail.com>>
>      > + */
>      > +
>      > +#include <linux/err.h>
>      > +#include <linux/hwmon.h>
>      > +#include <linux/i2c.h>
>      > +#include <linux/init.h>
>      > +#include <linux/jiffies.h>
>      > +#include <linux/module.h>
>      > +#include <linux/slab.h>
>      > +
>      > +/* CTF2304 registers */
>      > +#define CTF2304_REG_LOCAL_TEMP               0x00
>      > +#define CTF2304_REG_REMOTE_CHANNEL(ch)       (0x01 + (ch))
>      > +#define CTF2304_REG_TACH_COUNT(ch)   (0x09 + (ch))
>      > +#define CTF2304_REG_FAN_CONFIG1              0x10
>      > +#define CTF2304_REG_FAN_CONFIG2              0x11
>      > +#define CTF2304_REG_FAN_RPM_CTRL     0x18
>      > +#define CTF2304_REG_PWMOUT(ch)               (0x40 + (ch))
>      > +#define CTF2304_REG_TARGET_COUNT(ch) (0x44 + (ch))
>      > +
>      > +/* Fan Configure1 register bits */
>      > +#define CTF2304_FAN_CFG1_TRANGE              0x0400
>      > +#define CTF2304_FAN_CFG1_MODE_MASK   (0x7)
>      > +#define CTF2304_FAN_CFG1_MODE_SHIFT  7
>      > +
>      > +/* Fan Configure2 register bits */
>      > +#define CTF2304_FAN_CFG2_MODE_MASK(ch)       (0x6 << (ch) * 4)
>      > +#define CTF2304_FAN_CFG2_MODE_SHIFT(ch)      (1 + (ch) * 4)
>      > +#define CTF2304_FAN_CFG2_DCY_MODE    0
>      > +#define CTF2304_FAN_CFG2_RPM_MODE    1
>      > +#define CTF2304_FAN_CFG2_TEMP_MODE   2
>      > +#define CTF2304_FAN_CFG2_MAX_MODE    3
>      > +
>      > +/* Fan RPM CTRL register bits */
>      > +#define CTF2304_FAN_DIV_MASK(ch)     (0x6 << (ch) * 4)
>      > +#define CTF2304_FAN_DIV_SHIFT(ch)    (1 + (ch) * 4)
>      > +
>      > +#define CTF2304_VCC                  3300
>      > +
>      > +#define FAN_RPM_MIN                  480
>      > +#define FAN_RPM_MAX                  1966080
>      > +
>      > +#define FAN_COUNT_REG_MAX            0xFFF0
>      > +
>      > +#define TEMP_FROM_REG(reg, tr)               ((tr) ? \
>      > +                                      (((((reg) & 0x7FF0) * 1000) >> 8) \
>      > +                                       + ((reg) >> 15) ? -64000 : 0) : \
>      > +                                      (((reg) * 1000) >> 8))
>      > +#define VOLT_FROM_REG(reg, fs)               ((((reg) >> 4) * (fs)) >> 12)
>      > +#define DIV_FROM_REG(reg)            (1 << (reg))
>      > +#define DIV_TO_REG(div)                      ((div == 8) ? 0x3 : \
>      > +                                      (div == 4) ? 0x2 : \
>      > +                                      (div == 1) ? 0x0 : 0x1)
>      > +#define RPM_FROM_REG(reg)            (((reg) >> 4) ? \
>      > +                                      ((32768 * 60) / ((reg) >> 4)) : \
>      > +                                      FAN_RPM_MAX)
>      > +#define RPM_TO_REG(rpm)                      ((rpm) ? \
>      > +                                      ((32768 * 60) / (rpm)) : \
>      > +                                      FAN_COUNT_REG_MAX)
>      > +
>      > +#define NR_CHANNEL                   8
>      > +#define NR_FAN_CHANNEL                       4
>      > +
>      > +/*
>      > + * Client data (each client gets its own)
>      > + */
>      > +struct ctf2304_data {
>      > +     struct i2c_client *client;
>      > +     struct mutex update_lock;
>      > +     char valid; /* zero until following fields are valid */
>      > +     unsigned long last_updated; /* in jiffies */
>      > +
>      > +     /* register values */
>      > +     u16 local_temp;
>      > +     u16 remote_channel[NR_CHANNEL];
>      > +     u16 tach[NR_FAN_CHANNEL];
>      > +     u16 fan_config1;
>      > +     u16 fan_config2;
>      > +     u16 fan_rpm_ctrl;
>      > +     u16 pwm[NR_FAN_CHANNEL];
>      > +     u16 target_count[NR_FAN_CHANNEL];
>      > +};
>      > +
>      > +static struct ctf2304_data *ctf2304_update_device(struct device *dev)
>      > +{
>      > +     struct ctf2304_data *data = dev_get_drvdata(dev);
>      > +     struct i2c_client *client = data->client;
>      > +     struct ctf2304_data *ret = data;
>      > +     int i;
>      > +     int rv;
>      > +
>      > +     mutex_lock(&data->update_lock);
>      > +
>      > +     if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
>      > +             rv = i2c_smbus_read_word_swapped(client,
>      > +                             CTF2304_REG_LOCAL_TEMP);
>      > +             if (rv < 0)
>      > +                     goto abort;
>      > +             data->local_temp = rv;
>      > +
>      > +             for (i = 0; i < NR_CHANNEL; i++) {
>      > +                     rv = i2c_smbus_read_word_swapped(client,
>      > +                                     CTF2304_REG_REMOTE_CHANNEL(i));
>      > +                     if (rv < 0)
>      > +                             goto abort;
>      > +                     data->remote_channel[i] = rv;
>      > +             }
>      > +
>      > +             rv = i2c_smbus_read_word_swapped(client,
>      > +                             CTF2304_REG_FAN_CONFIG1);
>      > +             if (rv < 0)
>      > +                     goto abort;
>      > +             data->fan_config1 = rv;
>      > +             rv = i2c_smbus_read_word_swapped(client,
>      > +                             CTF2304_REG_FAN_CONFIG2);
>      > +             if (rv < 0)
>      > +                     goto abort;
>      > +             data->fan_config2 = rv;
>      > +             rv = i2c_smbus_read_word_swapped(client,
>      > +                             CTF2304_REG_FAN_RPM_CTRL);
>      > +             if (rv < 0)
>      > +                     goto abort;
>      > +             data->fan_rpm_ctrl = rv;
>      > +
>      > +             for (i = 0; i < NR_FAN_CHANNEL; i++) {
>      > +                     rv = i2c_smbus_read_word_swapped(client,
>      > +                                     CTF2304_REG_TACH_COUNT(i));
>      > +                     if (rv < 0)
>      > +                             goto abort;
>      > +                     data->tach[i] = rv;
>      > +                     rv = i2c_smbus_read_word_swapped(client,
>      > +                                     CTF2304_REG_PWMOUT(i));
>      > +                     if (rv < 0)
>      > +                             goto abort;
>      > +                     data->pwm[i] = rv;
>      > +                     rv = i2c_smbus_read_word_swapped(client,
>      > +                                     CTF2304_REG_TARGET_COUNT(i));
>      > +                     if (rv < 0)
>      > +                             goto abort;
>      > +                     data->target_count[i] = rv;
>      > +             }
>      > +
>      > +             data->last_updated = jiffies;
>      > +             data->valid = true;
>      > +     }
>      > +     goto done;
>      > +
>      > +abort:
>      > +     data->valid = false;
>      > +     ret = ERR_PTR(rv);
>      > +
>      > +done:
>      > +     mutex_unlock(&data->update_lock);
>      > +
>      > +     return data;
>      > +}
>      > +
>      > +static int ctf2304_read_temp(struct device *dev, u32 attr, int channel,
>      > +                          long *val)
>      > +{
>      > +     struct ctf2304_data *data = ctf2304_update_device(dev);
>      > +     u16 reg;
>      > +
>      > +     switch (attr) {
>      > +     case hwmon_temp_input:
>      > +             if (channel == 0)
>      > +                     reg = data->local_temp;
>      > +             else
>      > +                     reg = data->remote_channel[channel-1];
>      > +             *val = TEMP_FROM_REG(reg, (data->fan_config1
>      > +                                        & CTF2304_FAN_CFG1_TRANGE));
>      > +             return 0;
>      > +     default:
>      > +             return -EOPNOTSUPP;
>      > +     }
>      > +}
>      > +
>      > +static int get_full_scale(u16 config)
>      > +{
>      > +     int full_scale;
>      > +     u8 bits;
>      > +
>      > +     bits = (config >> CTF2304_FAN_CFG1_MODE_SHIFT)
>      > +            & CTF2304_FAN_CFG1_MODE_MASK;
>      > +
>      > +     if (bits == 0x0)
>      > +             full_scale = 2560;
>      > +     else if (bits == 0x1)
>      > +             full_scale = CTF2304_VCC;
>      > +     else if (bits == 0x2)
>      > +             full_scale = 4096;
>      > +     else if (bits == 0x3)
>      > +             full_scale = 2048;
>      > +     else if (bits == 0x4)
>      > +             full_scale = 1024;
>      > +     else if (bits == 0x5)
>      > +             full_scale = 512;
>      > +     else
>      > +             full_scale = 256;
>      > +
>      > +     return full_scale;
>      > +}
>      > +
>      > +static int ctf2304_read_in(struct device *dev, u32 attr, int channel,
>      > +                        long *val)
>      > +{
>      > +     struct ctf2304_data *data = ctf2304_update_device(dev);
>      > +
>      > +     switch (attr) {
>      > +     case hwmon_temp_input:
>      > +             *val = VOLT_FROM_REG(data->remote_channel[channel],
>      > +                                  get_full_scale(data->fan_config1));
>      > +             return 0;
>      > +     default:
>      > +             return -EOPNOTSUPP;
>      > +     }
>      > +}
>      > +
>      > +static int ctf2304_read_fan(struct device *dev, u32 attr, int channel,
>      > +                         long *val)
>      > +{
>      > +     struct ctf2304_data *data = ctf2304_update_device(dev);
>      > +     u8 bits;
>      > +
>      > +     if (IS_ERR(data))
>      > +             return PTR_ERR(data);
>      > +
>      > +     switch (attr) {
>      > +     case hwmon_fan_input:
>      > +             if (data->tach[channel] == FAN_COUNT_REG_MAX)
>      > +                     *val = 0;
>      > +             else
>      > +                     *val = RPM_FROM_REG(data->tach[channel]);
>      > +             return 0;
>      > +     case hwmon_fan_target:
>      > +             *val = RPM_FROM_REG(data->target_count[channel]);
>      > +             return 0;
>      > +     case hwmon_fan_div:
>      > +             bits = (data->fan_rpm_ctrl & CTF2304_FAN_DIV_MASK(channel))
>      > +                    >> CTF2304_FAN_DIV_SHIFT(channel);
>      > +             *val = DIV_FROM_REG(bits);
>      > +             return 0;
>      > +     default:
>      > +             return -EOPNOTSUPP;
>      > +     }
>      > +}
>      > +
>      > +static int ctf2304_write_fan(struct device *dev, u32 attr, int channel,
>      > +                          long val)
>      > +{
>      > +     struct ctf2304_data *data = dev_get_drvdata(dev);
>      > +     struct i2c_client *client = data->client;
>      > +     int target_count;
>      > +     int err = 0;
>      > +
>      > +     mutex_lock(&data->update_lock);
>      > +
>      > +     switch (attr) {
>      > +     case hwmon_fan_target:
>      > +             val = clamp_val(val, FAN_RPM_MIN, FAN_RPM_MAX);
>      > +             target_count = RPM_TO_REG(val);
>      > +             target_count = clamp_val(target_count, 0x1, 0xFFF);
>      > +             data->target_count[channel] = target_count << 4;
>      > +             err = i2c_smbus_write_word_swapped(client,
>      > +                             CTF2304_REG_TARGET_COUNT(channel),
>      > +                             data->target_count[channel]);
>      > +             break;
>      > +     case hwmon_fan_div:
>      > +             data->fan_rpm_ctrl = (data->fan_rpm_ctrl
>      > +                                   & ~CTF2304_FAN_DIV_MASK(channel))
>      > +                                  | (DIV_TO_REG(val)
>      > +                                     << CTF2304_FAN_DIV_SHIFT(channel));
>      > +             err = i2c_smbus_write_word_swapped(client,
>      > +                             CTF2304_REG_FAN_RPM_CTRL,
>      > +                             data->fan_rpm_ctrl);
>      > +             break;
>      > +     default:
>      > +             err = -EOPNOTSUPP;
>      > +             break;
>      > +     }
>      > +
>      > +     mutex_unlock(&data->update_lock);
>      > +
>      > +     return err;
>      > +}
>      > +
>      > +static int ctf2304_read_pwm(struct device *dev, u32 attr, int channel,
>      > +                         long *val)
>      > +{
>      > +     struct ctf2304_data *data = ctf2304_update_device(dev);
>      > +     u8 bits;
>      > +
>      > +     if (IS_ERR(data))
>      > +             return PTR_ERR(data);
>      > +
>      > +     switch (attr) {
>      > +     case hwmon_pwm_input:
>      > +             *val = data->pwm[channel] >> 8;
>      > +             return 0;
>      > +     case hwmon_pwm_enable:
>      > +             bits = (data->fan_config2
>      > +                     & CTF2304_FAN_CFG2_MODE_MASK(channel))
>      > +                    >> CTF2304_FAN_CFG2_MODE_SHIFT(channel);
>      > +             if (bits == CTF2304_FAN_CFG2_RPM_MODE)
>      > +                     *val = 2;
>      > +             else if (bits == CTF2304_FAN_CFG2_DCY_MODE)
>      > +                     *val = 1;
>      > +             else
>      > +                     *val = 0;
>      > +             return 0;
>      > +     default:
>      > +             return -EOPNOTSUPP;
>      > +     }
>      > +}
>      > +
>      > +static int ctf2304_write_pwm(struct device *dev, u32 attr, int channel,
>      > +                          long val)
>      > +{
>      > +     struct ctf2304_data *data = dev_get_drvdata(dev);
>      > +     struct i2c_client *client = data->client;
>      > +     int err = 0;
>      > +
>      > +     mutex_lock(&data->update_lock);
>      > +
>      > +     switch (attr) {
>      > +     case hwmon_pwm_input:
>      > +             if (val < 0 || val > 255) {
>      > +                     err = -EINVAL;
>      > +                     break;
>      > +             }
>      > +             data->pwm[channel] = (data->pwm[channel] & 0xFF) | (val << 8);
>      > +             err = i2c_smbus_write_word_swapped(client,
>      > +                                                CTF2304_REG_PWMOUT(channel),
>      > +                                                data->pwm[channel]);
>      > +             break;
>      > +     case hwmon_pwm_enable:
>      > +             if (val == 0) {
>      > +                     data->fan_config2 = (data->fan_config2
>      > +                                          & ~CTF2304_FAN_CFG2_MODE_MASK(channel))
>      > +                                         | (CTF2304_FAN_CFG2_TEMP_MODE
>      > +                                            << CTF2304_FAN_CFG2_MODE_SHIFT(channel));
>      > +             } else if (val == 1) {
>      > +                     data->fan_config2 = (data->fan_config2
>      > +                                          & ~CTF2304_FAN_CFG2_MODE_MASK(channel))
>      > +                                         | (CTF2304_FAN_CFG2_DCY_MODE
>      > +                                            << CTF2304_FAN_CFG2_MODE_SHIFT(channel));
>      > +             } else if (val == 2) {
>      > +                     data->fan_config2 = (data->fan_config2
>      > +                                          & ~CTF2304_FAN_CFG2_MODE_MASK(channel))
>      > +                                         | (CTF2304_FAN_CFG2_RPM_MODE
>      > +                                            << CTF2304_FAN_CFG2_MODE_SHIFT(channel));
>      > +             } else {
>      > +                     err = -EINVAL;
>      > +                     break;
>      > +             }
>      > +             err = i2c_smbus_write_word_swapped(client,
>      > +                                                CTF2304_REG_FAN_CONFIG2,
>      > +                                                data->fan_config2);
>      > +             break;
>      > +     default:
>      > +             err = -EOPNOTSUPP;
>      > +             break;
>      > +     }
>      > +
>      > +     mutex_unlock(&data->update_lock);
>      > +
>      > +     return err;
>      > +}
>      > +
>      > +static int ctf2304_read(struct device *dev, enum hwmon_sensor_types type,
>      > +                     u32 attr, int channel, long *val)
>      > +{
>      > +     switch (type) {
>      > +     case hwmon_temp:
>      > +             return ctf2304_read_temp(dev, attr, channel, val);
>      > +     case hwmon_in:
>      > +             return ctf2304_read_in(dev, attr, channel, val);
>      > +     case hwmon_fan:
>      > +             return ctf2304_read_fan(dev, attr, channel, val);
>      > +     case hwmon_pwm:
>      > +             return ctf2304_read_pwm(dev, attr, channel, val);
>      > +     default:
>      > +             return -EOPNOTSUPP;
>      > +     }
>      > +}
>      > +
>      > +static int ctf2304_write(struct device *dev, enum hwmon_sensor_types type,
>      > +                      u32 attr, int channel, long val)
>      > +{
>      > +     switch (type) {
>      > +     case hwmon_fan:
>      > +             return ctf2304_write_fan(dev, attr, channel, val);
>      > +     case hwmon_pwm:
>      > +             return ctf2304_write_pwm(dev, attr, channel, val);
>      > +     default:
>      > +             return -EOPNOTSUPP;
>      > +     }
>      > +}
>      > +
>      > +static umode_t ctf2304_is_visible(const void *data,
>      > +                               enum hwmon_sensor_types type,
>      > +                               u32 attr, int channel)
>      > +{
>      > +     switch (type) {
>      > +     case hwmon_temp:
>      > +     case hwmon_in:
>      > +             return 0444;
>      > +     case hwmon_fan:
>      > +             switch (attr) {
>      > +             case hwmon_fan_input:
>      > +                     return 0444;
>      > +             case hwmon_fan_target:
>      > +             case hwmon_fan_div:
>      > +                     return 0644;
>      > +             default:
>      > +                     break;
>      > +             }
>      > +             break;
>      > +     case hwmon_pwm:
>      > +             return 0644;
>      > +     default:
>      > +             break;
>      > +     }
>      > +
>      > +     return 0;
>      > +}
>      > +
>      > +static const struct hwmon_channel_info *ctf2304_info[] = {
>      > +     HWMON_CHANNEL_INFO(temp,
>      > +                        HWMON_T_INPUT,
>      > +                        HWMON_T_INPUT,
>      > +                        HWMON_T_INPUT,
>      > +                        HWMON_T_INPUT,
>      > +                        HWMON_T_INPUT,
>      > +                        HWMON_T_INPUT,
>      > +                        HWMON_T_INPUT,
>      > +                        HWMON_T_INPUT,
>      > +                        HWMON_T_INPUT),
>      > +     HWMON_CHANNEL_INFO(in,
>      > +                        HWMON_I_INPUT,
>      > +                        HWMON_I_INPUT,
>      > +                        HWMON_I_INPUT,
>      > +                        HWMON_I_INPUT,
>      > +                        HWMON_I_INPUT,
>      > +                        HWMON_I_INPUT,
>      > +                        HWMON_I_INPUT,
>      > +                        HWMON_I_INPUT),
>      > +     HWMON_CHANNEL_INFO(fan,
>      > +                        HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV,
>      > +                        HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV,
>      > +                        HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV,
>      > +                        HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV),
>      > +     HWMON_CHANNEL_INFO(pwm,
>      > +                        HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
>      > +                        HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
>      > +                        HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
>      > +                        HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
>      > +     NULL
>      > +};
>      > +
>      > +static const struct hwmon_ops ctf2304_hwmon_ops = {
>      > +     .is_visible = ctf2304_is_visible,
>      > +     .read = ctf2304_read,
>      > +     .write = ctf2304_write,
>      > +};
>      > +
>      > +static const struct hwmon_chip_info ctf2304_chip_info = {
>      > +     .ops = &ctf2304_hwmon_ops,
>      > +     .info = ctf2304_info,
>      > +};
>      > +
>      > +static int ctf2304_probe(struct i2c_client *client)
>      > +{
>      > +     struct i2c_adapter *adapter = client->adapter;
>      > +     struct device *dev = &client->dev;
>      > +     struct ctf2304_data *data;
>      > +     struct device *hwmon_dev;
>      > +
>      > +     if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
>      > +             return -ENODEV;
>      > +
>      > +     data = devm_kzalloc(dev, sizeof(struct ctf2304_data), GFP_KERNEL);
>      > +     if (!data)
>      > +             return -ENOMEM;
>      > +
>      > +     data->client = client;
>      > +     mutex_init(&data->update_lock);
>      > +
>      > +     hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
>      > +                                                      data,
>      > +                                                      &ctf2304_chip_info,
>      > +                                                      NULL);
>      > +
>      > +     return PTR_ERR_OR_ZERO(hwmon_dev);
>      > +}
>      > +
>      > +static const struct i2c_device_id ctf2304_id[] = {
>      > +     { "ctf2304", 0 },
>      > +     { }
>      > +};
>      > +MODULE_DEVICE_TABLE(i2c, ctf2304_id);
>      > +
>      > +static struct i2c_driver ctf2304_driver = {
>      > +     .class          = I2C_CLASS_HWMON,
>      > +     .driver = {
>      > +             .name   = "ctf2304",
>      > +     },
>      > +     .probe_new      = ctf2304_probe,
>      > +     .id_table       = ctf2304_id,
>      > +};
>      > +
>      > +module_i2c_driver(ctf2304_driver);
>      > +
>      > +MODULE_AUTHOR("Il Han <corone.il.han@gmail.com <mailto:corone.il.han@gmail.com>>");
>      > +MODULE_DESCRIPTION("CTF2304 sensor driver");
>      > +MODULE_LICENSE("GPL");
>      > --
>      > 2.26.3
>      >
> 


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] hwmon: Driver for Sensylink CTF2304
  2023-03-31 13:34 Il Han
@ 2023-04-01 14:54 ` Guenter Roeck
       [not found]   ` <CADrkgWLUV2FbL0hrobQWUi4jsbZenVOXErtuRH_FQ9ZBwJsjHw@mail.gmail.com>
  0 siblings, 1 reply; 5+ messages in thread
From: Guenter Roeck @ 2023-04-01 14:54 UTC (permalink / raw)
  To: Il Han; +Cc: Jean Delvare, linux-hwmon

On Fri, Mar 31, 2023 at 10:34:37PM +0900, Il Han wrote:
> The driver supports the Sensylink CTF2304.
> 

Please version your patches, and provide change logs. I am not inclined
to compare this version with the previous version to find out what changed
besides the API conversion, and I am not inclined to dig through my e-mail
history to figure out how many versions of this patch have been submitted.

Please consult
Documentation/process/submitting-patches.rst
Documentation/process/submit-checklist.rst
Documentation/hwmon/submitting-patches.rst

and follow the guidance in those documents.

Thanks,
Guenter

> Signed-off-by: Il Han <corone.il.han@gmail.com>
> ---
>  Documentation/hwmon/ctf2304.rst |  41 +++
>  Documentation/hwmon/index.rst   |   1 +
>  drivers/hwmon/Kconfig           |  10 +
>  drivers/hwmon/Makefile          |   1 +
>  drivers/hwmon/ctf2304.c         | 522 ++++++++++++++++++++++++++++++++
>  5 files changed, 575 insertions(+)
>  create mode 100644 Documentation/hwmon/ctf2304.rst
>  create mode 100644 drivers/hwmon/ctf2304.c
> 
> diff --git a/Documentation/hwmon/ctf2304.rst b/Documentation/hwmon/ctf2304.rst
> new file mode 100644
> index 000000000000..e1584524d612
> --- /dev/null
> +++ b/Documentation/hwmon/ctf2304.rst
> @@ -0,0 +1,41 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +Kernel driver ctf2304
> +=====================
> +
> +Supported chips:
> +
> +  * Sensylink CTF2304
> +
> +    Prefix: 'ctf2304'
> +
> +    Addresses scanned: -
> +
> +    Datasheet:
> +
> +Author: Il Han <corone.il.han@gmail.com>
> +
> +
> +Description
> +-----------
> +
> +This driver implements support for the Sensylink CTF2304 chip.
> +
> +The CTF2304 controls the speeds of up to four fans using four independent
> +PWM outputs with local and remote temperature and remote voltage sensing.
> +
> +
> +Sysfs entries
> +-------------
> +
> +================== === =======================================================
> +fan[1-4]_input     RO  fan tachometer speed in RPM
> +fan[1-4]_target    RW  desired fan speed in RPM
> +fan[1-4]_div       RW  sets the RPM range of the fan
> +pwm[1-4]_enable    RW  regulator mode,
> +                       0=auto temperature mode, 1=manual mode, 2=rpm mode
> +pwm[1-4]           RW  read: current pwm duty cycle,
> +                       write: target pwm duty cycle (0-255)
> +in[0-7]_input      RO  measured output voltage
> +temp[1-9]_input    RO  measured temperature
> +================== === =======================================================
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index f1fe75f596a5..a74cd43a3916 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -54,6 +54,7 @@ Hardware Monitoring Kernel Drivers
>     coretemp
>     corsair-cpro
>     corsair-psu
> +   ctf2304
>     da9052
>     da9055
>     dell-smm-hwmon
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 5b3b76477b0e..da9fbb0f8af3 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -474,6 +474,16 @@ config SENSORS_CORSAIR_PSU
>  	  This driver can also be built as a module. If so, the module
>  	  will be called corsair-psu.
>  
> +config SENSORS_CTF2304
> +	tristate "Sensylink CTF2304 sensor chip"
> +	depends on I2C
> +	help
> +	  If you say yes here you get support for PWM and Fan Controller
> +	  with temperature and voltage sensing.
> +
> +	  This driver can also be built as a module. If so, the module
> +	  will be called ctf2304.
> +
>  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 88712b5031c8..3742b52f032d 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -60,6 +60,7 @@ obj-$(CONFIG_SENSORS_BT1_PVT)	+= bt1-pvt.o
>  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_CTF2304)	+= ctf2304.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/ctf2304.c b/drivers/hwmon/ctf2304.c
> new file mode 100644
> index 000000000000..102c41957219
> --- /dev/null
> +++ b/drivers/hwmon/ctf2304.c
> @@ -0,0 +1,522 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * ctf2304.c - Part of lm_sensors, Linux kernel modules for hardware
> + *             monitoring.
> + *
> + * (C) 2023 by Il Han <corone.il.han@gmail.com>
> + */
> +
> +#include <linux/err.h>
> +#include <linux/hwmon.h>
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/jiffies.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +
> +/* CTF2304 registers */
> +#define CTF2304_REG_LOCAL_TEMP		0x00
> +#define CTF2304_REG_REMOTE_CHANNEL(ch)	(0x01 + (ch))
> +#define CTF2304_REG_TACH_COUNT(ch)	(0x09 + (ch))
> +#define CTF2304_REG_FAN_CONFIG1		0x10
> +#define CTF2304_REG_FAN_CONFIG2		0x11
> +#define CTF2304_REG_FAN_RPM_CTRL	0x18
> +#define CTF2304_REG_PWMOUT(ch)		(0x40 + (ch))
> +#define CTF2304_REG_TARGET_COUNT(ch)	(0x44 + (ch))
> +
> +/* Fan Configure1 register bits */
> +#define CTF2304_FAN_CFG1_TRANGE		0x0400
> +#define CTF2304_FAN_CFG1_MODE_MASK	(0x7)
> +#define CTF2304_FAN_CFG1_MODE_SHIFT	7
> +
> +/* Fan Configure2 register bits */
> +#define CTF2304_FAN_CFG2_MODE_MASK(ch)	(0x6 << (ch) * 4)
> +#define CTF2304_FAN_CFG2_MODE_SHIFT(ch)	(1 + (ch) * 4)
> +#define CTF2304_FAN_CFG2_DCY_MODE	0
> +#define CTF2304_FAN_CFG2_RPM_MODE	1
> +#define CTF2304_FAN_CFG2_TEMP_MODE	2
> +#define CTF2304_FAN_CFG2_MAX_MODE	3
> +
> +/* Fan RPM CTRL register bits */
> +#define CTF2304_FAN_DIV_MASK(ch)	(0x6 << (ch) * 4)
> +#define CTF2304_FAN_DIV_SHIFT(ch)	(1 + (ch) * 4)
> +
> +#define CTF2304_VCC			3300
> +
> +#define FAN_RPM_MIN			480
> +#define FAN_RPM_MAX			1966080
> +
> +#define FAN_COUNT_REG_MAX		0xFFF0
> +
> +#define TEMP_FROM_REG(reg, tr)		((tr) ? \
> +					 (((((reg) & 0x7FF0) * 1000) >> 8) \
> +					  + ((reg) >> 15) ? -64000 : 0) : \
> +					 (((reg) * 1000) >> 8))
> +#define VOLT_FROM_REG(reg, fs)		((((reg) >> 4) * (fs)) >> 12)
> +#define DIV_FROM_REG(reg)		(1 << (reg))
> +#define DIV_TO_REG(div)			((div == 8) ? 0x3 : \
> +					 (div == 4) ? 0x2 : \
> +					 (div == 1) ? 0x0 : 0x1)
> +#define RPM_FROM_REG(reg)		(((reg) >> 4) ? \
> +					 ((32768 * 60) / ((reg) >> 4)) : \
> +					 FAN_RPM_MAX)
> +#define RPM_TO_REG(rpm)			((rpm) ? \
> +					 ((32768 * 60) / (rpm)) : \
> +					 FAN_COUNT_REG_MAX)
> +
> +#define NR_CHANNEL			8
> +#define NR_FAN_CHANNEL			4
> +
> +/*
> + * Client data (each client gets its own)
> + */
> +struct ctf2304_data {
> +	struct i2c_client *client;
> +	struct mutex update_lock;
> +	char valid; /* zero until following fields are valid */
> +	unsigned long last_updated; /* in jiffies */
> +
> +	/* register values */
> +	u16 local_temp;
> +	u16 remote_channel[NR_CHANNEL];
> +	u16 tach[NR_FAN_CHANNEL];
> +	u16 fan_config1;
> +	u16 fan_config2;
> +	u16 fan_rpm_ctrl;
> +	u16 pwm[NR_FAN_CHANNEL];
> +	u16 target_count[NR_FAN_CHANNEL];
> +};
> +
> +static struct ctf2304_data *ctf2304_update_device(struct device *dev)
> +{
> +	struct ctf2304_data *data = dev_get_drvdata(dev);
> +	struct i2c_client *client = data->client;
> +	struct ctf2304_data *ret = data;
> +	int i;
> +	int rv;
> +
> +	mutex_lock(&data->update_lock);
> +
> +	if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
> +		rv = i2c_smbus_read_word_swapped(client,
> +				CTF2304_REG_LOCAL_TEMP);
> +		if (rv < 0)
> +			goto abort;
> +		data->local_temp = rv;
> +
> +		for (i = 0; i < NR_CHANNEL; i++) {
> +			rv = i2c_smbus_read_word_swapped(client,
> +					CTF2304_REG_REMOTE_CHANNEL(i));
> +			if (rv < 0)
> +				goto abort;
> +			data->remote_channel[i] = rv;
> +		}
> +
> +		rv = i2c_smbus_read_word_swapped(client,
> +				CTF2304_REG_FAN_CONFIG1);
> +		if (rv < 0)
> +			goto abort;
> +		data->fan_config1 = rv;
> +		rv = i2c_smbus_read_word_swapped(client,
> +				CTF2304_REG_FAN_CONFIG2);
> +		if (rv < 0)
> +			goto abort;
> +		data->fan_config2 = rv;
> +		rv = i2c_smbus_read_word_swapped(client,
> +				CTF2304_REG_FAN_RPM_CTRL);
> +		if (rv < 0)
> +			goto abort;
> +		data->fan_rpm_ctrl = rv;
> +
> +		for (i = 0; i < NR_FAN_CHANNEL; i++) {
> +			rv = i2c_smbus_read_word_swapped(client,
> +					CTF2304_REG_TACH_COUNT(i));
> +			if (rv < 0)
> +				goto abort;
> +			data->tach[i] = rv;
> +			rv = i2c_smbus_read_word_swapped(client,
> +					CTF2304_REG_PWMOUT(i));
> +			if (rv < 0)
> +				goto abort;
> +			data->pwm[i] = rv;
> +			rv = i2c_smbus_read_word_swapped(client,
> +					CTF2304_REG_TARGET_COUNT(i));
> +			if (rv < 0)
> +				goto abort;
> +			data->target_count[i] = rv;
> +		}
> +
> +		data->last_updated = jiffies;
> +		data->valid = true;
> +	}
> +	goto done;
> +
> +abort:
> +	data->valid = false;
> +	ret = ERR_PTR(rv);
> +
> +done:
> +	mutex_unlock(&data->update_lock);
> +
> +	return data;
> +}
> +
> +static int ctf2304_read_temp(struct device *dev, u32 attr, int channel,
> +			     long *val)
> +{
> +	struct ctf2304_data *data = ctf2304_update_device(dev);
> +	u16 reg;
> +
> +	switch (attr) {
> +	case hwmon_temp_input:
> +		if (channel == 0)
> +			reg = data->local_temp;
> +		else
> +			reg = data->remote_channel[channel-1];
> +		*val = TEMP_FROM_REG(reg, (data->fan_config1
> +					   & CTF2304_FAN_CFG1_TRANGE));
> +		return 0;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int get_full_scale(u16 config)
> +{
> +	int full_scale;
> +	u8 bits;
> +
> +	bits = (config >> CTF2304_FAN_CFG1_MODE_SHIFT)
> +	       & CTF2304_FAN_CFG1_MODE_MASK;
> +
> +	if (bits == 0x0)
> +		full_scale = 2560;
> +	else if (bits == 0x1)
> +		full_scale = CTF2304_VCC;
> +	else if (bits == 0x2)
> +		full_scale = 4096;
> +	else if (bits == 0x3)
> +		full_scale = 2048;
> +	else if (bits == 0x4)
> +		full_scale = 1024;
> +	else if (bits == 0x5)
> +		full_scale = 512;
> +	else
> +		full_scale = 256;
> +
> +	return full_scale;
> +}
> +
> +static int ctf2304_read_in(struct device *dev, u32 attr, int channel,
> +			   long *val)
> +{
> +	struct ctf2304_data *data = ctf2304_update_device(dev);
> +
> +	switch (attr) {
> +	case hwmon_temp_input:
> +		*val = VOLT_FROM_REG(data->remote_channel[channel],
> +				     get_full_scale(data->fan_config1));
> +		return 0;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int ctf2304_read_fan(struct device *dev, u32 attr, int channel,
> +			    long *val)
> +{
> +	struct ctf2304_data *data = ctf2304_update_device(dev);
> +	u8 bits;
> +
> +	if (IS_ERR(data))
> +		return PTR_ERR(data);
> +
> +	switch (attr) {
> +	case hwmon_fan_input:
> +		if (data->tach[channel] == FAN_COUNT_REG_MAX)
> +			*val = 0;
> +		else
> +			*val = RPM_FROM_REG(data->tach[channel]);
> +		return 0;
> +	case hwmon_fan_target:
> +		*val = RPM_FROM_REG(data->target_count[channel]);
> +		return 0;
> +	case hwmon_fan_div:
> +		bits = (data->fan_rpm_ctrl & CTF2304_FAN_DIV_MASK(channel))
> +		       >> CTF2304_FAN_DIV_SHIFT(channel);
> +		*val = DIV_FROM_REG(bits);
> +		return 0;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int ctf2304_write_fan(struct device *dev, u32 attr, int channel,
> +			     long val)
> +{
> +	struct ctf2304_data *data = dev_get_drvdata(dev);
> +	struct i2c_client *client = data->client;
> +	int target_count;
> +	int err = 0;
> +
> +	mutex_lock(&data->update_lock);
> +
> +	switch (attr) {
> +	case hwmon_fan_target:
> +		val = clamp_val(val, FAN_RPM_MIN, FAN_RPM_MAX);
> +		target_count = RPM_TO_REG(val);
> +		target_count = clamp_val(target_count, 0x1, 0xFFF);
> +		data->target_count[channel] = target_count << 4;
> +		err = i2c_smbus_write_word_swapped(client,
> +				CTF2304_REG_TARGET_COUNT(channel),
> +				data->target_count[channel]);
> +		break;
> +	case hwmon_fan_div:
> +		data->fan_rpm_ctrl = (data->fan_rpm_ctrl
> +				      & ~CTF2304_FAN_DIV_MASK(channel))
> +				     | (DIV_TO_REG(val)
> +					<< CTF2304_FAN_DIV_SHIFT(channel));
> +		err = i2c_smbus_write_word_swapped(client,
> +				CTF2304_REG_FAN_RPM_CTRL,
> +				data->fan_rpm_ctrl);
> +		break;
> +	default:
> +		err = -EOPNOTSUPP;
> +		break;
> +	}
> +
> +	mutex_unlock(&data->update_lock);
> +
> +	return err;
> +}
> +
> +static int ctf2304_read_pwm(struct device *dev, u32 attr, int channel,
> +			    long *val)
> +{
> +	struct ctf2304_data *data = ctf2304_update_device(dev);
> +	u8 bits;
> +
> +	if (IS_ERR(data))
> +		return PTR_ERR(data);
> +
> +	switch (attr) {
> +	case hwmon_pwm_input:
> +		*val = data->pwm[channel] >> 8;
> +		return 0;
> +	case hwmon_pwm_enable:
> +		bits = (data->fan_config2
> +			& CTF2304_FAN_CFG2_MODE_MASK(channel))
> +		       >> CTF2304_FAN_CFG2_MODE_SHIFT(channel);
> +		if (bits == CTF2304_FAN_CFG2_RPM_MODE)
> +			*val = 2;
> +		else if (bits == CTF2304_FAN_CFG2_DCY_MODE)
> +			*val = 1;
> +		else
> +			*val = 0;
> +		return 0;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int ctf2304_write_pwm(struct device *dev, u32 attr, int channel,
> +			     long val)
> +{
> +	struct ctf2304_data *data = dev_get_drvdata(dev);
> +	struct i2c_client *client = data->client;
> +	int err = 0;
> +
> +	mutex_lock(&data->update_lock);
> +
> +	switch (attr) {
> +	case hwmon_pwm_input:
> +		if (val < 0 || val > 255) {
> +			err = -EINVAL;
> +			break;
> +		}
> +		data->pwm[channel] = (data->pwm[channel] & 0xFF) | (val << 8);
> +		err = i2c_smbus_write_word_swapped(client,
> +						   CTF2304_REG_PWMOUT(channel),
> +						   data->pwm[channel]);
> +		break;
> +	case hwmon_pwm_enable:
> +		if (val == 0) {
> +			data->fan_config2 = (data->fan_config2
> +					     & ~CTF2304_FAN_CFG2_MODE_MASK(channel))
> +					    | (CTF2304_FAN_CFG2_TEMP_MODE
> +					       << CTF2304_FAN_CFG2_MODE_SHIFT(channel));
> +		} else if (val == 1) {
> +			data->fan_config2 = (data->fan_config2
> +					     & ~CTF2304_FAN_CFG2_MODE_MASK(channel))
> +					    | (CTF2304_FAN_CFG2_DCY_MODE
> +					       << CTF2304_FAN_CFG2_MODE_SHIFT(channel));
> +		} else if (val == 2) {
> +			data->fan_config2 = (data->fan_config2
> +					     & ~CTF2304_FAN_CFG2_MODE_MASK(channel))
> +					    | (CTF2304_FAN_CFG2_RPM_MODE
> +					       << CTF2304_FAN_CFG2_MODE_SHIFT(channel));
> +		} else {
> +			err = -EINVAL;
> +			break;
> +		}
> +		err = i2c_smbus_write_word_swapped(client,
> +						   CTF2304_REG_FAN_CONFIG2,
> +						   data->fan_config2);
> +		break;
> +	default:
> +		err = -EOPNOTSUPP;
> +		break;
> +	}
> +
> +	mutex_unlock(&data->update_lock);
> +
> +	return err;
> +}
> +
> +static int ctf2304_read(struct device *dev, enum hwmon_sensor_types type,
> +			u32 attr, int channel, long *val)
> +{
> +	switch (type) {
> +	case hwmon_temp:
> +		return ctf2304_read_temp(dev, attr, channel, val);
> +	case hwmon_in:
> +		return ctf2304_read_in(dev, attr, channel, val);
> +	case hwmon_fan:
> +		return ctf2304_read_fan(dev, attr, channel, val);
> +	case hwmon_pwm:
> +		return ctf2304_read_pwm(dev, attr, channel, val);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int ctf2304_write(struct device *dev, enum hwmon_sensor_types type,
> +			 u32 attr, int channel, long val)
> +{
> +	switch (type) {
> +	case hwmon_fan:
> +		return ctf2304_write_fan(dev, attr, channel, val);
> +	case hwmon_pwm:
> +		return ctf2304_write_pwm(dev, attr, channel, val);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static umode_t ctf2304_is_visible(const void *data,
> +				  enum hwmon_sensor_types type,
> +				  u32 attr, int channel)
> +{
> +	switch (type) {
> +	case hwmon_temp:
> +	case hwmon_in:
> +		return 0444;
> +	case hwmon_fan:
> +		switch (attr) {
> +		case hwmon_fan_input:
> +			return 0444;
> +		case hwmon_fan_target:
> +		case hwmon_fan_div:
> +			return 0644;
> +		default:
> +			break;
> +		}
> +		break;
> +	case hwmon_pwm:
> +		return 0644;
> +	default:
> +		break;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct hwmon_channel_info *ctf2304_info[] = {
> +	HWMON_CHANNEL_INFO(temp,
> +			   HWMON_T_INPUT,
> +			   HWMON_T_INPUT,
> +			   HWMON_T_INPUT,
> +			   HWMON_T_INPUT,
> +			   HWMON_T_INPUT,
> +			   HWMON_T_INPUT,
> +			   HWMON_T_INPUT,
> +			   HWMON_T_INPUT,
> +			   HWMON_T_INPUT),
> +	HWMON_CHANNEL_INFO(in,
> +			   HWMON_I_INPUT,
> +			   HWMON_I_INPUT,
> +			   HWMON_I_INPUT,
> +			   HWMON_I_INPUT,
> +			   HWMON_I_INPUT,
> +			   HWMON_I_INPUT,
> +			   HWMON_I_INPUT,
> +			   HWMON_I_INPUT),
> +	HWMON_CHANNEL_INFO(fan,
> +			   HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV,
> +			   HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV,
> +			   HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV,
> +			   HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV),
> +	HWMON_CHANNEL_INFO(pwm,
> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> +			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
> +	NULL
> +};
> +
> +static const struct hwmon_ops ctf2304_hwmon_ops = {
> +	.is_visible = ctf2304_is_visible,
> +	.read = ctf2304_read,
> +	.write = ctf2304_write,
> +};
> +
> +static const struct hwmon_chip_info ctf2304_chip_info = {
> +	.ops = &ctf2304_hwmon_ops,
> +	.info = ctf2304_info,
> +};
> +
> +static int ctf2304_probe(struct i2c_client *client)
> +{
> +	struct i2c_adapter *adapter = client->adapter;
> +	struct device *dev = &client->dev;
> +	struct ctf2304_data *data;
> +	struct device *hwmon_dev;
> +
> +	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
> +		return -ENODEV;
> +
> +	data = devm_kzalloc(dev, sizeof(struct ctf2304_data), GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	data->client = client;
> +	mutex_init(&data->update_lock);
> +
> +	hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
> +							 data,
> +							 &ctf2304_chip_info,
> +							 NULL);
> +
> +	return PTR_ERR_OR_ZERO(hwmon_dev);
> +}
> +
> +static const struct i2c_device_id ctf2304_id[] = {
> +	{ "ctf2304", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, ctf2304_id);
> +
> +static struct i2c_driver ctf2304_driver = {
> +	.class		= I2C_CLASS_HWMON,
> +	.driver = {
> +		.name	= "ctf2304",
> +	},
> +	.probe_new	= ctf2304_probe,
> +	.id_table	= ctf2304_id,
> +};
> +
> +module_i2c_driver(ctf2304_driver);
> +
> +MODULE_AUTHOR("Il Han <corone.il.han@gmail.com>");
> +MODULE_DESCRIPTION("CTF2304 sensor driver");
> +MODULE_LICENSE("GPL");
> -- 
> 2.26.3
> 

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH] hwmon: Driver for Sensylink CTF2304
@ 2023-03-31 13:34 Il Han
  2023-04-01 14:54 ` Guenter Roeck
  0 siblings, 1 reply; 5+ messages in thread
From: Il Han @ 2023-03-31 13:34 UTC (permalink / raw)
  To: Guenter Roeck, Jean Delvare; +Cc: linux-hwmon, Il Han

The driver supports the Sensylink CTF2304.

Signed-off-by: Il Han <corone.il.han@gmail.com>
---
 Documentation/hwmon/ctf2304.rst |  41 +++
 Documentation/hwmon/index.rst   |   1 +
 drivers/hwmon/Kconfig           |  10 +
 drivers/hwmon/Makefile          |   1 +
 drivers/hwmon/ctf2304.c         | 522 ++++++++++++++++++++++++++++++++
 5 files changed, 575 insertions(+)
 create mode 100644 Documentation/hwmon/ctf2304.rst
 create mode 100644 drivers/hwmon/ctf2304.c

diff --git a/Documentation/hwmon/ctf2304.rst b/Documentation/hwmon/ctf2304.rst
new file mode 100644
index 000000000000..e1584524d612
--- /dev/null
+++ b/Documentation/hwmon/ctf2304.rst
@@ -0,0 +1,41 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Kernel driver ctf2304
+=====================
+
+Supported chips:
+
+  * Sensylink CTF2304
+
+    Prefix: 'ctf2304'
+
+    Addresses scanned: -
+
+    Datasheet:
+
+Author: Il Han <corone.il.han@gmail.com>
+
+
+Description
+-----------
+
+This driver implements support for the Sensylink CTF2304 chip.
+
+The CTF2304 controls the speeds of up to four fans using four independent
+PWM outputs with local and remote temperature and remote voltage sensing.
+
+
+Sysfs entries
+-------------
+
+================== === =======================================================
+fan[1-4]_input     RO  fan tachometer speed in RPM
+fan[1-4]_target    RW  desired fan speed in RPM
+fan[1-4]_div       RW  sets the RPM range of the fan
+pwm[1-4]_enable    RW  regulator mode,
+                       0=auto temperature mode, 1=manual mode, 2=rpm mode
+pwm[1-4]           RW  read: current pwm duty cycle,
+                       write: target pwm duty cycle (0-255)
+in[0-7]_input      RO  measured output voltage
+temp[1-9]_input    RO  measured temperature
+================== === =======================================================
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index f1fe75f596a5..a74cd43a3916 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -54,6 +54,7 @@ Hardware Monitoring Kernel Drivers
    coretemp
    corsair-cpro
    corsair-psu
+   ctf2304
    da9052
    da9055
    dell-smm-hwmon
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 5b3b76477b0e..da9fbb0f8af3 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -474,6 +474,16 @@ config SENSORS_CORSAIR_PSU
 	  This driver can also be built as a module. If so, the module
 	  will be called corsair-psu.
 
+config SENSORS_CTF2304
+	tristate "Sensylink CTF2304 sensor chip"
+	depends on I2C
+	help
+	  If you say yes here you get support for PWM and Fan Controller
+	  with temperature and voltage sensing.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called ctf2304.
+
 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 88712b5031c8..3742b52f032d 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -60,6 +60,7 @@ obj-$(CONFIG_SENSORS_BT1_PVT)	+= bt1-pvt.o
 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_CTF2304)	+= ctf2304.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/ctf2304.c b/drivers/hwmon/ctf2304.c
new file mode 100644
index 000000000000..102c41957219
--- /dev/null
+++ b/drivers/hwmon/ctf2304.c
@@ -0,0 +1,522 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ctf2304.c - Part of lm_sensors, Linux kernel modules for hardware
+ *             monitoring.
+ *
+ * (C) 2023 by Il Han <corone.il.han@gmail.com>
+ */
+
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+/* CTF2304 registers */
+#define CTF2304_REG_LOCAL_TEMP		0x00
+#define CTF2304_REG_REMOTE_CHANNEL(ch)	(0x01 + (ch))
+#define CTF2304_REG_TACH_COUNT(ch)	(0x09 + (ch))
+#define CTF2304_REG_FAN_CONFIG1		0x10
+#define CTF2304_REG_FAN_CONFIG2		0x11
+#define CTF2304_REG_FAN_RPM_CTRL	0x18
+#define CTF2304_REG_PWMOUT(ch)		(0x40 + (ch))
+#define CTF2304_REG_TARGET_COUNT(ch)	(0x44 + (ch))
+
+/* Fan Configure1 register bits */
+#define CTF2304_FAN_CFG1_TRANGE		0x0400
+#define CTF2304_FAN_CFG1_MODE_MASK	(0x7)
+#define CTF2304_FAN_CFG1_MODE_SHIFT	7
+
+/* Fan Configure2 register bits */
+#define CTF2304_FAN_CFG2_MODE_MASK(ch)	(0x6 << (ch) * 4)
+#define CTF2304_FAN_CFG2_MODE_SHIFT(ch)	(1 + (ch) * 4)
+#define CTF2304_FAN_CFG2_DCY_MODE	0
+#define CTF2304_FAN_CFG2_RPM_MODE	1
+#define CTF2304_FAN_CFG2_TEMP_MODE	2
+#define CTF2304_FAN_CFG2_MAX_MODE	3
+
+/* Fan RPM CTRL register bits */
+#define CTF2304_FAN_DIV_MASK(ch)	(0x6 << (ch) * 4)
+#define CTF2304_FAN_DIV_SHIFT(ch)	(1 + (ch) * 4)
+
+#define CTF2304_VCC			3300
+
+#define FAN_RPM_MIN			480
+#define FAN_RPM_MAX			1966080
+
+#define FAN_COUNT_REG_MAX		0xFFF0
+
+#define TEMP_FROM_REG(reg, tr)		((tr) ? \
+					 (((((reg) & 0x7FF0) * 1000) >> 8) \
+					  + ((reg) >> 15) ? -64000 : 0) : \
+					 (((reg) * 1000) >> 8))
+#define VOLT_FROM_REG(reg, fs)		((((reg) >> 4) * (fs)) >> 12)
+#define DIV_FROM_REG(reg)		(1 << (reg))
+#define DIV_TO_REG(div)			((div == 8) ? 0x3 : \
+					 (div == 4) ? 0x2 : \
+					 (div == 1) ? 0x0 : 0x1)
+#define RPM_FROM_REG(reg)		(((reg) >> 4) ? \
+					 ((32768 * 60) / ((reg) >> 4)) : \
+					 FAN_RPM_MAX)
+#define RPM_TO_REG(rpm)			((rpm) ? \
+					 ((32768 * 60) / (rpm)) : \
+					 FAN_COUNT_REG_MAX)
+
+#define NR_CHANNEL			8
+#define NR_FAN_CHANNEL			4
+
+/*
+ * Client data (each client gets its own)
+ */
+struct ctf2304_data {
+	struct i2c_client *client;
+	struct mutex update_lock;
+	char valid; /* zero until following fields are valid */
+	unsigned long last_updated; /* in jiffies */
+
+	/* register values */
+	u16 local_temp;
+	u16 remote_channel[NR_CHANNEL];
+	u16 tach[NR_FAN_CHANNEL];
+	u16 fan_config1;
+	u16 fan_config2;
+	u16 fan_rpm_ctrl;
+	u16 pwm[NR_FAN_CHANNEL];
+	u16 target_count[NR_FAN_CHANNEL];
+};
+
+static struct ctf2304_data *ctf2304_update_device(struct device *dev)
+{
+	struct ctf2304_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	struct ctf2304_data *ret = data;
+	int i;
+	int rv;
+
+	mutex_lock(&data->update_lock);
+
+	if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
+		rv = i2c_smbus_read_word_swapped(client,
+				CTF2304_REG_LOCAL_TEMP);
+		if (rv < 0)
+			goto abort;
+		data->local_temp = rv;
+
+		for (i = 0; i < NR_CHANNEL; i++) {
+			rv = i2c_smbus_read_word_swapped(client,
+					CTF2304_REG_REMOTE_CHANNEL(i));
+			if (rv < 0)
+				goto abort;
+			data->remote_channel[i] = rv;
+		}
+
+		rv = i2c_smbus_read_word_swapped(client,
+				CTF2304_REG_FAN_CONFIG1);
+		if (rv < 0)
+			goto abort;
+		data->fan_config1 = rv;
+		rv = i2c_smbus_read_word_swapped(client,
+				CTF2304_REG_FAN_CONFIG2);
+		if (rv < 0)
+			goto abort;
+		data->fan_config2 = rv;
+		rv = i2c_smbus_read_word_swapped(client,
+				CTF2304_REG_FAN_RPM_CTRL);
+		if (rv < 0)
+			goto abort;
+		data->fan_rpm_ctrl = rv;
+
+		for (i = 0; i < NR_FAN_CHANNEL; i++) {
+			rv = i2c_smbus_read_word_swapped(client,
+					CTF2304_REG_TACH_COUNT(i));
+			if (rv < 0)
+				goto abort;
+			data->tach[i] = rv;
+			rv = i2c_smbus_read_word_swapped(client,
+					CTF2304_REG_PWMOUT(i));
+			if (rv < 0)
+				goto abort;
+			data->pwm[i] = rv;
+			rv = i2c_smbus_read_word_swapped(client,
+					CTF2304_REG_TARGET_COUNT(i));
+			if (rv < 0)
+				goto abort;
+			data->target_count[i] = rv;
+		}
+
+		data->last_updated = jiffies;
+		data->valid = true;
+	}
+	goto done;
+
+abort:
+	data->valid = false;
+	ret = ERR_PTR(rv);
+
+done:
+	mutex_unlock(&data->update_lock);
+
+	return data;
+}
+
+static int ctf2304_read_temp(struct device *dev, u32 attr, int channel,
+			     long *val)
+{
+	struct ctf2304_data *data = ctf2304_update_device(dev);
+	u16 reg;
+
+	switch (attr) {
+	case hwmon_temp_input:
+		if (channel == 0)
+			reg = data->local_temp;
+		else
+			reg = data->remote_channel[channel-1];
+		*val = TEMP_FROM_REG(reg, (data->fan_config1
+					   & CTF2304_FAN_CFG1_TRANGE));
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int get_full_scale(u16 config)
+{
+	int full_scale;
+	u8 bits;
+
+	bits = (config >> CTF2304_FAN_CFG1_MODE_SHIFT)
+	       & CTF2304_FAN_CFG1_MODE_MASK;
+
+	if (bits == 0x0)
+		full_scale = 2560;
+	else if (bits == 0x1)
+		full_scale = CTF2304_VCC;
+	else if (bits == 0x2)
+		full_scale = 4096;
+	else if (bits == 0x3)
+		full_scale = 2048;
+	else if (bits == 0x4)
+		full_scale = 1024;
+	else if (bits == 0x5)
+		full_scale = 512;
+	else
+		full_scale = 256;
+
+	return full_scale;
+}
+
+static int ctf2304_read_in(struct device *dev, u32 attr, int channel,
+			   long *val)
+{
+	struct ctf2304_data *data = ctf2304_update_device(dev);
+
+	switch (attr) {
+	case hwmon_temp_input:
+		*val = VOLT_FROM_REG(data->remote_channel[channel],
+				     get_full_scale(data->fan_config1));
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int ctf2304_read_fan(struct device *dev, u32 attr, int channel,
+			    long *val)
+{
+	struct ctf2304_data *data = ctf2304_update_device(dev);
+	u8 bits;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	switch (attr) {
+	case hwmon_fan_input:
+		if (data->tach[channel] == FAN_COUNT_REG_MAX)
+			*val = 0;
+		else
+			*val = RPM_FROM_REG(data->tach[channel]);
+		return 0;
+	case hwmon_fan_target:
+		*val = RPM_FROM_REG(data->target_count[channel]);
+		return 0;
+	case hwmon_fan_div:
+		bits = (data->fan_rpm_ctrl & CTF2304_FAN_DIV_MASK(channel))
+		       >> CTF2304_FAN_DIV_SHIFT(channel);
+		*val = DIV_FROM_REG(bits);
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int ctf2304_write_fan(struct device *dev, u32 attr, int channel,
+			     long val)
+{
+	struct ctf2304_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	int target_count;
+	int err = 0;
+
+	mutex_lock(&data->update_lock);
+
+	switch (attr) {
+	case hwmon_fan_target:
+		val = clamp_val(val, FAN_RPM_MIN, FAN_RPM_MAX);
+		target_count = RPM_TO_REG(val);
+		target_count = clamp_val(target_count, 0x1, 0xFFF);
+		data->target_count[channel] = target_count << 4;
+		err = i2c_smbus_write_word_swapped(client,
+				CTF2304_REG_TARGET_COUNT(channel),
+				data->target_count[channel]);
+		break;
+	case hwmon_fan_div:
+		data->fan_rpm_ctrl = (data->fan_rpm_ctrl
+				      & ~CTF2304_FAN_DIV_MASK(channel))
+				     | (DIV_TO_REG(val)
+					<< CTF2304_FAN_DIV_SHIFT(channel));
+		err = i2c_smbus_write_word_swapped(client,
+				CTF2304_REG_FAN_RPM_CTRL,
+				data->fan_rpm_ctrl);
+		break;
+	default:
+		err = -EOPNOTSUPP;
+		break;
+	}
+
+	mutex_unlock(&data->update_lock);
+
+	return err;
+}
+
+static int ctf2304_read_pwm(struct device *dev, u32 attr, int channel,
+			    long *val)
+{
+	struct ctf2304_data *data = ctf2304_update_device(dev);
+	u8 bits;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	switch (attr) {
+	case hwmon_pwm_input:
+		*val = data->pwm[channel] >> 8;
+		return 0;
+	case hwmon_pwm_enable:
+		bits = (data->fan_config2
+			& CTF2304_FAN_CFG2_MODE_MASK(channel))
+		       >> CTF2304_FAN_CFG2_MODE_SHIFT(channel);
+		if (bits == CTF2304_FAN_CFG2_RPM_MODE)
+			*val = 2;
+		else if (bits == CTF2304_FAN_CFG2_DCY_MODE)
+			*val = 1;
+		else
+			*val = 0;
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int ctf2304_write_pwm(struct device *dev, u32 attr, int channel,
+			     long val)
+{
+	struct ctf2304_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	int err = 0;
+
+	mutex_lock(&data->update_lock);
+
+	switch (attr) {
+	case hwmon_pwm_input:
+		if (val < 0 || val > 255) {
+			err = -EINVAL;
+			break;
+		}
+		data->pwm[channel] = (data->pwm[channel] & 0xFF) | (val << 8);
+		err = i2c_smbus_write_word_swapped(client,
+						   CTF2304_REG_PWMOUT(channel),
+						   data->pwm[channel]);
+		break;
+	case hwmon_pwm_enable:
+		if (val == 0) {
+			data->fan_config2 = (data->fan_config2
+					     & ~CTF2304_FAN_CFG2_MODE_MASK(channel))
+					    | (CTF2304_FAN_CFG2_TEMP_MODE
+					       << CTF2304_FAN_CFG2_MODE_SHIFT(channel));
+		} else if (val == 1) {
+			data->fan_config2 = (data->fan_config2
+					     & ~CTF2304_FAN_CFG2_MODE_MASK(channel))
+					    | (CTF2304_FAN_CFG2_DCY_MODE
+					       << CTF2304_FAN_CFG2_MODE_SHIFT(channel));
+		} else if (val == 2) {
+			data->fan_config2 = (data->fan_config2
+					     & ~CTF2304_FAN_CFG2_MODE_MASK(channel))
+					    | (CTF2304_FAN_CFG2_RPM_MODE
+					       << CTF2304_FAN_CFG2_MODE_SHIFT(channel));
+		} else {
+			err = -EINVAL;
+			break;
+		}
+		err = i2c_smbus_write_word_swapped(client,
+						   CTF2304_REG_FAN_CONFIG2,
+						   data->fan_config2);
+		break;
+	default:
+		err = -EOPNOTSUPP;
+		break;
+	}
+
+	mutex_unlock(&data->update_lock);
+
+	return err;
+}
+
+static int ctf2304_read(struct device *dev, enum hwmon_sensor_types type,
+			u32 attr, int channel, long *val)
+{
+	switch (type) {
+	case hwmon_temp:
+		return ctf2304_read_temp(dev, attr, channel, val);
+	case hwmon_in:
+		return ctf2304_read_in(dev, attr, channel, val);
+	case hwmon_fan:
+		return ctf2304_read_fan(dev, attr, channel, val);
+	case hwmon_pwm:
+		return ctf2304_read_pwm(dev, attr, channel, val);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int ctf2304_write(struct device *dev, enum hwmon_sensor_types type,
+			 u32 attr, int channel, long val)
+{
+	switch (type) {
+	case hwmon_fan:
+		return ctf2304_write_fan(dev, attr, channel, val);
+	case hwmon_pwm:
+		return ctf2304_write_pwm(dev, attr, channel, val);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static umode_t ctf2304_is_visible(const void *data,
+				  enum hwmon_sensor_types type,
+				  u32 attr, int channel)
+{
+	switch (type) {
+	case hwmon_temp:
+	case hwmon_in:
+		return 0444;
+	case hwmon_fan:
+		switch (attr) {
+		case hwmon_fan_input:
+			return 0444;
+		case hwmon_fan_target:
+		case hwmon_fan_div:
+			return 0644;
+		default:
+			break;
+		}
+		break;
+	case hwmon_pwm:
+		return 0644;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static const struct hwmon_channel_info *ctf2304_info[] = {
+	HWMON_CHANNEL_INFO(temp,
+			   HWMON_T_INPUT,
+			   HWMON_T_INPUT,
+			   HWMON_T_INPUT,
+			   HWMON_T_INPUT,
+			   HWMON_T_INPUT,
+			   HWMON_T_INPUT,
+			   HWMON_T_INPUT,
+			   HWMON_T_INPUT,
+			   HWMON_T_INPUT),
+	HWMON_CHANNEL_INFO(in,
+			   HWMON_I_INPUT,
+			   HWMON_I_INPUT,
+			   HWMON_I_INPUT,
+			   HWMON_I_INPUT,
+			   HWMON_I_INPUT,
+			   HWMON_I_INPUT,
+			   HWMON_I_INPUT,
+			   HWMON_I_INPUT),
+	HWMON_CHANNEL_INFO(fan,
+			   HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV,
+			   HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV,
+			   HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV,
+			   HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV),
+	HWMON_CHANNEL_INFO(pwm,
+			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
+			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
+			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
+			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
+	NULL
+};
+
+static const struct hwmon_ops ctf2304_hwmon_ops = {
+	.is_visible = ctf2304_is_visible,
+	.read = ctf2304_read,
+	.write = ctf2304_write,
+};
+
+static const struct hwmon_chip_info ctf2304_chip_info = {
+	.ops = &ctf2304_hwmon_ops,
+	.info = ctf2304_info,
+};
+
+static int ctf2304_probe(struct i2c_client *client)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	struct device *dev = &client->dev;
+	struct ctf2304_data *data;
+	struct device *hwmon_dev;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
+		return -ENODEV;
+
+	data = devm_kzalloc(dev, sizeof(struct ctf2304_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->client = client;
+	mutex_init(&data->update_lock);
+
+	hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
+							 data,
+							 &ctf2304_chip_info,
+							 NULL);
+
+	return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct i2c_device_id ctf2304_id[] = {
+	{ "ctf2304", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ctf2304_id);
+
+static struct i2c_driver ctf2304_driver = {
+	.class		= I2C_CLASS_HWMON,
+	.driver = {
+		.name	= "ctf2304",
+	},
+	.probe_new	= ctf2304_probe,
+	.id_table	= ctf2304_id,
+};
+
+module_i2c_driver(ctf2304_driver);
+
+MODULE_AUTHOR("Il Han <corone.il.han@gmail.com>");
+MODULE_DESCRIPTION("CTF2304 sensor driver");
+MODULE_LICENSE("GPL");
-- 
2.26.3


^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2023-04-05 13:49 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-03-25  3:21 [PATCH] hwmon: Driver for Sensylink CTF2304 Il Han
2023-03-25 13:20 ` Guenter Roeck
2023-03-31 13:34 Il Han
2023-04-01 14:54 ` Guenter Roeck
     [not found]   ` <CADrkgWLUV2FbL0hrobQWUi4jsbZenVOXErtuRH_FQ9ZBwJsjHw@mail.gmail.com>
2023-04-05 13:49     ` Guenter Roeck

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).