1038 lines
26 KiB
Diff
1038 lines
26 KiB
Diff
|
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
|
||
|
|
||
|
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
|
||
|
index ff94007..941fe4951 100644
|
||
|
--- a/drivers/hwmon/Kconfig
|
||
|
+++ b/drivers/hwmon/Kconfig
|
||
|
@@ -1514,6 +1514,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
|
||
|
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
|
||
|
index 2ef5b7c..04270c7 100644
|
||
|
--- a/drivers/hwmon/Makefile
|
||
|
+++ b/drivers/hwmon/Makefile
|
||
|
@@ -145,6 +145,7 @@ obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
|
||
|
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
|
||
|
diff --git a/drivers/hwmon/tc654.c b/drivers/hwmon/tc654.c
|
||
|
new file mode 100644
|
||
|
index 0000000..d584baf
|
||
|
--- /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");
|
||
|
--
|
||
|
2.8.1
|
||
|
|