mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-24 20:54:10 +08:00
- Support for new chips
- Add support for IT8620E, IT8781F, IT8786E, and IT8790E to it87 driver - Add driver for NCT7904 - Functional improvements - Support for new devicetree structure in ibmpowernv driver - Register pwm-fan and gpio-fan drivers as thermal cooling devices - Various minor cleanup and improvements to it87, nct6775, jc42, ibmpex, and coretemp drivers -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJVKv4fAAoJEMsfJm/On5mByigP/1Sp6k/k/BibUDWeNbst5Fq6 YxD5y622k8Q4Yh+sABxmFd+adAqLn+6qD7kQCanmkGOMGr97ABcjLeFiHwwYwXii LPrrY9JmyLkL97D9yKu+hcUJN9Mb3nfIeg5ED4RqbVLFcShWfLDY5YGhjPhzmNk+ kj03fRIl+DeHdMtpEYQS52hSrAqX6eIl/Xgt/UNkx/phlpOVxRvvlZUJPLjtwk8i yCE6muObAJDz2lxSw92Ep6nEqQdy0SGm1ySIabBjv569iWM/Ml/8FF0RYirS2d7j dV1L27JmhPoLIjdVMrF4qHHyyZhfLMgQlKsmoCInd5TFnKZ2rmeZfd6Ria2+mdL9 6R/xq4dM4exTSiN9jEWuN2xfqoHgBLnhEz13f1Xr/yTt+3JH0XELeyi/4l5pL6DO mqvelZNWNV0oapGWe2SDnuoGJgSxhnAKEZGGkWOTattZVvHFH3J7BlzKzdwttRN8 ipKxV3dv6OkK92/qA2xfuGnH03VahpG9oS7mC4n1YA0ZMOKNwVVhLN8mjDiHY+I1 GOjUA1V/j+RiC581KnTCW3qXaE7uKLgVlxqXVbs3M+NR0Jq75FmtNpjo6+zifCrh dCBrpfv0R6BXgp6ZeFPwX+R+hCiF19uuqIKH+Y6u7QOk2WyacOzAQPtuWc52bg6F lWtb7N+VM468Xo3j8pXA =RD/q -----END PGP SIGNATURE----- Merge tag 'hwmon-for-linus-v4.1' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging Pull hwmon updates from Guenter Roeck: "Support for new chips: - add support for IT8620E, IT8781F, IT8786E, and IT8790E to it87 driver - add driver for NCT7904 Functional improvements: - support for new devicetree structure in ibmpowernv driver - register pwm-fan and gpio-fan drivers as thermal cooling devices - various minor cleanup and improvements to it87, nct6775, jc42, ibmpex, and coretemp drivers" * tag 'hwmon-for-linus-v4.1' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (43 commits) hwmon: (pwm-fan) Update the duty cycle inorder to control the pwm-fan hwmon: (it87) Use feature macros on sio_data hwmon: (ibmpowernv) Fix build error seen for some configurations hwmon: (gpio-fan) Move the thermal registration after registration is complete hwmon: (ibmpowernv) pretty print labels hwmon: (ibmpowernv) add a label attribute hwmon: (ibmpowernv) add support for the new device tree hwmon: (ibmpowernv) add a helper routine create_hwmon_attr hwmon: (it87) Add support for 6th fan of IT8620E hwmon: (it87) Add support for IT8620E hwmon: (it87) Add support for IT8790E hwmon: (it87) Introduce feature flag to reflect internal in7 sensor hwmon: (it87) Introduce configuration field for chip suffix hwmon: (it87) Fix PWM frequency display for chips with newer PWM control hwmon: (it87) Fix pwm sysfs attribute removal hwmon: (ibmpowernv) do not use the OPAL index for hwmon attribute names hwmon: (ibmpowernv) change create_hwmon_attr_name() prototype hwmon: (ibmpowernv) add a convert_opal_attr_name() routine hwmon: (ibmpowernv) add a get_sensor_type() routine hwmon: (ibmpowernv) replace AMBIENT_TEMP by TEMP ...
This commit is contained in:
commit
4b5ca74113
@ -2,15 +2,20 @@ Bindings for fan connected to GPIO lines
|
||||
|
||||
Required properties:
|
||||
- compatible : "gpio-fan"
|
||||
|
||||
Optional properties:
|
||||
- gpios: Specifies the pins that map to bits in the control value,
|
||||
ordered MSB-->LSB.
|
||||
- gpio-fan,speed-map: A mapping of possible fan RPM speeds and the
|
||||
control value that should be set to achieve them. This array
|
||||
must have the RPM values in ascending order.
|
||||
|
||||
Optional properties:
|
||||
- alarm-gpios: This pin going active indicates something is wrong with
|
||||
the fan, and a udev event will be fired.
|
||||
- cooling-cells: If used as a cooling device, must be <2>
|
||||
Also see: Documentation/devicetree/bindings/thermal/thermal.txt
|
||||
min and max states are derived from the speed-map of the fan.
|
||||
|
||||
Note: At least one the "gpios" or "alarm-gpios" properties must be set.
|
||||
|
||||
Examples:
|
||||
|
||||
@ -23,3 +28,13 @@ Examples:
|
||||
6000 2>;
|
||||
alarm-gpios = <&gpio1 15 1>;
|
||||
};
|
||||
gpio_fan_cool: gpio_fan {
|
||||
compatible = "gpio-fan";
|
||||
gpios = <&gpio2 14 1
|
||||
&gpio2 13 1>;
|
||||
gpio-fan,speed-map = <0 0>,
|
||||
<3000 1>,
|
||||
<6000 2>;
|
||||
alarm-gpios = <&gpio2 15 1>;
|
||||
#cooling-cells = <2>; /* min followed by max */
|
||||
};
|
||||
|
@ -6,6 +6,10 @@ Supported chips:
|
||||
Prefix: 'it8603'
|
||||
Addresses scanned: from Super I/O config space (8 I/O ports)
|
||||
Datasheet: Not publicly available
|
||||
* IT8620E
|
||||
Prefix: 'it8620'
|
||||
Addresses scanned: from Super I/O config space (8 I/O ports)
|
||||
Datasheet: Not publicly available
|
||||
* IT8705F
|
||||
Prefix: 'it87'
|
||||
Addresses scanned: from Super I/O config space (8 I/O ports)
|
||||
@ -42,6 +46,10 @@ Supported chips:
|
||||
Prefix: 'it8772'
|
||||
Addresses scanned: from Super I/O config space (8 I/O ports)
|
||||
Datasheet: Not publicly available
|
||||
* IT8781F
|
||||
Prefix: 'it8781'
|
||||
Addresses scanned: from Super I/O config space (8 I/O ports)
|
||||
Datasheet: Not publicly available
|
||||
* IT8782F
|
||||
Prefix: 'it8782'
|
||||
Addresses scanned: from Super I/O config space (8 I/O ports)
|
||||
@ -50,6 +58,14 @@ Supported chips:
|
||||
Prefix: 'it8783'
|
||||
Addresses scanned: from Super I/O config space (8 I/O ports)
|
||||
Datasheet: Not publicly available
|
||||
* IT8786E
|
||||
Prefix: 'it8786'
|
||||
Addresses scanned: from Super I/O config space (8 I/O ports)
|
||||
Datasheet: Not publicly available
|
||||
* IT8790E
|
||||
Prefix: 'it8790'
|
||||
Addresses scanned: from Super I/O config space (8 I/O ports)
|
||||
Datasheet: Not publicly available
|
||||
* SiS950 [clone of IT8705F]
|
||||
Prefix: 'it87'
|
||||
Addresses scanned: from Super I/O config space (8 I/O ports)
|
||||
@ -94,9 +110,10 @@ motherboard models.
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver implements support for the IT8603E, IT8623E, IT8705F, IT8712F,
|
||||
IT8716F, IT8718F, IT8720F, IT8721F, IT8726F, IT8728F, IT8758E, IT8771E,
|
||||
IT8772E, IT8782F, IT8783E/F, and SiS950 chips.
|
||||
This driver implements support for the IT8603E, IT8620E, IT8623E, IT8705F,
|
||||
IT8712F, IT8716F, IT8718F, IT8720F, IT8721F, IT8726F, IT8728F, IT8758E,
|
||||
IT8771E, IT8772E, IT8781F, IT8782F, IT8783E/F, IT8786E, IT8790E, and SiS950
|
||||
chips.
|
||||
|
||||
These chips are 'Super I/O chips', supporting floppy disks, infrared ports,
|
||||
joysticks and other miscellaneous stuff. For hardware monitoring, they
|
||||
@ -120,11 +137,11 @@ The IT8716F, IT8718F, IT8720F, IT8721F/IT8758E and later IT8712F revisions
|
||||
have support for 2 additional fans. The additional fans are supported by the
|
||||
driver.
|
||||
|
||||
The IT8716F, IT8718F, IT8720F, IT8721F/IT8758E, IT8782F, IT8783E/F, and late
|
||||
IT8712F and IT8705F also have optional 16-bit tachometer counters for fans 1 to
|
||||
3. This is better (no more fan clock divider mess) but not compatible with the
|
||||
older chips and revisions. The 16-bit tachometer mode is enabled by the driver
|
||||
when one of the above chips is detected.
|
||||
The IT8716F, IT8718F, IT8720F, IT8721F/IT8758E, IT8781F, IT8782F, IT8783E/F,
|
||||
and late IT8712F and IT8705F also have optional 16-bit tachometer counters
|
||||
for fans 1 to 3. This is better (no more fan clock divider mess) but not
|
||||
compatible with the older chips and revisions. The 16-bit tachometer mode
|
||||
is enabled by the driver when one of the above chips is detected.
|
||||
|
||||
The IT8726F is just bit enhanced IT8716F with additional hardware
|
||||
for AMD power sequencing. Therefore the chip will appear as IT8716F
|
||||
@ -134,8 +151,13 @@ The IT8728F, IT8771E, and IT8772E are considered compatible with the IT8721F,
|
||||
until a datasheet becomes available (hopefully.)
|
||||
|
||||
The IT8603E/IT8623E is a custom design, hardware monitoring part is similar to
|
||||
IT8728F. It only supports 16-bit fan mode, the full speed mode of the
|
||||
fan is not supported (value 0 of pwmX_enable).
|
||||
IT8728F. It only supports 3 fans, 16-bit fan mode, and the full speed mode
|
||||
of the fan is not supported (value 0 of pwmX_enable).
|
||||
|
||||
The IT8620E is another custom design, hardware monitoring part is similar to
|
||||
IT8728F. It only supports 16-bit fan mode.
|
||||
|
||||
The IT8790E supports up to 3 fans. 16-bit fan mode is always enabled.
|
||||
|
||||
Temperatures are measured in degrees Celsius. An alarm is triggered once
|
||||
when the Overtemperature Shutdown limit is crossed.
|
||||
@ -156,10 +178,10 @@ inputs can measure voltages between 0 and 4.08 volts, with a resolution of
|
||||
0.016 volt (except IT8603E, IT8721F/IT8758E and IT8728F: 0.012 volt.) The
|
||||
battery voltage in8 does not have limit registers.
|
||||
|
||||
On the IT8603E, IT8721F/IT8758E, IT8782F, and IT8783E/F, some voltage inputs
|
||||
are internal and scaled inside the chip:
|
||||
On the IT8603E, IT8721F/IT8758E, IT8781F, IT8782F, and IT8783E/F, some
|
||||
voltage inputs are internal and scaled inside the chip:
|
||||
* in3 (optional)
|
||||
* in7 (optional for IT8782F and IT8783E/F)
|
||||
* in7 (optional for IT8781F, IT8782F, and IT8783E/F)
|
||||
* in8 (always)
|
||||
* in9 (relevant for IT8603E only)
|
||||
The driver handles this transparently so user-space doesn't have to care.
|
||||
|
@ -11,12 +11,10 @@ Supported chips:
|
||||
http://www.atmel.com/Images/doc8711.pdf
|
||||
http://www.atmel.com/Images/Atmel-8852-SEEPROM-AT30TSE002A-Datasheet.pdf
|
||||
http://www.atmel.com/Images/Atmel-8868-DTS-AT30TSE004A-Datasheet.pdf
|
||||
* IDT TSE2002B3, TSE2002GB2, TS3000B3, TS3000GB2
|
||||
* IDT TSE2002B3, TSE2002GB2, TSE2004GB2, TS3000B3, TS3000GB0, TS3000GB2,
|
||||
TS3001GB2
|
||||
Datasheets:
|
||||
http://www.idt.com/sites/default/files/documents/IDT_TSE2002B3C_DST_20100512_120303152056.pdf
|
||||
http://www.idt.com/sites/default/files/documents/IDT_TSE2002GB2A1_DST_20111107_120303145914.pdf
|
||||
http://www.idt.com/sites/default/files/documents/IDT_TS3000B3A_DST_20101129_120303152013.pdf
|
||||
http://www.idt.com/sites/default/files/documents/IDT_TS3000GB2A1_DST_20111104_120303151012.pdf
|
||||
Available from IDT web site
|
||||
* Maxim MAX6604
|
||||
Datasheets:
|
||||
http://datasheets.maxim-ic.com/en/ds/MAX6604.pdf
|
||||
|
60
Documentation/hwmon/nct7904
Normal file
60
Documentation/hwmon/nct7904
Normal file
@ -0,0 +1,60 @@
|
||||
Kernel driver nct7904
|
||||
====================
|
||||
|
||||
Supported chip:
|
||||
* Nuvoton NCT7904D
|
||||
Prefix: nct7904
|
||||
Addresses: I2C 0x2d, 0x2e
|
||||
Datasheet: Publicly available at Nuvoton website
|
||||
http://www.nuvoton.com/
|
||||
|
||||
Author: Vadim V. Vlasov <vvlasov@dev.rtsoft.ru>
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The NCT7904D is a hardware monitor supporting up to 20 voltage sensors,
|
||||
internal temperature sensor, Intel PECI and AMD SB-TSI CPU temperature
|
||||
interface, up to 12 fan tachometer inputs, up to 4 fan control channels
|
||||
with SmartFan.
|
||||
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
Currently, the driver supports only the following features:
|
||||
|
||||
in[1-20]_input Input voltage measurements (mV)
|
||||
|
||||
fan[1-12]_input Fan tachometer measurements (rpm)
|
||||
|
||||
temp1_input Local temperature (1/1000 degree,
|
||||
0.125 degree resolution)
|
||||
|
||||
temp[2-9]_input CPU temperatures (1/1000 degree,
|
||||
0.125 degree resolution)
|
||||
|
||||
fan[1-4]_mode R/W, 0/1 for manual or SmartFan mode
|
||||
Setting SmartFan mode is supported only if it has been
|
||||
previously configured by BIOS (or configuration EEPROM)
|
||||
|
||||
fan[1-4]_pwm R/O in SmartFan mode, R/W in manual control mode
|
||||
|
||||
The driver checks sensor control registers and does not export the sensors
|
||||
that are not enabled. Anyway, a sensor that is enabled may actually be not
|
||||
connected and thus provide zero readings.
|
||||
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
The following features are not supported in current version:
|
||||
|
||||
- SmartFan control
|
||||
- Watchdog
|
||||
- GPIO
|
||||
- external temperature sensors
|
||||
- SMI
|
||||
- min/max values
|
||||
- many other...
|
@ -510,6 +510,7 @@ config SENSORS_G762
|
||||
config SENSORS_GPIO_FAN
|
||||
tristate "GPIO fan"
|
||||
depends on GPIOLIB
|
||||
depends on THERMAL || THERMAL=n
|
||||
help
|
||||
If you say yes here you get support for fans connected to GPIO lines.
|
||||
|
||||
@ -599,8 +600,8 @@ config SENSORS_IT87
|
||||
help
|
||||
If you say yes here you get support for ITE IT8705F, IT8712F,
|
||||
IT8716F, IT8718F, IT8720F, IT8721F, IT8726F, IT8728F, IT8758E,
|
||||
IT8771E, IT8772E, IT8782F, IT8783E/F and IT8603E sensor chips,
|
||||
and the SiS950 clone.
|
||||
IT8771E, IT8772E, IT8781F, IT8782F, IT8783E/F, IT8786E, IT8790E,
|
||||
IT8603E, IT8620E, and IT8623E sensor chips, and the SiS950 clone.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called it87.
|
||||
@ -624,7 +625,7 @@ config SENSORS_JC42
|
||||
mobile devices and servers. Support will include, but not be limited
|
||||
to, ADT7408, AT30TS00, CAT34TS02, CAT6095, MAX6604, MCP9804, MCP9805,
|
||||
MCP98242, MCP98243, MCP98244, MCP9843, SE97, SE98, STTS424(E),
|
||||
STTS2002, STTS3000, TSE2002B3, TSE2002GB2, TS3000B3, and TS3000GB2.
|
||||
STTS2002, STTS3000, TSE2002, TSE2004, TS3000, and TS3001.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called jc42.
|
||||
@ -1145,6 +1146,16 @@ config SENSORS_NCT7802
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called nct7802.
|
||||
|
||||
config SENSORS_NCT7904
|
||||
tristate "Nuvoton NCT7904"
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for the Nuvoton NCT7904
|
||||
hardware monitoring chip, including manual fan speed control.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called nct7904.
|
||||
|
||||
config SENSORS_PCF8591
|
||||
tristate "Philips PCF8591 ADC/DAC"
|
||||
depends on I2C
|
||||
@ -1164,6 +1175,7 @@ source drivers/hwmon/pmbus/Kconfig
|
||||
config SENSORS_PWM_FAN
|
||||
tristate "PWM fan"
|
||||
depends on (PWM && OF) || COMPILE_TEST
|
||||
depends on THERMAL || THERMAL=n
|
||||
help
|
||||
If you say yes here you get support for fans connected to PWM lines.
|
||||
The driver uses the generic PWM interface, thus it will work on a
|
||||
|
@ -120,6 +120,7 @@ obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
|
||||
obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o
|
||||
obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o
|
||||
obj-$(CONFIG_SENSORS_NCT7802) += nct7802.o
|
||||
obj-$(CONFIG_SENSORS_NCT7904) += nct7904.o
|
||||
obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o
|
||||
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
|
||||
obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
|
||||
|
@ -397,14 +397,13 @@ static int create_core_attrs(struct temp_data *tdata, struct device *dev,
|
||||
struct device_attribute *devattr, char *buf) = {
|
||||
show_label, show_crit_alarm, show_temp, show_tjmax,
|
||||
show_ttarget };
|
||||
static const char *const names[TOTAL_ATTRS] = {
|
||||
"temp%d_label", "temp%d_crit_alarm",
|
||||
"temp%d_input", "temp%d_crit",
|
||||
"temp%d_max" };
|
||||
static const char *const suffixes[TOTAL_ATTRS] = {
|
||||
"label", "crit_alarm", "input", "crit", "max"
|
||||
};
|
||||
|
||||
for (i = 0; i < tdata->attr_size; i++) {
|
||||
snprintf(tdata->attr_name[i], CORETEMP_NAME_LENGTH, names[i],
|
||||
attr_no);
|
||||
snprintf(tdata->attr_name[i], CORETEMP_NAME_LENGTH,
|
||||
"temp%d_%s", attr_no, suffixes[i]);
|
||||
sysfs_attr_init(&tdata->sd_attrs[i].dev_attr.attr);
|
||||
tdata->sd_attrs[i].dev_attr.attr.name = tdata->attr_name[i];
|
||||
tdata->sd_attrs[i].dev_attr.attr.mode = S_IRUGO;
|
||||
|
@ -34,10 +34,13 @@
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/thermal.h>
|
||||
|
||||
struct gpio_fan_data {
|
||||
struct platform_device *pdev;
|
||||
struct device *hwmon_dev;
|
||||
/* Cooling device if any */
|
||||
struct thermal_cooling_device *cdev;
|
||||
struct mutex lock; /* lock GPIOs operations. */
|
||||
int num_ctrl;
|
||||
unsigned *ctrl;
|
||||
@ -387,6 +390,53 @@ static int fan_ctrl_init(struct gpio_fan_data *fan_data,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gpio_fan_get_max_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
struct gpio_fan_data *fan_data = cdev->devdata;
|
||||
|
||||
if (!fan_data)
|
||||
return -EINVAL;
|
||||
|
||||
*state = fan_data->num_speed - 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gpio_fan_get_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
struct gpio_fan_data *fan_data = cdev->devdata;
|
||||
int r;
|
||||
|
||||
if (!fan_data)
|
||||
return -EINVAL;
|
||||
|
||||
r = get_fan_speed_index(fan_data);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*state = r;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gpio_fan_set_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long state)
|
||||
{
|
||||
struct gpio_fan_data *fan_data = cdev->devdata;
|
||||
|
||||
if (!fan_data)
|
||||
return -EINVAL;
|
||||
|
||||
set_fan_speed(fan_data, state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct thermal_cooling_device_ops gpio_fan_cool_ops = {
|
||||
.get_max_state = gpio_fan_get_max_state,
|
||||
.get_cur_state = gpio_fan_get_cur_state,
|
||||
.set_cur_state = gpio_fan_set_cur_state,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF_GPIO
|
||||
/*
|
||||
* Translate OpenFirmware node properties into platform_data
|
||||
@ -404,10 +454,32 @@ static int gpio_fan_get_of_pdata(struct device *dev,
|
||||
|
||||
node = dev->of_node;
|
||||
|
||||
/* Alarm GPIO if one exists */
|
||||
if (of_gpio_named_count(node, "alarm-gpios") > 0) {
|
||||
struct gpio_fan_alarm *alarm;
|
||||
int val;
|
||||
enum of_gpio_flags flags;
|
||||
|
||||
alarm = devm_kzalloc(dev, sizeof(struct gpio_fan_alarm),
|
||||
GFP_KERNEL);
|
||||
if (!alarm)
|
||||
return -ENOMEM;
|
||||
|
||||
val = of_get_named_gpio_flags(node, "alarm-gpios", 0, &flags);
|
||||
if (val < 0)
|
||||
return val;
|
||||
alarm->gpio = val;
|
||||
alarm->active_low = flags & OF_GPIO_ACTIVE_LOW;
|
||||
|
||||
pdata->alarm = alarm;
|
||||
}
|
||||
|
||||
/* Fill GPIO pin array */
|
||||
pdata->num_ctrl = of_gpio_count(node);
|
||||
if (pdata->num_ctrl <= 0) {
|
||||
dev_err(dev, "gpios DT property empty / missing");
|
||||
if (pdata->alarm)
|
||||
return 0;
|
||||
dev_err(dev, "DT properties empty / missing");
|
||||
return -ENODEV;
|
||||
}
|
||||
ctrl = devm_kzalloc(dev, pdata->num_ctrl * sizeof(unsigned),
|
||||
@ -460,26 +532,6 @@ static int gpio_fan_get_of_pdata(struct device *dev,
|
||||
}
|
||||
pdata->speed = speed;
|
||||
|
||||
/* Alarm GPIO if one exists */
|
||||
if (of_gpio_named_count(node, "alarm-gpios") > 0) {
|
||||
struct gpio_fan_alarm *alarm;
|
||||
int val;
|
||||
enum of_gpio_flags flags;
|
||||
|
||||
alarm = devm_kzalloc(dev, sizeof(struct gpio_fan_alarm),
|
||||
GFP_KERNEL);
|
||||
if (!alarm)
|
||||
return -ENOMEM;
|
||||
|
||||
val = of_get_named_gpio_flags(node, "alarm-gpios", 0, &flags);
|
||||
if (val < 0)
|
||||
return val;
|
||||
alarm->gpio = val;
|
||||
alarm->active_low = flags & OF_GPIO_ACTIVE_LOW;
|
||||
|
||||
pdata->alarm = alarm;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -495,6 +547,11 @@ static int gpio_fan_probe(struct platform_device *pdev)
|
||||
struct gpio_fan_data *fan_data;
|
||||
struct gpio_fan_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
|
||||
fan_data = devm_kzalloc(&pdev->dev, sizeof(struct gpio_fan_data),
|
||||
GFP_KERNEL);
|
||||
if (!fan_data)
|
||||
return -ENOMEM;
|
||||
|
||||
#ifdef CONFIG_OF_GPIO
|
||||
if (!pdata) {
|
||||
pdata = devm_kzalloc(&pdev->dev,
|
||||
@ -512,11 +569,6 @@ static int gpio_fan_probe(struct platform_device *pdev)
|
||||
return -EINVAL;
|
||||
#endif /* CONFIG_OF_GPIO */
|
||||
|
||||
fan_data = devm_kzalloc(&pdev->dev, sizeof(struct gpio_fan_data),
|
||||
GFP_KERNEL);
|
||||
if (!fan_data)
|
||||
return -ENOMEM;
|
||||
|
||||
fan_data->pdev = pdev;
|
||||
platform_set_drvdata(pdev, fan_data);
|
||||
mutex_init(&fan_data->lock);
|
||||
@ -544,18 +596,39 @@ static int gpio_fan_probe(struct platform_device *pdev)
|
||||
gpio_fan_groups);
|
||||
if (IS_ERR(fan_data->hwmon_dev))
|
||||
return PTR_ERR(fan_data->hwmon_dev);
|
||||
#ifdef CONFIG_OF_GPIO
|
||||
/* Optional cooling device register for Device tree platforms */
|
||||
fan_data->cdev = thermal_of_cooling_device_register(pdev->dev.of_node,
|
||||
"gpio-fan",
|
||||
fan_data,
|
||||
&gpio_fan_cool_ops);
|
||||
#else /* CONFIG_OF_GPIO */
|
||||
/* Optional cooling device register for non Device tree platforms */
|
||||
fan_data->cdev = thermal_cooling_device_register("gpio-fan", fan_data,
|
||||
&gpio_fan_cool_ops);
|
||||
#endif /* CONFIG_OF_GPIO */
|
||||
|
||||
dev_info(&pdev->dev, "GPIO fan initialized\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gpio_fan_shutdown(struct platform_device *pdev)
|
||||
static int gpio_fan_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct gpio_fan_data *fan_data = dev_get_drvdata(&pdev->dev);
|
||||
struct gpio_fan_data *fan_data = platform_get_drvdata(pdev);
|
||||
|
||||
if (!IS_ERR(fan_data->cdev))
|
||||
thermal_cooling_device_unregister(fan_data->cdev);
|
||||
|
||||
if (fan_data->ctrl)
|
||||
set_fan_speed(fan_data, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gpio_fan_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
gpio_fan_remove(pdev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
@ -589,6 +662,7 @@ static SIMPLE_DEV_PM_OPS(gpio_fan_pm, gpio_fan_suspend, gpio_fan_resume);
|
||||
|
||||
static struct platform_driver gpio_fan_driver = {
|
||||
.probe = gpio_fan_probe,
|
||||
.remove = gpio_fan_remove,
|
||||
.shutdown = gpio_fan_shutdown,
|
||||
.driver = {
|
||||
.name = "gpio-fan",
|
||||
|
@ -56,15 +56,10 @@ static u8 const temp_sensor_sig[] = {0x74, 0x65, 0x6D};
|
||||
static u8 const watt_sensor_sig[] = {0x41, 0x43};
|
||||
|
||||
#define PEX_NUM_SENSOR_FUNCS 3
|
||||
static char const * const power_sensor_name_templates[] = {
|
||||
"%s%d_average",
|
||||
"%s%d_average_lowest",
|
||||
"%s%d_average_highest"
|
||||
};
|
||||
static char const * const temp_sensor_name_templates[] = {
|
||||
"%s%d_input",
|
||||
"%s%d_input_lowest",
|
||||
"%s%d_input_highest"
|
||||
static const char * const sensor_name_suffixes[] = {
|
||||
"",
|
||||
"_lowest",
|
||||
"_highest"
|
||||
};
|
||||
|
||||
static void ibmpex_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data);
|
||||
@ -355,9 +350,11 @@ static int create_sensor(struct ibmpex_bmc_data *data, int type,
|
||||
return -ENOMEM;
|
||||
|
||||
if (type == TEMP_SENSOR)
|
||||
sprintf(n, temp_sensor_name_templates[func], "temp", counter);
|
||||
sprintf(n, "temp%d_input%s",
|
||||
counter, sensor_name_suffixes[func]);
|
||||
else if (type == POWER_SENSOR)
|
||||
sprintf(n, power_sensor_name_templates[func], "power", counter);
|
||||
sprintf(n, "power%d_average%s",
|
||||
counter, sensor_name_suffixes[func]);
|
||||
|
||||
sysfs_attr_init(&data->sensors[sensor].attr[func].dev_attr.attr);
|
||||
data->sensors[sensor].attr[func].dev_attr.attr.name = n;
|
||||
|
@ -30,8 +30,11 @@
|
||||
#include <linux/platform_device.h>
|
||||
#include <asm/opal.h>
|
||||
#include <linux/err.h>
|
||||
#include <asm/cputhreads.h>
|
||||
#include <asm/smp.h>
|
||||
|
||||
#define MAX_ATTR_LEN 32
|
||||
#define MAX_LABEL_LEN 64
|
||||
|
||||
/* Sensor suffix name from DT */
|
||||
#define DT_FAULT_ATTR_SUFFIX "faulted"
|
||||
@ -44,17 +47,20 @@
|
||||
*/
|
||||
enum sensors {
|
||||
FAN,
|
||||
AMBIENT_TEMP,
|
||||
TEMP,
|
||||
POWER_SUPPLY,
|
||||
POWER_INPUT,
|
||||
MAX_SENSOR_TYPE,
|
||||
};
|
||||
|
||||
#define INVALID_INDEX (-1U)
|
||||
|
||||
static struct sensor_group {
|
||||
const char *name;
|
||||
const char *compatible;
|
||||
struct attribute_group group;
|
||||
u32 attr_count;
|
||||
u32 hwmon_index;
|
||||
} sensor_groups[] = {
|
||||
{"fan", "ibm,opal-sensor-cooling-fan"},
|
||||
{"temp", "ibm,opal-sensor-amb-temp"},
|
||||
@ -64,7 +70,10 @@ static struct sensor_group {
|
||||
|
||||
struct sensor_data {
|
||||
u32 id; /* An opaque id of the firmware for each sensor */
|
||||
u32 hwmon_index;
|
||||
u32 opal_index;
|
||||
enum sensors type;
|
||||
char label[MAX_LABEL_LEN];
|
||||
char name[MAX_ATTR_LEN];
|
||||
struct device_attribute dev_attr;
|
||||
};
|
||||
@ -87,7 +96,7 @@ static ssize_t show_sensor(struct device *dev, struct device_attribute *devattr,
|
||||
return ret;
|
||||
|
||||
/* Convert temperature to milli-degrees */
|
||||
if (sdata->type == AMBIENT_TEMP)
|
||||
if (sdata->type == TEMP)
|
||||
x *= 1000;
|
||||
/* Convert power to micro-watts */
|
||||
else if (sdata->type == POWER_INPUT)
|
||||
@ -96,8 +105,65 @@ static ssize_t show_sensor(struct device *dev, struct device_attribute *devattr,
|
||||
return sprintf(buf, "%u\n", x);
|
||||
}
|
||||
|
||||
static int get_sensor_index_attr(const char *name, u32 *index,
|
||||
char *attr)
|
||||
static ssize_t show_label(struct device *dev, struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
struct sensor_data *sdata = container_of(devattr, struct sensor_data,
|
||||
dev_attr);
|
||||
|
||||
return sprintf(buf, "%s\n", sdata->label);
|
||||
}
|
||||
|
||||
static int __init get_logical_cpu(int hwcpu)
|
||||
{
|
||||
int cpu;
|
||||
|
||||
for_each_possible_cpu(cpu)
|
||||
if (get_hard_smp_processor_id(cpu) == hwcpu)
|
||||
return cpu;
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static void __init make_sensor_label(struct device_node *np,
|
||||
struct sensor_data *sdata,
|
||||
const char *label)
|
||||
{
|
||||
u32 id;
|
||||
size_t n;
|
||||
|
||||
n = snprintf(sdata->label, sizeof(sdata->label), "%s", label);
|
||||
|
||||
/*
|
||||
* Core temp pretty print
|
||||
*/
|
||||
if (!of_property_read_u32(np, "ibm,pir", &id)) {
|
||||
int cpuid = get_logical_cpu(id);
|
||||
|
||||
if (cpuid >= 0)
|
||||
/*
|
||||
* The digital thermal sensors are associated
|
||||
* with a core. Let's print out the range of
|
||||
* cpu ids corresponding to the hardware
|
||||
* threads of the core.
|
||||
*/
|
||||
n += snprintf(sdata->label + n,
|
||||
sizeof(sdata->label) - n, " %d-%d",
|
||||
cpuid, cpuid + threads_per_core - 1);
|
||||
else
|
||||
n += snprintf(sdata->label + n,
|
||||
sizeof(sdata->label) - n, " phy%d", id);
|
||||
}
|
||||
|
||||
/*
|
||||
* Membuffer pretty print
|
||||
*/
|
||||
if (!of_property_read_u32(np, "ibm,chip-id", &id))
|
||||
n += snprintf(sdata->label + n, sizeof(sdata->label) - n,
|
||||
" %d", id & 0xffff);
|
||||
}
|
||||
|
||||
static int get_sensor_index_attr(const char *name, u32 *index, char *attr)
|
||||
{
|
||||
char *hash_pos = strchr(name, '#');
|
||||
char buf[8] = { 0 };
|
||||
@ -127,46 +193,90 @@ static int get_sensor_index_attr(const char *name, u32 *index,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *convert_opal_attr_name(enum sensors type,
|
||||
const char *opal_attr)
|
||||
{
|
||||
const char *attr_name = NULL;
|
||||
|
||||
if (!strcmp(opal_attr, DT_FAULT_ATTR_SUFFIX)) {
|
||||
attr_name = "fault";
|
||||
} else if (!strcmp(opal_attr, DT_DATA_ATTR_SUFFIX)) {
|
||||
attr_name = "input";
|
||||
} else if (!strcmp(opal_attr, DT_THRESHOLD_ATTR_SUFFIX)) {
|
||||
if (type == TEMP)
|
||||
attr_name = "max";
|
||||
else if (type == FAN)
|
||||
attr_name = "min";
|
||||
}
|
||||
|
||||
return attr_name;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function translates the DT node name into the 'hwmon' attribute name.
|
||||
* IBMPOWERNV device node appear like cooling-fan#2-data, amb-temp#1-thrs etc.
|
||||
* which need to be mapped as fan2_input, temp1_max respectively before
|
||||
* populating them inside hwmon device class.
|
||||
*/
|
||||
static int create_hwmon_attr_name(struct device *dev, enum sensors type,
|
||||
const char *node_name,
|
||||
char *hwmon_attr_name)
|
||||
static const char *parse_opal_node_name(const char *node_name,
|
||||
enum sensors type, u32 *index)
|
||||
{
|
||||
char attr_suffix[MAX_ATTR_LEN];
|
||||
char *attr_name;
|
||||
u32 index;
|
||||
const char *attr_name;
|
||||
int err;
|
||||
|
||||
err = get_sensor_index_attr(node_name, &index, attr_suffix);
|
||||
if (err) {
|
||||
dev_err(dev, "Sensor device node name '%s' is invalid\n",
|
||||
node_name);
|
||||
return err;
|
||||
err = get_sensor_index_attr(node_name, index, attr_suffix);
|
||||
if (err)
|
||||
return ERR_PTR(err);
|
||||
|
||||
attr_name = convert_opal_attr_name(type, attr_suffix);
|
||||
if (!attr_name)
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
return attr_name;
|
||||
}
|
||||
|
||||
static int get_sensor_type(struct device_node *np)
|
||||
{
|
||||
enum sensors type;
|
||||
const char *str;
|
||||
|
||||
for (type = 0; type < MAX_SENSOR_TYPE; type++) {
|
||||
if (of_device_is_compatible(np, sensor_groups[type].compatible))
|
||||
return type;
|
||||
}
|
||||
|
||||
if (!strcmp(attr_suffix, DT_FAULT_ATTR_SUFFIX)) {
|
||||
attr_name = "fault";
|
||||
} else if (!strcmp(attr_suffix, DT_DATA_ATTR_SUFFIX)) {
|
||||
attr_name = "input";
|
||||
} else if (!strcmp(attr_suffix, DT_THRESHOLD_ATTR_SUFFIX)) {
|
||||
if (type == AMBIENT_TEMP)
|
||||
attr_name = "max";
|
||||
else if (type == FAN)
|
||||
attr_name = "min";
|
||||
else
|
||||
return -ENOENT;
|
||||
} else {
|
||||
return -ENOENT;
|
||||
}
|
||||
/*
|
||||
* Let's check if we have a newer device tree
|
||||
*/
|
||||
if (!of_device_is_compatible(np, "ibm,opal-sensor"))
|
||||
return MAX_SENSOR_TYPE;
|
||||
|
||||
snprintf(hwmon_attr_name, MAX_ATTR_LEN, "%s%d_%s",
|
||||
sensor_groups[type].name, index, attr_name);
|
||||
return 0;
|
||||
if (of_property_read_string(np, "sensor-type", &str))
|
||||
return MAX_SENSOR_TYPE;
|
||||
|
||||
for (type = 0; type < MAX_SENSOR_TYPE; type++)
|
||||
if (!strcmp(str, sensor_groups[type].name))
|
||||
return type;
|
||||
|
||||
return MAX_SENSOR_TYPE;
|
||||
}
|
||||
|
||||
static u32 get_sensor_hwmon_index(struct sensor_data *sdata,
|
||||
struct sensor_data *sdata_table, int count)
|
||||
{
|
||||
int i;
|
||||
|
||||
/*
|
||||
* We don't use the OPAL index on newer device trees
|
||||
*/
|
||||
if (sdata->opal_index != INVALID_INDEX) {
|
||||
for (i = 0; i < count; i++)
|
||||
if (sdata_table[i].opal_index == sdata->opal_index &&
|
||||
sdata_table[i].type == sdata->type)
|
||||
return sdata_table[i].hwmon_index;
|
||||
}
|
||||
return ++sensor_groups[sdata->type].hwmon_index;
|
||||
}
|
||||
|
||||
static int populate_attr_groups(struct platform_device *pdev)
|
||||
@ -178,15 +288,22 @@ static int populate_attr_groups(struct platform_device *pdev)
|
||||
|
||||
opal = of_find_node_by_path("/ibm,opal/sensors");
|
||||
for_each_child_of_node(opal, np) {
|
||||
const char *label;
|
||||
|
||||
if (np->name == NULL)
|
||||
continue;
|
||||
|
||||
for (type = 0; type < MAX_SENSOR_TYPE; type++)
|
||||
if (of_device_is_compatible(np,
|
||||
sensor_groups[type].compatible)) {
|
||||
sensor_groups[type].attr_count++;
|
||||
break;
|
||||
}
|
||||
type = get_sensor_type(np);
|
||||
if (type == MAX_SENSOR_TYPE)
|
||||
continue;
|
||||
|
||||
sensor_groups[type].attr_count++;
|
||||
|
||||
/*
|
||||
* add a new attribute for labels
|
||||
*/
|
||||
if (!of_property_read_string(np, "label", &label))
|
||||
sensor_groups[type].attr_count++;
|
||||
}
|
||||
|
||||
of_node_put(opal);
|
||||
@ -207,6 +324,21 @@ static int populate_attr_groups(struct platform_device *pdev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void create_hwmon_attr(struct sensor_data *sdata, const char *attr_name,
|
||||
ssize_t (*show)(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf))
|
||||
{
|
||||
snprintf(sdata->name, MAX_ATTR_LEN, "%s%d_%s",
|
||||
sensor_groups[sdata->type].name, sdata->hwmon_index,
|
||||
attr_name);
|
||||
|
||||
sysfs_attr_init(&sdata->dev_attr.attr);
|
||||
sdata->dev_attr.attr.name = sdata->name;
|
||||
sdata->dev_attr.attr.mode = S_IRUGO;
|
||||
sdata->dev_attr.show = show;
|
||||
}
|
||||
|
||||
/*
|
||||
* Iterate through the device tree for each child of 'sensors' node, create
|
||||
* a sysfs attribute file, the file is named by translating the DT node name
|
||||
@ -233,18 +365,23 @@ static int create_device_attrs(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
for_each_child_of_node(opal, np) {
|
||||
const char *attr_name;
|
||||
u32 opal_index;
|
||||
const char *label;
|
||||
|
||||
if (np->name == NULL)
|
||||
continue;
|
||||
|
||||
for (type = 0; type < MAX_SENSOR_TYPE; type++)
|
||||
if (of_device_is_compatible(np,
|
||||
sensor_groups[type].compatible))
|
||||
break;
|
||||
|
||||
type = get_sensor_type(np);
|
||||
if (type == MAX_SENSOR_TYPE)
|
||||
continue;
|
||||
|
||||
if (of_property_read_u32(np, "sensor-id", &sensor_id)) {
|
||||
/*
|
||||
* Newer device trees use a "sensor-data" property
|
||||
* name for input.
|
||||
*/
|
||||
if (of_property_read_u32(np, "sensor-id", &sensor_id) &&
|
||||
of_property_read_u32(np, "sensor-data", &sensor_id)) {
|
||||
dev_info(&pdev->dev,
|
||||
"'sensor-id' missing in the node '%s'\n",
|
||||
np->name);
|
||||
@ -253,18 +390,46 @@ static int create_device_attrs(struct platform_device *pdev)
|
||||
|
||||
sdata[count].id = sensor_id;
|
||||
sdata[count].type = type;
|
||||
err = create_hwmon_attr_name(&pdev->dev, type, np->name,
|
||||
sdata[count].name);
|
||||
if (err)
|
||||
goto exit_put_node;
|
||||
|
||||
sysfs_attr_init(&sdata[count].dev_attr.attr);
|
||||
sdata[count].dev_attr.attr.name = sdata[count].name;
|
||||
sdata[count].dev_attr.attr.mode = S_IRUGO;
|
||||
sdata[count].dev_attr.show = show_sensor;
|
||||
/*
|
||||
* If we can not parse the node name, it means we are
|
||||
* running on a newer device tree. We can just forget
|
||||
* about the OPAL index and use a defaut value for the
|
||||
* hwmon attribute name
|
||||
*/
|
||||
attr_name = parse_opal_node_name(np->name, type, &opal_index);
|
||||
if (IS_ERR(attr_name)) {
|
||||
attr_name = "input";
|
||||
opal_index = INVALID_INDEX;
|
||||
}
|
||||
|
||||
sdata[count].opal_index = opal_index;
|
||||
sdata[count].hwmon_index =
|
||||
get_sensor_hwmon_index(&sdata[count], sdata, count);
|
||||
|
||||
create_hwmon_attr(&sdata[count], attr_name, show_sensor);
|
||||
|
||||
pgroups[type]->attrs[sensor_groups[type].attr_count++] =
|
||||
&sdata[count++].dev_attr.attr;
|
||||
|
||||
if (!of_property_read_string(np, "label", &label)) {
|
||||
/*
|
||||
* For the label attribute, we can reuse the
|
||||
* "properties" of the previous "input"
|
||||
* attribute. They are related to the same
|
||||
* sensor.
|
||||
*/
|
||||
sdata[count].type = type;
|
||||
sdata[count].opal_index = sdata[count - 1].opal_index;
|
||||
sdata[count].hwmon_index = sdata[count - 1].hwmon_index;
|
||||
|
||||
make_sensor_label(np, &sdata[count], label);
|
||||
|
||||
create_hwmon_attr(&sdata[count], "label", show_label);
|
||||
|
||||
pgroups[type]->attrs[sensor_groups[type].attr_count++] =
|
||||
&sdata[count++].dev_attr.attr;
|
||||
}
|
||||
}
|
||||
|
||||
exit_put_node:
|
||||
|
@ -11,6 +11,7 @@
|
||||
* similar parts. The other devices are supported by different drivers.
|
||||
*
|
||||
* Supports: IT8603E Super I/O chip w/LPC interface
|
||||
* IT8620E Super I/O chip w/LPC interface
|
||||
* IT8623E Super I/O chip w/LPC interface
|
||||
* IT8705F Super I/O chip w/LPC interface
|
||||
* IT8712F Super I/O chip w/LPC interface
|
||||
@ -23,8 +24,11 @@
|
||||
* IT8758E Super I/O chip w/LPC interface
|
||||
* IT8771E Super I/O chip w/LPC interface
|
||||
* IT8772E Super I/O chip w/LPC interface
|
||||
* IT8781F Super I/O chip w/LPC interface
|
||||
* IT8782F Super I/O chip w/LPC interface
|
||||
* IT8783E/F Super I/O chip w/LPC interface
|
||||
* IT8786E Super I/O chip w/LPC interface
|
||||
* IT8790E Super I/O chip w/LPC interface
|
||||
* Sis950 A clone of the IT8705F
|
||||
*
|
||||
* Copyright (C) 2001 Chris Gauthron
|
||||
@ -66,7 +70,7 @@
|
||||
#define DRVNAME "it87"
|
||||
|
||||
enum chips { it87, it8712, it8716, it8718, it8720, it8721, it8728, it8771,
|
||||
it8772, it8782, it8783, it8603 };
|
||||
it8772, it8781, it8782, it8783, it8786, it8790, it8603, it8620 };
|
||||
|
||||
static unsigned short force_id;
|
||||
module_param(force_id, ushort, 0);
|
||||
@ -146,15 +150,20 @@ static inline void superio_exit(void)
|
||||
#define IT8728F_DEVID 0x8728
|
||||
#define IT8771E_DEVID 0x8771
|
||||
#define IT8772E_DEVID 0x8772
|
||||
#define IT8781F_DEVID 0x8781
|
||||
#define IT8782F_DEVID 0x8782
|
||||
#define IT8783E_DEVID 0x8783
|
||||
#define IT8786E_DEVID 0x8786
|
||||
#define IT8790E_DEVID 0x8790
|
||||
#define IT8603E_DEVID 0x8603
|
||||
#define IT8620E_DEVID 0x8620
|
||||
#define IT8623E_DEVID 0x8623
|
||||
#define IT87_ACT_REG 0x30
|
||||
#define IT87_BASE_REG 0x60
|
||||
|
||||
/* Logical device 7 registers (IT8712F and later) */
|
||||
#define IT87_SIO_GPIO1_REG 0x25
|
||||
#define IT87_SIO_GPIO2_REG 0x26
|
||||
#define IT87_SIO_GPIO3_REG 0x27
|
||||
#define IT87_SIO_GPIO5_REG 0x29
|
||||
#define IT87_SIO_PINX1_REG 0x2a /* Pin selection */
|
||||
@ -207,11 +216,11 @@ static bool fix_pwm_polarity;
|
||||
|
||||
/* Monitors: 9 voltage (0 to 7, battery), 3 temp (1 to 3), 3 fan (1 to 3) */
|
||||
|
||||
static const u8 IT87_REG_FAN[] = { 0x0d, 0x0e, 0x0f, 0x80, 0x82 };
|
||||
static const u8 IT87_REG_FAN_MIN[] = { 0x10, 0x11, 0x12, 0x84, 0x86 };
|
||||
static const u8 IT87_REG_FANX[] = { 0x18, 0x19, 0x1a, 0x81, 0x83 };
|
||||
static const u8 IT87_REG_FANX_MIN[] = { 0x1b, 0x1c, 0x1d, 0x85, 0x87 };
|
||||
static const u8 IT87_REG_TEMP_OFFSET[] = { 0x56, 0x57, 0x59 };
|
||||
static const u8 IT87_REG_FAN[] = { 0x0d, 0x0e, 0x0f, 0x80, 0x82, 0x4c };
|
||||
static const u8 IT87_REG_FAN_MIN[] = { 0x10, 0x11, 0x12, 0x84, 0x86, 0x4e };
|
||||
static const u8 IT87_REG_FANX[] = { 0x18, 0x19, 0x1a, 0x81, 0x83, 0x4d };
|
||||
static const u8 IT87_REG_FANX_MIN[] = { 0x1b, 0x1c, 0x1d, 0x85, 0x87, 0x4f };
|
||||
static const u8 IT87_REG_TEMP_OFFSET[] = { 0x56, 0x57, 0x59 };
|
||||
|
||||
#define IT87_REG_FAN_MAIN_CTRL 0x13
|
||||
#define IT87_REG_FAN_CTL 0x14
|
||||
@ -238,6 +247,7 @@ static const u8 IT87_REG_TEMP_OFFSET[] = { 0x56, 0x57, 0x59 };
|
||||
|
||||
struct it87_devices {
|
||||
const char *name;
|
||||
const char * const suffix;
|
||||
u16 features;
|
||||
u8 peci_mask;
|
||||
u8 old_peci_mask;
|
||||
@ -250,79 +260,131 @@ struct it87_devices {
|
||||
#define FEAT_TEMP_OFFSET (1 << 4)
|
||||
#define FEAT_TEMP_PECI (1 << 5)
|
||||
#define FEAT_TEMP_OLD_PECI (1 << 6)
|
||||
#define FEAT_FAN16_CONFIG (1 << 7) /* Need to enable 16-bit fans */
|
||||
#define FEAT_FIVE_FANS (1 << 8) /* Supports five fans */
|
||||
#define FEAT_VID (1 << 9) /* Set if chip supports VID */
|
||||
#define FEAT_IN7_INTERNAL (1 << 10) /* Set if in7 is internal */
|
||||
#define FEAT_SIX_FANS (1 << 11) /* Supports six fans */
|
||||
|
||||
static const struct it87_devices it87_devices[] = {
|
||||
[it87] = {
|
||||
.name = "it87",
|
||||
.suffix = "F",
|
||||
.features = FEAT_OLD_AUTOPWM, /* may need to overwrite */
|
||||
},
|
||||
[it8712] = {
|
||||
.name = "it8712",
|
||||
.features = FEAT_OLD_AUTOPWM, /* may need to overwrite */
|
||||
.suffix = "F",
|
||||
.features = FEAT_OLD_AUTOPWM | FEAT_VID,
|
||||
/* may need to overwrite */
|
||||
},
|
||||
[it8716] = {
|
||||
.name = "it8716",
|
||||
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET,
|
||||
.suffix = "F",
|
||||
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
|
||||
| FEAT_FAN16_CONFIG | FEAT_FIVE_FANS,
|
||||
},
|
||||
[it8718] = {
|
||||
.name = "it8718",
|
||||
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
|
||||
| FEAT_TEMP_OLD_PECI,
|
||||
.suffix = "F",
|
||||
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
|
||||
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS,
|
||||
.old_peci_mask = 0x4,
|
||||
},
|
||||
[it8720] = {
|
||||
.name = "it8720",
|
||||
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
|
||||
| FEAT_TEMP_OLD_PECI,
|
||||
.suffix = "F",
|
||||
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
|
||||
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS,
|
||||
.old_peci_mask = 0x4,
|
||||
},
|
||||
[it8721] = {
|
||||
.name = "it8721",
|
||||
.suffix = "F",
|
||||
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
|
||||
| FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI,
|
||||
| FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
|
||||
| FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_IN7_INTERNAL,
|
||||
.peci_mask = 0x05,
|
||||
.old_peci_mask = 0x02, /* Actually reports PCH */
|
||||
},
|
||||
[it8728] = {
|
||||
.name = "it8728",
|
||||
.suffix = "F",
|
||||
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
|
||||
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI,
|
||||
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS
|
||||
| FEAT_IN7_INTERNAL,
|
||||
.peci_mask = 0x07,
|
||||
},
|
||||
[it8771] = {
|
||||
.name = "it8771",
|
||||
.suffix = "E",
|
||||
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
|
||||
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI,
|
||||
/* PECI: guesswork */
|
||||
/* 12mV ADC (OHM) */
|
||||
/* 16 bit fans (OHM) */
|
||||
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL,
|
||||
/* PECI: guesswork */
|
||||
/* 12mV ADC (OHM) */
|
||||
/* 16 bit fans (OHM) */
|
||||
/* three fans, always 16 bit (guesswork) */
|
||||
.peci_mask = 0x07,
|
||||
},
|
||||
[it8772] = {
|
||||
.name = "it8772",
|
||||
.suffix = "E",
|
||||
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
|
||||
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI,
|
||||
/* PECI (coreboot) */
|
||||
/* 12mV ADC (HWSensors4, OHM) */
|
||||
/* 16 bit fans (HWSensors4, OHM) */
|
||||
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL,
|
||||
/* PECI (coreboot) */
|
||||
/* 12mV ADC (HWSensors4, OHM) */
|
||||
/* 16 bit fans (HWSensors4, OHM) */
|
||||
/* three fans, always 16 bit (datasheet) */
|
||||
.peci_mask = 0x07,
|
||||
},
|
||||
[it8781] = {
|
||||
.name = "it8781",
|
||||
.suffix = "F",
|
||||
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
|
||||
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG,
|
||||
.old_peci_mask = 0x4,
|
||||
},
|
||||
[it8782] = {
|
||||
.name = "it8782",
|
||||
.suffix = "F",
|
||||
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
|
||||
| FEAT_TEMP_OLD_PECI,
|
||||
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG,
|
||||
.old_peci_mask = 0x4,
|
||||
},
|
||||
[it8783] = {
|
||||
.name = "it8783",
|
||||
.suffix = "E/F",
|
||||
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
|
||||
| FEAT_TEMP_OLD_PECI,
|
||||
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG,
|
||||
.old_peci_mask = 0x4,
|
||||
},
|
||||
[it8786] = {
|
||||
.name = "it8786",
|
||||
.suffix = "E",
|
||||
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
|
||||
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL,
|
||||
.peci_mask = 0x07,
|
||||
},
|
||||
[it8790] = {
|
||||
.name = "it8790",
|
||||
.suffix = "E",
|
||||
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
|
||||
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL,
|
||||
.peci_mask = 0x07,
|
||||
},
|
||||
[it8603] = {
|
||||
.name = "it8603",
|
||||
.suffix = "E",
|
||||
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
|
||||
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI,
|
||||
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL,
|
||||
.peci_mask = 0x07,
|
||||
},
|
||||
[it8620] = {
|
||||
.name = "it8620",
|
||||
.suffix = "E",
|
||||
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
|
||||
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_SIX_FANS
|
||||
| FEAT_IN7_INTERNAL,
|
||||
.peci_mask = 0x07,
|
||||
},
|
||||
};
|
||||
@ -337,6 +399,12 @@ static const struct it87_devices it87_devices[] = {
|
||||
#define has_temp_old_peci(data, nr) \
|
||||
(((data)->features & FEAT_TEMP_OLD_PECI) && \
|
||||
((data)->old_peci_mask & (1 << nr)))
|
||||
#define has_fan16_config(data) ((data)->features & FEAT_FAN16_CONFIG)
|
||||
#define has_five_fans(data) ((data)->features & (FEAT_FIVE_FANS | \
|
||||
FEAT_SIX_FANS))
|
||||
#define has_vid(data) ((data)->features & FEAT_VID)
|
||||
#define has_in7_internal(data) ((data)->features & FEAT_IN7_INTERNAL)
|
||||
#define has_six_fans(data) ((data)->features & FEAT_SIX_FANS)
|
||||
|
||||
struct it87_sio_data {
|
||||
enum chips type;
|
||||
@ -373,7 +441,7 @@ struct it87_data {
|
||||
u16 in_scaled; /* Internal voltage sensors are scaled */
|
||||
u8 in[10][3]; /* [nr][0]=in, [1]=min, [2]=max */
|
||||
u8 has_fan; /* Bitfield, fans enabled */
|
||||
u16 fan[5][2]; /* Register values, [nr][0]=fan, [1]=min */
|
||||
u16 fan[6][2]; /* Register values, [nr][0]=fan, [1]=min */
|
||||
u8 has_temp; /* Bitfield, temp sensors enabled */
|
||||
s8 temp[3][4]; /* [nr][0]=temp, [1]=min, [2]=max, [3]=offset */
|
||||
u8 sensor; /* Register value (IT87_REG_TEMP_ENABLE) */
|
||||
@ -475,15 +543,25 @@ static int DIV_TO_REG(int val)
|
||||
}
|
||||
#define DIV_FROM_REG(val) (1 << (val))
|
||||
|
||||
/*
|
||||
* PWM base frequencies. The frequency has to be divided by either 128 or 256,
|
||||
* depending on the chip type, to calculate the actual PWM frequency.
|
||||
*
|
||||
* Some of the chip datasheets suggest a base frequency of 51 kHz instead
|
||||
* of 750 kHz for the slowest base frequency, resulting in a PWM frequency
|
||||
* of 200 Hz. Sometimes both PWM frequency select registers are affected,
|
||||
* sometimes just one. It is unknown if this is a datasheet error or real,
|
||||
* so this is ignored for now.
|
||||
*/
|
||||
static const unsigned int pwm_freq[8] = {
|
||||
48000000 / 128,
|
||||
24000000 / 128,
|
||||
12000000 / 128,
|
||||
8000000 / 128,
|
||||
6000000 / 128,
|
||||
3000000 / 128,
|
||||
1500000 / 128,
|
||||
750000 / 128,
|
||||
48000000,
|
||||
24000000,
|
||||
12000000,
|
||||
8000000,
|
||||
6000000,
|
||||
3000000,
|
||||
1500000,
|
||||
750000,
|
||||
};
|
||||
|
||||
static int it87_probe(struct platform_device *pdev);
|
||||
@ -801,8 +879,11 @@ static ssize_t show_pwm_freq(struct device *dev, struct device_attribute *attr,
|
||||
{
|
||||
struct it87_data *data = it87_update_device(dev);
|
||||
int index = (data->fan_ctl >> 4) & 0x07;
|
||||
unsigned int freq;
|
||||
|
||||
return sprintf(buf, "%u\n", pwm_freq[index]);
|
||||
freq = pwm_freq[index] / (has_newer_autopwm(data) ? 256 : 128);
|
||||
|
||||
return sprintf(buf, "%u\n", freq);
|
||||
}
|
||||
|
||||
static ssize_t set_fan(struct device *dev, struct device_attribute *attr,
|
||||
@ -1024,6 +1105,9 @@ static ssize_t set_pwm_freq(struct device *dev,
|
||||
if (kstrtoul(buf, 10, &val) < 0)
|
||||
return -EINVAL;
|
||||
|
||||
val = clamp_val(val, 0, 1000000);
|
||||
val *= has_newer_autopwm(data) ? 256 : 128;
|
||||
|
||||
/* Search for the nearest available frequency */
|
||||
for (i = 0; i < 7; i++) {
|
||||
if (val > (pwm_freq[i] + pwm_freq[i+1]) / 2)
|
||||
@ -1196,6 +1280,10 @@ static SENSOR_DEVICE_ATTR_2(fan5_input, S_IRUGO, show_fan, NULL, 4, 0);
|
||||
static SENSOR_DEVICE_ATTR_2(fan5_min, S_IRUGO | S_IWUSR, show_fan, set_fan,
|
||||
4, 1);
|
||||
|
||||
static SENSOR_DEVICE_ATTR_2(fan6_input, S_IRUGO, show_fan, NULL, 5, 0);
|
||||
static SENSOR_DEVICE_ATTR_2(fan6_min, S_IRUGO | S_IWUSR, show_fan, set_fan,
|
||||
5, 1);
|
||||
|
||||
static SENSOR_DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR,
|
||||
show_pwm_enable, set_pwm_enable, 0);
|
||||
static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm, 0);
|
||||
@ -1326,6 +1414,7 @@ static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 1);
|
||||
static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, 2);
|
||||
static SENSOR_DEVICE_ATTR(fan4_alarm, S_IRUGO, show_alarm, NULL, 3);
|
||||
static SENSOR_DEVICE_ATTR(fan5_alarm, S_IRUGO, show_alarm, NULL, 6);
|
||||
static SENSOR_DEVICE_ATTR(fan6_alarm, S_IRUGO, show_alarm, NULL, 7);
|
||||
static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 16);
|
||||
static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 17);
|
||||
static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 18);
|
||||
@ -1376,6 +1465,7 @@ static SENSOR_DEVICE_ATTR(fan2_beep, S_IRUGO, show_beep, set_beep, 0);
|
||||
static SENSOR_DEVICE_ATTR(fan3_beep, S_IRUGO, show_beep, set_beep, 0);
|
||||
static SENSOR_DEVICE_ATTR(fan4_beep, S_IRUGO, show_beep, set_beep, 0);
|
||||
static SENSOR_DEVICE_ATTR(fan5_beep, S_IRUGO, show_beep, set_beep, 0);
|
||||
static SENSOR_DEVICE_ATTR(fan6_beep, S_IRUGO, show_beep, set_beep, 0);
|
||||
static SENSOR_DEVICE_ATTR(temp1_beep, S_IRUGO | S_IWUSR,
|
||||
show_beep, set_beep, 2);
|
||||
static SENSOR_DEVICE_ATTR(temp2_beep, S_IRUGO, show_beep, NULL, 2);
|
||||
@ -1579,7 +1669,7 @@ static struct attribute *it87_attributes_temp_beep[] = {
|
||||
&sensor_dev_attr_temp3_beep.dev_attr.attr,
|
||||
};
|
||||
|
||||
static struct attribute *it87_attributes_fan[5][3+1] = { {
|
||||
static struct attribute *it87_attributes_fan[6][3+1] = { {
|
||||
&sensor_dev_attr_fan1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan1_min.dev_attr.attr,
|
||||
&sensor_dev_attr_fan1_alarm.dev_attr.attr,
|
||||
@ -1604,14 +1694,20 @@ static struct attribute *it87_attributes_fan[5][3+1] = { {
|
||||
&sensor_dev_attr_fan5_min.dev_attr.attr,
|
||||
&sensor_dev_attr_fan5_alarm.dev_attr.attr,
|
||||
NULL
|
||||
}, {
|
||||
&sensor_dev_attr_fan6_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan6_min.dev_attr.attr,
|
||||
&sensor_dev_attr_fan6_alarm.dev_attr.attr,
|
||||
NULL
|
||||
} };
|
||||
|
||||
static const struct attribute_group it87_group_fan[5] = {
|
||||
static const struct attribute_group it87_group_fan[6] = {
|
||||
{ .attrs = it87_attributes_fan[0] },
|
||||
{ .attrs = it87_attributes_fan[1] },
|
||||
{ .attrs = it87_attributes_fan[2] },
|
||||
{ .attrs = it87_attributes_fan[3] },
|
||||
{ .attrs = it87_attributes_fan[4] },
|
||||
{ .attrs = it87_attributes_fan[5] },
|
||||
};
|
||||
|
||||
static const struct attribute *it87_attributes_fan_div[] = {
|
||||
@ -1693,6 +1789,7 @@ static struct attribute *it87_attributes_fan_beep[] = {
|
||||
&sensor_dev_attr_fan3_beep.dev_attr.attr,
|
||||
&sensor_dev_attr_fan4_beep.dev_attr.attr,
|
||||
&sensor_dev_attr_fan5_beep.dev_attr.attr,
|
||||
&sensor_dev_attr_fan6_beep.dev_attr.attr,
|
||||
};
|
||||
|
||||
static struct attribute *it87_attributes_vid[] = {
|
||||
@ -1724,6 +1821,7 @@ static int __init it87_find(unsigned short *address,
|
||||
int err;
|
||||
u16 chip_type;
|
||||
const char *board_vendor, *board_name;
|
||||
const struct it87_devices *config;
|
||||
|
||||
err = superio_enter();
|
||||
if (err)
|
||||
@ -1761,16 +1859,28 @@ static int __init it87_find(unsigned short *address,
|
||||
case IT8772E_DEVID:
|
||||
sio_data->type = it8772;
|
||||
break;
|
||||
case IT8781F_DEVID:
|
||||
sio_data->type = it8781;
|
||||
break;
|
||||
case IT8782F_DEVID:
|
||||
sio_data->type = it8782;
|
||||
break;
|
||||
case IT8783E_DEVID:
|
||||
sio_data->type = it8783;
|
||||
break;
|
||||
case IT8786E_DEVID:
|
||||
sio_data->type = it8786;
|
||||
break;
|
||||
case IT8790E_DEVID:
|
||||
sio_data->type = it8790;
|
||||
break;
|
||||
case IT8603E_DEVID:
|
||||
case IT8623E_DEVID:
|
||||
sio_data->type = it8603;
|
||||
break;
|
||||
case IT8620E_DEVID:
|
||||
sio_data->type = it8620;
|
||||
break;
|
||||
case 0xffff: /* No device at all */
|
||||
goto exit;
|
||||
default:
|
||||
@ -1792,30 +1902,34 @@ static int __init it87_find(unsigned short *address,
|
||||
|
||||
err = 0;
|
||||
sio_data->revision = superio_inb(DEVREV) & 0x0f;
|
||||
pr_info("Found IT%04x%c chip at 0x%x, revision %d\n", chip_type,
|
||||
chip_type == 0x8771 || chip_type == 0x8772 ||
|
||||
chip_type == 0x8603 ? 'E' : 'F', *address,
|
||||
sio_data->revision);
|
||||
pr_info("Found IT%04x%s chip at 0x%x, revision %d\n", chip_type,
|
||||
it87_devices[sio_data->type].suffix,
|
||||
*address, sio_data->revision);
|
||||
|
||||
config = &it87_devices[sio_data->type];
|
||||
|
||||
/* in7 (VSB or VCCH5V) is always internal on some chips */
|
||||
if (has_in7_internal(config))
|
||||
sio_data->internal |= (1 << 1);
|
||||
|
||||
/* in8 (Vbat) is always internal */
|
||||
sio_data->internal = (1 << 2);
|
||||
sio_data->internal |= (1 << 2);
|
||||
|
||||
/* Only the IT8603E has in9 */
|
||||
if (sio_data->type != it8603)
|
||||
sio_data->skip_in |= (1 << 9);
|
||||
|
||||
/* Read GPIO config and VID value from LDN 7 (GPIO) */
|
||||
if (sio_data->type == it87) {
|
||||
/* The IT8705F doesn't have VID pins at all */
|
||||
if (!has_vid(config))
|
||||
sio_data->skip_vid = 1;
|
||||
|
||||
/* Read GPIO config and VID value from LDN 7 (GPIO) */
|
||||
if (sio_data->type == it87) {
|
||||
/* The IT8705F has a different LD number for GPIO */
|
||||
superio_select(5);
|
||||
sio_data->beep_pin = superio_inb(IT87_SIO_BEEP_PIN_REG) & 0x3f;
|
||||
} else if (sio_data->type == it8783) {
|
||||
int reg25, reg27, reg2a, reg2c, regef;
|
||||
|
||||
sio_data->skip_vid = 1; /* No VID */
|
||||
|
||||
superio_select(GPIO);
|
||||
|
||||
reg25 = superio_inb(IT87_SIO_GPIO1_REG);
|
||||
@ -1881,7 +1995,6 @@ static int __init it87_find(unsigned short *address,
|
||||
} else if (sio_data->type == it8603) {
|
||||
int reg27, reg29;
|
||||
|
||||
sio_data->skip_vid = 1; /* No VID */
|
||||
superio_select(GPIO);
|
||||
|
||||
reg27 = superio_inb(IT87_SIO_GPIO3_REG);
|
||||
@ -1902,13 +2015,35 @@ static int __init it87_find(unsigned short *address,
|
||||
sio_data->skip_in |= (1 << 5); /* No VIN5 */
|
||||
sio_data->skip_in |= (1 << 6); /* No VIN6 */
|
||||
|
||||
/* no fan4 */
|
||||
sio_data->skip_pwm |= (1 << 3);
|
||||
sio_data->skip_fan |= (1 << 3);
|
||||
|
||||
sio_data->internal |= (1 << 1); /* in7 is VSB */
|
||||
sio_data->internal |= (1 << 3); /* in9 is AVCC */
|
||||
|
||||
sio_data->beep_pin = superio_inb(IT87_SIO_BEEP_PIN_REG) & 0x3f;
|
||||
} else if (sio_data->type == it8620) {
|
||||
int reg;
|
||||
|
||||
superio_select(GPIO);
|
||||
|
||||
/* Check for fan4, fan5 */
|
||||
reg = superio_inb(IT87_SIO_GPIO2_REG);
|
||||
if (!(reg & (1 << 5)))
|
||||
sio_data->skip_fan |= (1 << 3);
|
||||
if (!(reg & (1 << 4)))
|
||||
sio_data->skip_fan |= (1 << 4);
|
||||
|
||||
/* Check for pwm3, fan3 */
|
||||
reg = superio_inb(IT87_SIO_GPIO3_REG);
|
||||
if (reg & (1 << 6))
|
||||
sio_data->skip_pwm |= (1 << 2);
|
||||
if (reg & (1 << 7))
|
||||
sio_data->skip_fan |= (1 << 2);
|
||||
|
||||
/* Check for pwm2, fan2 */
|
||||
reg = superio_inb(IT87_SIO_GPIO5_REG);
|
||||
if (reg & (1 << 1))
|
||||
sio_data->skip_pwm |= (1 << 1);
|
||||
if (reg & (1 << 2))
|
||||
sio_data->skip_fan |= (1 << 1);
|
||||
|
||||
sio_data->beep_pin = superio_inb(IT87_SIO_BEEP_PIN_REG) & 0x3f;
|
||||
} else {
|
||||
int reg;
|
||||
@ -1917,15 +2052,7 @@ static int __init it87_find(unsigned short *address,
|
||||
superio_select(GPIO);
|
||||
|
||||
reg = superio_inb(IT87_SIO_GPIO3_REG);
|
||||
if (sio_data->type == it8721 || sio_data->type == it8728 ||
|
||||
sio_data->type == it8771 || sio_data->type == it8772 ||
|
||||
sio_data->type == it8782) {
|
||||
/*
|
||||
* IT8721F/IT8758E, and IT8782F don't have VID pins
|
||||
* at all, not sure about the IT8728F and compatibles.
|
||||
*/
|
||||
sio_data->skip_vid = 1;
|
||||
} else {
|
||||
if (!sio_data->skip_vid) {
|
||||
/* We need at least 4 VID pins */
|
||||
if (reg & 0x0f) {
|
||||
pr_info("VID is disabled (pins used for GPIO)\n");
|
||||
@ -1975,10 +2102,7 @@ static int __init it87_find(unsigned short *address,
|
||||
}
|
||||
if (reg & (1 << 0))
|
||||
sio_data->internal |= (1 << 0);
|
||||
if ((reg & (1 << 1)) || sio_data->type == it8721 ||
|
||||
sio_data->type == it8728 ||
|
||||
sio_data->type == it8771 ||
|
||||
sio_data->type == it8772)
|
||||
if (reg & (1 << 1))
|
||||
sio_data->internal |= (1 << 1);
|
||||
|
||||
/*
|
||||
@ -2050,7 +2174,7 @@ static void it87_remove_files(struct device *dev)
|
||||
sysfs_remove_file(&dev->kobj,
|
||||
it87_attributes_temp_beep[i]);
|
||||
}
|
||||
for (i = 0; i < 5; i++) {
|
||||
for (i = 0; i < 6; i++) {
|
||||
if (!(data->has_fan & (1 << i)))
|
||||
continue;
|
||||
sysfs_remove_group(&dev->kobj, &it87_group_fan[i]);
|
||||
@ -2062,7 +2186,7 @@ static void it87_remove_files(struct device *dev)
|
||||
it87_attributes_fan_div[i]);
|
||||
}
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (sio_data->skip_pwm & (1 << 0))
|
||||
if (sio_data->skip_pwm & (1 << i))
|
||||
continue;
|
||||
sysfs_remove_group(&dev->kobj, &it87_group_pwm[i]);
|
||||
if (has_old_autopwm(data))
|
||||
@ -2112,13 +2236,14 @@ static int it87_probe(struct platform_device *pdev)
|
||||
case it87:
|
||||
if (sio_data->revision >= 0x03) {
|
||||
data->features &= ~FEAT_OLD_AUTOPWM;
|
||||
data->features |= FEAT_16BIT_FANS;
|
||||
data->features |= FEAT_FAN16_CONFIG | FEAT_16BIT_FANS;
|
||||
}
|
||||
break;
|
||||
case it8712:
|
||||
if (sio_data->revision >= 0x08) {
|
||||
data->features &= ~FEAT_OLD_AUTOPWM;
|
||||
data->features |= FEAT_16BIT_FANS;
|
||||
data->features |= FEAT_FAN16_CONFIG | FEAT_16BIT_FANS |
|
||||
FEAT_FIVE_FANS;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@ -2147,7 +2272,8 @@ static int it87_probe(struct platform_device *pdev)
|
||||
data->in_scaled |= (1 << 8); /* in8 is Vbat */
|
||||
if (sio_data->internal & (1 << 3))
|
||||
data->in_scaled |= (1 << 9); /* in9 is AVCC */
|
||||
} else if (sio_data->type == it8782 || sio_data->type == it8783) {
|
||||
} else if (sio_data->type == it8781 || sio_data->type == it8782 ||
|
||||
sio_data->type == it8783) {
|
||||
if (sio_data->internal & (1 << 0))
|
||||
data->in_scaled |= (1 << 3); /* in3 is VCC5V */
|
||||
if (sio_data->internal & (1 << 1))
|
||||
@ -2205,7 +2331,7 @@ static int it87_probe(struct platform_device *pdev)
|
||||
|
||||
/* Do not create fan files for disabled fans */
|
||||
fan_beep_need_rw = 1;
|
||||
for (i = 0; i < 5; i++) {
|
||||
for (i = 0; i < 6; i++) {
|
||||
if (!(data->has_fan & (1 << i)))
|
||||
continue;
|
||||
err = sysfs_create_group(&dev->kobj, &it87_group_fan[i]);
|
||||
@ -2450,24 +2576,26 @@ static void it87_init_device(struct platform_device *pdev)
|
||||
}
|
||||
data->has_fan = (data->fan_main_ctrl >> 4) & 0x07;
|
||||
|
||||
/* Set tachometers to 16-bit mode if needed, IT8603E (and IT8728F?)
|
||||
* has it by default */
|
||||
if (has_16bit_fans(data) && data->type != it8603) {
|
||||
tmp = it87_read_value(data, IT87_REG_FAN_16BIT);
|
||||
tmp = it87_read_value(data, IT87_REG_FAN_16BIT);
|
||||
|
||||
/* Set tachometers to 16-bit mode if needed */
|
||||
if (has_fan16_config(data)) {
|
||||
if (~tmp & 0x07 & data->has_fan) {
|
||||
dev_dbg(&pdev->dev,
|
||||
"Setting fan1-3 to 16-bit mode\n");
|
||||
it87_write_value(data, IT87_REG_FAN_16BIT,
|
||||
tmp | 0x07);
|
||||
}
|
||||
/* IT8705F, IT8782F, and IT8783E/F only support three fans. */
|
||||
if (data->type != it87 && data->type != it8782 &&
|
||||
data->type != it8783) {
|
||||
if (tmp & (1 << 4))
|
||||
data->has_fan |= (1 << 3); /* fan4 enabled */
|
||||
if (tmp & (1 << 5))
|
||||
data->has_fan |= (1 << 4); /* fan5 enabled */
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for additional fans */
|
||||
if (has_five_fans(data)) {
|
||||
if (tmp & (1 << 4))
|
||||
data->has_fan |= (1 << 3); /* fan4 enabled */
|
||||
if (tmp & (1 << 5))
|
||||
data->has_fan |= (1 << 4); /* fan5 enabled */
|
||||
if (has_six_fans(data) && (tmp & (1 << 2)))
|
||||
data->has_fan |= (1 << 5); /* fan6 enabled */
|
||||
}
|
||||
|
||||
/* Fan input pins may be used for alternative functions */
|
||||
@ -2535,7 +2663,7 @@ static struct it87_data *it87_update_device(struct device *dev)
|
||||
if (data->type == it8603)
|
||||
data->in[9][0] = it87_read_value(data, 0x2f);
|
||||
|
||||
for (i = 0; i < 5; i++) {
|
||||
for (i = 0; i < 6; i++) {
|
||||
/* Skip disabled fans */
|
||||
if (!(data->has_fan & (1 << i)))
|
||||
continue;
|
||||
|
@ -87,11 +87,14 @@ static const unsigned short normal_i2c[] = {
|
||||
#define AT30TSE004_DEVID_MASK 0xffff
|
||||
|
||||
/* IDT */
|
||||
#define TS3000B3_DEVID 0x2903 /* Also matches TSE2002B3 */
|
||||
#define TS3000B3_DEVID_MASK 0xffff
|
||||
#define TSE2004_DEVID 0x2200
|
||||
#define TSE2004_DEVID_MASK 0xff00
|
||||
|
||||
#define TS3000GB2_DEVID 0x2912 /* Also matches TSE2002GB2 */
|
||||
#define TS3000GB2_DEVID_MASK 0xffff
|
||||
#define TS3000_DEVID 0x2900 /* Also matches TSE2002 */
|
||||
#define TS3000_DEVID_MASK 0xff00
|
||||
|
||||
#define TS3001_DEVID 0x3000
|
||||
#define TS3001_DEVID_MASK 0xff00
|
||||
|
||||
/* Maxim */
|
||||
#define MAX6604_DEVID 0x3e00
|
||||
@ -152,8 +155,9 @@ static struct jc42_chips jc42_chips[] = {
|
||||
{ ADT_MANID, ADT7408_DEVID, ADT7408_DEVID_MASK },
|
||||
{ ATMEL_MANID, AT30TS00_DEVID, AT30TS00_DEVID_MASK },
|
||||
{ ATMEL_MANID2, AT30TSE004_DEVID, AT30TSE004_DEVID_MASK },
|
||||
{ IDT_MANID, TS3000B3_DEVID, TS3000B3_DEVID_MASK },
|
||||
{ IDT_MANID, TS3000GB2_DEVID, TS3000GB2_DEVID_MASK },
|
||||
{ IDT_MANID, TSE2004_DEVID, TSE2004_DEVID_MASK },
|
||||
{ IDT_MANID, TS3000_DEVID, TS3000_DEVID_MASK },
|
||||
{ IDT_MANID, TS3001_DEVID, TS3001_DEVID_MASK },
|
||||
{ MAX_MANID, MAX6604_DEVID, MAX6604_DEVID_MASK },
|
||||
{ MCP_MANID, MCP9804_DEVID, MCP9804_DEVID_MASK },
|
||||
{ MCP_MANID, MCP98242_DEVID, MCP98242_DEVID_MASK },
|
||||
|
@ -57,6 +57,7 @@
|
||||
#include <linux/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/io.h>
|
||||
#include "lm75.h"
|
||||
|
||||
@ -880,12 +881,12 @@ struct nct6775_data {
|
||||
u16 have_temp;
|
||||
u16 have_temp_fixed;
|
||||
u16 have_in;
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
/* Remember extra register values over suspend/resume */
|
||||
u8 vbat;
|
||||
u8 fandiv1;
|
||||
u8 fandiv2;
|
||||
#endif
|
||||
u8 sio_reg_enable;
|
||||
};
|
||||
|
||||
struct nct6775_sio_data {
|
||||
@ -3178,6 +3179,10 @@ nct6775_check_fan_inputs(struct nct6775_data *data)
|
||||
int sioreg = data->sioreg;
|
||||
int regval;
|
||||
|
||||
/* Store SIO_REG_ENABLE for use during resume */
|
||||
superio_select(sioreg, NCT6775_LD_HWM);
|
||||
data->sio_reg_enable = superio_inb(sioreg, SIO_REG_ENABLE);
|
||||
|
||||
/* fan4 and fan5 share some pins with the GPIO and serial flash */
|
||||
if (data->kind == nct6775) {
|
||||
regval = superio_inb(sioreg, 0x2c);
|
||||
@ -3195,21 +3200,38 @@ nct6775_check_fan_inputs(struct nct6775_data *data)
|
||||
pwm6pin = false;
|
||||
} else if (data->kind == nct6776) {
|
||||
bool gpok = superio_inb(sioreg, 0x27) & 0x80;
|
||||
const char *board_vendor, *board_name;
|
||||
|
||||
superio_select(sioreg, NCT6775_LD_HWM);
|
||||
regval = superio_inb(sioreg, SIO_REG_ENABLE);
|
||||
board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR);
|
||||
board_name = dmi_get_system_info(DMI_BOARD_NAME);
|
||||
|
||||
if (regval & 0x80)
|
||||
if (board_name && board_vendor &&
|
||||
!strcmp(board_vendor, "ASRock")) {
|
||||
/*
|
||||
* Auxiliary fan monitoring is not enabled on ASRock
|
||||
* Z77 Pro4-M if booted in UEFI Ultra-FastBoot mode.
|
||||
* Observed with BIOS version 2.00.
|
||||
*/
|
||||
if (!strcmp(board_name, "Z77 Pro4-M")) {
|
||||
if ((data->sio_reg_enable & 0xe0) != 0xe0) {
|
||||
data->sio_reg_enable |= 0xe0;
|
||||
superio_outb(sioreg, SIO_REG_ENABLE,
|
||||
data->sio_reg_enable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data->sio_reg_enable & 0x80)
|
||||
fan3pin = gpok;
|
||||
else
|
||||
fan3pin = !(superio_inb(sioreg, 0x24) & 0x40);
|
||||
|
||||
if (regval & 0x40)
|
||||
if (data->sio_reg_enable & 0x40)
|
||||
fan4pin = gpok;
|
||||
else
|
||||
fan4pin = superio_inb(sioreg, 0x1C) & 0x01;
|
||||
|
||||
if (regval & 0x20)
|
||||
if (data->sio_reg_enable & 0x20)
|
||||
fan5pin = gpok;
|
||||
else
|
||||
fan5pin = superio_inb(sioreg, 0x1C) & 0x02;
|
||||
@ -3989,8 +4011,7 @@ static void nct6791_enable_io_mapping(int sioaddr)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int nct6775_suspend(struct device *dev)
|
||||
static int __maybe_unused nct6775_suspend(struct device *dev)
|
||||
{
|
||||
struct nct6775_data *data = nct6775_update_device(dev);
|
||||
|
||||
@ -4005,22 +4026,29 @@ static int nct6775_suspend(struct device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nct6775_resume(struct device *dev)
|
||||
static int __maybe_unused nct6775_resume(struct device *dev)
|
||||
{
|
||||
struct nct6775_data *data = dev_get_drvdata(dev);
|
||||
int sioreg = data->sioreg;
|
||||
int i, j, err = 0;
|
||||
u8 reg;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
data->bank = 0xff; /* Force initial bank selection */
|
||||
|
||||
if (data->kind == nct6791 || data->kind == nct6792) {
|
||||
err = superio_enter(data->sioreg);
|
||||
if (err)
|
||||
goto abort;
|
||||
err = superio_enter(sioreg);
|
||||
if (err)
|
||||
goto abort;
|
||||
|
||||
nct6791_enable_io_mapping(data->sioreg);
|
||||
superio_exit(data->sioreg);
|
||||
}
|
||||
superio_select(sioreg, NCT6775_LD_HWM);
|
||||
reg = superio_inb(sioreg, SIO_REG_ENABLE);
|
||||
if (reg != data->sio_reg_enable)
|
||||
superio_outb(sioreg, SIO_REG_ENABLE, data->sio_reg_enable);
|
||||
|
||||
if (data->kind == nct6791 || data->kind == nct6792)
|
||||
nct6791_enable_io_mapping(sioreg);
|
||||
|
||||
superio_exit(sioreg);
|
||||
|
||||
/* Restore limits */
|
||||
for (i = 0; i < data->in_num; i++) {
|
||||
@ -4066,22 +4094,12 @@ abort:
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops nct6775_dev_pm_ops = {
|
||||
.suspend = nct6775_suspend,
|
||||
.resume = nct6775_resume,
|
||||
.freeze = nct6775_suspend,
|
||||
.restore = nct6775_resume,
|
||||
};
|
||||
|
||||
#define NCT6775_DEV_PM_OPS (&nct6775_dev_pm_ops)
|
||||
#else
|
||||
#define NCT6775_DEV_PM_OPS NULL
|
||||
#endif /* CONFIG_PM */
|
||||
static SIMPLE_DEV_PM_OPS(nct6775_dev_pm_ops, nct6775_suspend, nct6775_resume);
|
||||
|
||||
static struct platform_driver nct6775_driver = {
|
||||
.driver = {
|
||||
.name = DRVNAME,
|
||||
.pm = NCT6775_DEV_PM_OPS,
|
||||
.pm = &nct6775_dev_pm_ops,
|
||||
},
|
||||
.probe = nct6775_probe,
|
||||
};
|
||||
|
593
drivers/hwmon/nct7904.c
Normal file
593
drivers/hwmon/nct7904.c
Normal file
@ -0,0 +1,593 @@
|
||||
/*
|
||||
* nct7904.c - driver for Nuvoton NCT7904D.
|
||||
*
|
||||
* Copyright (c) 2015 Kontron
|
||||
* Author: Vadim V. Vlasov <vvlasov@dev.rtsoft.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
|
||||
#define VENDOR_ID_REG 0x7A /* Any bank */
|
||||
#define NUVOTON_ID 0x50
|
||||
#define CHIP_ID_REG 0x7B /* Any bank */
|
||||
#define NCT7904_ID 0xC5
|
||||
#define DEVICE_ID_REG 0x7C /* Any bank */
|
||||
|
||||
#define BANK_SEL_REG 0xFF
|
||||
#define BANK_0 0x00
|
||||
#define BANK_1 0x01
|
||||
#define BANK_2 0x02
|
||||
#define BANK_3 0x03
|
||||
#define BANK_4 0x04
|
||||
#define BANK_MAX 0x04
|
||||
|
||||
#define FANIN_MAX 12 /* Counted from 1 */
|
||||
#define VSEN_MAX 21 /* VSEN1..14, 3VDD, VBAT, V3VSB,
|
||||
LTD (not a voltage), VSEN17..19 */
|
||||
#define FANCTL_MAX 4 /* Counted from 1 */
|
||||
#define TCPU_MAX 8 /* Counted from 1 */
|
||||
#define TEMP_MAX 4 /* Counted from 1 */
|
||||
|
||||
#define VT_ADC_CTRL0_REG 0x20 /* Bank 0 */
|
||||
#define VT_ADC_CTRL1_REG 0x21 /* Bank 0 */
|
||||
#define VT_ADC_CTRL2_REG 0x22 /* Bank 0 */
|
||||
#define FANIN_CTRL0_REG 0x24
|
||||
#define FANIN_CTRL1_REG 0x25
|
||||
#define DTS_T_CTRL0_REG 0x26
|
||||
#define DTS_T_CTRL1_REG 0x27
|
||||
#define VT_ADC_MD_REG 0x2E
|
||||
|
||||
#define VSEN1_HV_REG 0x40 /* Bank 0; 2 regs (HV/LV) per sensor */
|
||||
#define TEMP_CH1_HV_REG 0x42 /* Bank 0; same as VSEN2_HV */
|
||||
#define LTD_HV_REG 0x62 /* Bank 0; 2 regs in VSEN range */
|
||||
#define FANIN1_HV_REG 0x80 /* Bank 0; 2 regs (HV/LV) per sensor */
|
||||
#define T_CPU1_HV_REG 0xA0 /* Bank 0; 2 regs (HV/LV) per sensor */
|
||||
|
||||
#define PRTS_REG 0x03 /* Bank 2 */
|
||||
#define FANCTL1_FMR_REG 0x00 /* Bank 3; 1 reg per channel */
|
||||
#define FANCTL1_OUT_REG 0x10 /* Bank 3; 1 reg per channel */
|
||||
|
||||
static const unsigned short normal_i2c[] = {
|
||||
0x2d, 0x2e, I2C_CLIENT_END
|
||||
};
|
||||
|
||||
struct nct7904_data {
|
||||
struct i2c_client *client;
|
||||
struct mutex bank_lock;
|
||||
int bank_sel;
|
||||
u32 fanin_mask;
|
||||
u32 vsen_mask;
|
||||
u32 tcpu_mask;
|
||||
u8 fan_mode[FANCTL_MAX];
|
||||
};
|
||||
|
||||
/* Access functions */
|
||||
static int nct7904_bank_lock(struct nct7904_data *data, unsigned bank)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->bank_lock);
|
||||
if (data->bank_sel == bank)
|
||||
return 0;
|
||||
ret = i2c_smbus_write_byte_data(data->client, BANK_SEL_REG, bank);
|
||||
if (ret == 0)
|
||||
data->bank_sel = bank;
|
||||
else
|
||||
data->bank_sel = -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline void nct7904_bank_release(struct nct7904_data *data)
|
||||
{
|
||||
mutex_unlock(&data->bank_lock);
|
||||
}
|
||||
|
||||
/* Read 1-byte register. Returns unsigned reg or -ERRNO on error. */
|
||||
static int nct7904_read_reg(struct nct7904_data *data,
|
||||
unsigned bank, unsigned reg)
|
||||
{
|
||||
struct i2c_client *client = data->client;
|
||||
int ret;
|
||||
|
||||
ret = nct7904_bank_lock(data, bank);
|
||||
if (ret == 0)
|
||||
ret = i2c_smbus_read_byte_data(client, reg);
|
||||
|
||||
nct7904_bank_release(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read 2-byte register. Returns register in big-endian format or
|
||||
* -ERRNO on error.
|
||||
*/
|
||||
static int nct7904_read_reg16(struct nct7904_data *data,
|
||||
unsigned bank, unsigned reg)
|
||||
{
|
||||
struct i2c_client *client = data->client;
|
||||
int ret, hi;
|
||||
|
||||
ret = nct7904_bank_lock(data, bank);
|
||||
if (ret == 0) {
|
||||
ret = i2c_smbus_read_byte_data(client, reg);
|
||||
if (ret >= 0) {
|
||||
hi = ret;
|
||||
ret = i2c_smbus_read_byte_data(client, reg + 1);
|
||||
if (ret >= 0)
|
||||
ret |= hi << 8;
|
||||
}
|
||||
}
|
||||
|
||||
nct7904_bank_release(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Write 1-byte register. Returns 0 or -ERRNO on error. */
|
||||
static int nct7904_write_reg(struct nct7904_data *data,
|
||||
unsigned bank, unsigned reg, u8 val)
|
||||
{
|
||||
struct i2c_client *client = data->client;
|
||||
int ret;
|
||||
|
||||
ret = nct7904_bank_lock(data, bank);
|
||||
if (ret == 0)
|
||||
ret = i2c_smbus_write_byte_data(client, reg, val);
|
||||
|
||||
nct7904_bank_release(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* FANIN ATTR */
|
||||
static ssize_t show_fan(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
int index = to_sensor_dev_attr(devattr)->index;
|
||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
unsigned cnt, rpm;
|
||||
|
||||
ret = nct7904_read_reg16(data, BANK_0, FANIN1_HV_REG + index * 2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
cnt = ((ret & 0xff00) >> 3) | (ret & 0x1f);
|
||||
if (cnt == 0x1fff)
|
||||
rpm = 0;
|
||||
else
|
||||
rpm = 1350000 / cnt;
|
||||
return sprintf(buf, "%u\n", rpm);
|
||||
}
|
||||
|
||||
static umode_t nct7904_fanin_is_visible(struct kobject *kobj,
|
||||
struct attribute *a, int n)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
||||
|
||||
if (data->fanin_mask & (1 << n))
|
||||
return a->mode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1);
|
||||
static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2);
|
||||
static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3);
|
||||
static SENSOR_DEVICE_ATTR(fan5_input, S_IRUGO, show_fan, NULL, 4);
|
||||
static SENSOR_DEVICE_ATTR(fan6_input, S_IRUGO, show_fan, NULL, 5);
|
||||
static SENSOR_DEVICE_ATTR(fan7_input, S_IRUGO, show_fan, NULL, 6);
|
||||
static SENSOR_DEVICE_ATTR(fan8_input, S_IRUGO, show_fan, NULL, 7);
|
||||
static SENSOR_DEVICE_ATTR(fan9_input, S_IRUGO, show_fan, NULL, 8);
|
||||
static SENSOR_DEVICE_ATTR(fan10_input, S_IRUGO, show_fan, NULL, 9);
|
||||
static SENSOR_DEVICE_ATTR(fan11_input, S_IRUGO, show_fan, NULL, 10);
|
||||
static SENSOR_DEVICE_ATTR(fan12_input, S_IRUGO, show_fan, NULL, 11);
|
||||
|
||||
static struct attribute *nct7904_fanin_attrs[] = {
|
||||
&sensor_dev_attr_fan1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan2_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan3_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan4_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan5_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan6_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan7_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan8_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan9_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan10_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan11_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan12_input.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group nct7904_fanin_group = {
|
||||
.attrs = nct7904_fanin_attrs,
|
||||
.is_visible = nct7904_fanin_is_visible,
|
||||
};
|
||||
|
||||
/* VSEN ATTR */
|
||||
static ssize_t show_voltage(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
int index = to_sensor_dev_attr(devattr)->index;
|
||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
int volt;
|
||||
|
||||
ret = nct7904_read_reg16(data, BANK_0, VSEN1_HV_REG + index * 2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
volt = ((ret & 0xff00) >> 5) | (ret & 0x7);
|
||||
if (index < 14)
|
||||
volt *= 2; /* 0.002V scale */
|
||||
else
|
||||
volt *= 6; /* 0.006V scale */
|
||||
|
||||
return sprintf(buf, "%d\n", volt);
|
||||
}
|
||||
|
||||
static ssize_t show_ltemp(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
int temp;
|
||||
|
||||
ret = nct7904_read_reg16(data, BANK_0, LTD_HV_REG);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
temp = ((ret & 0xff00) >> 5) | (ret & 0x7);
|
||||
temp = sign_extend32(temp, 10) * 125;
|
||||
|
||||
return sprintf(buf, "%d\n", temp);
|
||||
}
|
||||
|
||||
static umode_t nct7904_vsen_is_visible(struct kobject *kobj,
|
||||
struct attribute *a, int n)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
||||
|
||||
if (data->vsen_mask & (1 << n))
|
||||
return a->mode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_voltage, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_voltage, NULL, 1);
|
||||
static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_voltage, NULL, 2);
|
||||
static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_voltage, NULL, 3);
|
||||
static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, show_voltage, NULL, 4);
|
||||
static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, show_voltage, NULL, 5);
|
||||
static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, show_voltage, NULL, 6);
|
||||
static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, show_voltage, NULL, 7);
|
||||
static SENSOR_DEVICE_ATTR(in9_input, S_IRUGO, show_voltage, NULL, 8);
|
||||
static SENSOR_DEVICE_ATTR(in10_input, S_IRUGO, show_voltage, NULL, 9);
|
||||
static SENSOR_DEVICE_ATTR(in11_input, S_IRUGO, show_voltage, NULL, 10);
|
||||
static SENSOR_DEVICE_ATTR(in12_input, S_IRUGO, show_voltage, NULL, 11);
|
||||
static SENSOR_DEVICE_ATTR(in13_input, S_IRUGO, show_voltage, NULL, 12);
|
||||
static SENSOR_DEVICE_ATTR(in14_input, S_IRUGO, show_voltage, NULL, 13);
|
||||
/*
|
||||
* Next 3 voltage sensors have specific names in the Nuvoton doc
|
||||
* (3VDD, VBAT, 3VSB) but we use vacant numbers for them.
|
||||
*/
|
||||
static SENSOR_DEVICE_ATTR(in15_input, S_IRUGO, show_voltage, NULL, 14);
|
||||
static SENSOR_DEVICE_ATTR(in16_input, S_IRUGO, show_voltage, NULL, 15);
|
||||
static SENSOR_DEVICE_ATTR(in20_input, S_IRUGO, show_voltage, NULL, 16);
|
||||
/* This is not a voltage, but a local temperature sensor. */
|
||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_ltemp, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(in17_input, S_IRUGO, show_voltage, NULL, 18);
|
||||
static SENSOR_DEVICE_ATTR(in18_input, S_IRUGO, show_voltage, NULL, 19);
|
||||
static SENSOR_DEVICE_ATTR(in19_input, S_IRUGO, show_voltage, NULL, 20);
|
||||
|
||||
static struct attribute *nct7904_vsen_attrs[] = {
|
||||
&sensor_dev_attr_in1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in2_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in3_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in4_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in5_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in6_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in7_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in8_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in9_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in10_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in11_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in12_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in13_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in14_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in15_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in16_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in20_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in17_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in18_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in19_input.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group nct7904_vsen_group = {
|
||||
.attrs = nct7904_vsen_attrs,
|
||||
.is_visible = nct7904_vsen_is_visible,
|
||||
};
|
||||
|
||||
/* CPU_TEMP ATTR */
|
||||
static ssize_t show_tcpu(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
int index = to_sensor_dev_attr(devattr)->index;
|
||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
int temp;
|
||||
|
||||
ret = nct7904_read_reg16(data, BANK_0, T_CPU1_HV_REG + index * 2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
temp = ((ret & 0xff00) >> 5) | (ret & 0x7);
|
||||
temp = sign_extend32(temp, 10) * 125;
|
||||
return sprintf(buf, "%d\n", temp);
|
||||
}
|
||||
|
||||
static umode_t nct7904_tcpu_is_visible(struct kobject *kobj,
|
||||
struct attribute *a, int n)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
||||
|
||||
if (data->tcpu_mask & (1 << n))
|
||||
return a->mode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* "temp1_input" reserved for local temp */
|
||||
static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_tcpu, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_tcpu, NULL, 1);
|
||||
static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_tcpu, NULL, 2);
|
||||
static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_tcpu, NULL, 3);
|
||||
static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_tcpu, NULL, 4);
|
||||
static SENSOR_DEVICE_ATTR(temp7_input, S_IRUGO, show_tcpu, NULL, 5);
|
||||
static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO, show_tcpu, NULL, 6);
|
||||
static SENSOR_DEVICE_ATTR(temp9_input, S_IRUGO, show_tcpu, NULL, 7);
|
||||
|
||||
static struct attribute *nct7904_tcpu_attrs[] = {
|
||||
&sensor_dev_attr_temp2_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp3_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp4_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp5_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp6_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp7_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp8_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp9_input.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group nct7904_tcpu_group = {
|
||||
.attrs = nct7904_tcpu_attrs,
|
||||
.is_visible = nct7904_tcpu_is_visible,
|
||||
};
|
||||
|
||||
/* PWM ATTR */
|
||||
static ssize_t store_pwm(struct device *dev, struct device_attribute *devattr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int index = to_sensor_dev_attr(devattr)->index;
|
||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
||||
unsigned long val;
|
||||
int ret;
|
||||
|
||||
if (kstrtoul(buf, 10, &val) < 0)
|
||||
return -EINVAL;
|
||||
if (val > 255)
|
||||
return -EINVAL;
|
||||
|
||||
ret = nct7904_write_reg(data, BANK_3, FANCTL1_OUT_REG + index, val);
|
||||
|
||||
return ret ? ret : count;
|
||||
}
|
||||
|
||||
static ssize_t show_pwm(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
int index = to_sensor_dev_attr(devattr)->index;
|
||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
||||
int val;
|
||||
|
||||
val = nct7904_read_reg(data, BANK_3, FANCTL1_OUT_REG + index);
|
||||
if (val < 0)
|
||||
return val;
|
||||
|
||||
return sprintf(buf, "%d\n", val);
|
||||
}
|
||||
|
||||
static ssize_t store_mode(struct device *dev, struct device_attribute *devattr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int index = to_sensor_dev_attr(devattr)->index;
|
||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
||||
unsigned long val;
|
||||
int ret;
|
||||
|
||||
if (kstrtoul(buf, 10, &val) < 0)
|
||||
return -EINVAL;
|
||||
if (val > 1 || (val && !data->fan_mode[index]))
|
||||
return -EINVAL;
|
||||
|
||||
ret = nct7904_write_reg(data, BANK_3, FANCTL1_FMR_REG + index,
|
||||
val ? data->fan_mode[index] : 0);
|
||||
|
||||
return ret ? ret : count;
|
||||
}
|
||||
|
||||
/* Return 0 for manual mode or 1 for SmartFan mode */
|
||||
static ssize_t show_mode(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
int index = to_sensor_dev_attr(devattr)->index;
|
||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
||||
int val;
|
||||
|
||||
val = nct7904_read_reg(data, BANK_3, FANCTL1_FMR_REG + index);
|
||||
if (val < 0)
|
||||
return val;
|
||||
|
||||
return sprintf(buf, "%d\n", val ? 1 : 0);
|
||||
}
|
||||
|
||||
/* 2 attributes per channel: pwm and mode */
|
||||
static SENSOR_DEVICE_ATTR(fan1_pwm, S_IRUGO | S_IWUSR,
|
||||
show_pwm, store_pwm, 0);
|
||||
static SENSOR_DEVICE_ATTR(fan1_mode, S_IRUGO | S_IWUSR,
|
||||
show_mode, store_mode, 0);
|
||||
static SENSOR_DEVICE_ATTR(fan2_pwm, S_IRUGO | S_IWUSR,
|
||||
show_pwm, store_pwm, 1);
|
||||
static SENSOR_DEVICE_ATTR(fan2_mode, S_IRUGO | S_IWUSR,
|
||||
show_mode, store_mode, 1);
|
||||
static SENSOR_DEVICE_ATTR(fan3_pwm, S_IRUGO | S_IWUSR,
|
||||
show_pwm, store_pwm, 2);
|
||||
static SENSOR_DEVICE_ATTR(fan3_mode, S_IRUGO | S_IWUSR,
|
||||
show_mode, store_mode, 2);
|
||||
static SENSOR_DEVICE_ATTR(fan4_pwm, S_IRUGO | S_IWUSR,
|
||||
show_pwm, store_pwm, 3);
|
||||
static SENSOR_DEVICE_ATTR(fan4_mode, S_IRUGO | S_IWUSR,
|
||||
show_mode, store_mode, 3);
|
||||
|
||||
static struct attribute *nct7904_fanctl_attrs[] = {
|
||||
&sensor_dev_attr_fan1_pwm.dev_attr.attr,
|
||||
&sensor_dev_attr_fan1_mode.dev_attr.attr,
|
||||
&sensor_dev_attr_fan2_pwm.dev_attr.attr,
|
||||
&sensor_dev_attr_fan2_mode.dev_attr.attr,
|
||||
&sensor_dev_attr_fan3_pwm.dev_attr.attr,
|
||||
&sensor_dev_attr_fan3_mode.dev_attr.attr,
|
||||
&sensor_dev_attr_fan4_pwm.dev_attr.attr,
|
||||
&sensor_dev_attr_fan4_mode.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group nct7904_fanctl_group = {
|
||||
.attrs = nct7904_fanctl_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group *nct7904_groups[] = {
|
||||
&nct7904_fanin_group,
|
||||
&nct7904_vsen_group,
|
||||
&nct7904_tcpu_group,
|
||||
&nct7904_fanctl_group,
|
||||
NULL
|
||||
};
|
||||
|
||||
/* Return 0 if detection is successful, -ENODEV otherwise */
|
||||
static int nct7904_detect(struct i2c_client *client,
|
||||
struct i2c_board_info *info)
|
||||
{
|
||||
struct i2c_adapter *adapter = client->adapter;
|
||||
|
||||
if (!i2c_check_functionality(adapter,
|
||||
I2C_FUNC_SMBUS_READ_BYTE |
|
||||
I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
/* Determine the chip type. */
|
||||
if (i2c_smbus_read_byte_data(client, VENDOR_ID_REG) != NUVOTON_ID ||
|
||||
i2c_smbus_read_byte_data(client, CHIP_ID_REG) != NCT7904_ID ||
|
||||
(i2c_smbus_read_byte_data(client, DEVICE_ID_REG) & 0xf0) != 0x50 ||
|
||||
(i2c_smbus_read_byte_data(client, BANK_SEL_REG) & 0xf8) != 0x00)
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, "nct7904", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nct7904_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct nct7904_data *data;
|
||||
struct device *hwmon_dev;
|
||||
struct device *dev = &client->dev;
|
||||
int ret, i;
|
||||
u32 mask;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(struct nct7904_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->client = client;
|
||||
mutex_init(&data->bank_lock);
|
||||
data->bank_sel = -1;
|
||||
|
||||
/* Setup sensor groups. */
|
||||
/* FANIN attributes */
|
||||
ret = nct7904_read_reg16(data, BANK_0, FANIN_CTRL0_REG);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
data->fanin_mask = (ret >> 8) | ((ret & 0xff) << 8);
|
||||
|
||||
/*
|
||||
* VSEN attributes
|
||||
*
|
||||
* Note: voltage sensors overlap with external temperature
|
||||
* sensors. So, if we ever decide to support the latter
|
||||
* we will have to adjust 'vsen_mask' accordingly.
|
||||
*/
|
||||
mask = 0;
|
||||
ret = nct7904_read_reg16(data, BANK_0, VT_ADC_CTRL0_REG);
|
||||
if (ret >= 0)
|
||||
mask = (ret >> 8) | ((ret & 0xff) << 8);
|
||||
ret = nct7904_read_reg(data, BANK_0, VT_ADC_CTRL2_REG);
|
||||
if (ret >= 0)
|
||||
mask |= (ret << 16);
|
||||
data->vsen_mask = mask;
|
||||
|
||||
/* CPU_TEMP attributes */
|
||||
ret = nct7904_read_reg16(data, BANK_0, DTS_T_CTRL0_REG);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
data->tcpu_mask = ((ret >> 8) & 0xf) | ((ret & 0xf) << 4);
|
||||
|
||||
for (i = 0; i < FANCTL_MAX; i++) {
|
||||
ret = nct7904_read_reg(data, BANK_3, FANCTL1_FMR_REG + i);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
data->fan_mode[i] = ret;
|
||||
}
|
||||
|
||||
hwmon_dev =
|
||||
devm_hwmon_device_register_with_groups(dev, client->name, data,
|
||||
nct7904_groups);
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id nct7904_id[] = {
|
||||
{"nct7904", 0},
|
||||
{}
|
||||
};
|
||||
|
||||
static struct i2c_driver nct7904_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "nct7904",
|
||||
},
|
||||
.probe = nct7904_probe,
|
||||
.id_table = nct7904_id,
|
||||
.detect = nct7904_detect,
|
||||
.address_list = normal_i2c,
|
||||
};
|
||||
|
||||
module_i2c_driver(nct7904_driver);
|
||||
|
||||
MODULE_AUTHOR("Vadim V. Vlasov <vvlasov@dev.rtsoft.ru>");
|
||||
MODULE_DESCRIPTION("Hwmon driver for NUVOTON NCT7904");
|
||||
MODULE_LICENSE("GPL");
|
@ -24,55 +24,78 @@
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pwm.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/thermal.h>
|
||||
|
||||
#define MAX_PWM 255
|
||||
|
||||
struct pwm_fan_ctx {
|
||||
struct mutex lock;
|
||||
struct pwm_device *pwm;
|
||||
unsigned char pwm_value;
|
||||
unsigned int pwm_value;
|
||||
unsigned int pwm_fan_state;
|
||||
unsigned int pwm_fan_max_state;
|
||||
unsigned int *pwm_fan_cooling_levels;
|
||||
struct thermal_cooling_device *cdev;
|
||||
};
|
||||
|
||||
static ssize_t set_pwm(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
static int __set_pwm(struct pwm_fan_ctx *ctx, unsigned long pwm)
|
||||
{
|
||||
struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
|
||||
unsigned long pwm, duty;
|
||||
ssize_t ret;
|
||||
|
||||
if (kstrtoul(buf, 10, &pwm) || pwm > MAX_PWM)
|
||||
return -EINVAL;
|
||||
unsigned long duty;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&ctx->lock);
|
||||
|
||||
if (ctx->pwm_value == pwm)
|
||||
goto exit_set_pwm_no_change;
|
||||
|
||||
if (pwm == 0) {
|
||||
pwm_disable(ctx->pwm);
|
||||
goto exit_set_pwm;
|
||||
}
|
||||
goto exit_set_pwm_err;
|
||||
|
||||
duty = DIV_ROUND_UP(pwm * (ctx->pwm->period - 1), MAX_PWM);
|
||||
ret = pwm_config(ctx->pwm, duty, ctx->pwm->period);
|
||||
if (ret)
|
||||
goto exit_set_pwm_err;
|
||||
|
||||
if (pwm == 0)
|
||||
pwm_disable(ctx->pwm);
|
||||
|
||||
if (ctx->pwm_value == 0) {
|
||||
ret = pwm_enable(ctx->pwm);
|
||||
if (ret)
|
||||
goto exit_set_pwm_err;
|
||||
}
|
||||
|
||||
exit_set_pwm:
|
||||
ctx->pwm_value = pwm;
|
||||
exit_set_pwm_no_change:
|
||||
ret = count;
|
||||
exit_set_pwm_err:
|
||||
mutex_unlock(&ctx->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void pwm_fan_update_state(struct pwm_fan_ctx *ctx, unsigned long pwm)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ctx->pwm_fan_max_state; ++i)
|
||||
if (pwm < ctx->pwm_fan_cooling_levels[i + 1])
|
||||
break;
|
||||
|
||||
ctx->pwm_fan_state = i;
|
||||
}
|
||||
|
||||
static ssize_t set_pwm(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
|
||||
unsigned long pwm;
|
||||
int ret;
|
||||
|
||||
if (kstrtoul(buf, 10, &pwm) || pwm > MAX_PWM)
|
||||
return -EINVAL;
|
||||
|
||||
ret = __set_pwm(ctx, pwm);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pwm_fan_update_state(ctx, pwm);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_pwm(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
@ -91,10 +114,108 @@ static struct attribute *pwm_fan_attrs[] = {
|
||||
|
||||
ATTRIBUTE_GROUPS(pwm_fan);
|
||||
|
||||
/* thermal cooling device callbacks */
|
||||
static int pwm_fan_get_max_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
struct pwm_fan_ctx *ctx = cdev->devdata;
|
||||
|
||||
if (!ctx)
|
||||
return -EINVAL;
|
||||
|
||||
*state = ctx->pwm_fan_max_state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pwm_fan_get_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
struct pwm_fan_ctx *ctx = cdev->devdata;
|
||||
|
||||
if (!ctx)
|
||||
return -EINVAL;
|
||||
|
||||
*state = ctx->pwm_fan_state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
pwm_fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
|
||||
{
|
||||
struct pwm_fan_ctx *ctx = cdev->devdata;
|
||||
int ret;
|
||||
|
||||
if (!ctx || (state > ctx->pwm_fan_max_state))
|
||||
return -EINVAL;
|
||||
|
||||
if (state == ctx->pwm_fan_state)
|
||||
return 0;
|
||||
|
||||
ret = __set_pwm(ctx, ctx->pwm_fan_cooling_levels[state]);
|
||||
if (ret) {
|
||||
dev_err(&cdev->device, "Cannot set pwm!\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ctx->pwm_fan_state = state;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct thermal_cooling_device_ops pwm_fan_cooling_ops = {
|
||||
.get_max_state = pwm_fan_get_max_state,
|
||||
.get_cur_state = pwm_fan_get_cur_state,
|
||||
.set_cur_state = pwm_fan_set_cur_state,
|
||||
};
|
||||
|
||||
static int pwm_fan_of_get_cooling_data(struct device *dev,
|
||||
struct pwm_fan_ctx *ctx)
|
||||
{
|
||||
struct device_node *np = dev->of_node;
|
||||
int num, i, ret;
|
||||
|
||||
if (!of_find_property(np, "cooling-levels", NULL))
|
||||
return 0;
|
||||
|
||||
ret = of_property_count_u32_elems(np, "cooling-levels");
|
||||
if (ret <= 0) {
|
||||
dev_err(dev, "Wrong data!\n");
|
||||
return ret ? : -EINVAL;
|
||||
}
|
||||
|
||||
num = ret;
|
||||
ctx->pwm_fan_cooling_levels = devm_kzalloc(dev, num * sizeof(u32),
|
||||
GFP_KERNEL);
|
||||
if (!ctx->pwm_fan_cooling_levels)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = of_property_read_u32_array(np, "cooling-levels",
|
||||
ctx->pwm_fan_cooling_levels, num);
|
||||
if (ret) {
|
||||
dev_err(dev, "Property 'cooling-levels' cannot be read!\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
if (ctx->pwm_fan_cooling_levels[i] > MAX_PWM) {
|
||||
dev_err(dev, "PWM fan state[%d]:%d > %d\n", i,
|
||||
ctx->pwm_fan_cooling_levels[i], MAX_PWM);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
ctx->pwm_fan_max_state = num - 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pwm_fan_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *hwmon;
|
||||
struct thermal_cooling_device *cdev;
|
||||
struct pwm_fan_ctx *ctx;
|
||||
struct device *hwmon;
|
||||
int duty_cycle;
|
||||
int ret;
|
||||
|
||||
@ -136,6 +257,26 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
||||
pwm_disable(ctx->pwm);
|
||||
return PTR_ERR(hwmon);
|
||||
}
|
||||
|
||||
ret = pwm_fan_of_get_cooling_data(&pdev->dev, ctx);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ctx->pwm_fan_state = ctx->pwm_fan_max_state;
|
||||
if (IS_ENABLED(CONFIG_THERMAL)) {
|
||||
cdev = thermal_of_cooling_device_register(pdev->dev.of_node,
|
||||
"pwm-fan", ctx,
|
||||
&pwm_fan_cooling_ops);
|
||||
if (IS_ERR(cdev)) {
|
||||
dev_err(&pdev->dev,
|
||||
"Failed to register pwm-fan as cooling device");
|
||||
pwm_disable(ctx->pwm);
|
||||
return PTR_ERR(cdev);
|
||||
}
|
||||
ctx->cdev = cdev;
|
||||
thermal_cdev_update(cdev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -143,6 +284,7 @@ static int pwm_fan_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct pwm_fan_ctx *ctx = platform_get_drvdata(pdev);
|
||||
|
||||
thermal_cooling_device_unregister(ctx->cdev);
|
||||
if (ctx->pwm_value)
|
||||
pwm_disable(ctx->pwm);
|
||||
return 0;
|
||||
@ -177,7 +319,7 @@ static int pwm_fan_resume(struct device *dev)
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(pwm_fan_pm, pwm_fan_suspend, pwm_fan_resume);
|
||||
|
||||
static struct of_device_id of_pwm_fan_match[] = {
|
||||
static const struct of_device_id of_pwm_fan_match[] = {
|
||||
{ .compatible = "pwm-fan", },
|
||||
{},
|
||||
};
|
||||
|
@ -193,7 +193,7 @@ static struct vexpress_hwmon_type vexpress_hwmon_energy = {
|
||||
},
|
||||
};
|
||||
|
||||
static struct of_device_id vexpress_hwmon_of_match[] = {
|
||||
static const struct of_device_id vexpress_hwmon_of_match[] = {
|
||||
#if !defined(CONFIG_REGULATOR_VEXPRESS)
|
||||
{
|
||||
.compatible = "arm,vexpress-volt",
|
||||
|
Loading…
Reference in New Issue
Block a user