mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-17 09:14:19 +08:00
hwmon updates for v4.16
New driver for W83773G Fan control support for PMBus drivers Improvements and minor fixes in several drivers -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJabPhXAAoJEMsfJm/On5mBZb8P/3PXwpM7itFwnJgqjH9oy8fk x5NyPZgfaGZ2cWfny/yQJVcXHH25+TO9vJjwXzyN6D+IBkPKEpXH7yqJK5rk5uwQ UNDMkJhW6T4Wzp8yU3gsRXHe0bsFQUxhRPsexp1hITaTbk+/NXUqfNUQO575zIGG MKZOO6b1qXs4EVk5a7TXF6Ncb5qKtFCI/uRSP4dPI8+fQhi9gcwrSETtujGC4j2w qMbccnOk6B0LpYiufuO4epZh2npJ2OKL5Hkg/GzGZaqSjYwLsXVrU80rpy7jLMuP 57dL3E91veWj71JdLx6cm2vVQQmQUd0CURzX9AzpiTGiELIhgTJrr8njhl/1mSGH zOHNPJJ+hZw07gzO6Ca4CCgcZy9F0g/eRoP+aC3enMegztM27AT+Tdk7GvFkT9O7 pSGSi6oAVk0T0hdIiyzJspq4iHqMOxEfGhmUujo+lajP4dITDgAd5ZzuCO6PboEu eK+vXmyNICj8aWZYc+aGjYE8BL+B/vCknsXO0gYRdW91brOSbi3giZaZIrDWYnRh Jg5LL3j2UgQDMP08JIGJqdZwLwGTc1fwGe2i5jLC5joc3W8cVhyjbzmL8ro8/ms7 dtcz5dvT08yyFl8LYTCk9u5wEPDY7a3xZVgM15gJUWHRGZmktfGJJU9Uz031rfzT sEaJ+PPqaWkNC08lRgP2 =D+C1 -----END PGP SIGNATURE----- Merge tag 'hwmon-for-linus-v4.16' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging Pull hwmon updates from Guenter Roeck: - New driver for W83773G - Fan control support for PMBus drivers - Improvements and minor fixes in several drivers * tag 'hwmon-for-linus-v4.16' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (32 commits) hwmon: (dell-smm) Disable fan support for Dell Vostro 3360 hwmon: (dell-smm) Disable fan support for Dell Inspiron 7720 hwmon: (dell-smm) Enable broken functionality via "force" module param hwmon: (k10temp) Add temperature offset for Ryzen 1900X hwmon: (lm75) Fix trailing semicolon hwmon: (ina2xx) Fix access to uninitialized mutex hwmon: (pmbus/ir35221) Remove unnecessary scaling hwmon: (sht3x) wait predefined limits loading complete before access hwmon: (pmbus/ibm-cffps) Add dependency on LEDS_CLASS hwmon: (pmbus/cffps) Add led class device for power supply fault led hwmon: (pmbus) cffps: Add PMBUS_SKIP_STATUS_CHECK hwmon: (aspeed-pwm-tacho) Deassert reset in probe dt-bindings: hwmon: aspeed-pwm-tacho: Add reset node hwmon: (pmbus) cffps: Add debugfs entries hwmon: (pmbus) Export pmbus device debugfs directory entry hwmon: (w83773g) Fix fault detection and reporting hwmon: (hih6130) Fix documentation of struct hih6130 hwmon: (iio_hwmon) Fix documentation of struct iio_hwmon_state hwmon: (sht15) Fix parameter documentation of sht15_crc8() hwmon: (sht21) Fix documentation of struct sht21 ...
This commit is contained in:
commit
47d5cc5be3
@ -22,8 +22,9 @@ Required properties for pwm-tacho node:
|
||||
- compatible : should be "aspeed,ast2400-pwm-tacho" for AST2400 and
|
||||
"aspeed,ast2500-pwm-tacho" for AST2500.
|
||||
|
||||
- clocks : a fixed clock providing input clock frequency(PWM
|
||||
and Fan Tach clock)
|
||||
- clocks : phandle to clock provider with the clock number in the second cell
|
||||
|
||||
- resets : phandle to reset controller with the reset number in the second cell
|
||||
|
||||
fan subnode format:
|
||||
===================
|
||||
@ -48,19 +49,14 @@ Required properties for each child node:
|
||||
|
||||
Examples:
|
||||
|
||||
pwm_tacho_fixed_clk: fixedclk {
|
||||
compatible = "fixed-clock";
|
||||
#clock-cells = <0>;
|
||||
clock-frequency = <24000000>;
|
||||
};
|
||||
|
||||
pwm_tacho: pwmtachocontroller@1e786000 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
#cooling-cells = <2>;
|
||||
reg = <0x1E786000 0x1000>;
|
||||
compatible = "aspeed,ast2500-pwm-tacho";
|
||||
clocks = <&pwm_tacho_fixed_clk>;
|
||||
clocks = <&syscon ASPEED_CLK_APB>;
|
||||
resets = <&syscon ASPEED_RESET_PWM>;
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&pinctrl_pwm0_default &pinctrl_pwm1_default>;
|
||||
|
||||
|
@ -8,11 +8,6 @@ Supported chips:
|
||||
Datasheets:
|
||||
http://www.ti.com/lit/gpn/lm25056
|
||||
http://www.ti.com/lit/gpn/lm25056a
|
||||
* TI LM25063
|
||||
Prefix: 'lm25063'
|
||||
Addresses scanned: -
|
||||
Datasheet:
|
||||
To be announced
|
||||
* National Semiconductor LM25066
|
||||
Prefix: 'lm25066'
|
||||
Addresses scanned: -
|
||||
@ -42,7 +37,7 @@ Description
|
||||
-----------
|
||||
|
||||
This driver supports hardware monitoring for National Semiconductor / TI LM25056,
|
||||
LM25063, LM25066, LM5064, and LM5066/LM5066I Power Management, Monitoring,
|
||||
LM25066, LM5064, and LM5066/LM5066I Power Management, Monitoring,
|
||||
Control, and Protection ICs.
|
||||
|
||||
The driver is a client driver to the core PMBus driver. Please see
|
||||
@ -74,12 +69,8 @@ in1_input Measured input voltage.
|
||||
in1_average Average measured input voltage.
|
||||
in1_min Minimum input voltage.
|
||||
in1_max Maximum input voltage.
|
||||
in1_crit Critical high input voltage (LM25063 only).
|
||||
in1_lcrit Critical low input voltage (LM25063 only).
|
||||
in1_min_alarm Input voltage low alarm.
|
||||
in1_max_alarm Input voltage high alarm.
|
||||
in1_lcrit_alarm Input voltage critical low alarm (LM25063 only).
|
||||
in1_crit_alarm Input voltage critical high alarm. (LM25063 only).
|
||||
|
||||
in2_label "vmon"
|
||||
in2_input Measured voltage on VAUX pin
|
||||
@ -94,16 +85,12 @@ in3_input Measured output voltage.
|
||||
in3_average Average measured output voltage.
|
||||
in3_min Minimum output voltage.
|
||||
in3_min_alarm Output voltage low alarm.
|
||||
in3_highest Historical minimum output voltage (LM25063 only).
|
||||
in3_lowest Historical maximum output voltage (LM25063 only).
|
||||
|
||||
curr1_label "iin"
|
||||
curr1_input Measured input current.
|
||||
curr1_average Average measured input current.
|
||||
curr1_max Maximum input current.
|
||||
curr1_crit Critical input current (LM25063 only).
|
||||
curr1_max_alarm Input current high alarm.
|
||||
curr1_crit_alarm Input current critical high alarm (LM25063 only).
|
||||
|
||||
power1_label "pin"
|
||||
power1_input Measured input power.
|
||||
@ -113,11 +100,6 @@ power1_alarm Input power alarm
|
||||
power1_input_highest Historical maximum power.
|
||||
power1_reset_history Write any value to reset maximum power history.
|
||||
|
||||
power2_label "pout". LM25063 only.
|
||||
power2_input Measured output power.
|
||||
power2_max Maximum output power limit.
|
||||
power2_crit Critical output power limit.
|
||||
|
||||
temp1_input Measured temperature.
|
||||
temp1_max Maximum temperature.
|
||||
temp1_crit Critical high temperature.
|
||||
|
@ -17,8 +17,9 @@ management with temperature and remote voltage sensing. Various fan control
|
||||
features are provided, including PWM frequency control, temperature hysteresis,
|
||||
dual tachometer measurements, and fan health monitoring.
|
||||
|
||||
For dual rotor fan configuration, the MAX31785 exposes the slowest rotor of the
|
||||
two in the fan[1-4]_input attributes.
|
||||
For dual-rotor configurations the MAX31785A exposes the second rotor tachometer
|
||||
readings in attributes fan[5-8]_input. By contrast the MAX31785 only exposes
|
||||
the slowest rotor measurement, and does so in the fan[1-4]_input attributes.
|
||||
|
||||
Usage Notes
|
||||
-----------
|
||||
@ -31,7 +32,9 @@ Sysfs attributes
|
||||
|
||||
fan[1-4]_alarm Fan alarm.
|
||||
fan[1-4]_fault Fan fault.
|
||||
fan[1-4]_input Fan RPM.
|
||||
fan[1-8]_input Fan RPM. On the MAX31785A, inputs 5-8 correspond to the
|
||||
second rotor of fans 1-4
|
||||
fan[1-4]_target Fan input target
|
||||
|
||||
in[1-6]_crit Critical maximum output voltage
|
||||
in[1-6]_crit_alarm Output voltage critical high alarm
|
||||
@ -44,6 +47,12 @@ in[1-6]_max_alarm Output voltage high alarm
|
||||
in[1-6]_min Minimum output voltage
|
||||
in[1-6]_min_alarm Output voltage low alarm
|
||||
|
||||
pwm[1-4] Fan target duty cycle (0..255)
|
||||
pwm[1-4]_enable 0: Full-speed
|
||||
1: Manual PWM control
|
||||
2: Automatic PWM (tach-feedback RPM fan-control)
|
||||
3: Automatic closed-loop (temp-feedback fan-control)
|
||||
|
||||
temp[1-11]_crit Critical high temperature
|
||||
temp[1-11]_crit_alarm Chip temperature critical high alarm
|
||||
temp[1-11]_input Measured temperature
|
||||
|
33
Documentation/hwmon/w83773g
Normal file
33
Documentation/hwmon/w83773g
Normal file
@ -0,0 +1,33 @@
|
||||
Kernel driver w83773g
|
||||
====================
|
||||
|
||||
Supported chips:
|
||||
* Nuvoton W83773G
|
||||
Prefix: 'w83773g'
|
||||
Addresses scanned: I2C 0x4c and 0x4d
|
||||
Datasheet: https://www.nuvoton.com/resource-files/W83773G_SG_DatasheetV1_2.pdf
|
||||
|
||||
Authors:
|
||||
Lei YU <mine260309@gmail.com>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver implements support for Nuvoton W83773G temperature sensor
|
||||
chip. This chip implements one local and two remote sensors.
|
||||
The chip also features offsets for the two remote sensors which get added to
|
||||
the input readings. The chip does all the scaling by itself and the driver
|
||||
therefore reports true temperatures that don't need any user-space adjustments.
|
||||
Temperature is measured in degrees Celsius.
|
||||
The chip is wired over I2C/SMBus and specified over a temperature
|
||||
range of -40 to +125 degrees Celsius (for local sensor) and -40 to +127
|
||||
degrees Celsius (for remote sensors).
|
||||
Resolution for both the local and remote channels is 0.125 degree C.
|
||||
|
||||
The chip supports only temperature measurement. The driver exports
|
||||
the temperature values via the following sysfs files:
|
||||
|
||||
temp[1-3]_input
|
||||
temp[2-3]_fault
|
||||
temp[2-3]_offset
|
||||
update_interval
|
@ -26,11 +26,9 @@ if HWMON
|
||||
|
||||
config HWMON_VID
|
||||
tristate
|
||||
default n
|
||||
|
||||
config HWMON_DEBUG_CHIP
|
||||
bool "Hardware Monitoring Chip debugging messages"
|
||||
default n
|
||||
help
|
||||
Say Y here if you want the I2C chip drivers to produce a bunch of
|
||||
debug messages to the system log. Select this if you are having
|
||||
@ -42,7 +40,6 @@ comment "Native drivers"
|
||||
config SENSORS_AB8500
|
||||
tristate "AB8500 thermal monitoring"
|
||||
depends on AB8500_GPADC && AB8500_BM
|
||||
default n
|
||||
help
|
||||
If you say yes here you get support for the thermal sensor part
|
||||
of the AB8500 chip. The driver includes thermal management for
|
||||
@ -302,7 +299,6 @@ config SENSORS_APPLESMC
|
||||
select NEW_LEDS
|
||||
select LEDS_CLASS
|
||||
select INPUT_POLLDEV
|
||||
default n
|
||||
help
|
||||
This driver provides support for the Apple System Management
|
||||
Controller, which provides an accelerometer (Apple Sudden Motion
|
||||
@ -678,7 +674,6 @@ config SENSORS_JC42
|
||||
config SENSORS_POWR1220
|
||||
tristate "Lattice POWR1220 Power Monitoring"
|
||||
depends on I2C
|
||||
default n
|
||||
help
|
||||
If you say yes here you get access to the hardware monitoring
|
||||
functions of the Lattice POWR1220 isp Power Supply Monitoring,
|
||||
@ -702,7 +697,6 @@ config SENSORS_LTC2945
|
||||
tristate "Linear Technology LTC2945"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
default n
|
||||
help
|
||||
If you say yes here you get support for Linear Technology LTC2945
|
||||
I2C System Monitor.
|
||||
@ -727,7 +721,6 @@ config SENSORS_LTC2990
|
||||
config SENSORS_LTC4151
|
||||
tristate "Linear Technology LTC4151"
|
||||
depends on I2C
|
||||
default n
|
||||
help
|
||||
If you say yes here you get support for Linear Technology LTC4151
|
||||
High Voltage I2C Current and Voltage Monitor interface.
|
||||
@ -738,7 +731,6 @@ config SENSORS_LTC4151
|
||||
config SENSORS_LTC4215
|
||||
tristate "Linear Technology LTC4215"
|
||||
depends on I2C
|
||||
default n
|
||||
help
|
||||
If you say yes here you get support for Linear Technology LTC4215
|
||||
Hot Swap Controller I2C interface.
|
||||
@ -750,7 +742,6 @@ config SENSORS_LTC4222
|
||||
tristate "Linear Technology LTC4222"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
default n
|
||||
help
|
||||
If you say yes here you get support for Linear Technology LTC4222
|
||||
Dual Hot Swap Controller I2C interface.
|
||||
@ -761,7 +752,6 @@ config SENSORS_LTC4222
|
||||
config SENSORS_LTC4245
|
||||
tristate "Linear Technology LTC4245"
|
||||
depends on I2C
|
||||
default n
|
||||
help
|
||||
If you say yes here you get support for Linear Technology LTC4245
|
||||
Multiple Supply Hot Swap Controller I2C interface.
|
||||
@ -773,7 +763,6 @@ config SENSORS_LTC4260
|
||||
tristate "Linear Technology LTC4260"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
default n
|
||||
help
|
||||
If you say yes here you get support for Linear Technology LTC4260
|
||||
Positive Voltage Hot Swap Controller I2C interface.
|
||||
@ -784,7 +773,6 @@ config SENSORS_LTC4260
|
||||
config SENSORS_LTC4261
|
||||
tristate "Linear Technology LTC4261"
|
||||
depends on I2C
|
||||
default n
|
||||
help
|
||||
If you say yes here you get support for Linear Technology LTC4261
|
||||
Negative Voltage Hot Swap Controller I2C interface.
|
||||
@ -1276,7 +1264,6 @@ config SENSORS_NSA320
|
||||
config SENSORS_PCF8591
|
||||
tristate "Philips PCF8591 ADC/DAC"
|
||||
depends on I2C
|
||||
default n
|
||||
help
|
||||
If you say yes here you get support for Philips PCF8591 4-channel
|
||||
ADC, 1-channel DAC chips.
|
||||
@ -1459,7 +1446,6 @@ config SENSORS_SMSC47B397
|
||||
|
||||
config SENSORS_SCH56XX_COMMON
|
||||
tristate
|
||||
default n
|
||||
|
||||
config SENSORS_SCH5627
|
||||
tristate "SMSC SCH5627"
|
||||
@ -1505,7 +1491,6 @@ config SENSORS_STTS751
|
||||
config SENSORS_SMM665
|
||||
tristate "Summit Microelectronics SMM665"
|
||||
depends on I2C
|
||||
default n
|
||||
help
|
||||
If you say yes here you get support for the hardware monitoring
|
||||
features of the Summit Microelectronics SMM665/SMM665B Six-Channel
|
||||
@ -1725,6 +1710,16 @@ config SENSORS_VT8231
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called vt8231.
|
||||
|
||||
config SENSORS_W83773G
|
||||
tristate "Nuvoton W83773G"
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for the Nuvoton W83773G hardware
|
||||
monitoring chip.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called w83773g.
|
||||
|
||||
config SENSORS_W83781D
|
||||
tristate "Winbond W83781D, W83782D, W83783S, Asus AS99127F"
|
||||
depends on I2C
|
||||
@ -1782,7 +1777,6 @@ config SENSORS_W83795
|
||||
config SENSORS_W83795_FANCTRL
|
||||
bool "Include automatic fan control support (DANGEROUS)"
|
||||
depends on SENSORS_W83795
|
||||
default n
|
||||
help
|
||||
If you say yes here, support for automatic fan speed control
|
||||
will be included in the driver.
|
||||
|
@ -14,6 +14,7 @@ obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o
|
||||
# asb100, then w83781d go first, as they can override other drivers' addresses.
|
||||
obj-$(CONFIG_SENSORS_ASB100) += asb100.o
|
||||
obj-$(CONFIG_SENSORS_W83627HF) += w83627hf.o
|
||||
obj-$(CONFIG_SENSORS_W83773G) += w83773g.o
|
||||
obj-$(CONFIG_SENSORS_W83792D) += w83792d.o
|
||||
obj-$(CONFIG_SENSORS_W83793) += w83793.o
|
||||
obj-$(CONFIG_SENSORS_W83795) += w83795.o
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/thermal.h>
|
||||
|
||||
@ -181,6 +182,7 @@ struct aspeed_cooling_device {
|
||||
|
||||
struct aspeed_pwm_tacho_data {
|
||||
struct regmap *regmap;
|
||||
struct reset_control *rst;
|
||||
unsigned long clk_freq;
|
||||
bool pwm_present[8];
|
||||
bool fan_tach_present[16];
|
||||
@ -905,6 +907,13 @@ static int aspeed_create_fan(struct device *dev,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void aspeed_pwm_tacho_remove(void *data)
|
||||
{
|
||||
struct aspeed_pwm_tacho_data *priv = data;
|
||||
|
||||
reset_control_assert(priv->rst);
|
||||
}
|
||||
|
||||
static int aspeed_pwm_tacho_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
@ -931,6 +940,19 @@ static int aspeed_pwm_tacho_probe(struct platform_device *pdev)
|
||||
&aspeed_pwm_tacho_regmap_config);
|
||||
if (IS_ERR(priv->regmap))
|
||||
return PTR_ERR(priv->regmap);
|
||||
|
||||
priv->rst = devm_reset_control_get_exclusive(dev, NULL);
|
||||
if (IS_ERR(priv->rst)) {
|
||||
dev_err(dev,
|
||||
"missing or invalid reset controller device tree entry");
|
||||
return PTR_ERR(priv->rst);
|
||||
}
|
||||
reset_control_deassert(priv->rst);
|
||||
|
||||
ret = devm_add_action_or_reset(dev, aspeed_pwm_tacho_remove, priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
regmap_write(priv->regmap, ASPEED_PTCR_TACH_SOURCE, 0);
|
||||
regmap_write(priv->regmap, ASPEED_PTCR_TACH_SOURCE_EXT, 0);
|
||||
|
||||
|
@ -246,7 +246,8 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
|
||||
int err;
|
||||
u32 eax, edx;
|
||||
int i;
|
||||
struct pci_dev *host_bridge = pci_get_bus_and_slot(0, PCI_DEVFN(0, 0));
|
||||
u16 devfn = PCI_DEVFN(0, 0);
|
||||
struct pci_dev *host_bridge = pci_get_domain_bus_and_slot(0, 0, devfn);
|
||||
|
||||
/*
|
||||
* Explicit tjmax table entries override heuristics.
|
||||
|
@ -76,6 +76,7 @@ static uint i8k_fan_mult = I8K_FAN_MULT;
|
||||
static uint i8k_pwm_mult;
|
||||
static uint i8k_fan_max = I8K_FAN_HIGH;
|
||||
static bool disallow_fan_type_call;
|
||||
static bool disallow_fan_support;
|
||||
|
||||
#define I8K_HWMON_HAVE_TEMP1 (1 << 0)
|
||||
#define I8K_HWMON_HAVE_TEMP2 (1 << 1)
|
||||
@ -242,6 +243,9 @@ static int i8k_get_fan_status(int fan)
|
||||
{
|
||||
struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, };
|
||||
|
||||
if (disallow_fan_support)
|
||||
return -EINVAL;
|
||||
|
||||
regs.ebx = fan & 0xff;
|
||||
return i8k_smm(®s) ? : regs.eax & 0xff;
|
||||
}
|
||||
@ -253,6 +257,9 @@ static int i8k_get_fan_speed(int fan)
|
||||
{
|
||||
struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, };
|
||||
|
||||
if (disallow_fan_support)
|
||||
return -EINVAL;
|
||||
|
||||
regs.ebx = fan & 0xff;
|
||||
return i8k_smm(®s) ? : (regs.eax & 0xffff) * i8k_fan_mult;
|
||||
}
|
||||
@ -264,7 +271,7 @@ static int _i8k_get_fan_type(int fan)
|
||||
{
|
||||
struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, };
|
||||
|
||||
if (disallow_fan_type_call)
|
||||
if (disallow_fan_support || disallow_fan_type_call)
|
||||
return -EINVAL;
|
||||
|
||||
regs.ebx = fan & 0xff;
|
||||
@ -289,6 +296,9 @@ static int i8k_get_fan_nominal_speed(int fan, int speed)
|
||||
{
|
||||
struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, };
|
||||
|
||||
if (disallow_fan_support)
|
||||
return -EINVAL;
|
||||
|
||||
regs.ebx = (fan & 0xff) | (speed << 8);
|
||||
return i8k_smm(®s) ? : (regs.eax & 0xffff) * i8k_fan_mult;
|
||||
}
|
||||
@ -300,6 +310,9 @@ static int i8k_set_fan(int fan, int speed)
|
||||
{
|
||||
struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
|
||||
|
||||
if (disallow_fan_support)
|
||||
return -EINVAL;
|
||||
|
||||
speed = (speed < 0) ? 0 : ((speed > i8k_fan_max) ? i8k_fan_max : speed);
|
||||
regs.ebx = (fan & 0xff) | (speed << 8);
|
||||
|
||||
@ -772,6 +785,8 @@ static struct attribute *i8k_attrs[] = {
|
||||
static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr,
|
||||
int index)
|
||||
{
|
||||
if (disallow_fan_support && index >= 8)
|
||||
return 0;
|
||||
if (disallow_fan_type_call &&
|
||||
(index == 9 || index == 12 || index == 15))
|
||||
return 0;
|
||||
@ -1038,6 +1053,30 @@ static const struct dmi_system_id i8k_blacklist_fan_type_dmi_table[] __initconst
|
||||
{ }
|
||||
};
|
||||
|
||||
/*
|
||||
* On some machines all fan related SMM functions implemented by Dell BIOS
|
||||
* firmware freeze kernel for about 500ms. Until Dell fixes these problems fan
|
||||
* support for affected blacklisted Dell machines stay disabled.
|
||||
* See bug: https://bugzilla.kernel.org/show_bug.cgi?id=195751
|
||||
*/
|
||||
static struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initdata = {
|
||||
{
|
||||
.ident = "Dell Inspiron 7720",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 7720"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Dell Vostro 3360",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Vostro 3360"),
|
||||
},
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
/*
|
||||
* Probe for the presence of a supported laptop.
|
||||
*/
|
||||
@ -1060,8 +1099,17 @@ static int __init i8k_probe(void)
|
||||
i8k_get_dmi_data(DMI_BIOS_VERSION));
|
||||
}
|
||||
|
||||
if (dmi_check_system(i8k_blacklist_fan_type_dmi_table))
|
||||
disallow_fan_type_call = true;
|
||||
if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) {
|
||||
pr_warn("broken Dell BIOS detected, disallow fan support\n");
|
||||
if (!force)
|
||||
disallow_fan_support = true;
|
||||
}
|
||||
|
||||
if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) {
|
||||
pr_warn("broken Dell BIOS detected, disallow fan type call\n");
|
||||
if (!force)
|
||||
disallow_fan_type_call = true;
|
||||
}
|
||||
|
||||
strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
|
||||
sizeof(bios_version));
|
||||
|
@ -37,7 +37,7 @@
|
||||
|
||||
/**
|
||||
* struct hih6130 - HIH-6130 device specific data
|
||||
* @hwmon_dev: device registered with hwmon
|
||||
* @client: pointer to I2C client device
|
||||
* @lock: mutex to protect measurement values
|
||||
* @valid: only false before first measurement is taken
|
||||
* @last_update: time of last update (jiffies)
|
||||
|
@ -678,7 +678,7 @@ EXPORT_SYMBOL_GPL(hwmon_device_register_with_groups);
|
||||
* @dev: the parent device
|
||||
* @name: hwmon name attribute
|
||||
* @drvdata: driver data to attach to created device
|
||||
* @info: pointer to hwmon chip information
|
||||
* @chip: pointer to hwmon chip information
|
||||
* @extra_groups: pointer to list of additional non-standard attribute groups
|
||||
*
|
||||
* hwmon_device_unregister() must be called when the device is no
|
||||
@ -785,11 +785,11 @@ EXPORT_SYMBOL_GPL(devm_hwmon_device_register_with_groups);
|
||||
|
||||
/**
|
||||
* devm_hwmon_device_register_with_info - register w/ hwmon
|
||||
* @dev: the parent device
|
||||
* @name: hwmon name attribute
|
||||
* @drvdata: driver data to attach to created device
|
||||
* @info: Pointer to hwmon chip information
|
||||
* @groups - pointer to list of driver specific attribute groups
|
||||
* @dev: the parent device
|
||||
* @name: hwmon name attribute
|
||||
* @drvdata: driver data to attach to created device
|
||||
* @chip: pointer to hwmon chip information
|
||||
* @groups: pointer to list of driver specific attribute groups
|
||||
*
|
||||
* Returns the pointer to the new device. The new device is automatically
|
||||
* unregistered with the parent device.
|
||||
|
@ -23,7 +23,8 @@
|
||||
* @channels: filled with array of channels from iio
|
||||
* @num_channels: number of channels in channels (saves counting twice)
|
||||
* @hwmon_dev: associated hwmon device
|
||||
* @attr_group: the group of attributes
|
||||
* @attr_group: the group of attributes
|
||||
* @groups: null terminated array of attribute groups
|
||||
* @attrs: null terminated array of attribute pointers.
|
||||
*/
|
||||
struct iio_hwmon_state {
|
||||
|
@ -95,18 +95,20 @@ enum ina2xx_ids { ina219, ina226 };
|
||||
|
||||
struct ina2xx_config {
|
||||
u16 config_default;
|
||||
int calibration_factor;
|
||||
int calibration_value;
|
||||
int registers;
|
||||
int shunt_div;
|
||||
int bus_voltage_shift;
|
||||
int bus_voltage_lsb; /* uV */
|
||||
int power_lsb; /* uW */
|
||||
int power_lsb_factor;
|
||||
};
|
||||
|
||||
struct ina2xx_data {
|
||||
const struct ina2xx_config *config;
|
||||
|
||||
long rshunt;
|
||||
long current_lsb_uA;
|
||||
long power_lsb_uW;
|
||||
struct mutex config_lock;
|
||||
struct regmap *regmap;
|
||||
|
||||
@ -116,21 +118,21 @@ struct ina2xx_data {
|
||||
static const struct ina2xx_config ina2xx_config[] = {
|
||||
[ina219] = {
|
||||
.config_default = INA219_CONFIG_DEFAULT,
|
||||
.calibration_factor = 40960000,
|
||||
.calibration_value = 4096,
|
||||
.registers = INA219_REGISTERS,
|
||||
.shunt_div = 100,
|
||||
.bus_voltage_shift = 3,
|
||||
.bus_voltage_lsb = 4000,
|
||||
.power_lsb = 20000,
|
||||
.power_lsb_factor = 20,
|
||||
},
|
||||
[ina226] = {
|
||||
.config_default = INA226_CONFIG_DEFAULT,
|
||||
.calibration_factor = 5120000,
|
||||
.calibration_value = 2048,
|
||||
.registers = INA226_REGISTERS,
|
||||
.shunt_div = 400,
|
||||
.bus_voltage_shift = 0,
|
||||
.bus_voltage_lsb = 1250,
|
||||
.power_lsb = 25000,
|
||||
.power_lsb_factor = 25,
|
||||
},
|
||||
};
|
||||
|
||||
@ -169,12 +171,16 @@ static u16 ina226_interval_to_reg(int interval)
|
||||
return INA226_SHIFT_AVG(avg_bits);
|
||||
}
|
||||
|
||||
/*
|
||||
* Calibration register is set to the best value, which eliminates
|
||||
* truncation errors on calculating current register in hardware.
|
||||
* According to datasheet (eq. 3) the best values are 2048 for
|
||||
* ina226 and 4096 for ina219. They are hardcoded as calibration_value.
|
||||
*/
|
||||
static int ina2xx_calibrate(struct ina2xx_data *data)
|
||||
{
|
||||
u16 val = DIV_ROUND_CLOSEST(data->config->calibration_factor,
|
||||
data->rshunt);
|
||||
|
||||
return regmap_write(data->regmap, INA2XX_CALIBRATION, val);
|
||||
return regmap_write(data->regmap, INA2XX_CALIBRATION,
|
||||
data->config->calibration_value);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -187,10 +193,6 @@ static int ina2xx_init(struct ina2xx_data *data)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Set current LSB to 1mA, shunt is in uOhms
|
||||
* (equation 13 in datasheet).
|
||||
*/
|
||||
return ina2xx_calibrate(data);
|
||||
}
|
||||
|
||||
@ -268,15 +270,15 @@ static int ina2xx_get_value(struct ina2xx_data *data, u8 reg,
|
||||
val = DIV_ROUND_CLOSEST(val, 1000);
|
||||
break;
|
||||
case INA2XX_POWER:
|
||||
val = regval * data->config->power_lsb;
|
||||
val = regval * data->power_lsb_uW;
|
||||
break;
|
||||
case INA2XX_CURRENT:
|
||||
/* signed register, LSB=1mA (selected), in mA */
|
||||
val = (s16)regval;
|
||||
/* signed register, result in mA */
|
||||
val = regval * data->current_lsb_uA;
|
||||
val = DIV_ROUND_CLOSEST(val, 1000);
|
||||
break;
|
||||
case INA2XX_CALIBRATION:
|
||||
val = DIV_ROUND_CLOSEST(data->config->calibration_factor,
|
||||
regval);
|
||||
val = regval;
|
||||
break;
|
||||
default:
|
||||
/* programmer goofed */
|
||||
@ -304,9 +306,32 @@ static ssize_t ina2xx_show_value(struct device *dev,
|
||||
ina2xx_get_value(data, attr->index, regval));
|
||||
}
|
||||
|
||||
static ssize_t ina2xx_set_shunt(struct device *dev,
|
||||
struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
/*
|
||||
* In order to keep calibration register value fixed, the product
|
||||
* of current_lsb and shunt_resistor should also be fixed and equal
|
||||
* to shunt_voltage_lsb = 1 / shunt_div multiplied by 10^9 in order
|
||||
* to keep the scale.
|
||||
*/
|
||||
static int ina2xx_set_shunt(struct ina2xx_data *data, long val)
|
||||
{
|
||||
unsigned int dividend = DIV_ROUND_CLOSEST(1000000000,
|
||||
data->config->shunt_div);
|
||||
if (val <= 0 || val > dividend)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&data->config_lock);
|
||||
data->rshunt = val;
|
||||
data->current_lsb_uA = DIV_ROUND_CLOSEST(dividend, val);
|
||||
data->power_lsb_uW = data->config->power_lsb_factor *
|
||||
data->current_lsb_uA;
|
||||
mutex_unlock(&data->config_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t ina2xx_store_shunt(struct device *dev,
|
||||
struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
unsigned long val;
|
||||
int status;
|
||||
@ -316,18 +341,9 @@ static ssize_t ina2xx_set_shunt(struct device *dev,
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
if (val == 0 ||
|
||||
/* Values greater than the calibration factor make no sense. */
|
||||
val > data->config->calibration_factor)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&data->config_lock);
|
||||
data->rshunt = val;
|
||||
status = ina2xx_calibrate(data);
|
||||
mutex_unlock(&data->config_lock);
|
||||
status = ina2xx_set_shunt(data, val);
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
@ -387,7 +403,7 @@ static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ina2xx_show_value, NULL,
|
||||
|
||||
/* shunt resistance */
|
||||
static SENSOR_DEVICE_ATTR(shunt_resistor, S_IRUGO | S_IWUSR,
|
||||
ina2xx_show_value, ina2xx_set_shunt,
|
||||
ina2xx_show_value, ina2xx_store_shunt,
|
||||
INA2XX_CALIBRATION);
|
||||
|
||||
/* update interval (ina226 only) */
|
||||
@ -438,6 +454,7 @@ static int ina2xx_probe(struct i2c_client *client,
|
||||
|
||||
/* set the device type */
|
||||
data->config = &ina2xx_config[chip];
|
||||
mutex_init(&data->config_lock);
|
||||
|
||||
if (of_property_read_u32(dev->of_node, "shunt-resistor", &val) < 0) {
|
||||
struct ina2xx_platform_data *pdata = dev_get_platdata(dev);
|
||||
@ -448,10 +465,7 @@ static int ina2xx_probe(struct i2c_client *client,
|
||||
val = INA2XX_RSHUNT_DEFAULT;
|
||||
}
|
||||
|
||||
if (val <= 0 || val > data->config->calibration_factor)
|
||||
return -ENODEV;
|
||||
|
||||
data->rshunt = val;
|
||||
ina2xx_set_shunt(data, val);
|
||||
|
||||
ina2xx_regmap_config.max_register = data->config->registers;
|
||||
|
||||
@ -467,8 +481,6 @@ static int ina2xx_probe(struct i2c_client *client,
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
mutex_init(&data->config_lock);
|
||||
|
||||
data->groups[group++] = &ina2xx_group;
|
||||
if (id->driver_data == ina226)
|
||||
data->groups[group++] = &ina226_group;
|
||||
|
@ -86,6 +86,7 @@ static const struct tctl_offset tctl_offset_table[] = {
|
||||
{ 0x17, "AMD Ryzen 7 1800X", 20000 },
|
||||
{ 0x17, "AMD Ryzen Threadripper 1950X", 27000 },
|
||||
{ 0x17, "AMD Ryzen Threadripper 1920X", 27000 },
|
||||
{ 0x17, "AMD Ryzen Threadripper 1900X", 27000 },
|
||||
{ 0x17, "AMD Ryzen Threadripper 1950", 10000 },
|
||||
{ 0x17, "AMD Ryzen Threadripper 1920", 10000 },
|
||||
{ 0x17, "AMD Ryzen Threadripper 1910", 10000 },
|
||||
|
@ -100,7 +100,7 @@ static int lm75_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
switch (attr) {
|
||||
case hwmon_chip_update_interval:
|
||||
*val = data->sample_time;
|
||||
break;;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ config SENSORS_ADM1275
|
||||
|
||||
config SENSORS_IBM_CFFPS
|
||||
tristate "IBM Common Form Factor Power Supply"
|
||||
depends on LEDS_CLASS
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for the IBM
|
||||
Common Form Factor power supply.
|
||||
|
@ -8,12 +8,29 @@
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pmbus.h>
|
||||
|
||||
#include "pmbus.h"
|
||||
|
||||
#define CFFPS_FRU_CMD 0x9A
|
||||
#define CFFPS_PN_CMD 0x9B
|
||||
#define CFFPS_SN_CMD 0x9E
|
||||
#define CFFPS_CCIN_CMD 0xBD
|
||||
#define CFFPS_FW_CMD_START 0xFA
|
||||
#define CFFPS_FW_NUM_BYTES 4
|
||||
#define CFFPS_SYS_CONFIG_CMD 0xDA
|
||||
|
||||
#define CFFPS_INPUT_HISTORY_CMD 0xD6
|
||||
#define CFFPS_INPUT_HISTORY_SIZE 100
|
||||
|
||||
/* STATUS_MFR_SPECIFIC bits */
|
||||
#define CFFPS_MFR_FAN_FAULT BIT(0)
|
||||
#define CFFPS_MFR_THERMAL_FAULT BIT(1)
|
||||
@ -24,6 +41,153 @@
|
||||
#define CFFPS_MFR_VAUX_FAULT BIT(6)
|
||||
#define CFFPS_MFR_CURRENT_SHARE_WARNING BIT(7)
|
||||
|
||||
#define CFFPS_LED_BLINK BIT(0)
|
||||
#define CFFPS_LED_ON BIT(1)
|
||||
#define CFFPS_LED_OFF BIT(2)
|
||||
#define CFFPS_BLINK_RATE_MS 250
|
||||
|
||||
enum {
|
||||
CFFPS_DEBUGFS_INPUT_HISTORY = 0,
|
||||
CFFPS_DEBUGFS_FRU,
|
||||
CFFPS_DEBUGFS_PN,
|
||||
CFFPS_DEBUGFS_SN,
|
||||
CFFPS_DEBUGFS_CCIN,
|
||||
CFFPS_DEBUGFS_FW,
|
||||
CFFPS_DEBUGFS_NUM_ENTRIES
|
||||
};
|
||||
|
||||
struct ibm_cffps_input_history {
|
||||
struct mutex update_lock;
|
||||
unsigned long last_update;
|
||||
|
||||
u8 byte_count;
|
||||
u8 data[CFFPS_INPUT_HISTORY_SIZE];
|
||||
};
|
||||
|
||||
struct ibm_cffps {
|
||||
struct i2c_client *client;
|
||||
|
||||
struct ibm_cffps_input_history input_history;
|
||||
|
||||
int debugfs_entries[CFFPS_DEBUGFS_NUM_ENTRIES];
|
||||
|
||||
char led_name[32];
|
||||
u8 led_state;
|
||||
struct led_classdev led;
|
||||
};
|
||||
|
||||
#define to_psu(x, y) container_of((x), struct ibm_cffps, debugfs_entries[(y)])
|
||||
|
||||
static ssize_t ibm_cffps_read_input_history(struct ibm_cffps *psu,
|
||||
char __user *buf, size_t count,
|
||||
loff_t *ppos)
|
||||
{
|
||||
int rc;
|
||||
u8 msgbuf0[1] = { CFFPS_INPUT_HISTORY_CMD };
|
||||
u8 msgbuf1[CFFPS_INPUT_HISTORY_SIZE + 1] = { 0 };
|
||||
struct i2c_msg msg[2] = {
|
||||
{
|
||||
.addr = psu->client->addr,
|
||||
.flags = psu->client->flags,
|
||||
.len = 1,
|
||||
.buf = msgbuf0,
|
||||
}, {
|
||||
.addr = psu->client->addr,
|
||||
.flags = psu->client->flags | I2C_M_RD,
|
||||
.len = CFFPS_INPUT_HISTORY_SIZE + 1,
|
||||
.buf = msgbuf1,
|
||||
},
|
||||
};
|
||||
|
||||
if (!*ppos) {
|
||||
mutex_lock(&psu->input_history.update_lock);
|
||||
if (time_after(jiffies, psu->input_history.last_update + HZ)) {
|
||||
/*
|
||||
* Use a raw i2c transfer, since we need more bytes
|
||||
* than Linux I2C supports through smbus xfr (only 32).
|
||||
*/
|
||||
rc = i2c_transfer(psu->client->adapter, msg, 2);
|
||||
if (rc < 0) {
|
||||
mutex_unlock(&psu->input_history.update_lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
psu->input_history.byte_count = msgbuf1[0];
|
||||
memcpy(psu->input_history.data, &msgbuf1[1],
|
||||
CFFPS_INPUT_HISTORY_SIZE);
|
||||
psu->input_history.last_update = jiffies;
|
||||
}
|
||||
|
||||
mutex_unlock(&psu->input_history.update_lock);
|
||||
}
|
||||
|
||||
return simple_read_from_buffer(buf, count, ppos,
|
||||
psu->input_history.data,
|
||||
psu->input_history.byte_count);
|
||||
}
|
||||
|
||||
static ssize_t ibm_cffps_debugfs_op(struct file *file, char __user *buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
u8 cmd;
|
||||
int i, rc;
|
||||
int *idxp = file->private_data;
|
||||
int idx = *idxp;
|
||||
struct ibm_cffps *psu = to_psu(idxp, idx);
|
||||
char data[I2C_SMBUS_BLOCK_MAX] = { 0 };
|
||||
|
||||
switch (idx) {
|
||||
case CFFPS_DEBUGFS_INPUT_HISTORY:
|
||||
return ibm_cffps_read_input_history(psu, buf, count, ppos);
|
||||
case CFFPS_DEBUGFS_FRU:
|
||||
cmd = CFFPS_FRU_CMD;
|
||||
break;
|
||||
case CFFPS_DEBUGFS_PN:
|
||||
cmd = CFFPS_PN_CMD;
|
||||
break;
|
||||
case CFFPS_DEBUGFS_SN:
|
||||
cmd = CFFPS_SN_CMD;
|
||||
break;
|
||||
case CFFPS_DEBUGFS_CCIN:
|
||||
rc = i2c_smbus_read_word_swapped(psu->client, CFFPS_CCIN_CMD);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = snprintf(data, 5, "%04X", rc);
|
||||
goto done;
|
||||
case CFFPS_DEBUGFS_FW:
|
||||
for (i = 0; i < CFFPS_FW_NUM_BYTES; ++i) {
|
||||
rc = i2c_smbus_read_byte_data(psu->client,
|
||||
CFFPS_FW_CMD_START + i);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
snprintf(&data[i * 2], 3, "%02X", rc);
|
||||
}
|
||||
|
||||
rc = i * 2;
|
||||
goto done;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = i2c_smbus_read_block_data(psu->client, cmd, data);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
done:
|
||||
data[rc] = '\n';
|
||||
rc += 2;
|
||||
|
||||
return simple_read_from_buffer(buf, count, ppos, data, rc);
|
||||
}
|
||||
|
||||
static const struct file_operations ibm_cffps_fops = {
|
||||
.llseek = noop_llseek,
|
||||
.read = ibm_cffps_debugfs_op,
|
||||
.open = simple_open,
|
||||
};
|
||||
|
||||
static int ibm_cffps_read_byte_data(struct i2c_client *client, int page,
|
||||
int reg)
|
||||
{
|
||||
@ -105,6 +269,69 @@ static int ibm_cffps_read_word_data(struct i2c_client *client, int page,
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void ibm_cffps_led_brightness_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
int rc;
|
||||
struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led);
|
||||
|
||||
if (brightness == LED_OFF) {
|
||||
psu->led_state = CFFPS_LED_OFF;
|
||||
} else {
|
||||
brightness = LED_FULL;
|
||||
if (psu->led_state != CFFPS_LED_BLINK)
|
||||
psu->led_state = CFFPS_LED_ON;
|
||||
}
|
||||
|
||||
rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD,
|
||||
psu->led_state);
|
||||
if (rc < 0)
|
||||
return;
|
||||
|
||||
led_cdev->brightness = brightness;
|
||||
}
|
||||
|
||||
static int ibm_cffps_led_blink_set(struct led_classdev *led_cdev,
|
||||
unsigned long *delay_on,
|
||||
unsigned long *delay_off)
|
||||
{
|
||||
int rc;
|
||||
struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led);
|
||||
|
||||
psu->led_state = CFFPS_LED_BLINK;
|
||||
|
||||
if (led_cdev->brightness == LED_OFF)
|
||||
return 0;
|
||||
|
||||
rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD,
|
||||
CFFPS_LED_BLINK);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
*delay_on = CFFPS_BLINK_RATE_MS;
|
||||
*delay_off = CFFPS_BLINK_RATE_MS;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ibm_cffps_create_led_class(struct ibm_cffps *psu)
|
||||
{
|
||||
int rc;
|
||||
struct i2c_client *client = psu->client;
|
||||
struct device *dev = &client->dev;
|
||||
|
||||
snprintf(psu->led_name, sizeof(psu->led_name), "%s-%02x", client->name,
|
||||
client->addr);
|
||||
psu->led.name = psu->led_name;
|
||||
psu->led.max_brightness = LED_FULL;
|
||||
psu->led.brightness_set = ibm_cffps_led_brightness_set;
|
||||
psu->led.blink_set = ibm_cffps_led_blink_set;
|
||||
|
||||
rc = devm_led_classdev_register(dev, &psu->led);
|
||||
if (rc)
|
||||
dev_warn(dev, "failed to register led class: %d\n", rc);
|
||||
}
|
||||
|
||||
static struct pmbus_driver_info ibm_cffps_info = {
|
||||
.pages = 1,
|
||||
.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
|
||||
@ -116,10 +343,69 @@ static struct pmbus_driver_info ibm_cffps_info = {
|
||||
.read_word_data = ibm_cffps_read_word_data,
|
||||
};
|
||||
|
||||
static struct pmbus_platform_data ibm_cffps_pdata = {
|
||||
.flags = PMBUS_SKIP_STATUS_CHECK,
|
||||
};
|
||||
|
||||
static int ibm_cffps_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
return pmbus_do_probe(client, id, &ibm_cffps_info);
|
||||
int i, rc;
|
||||
struct dentry *debugfs;
|
||||
struct dentry *ibm_cffps_dir;
|
||||
struct ibm_cffps *psu;
|
||||
|
||||
client->dev.platform_data = &ibm_cffps_pdata;
|
||||
rc = pmbus_do_probe(client, id, &ibm_cffps_info);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/*
|
||||
* Don't fail the probe if there isn't enough memory for leds and
|
||||
* debugfs.
|
||||
*/
|
||||
psu = devm_kzalloc(&client->dev, sizeof(*psu), GFP_KERNEL);
|
||||
if (!psu)
|
||||
return 0;
|
||||
|
||||
psu->client = client;
|
||||
mutex_init(&psu->input_history.update_lock);
|
||||
psu->input_history.last_update = jiffies - HZ;
|
||||
|
||||
ibm_cffps_create_led_class(psu);
|
||||
|
||||
/* Don't fail the probe if we can't create debugfs */
|
||||
debugfs = pmbus_get_debugfs_dir(client);
|
||||
if (!debugfs)
|
||||
return 0;
|
||||
|
||||
ibm_cffps_dir = debugfs_create_dir(client->name, debugfs);
|
||||
if (!ibm_cffps_dir)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < CFFPS_DEBUGFS_NUM_ENTRIES; ++i)
|
||||
psu->debugfs_entries[i] = i;
|
||||
|
||||
debugfs_create_file("input_history", 0444, ibm_cffps_dir,
|
||||
&psu->debugfs_entries[CFFPS_DEBUGFS_INPUT_HISTORY],
|
||||
&ibm_cffps_fops);
|
||||
debugfs_create_file("fru", 0444, ibm_cffps_dir,
|
||||
&psu->debugfs_entries[CFFPS_DEBUGFS_FRU],
|
||||
&ibm_cffps_fops);
|
||||
debugfs_create_file("part_number", 0444, ibm_cffps_dir,
|
||||
&psu->debugfs_entries[CFFPS_DEBUGFS_PN],
|
||||
&ibm_cffps_fops);
|
||||
debugfs_create_file("serial_number", 0444, ibm_cffps_dir,
|
||||
&psu->debugfs_entries[CFFPS_DEBUGFS_SN],
|
||||
&ibm_cffps_fops);
|
||||
debugfs_create_file("ccin", 0444, ibm_cffps_dir,
|
||||
&psu->debugfs_entries[CFFPS_DEBUGFS_CCIN],
|
||||
&ibm_cffps_fops);
|
||||
debugfs_create_file("fw_version", 0444, ibm_cffps_dir,
|
||||
&psu->debugfs_entries[CFFPS_DEBUGFS_FW],
|
||||
&ibm_cffps_fops);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ibm_cffps_id[] = {
|
||||
|
@ -25,168 +25,19 @@
|
||||
#define IR35221_MFR_IOUT_VALLEY 0xcb
|
||||
#define IR35221_MFR_TEMP_VALLEY 0xcc
|
||||
|
||||
static long ir35221_reg2data(int data, enum pmbus_sensor_classes class)
|
||||
{
|
||||
s16 exponent;
|
||||
s32 mantissa;
|
||||
long val;
|
||||
|
||||
/* We only modify LINEAR11 formats */
|
||||
exponent = ((s16)data) >> 11;
|
||||
mantissa = ((s16)((data & 0x7ff) << 5)) >> 5;
|
||||
|
||||
val = mantissa * 1000L;
|
||||
|
||||
/* scale result to micro-units for power sensors */
|
||||
if (class == PSC_POWER)
|
||||
val = val * 1000L;
|
||||
|
||||
if (exponent >= 0)
|
||||
val <<= exponent;
|
||||
else
|
||||
val >>= -exponent;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
#define MAX_MANTISSA (1023 * 1000)
|
||||
#define MIN_MANTISSA (511 * 1000)
|
||||
|
||||
static u16 ir35221_data2reg(long val, enum pmbus_sensor_classes class)
|
||||
{
|
||||
s16 exponent = 0, mantissa;
|
||||
bool negative = false;
|
||||
|
||||
if (val == 0)
|
||||
return 0;
|
||||
|
||||
if (val < 0) {
|
||||
negative = true;
|
||||
val = -val;
|
||||
}
|
||||
|
||||
/* Power is in uW. Convert to mW before converting. */
|
||||
if (class == PSC_POWER)
|
||||
val = DIV_ROUND_CLOSEST(val, 1000L);
|
||||
|
||||
/* Reduce large mantissa until it fits into 10 bit */
|
||||
while (val >= MAX_MANTISSA && exponent < 15) {
|
||||
exponent++;
|
||||
val >>= 1;
|
||||
}
|
||||
/* Increase small mantissa to improve precision */
|
||||
while (val < MIN_MANTISSA && exponent > -15) {
|
||||
exponent--;
|
||||
val <<= 1;
|
||||
}
|
||||
|
||||
/* Convert mantissa from milli-units to units */
|
||||
mantissa = DIV_ROUND_CLOSEST(val, 1000);
|
||||
|
||||
/* Ensure that resulting number is within range */
|
||||
if (mantissa > 0x3ff)
|
||||
mantissa = 0x3ff;
|
||||
|
||||
/* restore sign */
|
||||
if (negative)
|
||||
mantissa = -mantissa;
|
||||
|
||||
/* Convert to 5 bit exponent, 11 bit mantissa */
|
||||
return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800);
|
||||
}
|
||||
|
||||
static u16 ir35221_scale_result(s16 data, int shift,
|
||||
enum pmbus_sensor_classes class)
|
||||
{
|
||||
long val;
|
||||
|
||||
val = ir35221_reg2data(data, class);
|
||||
|
||||
if (shift < 0)
|
||||
val >>= -shift;
|
||||
else
|
||||
val <<= shift;
|
||||
|
||||
return ir35221_data2reg(val, class);
|
||||
}
|
||||
|
||||
static int ir35221_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_IOUT_OC_FAULT_LIMIT:
|
||||
case PMBUS_IOUT_OC_WARN_LIMIT:
|
||||
ret = pmbus_read_word_data(client, page, reg);
|
||||
if (ret < 0)
|
||||
break;
|
||||
ret = ir35221_scale_result(ret, 1, PSC_CURRENT_OUT);
|
||||
break;
|
||||
case PMBUS_VIN_OV_FAULT_LIMIT:
|
||||
case PMBUS_VIN_OV_WARN_LIMIT:
|
||||
case PMBUS_VIN_UV_WARN_LIMIT:
|
||||
ret = pmbus_read_word_data(client, page, reg);
|
||||
ret = ir35221_scale_result(ret, -4, PSC_VOLTAGE_IN);
|
||||
break;
|
||||
case PMBUS_IIN_OC_WARN_LIMIT:
|
||||
ret = pmbus_read_word_data(client, page, reg);
|
||||
if (ret < 0)
|
||||
break;
|
||||
ret = ir35221_scale_result(ret, -1, PSC_CURRENT_IN);
|
||||
break;
|
||||
case PMBUS_READ_VIN:
|
||||
ret = pmbus_read_word_data(client, page, PMBUS_READ_VIN);
|
||||
if (ret < 0)
|
||||
break;
|
||||
ret = ir35221_scale_result(ret, -5, PSC_VOLTAGE_IN);
|
||||
break;
|
||||
case PMBUS_READ_IIN:
|
||||
ret = pmbus_read_word_data(client, page, PMBUS_READ_IIN);
|
||||
if (ret < 0)
|
||||
break;
|
||||
if (page == 0)
|
||||
ret = ir35221_scale_result(ret, -4, PSC_CURRENT_IN);
|
||||
else
|
||||
ret = ir35221_scale_result(ret, -5, PSC_CURRENT_IN);
|
||||
break;
|
||||
case PMBUS_READ_POUT:
|
||||
ret = pmbus_read_word_data(client, page, PMBUS_READ_POUT);
|
||||
if (ret < 0)
|
||||
break;
|
||||
ret = ir35221_scale_result(ret, -1, PSC_POWER);
|
||||
break;
|
||||
case PMBUS_READ_PIN:
|
||||
ret = pmbus_read_word_data(client, page, PMBUS_READ_PIN);
|
||||
if (ret < 0)
|
||||
break;
|
||||
ret = ir35221_scale_result(ret, -1, PSC_POWER);
|
||||
break;
|
||||
case PMBUS_READ_IOUT:
|
||||
ret = pmbus_read_word_data(client, page, PMBUS_READ_IOUT);
|
||||
if (ret < 0)
|
||||
break;
|
||||
if (page == 0)
|
||||
ret = ir35221_scale_result(ret, -1, PSC_CURRENT_OUT);
|
||||
else
|
||||
ret = ir35221_scale_result(ret, -2, PSC_CURRENT_OUT);
|
||||
break;
|
||||
case PMBUS_VIRT_READ_VIN_MAX:
|
||||
ret = pmbus_read_word_data(client, page, IR35221_MFR_VIN_PEAK);
|
||||
if (ret < 0)
|
||||
break;
|
||||
ret = ir35221_scale_result(ret, -5, PSC_VOLTAGE_IN);
|
||||
break;
|
||||
case PMBUS_VIRT_READ_VOUT_MAX:
|
||||
ret = pmbus_read_word_data(client, page, IR35221_MFR_VOUT_PEAK);
|
||||
break;
|
||||
case PMBUS_VIRT_READ_IOUT_MAX:
|
||||
ret = pmbus_read_word_data(client, page, IR35221_MFR_IOUT_PEAK);
|
||||
if (ret < 0)
|
||||
break;
|
||||
if (page == 0)
|
||||
ret = ir35221_scale_result(ret, -1, PSC_CURRENT_IN);
|
||||
else
|
||||
ret = ir35221_scale_result(ret, -2, PSC_CURRENT_IN);
|
||||
break;
|
||||
case PMBUS_VIRT_READ_TEMP_MAX:
|
||||
ret = pmbus_read_word_data(client, page, IR35221_MFR_TEMP_PEAK);
|
||||
@ -194,9 +45,6 @@ static int ir35221_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
case PMBUS_VIRT_READ_VIN_MIN:
|
||||
ret = pmbus_read_word_data(client, page,
|
||||
IR35221_MFR_VIN_VALLEY);
|
||||
if (ret < 0)
|
||||
break;
|
||||
ret = ir35221_scale_result(ret, -5, PSC_VOLTAGE_IN);
|
||||
break;
|
||||
case PMBUS_VIRT_READ_VOUT_MIN:
|
||||
ret = pmbus_read_word_data(client, page,
|
||||
@ -205,12 +53,6 @@ static int ir35221_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
case PMBUS_VIRT_READ_IOUT_MIN:
|
||||
ret = pmbus_read_word_data(client, page,
|
||||
IR35221_MFR_IOUT_VALLEY);
|
||||
if (ret < 0)
|
||||
break;
|
||||
if (page == 0)
|
||||
ret = ir35221_scale_result(ret, -1, PSC_CURRENT_IN);
|
||||
else
|
||||
ret = ir35221_scale_result(ret, -2, PSC_CURRENT_IN);
|
||||
break;
|
||||
case PMBUS_VIRT_READ_TEMP_MIN:
|
||||
ret = pmbus_read_word_data(client, page,
|
||||
@ -224,36 +66,6 @@ static int ir35221_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ir35221_write_word_data(struct i2c_client *client, int page, int reg,
|
||||
u16 word)
|
||||
{
|
||||
int ret;
|
||||
u16 val;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_IOUT_OC_FAULT_LIMIT:
|
||||
case PMBUS_IOUT_OC_WARN_LIMIT:
|
||||
val = ir35221_scale_result(word, -1, PSC_CURRENT_OUT);
|
||||
ret = pmbus_write_word_data(client, page, reg, val);
|
||||
break;
|
||||
case PMBUS_VIN_OV_FAULT_LIMIT:
|
||||
case PMBUS_VIN_OV_WARN_LIMIT:
|
||||
case PMBUS_VIN_UV_WARN_LIMIT:
|
||||
val = ir35221_scale_result(word, 4, PSC_VOLTAGE_IN);
|
||||
ret = pmbus_write_word_data(client, page, reg, val);
|
||||
break;
|
||||
case PMBUS_IIN_OC_WARN_LIMIT:
|
||||
val = ir35221_scale_result(word, 1, PSC_CURRENT_IN);
|
||||
ret = pmbus_write_word_data(client, page, reg, val);
|
||||
break;
|
||||
default:
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ir35221_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
@ -292,7 +104,6 @@ static int ir35221_probe(struct i2c_client *client,
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
info->write_word_data = ir35221_write_word_data;
|
||||
info->read_word_data = ir35221_read_word_data;
|
||||
|
||||
info->pages = 2;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Hardware monitoring driver for LM25056 / LM25063 / LM25066 / LM5064 / LM5066
|
||||
* Hardware monitoring driver for LM25056 / LM25066 / LM5064 / LM5066
|
||||
*
|
||||
* Copyright (c) 2011 Ericsson AB.
|
||||
* Copyright (c) 2013 Guenter Roeck
|
||||
@ -28,7 +28,7 @@
|
||||
#include <linux/i2c.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
enum chips { lm25056, lm25063, lm25066, lm5064, lm5066, lm5066i };
|
||||
enum chips { lm25056, lm25066, lm5064, lm5066, lm5066i };
|
||||
|
||||
#define LM25066_READ_VAUX 0xd0
|
||||
#define LM25066_MFR_READ_IIN 0xd1
|
||||
@ -53,11 +53,6 @@ enum chips { lm25056, lm25063, lm25066, lm5064, lm5066, lm5066i };
|
||||
#define LM25056_MFR_STS_VAUX_OV_WARN BIT(1)
|
||||
#define LM25056_MFR_STS_VAUX_UV_WARN BIT(0)
|
||||
|
||||
/* LM25063 only */
|
||||
|
||||
#define LM25063_READ_VOUT_MAX 0xe5
|
||||
#define LM25063_READ_VOUT_MIN 0xe6
|
||||
|
||||
struct __coeff {
|
||||
short m, b, R;
|
||||
};
|
||||
@ -122,36 +117,6 @@ static struct __coeff lm25066_coeff[6][PSC_NUM_CLASSES + 2] = {
|
||||
.m = 16,
|
||||
},
|
||||
},
|
||||
[lm25063] = {
|
||||
[PSC_VOLTAGE_IN] = {
|
||||
.m = 16000,
|
||||
.R = -2,
|
||||
},
|
||||
[PSC_VOLTAGE_OUT] = {
|
||||
.m = 16000,
|
||||
.R = -2,
|
||||
},
|
||||
[PSC_CURRENT_IN] = {
|
||||
.m = 10000,
|
||||
.R = -2,
|
||||
},
|
||||
[PSC_CURRENT_IN_L] = {
|
||||
.m = 10000,
|
||||
.R = -2,
|
||||
},
|
||||
[PSC_POWER] = {
|
||||
.m = 5000,
|
||||
.R = -3,
|
||||
},
|
||||
[PSC_POWER_L] = {
|
||||
.m = 5000,
|
||||
.R = -3,
|
||||
},
|
||||
[PSC_TEMPERATURE] = {
|
||||
.m = 15596,
|
||||
.R = -3,
|
||||
},
|
||||
},
|
||||
[lm5064] = {
|
||||
[PSC_VOLTAGE_IN] = {
|
||||
.m = 4611,
|
||||
@ -272,10 +237,6 @@ static int lm25066_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
/* VIN: 6.14 mV VAUX: 293 uV LSB */
|
||||
ret = DIV_ROUND_CLOSEST(ret * 293, 6140);
|
||||
break;
|
||||
case lm25063:
|
||||
/* VIN: 6.25 mV VAUX: 200.0 uV LSB */
|
||||
ret = DIV_ROUND_CLOSEST(ret * 20, 625);
|
||||
break;
|
||||
case lm25066:
|
||||
/* VIN: 4.54 mV VAUX: 283.2 uV LSB */
|
||||
ret = DIV_ROUND_CLOSEST(ret * 2832, 45400);
|
||||
@ -330,24 +291,6 @@ static int lm25066_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lm25063_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_VIRT_READ_VOUT_MAX:
|
||||
ret = pmbus_read_word_data(client, 0, LM25063_READ_VOUT_MAX);
|
||||
break;
|
||||
case PMBUS_VIRT_READ_VOUT_MIN:
|
||||
ret = pmbus_read_word_data(client, 0, LM25063_READ_VOUT_MIN);
|
||||
break;
|
||||
default:
|
||||
ret = lm25066_read_word_data(client, page, reg);
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lm25056_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
int ret;
|
||||
@ -502,11 +445,6 @@ static int lm25066_probe(struct i2c_client *client,
|
||||
info->read_word_data = lm25056_read_word_data;
|
||||
info->read_byte_data = lm25056_read_byte_data;
|
||||
data->rlimit = 0x0fff;
|
||||
} else if (data->id == lm25063) {
|
||||
info->func[0] |= PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
|
||||
| PMBUS_HAVE_POUT;
|
||||
info->read_word_data = lm25063_read_word_data;
|
||||
data->rlimit = 0xffff;
|
||||
} else {
|
||||
info->func[0] |= PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT;
|
||||
info->read_word_data = lm25066_read_word_data;
|
||||
@ -543,7 +481,6 @@ static int lm25066_probe(struct i2c_client *client,
|
||||
|
||||
static const struct i2c_device_id lm25066_id[] = {
|
||||
{"lm25056", lm25056},
|
||||
{"lm25063", lm25063},
|
||||
{"lm25066", lm25066},
|
||||
{"lm5064", lm5064},
|
||||
{"lm5066", lm5066},
|
||||
|
@ -16,12 +16,231 @@
|
||||
|
||||
enum max31785_regs {
|
||||
MFR_REVISION = 0x9b,
|
||||
MFR_FAN_CONFIG = 0xf1,
|
||||
};
|
||||
|
||||
#define MAX31785 0x3030
|
||||
#define MAX31785A 0x3040
|
||||
|
||||
#define MFR_FAN_CONFIG_DUAL_TACH BIT(12)
|
||||
|
||||
#define MAX31785_NR_PAGES 23
|
||||
#define MAX31785_NR_FAN_PAGES 6
|
||||
|
||||
static int max31785_read_byte_data(struct i2c_client *client, int page,
|
||||
int reg)
|
||||
{
|
||||
if (page < MAX31785_NR_PAGES)
|
||||
return -ENODATA;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_VOUT_MODE:
|
||||
return -ENOTSUPP;
|
||||
case PMBUS_FAN_CONFIG_12:
|
||||
return pmbus_read_byte_data(client, page - MAX31785_NR_PAGES,
|
||||
reg);
|
||||
}
|
||||
|
||||
return -ENODATA;
|
||||
}
|
||||
|
||||
static int max31785_write_byte(struct i2c_client *client, int page, u8 value)
|
||||
{
|
||||
if (page < MAX31785_NR_PAGES)
|
||||
return -ENODATA;
|
||||
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static int max31785_read_long_data(struct i2c_client *client, int page,
|
||||
int reg, u32 *data)
|
||||
{
|
||||
unsigned char cmdbuf[1];
|
||||
unsigned char rspbuf[4];
|
||||
int rc;
|
||||
|
||||
struct i2c_msg msg[2] = {
|
||||
{
|
||||
.addr = client->addr,
|
||||
.flags = 0,
|
||||
.len = sizeof(cmdbuf),
|
||||
.buf = cmdbuf,
|
||||
},
|
||||
{
|
||||
.addr = client->addr,
|
||||
.flags = I2C_M_RD,
|
||||
.len = sizeof(rspbuf),
|
||||
.buf = rspbuf,
|
||||
},
|
||||
};
|
||||
|
||||
cmdbuf[0] = reg;
|
||||
|
||||
rc = pmbus_set_page(client, page);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
*data = (rspbuf[0] << (0 * 8)) | (rspbuf[1] << (1 * 8)) |
|
||||
(rspbuf[2] << (2 * 8)) | (rspbuf[3] << (3 * 8));
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int max31785_get_pwm(struct i2c_client *client, int page)
|
||||
{
|
||||
int rv;
|
||||
|
||||
rv = pmbus_get_fan_rate_device(client, page, 0, percent);
|
||||
if (rv < 0)
|
||||
return rv;
|
||||
else if (rv >= 0x8000)
|
||||
return 0;
|
||||
else if (rv >= 0x2711)
|
||||
return 0x2710;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int max31785_get_pwm_mode(struct i2c_client *client, int page)
|
||||
{
|
||||
int config;
|
||||
int command;
|
||||
|
||||
config = pmbus_read_byte_data(client, page, PMBUS_FAN_CONFIG_12);
|
||||
if (config < 0)
|
||||
return config;
|
||||
|
||||
command = pmbus_read_word_data(client, page, PMBUS_FAN_COMMAND_1);
|
||||
if (command < 0)
|
||||
return command;
|
||||
|
||||
if (config & PB_FAN_1_RPM)
|
||||
return (command >= 0x8000) ? 3 : 2;
|
||||
|
||||
if (command >= 0x8000)
|
||||
return 3;
|
||||
else if (command >= 0x2711)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int max31785_read_word_data(struct i2c_client *client, int page,
|
||||
int reg)
|
||||
{
|
||||
u32 val;
|
||||
int rv;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_READ_FAN_SPEED_1:
|
||||
if (page < MAX31785_NR_PAGES)
|
||||
return -ENODATA;
|
||||
|
||||
rv = max31785_read_long_data(client, page - MAX31785_NR_PAGES,
|
||||
reg, &val);
|
||||
if (rv < 0)
|
||||
return rv;
|
||||
|
||||
rv = (val >> 16) & 0xffff;
|
||||
break;
|
||||
case PMBUS_FAN_COMMAND_1:
|
||||
/*
|
||||
* PMBUS_FAN_COMMAND_x is probed to judge whether or not to
|
||||
* expose fan control registers.
|
||||
*
|
||||
* Don't expose fan_target attribute for virtual pages.
|
||||
*/
|
||||
rv = (page >= MAX31785_NR_PAGES) ? -ENOTSUPP : -ENODATA;
|
||||
break;
|
||||
case PMBUS_VIRT_PWM_1:
|
||||
rv = max31785_get_pwm(client, page);
|
||||
break;
|
||||
case PMBUS_VIRT_PWM_ENABLE_1:
|
||||
rv = max31785_get_pwm_mode(client, page);
|
||||
break;
|
||||
default:
|
||||
rv = -ENODATA;
|
||||
break;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static inline u32 max31785_scale_pwm(u32 sensor_val)
|
||||
{
|
||||
/*
|
||||
* The datasheet describes the accepted value range for manual PWM as
|
||||
* [0, 0x2710], while the hwmon pwmX sysfs interface accepts values in
|
||||
* [0, 255]. The MAX31785 uses DIRECT mode to scale the FAN_COMMAND
|
||||
* registers and in PWM mode the coefficients are m=1, b=0, R=2. The
|
||||
* important observation here is that 0x2710 == 10000 == 100 * 100.
|
||||
*
|
||||
* R=2 (== 10^2 == 100) accounts for scaling the value provided at the
|
||||
* sysfs interface into the required hardware resolution, but it does
|
||||
* not yet yield a value that we can write to the device (this initial
|
||||
* scaling is handled by pmbus_data2reg()). Multiplying by 100 below
|
||||
* translates the parameter value into the percentage units required by
|
||||
* PMBus, and then we scale back by 255 as required by the hwmon pwmX
|
||||
* interface to yield the percentage value at the appropriate
|
||||
* resolution for hardware.
|
||||
*/
|
||||
return (sensor_val * 100) / 255;
|
||||
}
|
||||
|
||||
static int max31785_pwm_enable(struct i2c_client *client, int page,
|
||||
u16 word)
|
||||
{
|
||||
int config = 0;
|
||||
int rate;
|
||||
|
||||
switch (word) {
|
||||
case 0:
|
||||
rate = 0x7fff;
|
||||
break;
|
||||
case 1:
|
||||
rate = pmbus_get_fan_rate_cached(client, page, 0, percent);
|
||||
if (rate < 0)
|
||||
return rate;
|
||||
rate = max31785_scale_pwm(rate);
|
||||
break;
|
||||
case 2:
|
||||
config = PB_FAN_1_RPM;
|
||||
rate = pmbus_get_fan_rate_cached(client, page, 0, rpm);
|
||||
if (rate < 0)
|
||||
return rate;
|
||||
break;
|
||||
case 3:
|
||||
rate = 0xffff;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return pmbus_update_fan(client, page, 0, config, PB_FAN_1_RPM, rate);
|
||||
}
|
||||
|
||||
static int max31785_write_word_data(struct i2c_client *client, int page,
|
||||
int reg, u16 word)
|
||||
{
|
||||
switch (reg) {
|
||||
case PMBUS_VIRT_PWM_1:
|
||||
return pmbus_update_fan(client, page, 0, 0, PB_FAN_1_RPM,
|
||||
max31785_scale_pwm(word));
|
||||
case PMBUS_VIRT_PWM_ENABLE_1:
|
||||
return max31785_pwm_enable(client, page, word);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -ENODATA;
|
||||
}
|
||||
|
||||
#define MAX31785_FAN_FUNCS \
|
||||
(PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12)
|
||||
(PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12 | PMBUS_HAVE_PWM12)
|
||||
|
||||
#define MAX31785_TEMP_FUNCS \
|
||||
(PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP)
|
||||
@ -29,14 +248,26 @@ enum max31785_regs {
|
||||
#define MAX31785_VOUT_FUNCS \
|
||||
(PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT)
|
||||
|
||||
#define MAX37185_NUM_FAN_PAGES 6
|
||||
|
||||
static const struct pmbus_driver_info max31785_info = {
|
||||
.pages = MAX31785_NR_PAGES,
|
||||
|
||||
.write_word_data = max31785_write_word_data,
|
||||
.read_byte_data = max31785_read_byte_data,
|
||||
.read_word_data = max31785_read_word_data,
|
||||
.write_byte = max31785_write_byte,
|
||||
|
||||
/* RPM */
|
||||
.format[PSC_FAN] = direct,
|
||||
.m[PSC_FAN] = 1,
|
||||
.b[PSC_FAN] = 0,
|
||||
.R[PSC_FAN] = 0,
|
||||
/* PWM */
|
||||
.format[PSC_PWM] = direct,
|
||||
.m[PSC_PWM] = 1,
|
||||
.b[PSC_PWM] = 0,
|
||||
.R[PSC_PWM] = 2,
|
||||
.func[0] = MAX31785_FAN_FUNCS,
|
||||
.func[1] = MAX31785_FAN_FUNCS,
|
||||
.func[2] = MAX31785_FAN_FUNCS,
|
||||
@ -72,13 +303,46 @@ static const struct pmbus_driver_info max31785_info = {
|
||||
.func[22] = MAX31785_VOUT_FUNCS,
|
||||
};
|
||||
|
||||
static int max31785_configure_dual_tach(struct i2c_client *client,
|
||||
struct pmbus_driver_info *info)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX31785_NR_FAN_PAGES; i++) {
|
||||
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, MFR_FAN_CONFIG);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (ret & MFR_FAN_CONFIG_DUAL_TACH) {
|
||||
int virtual = MAX31785_NR_PAGES + i;
|
||||
|
||||
info->pages = virtual + 1;
|
||||
info->func[virtual] |= PMBUS_HAVE_FAN12;
|
||||
info->func[virtual] |= PMBUS_PAGE_VIRTUAL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max31785_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct pmbus_driver_info *info;
|
||||
bool dual_tach = false;
|
||||
s64 ret;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_WORD_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
info = devm_kzalloc(dev, sizeof(struct pmbus_driver_info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
@ -89,6 +353,25 @@ static int max31785_probe(struct i2c_client *client,
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, MFR_REVISION);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (ret == MAX31785A) {
|
||||
dual_tach = true;
|
||||
} else if (ret == MAX31785) {
|
||||
if (!strcmp("max31785a", id->name))
|
||||
dev_warn(dev, "Expected max3175a, found max31785: cannot provide secondary tachometer readings\n");
|
||||
} else {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (dual_tach) {
|
||||
ret = max31785_configure_dual_tach(client, info);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return pmbus_do_probe(client, id, info);
|
||||
}
|
||||
|
||||
@ -100,9 +383,18 @@ static const struct i2c_device_id max31785_id[] = {
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, max31785_id);
|
||||
|
||||
static const struct of_device_id max31785_of_match[] = {
|
||||
{ .compatible = "maxim,max31785" },
|
||||
{ .compatible = "maxim,max31785a" },
|
||||
{ },
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, max31785_of_match);
|
||||
|
||||
static struct i2c_driver max31785_driver = {
|
||||
.driver = {
|
||||
.name = "max31785",
|
||||
.of_match_table = max31785_of_match,
|
||||
},
|
||||
.probe = max31785_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
|
@ -190,6 +190,33 @@ enum pmbus_regs {
|
||||
PMBUS_VIRT_VMON_UV_FAULT_LIMIT,
|
||||
PMBUS_VIRT_VMON_OV_FAULT_LIMIT,
|
||||
PMBUS_VIRT_STATUS_VMON,
|
||||
|
||||
/*
|
||||
* RPM and PWM Fan control
|
||||
*
|
||||
* Drivers wanting to expose PWM control must define the behaviour of
|
||||
* PMBUS_VIRT_PWM_[1-4] and PMBUS_VIRT_PWM_ENABLE_[1-4] in the
|
||||
* {read,write}_word_data callback.
|
||||
*
|
||||
* pmbus core provides a default implementation for
|
||||
* PMBUS_VIRT_FAN_TARGET_[1-4].
|
||||
*
|
||||
* TARGET, PWM and PWM_ENABLE members must be defined sequentially;
|
||||
* pmbus core uses the difference between the provided register and
|
||||
* it's _1 counterpart to calculate the FAN/PWM ID.
|
||||
*/
|
||||
PMBUS_VIRT_FAN_TARGET_1,
|
||||
PMBUS_VIRT_FAN_TARGET_2,
|
||||
PMBUS_VIRT_FAN_TARGET_3,
|
||||
PMBUS_VIRT_FAN_TARGET_4,
|
||||
PMBUS_VIRT_PWM_1,
|
||||
PMBUS_VIRT_PWM_2,
|
||||
PMBUS_VIRT_PWM_3,
|
||||
PMBUS_VIRT_PWM_4,
|
||||
PMBUS_VIRT_PWM_ENABLE_1,
|
||||
PMBUS_VIRT_PWM_ENABLE_2,
|
||||
PMBUS_VIRT_PWM_ENABLE_3,
|
||||
PMBUS_VIRT_PWM_ENABLE_4,
|
||||
};
|
||||
|
||||
/*
|
||||
@ -223,6 +250,8 @@ enum pmbus_regs {
|
||||
#define PB_FAN_1_RPM BIT(6)
|
||||
#define PB_FAN_1_INSTALLED BIT(7)
|
||||
|
||||
enum pmbus_fan_mode { percent = 0, rpm };
|
||||
|
||||
/*
|
||||
* STATUS_BYTE, STATUS_WORD (lower)
|
||||
*/
|
||||
@ -313,6 +342,7 @@ enum pmbus_sensor_classes {
|
||||
PSC_POWER,
|
||||
PSC_TEMPERATURE,
|
||||
PSC_FAN,
|
||||
PSC_PWM,
|
||||
PSC_NUM_CLASSES /* Number of power sensor classes */
|
||||
};
|
||||
|
||||
@ -339,6 +369,10 @@ enum pmbus_sensor_classes {
|
||||
#define PMBUS_HAVE_STATUS_FAN34 BIT(17)
|
||||
#define PMBUS_HAVE_VMON BIT(18)
|
||||
#define PMBUS_HAVE_STATUS_VMON BIT(19)
|
||||
#define PMBUS_HAVE_PWM12 BIT(20)
|
||||
#define PMBUS_HAVE_PWM34 BIT(21)
|
||||
|
||||
#define PMBUS_PAGE_VIRTUAL BIT(31)
|
||||
|
||||
enum pmbus_data_format { linear = 0, direct, vid };
|
||||
enum vrm_version { vr11 = 0, vr12, vr13 };
|
||||
@ -421,5 +455,12 @@ int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id,
|
||||
int pmbus_do_remove(struct i2c_client *client);
|
||||
const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client
|
||||
*client);
|
||||
int pmbus_get_fan_rate_device(struct i2c_client *client, int page, int id,
|
||||
enum pmbus_fan_mode mode);
|
||||
int pmbus_get_fan_rate_cached(struct i2c_client *client, int page, int id,
|
||||
enum pmbus_fan_mode mode);
|
||||
int pmbus_update_fan(struct i2c_client *client, int page, int id,
|
||||
u8 config, u8 mask, u16 command);
|
||||
struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client);
|
||||
|
||||
#endif /* PMBUS_H */
|
||||
|
@ -65,6 +65,7 @@ struct pmbus_sensor {
|
||||
u16 reg; /* register */
|
||||
enum pmbus_sensor_classes class; /* sensor class */
|
||||
bool update; /* runtime sensor update needed */
|
||||
bool convert; /* Whether or not to apply linear/vid/direct */
|
||||
int data; /* Sensor data.
|
||||
Negative if there was a read error */
|
||||
};
|
||||
@ -129,6 +130,27 @@ struct pmbus_debugfs_entry {
|
||||
u8 reg;
|
||||
};
|
||||
|
||||
static const int pmbus_fan_rpm_mask[] = {
|
||||
PB_FAN_1_RPM,
|
||||
PB_FAN_2_RPM,
|
||||
PB_FAN_1_RPM,
|
||||
PB_FAN_2_RPM,
|
||||
};
|
||||
|
||||
static const int pmbus_fan_config_registers[] = {
|
||||
PMBUS_FAN_CONFIG_12,
|
||||
PMBUS_FAN_CONFIG_12,
|
||||
PMBUS_FAN_CONFIG_34,
|
||||
PMBUS_FAN_CONFIG_34
|
||||
};
|
||||
|
||||
static const int pmbus_fan_command_registers[] = {
|
||||
PMBUS_FAN_COMMAND_1,
|
||||
PMBUS_FAN_COMMAND_2,
|
||||
PMBUS_FAN_COMMAND_3,
|
||||
PMBUS_FAN_COMMAND_4,
|
||||
};
|
||||
|
||||
void pmbus_clear_cache(struct i2c_client *client)
|
||||
{
|
||||
struct pmbus_data *data = i2c_get_clientdata(client);
|
||||
@ -140,18 +162,27 @@ EXPORT_SYMBOL_GPL(pmbus_clear_cache);
|
||||
int pmbus_set_page(struct i2c_client *client, int page)
|
||||
{
|
||||
struct pmbus_data *data = i2c_get_clientdata(client);
|
||||
int rv = 0;
|
||||
int newpage;
|
||||
int rv;
|
||||
|
||||
if (page >= 0 && page != data->currpage) {
|
||||
if (page < 0 || page == data->currpage)
|
||||
return 0;
|
||||
|
||||
if (!(data->info->func[page] & PMBUS_PAGE_VIRTUAL)) {
|
||||
rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
|
||||
newpage = i2c_smbus_read_byte_data(client, PMBUS_PAGE);
|
||||
if (newpage != page)
|
||||
rv = -EIO;
|
||||
else
|
||||
data->currpage = page;
|
||||
if (rv < 0)
|
||||
return rv;
|
||||
|
||||
rv = i2c_smbus_read_byte_data(client, PMBUS_PAGE);
|
||||
if (rv < 0)
|
||||
return rv;
|
||||
|
||||
if (rv != page)
|
||||
return -EIO;
|
||||
}
|
||||
return rv;
|
||||
|
||||
data->currpage = page;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pmbus_set_page);
|
||||
|
||||
@ -198,6 +229,28 @@ int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pmbus_write_word_data);
|
||||
|
||||
|
||||
static int pmbus_write_virt_reg(struct i2c_client *client, int page, int reg,
|
||||
u16 word)
|
||||
{
|
||||
int bit;
|
||||
int id;
|
||||
int rv;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_VIRT_FAN_TARGET_1 ... PMBUS_VIRT_FAN_TARGET_4:
|
||||
id = reg - PMBUS_VIRT_FAN_TARGET_1;
|
||||
bit = pmbus_fan_rpm_mask[id];
|
||||
rv = pmbus_update_fan(client, page, id, bit, bit, word);
|
||||
break;
|
||||
default:
|
||||
rv = -ENXIO;
|
||||
break;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*
|
||||
* _pmbus_write_word_data() is similar to pmbus_write_word_data(), but checks if
|
||||
* a device specific mapping function exists and calls it if necessary.
|
||||
@ -214,11 +267,38 @@ static int _pmbus_write_word_data(struct i2c_client *client, int page, int reg,
|
||||
if (status != -ENODATA)
|
||||
return status;
|
||||
}
|
||||
|
||||
if (reg >= PMBUS_VIRT_BASE)
|
||||
return -ENXIO;
|
||||
return pmbus_write_virt_reg(client, page, reg, word);
|
||||
|
||||
return pmbus_write_word_data(client, page, reg, word);
|
||||
}
|
||||
|
||||
int pmbus_update_fan(struct i2c_client *client, int page, int id,
|
||||
u8 config, u8 mask, u16 command)
|
||||
{
|
||||
int from;
|
||||
int rv;
|
||||
u8 to;
|
||||
|
||||
from = pmbus_read_byte_data(client, page,
|
||||
pmbus_fan_config_registers[id]);
|
||||
if (from < 0)
|
||||
return from;
|
||||
|
||||
to = (from & ~mask) | (config & mask);
|
||||
if (to != from) {
|
||||
rv = pmbus_write_byte_data(client, page,
|
||||
pmbus_fan_config_registers[id], to);
|
||||
if (rv < 0)
|
||||
return rv;
|
||||
}
|
||||
|
||||
return _pmbus_write_word_data(client, page,
|
||||
pmbus_fan_command_registers[id], command);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pmbus_update_fan);
|
||||
|
||||
int pmbus_read_word_data(struct i2c_client *client, int page, u8 reg)
|
||||
{
|
||||
int rv;
|
||||
@ -231,6 +311,24 @@ int pmbus_read_word_data(struct i2c_client *client, int page, u8 reg)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pmbus_read_word_data);
|
||||
|
||||
static int pmbus_read_virt_reg(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
int rv;
|
||||
int id;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_VIRT_FAN_TARGET_1 ... PMBUS_VIRT_FAN_TARGET_4:
|
||||
id = reg - PMBUS_VIRT_FAN_TARGET_1;
|
||||
rv = pmbus_get_fan_rate_device(client, page, id, rpm);
|
||||
break;
|
||||
default:
|
||||
rv = -ENXIO;
|
||||
break;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*
|
||||
* _pmbus_read_word_data() is similar to pmbus_read_word_data(), but checks if
|
||||
* a device specific mapping function exists and calls it if necessary.
|
||||
@ -246,8 +344,10 @@ static int _pmbus_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
if (status != -ENODATA)
|
||||
return status;
|
||||
}
|
||||
|
||||
if (reg >= PMBUS_VIRT_BASE)
|
||||
return -ENXIO;
|
||||
return pmbus_read_virt_reg(client, page, reg);
|
||||
|
||||
return pmbus_read_word_data(client, page, reg);
|
||||
}
|
||||
|
||||
@ -312,6 +412,68 @@ static int _pmbus_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
return pmbus_read_byte_data(client, page, reg);
|
||||
}
|
||||
|
||||
static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page,
|
||||
int reg)
|
||||
{
|
||||
struct pmbus_sensor *sensor;
|
||||
|
||||
for (sensor = data->sensors; sensor; sensor = sensor->next) {
|
||||
if (sensor->page == page && sensor->reg == reg)
|
||||
return sensor;
|
||||
}
|
||||
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
static int pmbus_get_fan_rate(struct i2c_client *client, int page, int id,
|
||||
enum pmbus_fan_mode mode,
|
||||
bool from_cache)
|
||||
{
|
||||
struct pmbus_data *data = i2c_get_clientdata(client);
|
||||
bool want_rpm, have_rpm;
|
||||
struct pmbus_sensor *s;
|
||||
int config;
|
||||
int reg;
|
||||
|
||||
want_rpm = (mode == rpm);
|
||||
|
||||
if (from_cache) {
|
||||
reg = want_rpm ? PMBUS_VIRT_FAN_TARGET_1 : PMBUS_VIRT_PWM_1;
|
||||
s = pmbus_find_sensor(data, page, reg + id);
|
||||
if (IS_ERR(s))
|
||||
return PTR_ERR(s);
|
||||
|
||||
return s->data;
|
||||
}
|
||||
|
||||
config = pmbus_read_byte_data(client, page,
|
||||
pmbus_fan_config_registers[id]);
|
||||
if (config < 0)
|
||||
return config;
|
||||
|
||||
have_rpm = !!(config & pmbus_fan_rpm_mask[id]);
|
||||
if (want_rpm == have_rpm)
|
||||
return pmbus_read_word_data(client, page,
|
||||
pmbus_fan_command_registers[id]);
|
||||
|
||||
/* Can't sensibly map between RPM and PWM, just return zero */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pmbus_get_fan_rate_device(struct i2c_client *client, int page, int id,
|
||||
enum pmbus_fan_mode mode)
|
||||
{
|
||||
return pmbus_get_fan_rate(client, page, id, mode, false);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pmbus_get_fan_rate_device);
|
||||
|
||||
int pmbus_get_fan_rate_cached(struct i2c_client *client, int page, int id,
|
||||
enum pmbus_fan_mode mode)
|
||||
{
|
||||
return pmbus_get_fan_rate(client, page, id, mode, true);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pmbus_get_fan_rate_cached);
|
||||
|
||||
static void pmbus_clear_fault_page(struct i2c_client *client, int page)
|
||||
{
|
||||
_pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS);
|
||||
@ -513,7 +675,7 @@ static long pmbus_reg2data_direct(struct pmbus_data *data,
|
||||
/* X = 1/m * (Y * 10^-R - b) */
|
||||
R = -R;
|
||||
/* scale result to milli-units for everything but fans */
|
||||
if (sensor->class != PSC_FAN) {
|
||||
if (!(sensor->class == PSC_FAN || sensor->class == PSC_PWM)) {
|
||||
R += 3;
|
||||
b *= 1000;
|
||||
}
|
||||
@ -568,6 +730,9 @@ static long pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor)
|
||||
{
|
||||
long val;
|
||||
|
||||
if (!sensor->convert)
|
||||
return sensor->data;
|
||||
|
||||
switch (data->info->format[sensor->class]) {
|
||||
case direct:
|
||||
val = pmbus_reg2data_direct(data, sensor);
|
||||
@ -672,7 +837,7 @@ static u16 pmbus_data2reg_direct(struct pmbus_data *data,
|
||||
}
|
||||
|
||||
/* Calculate Y = (m * X + b) * 10^R */
|
||||
if (sensor->class != PSC_FAN) {
|
||||
if (!(sensor->class == PSC_FAN || sensor->class == PSC_PWM)) {
|
||||
R -= 3; /* Adjust R and b for data in milli-units */
|
||||
b *= 1000;
|
||||
}
|
||||
@ -703,6 +868,9 @@ static u16 pmbus_data2reg(struct pmbus_data *data,
|
||||
{
|
||||
u16 regval;
|
||||
|
||||
if (!sensor->convert)
|
||||
return val;
|
||||
|
||||
switch (data->info->format[sensor->class]) {
|
||||
case direct:
|
||||
regval = pmbus_data2reg_direct(data, sensor, val);
|
||||
@ -915,7 +1083,8 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data,
|
||||
const char *name, const char *type,
|
||||
int seq, int page, int reg,
|
||||
enum pmbus_sensor_classes class,
|
||||
bool update, bool readonly)
|
||||
bool update, bool readonly,
|
||||
bool convert)
|
||||
{
|
||||
struct pmbus_sensor *sensor;
|
||||
struct device_attribute *a;
|
||||
@ -925,12 +1094,18 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data,
|
||||
return NULL;
|
||||
a = &sensor->attribute;
|
||||
|
||||
snprintf(sensor->name, sizeof(sensor->name), "%s%d_%s",
|
||||
name, seq, type);
|
||||
if (type)
|
||||
snprintf(sensor->name, sizeof(sensor->name), "%s%d_%s",
|
||||
name, seq, type);
|
||||
else
|
||||
snprintf(sensor->name, sizeof(sensor->name), "%s%d",
|
||||
name, seq);
|
||||
|
||||
sensor->page = page;
|
||||
sensor->reg = reg;
|
||||
sensor->class = class;
|
||||
sensor->update = update;
|
||||
sensor->convert = convert;
|
||||
pmbus_dev_attr_init(a, sensor->name,
|
||||
readonly ? S_IRUGO : S_IRUGO | S_IWUSR,
|
||||
pmbus_show_sensor, pmbus_set_sensor);
|
||||
@ -1029,7 +1204,7 @@ static int pmbus_add_limit_attrs(struct i2c_client *client,
|
||||
curr = pmbus_add_sensor(data, name, l->attr, index,
|
||||
page, l->reg, attr->class,
|
||||
attr->update || l->update,
|
||||
false);
|
||||
false, true);
|
||||
if (!curr)
|
||||
return -ENOMEM;
|
||||
if (l->sbit && (info->func[page] & attr->sfunc)) {
|
||||
@ -1068,7 +1243,7 @@ static int pmbus_add_sensor_attrs_one(struct i2c_client *client,
|
||||
return ret;
|
||||
}
|
||||
base = pmbus_add_sensor(data, name, "input", index, page, attr->reg,
|
||||
attr->class, true, true);
|
||||
attr->class, true, true, true);
|
||||
if (!base)
|
||||
return -ENOMEM;
|
||||
if (attr->sfunc) {
|
||||
@ -1592,13 +1767,6 @@ static const int pmbus_fan_registers[] = {
|
||||
PMBUS_READ_FAN_SPEED_4
|
||||
};
|
||||
|
||||
static const int pmbus_fan_config_registers[] = {
|
||||
PMBUS_FAN_CONFIG_12,
|
||||
PMBUS_FAN_CONFIG_12,
|
||||
PMBUS_FAN_CONFIG_34,
|
||||
PMBUS_FAN_CONFIG_34
|
||||
};
|
||||
|
||||
static const int pmbus_fan_status_registers[] = {
|
||||
PMBUS_STATUS_FAN_12,
|
||||
PMBUS_STATUS_FAN_12,
|
||||
@ -1621,6 +1789,42 @@ static const u32 pmbus_fan_status_flags[] = {
|
||||
};
|
||||
|
||||
/* Fans */
|
||||
|
||||
/* Precondition: FAN_CONFIG_x_y and FAN_COMMAND_x must exist for the fan ID */
|
||||
static int pmbus_add_fan_ctrl(struct i2c_client *client,
|
||||
struct pmbus_data *data, int index, int page, int id,
|
||||
u8 config)
|
||||
{
|
||||
struct pmbus_sensor *sensor;
|
||||
|
||||
sensor = pmbus_add_sensor(data, "fan", "target", index, page,
|
||||
PMBUS_VIRT_FAN_TARGET_1 + id, PSC_FAN,
|
||||
false, false, true);
|
||||
|
||||
if (!sensor)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!((data->info->func[page] & PMBUS_HAVE_PWM12) ||
|
||||
(data->info->func[page] & PMBUS_HAVE_PWM34)))
|
||||
return 0;
|
||||
|
||||
sensor = pmbus_add_sensor(data, "pwm", NULL, index, page,
|
||||
PMBUS_VIRT_PWM_1 + id, PSC_PWM,
|
||||
false, false, true);
|
||||
|
||||
if (!sensor)
|
||||
return -ENOMEM;
|
||||
|
||||
sensor = pmbus_add_sensor(data, "pwm", "enable", index, page,
|
||||
PMBUS_VIRT_PWM_ENABLE_1 + id, PSC_PWM,
|
||||
true, false, false);
|
||||
|
||||
if (!sensor)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pmbus_add_fan_attributes(struct i2c_client *client,
|
||||
struct pmbus_data *data)
|
||||
{
|
||||
@ -1655,9 +1859,18 @@ static int pmbus_add_fan_attributes(struct i2c_client *client,
|
||||
|
||||
if (pmbus_add_sensor(data, "fan", "input", index,
|
||||
page, pmbus_fan_registers[f],
|
||||
PSC_FAN, true, true) == NULL)
|
||||
PSC_FAN, true, true, true) == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Fan control */
|
||||
if (pmbus_check_word_register(client, page,
|
||||
pmbus_fan_command_registers[f])) {
|
||||
ret = pmbus_add_fan_ctrl(client, data, index,
|
||||
page, f, regval);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Each fan status register covers multiple fans,
|
||||
* so we have to do some magic.
|
||||
@ -2168,6 +2381,14 @@ int pmbus_do_remove(struct i2c_client *client)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pmbus_do_remove);
|
||||
|
||||
struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client)
|
||||
{
|
||||
struct pmbus_data *data = i2c_get_clientdata(client);
|
||||
|
||||
return data->debugfs;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pmbus_get_debugfs_dir);
|
||||
|
||||
static int __init pmbus_core_init(void)
|
||||
{
|
||||
pmbus_debugfs_dir = debugfs_create_dir("pmbus", NULL);
|
||||
|
@ -179,6 +179,7 @@ struct sht15_data {
|
||||
* sht15_crc8() - compute crc8
|
||||
* @data: sht15 specific data.
|
||||
* @value: sht15 retrieved data.
|
||||
* @len: Length of retrieved data
|
||||
*
|
||||
* This implements section 2 of the CRC datasheet.
|
||||
*/
|
||||
|
@ -41,7 +41,7 @@
|
||||
|
||||
/**
|
||||
* struct sht21 - SHT21 device specific data
|
||||
* @hwmon_dev: device registered with hwmon
|
||||
* @client: I2C client device
|
||||
* @lock: mutex to protect measurement values
|
||||
* @last_update: time of last update (jiffies)
|
||||
* @temperature: cached temperature measurement value
|
||||
|
@ -732,6 +732,13 @@ static int sht3x_probe(struct i2c_client *client,
|
||||
mutex_init(&data->i2c_lock);
|
||||
mutex_init(&data->data_lock);
|
||||
|
||||
/*
|
||||
* An attempt to read limits register too early
|
||||
* causes a NACK response from the chip.
|
||||
* Waiting for an empirical delay of 500 us solves the issue.
|
||||
*/
|
||||
usleep_range(500, 600);
|
||||
|
||||
ret = limits_update(data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
329
drivers/hwmon/w83773g.c
Normal file
329
drivers/hwmon/w83773g.c
Normal file
@ -0,0 +1,329 @@
|
||||
/*
|
||||
* Copyright (C) 2017 IBM Corp.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Driver for the Nuvoton W83773G SMBus temperature sensor IC.
|
||||
* Supported models: W83773G
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
/* W83773 has 3 channels */
|
||||
#define W83773_CHANNELS 3
|
||||
|
||||
/* The W83773 registers */
|
||||
#define W83773_CONVERSION_RATE_REG_READ 0x04
|
||||
#define W83773_CONVERSION_RATE_REG_WRITE 0x0A
|
||||
#define W83773_MANUFACTURER_ID_REG 0xFE
|
||||
#define W83773_LOCAL_TEMP 0x00
|
||||
|
||||
static const u8 W83773_STATUS[2] = { 0x02, 0x17 };
|
||||
|
||||
static const u8 W83773_TEMP_LSB[2] = { 0x10, 0x25 };
|
||||
static const u8 W83773_TEMP_MSB[2] = { 0x01, 0x24 };
|
||||
|
||||
static const u8 W83773_OFFSET_LSB[2] = { 0x12, 0x16 };
|
||||
static const u8 W83773_OFFSET_MSB[2] = { 0x11, 0x15 };
|
||||
|
||||
/* this is the number of sensors in the device */
|
||||
static const struct i2c_device_id w83773_id[] = {
|
||||
{ "w83773g" },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, w83773_id);
|
||||
|
||||
static const struct of_device_id w83773_of_match[] = {
|
||||
{
|
||||
.compatible = "nuvoton,w83773g"
|
||||
},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, w83773_of_match);
|
||||
|
||||
static inline long temp_of_local(s8 reg)
|
||||
{
|
||||
return reg * 1000;
|
||||
}
|
||||
|
||||
static inline long temp_of_remote(s8 hb, u8 lb)
|
||||
{
|
||||
return (hb << 3 | lb >> 5) * 125;
|
||||
}
|
||||
|
||||
static int get_local_temp(struct regmap *regmap, long *val)
|
||||
{
|
||||
unsigned int regval;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(regmap, W83773_LOCAL_TEMP, ®val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = temp_of_local(regval);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_remote_temp(struct regmap *regmap, int index, long *val)
|
||||
{
|
||||
unsigned int regval_high;
|
||||
unsigned int regval_low;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(regmap, W83773_TEMP_MSB[index], ®val_high);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read(regmap, W83773_TEMP_LSB[index], ®val_low);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = temp_of_remote(regval_high, regval_low);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_fault(struct regmap *regmap, int index, long *val)
|
||||
{
|
||||
unsigned int regval;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(regmap, W83773_STATUS[index], ®val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = (regval & 0x04) >> 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_offset(struct regmap *regmap, int index, long *val)
|
||||
{
|
||||
unsigned int regval_high;
|
||||
unsigned int regval_low;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(regmap, W83773_OFFSET_MSB[index], ®val_high);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read(regmap, W83773_OFFSET_LSB[index], ®val_low);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = temp_of_remote(regval_high, regval_low);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_offset(struct regmap *regmap, int index, long val)
|
||||
{
|
||||
int ret;
|
||||
u8 high_byte;
|
||||
u8 low_byte;
|
||||
|
||||
val = clamp_val(val, -127825, 127825);
|
||||
/* offset value equals to (high_byte << 3 | low_byte >> 5) * 125 */
|
||||
val /= 125;
|
||||
high_byte = val >> 3;
|
||||
low_byte = (val & 0x07) << 5;
|
||||
|
||||
ret = regmap_write(regmap, W83773_OFFSET_MSB[index], high_byte);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return regmap_write(regmap, W83773_OFFSET_LSB[index], low_byte);
|
||||
}
|
||||
|
||||
static int get_update_interval(struct regmap *regmap, long *val)
|
||||
{
|
||||
unsigned int regval;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(regmap, W83773_CONVERSION_RATE_REG_READ, ®val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = 16000 >> regval;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_update_interval(struct regmap *regmap, long val)
|
||||
{
|
||||
int rate;
|
||||
|
||||
/*
|
||||
* For valid rates, interval can be calculated as
|
||||
* interval = (1 << (8 - rate)) * 62.5;
|
||||
* Rounded rate is therefore
|
||||
* rate = 8 - __fls(interval * 8 / (62.5 * 7));
|
||||
* Use clamp_val() to avoid overflows, and to ensure valid input
|
||||
* for __fls.
|
||||
*/
|
||||
val = clamp_val(val, 62, 16000) * 10;
|
||||
rate = 8 - __fls((val * 8 / (625 * 7)));
|
||||
return regmap_write(regmap, W83773_CONVERSION_RATE_REG_WRITE, rate);
|
||||
}
|
||||
|
||||
static int w83773_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct regmap *regmap = dev_get_drvdata(dev);
|
||||
|
||||
if (type == hwmon_chip) {
|
||||
if (attr == hwmon_chip_update_interval)
|
||||
return get_update_interval(regmap, val);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
if (channel == 0)
|
||||
return get_local_temp(regmap, val);
|
||||
return get_remote_temp(regmap, channel - 1, val);
|
||||
case hwmon_temp_fault:
|
||||
return get_fault(regmap, channel - 1, val);
|
||||
case hwmon_temp_offset:
|
||||
return get_offset(regmap, channel - 1, val);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int w83773_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val)
|
||||
{
|
||||
struct regmap *regmap = dev_get_drvdata(dev);
|
||||
|
||||
if (type == hwmon_chip && attr == hwmon_chip_update_interval)
|
||||
return set_update_interval(regmap, val);
|
||||
|
||||
if (type == hwmon_temp && attr == hwmon_temp_offset)
|
||||
return set_offset(regmap, channel - 1, val);
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static umode_t w83773_is_visible(const void *data, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_chip:
|
||||
switch (attr) {
|
||||
case hwmon_chip_update_interval:
|
||||
return 0644;
|
||||
}
|
||||
break;
|
||||
case hwmon_temp:
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
case hwmon_temp_fault:
|
||||
return 0444;
|
||||
case hwmon_temp_offset:
|
||||
return 0644;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const u32 w83773_chip_config[] = {
|
||||
HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL,
|
||||
0
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info w83773_chip = {
|
||||
.type = hwmon_chip,
|
||||
.config = w83773_chip_config,
|
||||
};
|
||||
|
||||
static const u32 w83773_temp_config[] = {
|
||||
HWMON_T_INPUT,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_OFFSET,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_OFFSET,
|
||||
0
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info w83773_temp = {
|
||||
.type = hwmon_temp,
|
||||
.config = w83773_temp_config,
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info *w83773_info[] = {
|
||||
&w83773_chip,
|
||||
&w83773_temp,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_ops w83773_ops = {
|
||||
.is_visible = w83773_is_visible,
|
||||
.read = w83773_read,
|
||||
.write = w83773_write,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info w83773_chip_info = {
|
||||
.ops = &w83773_ops,
|
||||
.info = w83773_info,
|
||||
};
|
||||
|
||||
static const struct regmap_config w83773_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
};
|
||||
|
||||
static int w83773_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct device *hwmon_dev;
|
||||
struct regmap *regmap;
|
||||
int ret;
|
||||
|
||||
regmap = devm_regmap_init_i2c(client, &w83773_regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(dev, "failed to allocate register map\n");
|
||||
return PTR_ERR(regmap);
|
||||
}
|
||||
|
||||
/* Set the conversion rate to 2 Hz */
|
||||
ret = regmap_write(regmap, W83773_CONVERSION_RATE_REG_WRITE, 0x05);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "error writing config rate register\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, regmap);
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev,
|
||||
client->name,
|
||||
regmap,
|
||||
&w83773_chip_info,
|
||||
NULL);
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
static struct i2c_driver w83773_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "w83773g",
|
||||
.of_match_table = of_match_ptr(w83773_of_match),
|
||||
},
|
||||
.probe = w83773_probe,
|
||||
.id_table = w83773_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(w83773_driver);
|
||||
|
||||
MODULE_AUTHOR("Lei YU <mine260309@gmail.com>");
|
||||
MODULE_DESCRIPTION("W83773G temperature sensor driver");
|
||||
MODULE_LICENSE("GPL");
|
Loading…
Reference in New Issue
Block a user