Commit 36f021b5 authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'hwmon-for-linus' of git://jdelvare.pck.nerim.net/jdelvare-2.6

* 'hwmon-for-linus' of git://jdelvare.pck.nerim.net/jdelvare-2.6: (32 commits)
  Use menuconfig objects - hwmon
  hwmon/smsc47b397: Use dynamic sysfs callbacks
  hwmon/smsc47b397: Convert to a platform driver
  hwmon/w83781d: Deprecate W83627HF support
  hwmon/w83781d: Use dynamic sysfs callbacks
  hwmon/w83781d: Be less i2c_client-centric
  hwmon/w83781d: Clean up conversion macros
  hwmon/w83781d: No longer use i2c-isa
  hwmon/ams: Do not print error on systems without apple motion sensor
  hwmon/ams: Fix I2C read retry logic
  hwmon: New AD7416, AD7417 and AD7418 driver
  hwmon/coretemp: Add documentation
  hwmon: New coretemp driver
  i386: Use functions from library in msr driver
  i386: Add safe variants of rdmsr_on_cpu and wrmsr_on_cpu
  hwmon/lm75: Use dynamic sysfs callbacks
  hwmon/lm78: Use dynamic sysfs callbacks
  hwmon/lm78: Be less i2c_client-centric
  hwmon/lm78: No longer use i2c-isa
  hwmon: New max6650 driver
  ...
parents 215d0678 1d72acf9
Kernel driver coretemp
======================
Supported chips:
* All Intel Core family
Prefix: 'coretemp'
CPUID: family 0x6, models 0xe, 0xf
Datasheet: Intel 64 and IA-32 Architectures Software Developer's Manual
Volume 3A: System Programming Guide
Author: Rudolf Marek
Description
-----------
This driver permits reading temperature sensor embedded inside Intel Core CPU.
Temperature is measured in degrees Celsius and measurement resolution is
1 degree C. Valid temperatures are from 0 to TjMax degrees C, because
the actual value of temperature register is in fact a delta from TjMax.
Temperature known as TjMax is the maximum junction temperature of processor.
Intel defines this temperature as 85C or 100C. At this temperature, protection
mechanism will perform actions to forcibly cool down the processor. Alarm
may be raised, if the temperature grows enough (more than TjMax) to trigger
the Out-Of-Spec bit. Following table summarizes the exported sysfs files:
temp1_input - Core temperature (in millidegrees Celsius).
temp1_crit - Maximum junction temperature (in millidegrees Celsius).
temp1_crit_alarm - Set when Out-of-spec bit is set, never clears.
Correct CPU operation is no longer guaranteed.
temp1_label - Contains string "Core X", where X is processor
number.
The TjMax temperature is set to 85 degrees C if undocumented model specific
register (UMSR) 0xee has bit 30 set. If not the TjMax is 100 degrees C as
(sometimes) documented in processor datasheet.
Kernel driver max6650
=====================
Supported chips:
* Maxim 6650 / 6651
Prefix: 'max6650'
Addresses scanned: I2C 0x1b, 0x1f, 0x48, 0x4b
Datasheet: http://pdfserv.maxim-ic.com/en/ds/MAX6650-MAX6651.pdf
Authors:
Hans J. Koch <hjk@linutronix.de>
John Morris <john.morris@spirentcom.com>
Claus Gindhart <claus.gindhart@kontron.com>
Description
-----------
This driver implements support for the Maxim 6650/6651
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 driver is not able to distinguish between the 2 devices.
The driver provides the following sensor accesses in sysfs:
fan1_input ro fan tachometer speed in RPM
fan2_input ro "
fan3_input ro "
fan4_input ro "
fan1_target rw desired fan speed in RPM (closed loop mode only)
pwm1_enable rw regulator mode, 0=full on, 1=open loop, 2=closed loop
pwm1 rw relative speed (0-255), 255=max. speed.
Used in open loop mode only.
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.
Module parameters
-----------------
If your board has a BIOS that initializes the MAX6650/6651 correctly, you can
simply load your module without parameters. It won't touch the configuration
registers then. If your board BIOS doesn't initialize the chip, or you want
different settings, you can set the following parameters:
voltage_12V: 5=5V fan, 12=12V fan, 0=don't change
prescaler: Possible values are 1,2,4,8,16, or 0 for don't change
clock: The clock frequency in Hz of the chip the driver should assume [254000]
Please have a look at the MAX6650/6651 data sheet and make sure that you fully
understand the meaning of these parameters before you attempt to change them.
...@@ -14,6 +14,10 @@ Supported chips: ...@@ -14,6 +14,10 @@ Supported chips:
http://www.smsc.com/main/datasheets/47m14x.pdf http://www.smsc.com/main/datasheets/47m14x.pdf
http://www.smsc.com/main/tools/discontinued/47m15x.pdf http://www.smsc.com/main/tools/discontinued/47m15x.pdf
http://www.smsc.com/main/datasheets/47m192.pdf http://www.smsc.com/main/datasheets/47m192.pdf
* SMSC LPC47M292
Addresses scanned: none, address read from Super I/O config space
Prefix: 'smsc47m2'
Datasheet: Not public
* SMSC LPC47M997 * SMSC LPC47M997
Addresses scanned: none, address read from Super I/O config space Addresses scanned: none, address read from Super I/O config space
Prefix: 'smsc47m1' Prefix: 'smsc47m1'
...@@ -32,9 +36,10 @@ Description ...@@ -32,9 +36,10 @@ Description
The Standard Microsystems Corporation (SMSC) 47M1xx Super I/O chips The Standard Microsystems Corporation (SMSC) 47M1xx Super I/O chips
contain monitoring and PWM control circuitry for two fans. contain monitoring and PWM control circuitry for two fans.
The 47M15x and 47M192 chips contain a full 'hardware monitoring block' The LPC47M15x, LPC47M192 and LPC47M292 chips contain a full 'hardware
in addition to the fan monitoring and control. The hardware monitoring monitoring block' in addition to the fan monitoring and control. The
block is not supported by the driver. hardware monitoring block is not supported by this driver, use the
smsc47m192 driver for that.
No documentation is available for the 47M997, but it has the same device No documentation is available for the 47M997, but it has the same device
ID as the 47M15x and 47M192 chips and seems to be compatible. ID as the 47M15x and 47M192 chips and seems to be compatible.
......
...@@ -2,12 +2,13 @@ Kernel driver smsc47m192 ...@@ -2,12 +2,13 @@ Kernel driver smsc47m192
======================== ========================
Supported chips: Supported chips:
* SMSC LPC47M192 and LPC47M997 * SMSC LPC47M192, LPC47M15x, LPC47M292 and LPC47M997
Prefix: 'smsc47m192' Prefix: 'smsc47m192'
Addresses scanned: I2C 0x2c - 0x2d Addresses scanned: I2C 0x2c - 0x2d
Datasheet: The datasheet for LPC47M192 is publicly available from Datasheet: The datasheet for LPC47M192 is publicly available from
http://www.smsc.com/ http://www.smsc.com/
The LPC47M997 is compatible for hardware monitoring. The LPC47M15x, LPC47M292 and LPC47M997 are compatible for
hardware monitoring.
Author: Hartmut Rick <linux@rick.claranet.de> Author: Hartmut Rick <linux@rick.claranet.de>
Special thanks to Jean Delvare for careful checking Special thanks to Jean Delvare for careful checking
...@@ -18,7 +19,7 @@ Description ...@@ -18,7 +19,7 @@ Description
----------- -----------
This driver implements support for the hardware sensor capabilities This driver implements support for the hardware sensor capabilities
of the SMSC LPC47M192 and LPC47M997 Super-I/O chips. of the SMSC LPC47M192 and compatible Super-I/O chips.
These chips support 3 temperature channels and 8 voltage inputs These chips support 3 temperature channels and 8 voltage inputs
as well as CPU voltage VID input. as well as CPU voltage VID input.
......
...@@ -152,6 +152,13 @@ fan[1-*]_div Fan divisor. ...@@ -152,6 +152,13 @@ fan[1-*]_div Fan divisor.
Note that this is actually an internal clock divisor, which Note that this is actually an internal clock divisor, which
affects the measurable speed range, not the read value. affects the measurable speed range, not the read value.
fan[1-*]_target
Desired fan speed
Unit: revolution/min (RPM)
RW
Only makes sense if the chip supports closed-loop fan speed
control based on the measured fan speed.
Also see the Alarms section for status flags associated with fans. Also see the Alarms section for status flags associated with fans.
......
...@@ -1040,6 +1040,12 @@ P: Simon Arlott ...@@ -1040,6 +1040,12 @@ P: Simon Arlott
M: cxacru@fire.lp0.eu M: cxacru@fire.lp0.eu
S: Maintained S: Maintained
CORETEMP HARDWARE MONITORING DRIVER
P: Rudolf Marek
M: r.marek@assembler.cz
L: lm-sensors@lm-sensors.org
S: Maintained
COSA/SRP SYNC SERIAL DRIVER COSA/SRP SYNC SERIAL DRIVER
P: Jan "Yenya" Kasprzak P: Jan "Yenya" Kasprzak
M: kas@fi.muni.cz M: kas@fi.muni.cz
...@@ -2314,6 +2320,12 @@ M: vandrove@vc.cvut.cz ...@@ -2314,6 +2320,12 @@ M: vandrove@vc.cvut.cz
L: linux-fbdev-devel@lists.sourceforge.net (subscribers-only) L: linux-fbdev-devel@lists.sourceforge.net (subscribers-only)
S: Maintained S: Maintained
MAX6650 HARDWARE MONITOR AND FAN CONTROLLER DRIVER
P: Hans J. Koch
M: hjk@linutronix.de
L: lm-sensors@lm-sensors.org
S: Maintained
MEGARAID SCSI DRIVERS MEGARAID SCSI DRIVERS
P: Neela Syam Kolli P: Neela Syam Kolli
M: Neela.Kolli@engenio.com M: Neela.Kolli@engenio.com
......
...@@ -45,104 +45,6 @@ ...@@ -45,104 +45,6 @@
static struct class *msr_class; static struct class *msr_class;
static inline int wrmsr_eio(u32 reg, u32 eax, u32 edx)
{
int err;
err = wrmsr_safe(reg, eax, edx);
if (err)
err = -EIO;
return err;
}
static inline int rdmsr_eio(u32 reg, u32 *eax, u32 *edx)
{
int err;
err = rdmsr_safe(reg, eax, edx);
if (err)
err = -EIO;
return err;
}
#ifdef CONFIG_SMP
struct msr_command {
int err;
u32 reg;
u32 data[2];
};
static void msr_smp_wrmsr(void *cmd_block)
{
struct msr_command *cmd = (struct msr_command *)cmd_block;
cmd->err = wrmsr_eio(cmd->reg, cmd->data[0], cmd->data[1]);
}
static void msr_smp_rdmsr(void *cmd_block)
{
struct msr_command *cmd = (struct msr_command *)cmd_block;
cmd->err = rdmsr_eio(cmd->reg, &cmd->data[0], &cmd->data[1]);
}
static inline int do_wrmsr(int cpu, u32 reg, u32 eax, u32 edx)
{
struct msr_command cmd;
int ret;
preempt_disable();
if (cpu == smp_processor_id()) {
ret = wrmsr_eio(reg, eax, edx);
} else {
cmd.reg = reg;
cmd.data[0] = eax;
cmd.data[1] = edx;
smp_call_function_single(cpu, msr_smp_wrmsr, &cmd, 1, 1);
ret = cmd.err;
}
preempt_enable();
return ret;
}
static inline int do_rdmsr(int cpu, u32 reg, u32 * eax, u32 * edx)
{
struct msr_command cmd;
int ret;
preempt_disable();
if (cpu == smp_processor_id()) {
ret = rdmsr_eio(reg, eax, edx);
} else {
cmd.reg = reg;
smp_call_function_single(cpu, msr_smp_rdmsr, &cmd, 1, 1);
*eax = cmd.data[0];
*edx = cmd.data[1];
ret = cmd.err;
}
preempt_enable();
return ret;
}
#else /* ! CONFIG_SMP */
static inline int do_wrmsr(int cpu, u32 reg, u32 eax, u32 edx)
{
return wrmsr_eio(reg, eax, edx);
}
static inline int do_rdmsr(int cpu, u32 reg, u32 *eax, u32 *edx)
{
return rdmsr_eio(reg, eax, edx);
}
#endif /* ! CONFIG_SMP */
static loff_t msr_seek(struct file *file, loff_t offset, int orig) static loff_t msr_seek(struct file *file, loff_t offset, int orig)
{ {
loff_t ret = -EINVAL; loff_t ret = -EINVAL;
...@@ -174,9 +76,9 @@ static ssize_t msr_read(struct file *file, char __user * buf, ...@@ -174,9 +76,9 @@ static ssize_t msr_read(struct file *file, char __user * buf,
return -EINVAL; /* Invalid chunk size */ return -EINVAL; /* Invalid chunk size */
for (; count; count -= 8) { for (; count; count -= 8) {
err = do_rdmsr(cpu, reg, &data[0], &data[1]); err = rdmsr_safe_on_cpu(cpu, reg, &data[0], &data[1]);
if (err) if (err)
return err; return -EIO;
if (copy_to_user(tmp, &data, 8)) if (copy_to_user(tmp, &data, 8))
return -EFAULT; return -EFAULT;
tmp += 2; tmp += 2;
...@@ -200,9 +102,9 @@ static ssize_t msr_write(struct file *file, const char __user *buf, ...@@ -200,9 +102,9 @@ static ssize_t msr_write(struct file *file, const char __user *buf,
for (; count; count -= 8) { for (; count; count -= 8) {
if (copy_from_user(&data, tmp, 8)) if (copy_from_user(&data, tmp, 8))
return -EFAULT; return -EFAULT;
err = do_wrmsr(cpu, reg, data[0], data[1]); err = wrmsr_safe_on_cpu(cpu, reg, data[0], data[1]);
if (err) if (err)
return err; return -EIO;
tmp += 2; tmp += 2;
} }
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
struct msr_info { struct msr_info {
u32 msr_no; u32 msr_no;
u32 l, h; u32 l, h;
int err;
}; };
static void __rdmsr_on_cpu(void *info) static void __rdmsr_on_cpu(void *info)
...@@ -15,20 +16,38 @@ static void __rdmsr_on_cpu(void *info) ...@@ -15,20 +16,38 @@ static void __rdmsr_on_cpu(void *info)
rdmsr(rv->msr_no, rv->l, rv->h); rdmsr(rv->msr_no, rv->l, rv->h);
} }
void rdmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h) static void __rdmsr_safe_on_cpu(void *info)
{ {
struct msr_info *rv = info;
rv->err = rdmsr_safe(rv->msr_no, &rv->l, &rv->h);
}
static int _rdmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h, int safe)
{
int err = 0;
preempt_disable(); preempt_disable();
if (smp_processor_id() == cpu) if (smp_processor_id() == cpu)
rdmsr(msr_no, *l, *h); if (safe)
err = rdmsr_safe(msr_no, l, h);
else
rdmsr(msr_no, *l, *h);
else { else {
struct msr_info rv; struct msr_info rv;
rv.msr_no = msr_no; rv.msr_no = msr_no;
smp_call_function_single(cpu, __rdmsr_on_cpu, &rv, 0, 1); if (safe) {
smp_call_function_single(cpu, __rdmsr_safe_on_cpu,
&rv, 0, 1);
err = rv.err;
} else {
smp_call_function_single(cpu, __rdmsr_on_cpu, &rv, 0, 1);
}
*l = rv.l; *l = rv.l;
*h = rv.h; *h = rv.h;
} }
preempt_enable(); preempt_enable();
return err;
} }
static void __wrmsr_on_cpu(void *info) static void __wrmsr_on_cpu(void *info)
...@@ -38,21 +57,63 @@ static void __wrmsr_on_cpu(void *info) ...@@ -38,21 +57,63 @@ static void __wrmsr_on_cpu(void *info)
wrmsr(rv->msr_no, rv->l, rv->h); wrmsr(rv->msr_no, rv->l, rv->h);
} }
void wrmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h) static void __wrmsr_safe_on_cpu(void *info)
{ {
struct msr_info *rv = info;
rv->err = wrmsr_safe(rv->msr_no, rv->l, rv->h);
}
static int _wrmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h, int safe)
{
int err = 0;
preempt_disable(); preempt_disable();
if (smp_processor_id() == cpu) if (smp_processor_id() == cpu)
wrmsr(msr_no, l, h); if (safe)
err = wrmsr_safe(msr_no, l, h);
else
wrmsr(msr_no, l, h);
else { else {
struct msr_info rv; struct msr_info rv;
rv.msr_no = msr_no; rv.msr_no = msr_no;
rv.l = l; rv.l = l;
rv.h = h; rv.h = h;
smp_call_function_single(cpu, __wrmsr_on_cpu, &rv, 0, 1); if (safe) {
smp_call_function_single(cpu, __wrmsr_safe_on_cpu,
&rv, 0, 1);
err = rv.err;
} else {
smp_call_function_single(cpu, __wrmsr_on_cpu, &rv, 0, 1);
}
} }
preempt_enable(); preempt_enable();
return err;
}
void wrmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h)
{
_wrmsr_on_cpu(cpu, msr_no, l, h, 0);
}
void rdmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h)
{
_rdmsr_on_cpu(cpu, msr_no, l, h, 0);
}
/* These "safe" variants are slower and should be used when the target MSR
may not actually exist. */
int wrmsr_safe_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h)
{
return _wrmsr_on_cpu(cpu, msr_no, l, h, 1);
}
int rdmsr_safe_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h)
{
return _rdmsr_on_cpu(cpu, msr_no, l, h, 1);
} }
EXPORT_SYMBOL(rdmsr_on_cpu); EXPORT_SYMBOL(rdmsr_on_cpu);
EXPORT_SYMBOL(wrmsr_on_cpu); EXPORT_SYMBOL(wrmsr_on_cpu);
EXPORT_SYMBOL(rdmsr_safe_on_cpu);
EXPORT_SYMBOL(wrmsr_safe_on_cpu);
This diff is collapsed.
...@@ -14,6 +14,7 @@ obj-$(CONFIG_SENSORS_W83781D) += w83781d.o ...@@ -14,6 +14,7 @@ obj-$(CONFIG_SENSORS_W83781D) += w83781d.o
obj-$(CONFIG_SENSORS_W83791D) += w83791d.o obj-$(CONFIG_SENSORS_W83791D) += w83791d.o
obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o
obj-$(CONFIG_SENSORS_AD7418) += ad7418.o
obj-$(CONFIG_SENSORS_ADM1021) += adm1021.o obj-$(CONFIG_SENSORS_ADM1021) += adm1021.o
obj-$(CONFIG_SENSORS_ADM1025) += adm1025.o obj-$(CONFIG_SENSORS_ADM1025) += adm1025.o
obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o
...@@ -23,6 +24,7 @@ obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o ...@@ -23,6 +24,7 @@ obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o
obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o
obj-$(CONFIG_SENSORS_AMS) += ams/ obj-$(CONFIG_SENSORS_AMS) += ams/
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
obj-$(CONFIG_SENSORS_DS1621) += ds1621.o obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
obj-$(CONFIG_SENSORS_F71805F) += f71805f.o obj-$(CONFIG_SENSORS_F71805F) += f71805f.o
obj-$(CONFIG_SENSORS_FSCHER) += fscher.o obj-$(CONFIG_SENSORS_FSCHER) += fscher.o
...@@ -44,6 +46,7 @@ obj-$(CONFIG_SENSORS_LM87) += lm87.o ...@@ -44,6 +46,7 @@ obj-$(CONFIG_SENSORS_LM87) += lm87.o
obj-$(CONFIG_SENSORS_LM90) += lm90.o obj-$(CONFIG_SENSORS_LM90) += lm90.o
obj-$(CONFIG_SENSORS_LM92) += lm92.o obj-$(CONFIG_SENSORS_LM92) += lm92.o
obj-$(CONFIG_SENSORS_MAX1619) += max1619.o obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
obj-$(CONFIG_SENSORS_MAX6650) += max6650.o
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
obj-$(CONFIG_SENSORS_PC87427) += pc87427.o obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
......
This diff is collapsed.
...@@ -219,9 +219,6 @@ int __init ams_init(void) ...@@ -219,9 +219,6 @@ int __init ams_init(void)
/* Found PMU motion sensor */ /* Found PMU motion sensor */
return ams_pmu_init(np); return ams_pmu_init(np);
#endif #endif
printk(KERN_ERR "ams: No motion sensor found.\n");
return -ENODEV; return -ENODEV;
} }
......
...@@ -85,17 +85,17 @@ static int ams_i2c_write(u8 reg, u8 value) ...@@ -85,17 +85,17 @@ static int ams_i2c_write(u8 reg, u8 value)
static int ams_i2c_cmd(enum ams_i2c_cmd cmd) static int ams_i2c_cmd(enum ams_i2c_cmd cmd)
{ {
s32 result; s32 result;
int remaining = HZ / 20; int count = 3;
ams_i2c_write(AMS_COMMAND, cmd); ams_i2c_write(AMS_COMMAND, cmd);
mdelay(5); msleep(5);
while (remaining) { while (count--) {
result = ams_i2c_read(AMS_COMMAND); result = ams_i2c_read(AMS_COMMAND);
if (result == 0 || result & 0x80) if (result == 0 || result & 0x80)
return 0; return 0;
remaining = schedule_timeout(remaining); schedule_timeout_uninterruptible(HZ / 20);
} }
return -1; return -1;
......
This diff is collapsed.
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
#include <linux/err.h> #include <linux/err.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/sysfs.h> #include <linux/sysfs.h>
#include <linux/ioport.h>
#include <asm/io.h> #include <asm/io.h>
static struct platform_device *pdev; static struct platform_device *pdev;
...@@ -1140,6 +1141,13 @@ static int __devinit f71805f_probe(struct platform_device *pdev) ...@@ -1140,6 +1141,13 @@ static int __devinit f71805f_probe(struct platform_device *pdev)
} }
res = platform_get_resource(pdev, IORESOURCE_IO, 0); res = platform_get_resource(pdev, IORESOURCE_IO, 0);
if (!request_region(res->start + ADDR_REG_OFFSET, 2, DRVNAME)) {
err = -EBUSY;
dev_err(&pdev->dev, "Failed to request region 0x%lx-0x%lx\n",
(unsigned long)(res->start + ADDR_REG_OFFSET),
(unsigned long)(res->start + ADDR_REG_OFFSET + 1));
goto exit_free;
}
data->addr = res->start; data->addr = res->start;
data->name = names[sio_data->kind]; data->name = names[sio_data->kind];
mutex_init(&data->update_lock); mutex_init(&data->update_lock);
...@@ -1165,7 +1173,7 @@ static int __devinit f71805f_probe(struct platform_device *pdev) ...@@ -1165,7 +1173,7 @@ static int __devinit f71805f_probe(struct platform_device *pdev)
/* Register sysfs interface files */ /* Register sysfs interface files */
if ((err = sysfs_create_group(&pdev->dev.kobj, &f71805f_group))) if ((err = sysfs_create_group(&pdev->dev.kobj, &f71805f_group)))
goto exit_free; goto exit_release_region;
if (data->has_in & (1 << 4)) { /* in4 */ if (data->has_in & (1 << 4)) { /* in4 */
if ((err = sysfs_create_group(&pdev->dev.kobj, if ((err = sysfs_create_group(&pdev->dev.kobj,
&f71805f_group_optin[0]))) &f71805f_group_optin[0])))
...@@ -1219,6 +1227,8 @@ static int __devinit f71805f_probe(struct platform_device *pdev) ...@@ -1219,6 +1227,8 @@ static int __devinit f71805f_probe(struct platform_device *pdev)
for (i = 0; i < 4; i++) for (i = 0; i < 4; i++)
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_optin[i]); sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_optin[i]);
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq); sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq);
exit_release_region:
release_region(res->start + ADDR_REG_OFFSET, 2);
exit_free: exit_free:
platform_set_drvdata(pdev, NULL); platform_set_drvdata(pdev, NULL);
kfree(data); kfree(data);
...@@ -1229,6 +1239,7 @@ static int __devinit f71805f_probe(struct platform_device *pdev) ...@@ -1229,6 +1239,7 @@ static int __devinit f71805f_probe(struct platform_device *pdev)
static int __devexit f71805f_remove(struct platform_device *pdev) static int __devexit f71805f_remove(struct platform_device *pdev)
{ {
struct f71805f_data *data = platform_get_drvdata(pdev); struct f71805f_data *data = platform_get_drvdata(pdev);
struct resource *res;
int i; int i;
platform_set_drvdata(pdev, NULL); platform_set_drvdata(pdev, NULL);
...@@ -1239,6 +1250,9 @@ static int __devexit f71805f_remove(struct platform_device *pdev) ...@@ -1239,6 +1250,9 @@ static int __devexit f71805f_remove(struct platform_device *pdev)
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq); sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq);
kfree(data); kfree(data);
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
release_region(res->start + ADDR_REG_OFFSET, 2);
return 0; return 0;
} }
......
...@@ -166,16 +166,16 @@ static struct vrm_model vrm_models[] = { ...@@ -166,16 +166,16 @@ static struct vrm_model vrm_models[] = {
{X86_VENDOR_INTEL, 0x6, 0xE, ANY, 14}, /* Intel Core (65 nm) */ {X86_VENDOR_INTEL, 0x6, 0xE, ANY, 14}, /* Intel Core (65 nm) */
{X86_VENDOR_INTEL, 0x6, 0xF, ANY, 110}, /* Intel Conroe */ {X86_VENDOR_INTEL, 0x6, 0xF, ANY, 110}, /* Intel Conroe */
{X86_VENDOR_INTEL, 0x6, ANY, ANY, 82}, /* any P6 */ {X86_VENDOR_INTEL, 0x6, ANY, ANY, 82}, /* any P6 */
{X86_VENDOR_INTEL, 0x7, ANY, ANY, 0}, /* Itanium */
{X86_VENDOR_INTEL, 0xF, 0x0, ANY, 90}, /* P4 */ {X86_VENDOR_INTEL, 0xF, 0x0, ANY, 90}, /* P4 */
{X86_VENDOR_INTEL, 0xF, 0x1, ANY, 90}, /* P4 Willamette */ {X86_VENDOR_INTEL, 0xF, 0x1, ANY, 90}, /* P4 Willamette */
{X86_VENDOR_INTEL, 0xF, 0x2, ANY, 90}, /* P4 Northwood */ {X86_VENDOR_INTEL, 0xF, 0x2, ANY, 90}, /* P4 Northwood */
{X86_VENDOR_INTEL, 0xF, ANY, ANY, 100}, /* Prescott and above assume VRD 10 */ {X86_VENDOR_INTEL, 0xF, ANY, ANY, 100}, /* Prescott and above assume VRD 10 */
{X86_VENDOR_INTEL, 0x10, ANY, ANY, 0}, /* Itanium 2 */
{X86_VENDOR_CENTAUR, 0x6, 0x7, ANY, 85}, /* Eden ESP/Ezra */ {X86_VENDOR_CENTAUR, 0x6, 0x7, ANY, 85}, /* Eden ESP/Ezra */
{X86_VENDOR_CENTAUR, 0x6, 0x8, 0x7, 85}, /* Ezra T */ {X86_VENDOR_CENTAUR, 0x6, 0x8, 0x7, 85}, /* Ezra T */
{X86_VENDOR_CENTAUR, 0x6, 0x9, 0x7, 85}, /* Nemiah */ {X86_VENDOR_CENTAUR, 0x6, 0x9, 0x7, 85}, /* Nemiah */
{X86_VENDOR_CENTAUR, 0x6, 0x9, ANY, 17}, /* C3-M */ {X86_VENDOR_CENTAUR, 0x6, 0x9, ANY, 17}, /* C3-M, Eden-N */
{X86_VENDOR_CENTAUR, 0x6, 0xA, 0x7, 0}, /* No information */
{X86_VENDOR_CENTAUR, 0x6, 0xA, ANY, 13}, /* C7, Esther */
{X86_VENDOR_UNKNOWN, ANY, ANY, ANY, 0} /* stop here */ {X86_VENDOR_UNKNOWN, ANY, ANY, ANY, 0} /* stop here */
}; };
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include <linux/jiffies.h> #include <linux/jiffies.h>
#include <linux/i2c.h> #include <linux/i2c.h>
#include <linux/hwmon.h> #include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include "lm75.h" #include "lm75.h"
...@@ -39,10 +40,12 @@ I2C_CLIENT_INSMOD_1(lm75); ...@@ -39,10 +40,12 @@ I2C_CLIENT_INSMOD_1(lm75);
/* Many LM75 constants specified below */ /* Many LM75 constants specified below */
/* The LM75 registers */ /* The LM75 registers */
#define LM75_REG_TEMP 0x00
#define LM75_REG_CONF 0x01 #define LM75_REG_CONF 0x01
#define LM75_REG_TEMP_HYST 0x02 static const u8 LM75_REG_TEMP[3] = {
#define LM75_REG_TEMP_OS 0x03 0x00, /* input */
0x03, /* max */
0x02, /* hyst */
};
/* Each client has this additional data */ /* Each client has this additional data */
struct lm75_data { struct lm75_data {
...@@ -51,9 +54,10 @@ struct lm75_data { ...@@ -51,9 +54,10 @@ struct lm75_data {
struct mutex update_lock; struct mutex update_lock;
char valid; /* !=0 if following fields are valid */ char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */ unsigned long last_updated; /* In jiffies */
u16 temp_input; /* Register values */ u16 temp[3]; /* Register values,
u16 temp_max; 0 = input
u16 temp_hyst; 1 = max
2 = hyst */
}; };
static int lm75_attach_adapter(struct i2c_adapter *adapter); static int lm75_attach_adapter(struct i2c_adapter *adapter);
...@@ -75,35 +79,36 @@ static struct i2c_driver lm75_driver = { ...@@ -75,35 +79,36 @@ static struct i2c_driver lm75_driver = {
.detach_client = lm75_detach_client, .detach_client = lm75_detach_client,
}; };
#define show(value) \ static ssize_t show_temp(struct device *dev, struct device_attribute *da,
static ssize_t show_##value(struct device *dev, struct device_attribute *attr, char *buf) \ char *buf)
{ \ {
struct lm75_data *data = lm75_update_device(dev); \ struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
return sprintf(buf, "%d\n", LM75_TEMP_FROM_REG(data->value)); \ struct lm75_data *data = lm75_update_device(dev);
return sprintf(buf, "%d\n",
LM75_TEMP_FROM_REG(data->temp[attr->index]));
} }
show(temp_max);
show(temp_hyst); static ssize_t set_temp(struct device *dev, struct device_attribute *da,
show(temp_input); const char *buf, size_t count)
{
#define set(value, reg) \ struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
static ssize_t set_##value(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) \ struct i2c_client *client = to_i2c_client(dev);
{ \ struct lm75_data *data = i2c_get_clientdata(client);
struct i2c_client *client = to_i2c_client(dev); \ int nr = attr->index;
struct lm75_data *data = i2c_get_clientdata(client); \ unsigned long temp = simple_strtoul(buf, NULL, 10);
int temp = simple_strtoul(buf, NULL, 10); \
\ mutex_lock(&data->update_lock);
mutex_lock(&data->update_lock); \ data->temp[nr] = LM75_TEMP_TO_REG(temp);
data->value = LM75_TEMP_TO_REG(temp); \ lm75_write_value(client, LM75_REG_TEMP[nr], data->temp[nr]);
lm75_write_value(client, reg, data->value); \ mutex_unlock(&data->update_lock);
mutex_unlock(&data->update_lock); \ return count;
return count; \
} }
set(temp_max, LM75_REG_TEMP_OS);
set(temp_hyst, LM75_REG_TEMP_HYST);
static DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max); static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO,
static DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, show_temp_hyst, set_temp_hyst); show_temp, set_temp, 1);
static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input, NULL); static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO,
show_temp, set_temp, 2);
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
static int lm75_attach_adapter(struct i2c_adapter *adapter) static int lm75_attach_adapter(struct i2c_adapter *adapter)
{ {
...@@ -113,9 +118,9 @@ static int lm75_attach_adapter(struct i2c_adapter *adapter) ...@@ -113,9 +118,9 @@ static int lm75_attach_adapter(struct i2c_adapter *adapter)
} }
static struct attribute *lm75_attributes[] = { static struct attribute *lm75_attributes[] = {
&dev_attr_temp1_input.attr, &sensor_dev_attr_temp1_input.dev_attr.attr,
&dev_attr_temp1_max.attr, &sensor_dev_attr_temp1_max.dev_attr.attr,
&dev_attr_temp1_max_hyst.attr, &sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
NULL NULL
}; };
...@@ -283,11 +288,12 @@ static struct lm75_data *lm75_update_device(struct device *dev) ...@@ -283,11 +288,12 @@ static struct lm75_data *lm75_update_device(struct device *dev)
if (time_after(jiffies, data->last_updated + HZ + HZ / 2) if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
|| !data->valid) { || !data->valid) {
int i;
dev_dbg(&client->dev, "Starting lm75 update\n"); dev_dbg(&client->dev, "Starting lm75 update\n");
data->temp_input = lm75_read_value(client, LM75_REG_TEMP); for (i = 0; i < ARRAY_SIZE(data->temp); i++)
data->temp_max = lm75_read_value(client, LM75_REG_TEMP_OS); data->temp[i] = lm75_read_value(client,
data->temp_hyst = lm75_read_value(client, LM75_REG_TEMP_HYST); LM75_REG_TEMP[i]);
data->last_updated = jiffies; data->last_updated = jiffies;
data->valid = 1; data->valid = 1;
} }
......
This diff is collapsed.
...@@ -747,6 +747,7 @@ static int lm87_detect(struct i2c_adapter *adapter, int address, int kind) ...@@ -747,6 +747,7 @@ static int lm87_detect(struct i2c_adapter *adapter, int address, int kind)
} }
if (!(data->channel & CHAN_NO_VID)) { if (!(data->channel & CHAN_NO_VID)) {
data->vrm = vid_which_vrm();
if ((err = device_create_file(&new_client->dev, if ((err = device_create_file(&new_client->dev,
&dev_attr_cpu0_vid)) &dev_attr_cpu0_vid))
|| (err = device_create_file(&new_client->dev, || (err = device_create_file(&new_client->dev,
...@@ -779,7 +780,6 @@ static void lm87_init_client(struct i2c_client *client) ...@@ -779,7 +780,6 @@ static void lm87_init_client(struct i2c_client *client)
u8 config; u8 config;
data->channel = lm87_read_value(client, LM87_REG_CHANNEL_MODE); data->channel = lm87_read_value(client, LM87_REG_CHANNEL_MODE);
data->vrm = vid_which_vrm();
config = lm87_read_value(client, LM87_REG_CONFIG); config = lm87_read_value(client, LM87_REG_CONFIG);
if (!(config & 0x01)) { if (!(config & 0x01)) {
......
This diff is collapsed.
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#include <linux/err.h> #include <linux/err.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/sysfs.h> #include <linux/sysfs.h>
#include <linux/ioport.h>
#include <asm/io.h> #include <asm/io.h>
static struct platform_device *pdev; static struct platform_device *pdev;
...@@ -429,6 +430,12 @@ static int __devinit pc87427_probe(struct platform_device *pdev) ...@@ -429,6 +430,12 @@ static int __devinit pc87427_probe(struct platform_device *pdev)
/* This will need to be revisited when we add support for /* This will need to be revisited when we add support for
temperature and voltage monitoring. */ temperature and voltage monitoring. */
res = platform_get_resource(pdev, IORESOURCE_IO, 0); res = platform_get_resource(pdev, IORESOURCE_IO, 0);
if (!request_region(res->start, res->end - res->start + 1, DRVNAME)) {
err = -EBUSY;
dev_err(&pdev->dev, "Failed to request region 0x%lx-0x%lx\n",
(unsigned long)res->start, (unsigned long)res->end);
goto exit_kfree;
}
data->address[0] = res->start; data->address[0] = res->start;
mutex_init(&data->lock); mutex_init(&data->lock);
...@@ -438,7 +445,7 @@ static int __devinit pc87427_probe(struct platform_device *pdev) ...@@ -438,7 +445,7 @@ static int __devinit pc87427_probe(struct platform_device *pdev)
/* Register sysfs hooks */ /* Register sysfs hooks */
if ((err = device_create_file(&pdev->dev, &dev_attr_name))) if ((err = device_create_file(&pdev->dev, &dev_attr_name)))
goto exit_kfree; goto exit_release_region;
for (i = 0; i < 8; i++) { for (i = 0; i < 8; i++) {
if (!(data->fan_enabled & (1 << i))) if (!(data->fan_enabled & (1 << i)))
continue; continue;
...@@ -462,6 +469,8 @@ static int __devinit pc87427_probe(struct platform_device *pdev) ...@@ -462,6 +469,8 @@ static int __devinit pc87427_probe(struct platform_device *pdev)
continue; continue;
sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]); sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]);
} }
exit_release_region:
release_region(res->start, res->end - res->start + 1);
exit_kfree: exit_kfree:
platform_set_drvdata(pdev, NULL); platform_set_drvdata(pdev, NULL);
kfree(data); kfree(data);
...@@ -472,6 +481,7 @@ static int __devinit pc87427_probe(struct platform_device *pdev) ...@@ -472,6 +481,7 @@ static int __devinit pc87427_probe(struct platform_device *pdev)
static int __devexit pc87427_remove(struct platform_device *pdev) static int __devexit pc87427_remove(struct platform_device *pdev)
{ {
struct pc87427_data *data = platform_get_drvdata(pdev); struct pc87427_data *data = platform_get_drvdata(pdev);
struct resource *res;
int i; int i;
platform_set_drvdata(pdev, NULL); platform_set_drvdata(pdev, NULL);
...@@ -484,6 +494,9 @@ static int __devexit pc87427_remove(struct platform_device *pdev) ...@@ -484,6 +494,9 @@ static int __devexit pc87427_remove(struct platform_device *pdev)
} }
kfree(data); kfree(data);
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
release_region(res->start, res->end - res->start + 1);
return 0; return 0;
} }
......
This diff is collapsed.
This diff is collapsed.
/* /*
smsc47m192.c - Support for hardware monitoring block of smsc47m192.c - Support for hardware monitoring block of
SMSC LPC47M192 and LPC47M997 Super I/O chips SMSC LPC47M192 and compatible Super I/O chips
Copyright (C) 2006 Hartmut Rick <linux@rick.claranet.de> Copyright (C) 2006 Hartmut Rick <linux@rick.claranet.de>
...@@ -518,7 +518,7 @@ static int smsc47m192_detect(struct i2c_adapter *adapter, int address, ...@@ -518,7 +518,7 @@ static int smsc47m192_detect(struct i2c_adapter *adapter, int address,
&& (i2c_smbus_read_byte_data(client, && (i2c_smbus_read_byte_data(client,
SMSC47M192_REG_VID4) & 0xfe) == 0x80) { SMSC47M192_REG_VID4) & 0xfe) == 0x80) {
dev_info(&adapter->dev, dev_info(&adapter->dev,
"found SMSC47M192 or SMSC47M997, " "found SMSC47M192 or compatible, "
"version 2, stepping A%d\n", version & 0x0f); "version 2, stepping A%d\n", version & 0x0f);
} else { } else {
dev_dbg(&adapter->dev, dev_dbg(&adapter->dev,
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#include <linux/hwmon-vid.h> #include <linux/hwmon-vid.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/ioport.h>
#include <asm/io.h> #include <asm/io.h>
static int uch_config = -1; static int uch_config = -1;
...@@ -1130,6 +1131,12 @@ static int __devinit vt1211_probe(struct platform_device *pdev) ...@@ -1130,6 +1131,12 @@ static int __devinit vt1211_probe(struct platform_device *pdev)
} }
res = platform_get_resource(pdev, IORESOURCE_IO, 0); res = platform_get_resource(pdev, IORESOURCE_IO, 0);
if (!request_region(res->start, res->end - res->start + 1, DRVNAME)) {
err = -EBUSY;
dev_err(dev, "Failed to request region 0x%lx-0x%lx\n",
(unsigned long)res->start, (unsigned long)res->end);
goto EXIT_KFREE;
}
data->addr = res->start; data->addr = res->start;
data->name = DRVNAME; data->name = DRVNAME;
mutex_init(&data->update_lock); mutex_init(&data->update_lock);
...@@ -1197,6 +1204,8 @@ static int __devinit vt1211_probe(struct platform_device *pdev) ...@@ -1197,6 +1204,8 @@ static int __devinit vt1211_probe(struct platform_device *pdev)
dev_err(dev, "Sysfs interface creation failed (%d)\n", err); dev_err(dev, "Sysfs interface creation failed (%d)\n", err);
EXIT_DEV_REMOVE_SILENT: EXIT_DEV_REMOVE_SILENT:
vt1211_remove_sysfs(pdev); vt1211_remove_sysfs(pdev);
release_region(res->start, res->end - res->start + 1);
EXIT_KFREE:
platform_set_drvdata(pdev, NULL); platform_set_drvdata(pdev, NULL);
kfree(data); kfree(data);
EXIT: EXIT:
...@@ -1206,12 +1215,16 @@ static int __devinit vt1211_probe(struct platform_device *pdev) ...@@ -1206,12 +1215,16 @@ static int __devinit vt1211_probe(struct platform_device *pdev)
static int __devexit vt1211_remove(struct platform_device *pdev) static int __devexit vt1211_remove(struct platform_device *pdev)
{ {
struct vt1211_data *data = platform_get_drvdata(pdev); struct vt1211_data *data = platform_get_drvdata(pdev);
struct resource *res;
hwmon_device_unregister(data->class_dev); hwmon_device_unregister(data->class_dev);
vt1211_remove_sysfs(pdev); vt1211_remove_sysfs(pdev);
platform_set_drvdata(pdev, NULL); platform_set_drvdata(pdev, NULL);
kfree(data); kfree(data);
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
release_region(res->start, res->end - res->start + 1);
return 0; return 0;
} }
......
This diff is collapsed.
This diff is collapsed.
...@@ -77,7 +77,7 @@ static inline unsigned long long native_read_pmc(void) ...@@ -77,7 +77,7 @@ static inline unsigned long long native_read_pmc(void)
#ifdef CONFIG_PARAVIRT #ifdef CONFIG_PARAVIRT
#include <asm/paravirt.h> #include <asm/paravirt.h>
#else #else
#include <linux/errno.h>
/* /*
* Access to machine-specific registers (available on 586 and better only) * Access to machine-specific registers (available on 586 and better only)
* Note: the rd* operations modify the parameters directly (without using * Note: the rd* operations modify the parameters directly (without using
...@@ -148,6 +148,8 @@ static inline void wrmsrl (unsigned long msr, unsigned long long val) ...@@ -148,6 +148,8 @@ static inline void wrmsrl (unsigned long msr, unsigned long long val)
#ifdef CONFIG_SMP #ifdef CONFIG_SMP
void rdmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h); void rdmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h);
void wrmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h); void wrmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h);
int rdmsr_safe_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h);
int wrmsr_safe_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h);
#else /* CONFIG_SMP */ #else /* CONFIG_SMP */
static inline void rdmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h) static inline void rdmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h)
{ {
...@@ -157,6 +159,14 @@ static inline void wrmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h) ...@@ -157,6 +159,14 @@ static inline void wrmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h)
{ {
wrmsr(msr_no, l, h); wrmsr(msr_no, l, h);
} }
static inline int rdmsr_safe_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h)
{
return rdmsr_safe(msr_no, l, h);
}
static inline int wrmsr_safe_on_cpu(unsigned int cpu, u32 msr_no, u32 l, u32 h)
{
return wrmsr_safe(msr_no, l, h);
}
#endif /* CONFIG_SMP */ #endif /* CONFIG_SMP */
#endif #endif
#endif #endif
......
This diff is collapsed.
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