diff options
author | Christian Lamparter <chunkeey@googlemail.com> | 2017-02-10 17:44:43 +0100 |
---|---|---|
committer | Felix Fietkau <nbd@nbd.name> | 2017-02-11 20:57:56 +0100 |
commit | 9a9f2f97e6b66cc88866b3522372d4caa59b26de (patch) | |
tree | f05b66ca99f795a300efd2cea7b6dce0df8b365a /target/linux/apm821xx/patches-4.9/901-hwmon-add-driver-for-Microchip-TC654-TC655-PWM-fan-c.patch | |
parent | 9827c3e9b9d366be2edc8f1d27a1b84d03cfa97d (diff) | |
download | upstream-9a9f2f97e6b66cc88866b3522372d4caa59b26de.tar.gz upstream-9a9f2f97e6b66cc88866b3522372d4caa59b26de.tar.bz2 upstream-9a9f2f97e6b66cc88866b3522372d4caa59b26de.zip |
apm821xx: add linux 4.9 apm821xx patches
This patch updates the apm821xx target to use the 4.9 kernel.
Signed-off-by: Christian Lamparter <chunkeey@googlemail.com>
Diffstat (limited to 'target/linux/apm821xx/patches-4.9/901-hwmon-add-driver-for-Microchip-TC654-TC655-PWM-fan-c.patch')
-rw-r--r-- | target/linux/apm821xx/patches-4.9/901-hwmon-add-driver-for-Microchip-TC654-TC655-PWM-fan-c.patch | 1027 |
1 files changed, 1027 insertions, 0 deletions
diff --git a/target/linux/apm821xx/patches-4.9/901-hwmon-add-driver-for-Microchip-TC654-TC655-PWM-fan-c.patch b/target/linux/apm821xx/patches-4.9/901-hwmon-add-driver-for-Microchip-TC654-TC655-PWM-fan-c.patch new file mode 100644 index 0000000000..ceacde9dda --- /dev/null +++ b/target/linux/apm821xx/patches-4.9/901-hwmon-add-driver-for-Microchip-TC654-TC655-PWM-fan-c.patch @@ -0,0 +1,1027 @@ +From 5ea2e152d846bf60901107fefd81a58f792f3bc2 Mon Sep 17 00:00:00 2001 +From: Christian Lamparter <chunkeey@gmail.com> +Date: Fri, 10 Jun 2016 03:00:46 +0200 +Subject: [PATCH] hwmon: add driver for Microchip TC654/TC655 PWM fan + controllers + +This patch adds a hwmon driver for the Microchip TC654 and TC655 +Dual SMBus PWM Fan Speed Controllers with Fan Fault detection. + +The chip is described in the DS2001734C Spec Document from Microchip. +It supports: + - Shared PWM Fan Drive for two fans + - Provides RPM + - automatic PWM controller (needs additional + NTC/PTC Thermistors.) + - Overtemperature alarm (when using NTC/PTC + Thermistors) + +Signed-off-by: Christian Lamparter <chunkeey@gmail.com> +--- + drivers/hwmon/Kconfig | 10 + + drivers/hwmon/Makefile | 1 + + drivers/hwmon/tc654.c | 969 +++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 980 insertions(+) + create mode 100644 drivers/hwmon/tc654.c + +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -1549,6 +1549,16 @@ config SENSORS_INA3221 + This driver can also be built as a module. If so, the module + will be called ina3221. + ++config SENSORS_TC654 ++ tristate "Microchip TC654 and TC655" ++ depends on I2C ++ help ++ If you say yes here you get support for Microchip TC655 and TC654 ++ Dual PWM Fan Speed Controllers and sensor chips. ++ ++ This driver can also be built as a module. If so, the module ++ will be called tc654. ++ + config SENSORS_TC74 + tristate "Microchip TC74" + depends on I2C +--- a/drivers/hwmon/Makefile ++++ b/drivers/hwmon/Makefile +@@ -148,6 +148,7 @@ obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc4 + obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o + obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o + obj-$(CONFIG_SENSORS_AMC6821) += amc6821.o ++obj-$(CONFIG_SENSORS_TC654) += tc654.o + obj-$(CONFIG_SENSORS_TC74) += tc74.o + obj-$(CONFIG_SENSORS_THMC50) += thmc50.o + obj-$(CONFIG_SENSORS_TMP102) += tmp102.o +--- /dev/null ++++ b/drivers/hwmon/tc654.c +@@ -0,0 +1,969 @@ ++/* ++ * tc654.c - Support for Microchip TC654/TC655 ++ * "A Dual SMBus PWM FAN Speed Controllers with Fan Fault Detection" ++ * ++ * Copyright (c) 2016 Christian Lamparter <chunkeey@gmail.com> ++ * ++ * 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 version 2 of the License. ++ * ++ * 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. ++ * ++ * The chip is described in the DS2001734C Spec Document from Microchip. ++ */ ++ ++#include <linux/module.h> ++#include <linux/init.h> ++#include <linux/slab.h> ++#include <linux/jiffies.h> ++#include <linux/i2c.h> ++#include <linux/hwmon.h> ++#include <linux/hwmon-sysfs.h> ++#include <linux/err.h> ++#include <linux/mutex.h> ++#include <linux/thermal.h> ++ ++/* Hardware definitions */ ++/* 5.1.4 Address Byte stats that TC654/TC655 are fixed at 0x1b */ ++static const unsigned short normal_i2c[] = { 0x1b, I2C_CLIENT_END }; ++ ++enum TC654_REGS { ++ TC654_REG_RPM1 = 0x00, ++ TC654_REG_RPM2, ++ TC654_REG_FAN1_FAULT_THRESH, ++ TC654_REG_FAN2_FAULT_THRESH, ++ TC654_REG_CONFIG, ++ TC654_REG_STATUS, ++ TC654_REG_DUTY_CYCLE, ++ TC654_REG_MFR_ID, ++ TC654_REG_VER_ID, ++ ++ /* keep last */ ++ __TC654_REG_NUM, ++}; ++ ++#define TC654_MFR_ID_MICROCHIP 0x84 ++#define TC654_VER_ID 0x00 ++#define TC655_VER_ID 0x01 ++ ++enum TC654_CONTROL_BITS { ++ TC654_CTRL_SDM = BIT(0), ++ TC654_CTRL_F1PPR_S = 1, ++ TC654_CTRL_F1PPR_M = (BIT(1) | BIT(2)), ++ TC654_CTRL_F2PPR_S = 3, ++ TC654_CTRL_F2PPR_M = (BIT(3) | BIT(4)), ++ TC654_CTRL_DUTYC = BIT(5), ++ TC654_CTRL_RES = BIT(6), ++ TC654_CTRL_FFCLR = BIT(7), ++}; ++ ++enum TC654_STATUS_BITS { ++ TC654_STATUS_F1F = BIT(0), ++ TC654_STATUS_F2F = BIT(1), ++ TC654_STATUS_VSTAT = BIT(2), ++ TC654_STATUS_R1CO = BIT(3), ++ TC654_STATUS_R2CO = BIT(4), ++ TC654_STATUS_OTF = BIT(5), ++}; ++ ++enum TC654_FAN { ++ TC654_FAN1 = 0, ++ TC654_FAN2, ++ ++ /* keep last */ ++ __NUM_TC654_FAN, ++}; ++ ++enum TC654_FAN_MODE { ++ TC654_PWM_OFF, /* Shutdown Mode - switch of both fans */ ++ TC654_PWM_VIN, /* Fans will be controlled via V_in analog input pin */ ++ TC654_PWM_3000, /* sets fans to 30% duty cycle */ ++ TC654_PWM_3467, ++ TC654_PWM_3933, /* default case - if V_in pin is open */ ++ TC654_PWM_4400, ++ TC654_PWM_4867, ++ TC654_PWM_5333, ++ TC654_PWM_5800, ++ TC654_PWM_6267, ++ TC654_PWM_6733, ++ TC654_PWM_7200, ++ TC654_PWM_7667, ++ TC654_PWM_8133, ++ TC654_PWM_8600, ++ TC654_PWM_9067, ++ TC654_PWM_9533, ++ TC654_PWM_10000, /* sets fans to 100% duty cycle */ ++}; ++ ++enum TC654_ALARMS { ++ TC654_ALARM_FAN1_FAULT, ++ TC654_ALARM_FAN2_FAULT, ++ TC654_ALARM_FAN1_COUNTER_OVERFLOW, ++ TC654_ALARM_FAN2_COUNTER_OVERFLOW, ++ TC654_ALARM_OVER_TEMPERATURE, ++ ++ /* KEEP LAST */ ++ __NUM_TC654_ALARMS, ++}; ++ ++static const struct pwm_table_entry { ++ u8 min; ++ enum TC654_FAN_MODE mode; ++} pwm_table[] = { ++ { 0, TC654_PWM_OFF }, ++ { 1, TC654_PWM_3000 }, ++ { 88, TC654_PWM_3467 }, ++ {101, TC654_PWM_3933 }, ++ {113, TC654_PWM_4400 }, ++ {125, TC654_PWM_4867 }, ++ {137, TC654_PWM_5333 }, ++ {148, TC654_PWM_5800 }, ++ {160, TC654_PWM_6267 }, ++ {172, TC654_PWM_6733 }, ++ {184, TC654_PWM_7200 }, ++ {196, TC654_PWM_7667 }, ++ {208, TC654_PWM_8133 }, ++ {220, TC654_PWM_8600 }, ++ {232, TC654_PWM_9067 }, ++ {244, TC654_PWM_9533 }, ++ {255, TC654_PWM_10000 }, ++}; ++ ++/* driver context */ ++struct tc654 { ++ struct i2c_client *client; ++ ++ struct mutex update_lock; ++ ++ unsigned long last_updated; /* in jiffies */ ++ u8 cached_regs[__TC654_REG_NUM]; ++ ++ bool valid; /* monitored registers are valid */ ++ u16 fan_input[__NUM_TC654_FAN]; ++ bool alarms[__NUM_TC654_ALARMS]; ++ bool vin_status; ++ bool pwm_manual; ++ ++ /* optional cooling device */ ++ struct thermal_cooling_device *cdev; ++}; ++ ++/* hardware accessors and functions */ ++static int read_tc(struct tc654 *tc, u8 reg) ++{ ++ s32 status; ++ ++ if (reg <= TC654_REG_VER_ID) { ++ /* Table 6.1 states that all registers are readable */ ++ status = i2c_smbus_read_byte_data(tc->client, reg); ++ } else ++ status = -EINVAL; ++ ++ if (status < 0) { ++ dev_warn(&tc->client->dev, "can't read register 0x%02x due to error (%d)", ++ reg, status); ++ } else { ++ tc->cached_regs[reg] = status; ++ } ++ ++ return status; ++} ++ ++static int write_tc(struct tc654 *tc, u8 i2c_reg, u8 val) ++{ ++ s32 status; ++ ++ /* ++ * Table 6.1 states that both fan threshold registers, ++ * the Config and Duty Cycle are writeable. ++ */ ++ switch (i2c_reg) { ++ case TC654_REG_FAN1_FAULT_THRESH: ++ case TC654_REG_FAN2_FAULT_THRESH: ++ case TC654_REG_DUTY_CYCLE: ++ case TC654_REG_CONFIG: ++ status = i2c_smbus_write_byte_data(tc->client, i2c_reg, val); ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ if (status < 0) { ++ dev_warn(&tc->client->dev, "can't write register 0x%02x with value 0x%02x due to error (%d)", ++ i2c_reg, val, status); ++ } else { ++ tc->cached_regs[i2c_reg] = val; ++ } ++ ++ return status; ++} ++ ++static int mod_config(struct tc654 *tc, u8 set, u8 clear) ++{ ++ u8 val = 0; ++ ++ /* a bit can't be set and cleared on the same time. */ ++ if (set & clear) ++ return -EINVAL; ++ ++ /* invalidate data to force re-read from hardware */ ++ tc->valid = false; ++ val = (tc->cached_regs[TC654_REG_CONFIG] | set) & (~clear); ++ return write_tc(tc, TC654_REG_CONFIG, val); ++} ++ ++static int read_fan_rpm(struct tc654 *tc, enum TC654_FAN fan) ++{ ++ int ret; ++ ++ /* 6.1 RPM-OUTPUT1 and RPM-OUTPUT2 registers */ ++ ret = read_tc(tc, fan == TC654_FAN1 ? TC654_REG_RPM1 : TC654_REG_RPM2); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * The Resolution Selection Bit in 6.3 CONFIGURATION REGISTER ++ * is needed to convert the raw value to the RPM. ++ * 0 = RPM1 and RPM2 use (8-Bit) resolution => * 50 RPM ++ * 1 = RPM1 and RPM2 use (9-Bit) resolution => * 25 RPM ++ */ ++ return ret * (25 << ++ !(tc->cached_regs[TC654_REG_CONFIG] & TC654_CTRL_RES)); ++} ++ ++static int write_fan_fault_thresh(struct tc654 *tc, enum TC654_FAN fan, ++ u16 rpm) ++{ ++ u8 converted_rpm; ++ ++ if (rpm > 12750) ++ return -EINVAL; ++ ++ /* ++ * 6.2 FAN_FAULT1 and FAN_FAULT2 Threshold registers ++ * ++ * Both registers operate in 50 RPM mode exclusively. ++ */ ++ converted_rpm = rpm / 50; ++ ++ /* invalidate data to force re-read from hardware */ ++ tc->valid = false; ++ return write_tc(tc, fan == TC654_FAN1 ? TC654_REG_FAN1_FAULT_THRESH : ++ TC654_REG_FAN2_FAULT_THRESH, converted_rpm); ++} ++ ++ ++static int read_fan_fault_thresh(struct tc654 *tc, enum TC654_FAN fan) ++{ ++ /* ++ * 6.2 FAN_FAULT1 and FAN_FAULT2 Threshold registers ++ * ++ * Both registers operate in 50 RPM mode exclusively. ++ */ ++ return read_tc(tc, fan == TC654_FAN1 ? TC654_REG_FAN1_FAULT_THRESH : ++ TC654_REG_FAN2_FAULT_THRESH) * 50; ++} ++ ++static enum TC654_FAN_MODE get_fan_mode(struct tc654 *tc) ++{ ++ if (tc->cached_regs[TC654_REG_CONFIG] & TC654_CTRL_SDM) { ++ return TC654_PWM_OFF; ++ } else if (tc->cached_regs[TC654_REG_CONFIG] & TC654_CTRL_DUTYC) { ++ return TC654_PWM_3000 + tc->cached_regs[TC654_REG_DUTY_CYCLE]; ++ } else if (tc->vin_status == 0) ++ return TC654_PWM_VIN; ++ ++ return -EINVAL; ++} ++ ++static int write_fan_mode(struct tc654 *tc, enum TC654_FAN_MODE mode) ++{ ++ int err; ++ u8 pwm_mode; ++ bool in_sdm; ++ ++ in_sdm = !!(tc->cached_regs[TC654_REG_CONFIG] & ++ TC654_CTRL_SDM); ++ ++ switch (mode) { ++ case TC654_PWM_OFF: ++ if (in_sdm) ++ return 0; ++ ++ /* Enter Shutdown Mode - Switches off all fans */ ++ err = mod_config(tc, TC654_CTRL_SDM, TC654_CTRL_DUTYC); ++ if (err) ++ return err; ++ ++ return 0; ++ ++ case TC654_PWM_VIN: ++ if (tc->vin_status) { ++ dev_err(&tc->client->dev, ++ "V_in pin is open, can't enable automatic mode."); ++ return -EINVAL; ++ } ++ ++ err = mod_config(tc, 0, TC654_CTRL_SDM | TC654_CTRL_DUTYC); ++ if (err) ++ return err; ++ ++ tc->pwm_manual = false; ++ return 0; ++ ++ case TC654_PWM_3000: ++ case TC654_PWM_3467: ++ case TC654_PWM_3933: ++ case TC654_PWM_4400: ++ case TC654_PWM_4867: ++ case TC654_PWM_5333: ++ case TC654_PWM_5800: ++ case TC654_PWM_6267: ++ case TC654_PWM_6733: ++ case TC654_PWM_7200: ++ case TC654_PWM_7667: ++ case TC654_PWM_8133: ++ case TC654_PWM_8600: ++ case TC654_PWM_9067: ++ case TC654_PWM_9533: ++ case TC654_PWM_10000: ++ pwm_mode = mode - TC654_PWM_3000; ++ if (!in_sdm) { ++ err = write_tc(tc, TC654_REG_DUTY_CYCLE, pwm_mode); ++ if (err) ++ return err; ++ } ++ ++ err = mod_config(tc, TC654_CTRL_DUTYC, TC654_CTRL_SDM); ++ if (err) ++ return err; ++ ++ tc->pwm_manual = true; ++ ++ if (in_sdm) { ++ /* ++ * In case the TC654/TC655 was in SDM mode, the write ++ * above into the TC654_REG_DUTY_CYCLE register will ++ * have no effect because the chip was switched off. ++ * ++ * Note: The TC654/TC655 have a special "power-on" ++ * feature where the PWM will be forced to 100% for ++ * one full second in order to spin-up a resting fan. ++ */ ++ err = write_tc(tc, TC654_REG_DUTY_CYCLE, pwm_mode); ++ if (err) ++ return err; ++ } ++ ++ return 0; ++ ++ default: ++ return -EINVAL; ++ } ++} ++ ++static struct tc654 *tc654_update_device(struct device *dev) ++{ ++ struct tc654 *tc = dev_get_drvdata(dev); ++ ++ mutex_lock(&tc->update_lock); ++ ++ /* ++ * In Chapter "1.0 Electrical Characteristics", ++ * the "Fault Output Response Time" is specified as 2.4 seconds. ++ */ ++ if (time_after(jiffies, tc->last_updated + 2 * HZ + (HZ * 2) / 5) ++ || !tc->valid) { ++ size_t i; ++ int ret; ++ bool alarm_triggered; ++ ++ tc->valid = false; ++ ++ for (i = 0; i < __NUM_TC654_FAN; i++) { ++ ret = read_fan_rpm(tc, i); ++ if (ret < 0) ++ goto out; ++ ++ tc->fan_input[i] = ret; ++ } ++ ++ ret = read_tc(tc, TC654_REG_STATUS); ++ if (ret < 0) ++ goto out; ++ ++ alarm_triggered = !!(ret & (TC654_STATUS_F1F | ++ TC654_STATUS_F2F | TC654_STATUS_R1CO | ++ TC654_STATUS_R2CO | TC654_STATUS_OTF)); ++ ++ tc->alarms[TC654_ALARM_FAN1_FAULT] = !!(ret & TC654_STATUS_F1F); ++ tc->alarms[TC654_ALARM_FAN2_FAULT] = !!(ret & TC654_STATUS_F2F); ++ tc->alarms[TC654_ALARM_FAN1_COUNTER_OVERFLOW] = ++ !!(ret & TC654_STATUS_R1CO); ++ tc->alarms[TC654_ALARM_FAN2_COUNTER_OVERFLOW] = ++ !!(ret & TC654_STATUS_R2CO); ++ tc->alarms[TC654_ALARM_OVER_TEMPERATURE] = ++ !!(ret & TC654_STATUS_OTF); ++ tc->vin_status = !!(ret & TC654_STATUS_VSTAT); ++ ++ /* ++ * From 4.5 and 6.3 ++ * ++ * ... "If the V_in pin is open when TC654_CTRL_DUTYC is not ++ * selected, then V_out duty cycle will default to 39.33%.". ++ * ++ * and most importantly 6.5: ++ * ... "V_in pin is open, the duty cycle will go to the default ++ * setting of this register, which is 0010 (39.33%)." ++ */ ++ tc->pwm_manual |= tc->vin_status && ++ (tc->cached_regs[TC654_REG_CONFIG] & ++ TC654_CTRL_DUTYC); ++ ++ if (alarm_triggered) { ++ /* ++ * as the name implies, this FLAG needs to be ++ * set in order to clear the FAN Fault error. ++ */ ++ ret = mod_config(tc, TC654_CTRL_FFCLR, 0); ++ if (ret < 0) ++ goto out; ++ } ++ ++ tc->last_updated = jiffies; ++ tc->valid = true; ++ } ++ ++out: ++ mutex_unlock(&tc->update_lock); ++ return tc; ++} ++ ++static u8 get_fan_pulse(struct tc654 *tc, enum TC654_FAN fan) ++{ ++ u8 fan_pulse_mask = fan == TC654_FAN1 ? ++ TC654_CTRL_F1PPR_M : TC654_CTRL_F2PPR_M; ++ u8 fan_pulse_shift = fan == TC654_FAN1 ? ++ TC654_CTRL_F1PPR_S : TC654_CTRL_F2PPR_S; ++ ++ return 1 << ((tc->cached_regs[TC654_REG_CONFIG] & fan_pulse_mask) >> ++ fan_pulse_shift); ++} ++ ++static int ++set_fan_pulse(struct tc654 *tc, enum TC654_FAN fan, int pulses) ++{ ++ int old_pulses; ++ int err; ++ u8 new_pulse_per_rotation; ++ u8 fan_pulse_mask = fan == TC654_FAN1 ? ++ TC654_CTRL_F1PPR_M : TC654_CTRL_F2PPR_M; ++ u8 fan_pulse_shift = fan == TC654_FAN1 ? ++ TC654_CTRL_F1PPR_S : TC654_CTRL_F2PPR_S; ++ ++ switch (pulses) { ++ case 1: ++ new_pulse_per_rotation = 0; ++ break; ++ case 2: ++ new_pulse_per_rotation = 1; ++ break; ++ case 4: ++ new_pulse_per_rotation = 2; ++ break; ++ case 8: ++ new_pulse_per_rotation = 3; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ new_pulse_per_rotation <<= fan_pulse_shift; ++ new_pulse_per_rotation &= fan_pulse_mask; ++ ++ old_pulses = tc->cached_regs[TC654_REG_CONFIG]; ++ old_pulses &= fan_pulse_mask; ++ ++ if (new_pulse_per_rotation == old_pulses) ++ return 0; ++ ++ mutex_lock(&tc->update_lock); ++ err = mod_config(tc, new_pulse_per_rotation, ++ old_pulses & (~new_pulse_per_rotation)); ++ mutex_unlock(&tc->update_lock); ++ ++ /* invalidate RPM data to force re-read from hardware */ ++ tc->valid = false; ++ ++ return err; ++} ++ ++static int get_fan_speed(struct tc654 *tc) ++{ ++ enum TC654_FAN_MODE mode; ++ size_t i; ++ ++ mode = get_fan_mode(tc); ++ for (i = 0; i < ARRAY_SIZE(pwm_table); i++) { ++ if (mode == pwm_table[i].mode) ++ return pwm_table[i].min; ++ } ++ ++ return -EINVAL; ++} ++ ++static int set_fan_speed(struct tc654 *tc, int new_value) ++{ ++ int result; ++ size_t i; ++ ++ if (new_value > pwm_table[ARRAY_SIZE(pwm_table) - 1].min || ++ new_value < pwm_table[0].min) ++ return -EINVAL; ++ ++ for (i = 0; i < ARRAY_SIZE(pwm_table); i++) { ++ /* exact match */ ++ if (pwm_table[i].min == new_value) ++ break; ++ ++ /* a little bit too big - go with the previous entry */ ++ if (pwm_table[i].min > new_value) { ++ --i; ++ break; ++ } ++ } ++ ++ mutex_lock(&tc->update_lock); ++ result = write_fan_mode(tc, pwm_table[i].mode); ++ mutex_unlock(&tc->update_lock); ++ if (result < 0) ++ return result; ++ ++ return 0; ++} ++ ++/* sysfs */ ++ ++static ssize_t ++show_fan_input(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = tc654_update_device(dev); ++ int nr = to_sensor_dev_attr(da)->index; ++ ++ return sprintf(buf, "%d\n", tc->fan_input[nr]); ++} ++ ++static ssize_t ++show_fan_min(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = dev_get_drvdata(dev); ++ int nr = to_sensor_dev_attr(da)->index; ++ ++ return sprintf(buf, "%d\n", read_fan_fault_thresh(tc, nr)); ++} ++ ++static ssize_t ++show_fan_min_alarm(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = tc654_update_device(dev); ++ int nr = to_sensor_dev_attr(da)->index; ++ ++ return sprintf(buf, "%d\n", nr == TC654_FAN1 ? ++ tc->alarms[TC654_ALARM_FAN1_FAULT] : ++ tc->alarms[TC654_ALARM_FAN2_FAULT]); ++} ++ ++static ssize_t ++show_fan_max_alarm(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = tc654_update_device(dev); ++ int nr = to_sensor_dev_attr(da)->index; ++ ++ return sprintf(buf, "%d\n", nr == TC654_FAN1 ? ++ tc->alarms[TC654_ALARM_FAN1_COUNTER_OVERFLOW] : ++ tc->alarms[TC654_ALARM_FAN2_COUNTER_OVERFLOW]); ++} ++ ++static ssize_t ++set_fan_min(struct device *dev, struct device_attribute *da, ++ const char *buf, size_t count) ++{ ++ struct tc654 *tc = dev_get_drvdata(dev); ++ long new_min; ++ int nr = to_sensor_dev_attr(da)->index; ++ int old_min = read_fan_fault_thresh(tc, nr); ++ int status = kstrtol(buf, 10, &new_min); ++ ++ if (status < 0) ++ return status; ++ ++ new_min = (new_min / 50) * 50; ++ if (new_min == old_min) /* No change */ ++ return count; ++ ++ if (new_min < 0 || new_min > 12750) ++ return -EINVAL; ++ ++ mutex_lock(&tc->update_lock); ++ status = write_fan_fault_thresh(tc, nr, new_min); ++ mutex_unlock(&tc->update_lock); ++ return count; ++} ++ ++static ssize_t ++show_fan_max(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = dev_get_drvdata(dev); ++ int max_rpm = tc->cached_regs[TC654_REG_CONFIG] & TC654_CTRL_RES ? ++ (((1 << 9) - 1) * 25) /* ((2**9) - 1) * 25 RPM */: ++ (((1 << 8) - 1) * 50) /* ((2**8) - 1) * 50 RPM */; ++ ++ return sprintf(buf, "%d\n", max_rpm); ++} ++ ++static ssize_t ++show_fan_fault(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = tc654_update_device(dev); ++ int nr = to_sensor_dev_attr(da)->index; ++ u8 fan_fault_mask = nr == TC654_FAN1 ? ++ TC654_STATUS_F1F : TC654_STATUS_F2F; ++ ++ return sprintf(buf, "%d\n", ++ !!(tc->cached_regs[TC654_REG_STATUS] & fan_fault_mask)); ++} ++ ++static ssize_t ++show_fan_pulses(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = dev_get_drvdata(dev); ++ int nr = to_sensor_dev_attr(da)->index; ++ ++ return sprintf(buf, "%d\n", get_fan_pulse(tc, nr)); ++} ++ ++static ssize_t ++set_fan_pulses(struct device *dev, struct device_attribute *da, ++ const char *buf, size_t count) ++{ ++ struct tc654 *tc = dev_get_drvdata(dev); ++ long new_pulse; ++ int nr = to_sensor_dev_attr(da)->index; ++ int status = kstrtol(buf, 10, &new_pulse); ++ ++ if (status < 0) ++ return status; ++ ++ status = set_fan_pulse(tc, nr, new_pulse); ++ if (status < 0) ++ return status; ++ ++ return count; ++} ++ ++static ssize_t ++show_pwm_enable(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = tc654_update_device(dev); ++ int pwm_enabled; ++ ++ if ((tc->cached_regs[TC654_REG_CONFIG] & TC654_CTRL_SDM) && ++ !tc->pwm_manual) { ++ pwm_enabled = 0; /* full off */ ++ } else { ++ if (tc->valid && tc->vin_status == 0) ++ pwm_enabled = 2; /* automatic fan speed control */ ++ ++ pwm_enabled = 1; /* PWM Mode */ ++ } ++ ++ return sprintf(buf, "%d\n", pwm_enabled); ++} ++ ++static ssize_t ++set_pwm_enable(struct device *dev, struct device_attribute *da, ++ const char *buf, size_t count) ++{ ++ struct tc654 *tc = dev_get_drvdata(dev); ++ long new_value; ++ ++ int result = kstrtol(buf, 10, &new_value); ++ ++ if (result < 0) ++ return result; ++ ++ mutex_lock(&tc->update_lock); ++ switch (new_value) { ++ case 0: /* no fan control (i.e. is OFF) */ ++ result = write_fan_mode(tc, TC654_PWM_OFF); ++ tc->pwm_manual = false; ++ break; ++ ++ case 1: /* manual fan control enabled (using pwm) */ ++ result = write_fan_mode(tc, TC654_PWM_10000); ++ break; ++ ++ case 2: /* automatic fan speed control enabled */ ++ result = write_fan_mode(tc, TC654_PWM_VIN); ++ break; ++ ++ default: ++ result = -EINVAL; ++ } ++ ++ mutex_unlock(&tc->update_lock); ++ return result < 0 ? result : count; ++} ++ ++static ssize_t ++show_pwm(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = tc654_update_device(dev); ++ int ret; ++ ++ ret = get_fan_speed(tc); ++ if (ret < 0) ++ return ret; ++ ++ return sprintf(buf, "%d\n", ret); ++} ++ ++static ssize_t ++set_pwm(struct device *dev, struct device_attribute *da, ++ const char *buf, size_t count) ++{ ++ struct tc654 *tc = dev_get_drvdata(dev); ++ long new_value = -1; ++ int result = kstrtol(buf, 10, &new_value); ++ ++ if (result < 0) ++ return result; ++ ++ if (new_value < 0 || new_value > INT_MAX) ++ return -EINVAL; ++ ++ if (!tc->pwm_manual) ++ return -EINVAL; ++ ++ result = set_fan_speed(tc, new_value); ++ if (result < 0) ++ return result; ++ ++ return count; ++} ++ ++static ssize_t ++show_temp_alarm_otf(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = tc654_update_device(dev); ++ ++ return sprintf(buf, "%d\n", tc->alarms[TC654_ALARM_OVER_TEMPERATURE]); ++} ++ ++static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan_input, ++ NULL, TC654_FAN1); ++static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR, show_fan_min, ++ set_fan_min, TC654_FAN1); ++static SENSOR_DEVICE_ATTR(fan1_min_alarm, S_IRUGO, show_fan_min_alarm, ++ NULL, TC654_FAN1); ++static SENSOR_DEVICE_ATTR(fan1_max_alarm, S_IRUGO, show_fan_max_alarm, ++ NULL, TC654_FAN1); ++static SENSOR_DEVICE_ATTR(fan1_max, S_IRUGO, show_fan_max, NULL, TC654_FAN1); ++static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, show_fan_fault, ++ NULL, TC654_FAN1); ++static SENSOR_DEVICE_ATTR(fan1_pulses, S_IRUGO | S_IWUSR, show_fan_pulses, ++ set_fan_pulses, TC654_FAN1); ++static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan_input, ++ NULL, TC654_FAN2); ++static SENSOR_DEVICE_ATTR(fan2_min, S_IRUGO | S_IWUSR, show_fan_min, ++ set_fan_min, TC654_FAN2); ++static SENSOR_DEVICE_ATTR(fan2_max, S_IRUGO, show_fan_max, ++ NULL, TC654_FAN2); ++static SENSOR_DEVICE_ATTR(fan2_min_alarm, S_IRUGO, show_fan_min_alarm, ++ NULL, TC654_FAN2); ++static SENSOR_DEVICE_ATTR(fan2_max_alarm, S_IRUGO, show_fan_max_alarm, ++ NULL, TC654_FAN2); ++static SENSOR_DEVICE_ATTR(fan2_fault, S_IRUGO, show_fan_fault, ++ NULL, TC654_FAN2); ++static SENSOR_DEVICE_ATTR(fan2_pulses, S_IRUGO | S_IWUSR, show_fan_pulses, ++ set_fan_pulses, TC654_FAN2); ++ ++static DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, show_pwm_enable, ++ set_pwm_enable); ++static DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm); ++ ++static DEVICE_ATTR(temp1_emergency_alarm, S_IRUGO, show_temp_alarm_otf, NULL); ++ ++/* sensors present on all models */ ++static struct attribute *tc654_attrs[] = { ++ &sensor_dev_attr_fan1_input.dev_attr.attr, ++ &sensor_dev_attr_fan1_min.dev_attr.attr, ++ &sensor_dev_attr_fan1_max.dev_attr.attr, ++ &sensor_dev_attr_fan1_min_alarm.dev_attr.attr, ++ &sensor_dev_attr_fan1_max_alarm.dev_attr.attr, ++ &sensor_dev_attr_fan1_fault.dev_attr.attr, ++ &sensor_dev_attr_fan1_pulses.dev_attr.attr, ++ &sensor_dev_attr_fan2_input.dev_attr.attr, ++ &sensor_dev_attr_fan2_min.dev_attr.attr, ++ &sensor_dev_attr_fan2_max.dev_attr.attr, ++ &sensor_dev_attr_fan2_min_alarm.dev_attr.attr, ++ &sensor_dev_attr_fan2_max_alarm.dev_attr.attr, ++ &sensor_dev_attr_fan2_fault.dev_attr.attr, ++ &sensor_dev_attr_fan2_pulses.dev_attr.attr, ++ ++ &dev_attr_pwm1_enable.attr, ++ &dev_attr_pwm1.attr, ++ ++ &dev_attr_temp1_emergency_alarm.attr, ++ NULL ++}; ++ ++ATTRIBUTE_GROUPS(tc654); ++ ++/* cooling device */ ++ ++static int tc654_get_max_state(struct thermal_cooling_device *cdev, ++ unsigned long *state) ++{ ++ *state = 255; ++ return 0; ++} ++ ++static int tc654_get_cur_state(struct thermal_cooling_device *cdev, ++ unsigned long *state) ++{ ++ struct tc654 *tc = cdev->devdata; ++ int ret; ++ ++ if (!tc) ++ return -EINVAL; ++ ++ ret = get_fan_speed(tc); ++ if (ret < 0) ++ return ret; ++ ++ *state = ret; ++ return 0; ++} ++ ++static int tc654_set_cur_state(struct thermal_cooling_device *cdev, ++ unsigned long state) ++{ ++ struct tc654 *tc = cdev->devdata; ++ ++ if (!tc) ++ return -EINVAL; ++ ++ if (state > INT_MAX) ++ return -EINVAL; ++ ++ return set_fan_speed(tc, state); ++} ++ ++static const struct thermal_cooling_device_ops tc654_fan_cool_ops = { ++ .get_max_state = tc654_get_max_state, ++ .get_cur_state = tc654_get_cur_state, ++ .set_cur_state = tc654_set_cur_state, ++}; ++ ++ ++/* hardware probe and detection */ ++ ++static int ++tc654_probe(struct i2c_client *client, const struct i2c_device_id *id) ++{ ++ struct tc654 *tc; ++ struct device *hwmon_dev; ++ int ret, i; ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ return -EIO; ++ ++ tc = devm_kzalloc(&client->dev, sizeof(*tc), GFP_KERNEL); ++ if (!tc) ++ return -ENOMEM; ++ ++ i2c_set_clientdata(client, tc); ++ tc->client = client; ++ mutex_init(&tc->update_lock); ++ ++ /* cache all 8 registers */ ++ for (i = 0; i < __TC654_REG_NUM; i++) { ++ ret = read_tc(tc, i); ++ if (ret < 0) ++ return ret; ++ } ++ ++ /* sysfs hooks */ ++ hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev, ++ client->name, tc, ++ tc654_groups); ++ if (IS_ERR(hwmon_dev)) ++ return PTR_ERR(hwmon_dev); ++ ++#if IS_ENABLED(CONFIG_OF) ++ /* Optional cooling device register for Device tree platforms */ ++ tc->cdev = thermal_of_cooling_device_register(client->dev.of_node, ++ "tc654", tc, ++ &tc654_fan_cool_ops); ++#else /* CONFIG_OF */ ++ /* Optional cooling device register for non Device tree platforms */ ++ tc->cdev = thermal_cooling_device_register("tc654", tc, ++ &tc654_fan_cool_ops); ++#endif /* CONFIG_OF */ ++ ++ dev_info(&client->dev, "%s: sensor '%s'\n", ++ dev_name(hwmon_dev), client->name); ++ ++ return 0; ++} ++ ++static const struct i2c_device_id tc654_ids[] = { ++ { "tc654", 0, }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, tc654_ids); ++ ++/* Return 0 if detection is successful, -ENODEV otherwise */ ++static int ++tc654_detect(struct i2c_client *new_client, struct i2c_board_info *info) ++{ ++ struct i2c_adapter *adapter = new_client->adapter; ++ int manufacturer, product; ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ return -ENODEV; ++ ++ manufacturer = i2c_smbus_read_byte_data(new_client, TC654_REG_MFR_ID); ++ if (manufacturer != TC654_MFR_ID_MICROCHIP) ++ return -ENODEV; ++ ++ product = i2c_smbus_read_byte_data(new_client, TC654_REG_VER_ID); ++ if (!((product == TC654_VER_ID) || (product == TC655_VER_ID))) ++ return -ENODEV; ++ ++ strlcpy(info->type, "tc654", I2C_NAME_SIZE); ++ return 0; ++} ++ ++static struct i2c_driver tc654_driver = { ++ .class = I2C_CLASS_HWMON, ++ .driver = { ++ .name = "tc654", ++ }, ++ .probe = tc654_probe, ++ .id_table = tc654_ids, ++ .detect = tc654_detect, ++ .address_list = normal_i2c, ++}; ++ ++module_i2c_driver(tc654_driver); ++ ++MODULE_AUTHOR("Christian Lamparter <chunkeey@gmail.com>"); ++MODULE_DESCRIPTION("Microchip TC654/TC655 hwmon driver"); ++MODULE_LICENSE("GPL"); |