Commit 0c63e38a authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge branch 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jdelvare/staging

* 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jdelvare/staging:
  hwmon: New driver for the SMSC EMC6W201
  hwmon: (abituguru) Depend on DMI
  hwmon: (it87) Use request_muxed_region
  hwmon: (sch5627) Trigger Vbat measurements
  hwmon: (sch5627) Add sch5627_send_cmd function
  i8k: Integrate with the hwmon subsystem
  hwmon: (max6650) Properly support the MAX6650
  hwmon: (max6650) Drop device detection
  Move ACPI power meter driver to hwmon
  hwmon: (f71882fg) Add support for F71808A
  hwmon: (f71882fg) Split has_beep in fan_has_beep and temp_has_beep
  hwmon: (asc7621) Drop duplicate dependency
  hwmon: (jc42) Change detection class
  hwmon: Add driver for AMD family 15h processor power information
  hwmon: (k10temp) Add support for Fam15h (Bulldozer)
  hwmon: Use helper functions to set and get driver data
  i8k: Avoid lahf in 64-bit code
parents 0798b1db b0b349a8
Kernel driver emc6w201
======================
Supported chips:
* SMSC EMC6W201
Prefix: 'emc6w201'
Addresses scanned: I2C 0x2c, 0x2d, 0x2e
Datasheet: Not public
Author: Jean Delvare <khali@linux-fr.org>
Description
-----------
From the datasheet:
"The EMC6W201 is an environmental monitoring device with automatic fan
control capability and enhanced system acoustics for noise suppression.
This ACPI compliant device provides hardware monitoring for up to six
voltages (including its own VCC) and five external thermal sensors,
measures the speed of up to five fans, and controls the speed of
multiple DC fans using three Pulse Width Modulator (PWM) outputs. Note
that it is possible to control more than three fans by connecting two
fans to one PWM output. The EMC6W201 will be available in a 36-pin
QFN package."
The device is functionally close to the EMC6D100 series, but is
register-incompatible.
The driver currently only supports the monitoring of the voltages,
temperatures and fan speeds. Limits can be changed. Alarms are not
supported, and neither is fan speed control.
Known Systems With EMC6W201
---------------------------
The EMC6W201 is a rare device, only found on a few systems, made in
2005 and 2006. Known systems with this device:
* Dell Precision 670 workstation
* Gigabyte 2CEWH mainboard
......@@ -6,6 +6,10 @@ Supported chips:
Prefix: 'f71808e'
Addresses scanned: none, address read from Super I/O config space
Datasheet: Not public
* Fintek F71808A
Prefix: 'f71808a'
Addresses scanned: none, address read from Super I/O config space
Datasheet: Not public
* Fintek F71858FG
Prefix: 'f71858fg'
Addresses scanned: none, address read from Super I/O config space
......
Kernel driver fam15h_power
==========================
Supported chips:
* AMD Family 15h Processors
Prefix: 'fam15h_power'
Addresses scanned: PCI space
Datasheets:
BIOS and Kernel Developer's Guide (BKDG) For AMD Family 15h Processors
(not yet published)
Author: Andreas Herrmann <andreas.herrmann3@amd.com>
Description
-----------
This driver permits reading of registers providing power information
of AMD Family 15h processors.
For AMD Family 15h processors the following power values can be
calculated using different processor northbridge function registers:
* BasePwrWatts: Specifies in watts the maximum amount of power
consumed by the processor for NB and logic external to the core.
* ProcessorPwrWatts: Specifies in watts the maximum amount of power
the processor can support.
* CurrPwrWatts: Specifies in watts the current amount of power being
consumed by the processor.
This driver provides ProcessorPwrWatts and CurrPwrWatts:
* power1_crit (ProcessorPwrWatts)
* power1_input (CurrPwrWatts)
On multi-node processors the calculated value is for the entire
package and not for a single node. Thus the driver creates sysfs
attributes only for internal node0 of a multi-node processor.
......@@ -11,6 +11,7 @@ Supported chips:
Socket S1G2: Athlon (X2), Sempron (X2), Turion X2 (Ultra)
* AMD Family 12h processors: "Llano"
* AMD Family 14h processors: "Brazos" (C/E/G-Series)
* AMD Family 15h processors: "Bulldozer"
Prefix: 'k10temp'
Addresses scanned: PCI space
......@@ -40,7 +41,7 @@ Description
-----------
This driver permits reading of the internal temperature sensor of AMD
Family 10h/11h/12h/14h processors.
Family 10h/11h/12h/14h/15h processors.
All these processors have a sensor, but on those for Socket F or AM2+,
the sensor may return inconsistent values (erratum 319). The driver
......
......@@ -2,9 +2,13 @@ Kernel driver max6650
=====================
Supported chips:
* Maxim 6650 / 6651
* Maxim MAX6650
Prefix: 'max6650'
Addresses scanned: I2C 0x1b, 0x1f, 0x48, 0x4b
Addresses scanned: none
Datasheet: http://pdfserv.maxim-ic.com/en/ds/MAX6650-MAX6651.pdf
* Maxim MAX6651
Prefix: 'max6651'
Addresses scanned: none
Datasheet: http://pdfserv.maxim-ic.com/en/ds/MAX6650-MAX6651.pdf
Authors:
......@@ -15,10 +19,10 @@ Authors:
Description
-----------
This driver implements support for the Maxim 6650/6651
This driver implements support for the Maxim MAX6650 and MAX6651.
The 2 devices are very similar, but the Maxim 6550 has a reduced feature
set, e.g. only one fan-input, instead of 4 for the 6651.
The 2 devices are very similar, but the MAX6550 has a reduced feature
set, e.g. only one fan-input, instead of 4 for the MAX6651.
The driver is not able to distinguish between the 2 devices.
......@@ -36,6 +40,13 @@ fan1_div rw sets the speed range the inputs can handle. Legal
values are 1, 2, 4, and 8. Use lower values for
faster fans.
Usage notes
-----------
This driver does not auto-detect devices. You will have to instantiate the
devices explicitly. Please see Documentation/i2c/instantiating-devices for
details.
Module parameters
-----------------
......
......@@ -483,6 +483,13 @@ F: drivers/tty/serial/altera_jtaguart.c
F: include/linux/altera_uart.h
F: include/linux/altera_jtaguart.h
AMD FAM15H PROCESSOR POWER MONITORING DRIVER
M: Andreas Herrmann <andreas.herrmann3@amd.com>
L: lm-sensors@lm-sensors.org
S: Maintained
F: Documentation/hwmon/fam15h_power
F: drivers/hwmon/fam15h_power.c
AMD GEODE CS5536 USB DEVICE CONTROLLER DRIVER
M: Thomas Dahlmann <dahlmann.thomas@arcor.de>
L: linux-geode@lists.infradead.org (moderated for non-subscribers)
......
......@@ -915,6 +915,7 @@ config TOSHIBA
config I8K
tristate "Dell laptop support"
select HWMON
---help---
This adds a driver to safely access the System Management Mode
of the CPU on the Dell Inspiron 8000. The System Management Mode
......
......@@ -73,17 +73,6 @@ config ACPI_PROCFS_POWER
Say N to delete power /proc/acpi/ directories that have moved to /sys/
config ACPI_POWER_METER
tristate "ACPI 4.0 power meter"
depends on HWMON
help
This driver exposes ACPI 4.0 power meters as hardware monitoring
devices. Say Y (or M) if you have a computer with ACPI 4.0 firmware
and a power meter.
To compile this driver as a module, choose M here:
the module will be called power-meter.
config ACPI_EC_DEBUGFS
tristate "EC read/write access through /sys/kernel/debug/ec"
default n
......
......@@ -59,7 +59,6 @@ obj-$(CONFIG_ACPI_HOTPLUG_MEMORY) += acpi_memhotplug.o
obj-$(CONFIG_ACPI_BATTERY) += battery.o
obj-$(CONFIG_ACPI_SBS) += sbshc.o
obj-$(CONFIG_ACPI_SBS) += sbs.o
obj-$(CONFIG_ACPI_POWER_METER) += power_meter.o
obj-$(CONFIG_ACPI_HED) += hed.o
obj-$(CONFIG_ACPI_EC_DEBUGFS) += ec_sys.o
......
......@@ -5,6 +5,9 @@
*
* Copyright (C) 2001 Massimo Dal Zotto <dz@debian.org>
*
* Hwmon integration:
* Copyright (C) 2011 Jean Delvare <khali@linux-fr.org>
*
* 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, or (at your option) any
......@@ -24,6 +27,8 @@
#include <linux/dmi.h>
#include <linux/capability.h>
#include <linux/mutex.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <asm/uaccess.h>
#include <asm/io.h>
......@@ -58,6 +63,7 @@
static DEFINE_MUTEX(i8k_mutex);
static char bios_version[4];
static struct device *i8k_hwmon_dev;
MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
......@@ -139,8 +145,8 @@ static int i8k_smm(struct smm_regs *regs)
"movl %%edi,20(%%rax)\n\t"
"popq %%rdx\n\t"
"movl %%edx,0(%%rax)\n\t"
"lahf\n\t"
"shrl $8,%%eax\n\t"
"pushfq\n\t"
"popq %%rax\n\t"
"andl $1,%%eax\n"
:"=a"(rc)
: "a"(regs)
......@@ -455,6 +461,152 @@ static int i8k_open_fs(struct inode *inode, struct file *file)
return single_open(file, i8k_proc_show, NULL);
}
/*
* Hwmon interface
*/
static ssize_t i8k_hwmon_show_temp(struct device *dev,
struct device_attribute *devattr,
char *buf)
{
int cpu_temp;
cpu_temp = i8k_get_temp(0);
if (cpu_temp < 0)
return cpu_temp;
return sprintf(buf, "%d\n", cpu_temp * 1000);
}
static ssize_t i8k_hwmon_show_fan(struct device *dev,
struct device_attribute *devattr,
char *buf)
{
int index = to_sensor_dev_attr(devattr)->index;
int fan_speed;
fan_speed = i8k_get_fan_speed(index);
if (fan_speed < 0)
return fan_speed;
return sprintf(buf, "%d\n", fan_speed);
}
static ssize_t i8k_hwmon_show_label(struct device *dev,
struct device_attribute *devattr,
char *buf)
{
static const char *labels[4] = {
"i8k",
"CPU",
"Left Fan",
"Right Fan",
};
int index = to_sensor_dev_attr(devattr)->index;
return sprintf(buf, "%s\n", labels[index]);
}
static DEVICE_ATTR(temp1_input, S_IRUGO, i8k_hwmon_show_temp, NULL);
static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, i8k_hwmon_show_fan, NULL,
I8K_FAN_LEFT);
static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, i8k_hwmon_show_fan, NULL,
I8K_FAN_RIGHT);
static SENSOR_DEVICE_ATTR(name, S_IRUGO, i8k_hwmon_show_label, NULL, 0);
static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, i8k_hwmon_show_label, NULL, 1);
static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, i8k_hwmon_show_label, NULL, 2);
static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, i8k_hwmon_show_label, NULL, 3);
static void i8k_hwmon_remove_files(struct device *dev)
{
device_remove_file(dev, &dev_attr_temp1_input);
device_remove_file(dev, &sensor_dev_attr_fan1_input.dev_attr);
device_remove_file(dev, &sensor_dev_attr_fan2_input.dev_attr);
device_remove_file(dev, &sensor_dev_attr_temp1_label.dev_attr);
device_remove_file(dev, &sensor_dev_attr_fan1_label.dev_attr);
device_remove_file(dev, &sensor_dev_attr_fan2_label.dev_attr);
device_remove_file(dev, &sensor_dev_attr_name.dev_attr);
}
static int __init i8k_init_hwmon(void)
{
int err;
i8k_hwmon_dev = hwmon_device_register(NULL);
if (IS_ERR(i8k_hwmon_dev)) {
err = PTR_ERR(i8k_hwmon_dev);
i8k_hwmon_dev = NULL;
printk(KERN_ERR "i8k: hwmon registration failed (%d)\n", err);
return err;
}
/* Required name attribute */
err = device_create_file(i8k_hwmon_dev,
&sensor_dev_attr_name.dev_attr);
if (err)
goto exit_unregister;
/* CPU temperature attributes, if temperature reading is OK */
err = i8k_get_temp(0);
if (err < 0) {
dev_dbg(i8k_hwmon_dev,
"Not creating temperature attributes (%d)\n", err);
} else {
err = device_create_file(i8k_hwmon_dev, &dev_attr_temp1_input);
if (err)
goto exit_remove_files;
err = device_create_file(i8k_hwmon_dev,
&sensor_dev_attr_temp1_label.dev_attr);
if (err)
goto exit_remove_files;
}
/* Left fan attributes, if left fan is present */
err = i8k_get_fan_status(I8K_FAN_LEFT);
if (err < 0) {
dev_dbg(i8k_hwmon_dev,
"Not creating %s fan attributes (%d)\n", "left", err);
} else {
err = device_create_file(i8k_hwmon_dev,
&sensor_dev_attr_fan1_input.dev_attr);
if (err)
goto exit_remove_files;
err = device_create_file(i8k_hwmon_dev,
&sensor_dev_attr_fan1_label.dev_attr);
if (err)
goto exit_remove_files;
}
/* Right fan attributes, if right fan is present */
err = i8k_get_fan_status(I8K_FAN_RIGHT);
if (err < 0) {
dev_dbg(i8k_hwmon_dev,
"Not creating %s fan attributes (%d)\n", "right", err);
} else {
err = device_create_file(i8k_hwmon_dev,
&sensor_dev_attr_fan2_input.dev_attr);
if (err)
goto exit_remove_files;
err = device_create_file(i8k_hwmon_dev,
&sensor_dev_attr_fan2_label.dev_attr);
if (err)
goto exit_remove_files;
}
return 0;
exit_remove_files:
i8k_hwmon_remove_files(i8k_hwmon_dev);
exit_unregister:
hwmon_device_unregister(i8k_hwmon_dev);
return err;
}
static void __exit i8k_exit_hwmon(void)
{
i8k_hwmon_remove_files(i8k_hwmon_dev);
hwmon_device_unregister(i8k_hwmon_dev);
}
static struct dmi_system_id __initdata i8k_dmi_table[] = {
{
.ident = "Dell Inspiron",
......@@ -580,6 +732,7 @@ static int __init i8k_probe(void)
static int __init i8k_init(void)
{
struct proc_dir_entry *proc_i8k;
int err;
/* Are we running on an supported laptop? */
if (i8k_probe())
......@@ -590,15 +743,24 @@ static int __init i8k_init(void)
if (!proc_i8k)
return -ENOENT;
err = i8k_init_hwmon();
if (err)
goto exit_remove_proc;
printk(KERN_INFO
"Dell laptop SMM driver v%s Massimo Dal Zotto (dz@debian.org)\n",
I8K_VERSION);
return 0;
exit_remove_proc:
remove_proc_entry("i8k", NULL);
return err;
}
static void __exit i8k_exit(void)
{
i8k_exit_hwmon();
remove_proc_entry("i8k", NULL);
}
......
......@@ -41,7 +41,7 @@ comment "Native drivers"
config SENSORS_ABITUGURU
tristate "Abit uGuru (rev 1 & 2)"
depends on X86 && EXPERIMENTAL
depends on X86 && DMI && EXPERIMENTAL
help
If you say yes here you get support for the sensor part of the first
and second revision of the Abit uGuru chip. The voltage and frequency
......@@ -56,7 +56,7 @@ config SENSORS_ABITUGURU
config SENSORS_ABITUGURU3
tristate "Abit uGuru (rev 3)"
depends on X86 && EXPERIMENTAL
depends on X86 && DMI && EXPERIMENTAL
help
If you say yes here you get support for the sensor part of the
third revision of the Abit uGuru chip. Only reading the sensors
......@@ -213,7 +213,7 @@ config SENSORS_ADT7475
config SENSORS_ASC7621
tristate "Andigilog aSC7621"
depends on HWMON && I2C
depends on I2C
help
If you say yes here you get support for the aSC7621
family of SMBus sensors chip found on most Intel X38, X48, X58,
......@@ -237,17 +237,27 @@ config SENSORS_K8TEMP
will be called k8temp.
config SENSORS_K10TEMP
tristate "AMD Family 10h/11h/12h/14h temperature sensor"
tristate "AMD Family 10h+ temperature sensor"
depends on X86 && PCI
help
If you say yes here you get support for the temperature
sensor(s) inside your CPU. Supported are later revisions of
the AMD Family 10h and all revisions of the AMD Family 11h,
12h (Llano), and 14h (Brazos) microarchitectures.
12h (Llano), 14h (Brazos) and 15h (Bulldozer) microarchitectures.
This driver can also be built as a module. If so, the module
will be called k10temp.
config SENSORS_FAM15H_POWER
tristate "AMD Family 15h processor power"
depends on X86 && PCI
help
If you say yes here you get support for processor power
information of your AMD family 15h CPU.
This driver can also be built as a module. If so, the module
will be called fam15h_power.
config SENSORS_ASB100
tristate "Asus ASB100 Bach"
depends on X86 && I2C && EXPERIMENTAL
......@@ -319,7 +329,7 @@ config SENSORS_F71882FG
If you say yes here you get support for hardware monitoring
features of many Fintek Super-I/O (LPC) chips. The currently
supported chips are:
F71808E
F71808E/A
F71858FG
F71862FG
F71863FG
......@@ -978,6 +988,16 @@ config SENSORS_EMC2103
This driver can also be built as a module. If so, the module
will be called emc2103.
config SENSORS_EMC6W201
tristate "SMSC EMC6W201"
depends on I2C
help
If you say yes here you get support for the SMSC EMC6W201
hardware monitoring chip.
This driver can also be built as a module. If so, the module
will be called emc6w201.
config SENSORS_SMSC47M1
tristate "SMSC LPC47M10x and compatibles"
help
......@@ -1341,6 +1361,16 @@ if ACPI
comment "ACPI drivers"
config SENSORS_ACPI_POWER
tristate "ACPI 4.0 power meter"
help
This driver exposes ACPI 4.0 power meters as hardware monitoring
devices. Say Y (or M) if you have a computer with ACPI 4.0 firmware
and a power meter.
To compile this driver as a module, choose M here:
the module will be called acpi_power_meter.
config SENSORS_ATK0110
tristate "ASUS ATK0110"
depends on X86 && EXPERIMENTAL
......
......@@ -6,6 +6,7 @@ obj-$(CONFIG_HWMON) += hwmon.o
obj-$(CONFIG_HWMON_VID) += hwmon-vid.o
# APCI drivers
obj-$(CONFIG_SENSORS_ACPI_POWER) += acpi_power_meter.o
obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o
# Native drivers
......@@ -45,9 +46,11 @@ obj-$(CONFIG_SENSORS_DS620) += ds620.o
obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o
obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o
obj-$(CONFIG_SENSORS_EMC6W201) += emc6w201.o
obj-$(CONFIG_SENSORS_F71805F) += f71805f.o
obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o
obj-$(CONFIG_SENSORS_F75375S) += f75375s.o
obj-$(CONFIG_SENSORS_FAM15H_POWER) += fam15h_power.o
obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o
obj-$(CONFIG_SENSORS_G760A) += g760a.o
obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
......
......@@ -1448,15 +1448,12 @@ static int __init abituguru_init(void)
{
int address, err;
struct resource res = { .flags = IORESOURCE_IO };
#ifdef CONFIG_DMI
const char *board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR);
/* safety check, refuse to load on non Abit motherboards */
if (!force && (!board_vendor ||
strcmp(board_vendor, "http://www.abit.com.tw/")))
return -ENODEV;
#endif
address = abituguru_detect();
if (address < 0)
......
......@@ -1119,8 +1119,6 @@ static struct platform_driver abituguru3_driver = {
.resume = abituguru3_resume
};
#ifdef CONFIG_DMI
static int __init abituguru3_dmi_detect(void)
{
const char *board_vendor, *board_name;
......@@ -1159,15 +1157,6 @@ static int __init abituguru3_dmi_detect(void)
return 1;
}
#else /* !CONFIG_DMI */
static inline int abituguru3_dmi_detect(void)
{
return 1;
}
#endif /* CONFIG_DMI */
/* FIXME: Manual detection should die eventually; we need to collect stable
* DMI model names first before we can rely entirely on CONFIG_DMI.
*/
......@@ -1216,10 +1205,8 @@ static int __init abituguru3_init(void)
if (err)
return err;
#ifdef CONFIG_DMI
pr_warn("this motherboard was not detected using DMI. "
"Please send the output of \"dmidecode\" to the abituguru3 maintainer (see MAINTAINERS)\n");
#endif
}
err = platform_driver_register(&abituguru3_driver);
......
......@@ -62,7 +62,7 @@ static ssize_t adcxx_read(struct device *dev,
{
struct spi_device *spi = to_spi_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct adcxx *adc = dev_get_drvdata(&spi->dev);
struct adcxx *adc = spi_get_drvdata(spi);
u8 tx_buf[2];
u8 rx_buf[2];
int status;
......@@ -105,7 +105,7 @@ static ssize_t adcxx_show_max(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct spi_device *spi = to_spi_device(dev);
struct adcxx *adc = dev_get_drvdata(&spi->dev);
struct adcxx *adc = spi_get_drvdata(spi);
u32 reference;
if (mutex_lock_interruptible(&adc->lock))
......@@ -122,7 +122,7 @@ static ssize_t adcxx_set_max(struct device *dev,
struct device_attribute *devattr, const char *buf, size_t count)
{
struct spi_device *spi = to_spi_device(dev);
struct adcxx *adc = dev_get_drvdata(&spi->dev);
struct adcxx *adc = spi_get_drvdata(spi);
unsigned long value;
if (strict_strtoul(buf, 10, &value))
......@@ -142,7 +142,7 @@ static ssize_t adcxx_show_name(struct device *dev, struct device_attribute
*devattr, char *buf)
{
struct spi_device *spi = to_spi_device(dev);
struct adcxx *adc = dev_get_drvdata(&spi->dev);
struct adcxx *adc = spi_get_drvdata(spi);
return sprintf(buf, "adcxx%ds\n", adc->channels);
}
......@@ -182,7 +182,7 @@ static int __devinit adcxx_probe(struct spi_device *spi)
mutex_lock(&adc->lock);
dev_set_drvdata(&spi->dev, adc);
spi_set_drvdata(spi, adc);
for (i = 0; i < 3 + adc->channels; i++) {
status = device_create_file(&spi->dev, &ad_input[i].dev_attr);
......@@ -206,7 +206,7 @@ static int __devinit adcxx_probe(struct spi_device *spi)
for (i--; i >= 0; i--)
device_remove_file(&spi->dev, &ad_input[i].dev_attr);
dev_set_drvdata(&spi->dev, NULL);
spi_set_drvdata(spi, NULL);
mutex_unlock(&adc->lock);
kfree(adc);
return status;
......@@ -214,7 +214,7 @@ static int __devinit adcxx_probe(struct spi_device *spi)
static int __devexit adcxx_remove(struct spi_device *spi)
{
struct adcxx *adc = dev_get_drvdata(&spi->dev);
struct adcxx *adc = spi_get_drvdata(spi);
int i;
mutex_lock(&adc->lock);
......@@ -222,7 +222,7 @@ static int __devexit adcxx_remove(struct spi_device *spi)
for (i = 0; i < 3 + adc->channels; i++)
device_remove_file(&spi->dev, &ad_input[i].dev_attr);
dev_set_drvdata(&spi->dev, NULL);
spi_set_drvdata(spi, NULL);
mutex_unlock(&adc->lock);
kfree(adc);
......
/*
* emc6w201.c - Hardware monitoring driver for the SMSC EMC6W201
* Copyright (C) 2011 Jean Delvare <khali@linux-fr.org>
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/mutex.h>
/*
* Addresses to scan
*/
static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, I2C_CLIENT_END };
/*
* The EMC6W201 registers
*/
#define EMC6W201_REG_IN(nr) (0x20 + (nr))
#define EMC6W201_REG_TEMP(nr) (0x26 + (nr))
#define EMC6W201_REG_FAN(nr) (0x2C + (nr) * 2)
#define EMC6W201_REG_COMPANY 0x3E
#define EMC6W201_REG_VERSTEP 0x3F
#define EMC6W201_REG_CONFIG 0x40
#define EMC6W201_REG_IN_LOW(nr) (0x4A + (nr) * 2)
#define EMC6W201_REG_IN_HIGH(nr) (0x4B + (nr) * 2)
#define EMC6W201_REG_TEMP_LOW(nr) (0x56 + (nr) * 2)
#define EMC6W201_REG_TEMP_HIGH(nr) (0x57 + (nr) * 2)
#define EMC6W201_REG_FAN_MIN(nr) (0x62 + (nr) * 2)
enum { input, min, max } subfeature;
/*
* Per-device data
*/
struct emc6w201_data {
struct device *hwmon_dev;
struct mutex update_lock;
char valid; /* zero until following fields are valid */
unsigned long last_updated; /* in jiffies */
/* registers values */
u8 in[3][6];
s8 temp[3][6];
u16 fan[2][5];
};
/*
* Combine LSB and MSB registers in a single value
* Locking: must be called with data->update_lock held
*/
static u16 emc6w201_read16(struct i2c_client *client, u8 reg)
{
int lsb, msb;
lsb = i2c_smbus_read_byte_data(client, reg);
msb = i2c_smbus_read_byte_data(client, reg + 1);
if (lsb < 0 || msb < 0) {
dev_err(&client->dev, "16-bit read failed at 0x%02x\n", reg);
return 0xFFFF; /* Arbitrary value */
}
return (msb << 8) | lsb;
}
/*
* Write 16-bit value to LSB and MSB registers
* Locking: must be called with data->update_lock held
*/
static int emc6w201_write16(struct i2c_client *client, u8 reg, u16 val)
{
int err;
err = i2c_smbus_write_byte_data(client, reg, val & 0xff);
if (!err)
err = i2c_smbus_write_byte_data(client, reg + 1, val >> 8);
if (err < 0)
dev_err(&client->dev, "16-bit write failed at 0x%02x\n", reg);
return err;
}
static struct emc6w201_data *emc6w201_update_device(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct emc6w201_data *data = i2c_get_clientdata(client);
int nr;
mutex_lock(&data->update_lock);
if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
for (nr = 0; nr < 6; nr++) {
data->in[input][nr] =
i2c_smbus_read_byte_data(client,
EMC6W201_REG_IN(nr));
data->in[min][nr] =
i2c_smbus_read_byte_data(client,
EMC6W201_REG_IN_LOW(nr));
data->in[max][nr] =
i2c_smbus_read_byte_data(client,
EMC6W201_REG_IN_HIGH(nr));
}
for (nr = 0; nr < 6; nr++) {
data->temp[input][nr] =
i2c_smbus_read_byte_data(client,
EMC6W201_REG_TEMP(nr));
data->temp[min][nr] =
i2c_smbus_read_byte_data(client,
EMC6W201_REG_TEMP_LOW(nr));
data->temp[max][nr] =
i2c_smbus_read_byte_data(client,
EMC6W201_REG_TEMP_HIGH(nr));
}
for (nr = 0; nr < 5; nr++) {
data->fan[input][nr] =
emc6w201_read16(client,
EMC6W201_REG_FAN(nr));
data->fan[min][nr] =
emc6w201_read16(client,
EMC6W201_REG_FAN_MIN(nr));
}
data->last_updated = jiffies;
data->valid = 1;
}
mutex_unlock(&data->update_lock);
return data;
}
/*
* Sysfs callback functions
*/
static const u16 nominal_mv[6] = { 2500, 1500, 3300, 5000, 1500, 1500 };
static ssize_t show_in(struct device *dev, struct device_attribute *devattr,
char *buf)
{
struct emc6w201_data *data = emc6w201_update_device(dev);
int sf = to_sensor_dev_attr_2(devattr)->index;
int nr = to_sensor_dev_attr_2(devattr)->nr;
return sprintf(buf, "%u\n",
(unsigned)data->in[sf][nr] * nominal_mv[nr] / 0xC0);
}
static ssize_t set_in(struct device *dev, struct device_attribute *devattr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct emc6w201_data *data = i2c_get_clientdata(client);
int sf = to_sensor_dev_attr_2(devattr)->index;
int nr = to_sensor_dev_attr_2(devattr)->nr;
int err;
long val;
u8 reg;
err = strict_strtol(buf, 10, &val);
if (err < 0)
return err;
val = DIV_ROUND_CLOSEST(val * 0xC0, nominal_mv[nr]);
reg = (sf == min) ? EMC6W201_REG_IN_LOW(nr)
: EMC6W201_REG_IN_HIGH(nr);
mutex_lock(&data->update_lock);
data->in[sf][nr] = SENSORS_LIMIT(val, 0, 255);
err = i2c_smbus_write_byte_data(client, reg, data->in[sf][nr]);
mutex_unlock(&data->update_lock);
return err < 0 ? err : count;
}
static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
char *buf)
{
struct emc6w201_data *data = emc6w201_update_device(dev);
int sf = to_sensor_dev_attr_2(devattr)->index;
int nr = to_sensor_dev_attr_2(devattr)->nr;
return sprintf(buf, "%d\n", (int)data->temp[sf][nr] * 1000);
}
static ssize_t set_temp(struct device *dev, struct device_attribute *devattr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct emc6w201_data *data = i2c_get_clientdata(client);
int sf = to_sensor_dev_attr_2(devattr)->index;
int nr = to_sensor_dev_attr_2(devattr)->nr;
int err;
long val;
u8 reg;
err = strict_strtol(buf, 10, &val);
if (err < 0)
return err;
val /= 1000;
reg = (sf == min) ? EMC6W201_REG_TEMP_LOW(nr)
: EMC6W201_REG_TEMP_HIGH(nr);
mutex_lock(&data->update_lock);
data->temp[sf][nr] = SENSORS_LIMIT(val, -127, 128);
err = i2c_smbus_write_byte_data(client, reg, data->temp[sf][nr]);
mutex_unlock(&data->update_lock);
return err < 0 ? err : count;
}
static ssize_t show_fan(struct device *dev, struct device_attribute *devattr,
char *buf)
{
struct emc6w201_data *data = emc6w201_update_device(dev);
int sf = to_sensor_dev_attr_2(devattr)->index;
int nr = to_sensor_dev_attr_2(devattr)->nr;
unsigned rpm;
if (data->fan[sf][nr] == 0 || data->fan[sf][nr] == 0xFFFF)
rpm = 0;
else
rpm = 5400000U / data->fan[sf][nr];
return sprintf(buf, "%u\n", rpm);
}
static ssize_t set_fan(struct device *dev, struct device_attribute *devattr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct emc6w201_data *data = i2c_get_clientdata(client);
int sf = to_sensor_dev_attr_2(devattr)->index;
int nr = to_sensor_dev_attr_2(devattr)->nr;
int err;
unsigned long val;
err = strict_strtoul(buf, 10, &val);
if (err < 0)
return err;
if (val == 0) {
val = 0xFFFF;
} else {
val = DIV_ROUND_CLOSEST(5400000U, val);
val = SENSORS_LIMIT(val, 0, 0xFFFE);
}
mutex_lock(&data->update_lock);
data->fan[sf][nr] = val;
err = emc6w201_write16(client, EMC6W201_REG_FAN_MIN(nr),
data->fan[sf][nr]);
mutex_unlock(&data->update_lock);
return err < 0 ? err : count;
}
static SENSOR_DEVICE_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, input);
static SENSOR_DEVICE_ATTR_2(in0_min, S_IRUGO | S_IWUSR, show_in, set_in,
0, min);
static SENSOR_DEVICE_ATTR_2(in0_max, S_IRUGO | S_IWUSR, show_in, set_in,
0, max);
static SENSOR_DEVICE_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 1, input);
static SENSOR_DEVICE_ATTR_2(in1_min, S_IRUGO | S_IWUSR, show_in, set_in,
1, min);
static SENSOR_DEVICE_ATTR_2(in1_max, S_IRUGO | S_IWUSR, show_in, set_in,
1, max);
static SENSOR_DEVICE_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 2, input);
static SENSOR_DEVICE_ATTR_2(in2_min, S_IRUGO | S_IWUSR, show_in, set_in,
2, min);
static SENSOR_DEVICE_ATTR_2(in2_max, S_IRUGO | S_IWUSR, show_in, set_in,
2, max);
static SENSOR_DEVICE_ATTR_2(in3_input, S_IRUGO, show_in, NULL, 3, input);
static SENSOR_DEVICE_ATTR_2(in3_min, S_IRUGO | S_IWUSR, show_in, set_in,
3, min);
static SENSOR_DEVICE_ATTR_2(in3_max, S_IRUGO | S_IWUSR, show_in, set_in,
3, max);
static SENSOR_DEVICE_ATTR_2(in4_input, S_IRUGO, show_in, NULL, 4, input);
static SENSOR_DEVICE_ATTR_2(in4_min, S_IRUGO | S_IWUSR, show_in, set_in,
4, min);
static SENSOR_DEVICE_ATTR_2(in4_max, S_IRUGO | S_IWUSR, show_in, set_in,
4, max);
static SENSOR_DEVICE_ATTR_2(in5_input, S_IRUGO, show_in, NULL, 5, input);
static SENSOR_DEVICE_ATTR_2(in5_min, S_IRUGO | S_IWUSR, show_in, set_in,
5, min);
static SENSOR_DEVICE_ATTR_2(in5_max, S_IRUGO | S_IWUSR, show_in, set_in,
5, max);
static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, input);
static SENSOR_DEVICE_ATTR_2(temp1_min, S_IRUGO | S_IWUSR, show_temp, set_temp,
0, min);
static SENSOR_DEVICE_ATTR_2(temp1_max, S_IRUGO | S_IWUSR, show_temp, set_temp,
0, max);
static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 1, input);
static SENSOR_DEVICE_ATTR_2(temp2_min, S_IRUGO | S_IWUSR, show_temp, set_temp,
1, min);
static SENSOR_DEVICE_ATTR_2(temp2_max, S_IRUGO | S_IWUSR, show_temp, set_temp,
1, max);
static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 2, input);
static SENSOR_DEVICE_ATTR_2(temp3_min, S_IRUGO | S_IWUSR, show_temp, set_temp,
2, min);
static SENSOR_DEVICE_ATTR_2(temp3_max, S_IRUGO | S_IWUSR, show_temp, set_temp,
2, max);
static SENSOR_DEVICE_ATTR_2(temp4_input, S_IRUGO, show_temp, NULL, 3, input);
static SENSOR_DEVICE_ATTR_2(temp4_min, S_IRUGO | S_IWUSR, show_temp, set_temp,
3, min);
static SENSOR_DEVICE_ATTR_2(temp4_max, S_IRUGO | S_IWUSR, show_temp, set_temp,
3, max);
static SENSOR_DEVICE_ATTR_2(temp5_input, S_IRUGO, show_temp, NULL, 4, input);
static SENSOR_DEVICE_ATTR_2(temp5_min, S_IRUGO | S_IWUSR, show_temp, set_temp,
4, min);
static SENSOR_DEVICE_ATTR_2(temp5_max, S_IRUGO | S_IWUSR, show_temp, set_temp,
4, max);
static SENSOR_DEVICE_ATTR_2(temp6_input, S_IRUGO, show_temp, NULL, 5, input);
static SENSOR_DEVICE_ATTR_2(temp6_min, S_IRUGO | S_IWUSR, show_temp, set_temp,
5, min);
static SENSOR_DEVICE_ATTR_2(temp6_max, S_IRUGO | S_IWUSR, show_temp, set_temp,
5, max);
static SENSOR_DEVICE_ATTR_2(fan1_input, S_IRUGO, show_fan, NULL, 0, input);
static SENSOR_DEVICE_ATTR_2(fan1_min, S_IRUGO | S_IWUSR, show_fan, set_fan,
0, min);
static SENSOR_DEVICE_ATTR_2(fan2_input, S_IRUGO, show_fan, NULL, 1, input);
static SENSOR_DEVICE_ATTR_2(fan2_min, S_IRUGO | S_IWUSR, show_fan, set_fan,
1, min);
static SENSOR_DEVICE_ATTR_2(fan3_input, S_IRUGO, show_fan, NULL, 2, input);
static SENSOR_DEVICE_ATTR_2(fan3_min, S_IRUGO | S_IWUSR, show_fan, set_fan,
2, min);
static SENSOR_DEVICE_ATTR_2(fan4_input, S_IRUGO, show_fan, NULL, 3, input);
static SENSOR_DEVICE_ATTR_2(fan4_min, S_IRUGO | S_IWUSR, show_fan, set_fan,
3, min);
static SENSOR_DEVICE_ATTR_2(fan5_input, S_IRUGO, show_fan, NULL, 4, input);
static SENSOR_DEVICE_ATTR_2(fan5_min, S_IRUGO | S_IWUSR, show_fan, set_fan,
4, min);
static struct attribute *emc6w201_attributes[] = {
&sensor_dev_attr_in0_input.dev_attr.attr,
&sensor_dev_attr_in0_min.dev_attr.attr,
&sensor_dev_attr_in0_max.dev_attr.attr,
&sensor_dev_attr_in1_input.dev_attr.attr,
&sensor_dev_attr_in1_min.dev_attr.attr,
&sensor_dev_attr_in1_max.dev_attr.attr,
&sensor_dev_attr_in2_input.dev_attr.attr,
&sensor_dev_attr_in2_min.dev_attr.attr,
&sensor_dev_attr_in2_max.dev_attr.attr,
&sensor_dev_attr_in3_input.dev_attr.attr,
&sensor_dev_attr_in3_min.dev_attr.attr,
&sensor_dev_attr_in3_max.dev_attr.attr,
&sensor_dev_attr_in4_input.dev_attr.attr,
&sensor_dev_attr_in4_min.dev_attr.attr,
&sensor_dev_attr_in4_max.dev_attr.attr,
&sensor_dev_attr_in5_input.dev_attr.attr,
&sensor_dev_attr_in5_min.dev_attr.attr,
&sensor_dev_attr_in5_max.dev_attr.attr,
&sensor_dev_attr_temp1_input.dev_attr.attr,
&sensor_dev_attr_temp1_min.dev_attr.attr,
&sensor_dev_attr_temp1_max.dev_attr.attr,
&sensor_dev_attr_temp2_input.dev_attr.attr,
&sensor_dev_attr_temp2_min.dev_attr.attr,
&sensor_dev_attr_temp2_max.dev_attr.attr,
&sensor_dev_attr_temp3_input.dev_attr.attr,
&sensor_dev_attr_temp3_min.dev_attr.attr,
&sensor_dev_attr_temp3_max.dev_attr.attr,
&sensor_dev_attr_temp4_input.dev_attr.attr,
&sensor_dev_attr_temp4_min.dev_attr.attr,
&sensor_dev_attr_temp4_max.dev_attr.attr,
&sensor_dev_attr_temp5_input.dev_attr.attr,
&sensor_dev_attr_temp5_min.dev_attr.attr,
&sensor_dev_attr_temp5_max.dev_attr.attr,
&sensor_dev_attr_temp6_input.dev_attr.attr,
&sensor_dev_attr_temp6_min.dev_attr.attr,
&sensor_dev_attr_temp6_max.dev_attr.attr,
&sensor_dev_attr_fan1_input.dev_attr.attr,
&sensor_dev_attr_fan1_min.dev_attr.attr,
&sensor_dev_attr_fan2_input.dev_attr.attr,
&sensor_dev_attr_fan2_min.dev_attr.attr,
&sensor_dev_attr_fan3_input.dev_attr.attr,
&sensor_dev_attr_fan3_min.dev_attr.attr,
&sensor_dev_attr_fan4_input.dev_attr.attr,
&sensor_dev_attr_fan4_min.dev_attr.attr,
&sensor_dev_attr_fan5_input.dev_attr.attr,
&sensor_dev_attr_fan5_min.dev_attr.attr,
NULL
};
static const struct attribute_group emc6w201_group = {
.attrs = emc6w201_attributes,
};
/*
* Driver interface
*/
/* Return 0 if detection is successful, -ENODEV otherwise */
static int emc6w201_detect(struct i2c_client *client,
struct i2c_board_info *info)
{
struct i2c_adapter *adapter = client->adapter;
int company, verstep, config;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -ENODEV;
/* Identification */
company = i2c_smbus_read_byte_data(client, EMC6W201_REG_COMPANY);
if (company != 0x5C)
return -ENODEV;
verstep = i2c_smbus_read_byte_data(client, EMC6W201_REG_VERSTEP);
if (verstep < 0 || (verstep & 0xF0) != 0xB0)
return -ENODEV;
if ((verstep & 0x0F) > 2) {
dev_dbg(&client->dev, "Unknwown EMC6W201 stepping %d\n",
verstep & 0x0F);
return -ENODEV;
}
/* Check configuration */
config = i2c_smbus_read_byte_data(client, EMC6W201_REG_CONFIG);
if ((config & 0xF4) != 0x04)
return -ENODEV;
if (!(config & 0x01)) {
dev_err(&client->dev, "Monitoring not enabled\n");
return -ENODEV;
}
strlcpy(info->type, "emc6w201", I2C_NAME_SIZE);
return 0;
}
static int emc6w201_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct emc6w201_data *data;
int err;
data = kzalloc(sizeof(struct emc6w201_data), GFP_KERNEL);
if (!data) {
err = -ENOMEM;
goto exit;
}
i2c_set_clientdata(client, data);
mutex_init(&data->update_lock);
/* Create sysfs attribute */
err = sysfs_create_group(&client->dev.kobj, &emc6w201_group);
if (err)
goto exit_free;
/* Expose as a hwmon device */
data->hwmon_dev = hwmon_device_register(&client->dev);
if (IS_ERR(data->hwmon_dev)) {
err = PTR_ERR(data->hwmon_dev);
goto exit_remove;
}
return 0;
exit_remove:
sysfs_remove_group(&client->dev.kobj, &emc6w201_group);
exit_free:
kfree(data);
exit:
return err;
}
static int emc6w201_remove(struct i2c_client *client)
{
struct emc6w201_data *data = i2c_get_clientdata(client);
hwmon_device_unregister(data->hwmon_dev);
sysfs_remove_group(&client->dev.kobj, &emc6w201_group);
kfree(data);
return 0;
}
static const struct i2c_device_id emc6w201_id[] = {
{ "emc6w201", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, emc6w201_id);
static struct i2c_driver emc6w201_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "emc6w201",
},
.probe = emc6w201_probe,
.remove = emc6w201_remove,
.id_table = emc6w201_id,
.detect = emc6w201_detect,
.address_list = normal_i2c,
};
static int __init sensors_emc6w201_init(void)
{
return i2c_add_driver(&emc6w201_driver);
}
module_init(sensors_emc6w201_init);
static void __exit sensors_emc6w201_exit(void)
{
i2c_del_driver(&emc6w201_driver);
}
module_exit(sensors_emc6w201_exit);
MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>");
MODULE_DESCRIPTION("SMSC EMC6W201 hardware monitoring driver");
MODULE_LICENSE("GPL");
......@@ -48,6 +48,7 @@
#define SIO_FINTEK_ID 0x1934 /* Manufacturers ID */
#define SIO_F71808E_ID 0x0901 /* Chipset ID */
#define SIO_F71808A_ID 0x1001 /* Chipset ID */
#define SIO_F71858_ID 0x0507 /* Chipset ID */
#define SIO_F71862_ID 0x0601 /* Chipset ID */
#define SIO_F71869_ID 0x0814 /* Chipset ID */
......@@ -107,11 +108,12 @@ static unsigned short force_id;
module_param(force_id, ushort, 0);
MODULE_PARM_DESC(force_id, "Override the detected device ID");
enum chips { f71808e, f71858fg, f71862fg, f71869, f71882fg, f71889fg,
enum chips { f71808e, f71808a, f71858fg, f71862fg, f71869, f71882fg, f71889fg,
f71889ed, f71889a, f8000, f81865f };
static const char *f71882fg_names[] = {
"f71808e",
"f71808a",
"f71858fg",
"f71862fg",
"f71869", /* Both f71869f and f71869e, reg. compatible and same id */
......@@ -125,6 +127,7 @@ static const char *f71882fg_names[] = {
static const char f71882fg_has_in[][F71882FG_MAX_INS] = {
[f71808e] = { 1, 1, 1, 1, 1, 1, 0, 1, 1 },
[f71808a] = { 1, 1, 1, 1, 0, 0, 0, 1, 1 },
[f71858fg] = { 1, 1, 1, 0, 0, 0, 0, 0, 0 },
[f71862fg] = { 1, 1, 1, 1, 1, 1, 1, 1, 1 },
[f71869] = { 1, 1, 1, 1, 1, 1, 1, 1, 1 },
......@@ -138,6 +141,7 @@ static const char f71882fg_has_in[][F71882FG_MAX_INS] = {
static const char f71882fg_has_in1_alarm[] = {
[f71808e] = 0,
[f71808a] = 0,
[f71858fg] = 0,
[f71862fg] = 0,
[f71869] = 0,
......@@ -149,8 +153,9 @@ static const char f71882fg_has_in1_alarm[] = {
[f81865f] = 1,
};
static const char f71882fg_has_beep[] = {
static const char f71882fg_fan_has_beep[] = {
[f71808e] = 0,
[f71808a] = 0,
[f71858fg] = 0,
[f71862fg] = 1,
[f71869] = 1,
......@@ -164,6 +169,7 @@ static const char f71882fg_has_beep[] = {
static const char f71882fg_nr_fans[] = {
[f71808e] = 3,
[f71808a] = 2, /* +1 fan which is monitor + simple pwm only */
[f71858fg] = 3,
[f71862fg] = 3,
[f71869] = 3,
......@@ -171,12 +177,27 @@ static const char f71882fg_nr_fans[] = {
[f71889fg] = 3,
[f71889ed] = 3,
[f71889a] = 3,
[f8000] = 3,
[f8000] = 3, /* +1 fan which is monitor only */
[f81865f] = 2,
};
static const char f71882fg_temp_has_beep[] = {
[f71808e] = 0,
[f71808a] = 1,
[f71858fg] = 0,
[f71862fg] = 1,
[f71869] = 1,
[f71882fg] = 1,
[f71889fg] = 1,
[f71889ed] = 1,
[f71889a] = 1,
[f8000] = 0,
[f81865f] = 1,
};
static const char f71882fg_nr_temps[] = {
[f71808e] = 2,
[f71808a] = 2,
[f71858fg] = 3,
[f71862fg] = 3,
[f71869] = 3,
......@@ -301,6 +322,10 @@ static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr,
char *buf);
static ssize_t store_pwm(struct device *dev, struct device_attribute *devattr,
const char *buf, size_t count);
static ssize_t show_simple_pwm(struct device *dev,
struct device_attribute *devattr, char *buf);
static ssize_t store_simple_pwm(struct device *dev,
struct device_attribute *devattr, const char *buf, size_t count);
static ssize_t show_pwm_enable(struct device *dev,
struct device_attribute *devattr, char *buf);
static ssize_t store_pwm_enable(struct device *dev,
......@@ -550,6 +575,14 @@ static struct sensor_device_attribute_2 fxxxx_fan_attr[4][6] = { {
show_pwm_interpolate, store_pwm_interpolate, 0, 3),
} };
/* Attr for the third fan of the f71808a, which only has manual pwm */
static struct sensor_device_attribute_2 f71808a_fan3_attr[] = {
SENSOR_ATTR_2(fan3_input, S_IRUGO, show_fan, NULL, 0, 2),
SENSOR_ATTR_2(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 0, 2),
SENSOR_ATTR_2(pwm3, S_IRUGO|S_IWUSR,
show_simple_pwm, store_simple_pwm, 0, 2),
};
/* Attr for models which can beep on Fan alarm */
static struct sensor_device_attribute_2 fxxxx_fan_beep_attr[] = {
SENSOR_ATTR_2(fan1_beep, S_IRUGO|S_IWUSR, show_fan_beep,
......@@ -1146,12 +1179,13 @@ static struct f71882fg_data *f71882fg_update_device(struct device *dev)
data->temp_type[3] = (reg & 0x08) ? 2 : 4;
}
if (f71882fg_has_beep[data->type]) {
if (f71882fg_fan_has_beep[data->type])
data->fan_beep = f71882fg_read8(data,
F71882FG_REG_FAN_BEEP);
if (f71882fg_temp_has_beep[data->type])
data->temp_beep = f71882fg_read8(data,
F71882FG_REG_TEMP_BEEP);
}
data->pwm_enable = f71882fg_read8(data,
F71882FG_REG_PWM_ENABLE);
......@@ -1232,7 +1266,13 @@ static struct f71882fg_data *f71882fg_update_device(struct device *dev)
data->pwm[nr] =
f71882fg_read8(data, F71882FG_REG_PWM(nr));
}
/* The f8000 can monitor 1 more fan, but has no pwm for it */
/* Some models have 1 more fan with limited capabilities */
if (data->type == f71808a) {
data->fan[2] = f71882fg_read16(data,
F71882FG_REG_FAN(2));
data->pwm[2] = f71882fg_read8(data,
F71882FG_REG_PWM(2));
}
if (data->type == f8000)
data->fan[3] = f71882fg_read16(data,
F71882FG_REG_FAN(3));
......@@ -1722,6 +1762,38 @@ static ssize_t store_pwm(struct device *dev,
return count;
}
static ssize_t show_simple_pwm(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct f71882fg_data *data = f71882fg_update_device(dev);
int val, nr = to_sensor_dev_attr_2(devattr)->index;
val = data->pwm[nr];
return sprintf(buf, "%d\n", val);
}
static ssize_t store_simple_pwm(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count)
{
struct f71882fg_data *data = dev_get_drvdata(dev);
int err, nr = to_sensor_dev_attr_2(devattr)->index;
long val;
err = strict_strtol(buf, 10, &val);
if (err)
return err;
val = SENSORS_LIMIT(val, 0, 255);
mutex_lock(&data->update_lock);
f71882fg_write8(data, F71882FG_REG_PWM(nr), val);
data->pwm[nr] = val;
mutex_unlock(&data->update_lock);
return count;
}
static ssize_t show_pwm_enable(struct device *dev,
struct device_attribute *devattr, char *buf)
{
......@@ -2140,7 +2212,7 @@ static int __devinit f71882fg_probe(struct platform_device *pdev)
if (err)
goto exit_unregister_sysfs;
if (f71882fg_has_beep[data->type]) {
if (f71882fg_temp_has_beep[data->type]) {
err = f71882fg_create_sysfs_files(pdev,
&fxxxx_temp_beep_attr[0][0],
ARRAY_SIZE(fxxxx_temp_beep_attr[0])
......@@ -2169,6 +2241,7 @@ static int __devinit f71882fg_probe(struct platform_device *pdev)
if (start_reg & 0x02) {
switch (data->type) {
case f71808e:
case f71808a:
case f71869:
/* These always have signed auto point temps */
data->auto_point_temp_signed = 1;
......@@ -2221,7 +2294,7 @@ static int __devinit f71882fg_probe(struct platform_device *pdev)
if (err)
goto exit_unregister_sysfs;
if (f71882fg_has_beep[data->type]) {
if (f71882fg_fan_has_beep[data->type]) {
err = f71882fg_create_sysfs_files(pdev,
fxxxx_fan_beep_attr, nr_fans);
if (err)
......@@ -2230,6 +2303,7 @@ static int __devinit f71882fg_probe(struct platform_device *pdev)
switch (data->type) {
case f71808e:
case f71808a:
case f71869:
case f71889fg:
case f71889ed:
......@@ -2255,6 +2329,16 @@ static int __devinit f71882fg_probe(struct platform_device *pdev)
}
switch (data->type) {
case f71808a:
err = f71882fg_create_sysfs_files(pdev,
&fxxxx_auto_pwm_attr[0][0],
ARRAY_SIZE(fxxxx_auto_pwm_attr[0]) * nr_fans);
if (err)
goto exit_unregister_sysfs;
err = f71882fg_create_sysfs_files(pdev,
f71808a_fan3_attr,
ARRAY_SIZE(f71808a_fan3_attr));
break;
case f71862fg:
err = f71882fg_create_sysfs_files(pdev,
f71862fg_auto_pwm_attr,
......@@ -2343,7 +2427,7 @@ static int f71882fg_remove(struct platform_device *pdev)
&fxxxx_temp_attr[0][0],
ARRAY_SIZE(fxxxx_temp_attr[0]) * nr_temps);
}
if (f71882fg_has_beep[data->type]) {
if (f71882fg_temp_has_beep[data->type]) {
f71882fg_remove_sysfs_files(pdev,
&fxxxx_temp_beep_attr[0][0],
ARRAY_SIZE(fxxxx_temp_beep_attr[0]) * nr_temps);
......@@ -2366,12 +2450,20 @@ static int f71882fg_remove(struct platform_device *pdev)
f71882fg_remove_sysfs_files(pdev, &fxxxx_fan_attr[0][0],
ARRAY_SIZE(fxxxx_fan_attr[0]) * nr_fans);
if (f71882fg_has_beep[data->type]) {
if (f71882fg_fan_has_beep[data->type]) {
f71882fg_remove_sysfs_files(pdev,
fxxxx_fan_beep_attr, nr_fans);
}
switch (data->type) {
case f71808a:
f71882fg_remove_sysfs_files(pdev,
&fxxxx_auto_pwm_attr[0][0],
ARRAY_SIZE(fxxxx_auto_pwm_attr[0]) * nr_fans);
f71882fg_remove_sysfs_files(pdev,
f71808a_fan3_attr,
ARRAY_SIZE(f71808a_fan3_attr));
break;
case f71862fg:
f71882fg_remove_sysfs_files(pdev,
f71862fg_auto_pwm_attr,
......@@ -2424,6 +2516,9 @@ static int __init f71882fg_find(int sioaddr, unsigned short *address,
case SIO_F71808E_ID:
sio_data->type = f71808e;
break;
case SIO_F71808A_ID:
sio_data->type = f71808a;
break;
case SIO_F71858_ID:
sio_data->type = f71858fg;
break;
......
/*
* fam15h_power.c - AMD Family 15h processor power monitoring
*
* Copyright (c) 2011 Advanced Micro Devices, Inc.
* Author: Andreas Herrmann <andreas.herrmann3@amd.com>
*
*
* This driver is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License; either
* version 2 of the License, or (at your option) any later version.
*
* This driver 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.
*
* You should have received a copy of the GNU General Public License
* along with this driver; if not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/bitops.h>
#include <asm/processor.h>
MODULE_DESCRIPTION("AMD Family 15h CPU processor power monitor");
MODULE_AUTHOR("Andreas Herrmann <andreas.herrmann3@amd.com>");
MODULE_LICENSE("GPL");
/* D18F3 */
#define REG_NORTHBRIDGE_CAP 0xe8
/* D18F4 */
#define REG_PROCESSOR_TDP 0x1b8
/* D18F5 */
#define REG_TDP_RUNNING_AVERAGE 0xe0
#define REG_TDP_LIMIT3 0xe8
struct fam15h_power_data {
struct device *hwmon_dev;
unsigned int tdp_to_watts;
unsigned int base_tdp;
unsigned int processor_pwr_watts;
};
static ssize_t show_power(struct device *dev,
struct device_attribute *attr, char *buf)
{
u32 val, tdp_limit, running_avg_range;
s32 running_avg_capture;
u64 curr_pwr_watts;
struct pci_dev *f4 = to_pci_dev(dev);
struct fam15h_power_data *data = dev_get_drvdata(dev);
pci_bus_read_config_dword(f4->bus, PCI_DEVFN(PCI_SLOT(f4->devfn), 5),
REG_TDP_RUNNING_AVERAGE, &val);
running_avg_capture = (val >> 4) & 0x3fffff;
running_avg_capture = sign_extend32(running_avg_capture, 22);
running_avg_range = val & 0xf;
pci_bus_read_config_dword(f4->bus, PCI_DEVFN(PCI_SLOT(f4->devfn), 5),
REG_TDP_LIMIT3, &val);
tdp_limit = val >> 16;
curr_pwr_watts = tdp_limit + data->base_tdp -
(s32)(running_avg_capture >> (running_avg_range + 1));
curr_pwr_watts *= data->tdp_to_watts;
/*
* Convert to microWatt
*
* power is in Watt provided as fixed point integer with
* scaling factor 1/(2^16). For conversion we use
* (10^6)/(2^16) = 15625/(2^10)
*/
curr_pwr_watts = (curr_pwr_watts * 15625) >> 10;
return sprintf(buf, "%u\n", (unsigned int) curr_pwr_watts);
}
static DEVICE_ATTR(power1_input, S_IRUGO, show_power, NULL);
static ssize_t show_power_crit(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct fam15h_power_data *data = dev_get_drvdata(dev);
return sprintf(buf, "%u\n", data->processor_pwr_watts);
}
static DEVICE_ATTR(power1_crit, S_IRUGO, show_power_crit, NULL);
static ssize_t show_name(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "fam15h_power\n");
}
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
static struct attribute *fam15h_power_attrs[] = {
&dev_attr_power1_input.attr,
&dev_attr_power1_crit.attr,
&dev_attr_name.attr,
NULL
};
static const struct attribute_group fam15h_power_attr_group = {
.attrs = fam15h_power_attrs,
};
static bool __devinit fam15h_power_is_internal_node0(struct pci_dev *f4)
{
u32 val;
pci_bus_read_config_dword(f4->bus, PCI_DEVFN(PCI_SLOT(f4->devfn), 3),
REG_NORTHBRIDGE_CAP, &val);
if ((val & BIT(29)) && ((val >> 30) & 3))
return false;
return true;
}
static void __devinit fam15h_power_init_data(struct pci_dev *f4,
struct fam15h_power_data *data)
{
u32 val;
u64 tmp;
pci_read_config_dword(f4, REG_PROCESSOR_TDP, &val);
data->base_tdp = val >> 16;
tmp = val & 0xffff;
pci_bus_read_config_dword(f4->bus, PCI_DEVFN(PCI_SLOT(f4->devfn), 5),
REG_TDP_LIMIT3, &val);
data->tdp_to_watts = ((val & 0x3ff) << 6) | ((val >> 10) & 0x3f);
tmp *= data->tdp_to_watts;
/* result not allowed to be >= 256W */
if ((tmp >> 16) >= 256)
dev_warn(&f4->dev, "Bogus value for ProcessorPwrWatts "
"(processor_pwr_watts>=%u)\n",
(unsigned int) (tmp >> 16));
/* convert to microWatt */
data->processor_pwr_watts = (tmp * 15625) >> 10;
}
static int __devinit fam15h_power_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct fam15h_power_data *data;
struct device *dev;
int err;
if (!fam15h_power_is_internal_node0(pdev)) {
err = -ENODEV;
goto exit;
}
data = kzalloc(sizeof(struct fam15h_power_data), GFP_KERNEL);
if (!data) {
err = -ENOMEM;
goto exit;
}
fam15h_power_init_data(pdev, data);
dev = &pdev->dev;
dev_set_drvdata(dev, data);
err = sysfs_create_group(&dev->kobj, &fam15h_power_attr_group);
if (err)
goto exit_free_data;
data->hwmon_dev = hwmon_device_register(dev);
if (IS_ERR(data->hwmon_dev)) {
err = PTR_ERR(data->hwmon_dev);
goto exit_remove_group;
}
return 0;
exit_remove_group:
sysfs_remove_group(&dev->kobj, &fam15h_power_attr_group);
exit_free_data:
kfree(data);
exit:
return err;
}
static void __devexit fam15h_power_remove(struct pci_dev *pdev)
{
struct device *dev;
struct fam15h_power_data *data;
dev = &pdev->dev;
data = dev_get_drvdata(dev);
hwmon_device_unregister(data->hwmon_dev);
sysfs_remove_group(&dev->kobj, &fam15h_power_attr_group);
dev_set_drvdata(dev, NULL);
kfree(data);
}
static DEFINE_PCI_DEVICE_TABLE(fam15h_power_id_table) = {
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_NB_F4) },
{}
};
MODULE_DEVICE_TABLE(pci, fam15h_power_id_table);
static struct pci_driver fam15h_power_driver = {
.name = "fam15h_power",
.id_table = fam15h_power_id_table,
.probe = fam15h_power_probe,
.remove = __devexit_p(fam15h_power_remove),
};
static int __init fam15h_power_init(void)
{
return pci_register_driver(&fam15h_power_driver);
}
static void __exit fam15h_power_exit(void)
{
pci_unregister_driver(&fam15h_power_driver);
}
module_init(fam15h_power_init)
module_exit(fam15h_power_exit)
......@@ -523,7 +523,7 @@ static void aem_delete(struct aem_data *data)
aem_remove_sensors(data);
hwmon_device_unregister(data->hwmon_dev);
ipmi_destroy_user(data->ipmi.user);
dev_set_drvdata(&data->pdev->dev, NULL);
platform_set_drvdata(data->pdev, NULL);
platform_device_unregister(data->pdev);
aem_idr_put(data->id);
kfree(data);
......@@ -594,7 +594,7 @@ static int aem_init_aem1_inst(struct aem_ipmi_data *probe, u8 module_handle)
if (res)
goto ipmi_err;
dev_set_drvdata(&data->pdev->dev, data);
platform_set_drvdata(data->pdev, data);
/* Set up IPMI interface */
if (aem_init_ipmi_data(&data->ipmi, probe->interface,
......@@ -630,7 +630,7 @@ static int aem_init_aem1_inst(struct aem_ipmi_data *probe, u8 module_handle)
hwmon_reg_err:
ipmi_destroy_user(data->ipmi.user);
ipmi_err:
dev_set_drvdata(&data->pdev->dev, NULL);
platform_set_drvdata(data->pdev, NULL);
platform_device_unregister(data->pdev);
dev_err:
aem_idr_put(data->id);
......@@ -727,7 +727,7 @@ static int aem_init_aem2_inst(struct aem_ipmi_data *probe,
if (res)
goto ipmi_err;
dev_set_drvdata(&data->pdev->dev, data);
platform_set_drvdata(data->pdev, data);
/* Set up IPMI interface */
if (aem_init_ipmi_data(&data->ipmi, probe->interface,
......@@ -763,7 +763,7 @@ static int aem_init_aem2_inst(struct aem_ipmi_data *probe,
hwmon_reg_err:
ipmi_destroy_user(data->ipmi.user);
ipmi_err:
dev_set_drvdata(&data->pdev->dev, NULL);
platform_set_drvdata(data->pdev, NULL);
platform_device_unregister(data->pdev);
dev_err:
aem_idr_put(data->id);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment