[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <c893384d-4134-4510-be87-11a2c9ba6cc7@kernel.org>
Date: Wed, 2 Jul 2025 10:35:25 +0200
From: Krzysztof Kozlowski <krzk@...nel.org>
To: Álvaro Fernández Rojas <noltari@...il.com>,
jdelvare@...e.com, linux@...ck-us.net, robh@...nel.org, krzk+dt@...nel.org,
conor+dt@...nel.org, corbet@....net, linux-hwmon@...r.kernel.org,
devicetree@...r.kernel.org, linux-kernel@...r.kernel.org,
linux-doc@...r.kernel.org
Subject: Re: [PATCH] drivers: hwmon: add EMC2101 driver
On 01/07/2025 20:12, Álvaro Fernández Rojas wrote:
> The Microchip EMC2101 is a SMBus 2.0 fan controller with temperature
> monitoring.
> It supports up to 1 fan, 1 internal temperature sensor, 1 external
> temperature sensor and an 8 entry look up table to create a
> programmable temperature response.
>
> Signed-off-by: Álvaro Fernández Rojas <noltari@...il.com>
> ---
> drivers/hwmon/Kconfig | 10 +
> drivers/hwmon/Makefile | 1 +
> drivers/hwmon/emc2101.c | 2175 +++++++++++++++++++++++++++++++++++++++
> 3 files changed, 2186 insertions(+)
> create mode 100644 drivers/hwmon/emc2101.c
>
> v2: multiple improvements:
> - Remove FAN_RPM_MIN definition.
> - Rename FAN_FALSE_READ to FAN_MIN_READ.
> - pwm_auto_point_temp_hyst_store(): simplify function.
> - emc2101_fan_min_read(): add missing FAN_MIN_READ condition.
> - emc2101_fan_min_write(): fix tach_count calculation.
> - emc2101_init(): fix REG_TACH_MIN value.
>
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 079620dd4286..360b9f66275c 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -2002,6 +2002,16 @@ config SENSORS_EMC1403
> Threshold values can be configured using sysfs.
> Data from the different diodes are accessible via sysfs.
>
> +config SENSORS_EMC2101
> + tristate "SMSC EMC2101"
> + depends on I2C
> + help
> + If you say yes here you get support for the SMSC EMC2101
> + fan controller chips.
> +
> + This driver can also be built as a module. If so, the module
> + will be called emc2101.
> +
> config SENSORS_EMC2103
> tristate "SMSC EMC2103"
> depends on I2C
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 48e5866c0c9a..70e95096c6f2 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -73,6 +73,7 @@ obj-$(CONFIG_SENSORS_DRIVETEMP) += drivetemp.o
> obj-$(CONFIG_SENSORS_DS620) += ds620.o
> obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
> obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o
> +obj-$(CONFIG_SENSORS_EMC2101) += emc2101.o
> obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o
> obj-$(CONFIG_SENSORS_EMC2305) += emc2305.o
> obj-$(CONFIG_SENSORS_EMC6W201) += emc6w201.o
> diff --git a/drivers/hwmon/emc2101.c b/drivers/hwmon/emc2101.c
> new file mode 100644
> index 000000000000..65f2eff27aaf
> --- /dev/null
> +++ b/drivers/hwmon/emc2101.c
> @@ -0,0 +1,2176 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Driver for Microchip EMC2101 fan controller.
> + *
> + * Copyright 2025 Álvaro Fernández Rojas <noltari@...il.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/mutex.h>
> +#include <linux/slab.h>
> +#include <linux/util_macros.h>
> +
> +#define REG_TEMP_INT 0x00
> +#define REG_TEMP_EXT_HI 0x01
> +#define REG_STATUS 0x02
> +#define ADC_BUSY BIT(7)
> +#define TEMP_INT_HIGH BIT(6)
> +#define EEPROM_ERROR BIT(5)
> +#define TEMP_EXT_HIGH BIT(4)
> +#define TEMP_EXT_LOW BIT(3)
> +#define TEMP_EXT_FAULT BIT(2)
> +#define TEMP_EXT_CRIT BIT(1)
> +#define TACH_LOW BIT(0)
> +#define REG_CONFIG 0x03
> +#define ALERT_IRQ_ACK BIT(7)
> +#define FAN_STANDBY_ENABLE BIT(6)
> +#define FAN_STANDBY_MODE BIT(5)
> +#define FAN_MODE_DAC BIT(4)
> +#define SMBUS_TOUT_DISABLE BIT(3)
> +#define PIN_FUNC_TACH BIT(2)
> +#define TEMP_EXT_CRIT_UNLOCK BIT(1)
> +#define PIN_ASSERT_3_EXC BIT(0)
> +#define REG_CONV_RATE 0x04
> +#define CONV_RATE_MASK 0xf
> +#define REG_TEMP_INT_MAX 0x05
> +#define REG_TEMP_EXT_MAX_HI 0x07
> +#define REG_TEMP_EXT_MIN_HI 0x08
> +#define REG_TEMP_EXT_FORCE 0x0c
> +#define REG_ONE_SHOT 0x0f
> +#define REG_TEMP_EXT_LO 0x10
> +#define REG_SCRATCHPAD_1 0x11
> +#define REG_SCRATCHPAD_2 0x12
> +#define REG_TEMP_EXT_MAX_LO 0x13
> +#define REG_TEMP_EXT_MIN_LO 0x14
> +#define REG_ALERT_MASK 0x16
> +#define IRQ_TEMP_INT_MAX_DISABLE BIT(6)
> +#define IRQ_TEMP_EXT_MAX_DISABLE BIT(4)
> +#define IRQ_TEMP_EXT_MIN_DISABLE BIT(3)
> +#define IRQ_TEMP_EXT_CRIT_DISABLE BIT(1)
> +#define IRQ_TACH_MIN_DISABLE BIT(0)
> +#define REG_EXT_IDEALITY 0x17
> +#define EXT_IDEALITY_START 9846
> +#define EXT_IDEALITY_STEP 13
> +#define EXT_IDEALITY_VAL(x) (EXT_IDEALITY_START + \
> + ((x) * EXT_IDEALITY_STEP))
> +#define EXT_IDEALITY_MASK 0x3f
> +#define REG_BETA_COMP 0x18
> +#define BETA_COMP_AUTO BIT(3)
> +#define BETA_COMP_DISABLE 7
> +#define BETA_COMP_2_33 6
> +#define BETA_COMP_1_00 5
> +#define BETA_COMP_0_43 4
> +#define BETA_COMP_0_33 3
> +#define BETA_COMP_0_25 2
> +#define BETA_COMP_0_18 1
> +#define BETA_COMP_0_11 0
> +#define BETA_COMP_MASK 0x7
> +#define REG_TEMP_EXT_CRIT 0x19
> +#define REG_TEMP_EXT_CRIT_HYST 0x21
> +#define REG_TACH_LO 0x46
> +#define REG_TACH_HI 0x47
> +#define REG_TACH_MIN_LO 0x48
> +#define REG_TACH_MIN_HI 0x49
> +#define REG_FAN_CONFIG 0x4a
> +#define FAN_EXT_TEMP_FORCE BIT(6)
> +#define FAN_LUT_DISABLE BIT(5)
> +#define FAN_POL_INV BIT(4)
> +#define FAN_CLK_SEL BIT(3)
> +#define FAN_CLK_OVR BIT(2)
> +#define TACH_FALSE_READ_DISABLE BIT(0)
> +#define TACH_FALSE_READ_MASK 0x3
> +#define REG_FAN_SPIN 0x4b
> +#define FAN_SPIN_UP_ABORT BIT(5)
> +#define FAN_SPIN_UP_POWER_SHIFT 3
> +#define FAN_SPIN_UP_POWER_100 (3 << FAN_SPIN_UP_POWER_SHIFT)
> +#define FAN_SPIN_UP_POWER_75 (2 << FAN_SPIN_UP_POWER_SHIFT)
> +#define FAN_SPIN_UP_POWER_50 (1 << FAN_SPIN_UP_POWER_SHIFT)
> +#define FAN_SPIN_UP_POWER_0 (0 << FAN_SPIN_UP_POWER_SHIFT)
> +#define FAN_SPIN_UP_POWER_MASK (0x3 << FAN_SPIN_UP_POWER_SHIFT)
> +#define FAN_SPIN_UP_TIME_3200 7
> +#define FAN_SPIN_UP_TIME_1600 6
> +#define FAN_SPIN_UP_TIME_800 5
> +#define FAN_SPIN_UP_TIME_400 4
> +#define FAN_SPIN_UP_TIME_200 3
> +#define FAN_SPIN_UP_TIME_100 2
> +#define FAN_SPIN_UP_TIME_50 1
> +#define FAN_SPIN_UP_TIME_0 0
> +#define FAN_SPIN_UP_TIME_MASK 0x7
> +#define REG_FAN_SET 0x4c
> +#define FAN_SET_MASK 0x3f
> +#define REG_PWM_FREQ 0x4d
> +#define PWM_FREQ_MASK 0x1f
> +#define REG_PWM_FREQ_DIV 0x4e
> +#define REG_FAN_LUT_HYST 0x4f
> +#define FAN_LUT_HYST_MASK 0x1f
> +#define REG_FAN_LUT_TEMP(x) (0x50 + (0x2 * (x)))
> +/* Write only with FAN_LUT_DISABLE */
> +#define FAN_LUT_TEMP_MASK 0x7f
> +#define REG_FAN_LUT_SPEED(x) (0x51 + (0x2 * (x)))
> +/* Write only with FAN_LUT_DISABLE */
> +#define FAN_LUT_SPEED_MASK 0x3f
> +#define REG_AVG_FILTER 0xbf
> +#define FILTER_SHIFT 1
> +#define FILTER_L2 (3 << FILTER_SHIFT)
> +#define FILTER_L1 (1 << FILTER_SHIFT)
> +#define FILTER_NONE (0 << FILTER_SHIFT)
> +#define FILTER_MASK (0x3 << FILTER_SHIFT)
> +#define ALERT_PIN_TEMP_COMP BIT(0)
> +#define REG_PRODUCT_ID 0xfd
> +#define REG_MANUFACTURER_ID 0xfe
> +#define REG_REVISION 0xff
> +
> +#define CLK_FREQ_ALT 1400
> +#define CLK_FREQ_BASE 360000
> +
> +#define FAN_LUT_COUNT 8
> +#define FAN_LUT_HYST_DEF 4
> +#define FAN_LUT_HYST_MIN 0
> +#define FAN_LUT_HYST_MAX 31
> +#define FAN_MIN_READ 0xffff
> +#define FAN_RPM_FACTOR 5400000
> +
> +#define MANUFACTURER_ID 0x5d
> +
> +#define TEMP_EXT_HI_FAULT 0x7f
> +#define TEMP_EXT_LO_FAULT_OPEN 0x00
> +#define TEMP_EXT_LO_FAULT_SHORT 0xe0
> +
> +#define TEMP_LO_FRAC 125
> +#define TEMP_LO_SHIFT 5
> +#define TEMP_LO_MASK (0x3 << TEMP_LO_SHIFT)
> +
> +#define TEMP_MIN -64
> +#define TEMP_MAX 127
> +#define TEMP_MAX_FRAC 750
> +
> +enum emc2101_fan_spin_up_abort {
> + EMC2101_FAN_SPIN_ABORT_DISABLE = 0,
> + EMC2101_FAN_SPIN_ABORT_ENABLE
> +};
> +
> +enum emc2101_fan_standby {
> + EMC2101_FAN_STBY_DISABLE = 0,
> + EMC2101_FAN_STBY_ENABLE
> +};
> +
> +enum emc2101_mode {
> + EMC2101_MODE_PWM = 0,
> + EMC2101_MODE_DAC
> +};
> +
> +enum ecm2101_product_id {
> + EMC2101 = 0x16,
> + EMC2101_R = 0x28
> +};
> +
> +enum emc2101_pwm_enable {
> + EMC2101_PWM_MANUAL = 1,
> + EMC2101_PWM_LUT = 2
> +};
> +
> +enum emc2101_pwm_polarity {
> + EMC2101_POL_NORMAL = 0,
> + EMC2101_POL_INVERTED
> +};
> +
> +enum emc2101_temp_channels {
> + EMC2101_TC_INT = 0,
> + EMC2101_TC_EXT,
> + EMC2101_TC_FORCE,
> + EMC2101_TC_NUM
> +};
> +
> +enum emc2101_temp_diode {
> + EMC2101_TD_CPU = 1,
> + EMC2101_TD_2N3904 = 2
> +};
> +
> +struct emc2101_data {
> + struct i2c_client *client;
> + struct device *dev;
> + struct mutex mutex;
Add a comment describing what you are protecting here. It looks so far
like you could just use regmap and drop the mutex, but I didn't check
thoroughly.
> +};
...
> +
> +static int emc2101_probe(struct i2c_client *client)
> +{
> + struct i2c_adapter *adapter = client->adapter;
> + struct device *dev = &client->dev;
> + struct emc2101_data *data;
> + struct device *hwmon_dev;
> +
> + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
> + return -EIO;
> +
> + data = devm_kzalloc(dev, sizeof(struct emc2101_data), GFP_KERNEL);
sizeof(*)
> + if (!data)
> + return -ENOMEM;
> +
> + data->client = client;
> + data->dev = dev;
> + mutex_init(&data->mutex);
> +
> + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
> + &emc2101_chip_info,
> + emc2101_hwmon_groups);
> + if (IS_ERR(hwmon_dev))
> + return PTR_ERR(hwmon_dev);
> +
> + dev_info(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), client->name);
Drivers should be silent oon success. We already know that this probed
based on sysfs.
> +
> + return emc2101_init(data);
> +}
> +
> +static int emc2101_detect(struct i2c_client *client, struct i2c_board_info *info)
> +{
> + struct i2c_adapter *adapter = client->adapter;
> + s32 manufacturer, product, revision;
> + struct device *dev = &adapter->dev;
> +
> + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
> + return -ENODEV;
> +
> + manufacturer = i2c_smbus_read_byte_data(client, REG_MANUFACTURER_ID);
> + if (manufacturer != MANUFACTURER_ID)
> + return -ENODEV;
> +
> + product = i2c_smbus_read_byte_data(client, REG_PRODUCT_ID);
> + switch (product) {
> + case EMC2101:
> + strscpy(info->type, "emc2101", I2C_NAME_SIZE);
> + break;
> + case EMC2101_R:
> + strscpy(info->type, "emc2101-r", I2C_NAME_SIZE);
> + break;
> + default:
> + return -ENODEV;
> + }
> +
> + revision = i2c_smbus_read_byte_data(client, REG_REVISION);
> +
> + dev_info(dev, "Found %s at 0x%02x (rev 0x%02x).\n",
> + info->type, client->addr, revision);
> +
> + return 0;
> +}
> +
> +static const struct i2c_device_id emc2101_ids[] = {
> + { "emc2101" },
> + { "emc2101-r" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, emc2101_ids);
> +
> +static const struct of_device_id emc2101_of_match_table[] = {
> + { .compatible = "microchip,emc2101", },
> + { .compatible = "microchip,emc2101-r", },
Devices are compatible then? Express it in the bindings and drop this entry.
> + { },
> +};
> +MODULE_DEVICE_TABLE(of, emc2101_of_match_table);
> +
> +static const unsigned short emc2101_address_list[] = {
> + 0x4c, I2C_CLIENT_END
> +};
> +
Best regards,
Krzysztof
Powered by blists - more mailing lists