new file mode 100644
@@ -0,0 +1,41 @@
+Kernel driver max31760
+======================
+
+Supported chips:
+ * Maxim Integrated MAX31760
+ Prefix: 'max31760'
+ Addresses scanned: none
+ Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX31760.pdf
+
+Author:
+ John Muir <john@jmuir.com>
+
+Description
+-----------
+
+The MAX31760 integrates temperature sensing along with precision PWM fan
+control. Please read the datasheet referenced above for a comprehensive
+description of this device.
+
+This device driver's hwmon integration provides the common sysfs interfaces to
+manage two fans and two temperature sensors, and pwm controls for the fan speed.
+A temperature to pwm lookup table is exposed via a series of 'auto_point'
+configuration files. See Documentation/hwmon/sysfs-interface for more
+information.
+
+The following custom controls are defined (in the custom sub-directory):
+
+control - Accepts control commands:
+ "reset" - Execute a soft reset of the device.
+ "clearff" - Clear the fan fault.
+
+eeprom_read - Read from the EEPROM into registers.
+eeprom_write - Write register contents to the EEPROM.
+ Write "0" to these to read or write the entire register
+ contents. Write a bit-field as per the datasheet to write a
+ portion of the register contents.
+
+pwm1_fan_fault - PWM value in the range of 0 to 255 used when a fan is faulty.
+
+pwm1_ramp_rate - PWM increment per second when the PWM value is changed.
+ Accepted values are 8, 16, 32, or 255 (instantaneous).
@@ -895,6 +895,16 @@ config SENSORS_MAX6697
This driver can also be built as a module. If so, the module
will be called max6697.
+config SENSORS_MAX31760
+ tristate "Maxim MAX31760 fan controller"
+ depends on I2C
+ help
+ If you say yes here you get support for the Maxim Integrated
+ MAX31760 Precision Fan-Speed Controller.
+
+ This driver can also be built as a module. If so, the module
+ will be called max31760.
+
config SENSORS_MAX31790
tristate "Maxim MAX31790 sensor chip"
depends on I2C
@@ -120,6 +120,7 @@ obj-$(CONFIG_SENSORS_MAX6639) += max6639.o
obj-$(CONFIG_SENSORS_MAX6642) += max6642.o
obj-$(CONFIG_SENSORS_MAX6650) += max6650.o
obj-$(CONFIG_SENSORS_MAX6697) += max6697.o
+obj-$(CONFIG_SENSORS_MAX31760) += max31760.o
obj-$(CONFIG_SENSORS_MAX31790) += max31790.o
obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o
new file mode 100644
@@ -0,0 +1,1461 @@
+/* Maxim Integrated MAX31760 Precision Fan-Speed Controller driver
+ *
+ * Copyright (C) 2017 Google, Inc.
+ * Author: muirj
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/ctype.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+#define DRIVER_NAME "max31760"
+
+/*
+ * MAX31760 registers.
+ * Indentation helps identify how these constants apply:
+ * Register number.
+ * Per-register bit fields.
+ * Values for multi-bit fields.
+ */
+#define MAX31760_REG_CR1 0x00 /* Control Register 1 */
+#define MAX31760_CR1_TIS 0x01 /* Temperature Index Source */
+#define MAX31760_CR1_MTI 0x02 /* Maximum Temperature Index */
+#define MAX31760_CR1_PPS 0x04 /* PWM Polarity */
+#define MAX31760_CR1_DRV 0x18 /* PWM Frequency */
+#define MAX31760_DRV_33HZ 0x00
+#define MAX31760_DRV_150HZ 0x08
+#define MAX31760_DRV_1500HZ 0x10
+#define MAX31760_DRV_25KHZ MAX31760_CR1_DRV
+#define MAX31760_CR1_HYST 0x20 /* Lookup Table Hysteresis: 2C or 4C */
+#define MAX31760_CR1_POR 0x40 /* Software Power-On Reset */
+#define MAX31760_CR1_ALTMSK 0x80 /* Alert Mask */
+#define MAX31760_REG_CR2 0x01 /* Control Register 2 */
+#define MAX31760_CR2_DFC 0x01 /* Direct Fan Control */
+#define MAX31760_CR2_FSST 0x02 /* Fan Sense Signal Type */
+#define MAX31760_CR2_RDPS 0x04 /* RD Polarity Selection */
+#define MAX31760_CR2_FSEN 0x08 /* FS Input Enable */
+#define MAX31760_CR2_FFMODE 0x10 /* FF Functionality Selection */
+#define MAX31760_CR2_SPEN 0x20 /* Spin-up Enable */
+#define MAX31760_CR2_ALERTS 0x40 /* Alerts Functionality Selection */
+#define MAX31760_CR2_STBY 0x80 /* Standby Mode Enable */
+#define MAX31760_REG_CR3 0x02 /* Control Register 3 */
+#define MAX31760_CR3_TACH1E 0x01 /* Tachometer 1 Enable */
+#define MAX31760_CR3_TACH2E 0x02 /* Tachometer 2 Enable */
+#define MAX31760_CR3_PSEN 0x04 /* Pulse Stretch Enable */
+#define MAX31760_CR3_TACHFL 0x08 /* Fan Fail When 100% Duty Cycle Only */
+#define MAX31760_CR3_RAMP 0x30 /* PWM Duty-Cycle Ramp Rate */
+#define MAX31760_RAMP_SLOW 0x00
+#define MAX31760_RAMP_SMED 0x10
+#define MAX31760_RAMP_MEDF 0x20
+#define MAX31760_RAMP_FAST MAX31760_CR3_RAMP
+#define MAX31760_CR3_FF_0 0x40 /* 0 Duty-Cycle Fan-Fail Detection */
+#define MAX31760_CR3_CLR_FF 0x80 /* Clear Fan Fail */
+#define MAX31760_REG_FFDC 0x03 /* Fan Fault Duty Cycle */
+#define MAX31760_REG_MASK 0x04 /* Alert Mask Register */
+#define MAX31760_MASK_TACH1AM 0x01 /* TACH1 Alarm Mask */
+#define MAX31760_MASK_TACH2AM 0x02 /* TACH2 Alarm Mask */
+#define MAX31760_MASK_ROTAM 0x04 /* Remote Overtemperature Alarm Mask */
+#define MAX31760_MASK_RHAM 0x08 /* Remote High Temperature Alarm Mask */
+#define MAX31760_MASK_LOTAM 0x10 /* Local Overtemperature Alarm Mask */
+#define MAX31760_MASK_LHAM 0x20 /* Local High Temperature Alarm Mask */
+#define MAX31760_REG_IFR 0x05 /* Ideality Factor Register */
+#define MAX31760_IFR_MASK 0x3f /* Mask for value of the IFR */
+#define MAX31760_REG_RHSH 0x06 /* Remote High Set-point MSB */
+#define MAX31760_REG_RHSL 0x07 /* Remote High Set-point LSB */
+#define MAX31760_REG_LOTSH 0x08 /* Local Overtemperature Set-point MSB */
+#define MAX31760_REG_LOTSL 0x09 /* Local Overtemperature Set-point LSB */
+#define MAX31760_REG_ROTSH 0x0a /* Remote Overtemperature Set-point MSB */
+#define MAX31760_REG_ROTSL 0x0b /* Remote Overtemperature Set-point LSB */
+#define MAX31760_REG_LHSH 0x0c /* Local High Set-point MSB */
+#define MAX31760_REG_LHSL 0x0d /* Local High Set-point LSB */
+#define MAX31760_REG_TCTH 0x0e /* TACH Count Threshold Register, MSB */
+#define MAX31760_REG_TCTL 0x0f /* TACH Count Threshold Register, LSB */
+#define MAX31760_REG_USER 0x10 /* 8 bytes General Purpose User Memory */
+#define MAX31760_REG_USER0 0x10 /* Custom Control Register USER0 */
+#define MAX31760_USER0_PULSE1 0x07 /* Fan1 Pulses per revolution */
+#define MAX31760_USER0_PULSE2 0x38 /* Fan2 Pulses per revolution */
+#define MAX31760_REG_LUT 0x20 /* 48-Byte Lookup Table (LUT) */
+#define MAX31760_LUT_COUNT 48
+#define MAX31760_REG_PWMR 0x50 /* Direct Duty-Cycle Control Register */
+
+#define MAX31760_REG_PWMV 0x51 /* Current PWM Duty-Cycle Register */
+#define MAX31760_REG_TC1H 0x52 /* TACH1 Count Register, MSB */
+#define MAX31760_REG_TC1L 0x53 /* TACH1 Count Register, LSB */
+#define MAX31760_REG_TC2H 0x54 /* TACH2 Count Register, MSB */
+#define MAX31760_REG_TC2L 0x55 /* TACH2 Count Register, LSB */
+#define MAX31760_REG_RTH 0x56 /* Remote Temperature Reading Register, MSB */
+#define MAX31760_REG_RTL 0x57 /* Remote Temperature Reading Register, LSB */
+#define MAX31760_REG_LTH 0x58 /* Local Temperature Reading Register, MSB */
+#define MAX31760_REG_LTL 0x59 /* Local Temperature Reading Register, LSB */
+#define MAX31760_REG_SR 0x5a /* Status Register */
+#define MAX31760_SR_TACH1A 0x01 /* TACH1 Alarm */
+#define MAX31760_SR_TACH2A 0x02 /* TACH2 Alarm */
+#define MAX31760_SR_ROTA 0x04 /* Remote Overtemperature Alarm */
+#define MAX31760_SR_RHA 0x08 /* Remote High Temperature Alarm */
+#define MAX31760_SR_LOTA 0x10 /* Local Overtemperature Alarm */
+#define MAX31760_SR_LHA 0x20 /* Local High Temperature Alarm */
+#define MAX31760_SR_RDFA 0x40 /* Remote Diode Fault Alarm */
+#define MAX31760_SR_PC 0x80 /* Program Corrupt Bit */
+
+#define MAX31760_REG_EEX 0x5b /* Load EEPROM to RAM; Write RAM to EEPROM */
+#define MAX31760_EEX_LW 0x80 /* Load from or write to EEPROM */
+#define MAX31760_EEX_BLKS 0x1F /* Blocks to load/write */
+
+#define MAX31760_TEMP_MIN_MC -40000 /* Minimum Millicelcius */
+#define MAX31760_TEMP_MAX_MC 127875 /* Maximum Millicelcius */
+#define MAX31760_TEMP_HIGH_HYST 1000 /* 1C hysteresis on high temp alarms. */
+#define MAX31760_TEMP_OVER_HYST 10000 /* 10C hysteresis on over temp alarms. */
+#define MAX31760_LUT_HYST_CLEAR 2000 /* LUT hysteresis: bit clear. */
+#define MAX31760_LYT_HYST_THRESH 3000 /* LUT hysteresis: store threshold. */
+#define MAX31760_LUT_HYST_SET 4000 /* LUT hysteresis: bit set. */
+
+#define MAX31760_NUM_TEMPS 2 /* Number of temperature sensors. */
+#define MAX31760_NUM_FANS 2 /* Number of fans. */
+#define MAX31760_FAN_PULSES_DEF 2 /* Default number of fan pulses. */
+#define MAX31760_FAN_PULSES_MAX 8 /* Maximum number of fan pulses. */
+#define MAX31760_PWM_ENABLE_FULL 0 /* pwmX_enable: Set PWM at full power. */
+#define MAX31760_PWM_ENABLE_MANUAL 1 /* pwmX_enable: Set manual mode. */
+#define MAX31760_PWM_ENABLE_AUTO 2 /* pwmX_enable: Set automatic mode. */
+
+#define MAX31760_LUT_AUTO_ATTRS 3 /* Number of LUT auto-point attributes. */
+#define MAX31760_LUT_AUTO_ATTR_COUNT (MAX31760_LUT_COUNT * \
+ MAX31760_LUT_AUTO_ATTRS)
+#define MAX31760_LUT_NAME_SIZE 32 /* Fit: pwm1_auto_pointXX_temp_hyst\0. */
+
+/*
+ * struct max31760_dev_attr - for generated device attributes
+ * @sdattr: Sensor device attribute.
+ * @name: Name of this attribute.
+ */
+struct max31760_dev_attr {
+ struct sensor_device_attribute sdattr;
+ char name[MAX31760_LUT_NAME_SIZE];
+};
+
+/*
+ * struct max31760 - device data
+ * @regmap: Register map.
+ * @fan_pulses: Quick access to the number of fan tach pulses (per fan).
+ * @fan_label: Labels for the fans if provided in open firmware.
+ * @temp_label: Labels for the temperature sensors if provided in open firmware.
+ * @lut_dev_attrs:
+ * Device attributes for the temperature to PWM lookup table.
+ * @lut_attrs: Pointers to the struct attribute in each lut_dev_attr.
+ * @lut_group: Attribute group for the LUT attributes.
+ * @attr_groups:Sysfs attribute groups for this device.
+ */
+struct max31760 {
+ struct regmap *regmap;
+ int fan_pulses[MAX31760_NUM_FANS];
+ const char *fan_label[MAX31760_NUM_FANS];
+ const char *temp_label[MAX31760_NUM_TEMPS];
+ struct max31760_dev_attr lut_dev_attrs[MAX31760_LUT_AUTO_ATTR_COUNT];
+ struct attribute *lut_attrs[MAX31760_LUT_AUTO_ATTR_COUNT + 1];
+ struct attribute_group lut_group;
+ const struct attribute_group *attr_groups[4];
+};
+
+static bool max31760_readable_reg(struct device *dev, unsigned int reg)
+{
+ return reg != MAX31760_REG_EEX;
+}
+
+static bool max31760_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MAX31760_REG_PWMV ... MAX31760_REG_SR:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static bool max31760_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MAX31760_REG_MASK:
+ case MAX31760_REG_PWMR ... MAX31760_REG_EEX:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config max31760_regmap_config = {
+ /*
+ * Device has an EEPROM to store the register values, so don't define
+ * reg_defaults: read the current values from the hardware.
+ */
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = MAX31760_REG_EEX,
+ .writeable_reg = max31760_writeable_reg,
+ .readable_reg = max31760_readable_reg,
+ .volatile_reg = max31760_volatile_reg,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ .cache_type = REGCACHE_RBTREE,
+ .use_single_rw = true,
+};
+
+/* Convert 11-bit MAX31760 register value to milliCelsius */
+static inline int max31760_temp_reg_to_mC(s16 val)
+{
+ return (val & ~0x0f) * 1000 / 256;
+}
+
+/* Convert milliCelsius to left adjusted 11-bit MAX31760 register value */
+static inline u16 max31760_mC_to_temp_reg(int val)
+{
+ return (val * 256) / 1000;
+}
+
+/* Convert tachometer value to RPM. */
+static inline long max31760_rpm_from_tach(u16 tach_count, int pulses)
+{
+ return 60L * 100000L / (long)tach_count / (long)pulses;
+}
+
+/* Convert RPM to tachometer value. */
+static inline u16 max31760_tach_from_rpm(long rpm, int pulses)
+{
+ long tach = 60L * 100000L / rpm / (long)pulses;
+
+ if (tach < 0)
+ tach = 0;
+ else if (tach > (long)USHRT_MAX)
+ tach = USHRT_MAX;
+
+ return tach;
+}
+
+/*
+ * Read two subsequent registers into a 16-bit word, treating the first as the
+ * most significant byte.
+ */
+static int max31760_read_word(struct regmap *regmap, unsigned int regmsb,
+ u16 *word)
+{
+ int err;
+ unsigned int msb_val;
+ unsigned int lsb_val;
+
+ err = regmap_read(regmap, regmsb, &msb_val);
+ if (err < 0)
+ return err;
+ err = regmap_read(regmap, regmsb + 1, &lsb_val);
+ if (err < 0)
+ return err;
+
+ *word = ((msb_val << 8) & 0xff00) | (lsb_val & 0xff);
+ return 0;
+}
+
+/*
+ * Write a 16-bit word into two subsequent registers, treating the first as the
+ * most significant byte.
+ */
+static int max31760_write_word(struct regmap *regmap, unsigned int regmsb,
+ u16 word)
+{
+ int err;
+ unsigned int val;
+
+ val = (word >> 8) & 0xff;
+ err = regmap_write(regmap, regmsb, val);
+ if (err < 0)
+ return err;
+
+ val = word & 0xff;
+ return regmap_write(regmap, regmsb + 1, val);
+}
+
+/*
+ * Read an alarm which may be flagged in the status register, or masked in the
+ * alarm mask register. Reading from the status register will cause the bit in
+ * the mask register to be set.
+ */
+static int max31760_read_alarm(struct device *dev, unsigned int srflag,
+ unsigned int maskflag, long *val)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ unsigned int srval;
+ unsigned int maskval;
+ int err;
+
+ err = regmap_read(max31760->regmap, MAX31760_REG_SR, &srval);
+ if (err < 0)
+ return err;
+ err = regmap_read(max31760->regmap, MAX31760_REG_MASK, &maskval);
+ if (err < 0)
+ return err;
+
+ *val = !!((srval & srflag) | (maskval & maskflag));
+ return 0;
+}
+
+static int max31760_read_temp(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ unsigned int reg;
+ unsigned int regval;
+ unsigned int srflag;
+ unsigned int maskflag;
+ u16 temp;
+ int err;
+ int hyst = 0;
+
+ switch (attr) {
+ case hwmon_temp_emergency_hyst:
+ hyst = MAX31760_TEMP_OVER_HYST;
+ /* fallthrough */
+ case hwmon_temp_max_hyst:
+ if (attr == hwmon_temp_max_hyst)
+ hyst = MAX31760_TEMP_HIGH_HYST;
+ /* fallthrough */
+ case hwmon_temp_input:
+ case hwmon_temp_max:
+ case hwmon_temp_emergency:
+ switch (attr) {
+ case hwmon_temp_input:
+ reg = channel ? MAX31760_REG_RTH : MAX31760_REG_LTH;
+ break;
+ case hwmon_temp_max_hyst:
+ case hwmon_temp_max:
+ reg = channel ? MAX31760_REG_RHSH : MAX31760_REG_LHSH;
+ break;
+ case hwmon_temp_emergency_hyst:
+ case hwmon_temp_emergency:
+ reg = channel ? MAX31760_REG_ROTSH : MAX31760_REG_LOTSH;
+ break;
+ }
+ err = max31760_read_word(max31760->regmap, reg, &temp);
+ if (err < 0)
+ return err;
+ *val = max31760_temp_reg_to_mC(temp) - hyst;
+ break;
+ case hwmon_temp_max_alarm:
+ case hwmon_temp_emergency_alarm:
+ switch (attr) {
+ case hwmon_temp_max_alarm:
+ srflag = channel ? MAX31760_SR_RHA : MAX31760_SR_LHA;
+ maskflag = channel ? MAX31760_MASK_RHAM :
+ MAX31760_MASK_LHAM;
+ break;
+ case hwmon_temp_emergency_alarm:
+ srflag = channel ? MAX31760_SR_ROTA : MAX31760_SR_LOTA;
+ maskflag = channel ? MAX31760_MASK_RHAM :
+ MAX31760_MASK_LHAM;
+ break;
+ }
+ return max31760_read_alarm(dev, srflag, maskflag, val);
+ case hwmon_temp_fault:
+ err = regmap_read(max31760->regmap, MAX31760_REG_SR, ®val);
+ if (err < 0)
+ return err;
+ *val = !!(regval & MAX31760_SR_RDFA);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int max31760_read_fan(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ u16 tach_count;
+ unsigned int reg;
+ unsigned int regval;
+ unsigned int srflag;
+ unsigned int maskflag;
+ int err;
+
+ switch (attr) {
+ case hwmon_fan_input:
+ case hwmon_fan_min:
+ switch (attr) {
+ case hwmon_fan_input:
+ reg = channel ? MAX31760_REG_TC2H : MAX31760_REG_TC1H;
+ break;
+ case hwmon_fan_min:
+ reg = MAX31760_REG_TCTH;
+ break;
+ }
+ err = max31760_read_word(max31760->regmap, reg, &tach_count);
+ if (err)
+ return err;
+ *val = max31760_rpm_from_tach(tach_count,
+ max31760->fan_pulses[channel]);
+ break;
+ case hwmon_fan_fault:
+ /* TODO: Read FF/FS GPIO input when available. */
+ /* fallthrough */
+ case hwmon_fan_min_alarm:
+ srflag = channel ? MAX31760_SR_TACH2A : MAX31760_SR_TACH1A;
+ maskflag = channel ? MAX31760_MASK_TACH2AM :
+ MAX31760_MASK_TACH1AM;
+ return max31760_read_alarm(dev, srflag, maskflag, val);
+ case hwmon_fan_pulses:
+ err = regmap_read(max31760->regmap, MAX31760_REG_USER0,
+ ®val);
+ if (err)
+ return err;
+ if (channel)
+ *val = (regval & MAX31760_USER0_PULSE2) >> 3;
+ else
+ *val = regval & MAX31760_USER0_PULSE1;
+ if (*val == 0)
+ *val = MAX31760_FAN_PULSES_DEF;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ return 0;
+}
+
+static int max31760_read_pwm(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ unsigned int regval;
+ int err;
+
+ switch (attr) {
+ case hwmon_pwm_input:
+ /* Note that this is the current value, not the value stored to
+ * the duty-cycle register.
+ */
+ err = regmap_read(max31760->regmap, MAX31760_REG_PWMV, ®val);
+ if (err)
+ return err;
+ *val = regval;
+ break;
+ case hwmon_pwm_enable:
+ err = regmap_read(max31760->regmap, MAX31760_REG_CR2, ®val);
+ if (err)
+ return err;
+ if (regval & MAX31760_CR2_DFC)
+ *val = MAX31760_PWM_ENABLE_MANUAL;
+ else
+ *val = MAX31760_PWM_ENABLE_AUTO;
+ break;
+ case hwmon_pwm_freq:
+ err = regmap_read(max31760->regmap, MAX31760_REG_CR1, ®val);
+ if (err)
+ return err;
+ switch (regval & MAX31760_CR1_DRV) {
+ case MAX31760_DRV_33HZ:
+ default:
+ *val = 33;
+ break;
+ case MAX31760_DRV_150HZ:
+ *val = 150;
+ break;
+ case MAX31760_DRV_1500HZ:
+ *val = 1500;
+ break;
+ case MAX31760_DRV_25KHZ:
+ *val = 25000;
+ break;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int max31760_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ switch (type) {
+ case hwmon_temp:
+ return max31760_read_temp(dev, attr, channel, val);
+ case hwmon_fan:
+ return max31760_read_fan(dev, attr, channel, val);
+ case hwmon_pwm:
+ return max31760_read_pwm(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int max31760_read_string(struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel, const char **str)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_temp:
+ if (attr != hwmon_temp_label)
+ return -EOPNOTSUPP;
+ *str = max31760->temp_label[channel];
+ break;
+ case hwmon_fan:
+ if (attr != hwmon_fan_label)
+ return -EOPNOTSUPP;
+ *str = max31760->fan_label[channel];
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+/* Write a temperature to the two adjacent registers starting at regmsb. */
+static int max31760_write_temp_reg(struct regmap *regmap,
+ unsigned int regmsb, long temp)
+{
+ u16 word;
+
+ temp = clamp_val(temp, MAX31760_TEMP_MIN_MC, MAX31760_TEMP_MAX_MC);
+ word = max31760_mC_to_temp_reg(temp);
+
+ return max31760_write_word(regmap, regmsb, word);
+}
+
+static int max31760_write_temp(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+
+ switch (attr) {
+ case hwmon_temp_max:
+ return max31760_write_temp_reg(max31760->regmap,
+ channel ? MAX31760_REG_RHSH :
+ MAX31760_REG_LHSH,
+ val);
+ case hwmon_temp_emergency:
+ return max31760_write_temp_reg(max31760->regmap,
+ channel ? MAX31760_REG_ROTSH :
+ MAX31760_REG_LOTSH,
+ val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+/* Update the quick access fan pulses value. */
+static void max31760_update_fan_pulses(struct max31760 *max31760, int channel,
+ int pulses)
+{
+ if (pulses > MAX31760_FAN_PULSES_MAX)
+ pulses = MAX31760_FAN_PULSES_MAX;
+ else if (pulses <= 0)
+ pulses = MAX31760_FAN_PULSES_DEF;
+ max31760->fan_pulses[channel] = pulses;
+}
+
+static int max31760_write_fan(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ unsigned int regval;
+ unsigned int mask;
+ u16 tach;
+
+ switch (attr) {
+ case hwmon_fan_min:
+ tach = max31760_tach_from_rpm(val,
+ max31760->fan_pulses[channel]);
+ return max31760_write_word(max31760->regmap, MAX31760_REG_TCTH,
+ tach);
+ case hwmon_fan_pulses:
+ max31760_update_fan_pulses(max31760, channel, val);
+ regval = (unsigned int)max31760->fan_pulses[channel];
+ if (channel) {
+ regval <<= 3;
+ mask = MAX31760_USER0_PULSE2;
+ } else {
+ mask = MAX31760_USER0_PULSE1;
+ }
+ return regmap_update_bits(max31760->regmap, MAX31760_REG_USER0,
+ mask, regval);
+ default:
+ return -EOPNOTSUPP;
+ }
+ return 0;
+}
+
+static int max31760_write_pwm(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ unsigned int regval;
+ int err;
+
+ switch (attr) {
+ case hwmon_pwm_input:
+ regval = (unsigned int)val & 0xff;
+ return regmap_write(max31760->regmap, MAX31760_REG_PWMR,
+ regval);
+ case hwmon_pwm_enable:
+ switch (val) {
+ case MAX31760_PWM_ENABLE_FULL:
+ err = regmap_write(max31760->regmap, MAX31760_REG_PWMR,
+ 0xff);
+ if (err)
+ return err;
+ /* fallthrough */
+ case MAX31760_PWM_ENABLE_MANUAL:
+ return regmap_update_bits(max31760->regmap,
+ MAX31760_REG_CR2,
+ MAX31760_CR2_DFC,
+ MAX31760_CR2_DFC);
+ default:
+ case MAX31760_PWM_ENABLE_AUTO:
+ return regmap_update_bits(max31760->regmap,
+ MAX31760_REG_CR2,
+ MAX31760_CR2_DFC, 0);
+ }
+ break;
+ case hwmon_pwm_freq:
+ if (val < 91)
+ regval = MAX31760_DRV_33HZ;
+ else if (val < 825)
+ regval = MAX31760_DRV_150HZ;
+ else if (val < 11000)
+ regval = MAX31760_DRV_1500HZ;
+ else
+ regval = MAX31760_DRV_25KHZ;
+ return regmap_update_bits(max31760->regmap, MAX31760_REG_CR1,
+ MAX31760_CR1_DRV, regval);
+ }
+ return 0;
+}
+
+static int max31760_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ switch (type) {
+ case hwmon_temp:
+ return max31760_write_temp(dev, attr, channel, val);
+ case hwmon_fan:
+ return max31760_write_fan(dev, attr, channel, val);
+ case hwmon_pwm:
+ return max31760_write_pwm(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t max31760_is_visible(const void *dvrdata,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ struct max31760 *max31760 = (struct max31760 *)dvrdata;
+
+ switch (type) {
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_input:
+ case hwmon_temp_max_hyst:
+ case hwmon_temp_max_alarm:
+ case hwmon_temp_emergency_hyst:
+ case hwmon_temp_emergency_alarm:
+ case hwmon_temp_fault:
+ return 0444;
+ case hwmon_temp_label:
+ if (max31760->temp_label[channel])
+ return 0444;
+ return 0;
+ case hwmon_temp_max:
+ case hwmon_temp_emergency:
+ return 0644;
+ }
+ break;
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_input:
+ case hwmon_fan_fault:
+ case hwmon_fan_min_alarm:
+ return 0444;
+ case hwmon_fan_label:
+ if (max31760->fan_label[channel])
+ return 0444;
+ return 0;
+ case hwmon_fan_min:
+ case hwmon_fan_pulses:
+ return 0644;
+ }
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ case hwmon_pwm_enable:
+ case hwmon_pwm_freq:
+ return 0644;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static u32 max31760_chip_config[] = {
+ HWMON_C_REGISTER_TZ,
+ 0
+};
+
+static const struct hwmon_channel_info max31760_chip = {
+ .type = hwmon_chip,
+ .config = max31760_chip_config,
+};
+
+static u32 max31760_temp_config[] = {
+ /*
+ * Local temperature sensor:
+ * Local high set point (LHS) -> MAX,
+ * Local over-temperature set point (LOTS) -> EMERGENCY
+ * There is no fault flag for this temperature sensor.
+ */
+ HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MAX | HWMON_T_EMERGENCY |
+ HWMON_T_MAX_ALARM | HWMON_T_EMERGENCY_ALARM |
+ HWMON_T_MAX_HYST | HWMON_T_EMERGENCY_HYST,
+ /*
+ * Remote temperature sensor:
+ * Remote high set point (RHS) -> MAX,
+ * Remote over-temperature set point (ROTS) -> EMERGENCY
+ */
+ HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MAX | HWMON_T_EMERGENCY |
+ HWMON_T_MAX_ALARM | HWMON_T_EMERGENCY_ALARM |
+ HWMON_T_MAX_HYST | HWMON_T_EMERGENCY_HYST | HWMON_T_FAULT,
+ 0
+};
+
+static const struct hwmon_channel_info max31760_temp = {
+ .type = hwmon_temp,
+ .config = max31760_temp_config,
+};
+
+static u32 max31760_fan_config[] = {
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MIN_ALARM | HWMON_F_PULSES |
+ HWMON_F_FAULT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MIN_ALARM | HWMON_F_PULSES |
+ HWMON_F_FAULT | HWMON_F_LABEL,
+ 0
+};
+
+static const struct hwmon_channel_info max31760_fan = {
+ .type = hwmon_fan,
+ .config = max31760_fan_config,
+};
+
+static u32 max31760_pwm_config[] = {
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ,
+ 0
+};
+
+static const struct hwmon_channel_info max31760_pwm = {
+ .type = hwmon_pwm,
+ .config = max31760_pwm_config,
+};
+
+static const struct hwmon_channel_info *max31760_info[] = {
+ &max31760_chip,
+ &max31760_temp,
+ &max31760_fan,
+ &max31760_pwm,
+ NULL
+};
+
+static const struct hwmon_ops max31760_hwmon_ops = {
+ .is_visible = max31760_is_visible,
+ .read = max31760_read,
+ .read_string = max31760_read_string,
+ .write = max31760_write,
+};
+
+static const struct hwmon_chip_info max31760_chip_info = {
+ .ops = &max31760_hwmon_ops,
+ .info = max31760_info,
+};
+
+/* Show which temperature sensors are used to drive the PWM lookup table. */
+static ssize_t max31760_pwm_auto_channels_temp_show(
+ struct device *dev, struct device_attribute *devattr, char *buf)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ unsigned int regval;
+ int channels;
+ int err;
+
+ err = regmap_read(max31760->regmap, MAX31760_REG_CR1, ®val);
+ if (err < 0)
+ return err;
+
+ /*
+ * Auto channels is a bit-field. TIS bit clear: temp1 (local) is used
+ * for the LUT. TIS bit set: temp2 (remote) is used for the LUT.
+ * MTI bit set: maximum temp from both is used, TIS bit is ignored.
+ */
+ if (regval & MAX31760_CR1_MTI)
+ channels = 3;
+ else if (regval & MAX31760_CR1_TIS)
+ channels = 2;
+ else
+ channels = 1;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", channels);
+}
+
+/* Store which temperature sensors are used to drive the PWM lookup table. */
+static ssize_t max31760_pwm_auto_channels_temp_store(
+ struct device *dev, struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ unsigned int regval;
+ unsigned int mask;
+ unsigned long channels;
+ int err;
+
+ err = kstrtoul(buf, 10, &channels);
+ if (err < 0)
+ return err;
+
+ switch (channels & 0x3) {
+ case 3:
+ mask = MAX31760_CR1_MTI;
+ regval = MAX31760_CR1_MTI;
+ break;
+ case 1:
+ mask = MAX31760_CR1_TIS | MAX31760_CR1_MTI;
+ regval = 0;
+ break;
+ default:
+ case 2:
+ mask = MAX31760_CR1_TIS | MAX31760_CR1_MTI;
+ regval = MAX31760_CR1_TIS;
+ break;
+ }
+
+ err = regmap_update_bits(max31760->regmap, MAX31760_REG_CR1, mask,
+ regval);
+ if (err < 0)
+ return err;
+
+ return count;
+}
+
+/* Show the PWM value at a lookup table index. */
+static ssize_t max31760_pwm_auto_point_pwm_show(
+ struct device *dev, struct device_attribute *devattr, char *buf)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ struct sensor_device_attribute *sensor_dev_attr =
+ to_sensor_dev_attr(devattr);
+ unsigned int reg = MAX31760_REG_LUT + sensor_dev_attr->index;
+ unsigned int regval;
+ int err;
+
+ err = regmap_read(max31760->regmap, reg, ®val);
+ if (err < 0)
+ return err;
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", regval);
+}
+
+/* Store the PWM value at a lookup table index. */
+static ssize_t max31760_pwm_auto_point_pwm_store(
+ struct device *dev, struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ struct sensor_device_attribute *sensor_dev_attr =
+ to_sensor_dev_attr(devattr);
+ unsigned int reg = MAX31760_REG_LUT + sensor_dev_attr->index;
+ unsigned int regval;
+ unsigned long pwm;
+ int err;
+
+ err = kstrtoul(buf, 10, &pwm);
+ if (err < 0)
+ return err;
+ regval = pwm & 0xff;
+
+ err = regmap_write(max31760->regmap, reg, regval);
+ if (err < 0)
+ return err;
+
+ return count;
+}
+
+/* Returns the temperature for the given PWM lookup table index. */
+static int max31760_pwm_auto_point_temp(int index)
+{
+ if (index == 0)
+ return MAX31760_TEMP_MIN_MC;
+ else
+ return (16 + index * 2) * 1000;
+}
+
+/* Show the temperature for a PWM lookup table index. */
+static ssize_t max31760_pwm_auto_point_temp_show(
+ struct device *dev, struct device_attribute *devattr, char *buf)
+{
+ struct sensor_device_attribute *sensor_dev_attr =
+ to_sensor_dev_attr(devattr);
+ int temp = max31760_pwm_auto_point_temp(sensor_dev_attr->index);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", temp);
+}
+
+/* Show the temperature hysteresis for a PWM lookup table index. */
+static ssize_t max31760_pwm_auto_point_temp_hyst_show(
+ struct device *dev, struct device_attribute *devattr, char *buf)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ struct sensor_device_attribute *sensor_dev_attr =
+ to_sensor_dev_attr(devattr);
+ int temp = max31760_pwm_auto_point_temp(sensor_dev_attr->index);
+ unsigned int regval;
+ int err;
+
+ err = regmap_read(max31760->regmap, MAX31760_REG_CR1, ®val);
+ if (err < 0)
+ return err;
+
+ if (regval & MAX31760_CR1_HYST)
+ temp -= MAX31760_LUT_HYST_SET;
+ else
+ temp -= MAX31760_LUT_HYST_CLEAR;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", temp);
+}
+
+/* Store the temperature hysteresis for a PWM lookup table index. */
+static ssize_t max31760_pwm_auto_point_temp_hyst_store(
+ struct device *dev, struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ struct sensor_device_attribute *sensor_dev_attr =
+ to_sensor_dev_attr(devattr);
+ int temp = max31760_pwm_auto_point_temp(sensor_dev_attr->index);
+ unsigned int regval;
+ long hyst;
+ int err;
+
+ err = kstrtol(buf, 10, &hyst);
+ if (err < 0)
+ return err;
+
+ temp -= hyst;
+ if (temp >= MAX31760_LYT_HYST_THRESH)
+ regval = MAX31760_CR1_HYST;
+ else
+ regval = 0;
+ err = regmap_update_bits(max31760->regmap, MAX31760_REG_CR1,
+ MAX31760_CR1_HYST, regval);
+ if (err < 0)
+ return err;
+ return count;
+}
+
+static SENSOR_DEVICE_ATTR(pwm1_auto_channels_temp, 0644,
+ max31760_pwm_auto_channels_temp_show,
+ max31760_pwm_auto_channels_temp_store, 0);
+static struct attribute *max31760_attrs[] = {
+ &sensor_dev_attr_pwm1_auto_channels_temp.dev_attr.attr,
+ NULL
+};
+static const struct attribute_group max31760_group = {
+ .attrs = max31760_attrs,
+};
+
+/* Writes to the 'control' attribute. */
+static ssize_t max31760_control_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ int err;
+
+ if (sysfs_streq(buf, "reset")) {
+ err = regmap_update_bits(max31760->regmap, MAX31760_REG_CR1,
+ MAX31760_CR1_POR, MAX31760_CR1_POR);
+ if (err < 0)
+ return err;
+ } else if (sysfs_streq(buf, "clearff")) {
+ err = regmap_update_bits(max31760->regmap, MAX31760_REG_CR3,
+ MAX31760_CR3_CLR_FF,
+ MAX31760_CR3_CLR_FF);
+ if (err < 0)
+ return err;
+ } else {
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+/* Writes to the 'eeprom_read' attribute. */
+static ssize_t max31760_eeprom_read_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ unsigned long sections;
+ unsigned int regval;
+ int err;
+
+ err = kstrtoul(buf, 10, §ions);
+ if (err < 0)
+ return err;
+
+ if (sections == 0)
+ regval = MAX31760_EEX_BLKS;
+ else
+ regval = sections & MAX31760_EEX_BLKS;
+ regval |= MAX31760_EEX_LW;
+
+ err = regmap_write(max31760->regmap, MAX31760_REG_EEX, regval);
+ if (err < 0)
+ return err;
+
+ return count;
+}
+
+/* Writes to the 'eeprom_write' attribute. */
+static ssize_t max31760_eeprom_write_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ unsigned long sections;
+ unsigned int regval;
+ int err;
+
+ err = kstrtoul(buf, 10, §ions);
+ if (err < 0)
+ return err;
+
+ if (sections == 0)
+ regval = MAX31760_EEX_BLKS;
+ else
+ regval = sections & MAX31760_EEX_BLKS;
+
+ err = regmap_write(max31760->regmap, MAX31760_REG_EEX, regval);
+ if (err < 0)
+ return err;
+
+ return count;
+}
+
+/* Reads from the 'pwmX_fan_fault' attribute. */
+static ssize_t max31760_pwm_fan_fault_show(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ unsigned int regval;
+ int err;
+
+ err = regmap_read(max31760->regmap, MAX31760_REG_FFDC, ®val);
+ if (err < 0)
+ return err;
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", regval);
+}
+
+/* Writes to the 'pwmX_fan_fault' attribute. */
+static ssize_t max31760_pwm_fan_fault_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ unsigned int regval;
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err)
+ return err;
+ regval = val & 0xff;
+
+ err = regmap_write(max31760->regmap, MAX31760_REG_FFDC, regval);
+ if (err < 0)
+ return err;
+ return count;
+}
+
+/* Reads from the 'pwmX_ramp_rate' attribute. */
+static ssize_t max31760_pwm_ramp_rate_show(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ unsigned int regval;
+ int err;
+
+ err = regmap_read(max31760->regmap, MAX31760_REG_CR3, ®val);
+ if (err < 0)
+ return err;
+ switch (regval & MAX31760_CR3_RAMP) {
+ case MAX31760_RAMP_SLOW:
+ regval = 8;
+ break;
+ case MAX31760_RAMP_SMED:
+ regval = 16;
+ break;
+ case MAX31760_RAMP_MEDF:
+ regval = 32;
+ break;
+ case MAX31760_RAMP_FAST:
+ regval = 255;
+ break;
+ }
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", regval);
+}
+
+/* Writes to the 'pwmX_ramp_rate' attribute. */
+static ssize_t max31760_pwm_ramp_rate_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ unsigned int regval;
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err)
+ return err;
+ if (val <= 12)
+ regval = MAX31760_RAMP_SLOW;
+ else if (val <= 24)
+ regval = MAX31760_RAMP_SMED;
+ else if (val <= 143)
+ regval = MAX31760_RAMP_MEDF;
+ else
+ regval = MAX31760_RAMP_FAST;
+
+ err = regmap_update_bits(max31760->regmap, MAX31760_REG_CR3,
+ MAX31760_CR3_RAMP, regval);
+ if (err < 0)
+ return err;
+ return count;
+}
+
+static SENSOR_DEVICE_ATTR(control, 0200, NULL, max31760_control_store, 0);
+static SENSOR_DEVICE_ATTR(eeprom_read, 0200, NULL, max31760_eeprom_read_store,
+ 0);
+static SENSOR_DEVICE_ATTR(eeprom_write, 0200, NULL, max31760_eeprom_write_store,
+ 0);
+static SENSOR_DEVICE_ATTR(pwm1_fan_fault, 0644, max31760_pwm_fan_fault_show,
+ max31760_pwm_fan_fault_store, 0);
+static SENSOR_DEVICE_ATTR(pwm1_ramp_rate, 0644, max31760_pwm_ramp_rate_show,
+ max31760_pwm_ramp_rate_store, 0);
+
+static struct attribute *max31760_custom_attrs[] = {
+ &sensor_dev_attr_control.dev_attr.attr,
+ &sensor_dev_attr_eeprom_read.dev_attr.attr,
+ &sensor_dev_attr_eeprom_write.dev_attr.attr,
+ &sensor_dev_attr_pwm1_fan_fault.dev_attr.attr,
+ &sensor_dev_attr_pwm1_ramp_rate.dev_attr.attr,
+ NULL
+};
+static const struct attribute_group max31760_custom_group = {
+ .name = "custom",
+ .attrs = max31760_custom_attrs,
+};
+
+/* Generate auto_point sensor attributes. */
+static void max31760_setup_attr_groups(struct device *dev)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ struct max31760_dev_attr *lut_dev_attr = max31760->lut_dev_attrs;
+ struct device_attribute *dev_attr;
+ int attr_index = 0;
+ int i;
+
+ for (i = 0; i < MAX31760_LUT_COUNT; i++, lut_dev_attr++) {
+ snprintf(lut_dev_attr->name, MAX31760_LUT_NAME_SIZE,
+ "pwm1_auto_point%02d_pwm", i);
+ lut_dev_attr->sdattr.index = i;
+ dev_attr = &lut_dev_attr->sdattr.dev_attr;
+ dev_attr->attr.name = lut_dev_attr->name;
+ dev_attr->attr.mode = 0644;
+ dev_attr->show = max31760_pwm_auto_point_pwm_show;
+ dev_attr->store = max31760_pwm_auto_point_pwm_store;
+ max31760->lut_attrs[attr_index++] =
+ &lut_dev_attr->sdattr.dev_attr.attr;
+ }
+
+ for (i = 0; i < MAX31760_LUT_COUNT; i++, lut_dev_attr++) {
+ snprintf(lut_dev_attr->name, MAX31760_LUT_NAME_SIZE,
+ "pwm1_auto_point%02d_temp", i);
+ lut_dev_attr->sdattr.index = i;
+ dev_attr = &lut_dev_attr->sdattr.dev_attr;
+ dev_attr->attr.name = lut_dev_attr->name;
+ dev_attr->attr.mode = 0444;
+ dev_attr->show = max31760_pwm_auto_point_temp_show;
+ max31760->lut_attrs[attr_index++] =
+ &lut_dev_attr->sdattr.dev_attr.attr;
+ }
+
+ for (i = 0; i < MAX31760_LUT_COUNT; i++, lut_dev_attr++) {
+ snprintf(lut_dev_attr->name, MAX31760_LUT_NAME_SIZE,
+ "pwm1_auto_point%02d_temp_hyst", i);
+ lut_dev_attr->sdattr.index = i;
+ dev_attr = &lut_dev_attr->sdattr.dev_attr;
+ dev_attr->attr.name = lut_dev_attr->name;
+ dev_attr->attr.mode = 0644;
+ dev_attr->show = max31760_pwm_auto_point_temp_hyst_show;
+ dev_attr->store = max31760_pwm_auto_point_temp_hyst_store;
+ max31760->lut_attrs[attr_index++] =
+ &lut_dev_attr->sdattr.dev_attr.attr;
+ }
+
+ max31760->lut_group.attrs = max31760->lut_attrs;
+ max31760->attr_groups[0] = &max31760->lut_group;
+ max31760->attr_groups[1] = &max31760_group;
+ max31760->attr_groups[2] = &max31760_custom_group;
+}
+
+/* Update internal storage for the current register values. */
+static int max31760_update_from_registers(struct device *dev)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ long val;
+ int i;
+ int err;
+
+ for (i = 0; i < MAX31760_NUM_FANS; i++) {
+ err = max31760_read_fan(dev, hwmon_fan_pulses, i, &val);
+ if (err)
+ return err;
+ max31760->fan_pulses[i] = val;
+ }
+
+ /* Clear standby bit in case it is set. */
+ return regmap_update_bits(max31760->regmap, MAX31760_REG_CR2,
+ MAX31760_CR2_STBY, 0);
+}
+
+/* Configure registers which have associated device properties. */
+static int max31760_of_init(struct device *dev)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ struct device_node *node;
+ const char *label;
+ int err;
+ bool fan_enabled[MAX31760_NUM_FANS] = {0};
+ u32 reg;
+ u32 val;
+
+ for_each_available_child_of_node(dev->of_node, node) {
+ err = of_property_read_u32(node, "reg", ®);
+ if (err) {
+ dev_err(dev, "invalid reg on sub-node: %s", node->name);
+ return err;
+ }
+
+ if (!strcmp(node->name, "fan")) {
+ if (reg >= MAX31760_NUM_FANS) {
+ dev_err(dev, "invalid reg on fan: %s",
+ node->name);
+ return -EINVAL;
+ }
+
+ err = of_property_read_string(node, "label", &label);
+ if (!err)
+ max31760->fan_label[reg] = label;
+
+ fan_enabled[reg] = of_device_is_available(node);
+ } else if (!strcmp(node->name, "temp")) {
+ if (reg >= MAX31760_NUM_TEMPS) {
+ dev_err(dev, "invalid reg on fan: %s",
+ node->name);
+ return -EINVAL;
+ }
+
+ err = of_property_read_string(node, "label", &label);
+ if (!err)
+ max31760->temp_label[reg] = label;
+
+ err = of_property_read_u32(node, "ideality", &val);
+ if (reg == 1 && !err) {
+ /* Only external temp sensor has ideality. */
+ err = regmap_write(max31760->regmap,
+ MAX31760_REG_IFR,
+ val & 0x3f);
+ if (err)
+ return err;
+ }
+ } else {
+ dev_err(dev, "invalid subnode with name: %s",
+ node->name);
+ continue;
+ }
+ }
+
+ val = 0;
+ if (of_property_read_bool(dev->of_node, "maxim,pwm-polarity-negative"))
+ val |= MAX31760_CR1_PPS;
+ err = regmap_update_bits(max31760->regmap, MAX31760_REG_CR1,
+ MAX31760_CR1_PPS, val);
+ if (err)
+ return err;
+
+ /* Put ALERT pin into comparator mode: interrupts aren't supported. */
+ val = MAX31760_CR2_ALERTS | MAX31760_CR2_FFMODE;
+ if (of_property_read_bool(dev->of_node, "maxim,fan-spin-up-enabled"))
+ val |= MAX31760_CR2_SPEN;
+ if (of_property_read_bool(dev->of_node, "maxim,fan-rd-signal"))
+ val |= MAX31760_CR2_FSST;
+ if (of_property_read_bool(dev->of_node, "maxim,fan-rd-polarity-high"))
+ val |= MAX31760_CR2_RDPS;
+ if (of_property_read_bool(dev->of_node, "maxim,fan-signal-enabled"))
+ val |= MAX31760_CR2_FSEN;
+ /*
+ * Firmware configuration parameters planned:
+ * maxim,fan-fail-interrupt -> remove MAX31760_CR2_FFMODE
+ * maxim,temp-alert-interrupt -> remove MAX31760_CR2_ALERTS
+ */
+
+ err = regmap_update_bits(max31760->regmap, MAX31760_REG_CR2,
+ MAX31760_CR2_SPEN | MAX31760_CR2_FSST |
+ MAX31760_CR2_RDPS | MAX31760_CR2_FSEN |
+ MAX31760_CR2_ALERTS | MAX31760_CR2_FFMODE,
+ val);
+ if (err)
+ return err;
+
+ val = (fan_enabled[0] ? MAX31760_CR3_TACH1E : 0) |
+ (fan_enabled[1] ? MAX31760_CR3_TACH2E : 0);
+ if (of_property_read_bool(dev->of_node, "maxim,fan-fail-full-only"))
+ val |= MAX31760_CR3_TACHFL;
+ if (of_property_read_bool(dev->of_node,
+ "maxim,pwm-pulse-stretch-enabled"))
+ val |= MAX31760_CR3_PSEN;
+ if (of_property_read_bool(dev->of_node, "maxim,pwm-zero-fan-can-fail"))
+ val |= MAX31760_CR3_FF_0;
+
+ return regmap_update_bits(max31760->regmap, MAX31760_REG_CR3,
+ MAX31760_CR3_TACH1E | MAX31760_CR3_TACH2E |
+ MAX31760_CR3_TACHFL | MAX31760_CR3_PSEN |
+ MAX31760_CR3_FF_0, val);
+}
+
+static int max31760_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct device *hwmon_dev;
+ struct max31760 *max31760;
+ int err;
+
+ max31760 = devm_kzalloc(dev, sizeof(*max31760), GFP_KERNEL);
+ if (!max31760)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, max31760);
+
+ max31760->regmap = devm_regmap_init_i2c(client,
+ &max31760_regmap_config);
+ if (IS_ERR(max31760->regmap)) {
+ err = PTR_ERR(max31760->regmap);
+ dev_err(dev, "regmap init failed: %d", err);
+ return err;
+ }
+
+ err = max31760_of_init(dev);
+ if (err) {
+ dev_err(dev, "failed to initialize from firmware: %d", err);
+ return err;
+ }
+
+ err = max31760_update_from_registers(dev);
+ if (err) {
+ dev_err(dev, "failed to update from registers: %d", err);
+ return err;
+ }
+
+ max31760_setup_attr_groups(dev);
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
+ max31760,
+ &max31760_chip_info,
+ max31760->attr_groups);
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static int __maybe_unused max31760_suspend(struct device *dev)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+
+ return regmap_update_bits(max31760->regmap, MAX31760_REG_CR2,
+ MAX31760_CR2_STBY, MAX31760_CR2_STBY);
+}
+
+static int __maybe_unused max31760_resume(struct device *dev)
+{
+ struct max31760 *max31760 = dev_get_drvdata(dev);
+ int err;
+
+ err = regmap_update_bits(max31760->regmap, MAX31760_REG_CR2,
+ MAX31760_CR2_STBY, 0);
+ if (err)
+ dev_err(dev, "Could not clear Standby bit: %d", err);
+ return err;
+}
+
+static SIMPLE_DEV_PM_OPS(max31760_dev_pm_ops, max31760_suspend,
+ max31760_resume);
+
+static const struct i2c_device_id max31760_i2c_ids[] = {
+ { "max31760", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max31760_i2c_ids);
+
+#ifdef CONFIG_OF
+static const struct of_device_id max31760_of_ids[] = {
+ { .compatible = "maxim,max31760", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, max31760_of_ids);
+#endif
+
+static struct i2c_driver max31760_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .pm = &max31760_dev_pm_ops,
+ .of_match_table = of_match_ptr(max31760_of_ids),
+ },
+ .probe = max31760_probe,
+ .id_table = max31760_i2c_ids,
+};
+
+module_i2c_driver(max31760_driver);
+
+MODULE_AUTHOR("John Muir <john@jmuir.com>");
+MODULE_DESCRIPTION("Maxim Integrated MAX31760 Precision Fan-Speed Controller Driver");
+MODULE_LICENSE("GPL");
Add a driver for the Maxim Integrated MAX31760 Precision Fan Speed Controller. v2: - Fixup open firmware code. - Add a few comments. Signed-off-by: John Muir <john@jmuir.com> --- Documentation/hwmon/max31760 | 41 ++ drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/max31760.c | 1461 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1513 insertions(+) create mode 100644 Documentation/hwmon/max31760 create mode 100644 drivers/hwmon/max31760.c