Hi Krzysztof, El mié, 2 jul 2025 a las 10:35, Krzysztof Kozlowski (<krzk@xxxxxxxxxx>) escribió: > > 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@xxxxxxxxx> > > --- > > 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@xxxxxxxxx> > > + */ > > + > > +#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. The EMC2101 datasheet is explicit about the Look Up Table registers (REG_FAN_LUT_TEMP and REG_FAN_LUT_SPEED) being RO if FAN_LUT_DISABLE isn't set, so I believe that we need the mutex even if we switch to regmap. I will add an explanation with that in the next version. Should I still switch the implementation to regmap considering that we need a mutex? > > > +}; > > > > ... > > > + > > +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