From patchwork Tue Apr 4 19:20:33 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: John Muir X-Patchwork-Id: 9662479 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 0DC056032D for ; Tue, 4 Apr 2017 19:21:14 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id F01E328507 for ; Tue, 4 Apr 2017 19:21:13 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id E3A7128546; Tue, 4 Apr 2017 19:21:13 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.3 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI, RCVD_IN_SORBS_SPAM, T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id A0EC628507 for ; Tue, 4 Apr 2017 19:21:11 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755249AbdDDTVL (ORCPT ); Tue, 4 Apr 2017 15:21:11 -0400 Received: from mail-pg0-f67.google.com ([74.125.83.67]:36503 "EHLO mail-pg0-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755269AbdDDTVJ (ORCPT ); Tue, 4 Apr 2017 15:21:09 -0400 Received: by mail-pg0-f67.google.com with SMTP id 81so39256002pgh.3 for ; Tue, 04 Apr 2017 12:21:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=jmuir.com; s=jmuirsoft; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=Vm+ar2MQBm+W8O9bD+CM2HedUaIqLdahcImc3kb1TL4=; b=N788oX5BH+StB8F3+mZ0I56KhFfGvyqE2D6TxtgU1kTjWssM5zosR1D8K5Bcu3qmpU OubG8Cwy5jUxHAew3m/FhJNBdQ5cGCzdF098ndEYRg/QjdfNutCPiE43XeD/HVJzCyYt VhDg7Vahr+sdKD4fpnk2diji6s6TXpb6j52Vc= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=Vm+ar2MQBm+W8O9bD+CM2HedUaIqLdahcImc3kb1TL4=; b=bYZuSjBeNO7ML6FuSfQDZM1E6wr01t2knzepPxfA1fwSWXWEHNqvEtiY8Pz26cnEKu KxblvdlYGe/+YibM03POZxAgX7gYBQmTsLpvOirGA/rqw7y0Gld1wbxxjezmSO5cz5R+ pKSv+NzvLtfS9/sBcNChLTpAg+MiG0wbEB767fI14e95rqNSRkbcSM0paQCVPmXD4S9Q W956MJw6W7FeduF2NsUmS30OopBnODod+Uv4JFz7tVbQGlOwGtbiqSOKkMbbPT0ElwNM e91Ye2lOwMITxYiqt8IWTal7CCVF5xO8GNYa+PEWnURKNeRiEMe9I5yjXMCJ2xp039Nu KK1w== X-Gm-Message-State: AFeK/H1sxcZQnRdm6JiL6aEb7mOm4HbqqhB+gUTkFFHqnbNZovAymNyaHnOpEvwVEzUydw== X-Received: by 10.84.173.228 with SMTP id p91mr30895912plb.18.1491333667255; Tue, 04 Apr 2017 12:21:07 -0700 (PDT) Received: from muirj2.mtv.corp.google.com ([172.22.140.82]) by smtp.gmail.com with ESMTPSA id w129sm33113411pfb.130.2017.04.04.12.21.06 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 04 Apr 2017 12:21:06 -0700 (PDT) From: John Muir To: Jean Delvare , Guenter Roeck , Jonathan Corbet , Rob Herring , Pawel Moll , Ian Campbell , Kumar Gala , devicetree@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-doc@vger.kernel.org Cc: John Muir , Anatol Pomazau , Mark Segal Subject: [PATCH 1/2] hwmon: Add MAX31760 fan controller driver. Date: Tue, 4 Apr 2017 12:20:33 -0700 Message-Id: <20170404192034.158901-2-john@jmuir.com> X-Mailer: git-send-email 2.12.2.715.g7642488e1d-goog In-Reply-To: <20170404192034.158901-1-john@jmuir.com> References: <20170404192034.158901-1-john@jmuir.com> Sender: linux-hwmon-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-hwmon@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Add a driver for the Maxim Integrated MAX31760 Precision Fan Speed Controller. Signed-off-by: John Muir --- Documentation/hwmon/max31760 | 41 ++ drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/max31760.c | 1430 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1482 insertions(+) create mode 100644 Documentation/hwmon/max31760 create mode 100644 drivers/hwmon/max31760.c diff --git a/Documentation/hwmon/max31760 b/Documentation/hwmon/max31760 new file mode 100644 index 000000000000..95937844ed18 --- /dev/null +++ b/Documentation/hwmon/max31760 @@ -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 + +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 data-sheet 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). diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 2d5447bebab6..3aef5c07f1c3 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -849,6 +849,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 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 76e1456ddf2f..7b08f069d5a4 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -116,6 +116,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 diff --git a/drivers/hwmon/max31760.c b/drivers/hwmon/max31760.c new file mode 100644 index 000000000000..735b4fe9a510 --- /dev/null +++ b/drivers/hwmon/max31760.c @@ -0,0 +1,1430 @@ +/* Maxim Integrated MAX31760 Precision Fan-Speed Controller driver + * + * Copyright (C) 2017 John Muir + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "max31760" + +#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 -128000 /* Minimum Millicelcius */ +#define MAX31760_TEMP_LUT_MIN_MC -40000 +#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 +#define MAX31760_LYT_HYST_THRESH 3000 +#define MAX31760_LUT_HYST_SET 4000 + +#define MAX31760_NUM_FANS 2 +#define MAX31760_FAN_PULSES_DEF 2 +#define MAX31760_FAN_PULSES_MAX 8 +#define MAX31760_PWM_ENABLE_FULL 0 +#define MAX31760_PWM_ENABLE_MANUAL 1 +#define MAX31760_PWM_ENABLE_AUTO 2 +#define MAX31760_NUM_TEMPS 2 + +#define MAX31760_LUT_AUTO_ATTRS 3 +#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 { + struct sensor_device_attribute sdattr; + char name[MAX31760_LUT_NAME_SIZE]; +}; + +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; +} + +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; +} + +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); +} + +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; +} + +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; + } +} + +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 + */ + 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_FAULT | HWMON_T_MAX | + HWMON_T_EMERGENCY | HWMON_T_MAX_ALARM | + HWMON_T_EMERGENCY_ALARM | HWMON_T_MAX_HYST | + HWMON_T_EMERGENCY_HYST, + 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, +}; + +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); +} + +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; +} + +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); +} + +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; +} + +static int max31760_pwm_auto_point_temp(int index) +{ + if (index == 0) + return MAX31760_TEMP_LUT_MIN_MC; + else + return (16 + index * 2) * 1000; +} + +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); +} + +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); +} + +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, +}; + +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; +} + +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; +} + +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; +} + +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); +} + +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; +} + +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); +} + +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, +}; + +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; +} + +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); +} + +static int max31760_of_init(struct device *dev) +{ + struct max31760 *max31760 = dev_get_drvdata(dev); + const char *label; + int err; + u32 val; + + err = device_property_read_u32(dev, "maxim,fan1-enabled", &val); + if (!err) { + err = regmap_update_bits(max31760->regmap, MAX31760_REG_CR3, + MAX31760_CR3_TACH1E, + val ? MAX31760_CR3_TACH1E : 0); + if (err) + return err; + } + + err = device_property_read_u32(dev, "maxim,fan2-enabled", &val); + if (!err) { + err = regmap_update_bits(max31760->regmap, MAX31760_REG_CR3, + MAX31760_CR3_TACH2E, + val ? MAX31760_CR3_TACH2E : 0); + if (err) + return err; + } + + err = device_property_read_string(dev, "maxim,fan1-label", &label); + if (!err) + max31760->fan_label[0] = label; + + err = device_property_read_string(dev, "maxim,fan2-label", &label); + if (!err) + max31760->fan_label[1] = label; + + err = device_property_read_u32(dev, "maxim,fan-fail-full-only", + &val); + if (!err) { + err = regmap_update_bits(max31760->regmap, MAX31760_REG_CR3, + MAX31760_CR3_TACHFL, + val ? MAX31760_CR3_TACHFL : 0); + if (err) + return err; + } + + err = device_property_read_u32(dev, "maxim,fan-spin-up-enabled", &val); + if (!err) { + err = regmap_update_bits(max31760->regmap, MAX31760_REG_CR2, + MAX31760_CR2_SPEN, + val ? MAX31760_CR2_SPEN : 0); + if (err) + return err; + } + + err = device_property_read_u32(dev, "maxim,fan-rd-signal", &val); + if (!err) { + err = regmap_update_bits(max31760->regmap, MAX31760_REG_CR2, + MAX31760_CR2_FSST, + val ? MAX31760_CR2_FSST : 0); + if (err) + return err; + } + + err = device_property_read_u32(dev, "maxim,fan-rd-polarity", &val); + if (!err) { + err = regmap_update_bits(max31760->regmap, MAX31760_REG_CR2, + MAX31760_CR2_RDPS, + val ? MAX31760_CR2_RDPS : 0); + if (err) + return err; + } + + err = device_property_read_u32(dev, "maxim,fan-signal-enabled", &val); + if (!err) { + err = regmap_update_bits(max31760->regmap, MAX31760_REG_CR2, + MAX31760_CR2_FSEN, + val ? MAX31760_CR2_FSEN : 0); + if (err) + return err; + } + + err = device_property_read_u32(dev, "maxim,pwm-polarity", &val); + if (!err) { + err = regmap_update_bits(max31760->regmap, MAX31760_REG_CR1, + MAX31760_CR1_PPS, + val ? MAX31760_CR1_PPS : 0); + if (err) + return err; + } + + err = device_property_read_u32(dev, "maxim,pwm-pulse-stretch-enabled", + &val); + if (!err) { + err = regmap_update_bits(max31760->regmap, MAX31760_REG_CR3, + MAX31760_CR3_PSEN, + val ? MAX31760_CR3_PSEN : 0); + if (err) + return err; + } + + err = device_property_read_u32(dev, "maxim,pwm-zero-fan-can-fail", + &val); + if (!err) { + err = regmap_update_bits(max31760->regmap, MAX31760_REG_CR3, + MAX31760_CR3_FF_0, + val ? MAX31760_CR3_FF_0 : 0); + if (err) + return err; + } + + err = device_property_read_string(dev, "maxim,temp1-label", &label); + if (!err) + max31760->temp_label[0] = label; + + err = device_property_read_string(dev, "maxim,temp2-label", &label); + if (!err) + max31760->temp_label[1] = label; + + err = device_property_read_u32(dev, "maxim,temp2-ideality", &val); + if (!err) { + err = regmap_write(max31760->regmap, MAX31760_REG_IFR, + val & 0x3f); + if (err) + return err; + } + + /* Firmware configuration parameters planned: + * maxim,fan-fail-interrupt -> MAX31760_CR2_FFMODE + * maxim,temp-alert-interrupt -> MAX31760_CR2_ALERTS (default true) + */ + + /* Put ALERT pin into comparator mode: interrupts aren't supported. */ + return regmap_update_bits(max31760->regmap, MAX31760_REG_CR2, + MAX31760_CR2_ALERTS, MAX31760_CR2_ALERTS); +} + +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 "); +MODULE_DESCRIPTION("Maxim Integrated MAX31760 Precision Fan-Speed Controller Driver"); +MODULE_LICENSE("GPL");