1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027 |
- 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
- @@ -1484,6 +1484,16 @@ config SENSORS_INA2XX
- This driver can also be built as a module. If so, the module
- will be called ina2xx.
-
- +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
- @@ -143,6 +143,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");
|