From 9e5813111265ad3c37a4370f0ee7e634dc07a7d6 Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Wed, 25 May 2011 20:43:31 +0200 Subject: hwmon: (k10temp) Add support for Fam15h (Bulldozer) AMDs upcoming CPUs use the same mechanism for the internal temperature reporting as the Fam10h CPUs, so we just needed to add the appropriate PCI-ID to the list. This allows to use the k10temp driver on those CPUs. While at it change the Kconfig entry to be more generic. Signed-off-by: Andre Przywara Acked-by: Clemens Ladisch Signed-off-by: Jean Delvare --- Documentation/hwmon/k10temp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/k10temp b/Documentation/hwmon/k10temp index d2b56a4fd1f5..0393c89277c0 100644 --- a/Documentation/hwmon/k10temp +++ b/Documentation/hwmon/k10temp @@ -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 -- cgit v1.2.3-70-g09d2 From 512d1027a6c58def3c2a410e8be65b7e730aad3b Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Wed, 25 May 2011 20:43:31 +0200 Subject: hwmon: Add driver for AMD family 15h processor power information This CPU family provides NB register values to gather following TDP information * 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 * power1_crit (ProcessorPwrWatts) * power1_input (CurrPwrWatts) Signed-off-by: Andreas Herrmann Signed-off-by: Jean Delvare --- Documentation/hwmon/fam15h_power | 37 +++++++ MAINTAINERS | 7 ++ drivers/hwmon/Kconfig | 10 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/fam15h_power.c | 229 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 284 insertions(+) create mode 100644 Documentation/hwmon/fam15h_power create mode 100644 drivers/hwmon/fam15h_power.c (limited to 'Documentation') diff --git a/Documentation/hwmon/fam15h_power b/Documentation/hwmon/fam15h_power new file mode 100644 index 000000000000..a92918e0bd69 --- /dev/null +++ b/Documentation/hwmon/fam15h_power @@ -0,0 +1,37 @@ +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 + +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. diff --git a/MAINTAINERS b/MAINTAINERS index 98c324b07a16..88a5bef09473 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -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 +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 L: linux-geode@lists.infradead.org (moderated for non-subscribers) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 511f1f4802f1..567dfae94bbc 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -248,6 +248,16 @@ config SENSORS_K10TEMP 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 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 28e8d52f6379..8524db270bf0 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -48,6 +48,7 @@ obj-$(CONFIG_SENSORS_EMC2103) += emc2103.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 diff --git a/drivers/hwmon/fam15h_power.c b/drivers/hwmon/fam15h_power.c new file mode 100644 index 000000000000..523f8fb9e7d9 --- /dev/null +++ b/drivers/hwmon/fam15h_power.c @@ -0,0 +1,229 @@ +/* + * fam15h_power.c - AMD Family 15h processor power monitoring + * + * Copyright (c) 2011 Advanced Micro Devices, Inc. + * Author: Andreas Herrmann + * + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("AMD Family 15h CPU processor power monitor"); +MODULE_AUTHOR("Andreas Herrmann "); +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) -- cgit v1.2.3-70-g09d2 From 629c58bac082ae091e518187d63249fa7e9f796f Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 25 May 2011 20:43:32 +0200 Subject: hwmon: (f71882fg) Add support for F71808A Signed-off-by: Hans de Goede Signed-off-by: Jean Delvare --- Documentation/hwmon/f71882fg | 4 ++ drivers/hwmon/Kconfig | 2 +- drivers/hwmon/f71882fg.c | 87 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 89 insertions(+), 4 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/f71882fg b/Documentation/hwmon/f71882fg index df02245d1419..84d2623810f3 100644 --- a/Documentation/hwmon/f71882fg +++ b/Documentation/hwmon/f71882fg @@ -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 diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 9ebdb0da7f7c..a620a760397d 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -329,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 diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c index 16330db2bcdc..a4a94a096c90 100644 --- a/drivers/hwmon/f71882fg.c +++ b/drivers/hwmon/f71882fg.c @@ -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, @@ -151,6 +155,7 @@ static const char f71882fg_has_in1_alarm[] = { 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_fan_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,13 @@ 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, @@ -190,6 +197,7 @@ static const char f71882fg_temp_has_beep[] = { static const char f71882fg_nr_temps[] = { [f71808e] = 2, + [f71808a] = 2, [f71858fg] = 3, [f71862fg] = 3, [f71869] = 3, @@ -314,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, @@ -563,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, @@ -1246,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)); @@ -1736,6 +1762,38 @@ leave: 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) { @@ -2183,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; @@ -2244,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: @@ -2269,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, @@ -2386,6 +2456,14 @@ static int f71882fg_remove(struct platform_device *pdev) } 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, @@ -2438,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; -- cgit v1.2.3-70-g09d2 From 67b670ff04cdff1c9584ecdb22e297956664c9b5 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Wed, 25 May 2011 20:43:32 +0200 Subject: hwmon: (max6650) Drop device detection MAX6650 device detection is unreliable, we got reports of false positives. We now have many ways to let users instantiate the devices explicitly, so unreliable detection should be dropped. Signed-off-by: Jean Delvare Acked-by: "Hans J. Koch" Acked-by: Guenter Roeck --- Documentation/hwmon/max6650 | 9 ++++++++- drivers/hwmon/max6650.c | 44 -------------------------------------------- 2 files changed, 8 insertions(+), 45 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/max6650 b/Documentation/hwmon/max6650 index c565650fcfc6..b26fef5f950e 100644 --- a/Documentation/hwmon/max6650 +++ b/Documentation/hwmon/max6650 @@ -4,7 +4,7 @@ Kernel driver max6650 Supported chips: * Maxim 6650 / 6651 Prefix: 'max6650' - Addresses scanned: I2C 0x1b, 0x1f, 0x48, 0x4b + Addresses scanned: none Datasheet: http://pdfserv.maxim-ic.com/en/ds/MAX6650-MAX6651.pdf Authors: @@ -36,6 +36,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 ----------------- diff --git a/drivers/hwmon/max6650.c b/drivers/hwmon/max6650.c index 9a11532ecae8..cced18626174 100644 --- a/drivers/hwmon/max6650.c +++ b/drivers/hwmon/max6650.c @@ -40,13 +40,6 @@ #include #include -/* - * Addresses to scan. There are four disjoint possibilities, by pin config. - */ - -static const unsigned short normal_i2c[] = {0x1b, 0x1f, 0x48, 0x4b, - I2C_CLIENT_END}; - /* * Insmod parameters */ @@ -114,8 +107,6 @@ module_param(clock, int, S_IRUGO); static int max6650_probe(struct i2c_client *client, const struct i2c_device_id *id); -static int max6650_detect(struct i2c_client *client, - struct i2c_board_info *info); static int max6650_init_client(struct i2c_client *client); static int max6650_remove(struct i2c_client *client); static struct max6650_data *max6650_update_device(struct device *dev); @@ -131,15 +122,12 @@ static const struct i2c_device_id max6650_id[] = { MODULE_DEVICE_TABLE(i2c, max6650_id); static struct i2c_driver max6650_driver = { - .class = I2C_CLASS_HWMON, .driver = { .name = "max6650", }, .probe = max6650_probe, .remove = max6650_remove, .id_table = max6650_id, - .detect = max6650_detect, - .address_list = normal_i2c, }; /* @@ -525,38 +513,6 @@ static struct attribute_group max6650_attr_grp = { * Real code */ -/* Return 0 if detection is successful, -ENODEV otherwise */ -static int max6650_detect(struct i2c_client *client, - struct i2c_board_info *info) -{ - struct i2c_adapter *adapter = client->adapter; - int address = client->addr; - - dev_dbg(&adapter->dev, "max6650_detect called\n"); - - if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { - dev_dbg(&adapter->dev, "max6650: I2C bus doesn't support " - "byte read mode, skipping.\n"); - return -ENODEV; - } - - if (((i2c_smbus_read_byte_data(client, MAX6650_REG_CONFIG) & 0xC0) - ||(i2c_smbus_read_byte_data(client, MAX6650_REG_GPIO_STAT) & 0xE0) - ||(i2c_smbus_read_byte_data(client, MAX6650_REG_ALARM_EN) & 0xE0) - ||(i2c_smbus_read_byte_data(client, MAX6650_REG_ALARM) & 0xE0) - ||(i2c_smbus_read_byte_data(client, MAX6650_REG_COUNT) & 0xFC))) { - dev_dbg(&adapter->dev, - "max6650: detection failed at 0x%02x.\n", address); - return -ENODEV; - } - - dev_info(&adapter->dev, "max6650: chip found at 0x%02x.\n", address); - - strlcpy(info->type, "max6650", I2C_NAME_SIZE); - - return 0; -} - static int max6650_probe(struct i2c_client *client, const struct i2c_device_id *id) { -- cgit v1.2.3-70-g09d2 From 9c084dae5dc7ae0039e330230e70f2a5956e566a Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Wed, 25 May 2011 20:43:32 +0200 Subject: hwmon: (max6650) Properly support the MAX6650 The MAX6650 has only one fan input. Signed-off-by: Jean Delvare Acked-by: "Hans J. Koch" Acked-by: Guenter Roeck --- Documentation/hwmon/max6650 | 12 ++++++++---- drivers/hwmon/max6650.c | 34 ++++++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 10 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/max6650 b/Documentation/hwmon/max6650 index b26fef5f950e..58d9644a2bde 100644 --- a/Documentation/hwmon/max6650 +++ b/Documentation/hwmon/max6650 @@ -2,10 +2,14 @@ Kernel driver max6650 ===================== Supported chips: - * Maxim 6650 / 6651 + * Maxim MAX6650 Prefix: 'max6650' 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: Hans J. Koch @@ -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. diff --git a/drivers/hwmon/max6650.c b/drivers/hwmon/max6650.c index cced18626174..ece3aafa54b3 100644 --- a/drivers/hwmon/max6650.c +++ b/drivers/hwmon/max6650.c @@ -116,7 +116,8 @@ static struct max6650_data *max6650_update_device(struct device *dev); */ static const struct i2c_device_id max6650_id[] = { - { "max6650", 0 }, + { "max6650", 1 }, + { "max6651", 4 }, { } }; MODULE_DEVICE_TABLE(i2c, max6650_id); @@ -138,6 +139,7 @@ struct max6650_data { struct device *hwmon_dev; struct mutex update_lock; + int nr_fans; char valid; /* zero until following fields are valid */ unsigned long last_updated; /* in jiffies */ @@ -489,9 +491,6 @@ static mode_t max6650_attrs_visible(struct kobject *kobj, struct attribute *a, static struct attribute *max6650_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, &dev_attr_fan1_target.attr, &dev_attr_fan1_div.attr, &dev_attr_pwm1_enable.attr, @@ -509,6 +508,17 @@ static struct attribute_group max6650_attr_grp = { .is_visible = max6650_attrs_visible, }; +static struct attribute *max6651_attrs[] = { + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan4_input.dev_attr.attr, + NULL +}; + +static const struct attribute_group max6651_attr_grp = { + .attrs = max6651_attrs, +}; + /* * Real code */ @@ -526,6 +536,7 @@ static int max6650_probe(struct i2c_client *client, i2c_set_clientdata(client, data); mutex_init(&data->update_lock); + data->nr_fans = id->driver_data; /* * Initialize the max6650 chip @@ -537,6 +548,12 @@ static int max6650_probe(struct i2c_client *client, err = sysfs_create_group(&client->dev.kobj, &max6650_attr_grp); if (err) goto err_free; + /* 3 additional fan inputs for the MAX6651 */ + if (data->nr_fans == 4) { + err = sysfs_create_group(&client->dev.kobj, &max6651_attr_grp); + if (err) + goto err_remove; + } data->hwmon_dev = hwmon_device_register(&client->dev); if (!IS_ERR(data->hwmon_dev)) @@ -544,6 +561,9 @@ static int max6650_probe(struct i2c_client *client, err = PTR_ERR(data->hwmon_dev); dev_err(&client->dev, "error registering hwmon device.\n"); + if (data->nr_fans == 4) + sysfs_remove_group(&client->dev.kobj, &max6651_attr_grp); +err_remove: sysfs_remove_group(&client->dev.kobj, &max6650_attr_grp); err_free: kfree(data); @@ -554,8 +574,10 @@ static int max6650_remove(struct i2c_client *client) { struct max6650_data *data = i2c_get_clientdata(client); - sysfs_remove_group(&client->dev.kobj, &max6650_attr_grp); hwmon_device_unregister(data->hwmon_dev); + if (data->nr_fans == 4) + sysfs_remove_group(&client->dev.kobj, &max6651_attr_grp); + sysfs_remove_group(&client->dev.kobj, &max6650_attr_grp); kfree(data); return 0; } @@ -668,7 +690,7 @@ static struct max6650_data *max6650_update_device(struct device *dev) MAX6650_REG_SPEED); data->config = i2c_smbus_read_byte_data(client, MAX6650_REG_CONFIG); - for (i = 0; i < 4; i++) { + for (i = 0; i < data->nr_fans; i++) { data->tach[i] = i2c_smbus_read_byte_data(client, tach_reg[i]); } -- cgit v1.2.3-70-g09d2 From b0b349a85d3df00a40a8bd398e4a151fd8e91bbe Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Wed, 25 May 2011 20:43:33 +0200 Subject: hwmon: New driver for the SMSC EMC6W201 This is a new driver for the SMSC EMC6W201 hardware monitoring device. The device is functionally close to the EMC6D100 series, but is register-incompatible. Signed-off-by: Jean Delvare Tested-by: Harry G McGavran Jr Tested-by: Jeff Rickman Acked-by: Guenter Roeck --- Documentation/hwmon/emc6w201 | 42 ++++ drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/emc6w201.c | 539 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 592 insertions(+) create mode 100644 Documentation/hwmon/emc6w201 create mode 100644 drivers/hwmon/emc6w201.c (limited to 'Documentation') diff --git a/Documentation/hwmon/emc6w201 b/Documentation/hwmon/emc6w201 new file mode 100644 index 000000000000..32f355aaf56b --- /dev/null +++ b/Documentation/hwmon/emc6w201 @@ -0,0 +1,42 @@ +Kernel driver emc6w201 +====================== + +Supported chips: + * SMSC EMC6W201 + Prefix: 'emc6w201' + Addresses scanned: I2C 0x2c, 0x2d, 0x2e + Datasheet: Not public + +Author: Jean Delvare + + +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 diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 900409615ae9..16db83c83c8b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -988,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 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 8a57eca7a764..28061cfa0cdb 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -46,6 +46,7 @@ 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 diff --git a/drivers/hwmon/emc6w201.c b/drivers/hwmon/emc6w201.c new file mode 100644 index 000000000000..e0ef32378ac6 --- /dev/null +++ b/drivers/hwmon/emc6w201.c @@ -0,0 +1,539 @@ +/* + * emc6w201.c - Hardware monitoring driver for the SMSC EMC6W201 + * Copyright (C) 2011 Jean Delvare + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 "); +MODULE_DESCRIPTION("SMSC EMC6W201 hardware monitoring driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2