From b41a05832eedd5c29f510b165fc5ffccb44d1c38 Mon Sep 17 00:00:00 2001 From: John Erasmus Mari Geronimo Date: Tue, 24 Sep 2024 13:10:50 +0800 Subject: [PATCH 1/3] dt-bindings: hwmon: add adi,max31875.yaml Add documentation for devicetree bindings for max31875 Signed-off-by: John Erasmus Mari Geronimo --- .../bindings/hwmon/adi,max31875.yaml | 82 +++++++++++++++++++ MAINTAINERS | 7 ++ 2 files changed, 89 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/adi,max31875.yaml diff --git a/Documentation/devicetree/bindings/hwmon/adi,max31875.yaml b/Documentation/devicetree/bindings/hwmon/adi,max31875.yaml new file mode 100644 index 00000000000000..5f1f3677286a72 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/adi,max31875.yaml @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright 2024 Analog Devices Inc. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/adi,max31875.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices MAX31875 Low-Power I2C Temperature Sensor + +maintainers: + - John Erasmus Mari Geronimo + +description: | + The MAX31875 is a ±1°C-accurate local temperature sensor with I2C/SMBus + interface. The combination of tiny package and excellent temperature + measurement accuracy makes this product ideal for a variety of equipment. + https://www.analog.com/media/en/technical-documentation/data-sheets/MAX31875.pdf + +properties: + compatible: + enum: + - adi,max31875 + + reg: + maxItems: 1 + + vdd-supply: + description: + Must have values in the interval (1.6V; 3.6V) in order for the device to + function correctly. Must also have regulator-name of "vref". + + adi,extended-format: + description: + If present, the temperature register is read in extended format. If not + present, the temperature register is read in normal format (default). + type: boolean + + adi,comp-int: + description: + If present, the Overtemperature Status bit operates in interrupt mode. If + not present, it operates in comparator mode (default). + type: boolean + + adi,timeout-disable: + description: + Disables timeout. Bus timeout resets the I2C-compatible interface when SCL + is low for more than 30ms (nominal). + type: boolean + + adi,fault-q: + description: + Select how many consecutive overtemperature faults must occur before an + overtemperature fault is indicated in the Overtemperature Status bit. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [1, 2, 4, 6] + +required: + - compatible + - reg + - vdd-supply + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + temperature-sensor@48 { + compatible = "adi,max31875"; + reg = <0x48>; + vdd-supply = <&vdd>; + + adi,extended-format; + adi,comp-int; + adi,timeout-disable; + adi,fault-q = <1>; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 5aea9ad4ada5be..6cca985bca5456 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12596,6 +12596,13 @@ F: Documentation/devicetree/bindings/hwmon/adi,max31827.yaml F: Documentation/hwmon/max31827.rst F: drivers/hwmon/max31827.c +MAX31875 TEMPERATURE SENSOR DRIVER +M: John Erasmus Mari Geronimo +L: linux-hwmon@vger.kernel.org +S: Supported +W: http://ez.analog.com/community/linux-device-drivers +F: Documentation/devicetree/bindings/hwmon/adi,max31875.yaml + MAX31335 RTC DRIVER M: Antoniu Miclaus L: linux-rtc@vger.kernel.org From 0438b7a1814d8b887690ea4f4bb03cef781f9b31 Mon Sep 17 00:00:00 2001 From: John Erasmus Mari Geronimo Date: Tue, 24 Sep 2024 13:15:16 +0800 Subject: [PATCH 2/3] hwmon: add driver for max31875 MAX31875 Low-Power I2C Temperature Sensor Signed-off-by: John Erasmus Mari Geronimo --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/max31875.rst | 99 +++++ MAINTAINERS | 2 + drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/max31875.c | 635 +++++++++++++++++++++++++++++++ 6 files changed, 749 insertions(+) create mode 100644 Documentation/hwmon/max31875.rst create mode 100644 drivers/hwmon/max31875.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 8459b8a590609d..41863ce978a914 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -138,6 +138,7 @@ Hardware Monitoring Kernel Drivers max31785 max31790 max31827 + max31875 max34440 max6620 max6639 diff --git a/Documentation/hwmon/max31875.rst b/Documentation/hwmon/max31875.rst new file mode 100644 index 00000000000000..5af5f67339d01b --- /dev/null +++ b/Documentation/hwmon/max31875.rst @@ -0,0 +1,99 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver max31875 +====================== + +Supported chips: + + * Maxim MAX31875 + + Prefix: 'max31875' + + Addresses scanned: I2C 0x48 - 0x4f + + Datasheet: Publicly available at the Analog Devices website + + +Authors: + - John Erasmus Mari Geronimo + +Description +----------- + +MAX31875 implements a temperature sensor with a 4 WLP packaging scheme. This +sensor measures the temperature of the chip itself. + +MAX31875 has an over temperature alarm with an effective value and a hysteresis +value: +80 and +75 degrees. + +The alarm can be configured in comparator and interrupt mode from the +devicetree. In Comparator mode, the OT status bit have a value of 1 when the +temperature rises above the T_OS value, which is also subject to the Fault Queue +selection. OT status returns to 0 when the temperature drops below the T_HYST +value or when shutdown mode is entered. + +In interrupt mode exceeding T_OS also sets OT status to 1, which remains set +until a read operation is performed on any of the registers; at this point, it +returns to 0. Once OT status is set to 1 from exceeding T_OS and reset, it is +set to 1 again only when the temperature drops below T_HYST. The output remains +asserted until it is reset by a read. It is set again if the temperature rises +above T_OS, and so on. + +Putting the MAX31875 into shutdown mode also resets the OT status. Note that if +the mode is changed while OT status is set, an OT status reset may be required +before it begins to behave normally. To prevent this, it is recommended to +perform a read of the configuration/status register to clear the status bits +before changing the operating mode. + +The conversions can be manual with the one-shot functionality and automatic with +a set frequency. When powered on, the chip measures temperatures with 0.25 +conv/s. The conversion rate can be modified with update_interval attribute of +the chip. Conversion/second = 1/update_interval. Thus, the available options +according to the data sheet are: + +- 4000 (ms) = 0.25 conv/sec (default) +- 1000 (ms) = 1 conv/sec +- 250 (ms) = 4 conv/sec +- 125 (ms) = 8 conv/sec + +Enabling the device when it is already enabled has the side effect of setting +the conversion frequency to 0.25 conv/s. The conversion time varies depending on +the resolution. + +The conversion time doubles with every bit of increased resolution. The +available resolutions are: + +- 8 bit -> 8.75 ms conversion time +- 9 bit -> 17.5 ms conversion time +- 10 bit (default) -> 35 ms conversion time +- 12 bit -> 140 ms conversion time +- 13 bit (extended) -> 280 ms conversion time + +There is a temp1_resolution attribute which indicates the unit change in the +input temperature in milli-degrees C. In extended format, the MSB is given a +value of 128 degrees C (compared to normal format: 64 degrees C), which allows +temperatures as high as 150 degrees C to be measured. + +- 1000 mC -> 8 bit +- 500 mC -> 9 bit +- 250 mC -> 10 bit (default) +- 62 mC -> 12 bit - actually this is 62.5, but the file returns 62 +- 62 mC -> 13 bit (extended) - actually this is 62.5, but the file returns 62 + +When chip is in shutdown mode and a read operation is requested, one-shot is +triggered, the device waits for ms, and only after that is +the temperature value register read. Note that the conversion times are rounded +up to the nearest possible integer. + +The LSB of the temperature values is 0.0625 degrees Celsius, but the values of +the temperatures are displayed in milli-degrees. This means, that some data is +lost. The step between 2 consecutive values is 62 or 63. This effect can be seen +in the writing of alarm values too. For positive numbers the user-input value +will always be rounded down to the nearest possible value, for negative numbers +the user-input will always be rounded up to the nearest possible value. + +Bus timeout resets the I2C-compatible interface when SCL is low for more than +30ms (nominal). + +The Fault Queue bits select how many consecutive temperature faults must occur +before overtemperature fault is indicated in the OT status bit. diff --git a/MAINTAINERS b/MAINTAINERS index 6cca985bca5456..2928bd3957ff46 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12602,6 +12602,8 @@ L: linux-hwmon@vger.kernel.org S: Supported W: http://ez.analog.com/community/linux-device-drivers F: Documentation/devicetree/bindings/hwmon/adi,max31875.yaml +F: Documentation/hwmon/max31875.rst +F: drivers/hwmon/max31875.c MAX31335 RTC DRIVER M: Antoniu Miclaus diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 0e8797f2fdd56c..0a98b24f31604e 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1102,6 +1102,17 @@ config MAX31827 This driver can also be built as a module. If so, the module will be called max31827. +config SENSORS_MAX31875 + tristate "MAX31875 low-power I2C temperature sensor" + depends on I2C + help + If you say yes here you get support for MAX31875 temperature sensor. + The MAX31875 is a +-1 degree C-accurate local temperature sensor with + I2C/SMBus interface. + + This driver can also be built as a module. If so, the module + will be called max31875. + config SENSORS_MAX6620 tristate "Maxim MAX6620 fan controller" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index f5362385d0a861..9a627daafe4458 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -150,6 +150,7 @@ obj-$(CONFIG_SENSORS_MAX6650) += max6650.o obj-$(CONFIG_SENSORS_MAX6697) += max6697.o obj-$(CONFIG_SENSORS_MAX31790) += max31790.o obj-$(CONFIG_MAX31827) += max31827.o +obj-$(CONFIG_SENSORS_MAX31875) += max31875.o obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o obj-$(CONFIG_SENSORS_TC654) += tc654.o diff --git a/drivers/hwmon/max31875.c b/drivers/hwmon/max31875.c new file mode 100644 index 00000000000000..5a254efdf613d6 --- /dev/null +++ b/drivers/hwmon/max31875.c @@ -0,0 +1,635 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices MAX31875 I2C Temperature Sensor driver + * + * Copyright 2024 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX31875_TEMP_REG 0x0 +#define MAX31875_CONF_REG 0x1 +#define MAX31875_T_HYST_REG 0x2 +#define MAX31875_T_OS_REG 0x3 + +#define MAX31875_OVER_TEMP_STAT_MASK BIT(15) +#define MAX31875_FAULT_QUEUE_MASK GENMASK(12, 11) +#define MAX31875_COMP_INT_MASK BIT(9) +#define MAX31875_SHUT_DOWN_MASK BIT(8) +#define MAX31875_DATA_FORMAT_MASK BIT(7) +#define MAX31875_RESOLUTION_MASK GENMASK(6, 5) +#define MAX31875_TIMEOUT_MASK BIT(4) +#define MAX31875_PEC_MASK BIT(3) +#define MAX31875_CONV_RATE_MASK GENMASK(2, 1) +#define MAX31875_ONESHOT_MASK BIT(0) + +#define MAX31875_FAULT_QUEUE(x) FIELD_PREP(MAX31875_FAULT_QUEUE_MASK, x) +#define MAX31875_COMP_INT(x) FIELD_PREP(MAX31875_COMP_INT_MASK, x) +#define MAX31875_SHUT_DOWN(x) FIELD_PREP(MAX31875_SHUT_DOWN_MASK, x) +#define MAX31875_DATA_FORMAT(x) FIELD_PREP(MAX31875_DATA_FORMAT_MASK, x) +#define MAX31875_RESOLUTION(x) FIELD_PREP(MAX31875_RESOLUTION_MASK, x) +#define MAX31875_TIMEOUT(x) FIELD_PREP(MAX31875_TIMEOUT_MASK, x) +#define MAX31875_ONESHOT(x) FIELD_PREP(MAX31875_ONESHOT_MASK, x) + +#define MAX31875_TEMP_OS_DEFAULT (80 * MILLI) +#define MAX31875_TEMP_HYST_DEFAULT (75 * MILLI) + +#define MAX31875_8_BIT_CNV_TIME 8750 +#define MAX31875_9_BIT_CNV_TIME 17500 +#define MAX31875_10_BIT_CNV_TIME 35000 +#define MAX31875_12_BIT_CNV_TIME 140000 +#define MAX31875_EXTENDED_CNV_TIME 280000 + +enum max31875_conv { + MAX31875_CONV_0_25_HZ, + MAX31875_CONV_1_HZ, + MAX31875_CONV_4_HZ, + MAX31875_CONV_8_HZ, +}; + +static const u16 max31875_conversions[] = { + [MAX31875_CONV_0_25_HZ] = 4000, + [MAX31875_CONV_1_HZ] = 1000, + [MAX31875_CONV_4_HZ] = 250, + [MAX31875_CONV_8_HZ] = 125, +}; + +enum max31875_res { + MAX31875_RES_8_BIT, + MAX31875_RES_9_BIT, + MAX31875_RES_10_BIT, + MAX31875_RES_12_BIT, +}; + +static const u16 max31875_resolutions[] = { + [MAX31875_RES_8_BIT] = 1000, + [MAX31875_RES_9_BIT] = 500, + [MAX31875_RES_10_BIT] = 250, + [MAX31875_RES_12_BIT] = 62, +}; + +static const u32 max31875_conv_times[] = { + [MAX31875_RES_8_BIT] = MAX31875_8_BIT_CNV_TIME, + [MAX31875_RES_9_BIT] = MAX31875_9_BIT_CNV_TIME, + [MAX31875_RES_10_BIT] = MAX31875_10_BIT_CNV_TIME, + [MAX31875_RES_12_BIT] = MAX31875_12_BIT_CNV_TIME, +}; + +struct max31875_data { + /** + * Prevent simultaneous access to the i2c client. + */ + struct mutex lock; + struct regmap *regmap; + bool enable; + bool extend_fmt; + u8 bit_reso; + u16 update_interval; + long temp_os; + long temp_hyst; +}; + +static int max31875_shutdown_write(struct max31875_data *data, unsigned int reg, + unsigned int mask, unsigned int val) +{ + int ret; + + /** + * Before the Max Temperature, Max Temperature Hyteresis, Format + * and Resolution bits from Configuration register are changed over I2C, + * the part must be in shutdown mode. + * + * Mutex is used to ensure, that some other process doesn't change the + * configuration register. + */ + guard(mutex)(&data->lock); + + if (!data->enable) { + if (!mask) + ret = regmap_write(data->regmap, reg, val); + else + ret = regmap_update_bits(data->regmap, reg, mask, val); + return ret; + } + + ret = regmap_update_bits(data->regmap, MAX31875_CONF_REG, + MAX31875_SHUT_DOWN_MASK, + MAX31875_SHUT_DOWN(1)); + if (ret) + return ret; + + if (!mask) + ret = regmap_write(data->regmap, reg, val); + else + ret = regmap_update_bits(data->regmap, reg, mask, val); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, MAX31875_CONF_REG, + MAX31875_SHUT_DOWN_MASK, + MAX31875_SHUT_DOWN(0)); +} + +static int max31875_update_bits(struct max31875_data *data, bool shutdown, + const u16 max31875_list[], size_t size, + const u16 val, const u16 mask, u8 *idx) +{ + u8 res = 0; + int ret; + + /** + * Convert the desired value into register + * bits. + * + * This was inspired by lm73 driver. + */ + while (res < size && val < max31875_list[res]) + res++; + + if (res == size) + res--; + + if (shutdown) + ret = max31875_shutdown_write(data, MAX31875_CONF_REG, mask, + FIELD_PREP(mask, res)); + else + ret = regmap_update_bits(data->regmap, MAX31875_CONF_REG, mask, + FIELD_PREP(mask, res)); + + if (ret) + return ret; + + if (idx) + *idx = res; + + return 0; +} + +static inline void max31875_convert_raw_to_temp(bool extend_fmt, unsigned int raw, + long *val) +{ + /** + * In the extended format, the MSB is increased from 64C to 128C. + */ + s32 temp = (sign_extend32(raw, 15) * (long)MILLI) >> (extend_fmt ? 7 : 8); + + *val = temp; +} + +static int max31875_read_temp(struct max31875_data *data, bool is_temp_input, + u8 reg, long *val) +{ + int ret; + unsigned int uval; + + if (is_temp_input) { + if (!data->enable) { + ret = regmap_update_bits(data->regmap, + MAX31875_CONF_REG, + MAX31875_ONESHOT_MASK, + MAX31875_ONESHOT(1)); + if (ret) + return ret; + + if (data->extend_fmt) + fsleep(MAX31875_EXTENDED_CNV_TIME); + else + fsleep(max31875_conv_times[data->bit_reso]); + } + + /** + * The conversion time for 12-bit resolution is 140ms and for + * 13-bit resolution (extended) is 280ms. So, 15ms and 155ms are + * added when the update interval is set to 125ms. + */ + if (data->update_interval == max31875_conversions[MAX31875_CONV_8_HZ]) { + if (data->extend_fmt) + fsleep(155000); + else if (data->bit_reso == MAX31875_RES_12_BIT) + fsleep(15000); + } + } + + ret = regmap_read(data->regmap, reg, &uval); + if (ret) + return ret; + + max31875_convert_raw_to_temp(data->extend_fmt, uval, val); + + return 0; +} + +static inline void max31875_convert_temp_to_raw(bool extend_fmt, long *val) +{ + /** + * In the extended format, the MSB is increased from 64C to 128C. + */ + *val = (*val << (extend_fmt ? 7 : 8)) / (long)MILLI; +} + +static int max31875_write_temp(struct max31875_data *data, u8 reg, long val) +{ + max31875_convert_temp_to_raw(data->extend_fmt, &val); + + return max31875_shutdown_write(data, reg, 0, val); +} + +static int max31875_init_client(struct max31875_data *data, struct device *dev) +{ + unsigned int res = 0; + int ret; + u32 propval; + + data->enable = true; + + res |= MAX31875_RESOLUTION(MAX31875_RES_10_BIT); + + if (device_property_read_bool(dev, "adi,comp-int")) + res |= MAX31875_COMP_INT(1); + + if (device_property_read_bool(dev, "adi,timeout-disable")) + res |= MAX31875_TIMEOUT(1); + + if (!device_property_read_u32(dev, "adi,fault-q", &propval)) { + /** + * Convert the desired fault queue into register bits. + */ + switch (propval) { + case 1: + case 2: + case 4: + case 6: + res |= MAX31875_FAULT_QUEUE(propval >> 1); + break; + default: + return dev_err_probe(dev, -EINVAL, + "Invalid data in adi,fault-q\n"); + } + } + + if (device_property_read_bool(dev, "adi,extended-format")) { + data->extend_fmt = true; + res |= MAX31875_DATA_FORMAT(1); + + ret = max31875_write_temp(data, MAX31875_T_OS_REG, + MAX31875_TEMP_OS_DEFAULT); + if (ret) + return ret; + + ret = max31875_write_temp(data, MAX31875_T_HYST_REG, + MAX31875_TEMP_HYST_DEFAULT); + if (ret) + return ret; + } else { + data->extend_fmt = false; + } + + return regmap_write(data->regmap, MAX31875_CONF_REG, res); +} + +static ssize_t temp1_resolution_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct max31875_data *data = dev_get_drvdata(dev); + unsigned int uval; + int ret; + + /** + * The resolution of extended format is the same as the 12-bit + * resolution. + */ + if (data->extend_fmt) + return sysfs_emit(buf, "%u\n", + max31875_resolutions[MAX31875_RES_12_BIT]); + + ret = regmap_read(data->regmap, MAX31875_CONF_REG, &uval); + if (ret) + return ret; + + uval = FIELD_GET(MAX31875_RESOLUTION_MASK, uval); + + if (uval >= ARRAY_SIZE(max31875_resolutions)) + return -EIO; + + return sysfs_emit(buf, "%u\n", max31875_resolutions[uval]); +} + +static ssize_t temp1_resolution_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct max31875_data *data = dev_get_drvdata(dev); + int ret; + unsigned int uval; + + if (data->extend_fmt) + return -EOPNOTSUPP; + + ret = kstrtouint(buf, 10, &uval); + if (ret) + return ret; + + ret = max31875_update_bits(data, true, max31875_resolutions, + ARRAY_SIZE(max31875_resolutions), uval, + MAX31875_RESOLUTION_MASK, + &data->bit_reso); + + return ret ? ret : count; +} + +static DEVICE_ATTR_RW(temp1_resolution); + +static struct attribute *max31875_attrs[] = { + &dev_attr_temp1_resolution.attr, + NULL +}; +ATTRIBUTE_GROUPS(max31875); + +static umode_t max31875_is_visible(const void *_data, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return 0644; + default: + return 0; + } + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_max_alarm: + return 0444; + case hwmon_temp_enable: + case hwmon_temp_max: + case hwmon_temp_max_hyst: + return 0644; + default: + return 0; + } + default: + return 0; + } +} + +static int max31875_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int ret; + struct max31875_data *data = dev_get_drvdata(dev); + unsigned int uval; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + ret = regmap_read(data->regmap, MAX31875_CONF_REG, + &uval); + if (ret) + return ret; + + uval = FIELD_GET(MAX31875_CONV_RATE_MASK, uval); + + *val = max31875_conversions[uval]; + + return 0; + default: + return -EOPNOTSUPP; + } + case hwmon_temp: + switch (attr) { + case hwmon_temp_enable: + scoped_guard(mutex, &data->lock) { + ret = regmap_read(data->regmap, + MAX31875_CONF_REG, &uval); + if (ret) + return ret; + + *val = !FIELD_GET(MAX31875_SHUT_DOWN_MASK, + uval); + } + + return 0; + case hwmon_temp_input: + scoped_guard(mutex, &data->lock) + ret = max31875_read_temp(data, true, + MAX31875_TEMP_REG, + val); + + return ret; + case hwmon_temp_max: + return max31875_read_temp(data, false, + MAX31875_T_OS_REG, val); + case hwmon_temp_max_hyst: + return max31875_read_temp(data, false, + MAX31875_T_HYST_REG, val); + case hwmon_temp_max_alarm: + ret = regmap_read(data->regmap, MAX31875_CONF_REG, + &uval); + if (ret) + return ret; + + *val = FIELD_GET(MAX31875_OVER_TEMP_STAT_MASK, uval); + + return 0; + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static int max31875_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct max31875_data *data = dev_get_drvdata(dev); + int ret; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + ret = max31875_update_bits(data, false, + max31875_conversions, + ARRAY_SIZE(max31875_conversions), + (const u16)val, MAX31875_CONV_RATE_MASK, + NULL); + if (!ret) + data->update_interval = (u16)val; + + return ret; + default: + return -EOPNOTSUPP; + } + case hwmon_temp: + switch (attr) { + case hwmon_temp_enable: + if (val >> 1) + return -EOPNOTSUPP; + + scoped_guard(mutex, &data->lock) { + ret = regmap_update_bits(data->regmap, + MAX31875_CONF_REG, + MAX31875_SHUT_DOWN_MASK, + MAX31875_SHUT_DOWN(!val)); + if (!ret) + data->enable = val; + } + return ret; + case hwmon_temp_max: + /** + * The temperature hysteresis must be less than + * or equal to the temperature over limit. + * + * So, if the temperature over limit is less + * than the temperature hysteresis, the + * temperature hysteresis is set to the + * temperature over limit. + */ + if (val < data->temp_hyst) { + data->temp_hyst = val; + + ret = max31875_write_temp(data, + MAX31875_T_HYST_REG, + val); + if (ret) + return ret; + } + + ret = max31875_write_temp(data, MAX31875_T_OS_REG, val); + if (!ret) + data->temp_os = val; + + return ret; + case hwmon_temp_max_hyst: + /** + * The temperature hysteresis must be less than + * or equal to the temperature over limit. + * + * So, if the temperature hysteresis is greater + * than the temperature over limit, the + * temperature hyteresis is set to the + * temperature over limit. + */ + if (val > data->temp_os) + val = data->temp_os; + + ret = max31875_write_temp(data, MAX31875_T_HYST_REG, + val); + if (!ret) + data->temp_hyst = val; + + return ret; + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static const struct hwmon_ops max31875_hwmon_ops = { + .is_visible = max31875_is_visible, + .read = max31875_read, + .write = max31875_write, +}; + +static const struct hwmon_channel_info *max31875_hwmon_channel_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_ENABLE | HWMON_T_INPUT | HWMON_T_MAX | + HWMON_T_MAX_HYST | HWMON_T_MAX_ALARM), + HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL), + NULL, +}; + +static const struct hwmon_chip_info max31875_chip_info = { + .ops = &max31875_hwmon_ops, + .info = max31875_hwmon_channel_info, +}; + +static const struct regmap_config max31875_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = MAX31875_T_OS_REG, +}; + +static int max31875_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct max31875_data *data; + struct device *hwmon_dev; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -EOPNOTSUPP; + + data = devm_kzalloc(dev, sizeof(struct max31875_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + mutex_init(&data->lock); + + ret = devm_regulator_get_enable(dev, "vref"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable regulator\n"); + + data->temp_os = MAX31875_TEMP_OS_DEFAULT; + data->temp_hyst = MAX31875_TEMP_HYST_DEFAULT; + + data->regmap = devm_regmap_init_i2c(client, &max31875_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "Failed to allocate regmap.\n"); + + ret = max31875_init_client(data, dev); + if (ret) + return ret; + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, + &max31875_chip_info, + max31875_groups); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id max31875_id[] = { + { "max31875", }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, max31875_id); + +static const struct of_device_id max31875_of_match[] = { + { .compatible = "adi,max31875" }, + { }, +}; +MODULE_DEVICE_TABLE(of, max31875_of_match); + +static struct i2c_driver max31875_driver = { + .driver = { + .name = "max31875", + .of_match_table = max31875_of_match, + }, + .probe_new = max31875_probe, + .id_table = max31875_id, +}; +module_i2c_driver(max31875_driver) + +MODULE_AUTHOR("John Erasmus Mari Geronimo Date: Tue, 24 Sep 2024 13:17:47 +0800 Subject: [PATCH 3/3] hwmon: Kconfig.adi: imply MAX31875 Add entry for the MAX31875 driver. Signed-off-by: John Erasmus Mari Geronimo --- drivers/hwmon/Kconfig.adi | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/Kconfig.adi b/drivers/hwmon/Kconfig.adi index db613969acc86c..f39fabf97727f9 100644 --- a/drivers/hwmon/Kconfig.adi +++ b/drivers/hwmon/Kconfig.adi @@ -43,5 +43,6 @@ config HWMON_ALL_ADI_DRIVERS imply SENSORS_LTC4261 imply MAX31827 imply SENSORS_MAX31760 + imply SENSORS_MAX31875 imply SENSORS_LT7182S