From bfcd415dcb8042084d560a69a7aebb4b1f98a4db Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:35 +0100 Subject: [PATCH 01/14] hwmon: (w83795) Document pin mapping Apparently users are interested in this information, so let's provide it. Signed-off-by: Jean Delvare Acked-by: Guenter Roeck --- Documentation/hwmon/w83795 | 127 +++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 Documentation/hwmon/w83795 diff --git a/Documentation/hwmon/w83795 b/Documentation/hwmon/w83795 new file mode 100644 index 000000000000..9f160371f463 --- /dev/null +++ b/Documentation/hwmon/w83795 @@ -0,0 +1,127 @@ +Kernel driver w83795 +==================== + +Supported chips: + * Winbond/Nuvoton W83795G + Prefix: 'w83795g' + Addresses scanned: I2C 0x2c - 0x2f + Datasheet: Available for download on nuvoton.com + * Winbond/Nuvoton W83795ADG + Prefix: 'w83795adg' + Addresses scanned: I2C 0x2c - 0x2f + Datasheet: Available for download on nuvoton.com + +Authors: + Wei Song (Nuvoton) + Jean Delvare + + +Pin mapping +----------- + +Here is a summary of the pin mapping for the W83795G and W83795ADG. +This can be useful to convert data provided by board manufacturers +into working libsensors configuration statements. + + W83795G | + Pin | Name | Register | Sysfs attribute +------------------------------------------------------------------ + 13 | VSEN1 (VCORE1) | 10h | in0 + 14 | VSEN2 (VCORE2) | 11h | in1 + 15 | VSEN3 (VCORE3) | 12h | in2 + 16 | VSEN4 | 13h | in3 + 17 | VSEN5 | 14h | in4 + 18 | VSEN6 | 15h | in5 + 19 | VSEN7 | 16h | in6 + 20 | VSEN8 | 17h | in7 + 21 | VSEN9 | 18h | in8 + 22 | VSEN10 | 19h | in9 + 23 | VSEN11 | 1Ah | in10 + 28 | VTT | 1Bh | in11 + 24 | 3VDD | 1Ch | in12 + 25 | 3VSB | 1Dh | in13 + 26 | VBAT | 1Eh | in14 + 3 | VSEN12/TR5 | 1Fh | in15/temp5 + 4 | VSEN13/TR5 | 20h | in16/temp6 + 5/ 6 | VDSEN14/TR1/TD1 | 21h | in17/temp1 + 7/ 8 | VDSEN15/TR2/TD2 | 22h | in18/temp2 + 9/ 10 | VDSEN16/TR3/TD3 | 23h | in19/temp3 + 11/ 12 | VDSEN17/TR4/TD4 | 24h | in20/temp4 + 40 | FANIN1 | 2Eh | fan1 + 42 | FANIN2 | 2Fh | fan2 + 44 | FANIN3 | 30h | fan3 + 46 | FANIN4 | 31h | fan4 + 48 | FANIN5 | 32h | fan5 + 50 | FANIN6 | 33h | fan6 + 52 | FANIN7 | 34h | fan7 + 54 | FANIN8 | 35h | fan8 + 57 | FANIN9 | 36h | fan9 + 58 | FANIN10 | 37h | fan10 + 59 | FANIN11 | 38h | fan11 + 60 | FANIN12 | 39h | fan12 + 31 | FANIN13 | 3Ah | fan13 + 35 | FANIN14 | 3Bh | fan14 + 41 | FANCTL1 | 10h (bank 2) | pwm1 + 43 | FANCTL2 | 11h (bank 2) | pwm2 + 45 | FANCTL3 | 12h (bank 2) | pwm3 + 47 | FANCTL4 | 13h (bank 2) | pwm4 + 49 | FANCTL5 | 14h (bank 2) | pwm5 + 51 | FANCTL6 | 15h (bank 2) | pwm6 + 53 | FANCTL7 | 16h (bank 2) | pwm7 + 55 | FANCTL8 | 17h (bank 2) | pwm8 + 29/ 30 | PECI/TSI (DTS1) | 26h | temp7 + 29/ 30 | PECI/TSI (DTS2) | 27h | temp8 + 29/ 30 | PECI/TSI (DTS3) | 28h | temp9 + 29/ 30 | PECI/TSI (DTS4) | 29h | temp10 + 29/ 30 | PECI/TSI (DTS5) | 2Ah | temp11 + 29/ 30 | PECI/TSI (DTS6) | 2Bh | temp12 + 29/ 30 | PECI/TSI (DTS7) | 2Ch | temp13 + 29/ 30 | PECI/TSI (DTS8) | 2Dh | temp14 + 27 | CASEOPEN# | 46h | intrusion0 + + W83795ADG | + Pin | Name | Register | Sysfs attribute +------------------------------------------------------------------ + 10 | VSEN1 (VCORE1) | 10h | in0 + 11 | VSEN2 (VCORE2) | 11h | in1 + 12 | VSEN3 (VCORE3) | 12h | in2 + 13 | VSEN4 | 13h | in3 + 14 | VSEN5 | 14h | in4 + 15 | VSEN6 | 15h | in5 + 16 | VSEN7 | 16h | in6 + 17 | VSEN8 | 17h | in7 + 22 | VTT | 1Bh | in11 + 18 | 3VDD | 1Ch | in12 + 19 | 3VSB | 1Dh | in13 + 20 | VBAT | 1Eh | in14 + 48 | VSEN12/TR5 | 1Fh | in15/temp5 + 1 | VSEN13/TR5 | 20h | in16/temp6 + 2/ 3 | VDSEN14/TR1/TD1 | 21h | in17/temp1 + 4/ 5 | VDSEN15/TR2/TD2 | 22h | in18/temp2 + 6/ 7 | VDSEN16/TR3/TD3 | 23h | in19/temp3 + 8/ 9 | VDSEN17/TR4/TD4 | 24h | in20/temp4 + 32 | FANIN1 | 2Eh | fan1 + 34 | FANIN2 | 2Fh | fan2 + 36 | FANIN3 | 30h | fan3 + 37 | FANIN4 | 31h | fan4 + 38 | FANIN5 | 32h | fan5 + 39 | FANIN6 | 33h | fan6 + 40 | FANIN7 | 34h | fan7 + 41 | FANIN8 | 35h | fan8 + 43 | FANIN9 | 36h | fan9 + 44 | FANIN10 | 37h | fan10 + 45 | FANIN11 | 38h | fan11 + 46 | FANIN12 | 39h | fan12 + 24 | FANIN13 | 3Ah | fan13 + 28 | FANIN14 | 3Bh | fan14 + 33 | FANCTL1 | 10h (bank 2) | pwm1 + 35 | FANCTL2 | 11h (bank 2) | pwm2 + 23 | PECI (DTS1) | 26h | temp7 + 23 | PECI (DTS2) | 27h | temp8 + 23 | PECI (DTS3) | 28h | temp9 + 23 | PECI (DTS4) | 29h | temp10 + 23 | PECI (DTS5) | 2Ah | temp11 + 23 | PECI (DTS6) | 2Bh | temp12 + 23 | PECI (DTS7) | 2Ch | temp13 + 23 | PECI (DTS8) | 2Dh | temp14 + 21 | CASEOPEN# | 46h | intrusion0 From 396bd766a604b823369962ee0203c603b9c0bdec Mon Sep 17 00:00:00 2001 From: "Justin P. Mattock" Date: Mon, 21 Mar 2011 17:59:35 +0100 Subject: [PATCH 02/14] hwmon: (sht15) Spelling fix Remove one too many "n" in a word. Signed-off-by: Justin P. Mattock Acked-by: Jean Delvare --- drivers/hwmon/sht15.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c index a610e7880fb3..3ba7dd82589a 100644 --- a/drivers/hwmon/sht15.c +++ b/drivers/hwmon/sht15.c @@ -610,7 +610,7 @@ static int __devexit sht15_remove(struct platform_device *pdev) struct sht15_data *data = platform_get_drvdata(pdev); /* Make sure any reads from the device are done and - * prevent new ones beginnning */ + * prevent new ones from beginning */ mutex_lock(&data->read_lock); hwmon_device_unregister(data->hwmon_dev); sysfs_remove_group(&pdev->dev.kobj, &sht15_attr_group); From ccd32e735de7a941906e093f8dca924bb05c5794 Mon Sep 17 00:00:00 2001 From: Vivien Didelot Date: Mon, 21 Mar 2011 17:59:35 +0100 Subject: [PATCH 03/14] hwmon: (sht15) Fix integer overflow in humidity calculation An integer overflow occurs in the calculation of RHlinear when the relative humidity is greater than around 30%. The consequence is a subtle (but noticeable) error in the resulting humidity measurement. Signed-off-by: Vivien Didelot Signed-off-by: Jean Delvare Cc: stable@kernel.org Cc: Jonathan Cameron --- drivers/hwmon/sht15.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c index 3ba7dd82589a..1a9c32d6893a 100644 --- a/drivers/hwmon/sht15.c +++ b/drivers/hwmon/sht15.c @@ -333,11 +333,11 @@ static inline int sht15_calc_humid(struct sht15_data *data) const int c1 = -4; const int c2 = 40500; /* x 10 ^ -6 */ - const int c3 = -2800; /* x10 ^ -9 */ + const int c3 = -28; /* x 10 ^ -7 */ RHlinear = c1*1000 + c2 * data->val_humid/1000 - + (data->val_humid * data->val_humid * c3)/1000000; + + (data->val_humid * data->val_humid * c3) / 10000; return (temp - 25000) * (10000 + 80 * data->val_humid) / 1000000 + RHlinear; } From 39c3e721d65793373e7bc6f5dad0591ef6d09268 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:35 +0100 Subject: [PATCH 04/14] Let Kconfig handle lis3lv02d dependencies The dependencies between the various lis3lv02d drivers make it impossible to split them to different directories, while we really want to do this. Move handling of dependencies from Makefile to Kconfig, to make the move possible at all. Signed-off-by: Jean Delvare Acked-by: Guenter Roeck Acked-by: Eric Piel Acked-by: Jonathan Cameron Tested-by: Eric Piel Tested-by: Takashi Iwai --- drivers/hwmon/Kconfig | 43 +++++++++++++++++++----------------------- drivers/hwmon/Makefile | 9 ++++++--- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 1bfb4439e4e1..c48101cf58f4 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2,6 +2,12 @@ # Hardware monitoring chip drivers configuration # +config SENSORS_LIS3LV02D + tristate + depends on INPUT + select INPUT_POLLDEV + default n + menuconfig HWMON tristate "Hardware Monitoring support" depends on HAS_IOMEM @@ -1218,7 +1224,7 @@ config SENSORS_ULTRA45 config SENSORS_LIS3_SPI tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (SPI)" depends on !ACPI && SPI_MASTER && INPUT - select INPUT_POLLDEV + select SENSORS_LIS3LV02D default n help This driver provides support for the LIS3LV02Dx accelerometer connected @@ -1235,7 +1241,7 @@ config SENSORS_LIS3_SPI config SENSORS_LIS3_I2C tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (I2C)" depends on I2C && INPUT - select INPUT_POLLDEV + select SENSORS_LIS3LV02D default n help This driver provides support for the LIS3LV02Dx accelerometer connected @@ -1296,35 +1302,24 @@ config SENSORS_ATK0110 This driver can also be built as a module. If so, the module will be called asus_atk0110. -config SENSORS_LIS3LV02D - tristate "STMicroeletronics LIS3* three-axis digital accelerometer" +config HP_ACCEL + tristate "HP laptop accelerometer" depends on INPUT - select INPUT_POLLDEV + select SENSORS_LIS3LV02D select NEW_LEDS select LEDS_CLASS default n help - This driver provides support for the LIS3* accelerometers, such as the - LIS3LV02DL or the LIS331DL. In particular, it can be found in a number - of HP laptops, which have the "Mobile Data Protection System 3D" or - "3D DriveGuard" feature. On such systems the driver should load - automatically (via ACPI alias). The accelerometer might also be found - in other systems, connected via SPI or I2C. The accelerometer data is - readable via /sys/devices/platform/lis3lv02d. + This driver provides support for the "Mobile Data Protection System 3D" + or "3D DriveGuard" feature of HP laptops. On such systems the driver + should load automatically (via ACPI alias). - This driver also provides an absolute input class device, allowing - a laptop to act as a pinball machine-esque joystick. It provides also - a misc device which can be used to detect free-fall. On HP laptops, - if the led infrastructure is activated, support for a led indicating - disk protection will be provided as hp::hddprotect. For more - information on the feature, refer to Documentation/hwmon/lis3lv02d. + Support for a led indicating disk protection will be provided as + hp::hddprotect. For more information on the feature, refer to + Documentation/hwmon/lis3lv02d. - This driver can also be built as modules. If so, the core module - will be called lis3lv02d and a specific module for HP laptops will be - called hp_accel. - - Say Y here if you have an applicable laptop and want to experience - the awesome power of lis3lv02d. + To compile this driver as a module, choose M here: the module will + be called hp_accel. endif # ACPI diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 8a238dec5691..94bf27482979 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -5,8 +5,12 @@ obj-$(CONFIG_HWMON) += hwmon.o obj-$(CONFIG_HWMON_VID) += hwmon-vid.o +# Helper drivers +obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o + # APCI drivers obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o +obj-$(CONFIG_HP_ACCEL) += hp_accel.o # Native drivers # asb100, then w83781d go first, as they can override other drivers' addresses. @@ -63,9 +67,8 @@ obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o -obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o hp_accel.o -obj-$(CONFIG_SENSORS_LIS3_SPI) += lis3lv02d.o lis3lv02d_spi.o -obj-$(CONFIG_SENSORS_LIS3_I2C) += lis3lv02d.o lis3lv02d_i2c.o +obj-$(CONFIG_SENSORS_LIS3_SPI) += lis3lv02d_spi.o +obj-$(CONFIG_SENSORS_LIS3_I2C) += lis3lv02d_i2c.o obj-$(CONFIG_SENSORS_LM63) += lm63.o obj-$(CONFIG_SENSORS_LM70) += lm70.o obj-$(CONFIG_SENSORS_LM73) += lm73.o From efcfed9bad88be8193ee6a1b8e72d7381e7b0e0e Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: [PATCH 05/14] Move hp_accel to drivers/platform/x86 The hp_accel driver isn't a hardware monitoring driver, so it doesn't belong to drivers/hwmon. Move it to drivers/platform/x86, assuming HP doesn't ship non-x86 laptops. Signed-off-by: Jean Delvare Acked-by: Guenter Roeck Acked-by: Eric Piel Acked-by: Jonathan Cameron Tested-by: Eric Piel Tested-by: Takashi Iwai --- Documentation/hwmon/lis3lv02d | 4 ++-- Documentation/{hwmon => laptops}/hpfall.c | 0 drivers/hwmon/Kconfig | 19 ------------------- drivers/hwmon/Makefile | 1 - drivers/platform/x86/Kconfig | 18 ++++++++++++++++++ drivers/platform/x86/Makefile | 1 + drivers/{hwmon => platform/x86}/hp_accel.c | 5 ++--- 7 files changed, 23 insertions(+), 25 deletions(-) rename Documentation/{hwmon => laptops}/hpfall.c (100%) rename drivers/{hwmon => platform/x86}/hp_accel.c (99%) diff --git a/Documentation/hwmon/lis3lv02d b/Documentation/hwmon/lis3lv02d index 06534f25e643..f1a4ec840f86 100644 --- a/Documentation/hwmon/lis3lv02d +++ b/Documentation/hwmon/lis3lv02d @@ -17,8 +17,8 @@ Description This driver provides support for the accelerometer found in various HP laptops sporting the feature officially called "HP Mobile Data Protection System 3D" or "HP 3D DriveGuard". It detects automatically laptops with this sensor. Known -models (full list can be found in drivers/hwmon/hp_accel.c) will have their -axis automatically oriented on standard way (eg: you can directly play +models (full list can be found in drivers/platform/x86/hp_accel.c) will have +their axis automatically oriented on standard way (eg: you can directly play neverball). The accelerometer data is readable via /sys/devices/platform/lis3lv02d. Reported values are scaled to mg values (1/1000th of earth gravity). diff --git a/Documentation/hwmon/hpfall.c b/Documentation/laptops/hpfall.c similarity index 100% rename from Documentation/hwmon/hpfall.c rename to Documentation/laptops/hpfall.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index c48101cf58f4..3f8c895417de 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1302,25 +1302,6 @@ config SENSORS_ATK0110 This driver can also be built as a module. If so, the module will be called asus_atk0110. -config HP_ACCEL - tristate "HP laptop accelerometer" - depends on INPUT - select SENSORS_LIS3LV02D - select NEW_LEDS - select LEDS_CLASS - default n - help - This driver provides support for the "Mobile Data Protection System 3D" - or "3D DriveGuard" feature of HP laptops. On such systems the driver - should load automatically (via ACPI alias). - - Support for a led indicating disk protection will be provided as - hp::hddprotect. For more information on the feature, refer to - Documentation/hwmon/lis3lv02d. - - To compile this driver as a module, choose M here: the module will - be called hp_accel. - endif # ACPI endif # HWMON diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 94bf27482979..55ba906def74 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -10,7 +10,6 @@ obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o # APCI drivers obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o -obj-$(CONFIG_HP_ACCEL) += hp_accel.o # Native drivers # asb100, then w83781d go first, as they can override other drivers' addresses. diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index a59af5b24f0a..222dfb737b11 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -138,6 +138,24 @@ config TC1100_WMI This is a driver for the WMI extensions (wireless and bluetooth power control) of the HP Compaq TC1100 tablet. +config HP_ACCEL + tristate "HP laptop accelerometer" + depends on INPUT && ACPI + select SENSORS_LIS3LV02D + select NEW_LEDS + select LEDS_CLASS + help + This driver provides support for the "Mobile Data Protection System 3D" + or "3D DriveGuard" feature of HP laptops. On such systems the driver + should load automatically (via ACPI alias). + + Support for a led indicating disk protection will be provided as + hp::hddprotect. For more information on the feature, refer to + Documentation/hwmon/lis3lv02d. + + To compile this driver as a module, choose M here: the module will + be called hp_accel. + config HP_WMI tristate "HP WMI extras" depends on ACPI_WMI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 4ec4ff8f9182..299aefb3e74c 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o obj-$(CONFIG_DELL_WMI) += dell-wmi.o obj-$(CONFIG_ACER_WMI) += acer-wmi.o obj-$(CONFIG_ACERHDF) += acerhdf.o +obj-$(CONFIG_HP_ACCEL) += hp_accel.o obj-$(CONFIG_HP_WMI) += hp-wmi.o obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o diff --git a/drivers/hwmon/hp_accel.c b/drivers/platform/x86/hp_accel.c similarity index 99% rename from drivers/hwmon/hp_accel.c rename to drivers/platform/x86/hp_accel.c index 3d21fa2b97cd..23f09c1b234b 100644 --- a/drivers/hwmon/hp_accel.c +++ b/drivers/platform/x86/hp_accel.c @@ -35,9 +35,9 @@ #include #include #include +#include #include -#include -#include "lis3lv02d.h" +#include "../../hwmon/lis3lv02d.h" #define DRIVER_NAME "lis3lv02d" #define ACPI_MDPS_CLASS "accelerometer" @@ -402,4 +402,3 @@ MODULE_LICENSE("GPL"); module_init(lis3lv02d_init_module); module_exit(lis3lv02d_exit_module); - From ff606677f6a47c63329cf8e6c7cf978c29f2d736 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: [PATCH 06/14] Move lis3lv02d drivers to drivers/misc The lis3lv02d drivers aren't hardware monitoring drivers, so the don't belong to drivers/hwmon. Move them to drivers/misc, short of a better home. Signed-off-by: Jean Delvare Acked-by: Guenter Roeck Acked-by: Eric Piel Acked-by: Jonathan Cameron Tested-by: Eric Piel Tested-by: Takashi Iwai --- .../{hwmon => misc-devices}/lis3lv02d | 0 MAINTAINERS | 4 +- drivers/hwmon/Kconfig | 40 ------------------- drivers/hwmon/Makefile | 5 --- drivers/misc/Kconfig | 9 +++++ drivers/misc/Makefile | 1 + drivers/misc/lis3lv02d/Kconfig | 37 +++++++++++++++++ drivers/misc/lis3lv02d/Makefile | 7 ++++ drivers/{hwmon => misc/lis3lv02d}/lis3lv02d.c | 3 +- drivers/{hwmon => misc/lis3lv02d}/lis3lv02d.h | 0 .../{hwmon => misc/lis3lv02d}/lis3lv02d_i2c.c | 2 +- .../{hwmon => misc/lis3lv02d}/lis3lv02d_spi.c | 0 drivers/platform/x86/hp_accel.c | 2 +- 13 files changed, 59 insertions(+), 51 deletions(-) rename Documentation/{hwmon => misc-devices}/lis3lv02d (100%) create mode 100644 drivers/misc/lis3lv02d/Kconfig create mode 100644 drivers/misc/lis3lv02d/Makefile rename drivers/{hwmon => misc/lis3lv02d}/lis3lv02d.c (99%) rename drivers/{hwmon => misc/lis3lv02d}/lis3lv02d.h (100%) rename drivers/{hwmon => misc/lis3lv02d}/lis3lv02d_i2c.c (99%) rename drivers/{hwmon => misc/lis3lv02d}/lis3lv02d_spi.c (100%) diff --git a/Documentation/hwmon/lis3lv02d b/Documentation/misc-devices/lis3lv02d similarity index 100% rename from Documentation/hwmon/lis3lv02d rename to Documentation/misc-devices/lis3lv02d diff --git a/MAINTAINERS b/MAINTAINERS index c7a41b1fe453..4edb2a800b54 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3913,8 +3913,8 @@ S: Supported LIS3LV02D ACCELEROMETER DRIVER M: Eric Piel S: Maintained -F: Documentation/hwmon/lis3lv02d -F: drivers/hwmon/lis3lv02d.* +F: Documentation/misc-devices/lis3lv02d +F: drivers/misc/lis3lv02d/ LLC (802.2) M: Arnaldo Carvalho de Melo diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 3f8c895417de..47621abb9a05 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2,12 +2,6 @@ # Hardware monitoring chip drivers configuration # -config SENSORS_LIS3LV02D - tristate - depends on INPUT - select INPUT_POLLDEV - default n - menuconfig HWMON tristate "Hardware Monitoring support" depends on HAS_IOMEM @@ -1221,40 +1215,6 @@ config SENSORS_ULTRA45 This driver provides support for the Ultra45 workstation environmental sensors. -config SENSORS_LIS3_SPI - tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (SPI)" - depends on !ACPI && SPI_MASTER && INPUT - select SENSORS_LIS3LV02D - default n - help - This driver provides support for the LIS3LV02Dx accelerometer connected - via SPI. The accelerometer data is readable via - /sys/devices/platform/lis3lv02d. - - This driver also provides an absolute input class device, allowing - the laptop to act as a pinball machine-esque joystick. - - This driver can also be built as modules. If so, the core module - will be called lis3lv02d and a specific module for the SPI transport - is called lis3lv02d_spi. - -config SENSORS_LIS3_I2C - tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (I2C)" - depends on I2C && INPUT - select SENSORS_LIS3LV02D - default n - help - This driver provides support for the LIS3LV02Dx accelerometer connected - via I2C. The accelerometer data is readable via - /sys/devices/platform/lis3lv02d. - - This driver also provides an absolute input class device, allowing - the device to act as a pinball machine-esque joystick. - - This driver can also be built as modules. If so, the core module - will be called lis3lv02d and a specific module for the I2C transport - is called lis3lv02d_i2c. - config SENSORS_APPLESMC tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)" depends on INPUT && X86 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 55ba906def74..c068f82082cd 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -5,9 +5,6 @@ obj-$(CONFIG_HWMON) += hwmon.o obj-$(CONFIG_HWMON_VID) += hwmon-vid.o -# Helper drivers -obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o - # APCI drivers obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o @@ -66,8 +63,6 @@ obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o -obj-$(CONFIG_SENSORS_LIS3_SPI) += lis3lv02d_spi.o -obj-$(CONFIG_SENSORS_LIS3_I2C) += lis3lv02d_i2c.o obj-$(CONFIG_SENSORS_LM63) += lm63.o obj-$(CONFIG_SENSORS_LM70) += lm70.o obj-$(CONFIG_SENSORS_LM73) += lm73.o diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index b7d5ef234ac9..203500d9b848 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -2,6 +2,14 @@ # Misc strange devices # +# This one has to live outside of the MISC_DEVICES conditional, +# because it may be selected by drivers/platform/x86/hp_accel. +config SENSORS_LIS3LV02D + tristate + depends on INPUT + select INPUT_POLLDEV + default n + menuconfig MISC_DEVICES bool "Misc devices" ---help--- @@ -462,5 +470,6 @@ source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" source "drivers/misc/iwmc3200top/Kconfig" source "drivers/misc/ti-st/Kconfig" +source "drivers/misc/lis3lv02d/Kconfig" endif # MISC_DEVICES diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 98009cc20cb9..804f421bc079 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -42,3 +42,4 @@ obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o obj-$(CONFIG_PCH_PHUB) += pch_phub.o obj-y += ti-st/ obj-$(CONFIG_AB8500_PWM) += ab8500-pwm.o +obj-y += lis3lv02d/ diff --git a/drivers/misc/lis3lv02d/Kconfig b/drivers/misc/lis3lv02d/Kconfig new file mode 100644 index 000000000000..8f474e6fc7b4 --- /dev/null +++ b/drivers/misc/lis3lv02d/Kconfig @@ -0,0 +1,37 @@ +# +# STMicroelectonics LIS3LV02D and similar accelerometers +# + +config SENSORS_LIS3_SPI + tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (SPI)" + depends on !ACPI && SPI_MASTER && INPUT + select SENSORS_LIS3LV02D + default n + help + This driver provides support for the LIS3LV02Dx accelerometer connected + via SPI. The accelerometer data is readable via + /sys/devices/platform/lis3lv02d. + + This driver also provides an absolute input class device, allowing + the laptop to act as a pinball machine-esque joystick. + + This driver can also be built as modules. If so, the core module + will be called lis3lv02d and a specific module for the SPI transport + is called lis3lv02d_spi. + +config SENSORS_LIS3_I2C + tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (I2C)" + depends on I2C && INPUT + select SENSORS_LIS3LV02D + default n + help + This driver provides support for the LIS3LV02Dx accelerometer connected + via I2C. The accelerometer data is readable via + /sys/devices/platform/lis3lv02d. + + This driver also provides an absolute input class device, allowing + the device to act as a pinball machine-esque joystick. + + This driver can also be built as modules. If so, the core module + will be called lis3lv02d and a specific module for the I2C transport + is called lis3lv02d_i2c. diff --git a/drivers/misc/lis3lv02d/Makefile b/drivers/misc/lis3lv02d/Makefile new file mode 100644 index 000000000000..4bf58b16fcf8 --- /dev/null +++ b/drivers/misc/lis3lv02d/Makefile @@ -0,0 +1,7 @@ +# +# STMicroelectonics LIS3LV02D and similar accelerometers +# + +obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o +obj-$(CONFIG_SENSORS_LIS3_SPI) += lis3lv02d_spi.o +obj-$(CONFIG_SENSORS_LIS3_I2C) += lis3lv02d_i2c.o diff --git a/drivers/hwmon/lis3lv02d.c b/drivers/misc/lis3lv02d/lis3lv02d.c similarity index 99% rename from drivers/hwmon/lis3lv02d.c rename to drivers/misc/lis3lv02d/lis3lv02d.c index d805e8e57967..b928bc14e97b 100644 --- a/drivers/hwmon/lis3lv02d.c +++ b/drivers/misc/lis3lv02d/lis3lv02d.c @@ -38,7 +38,7 @@ #include #include #include -#include +#include #include "lis3lv02d.h" #define DRIVER_NAME "lis3lv02d" @@ -88,7 +88,6 @@ struct lis3lv02d lis3_dev = { .misc_wait = __WAIT_QUEUE_HEAD_INITIALIZER(lis3_dev.misc_wait), }; - EXPORT_SYMBOL_GPL(lis3_dev); /* just like param_set_int() but does sanity-check so that it won't point diff --git a/drivers/hwmon/lis3lv02d.h b/drivers/misc/lis3lv02d/lis3lv02d.h similarity index 100% rename from drivers/hwmon/lis3lv02d.h rename to drivers/misc/lis3lv02d/lis3lv02d.h diff --git a/drivers/hwmon/lis3lv02d_i2c.c b/drivers/misc/lis3lv02d/lis3lv02d_i2c.c similarity index 99% rename from drivers/hwmon/lis3lv02d_i2c.c rename to drivers/misc/lis3lv02d/lis3lv02d_i2c.c index 8853afce85ce..b20dfb4522d2 100644 --- a/drivers/hwmon/lis3lv02d_i2c.c +++ b/drivers/misc/lis3lv02d/lis3lv02d_i2c.c @@ -33,7 +33,7 @@ #include #include "lis3lv02d.h" -#define DRV_NAME "lis3lv02d_i2c" +#define DRV_NAME "lis3lv02d_i2c" static const char reg_vdd[] = "Vdd"; static const char reg_vdd_io[] = "Vdd_IO"; diff --git a/drivers/hwmon/lis3lv02d_spi.c b/drivers/misc/lis3lv02d/lis3lv02d_spi.c similarity index 100% rename from drivers/hwmon/lis3lv02d_spi.c rename to drivers/misc/lis3lv02d/lis3lv02d_spi.c diff --git a/drivers/platform/x86/hp_accel.c b/drivers/platform/x86/hp_accel.c index 23f09c1b234b..0a52711a6362 100644 --- a/drivers/platform/x86/hp_accel.c +++ b/drivers/platform/x86/hp_accel.c @@ -37,7 +37,7 @@ #include #include #include -#include "../../hwmon/lis3lv02d.h" +#include "../../misc/lis3lv02d/lis3lv02d.h" #define DRIVER_NAME "lis3lv02d" #define ACPI_MDPS_CLASS "accelerometer" From 96b4b9bfea28e38c38d26ca47e82ac0fbe2f28b6 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: [PATCH 07/14] hp_accel: Fix driver name I suspect that the "lis3lv02d" driver name is a legacy from before the split into several modules. Use a specific name for the hp_accel driver, for better error messages and easier investigation of issues. Signed-off-by: Jean Delvare Acked-by: Guenter Roeck Acked-by: Eric Piel Acked-by: Jonathan Cameron Tested-by: Eric Piel Tested-by: Takashi Iwai --- drivers/platform/x86/hp_accel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/hp_accel.c b/drivers/platform/x86/hp_accel.c index 0a52711a6362..1b52d00e2f90 100644 --- a/drivers/platform/x86/hp_accel.c +++ b/drivers/platform/x86/hp_accel.c @@ -39,7 +39,7 @@ #include #include "../../misc/lis3lv02d/lis3lv02d.h" -#define DRIVER_NAME "lis3lv02d" +#define DRIVER_NAME "hp_accel" #define ACPI_MDPS_CLASS "accelerometer" /* Delayed LEDs infrastructure ------------------------------------ */ From 05e82fe40faee8499b4e3ba12fddaaf013d84203 Mon Sep 17 00:00:00 2001 From: Len Sorensen Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: [PATCH 08/14] hwmon: (lm75) Add detection of the National Semiconductor LM75A Add support for detection of the National Semiconductor LM75A using the ID register value. Signed-off-by: Len Sorensen Signed-off-by: Jean Delvare --- Documentation/hwmon/lm75 | 5 ++++ drivers/hwmon/Kconfig | 2 +- drivers/hwmon/lm75.c | 56 +++++++++++++++++++++++++++++----------- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/Documentation/hwmon/lm75 b/Documentation/hwmon/lm75 index 8e6356fe05d7..a1790401fdde 100644 --- a/Documentation/hwmon/lm75 +++ b/Documentation/hwmon/lm75 @@ -7,6 +7,11 @@ Supported chips: Addresses scanned: I2C 0x48 - 0x4f Datasheet: Publicly available at the National Semiconductor website http://www.national.com/ + * National Semiconductor LM75A + Prefix: 'lm75a' + Addresses scanned: I2C 0x48 - 0x4f + Datasheet: Publicly available at the National Semiconductor website + http://www.national.com/ * Dallas Semiconductor DS75 Prefix: 'lm75' Addresses scanned: I2C 0x48 - 0x4f diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 47621abb9a05..19d72bcf966a 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -521,7 +521,7 @@ config SENSORS_LM75 - Dallas Semiconductor DS75 and DS1775 - Maxim MAX6625 and MAX6626 - Microchip MCP980x - - National Semiconductor LM75 + - National Semiconductor LM75, LM75A - NXP's LM75A - ST Microelectronics STDS75 - TelCom (now Microchip) TCN75 diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index f36eb80d227f..638dd0586e3f 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -232,6 +232,8 @@ static const struct i2c_device_id lm75_ids[] = { }; MODULE_DEVICE_TABLE(i2c, lm75_ids); +#define LM75A_ID 0xA1 + /* Return 0 if detection is successful, -ENODEV otherwise */ static int lm75_detect(struct i2c_client *new_client, struct i2c_board_info *info) @@ -239,6 +241,7 @@ static int lm75_detect(struct i2c_client *new_client, struct i2c_adapter *adapter = new_client->adapter; int i; int cur, conf, hyst, os; + bool is_lm75a = 0; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) @@ -250,23 +253,43 @@ static int lm75_detect(struct i2c_client *new_client, addresses 0x04-0x07 returning the last read value. The cycling+unused addresses combination is not tested, since it would significantly slow the detection down and would - hardly add any value. */ + hardly add any value. + + The National Semiconductor LM75A is different than earlier + LM75s. It has an ID byte of 0xaX (where X is the chip + revision, with 1 being the only revision in existence) in + register 7, and unused registers return 0xff rather than the + last read value. */ - /* Unused addresses */ cur = i2c_smbus_read_word_data(new_client, 0); conf = i2c_smbus_read_byte_data(new_client, 1); - hyst = i2c_smbus_read_word_data(new_client, 2); - if (i2c_smbus_read_word_data(new_client, 4) != hyst - || i2c_smbus_read_word_data(new_client, 5) != hyst - || i2c_smbus_read_word_data(new_client, 6) != hyst - || i2c_smbus_read_word_data(new_client, 7) != hyst) - return -ENODEV; - os = i2c_smbus_read_word_data(new_client, 3); - if (i2c_smbus_read_word_data(new_client, 4) != os - || i2c_smbus_read_word_data(new_client, 5) != os - || i2c_smbus_read_word_data(new_client, 6) != os - || i2c_smbus_read_word_data(new_client, 7) != os) - return -ENODEV; + + /* First check for LM75A */ + if (i2c_smbus_read_byte_data(new_client, 7) == LM75A_ID) { + /* LM75A returns 0xff on unused registers so + just to be sure we check for that too. */ + if (i2c_smbus_read_byte_data(new_client, 4) != 0xff + || i2c_smbus_read_byte_data(new_client, 5) != 0xff + || i2c_smbus_read_byte_data(new_client, 6) != 0xff) + return -ENODEV; + is_lm75a = 1; + hyst = i2c_smbus_read_word_data(new_client, 2); + os = i2c_smbus_read_word_data(new_client, 3); + } else { /* Traditional style LM75 detection */ + /* Unused addresses */ + hyst = i2c_smbus_read_word_data(new_client, 2); + if (i2c_smbus_read_word_data(new_client, 4) != hyst + || i2c_smbus_read_word_data(new_client, 5) != hyst + || i2c_smbus_read_word_data(new_client, 6) != hyst + || i2c_smbus_read_word_data(new_client, 7) != hyst) + return -ENODEV; + os = i2c_smbus_read_word_data(new_client, 3); + if (i2c_smbus_read_word_data(new_client, 4) != os + || i2c_smbus_read_word_data(new_client, 5) != os + || i2c_smbus_read_word_data(new_client, 6) != os + || i2c_smbus_read_word_data(new_client, 7) != os) + return -ENODEV; + } /* Unused bits */ if (conf & 0xe0) @@ -278,9 +301,12 @@ static int lm75_detect(struct i2c_client *new_client, || i2c_smbus_read_word_data(new_client, i + 2) != hyst || i2c_smbus_read_word_data(new_client, i + 3) != os) return -ENODEV; + if (is_lm75a && i2c_smbus_read_byte_data(new_client, i + 7) + != LM75A_ID) + return -ENODEV; } - strlcpy(info->type, "lm75", I2C_NAME_SIZE); + strlcpy(info->type, is_lm75a ? "lm75a" : "lm75", I2C_NAME_SIZE); return 0; } From e76f67b5babc65cd620d395a1fd231409808df90 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: [PATCH 09/14] hwmon: (lm75) Speed up detection Make the LM75/LM75A device detection faster: * Don't read the current temperature value when we don't use it. * Check for unused bits in the configuration register as soon as we have read its value. * Don't use word reads, not all devices support this, and some which don't misbehave when you try. * Check for cycling register values every 40 register addresses instead of every 8, it's 5 times faster and just as efficient. Some of these improvements come straight from the user-space sensors-detect script, so both detection routines are in line now. Signed-off-by: Jean Delvare Cc: Len Sorensen Acked-by: Guenter Roeck --- drivers/hwmon/lm75.c | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index 638dd0586e3f..ef902d5d06ab 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -240,7 +240,7 @@ static int lm75_detect(struct i2c_client *new_client, { struct i2c_adapter *adapter = new_client->adapter; int i; - int cur, conf, hyst, os; + int conf, hyst, os; bool is_lm75a = 0; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | @@ -261,8 +261,10 @@ static int lm75_detect(struct i2c_client *new_client, register 7, and unused registers return 0xff rather than the last read value. */ - cur = i2c_smbus_read_word_data(new_client, 0); + /* Unused bits */ conf = i2c_smbus_read_byte_data(new_client, 1); + if (conf & 0xe0) + return -ENODEV; /* First check for LM75A */ if (i2c_smbus_read_byte_data(new_client, 7) == LM75A_ID) { @@ -273,33 +275,29 @@ static int lm75_detect(struct i2c_client *new_client, || i2c_smbus_read_byte_data(new_client, 6) != 0xff) return -ENODEV; is_lm75a = 1; - hyst = i2c_smbus_read_word_data(new_client, 2); - os = i2c_smbus_read_word_data(new_client, 3); + hyst = i2c_smbus_read_byte_data(new_client, 2); + os = i2c_smbus_read_byte_data(new_client, 3); } else { /* Traditional style LM75 detection */ /* Unused addresses */ - hyst = i2c_smbus_read_word_data(new_client, 2); - if (i2c_smbus_read_word_data(new_client, 4) != hyst - || i2c_smbus_read_word_data(new_client, 5) != hyst - || i2c_smbus_read_word_data(new_client, 6) != hyst - || i2c_smbus_read_word_data(new_client, 7) != hyst) + hyst = i2c_smbus_read_byte_data(new_client, 2); + if (i2c_smbus_read_byte_data(new_client, 4) != hyst + || i2c_smbus_read_byte_data(new_client, 5) != hyst + || i2c_smbus_read_byte_data(new_client, 6) != hyst + || i2c_smbus_read_byte_data(new_client, 7) != hyst) return -ENODEV; - os = i2c_smbus_read_word_data(new_client, 3); - if (i2c_smbus_read_word_data(new_client, 4) != os - || i2c_smbus_read_word_data(new_client, 5) != os - || i2c_smbus_read_word_data(new_client, 6) != os - || i2c_smbus_read_word_data(new_client, 7) != os) + os = i2c_smbus_read_byte_data(new_client, 3); + if (i2c_smbus_read_byte_data(new_client, 4) != os + || i2c_smbus_read_byte_data(new_client, 5) != os + || i2c_smbus_read_byte_data(new_client, 6) != os + || i2c_smbus_read_byte_data(new_client, 7) != os) return -ENODEV; } - /* Unused bits */ - if (conf & 0xe0) - return -ENODEV; - /* Addresses cycling */ - for (i = 8; i < 0xff; i += 8) { + for (i = 8; i <= 248; i += 40) { if (i2c_smbus_read_byte_data(new_client, i + 1) != conf - || i2c_smbus_read_word_data(new_client, i + 2) != hyst - || i2c_smbus_read_word_data(new_client, i + 3) != os) + || i2c_smbus_read_byte_data(new_client, i + 2) != hyst + || i2c_smbus_read_byte_data(new_client, i + 3) != os) return -ENODEV; if (is_lm75a && i2c_smbus_read_byte_data(new_client, i + 7) != LM75A_ID) From 93d0cc588345a9de304285781e010e298f74e06e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: [PATCH 10/14] hwmon: (abituguru*) Update my email address Signed-off-by: Hans de Goede Signed-off-by: Jean Delvare --- MAINTAINERS | 2 +- drivers/hwmon/abituguru.c | 4 ++-- drivers/hwmon/abituguru3.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 4edb2a800b54..666287a6f3da 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -198,7 +198,7 @@ F: Documentation/scsi/aacraid.txt F: drivers/scsi/aacraid/ ABIT UGURU 1,2 HARDWARE MONITOR DRIVER -M: Hans de Goede +M: Hans de Goede L: lm-sensors@lm-sensors.org S: Maintained F: drivers/hwmon/abituguru.c diff --git a/drivers/hwmon/abituguru.c b/drivers/hwmon/abituguru.c index 8f07a9dda152..0e05aa179eaa 100644 --- a/drivers/hwmon/abituguru.c +++ b/drivers/hwmon/abituguru.c @@ -1,5 +1,5 @@ /* - abituguru.c Copyright (c) 2005-2006 Hans de Goede + abituguru.c Copyright (c) 2005-2006 Hans de Goede 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 @@ -1505,7 +1505,7 @@ static void __exit abituguru_exit(void) platform_driver_unregister(&abituguru_driver); } -MODULE_AUTHOR("Hans de Goede "); +MODULE_AUTHOR("Hans de Goede "); MODULE_DESCRIPTION("Abit uGuru Sensor device"); MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/abituguru3.c b/drivers/hwmon/abituguru3.c index 48d21e22e930..034cebfcd273 100644 --- a/drivers/hwmon/abituguru3.c +++ b/drivers/hwmon/abituguru3.c @@ -1,7 +1,7 @@ /* abituguru3.c - Copyright (c) 2006-2008 Hans de Goede + Copyright (c) 2006-2008 Hans de Goede Copyright (c) 2008 Alistair John Strachan This program is free software; you can redistribute it and/or modify @@ -1266,7 +1266,7 @@ static void __exit abituguru3_exit(void) platform_driver_unregister(&abituguru3_driver); } -MODULE_AUTHOR("Hans de Goede "); +MODULE_AUTHOR("Hans de Goede "); MODULE_DESCRIPTION("Abit uGuru3 Sensor device"); MODULE_LICENSE("GPL"); From a98d506c08ffe754fa013c7f70c4d578b991fb4b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: [PATCH 11/14] hwmon: New driver for SMSC SCH5627 SMSC SCH5627 Super I/O chips include complete hardware monitoring capabilities. They can monitor up to 5 voltages, 4 fans and 8 temperatures. The hardware monitoring part of the SMSC SCH5627 is accessed by talking through an embedded microcontroller. An application note describing the protocol for communicating with the microcontroller is available upon request. Please mail me if you want a copy. Signed-off-by: Hans de Goede Signed-off-by: Jean Delvare --- Documentation/hwmon/sch5627 | 22 + MAINTAINERS | 7 + drivers/hwmon/Kconfig | 9 + drivers/hwmon/Makefile | 1 + drivers/hwmon/sch5627.c | 858 ++++++++++++++++++++++++++++++++++++ 5 files changed, 897 insertions(+) create mode 100644 Documentation/hwmon/sch5627 create mode 100644 drivers/hwmon/sch5627.c diff --git a/Documentation/hwmon/sch5627 b/Documentation/hwmon/sch5627 new file mode 100644 index 000000000000..446a054e4912 --- /dev/null +++ b/Documentation/hwmon/sch5627 @@ -0,0 +1,22 @@ +Kernel driver sch5627 +===================== + +Supported chips: + * SMSC SCH5627 + Prefix: 'sch5627' + Addresses scanned: none, address read from Super I/O config space + Datasheet: Application Note available upon request + +Author: Hans de Goede + + +Description +----------- + +SMSC SCH5627 Super I/O chips include complete hardware monitoring +capabilities. They can monitor up to 5 voltages, 4 fans and 8 temperatures. + +The hardware monitoring part of the SMSC SCH5627 is accessed by talking +through an embedded microcontroller. An application note describing the +protocol for communicating with the microcontroller is available upon +request. Please mail me if you want a copy. diff --git a/MAINTAINERS b/MAINTAINERS index 666287a6f3da..b8a123f7e810 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5769,6 +5769,13 @@ S: Supported F: Documentation/hwmon/emc2103 F: drivers/hwmon/emc2103.c +SMSC SCH5627 HARDWARE MONITOR DRIVER +M: Hans de Goede +L: lm-sensors@lm-sensors.org +S: Supported +F: Documentation/hwmon/sch5627 +F: drivers/hwmon/sch5627.c + SMSC47B397 HARDWARE MONITOR DRIVER M: "Mark M. Hoffman" L: lm-sensors@lm-sensors.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 19d72bcf966a..6fad9f082f67 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -959,6 +959,15 @@ config SENSORS_SMSC47B397 This driver can also be built as a module. If so, the module will be called smsc47b397. +config SENSORS_SCH5627 + tristate "SMSC SCH5627" + help + If you say yes here you get support for the hardware monitoring + features of the SMSC SCH5627 Super-I/O chip. + + This driver can also be built as a module. If so, the module + will be called sch5627. + config SENSORS_ADS7828 tristate "Texas Instruments ADS7828" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index c068f82082cd..4d0122c52219 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -90,6 +90,7 @@ obj-$(CONFIG_SENSORS_PC87360) += pc87360.o obj-$(CONFIG_SENSORS_PC87427) += pc87427.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o obj-$(CONFIG_SENSORS_S3C) += s3c-hwmon.o +obj-$(CONFIG_SENSORS_SCH5627) += sch5627.o obj-$(CONFIG_SENSORS_SHT15) += sht15.o obj-$(CONFIG_SENSORS_SHT21) += sht21.o obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o diff --git a/drivers/hwmon/sch5627.c b/drivers/hwmon/sch5627.c new file mode 100644 index 000000000000..9a51dcca9b0d --- /dev/null +++ b/drivers/hwmon/sch5627.c @@ -0,0 +1,858 @@ +/*************************************************************************** + * Copyright (C) 2010-2011 Hans de Goede * + * * + * 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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "sch5627" +#define DEVNAME DRVNAME /* We only support one model */ + +#define SIO_SCH5627_EM_LD 0x0C /* Embedded Microcontroller LD */ +#define SIO_UNLOCK_KEY 0x55 /* Key to enable Super-I/O */ +#define SIO_LOCK_KEY 0xAA /* Key to disable Super-I/O */ + +#define SIO_REG_LDSEL 0x07 /* Logical device select */ +#define SIO_REG_DEVID 0x20 /* Device ID */ +#define SIO_REG_ENABLE 0x30 /* Logical device enable */ +#define SIO_REG_ADDR 0x66 /* Logical device address (2 bytes) */ + +#define SIO_SCH5627_ID 0xC6 /* Chipset ID */ + +#define REGION_LENGTH 9 + +#define SCH5627_HWMON_ID 0xa5 +#define SCH5627_COMPANY_ID 0x5c +#define SCH5627_PRIMARY_ID 0xa0 + +#define SCH5627_REG_BUILD_CODE 0x39 +#define SCH5627_REG_BUILD_ID 0x3a +#define SCH5627_REG_HWMON_ID 0x3c +#define SCH5627_REG_HWMON_REV 0x3d +#define SCH5627_REG_COMPANY_ID 0x3e +#define SCH5627_REG_PRIMARY_ID 0x3f +#define SCH5627_REG_CTRL 0x40 + +#define SCH5627_NO_TEMPS 8 +#define SCH5627_NO_FANS 4 +#define SCH5627_NO_IN 5 + +static const u16 SCH5627_REG_TEMP_MSB[SCH5627_NO_TEMPS] = { + 0x2B, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x180, 0x181 }; +static const u16 SCH5627_REG_TEMP_LSN[SCH5627_NO_TEMPS] = { + 0xE2, 0xE1, 0xE1, 0xE5, 0xE5, 0xE6, 0x182, 0x182 }; +static const u16 SCH5627_REG_TEMP_HIGH_NIBBLE[SCH5627_NO_TEMPS] = { + 0, 0, 1, 1, 0, 0, 0, 1 }; +static const u16 SCH5627_REG_TEMP_HIGH[SCH5627_NO_TEMPS] = { + 0x61, 0x57, 0x59, 0x5B, 0x5D, 0x5F, 0x184, 0x186 }; +static const u16 SCH5627_REG_TEMP_ABS[SCH5627_NO_TEMPS] = { + 0x9B, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x1A8, 0x1A9 }; + +static const u16 SCH5627_REG_FAN[SCH5627_NO_FANS] = { + 0x2C, 0x2E, 0x30, 0x32 }; +static const u16 SCH5627_REG_FAN_MIN[SCH5627_NO_FANS] = { + 0x62, 0x64, 0x66, 0x68 }; + +static const u16 SCH5627_REG_IN_MSB[SCH5627_NO_IN] = { + 0x22, 0x23, 0x24, 0x25, 0x189 }; +static const u16 SCH5627_REG_IN_LSN[SCH5627_NO_IN] = { + 0xE4, 0xE4, 0xE3, 0xE3, 0x18A }; +static const u16 SCH5627_REG_IN_HIGH_NIBBLE[SCH5627_NO_IN] = { + 1, 0, 1, 0, 1 }; +static const u16 SCH5627_REG_IN_FACTOR[SCH5627_NO_IN] = { + 10745, 3660, 9765, 10745, 3660 }; +static const char * const SCH5627_IN_LABELS[SCH5627_NO_IN] = { + "VCC", "VTT", "VBAT", "VTR", "V_IN" }; + +struct sch5627_data { + unsigned short addr; + struct device *hwmon_dev; + u8 temp_max[SCH5627_NO_TEMPS]; + u8 temp_crit[SCH5627_NO_TEMPS]; + u16 fan_min[SCH5627_NO_FANS]; + + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + u16 temp[SCH5627_NO_TEMPS]; + u16 fan[SCH5627_NO_FANS]; + u16 in[SCH5627_NO_IN]; +}; + +static struct platform_device *sch5627_pdev; + +/* Super I/O functions */ +static inline int superio_inb(int base, int reg) +{ + outb(reg, base); + return inb(base + 1); +} + +static inline int superio_enter(int base) +{ + /* Don't step on other drivers' I/O space by accident */ + if (!request_muxed_region(base, 2, DRVNAME)) { + pr_err("I/O address 0x%04x already in use\n", base); + return -EBUSY; + } + + outb(SIO_UNLOCK_KEY, base); + + return 0; +} + +static inline void superio_select(int base, int ld) +{ + outb(SIO_REG_LDSEL, base); + outb(ld, base + 1); +} + +static inline void superio_exit(int base) +{ + outb(SIO_LOCK_KEY, base); + release_region(base, 2); +} + +static int sch5627_read_virtual_reg(struct sch5627_data *data, u16 reg) +{ + u8 val; + int i; + /* + * According to SMSC for the commands we use the maximum time for + * the EM to respond is 15 ms, but testing shows in practice it + * responds within 15-32 reads, so we first busy poll, and if + * that fails sleep a bit and try again until we are way past + * the 15 ms maximum response time. + */ + const int max_busy_polls = 64; + const int max_lazy_polls = 32; + + /* (Optional) Write-Clear the EC to Host Mailbox Register */ + val = inb(data->addr + 1); + outb(val, data->addr + 1); + + /* Set Mailbox Address Pointer to first location in Region 1 */ + outb(0x00, data->addr + 2); + outb(0x80, data->addr + 3); + + /* Write Request Packet Header */ + outb(0x02, data->addr + 4); /* Access Type: VREG read */ + outb(0x01, data->addr + 5); /* # of Entries: 1 Byte (8-bit) */ + outb(0x04, data->addr + 2); /* Mailbox AP to first data entry loc. */ + + /* Write Address field */ + outb(reg & 0xff, data->addr + 6); + outb(reg >> 8, data->addr + 7); + + /* Execute the Random Access Command */ + outb(0x01, data->addr); /* Write 01h to the Host-to-EC register */ + + /* EM Interface Polling "Algorithm" */ + for (i = 0; i < max_busy_polls + max_lazy_polls; i++) { + if (i >= max_busy_polls) + msleep(1); + /* Read Interrupt source Register */ + val = inb(data->addr + 8); + /* Write Clear the interrupt source bits */ + if (val) + outb(val, data->addr + 8); + /* Command Completed ? */ + if (val & 0x01) + break; + } + if (i == max_busy_polls + max_lazy_polls) { + pr_err("Max retries exceeded reading virtual " + "register 0x%04hx (%d)\n", reg, 1); + return -EIO; + } + + /* + * According to SMSC we may need to retry this, but sofar I've always + * seen this succeed in 1 try. + */ + for (i = 0; i < max_busy_polls; i++) { + /* Read EC-to-Host Register */ + val = inb(data->addr + 1); + /* Command Completed ? */ + if (val == 0x01) + break; + + if (i == 0) + pr_warn("EC reports: 0x%02x reading virtual register " + "0x%04hx\n", (unsigned int)val, reg); + } + if (i == max_busy_polls) { + pr_err("Max retries exceeded reading virtual " + "register 0x%04hx (%d)\n", reg, 2); + return -EIO; + } + + /* + * According to the SMSC app note we should now do: + * + * Set Mailbox Address Pointer to first location in Region 1 * + * outb(0x00, data->addr + 2); + * outb(0x80, data->addr + 3); + * + * But if we do that things don't work, so let's not. + */ + + /* Read Data from Mailbox */ + return inb(data->addr + 4); +} + +static int sch5627_read_virtual_reg16(struct sch5627_data *data, u16 reg) +{ + int lsb, msb; + + /* Read LSB first, this will cause the matching MSB to be latched */ + lsb = sch5627_read_virtual_reg(data, reg); + if (lsb < 0) + return lsb; + + msb = sch5627_read_virtual_reg(data, reg + 1); + if (msb < 0) + return msb; + + return lsb | (msb << 8); +} + +static int sch5627_read_virtual_reg12(struct sch5627_data *data, u16 msb_reg, + u16 lsn_reg, int high_nibble) +{ + int msb, lsn; + + /* Read MSB first, this will cause the matching LSN to be latched */ + msb = sch5627_read_virtual_reg(data, msb_reg); + if (msb < 0) + return msb; + + lsn = sch5627_read_virtual_reg(data, lsn_reg); + if (lsn < 0) + return lsn; + + if (high_nibble) + return (msb << 4) | (lsn >> 4); + else + return (msb << 4) | (lsn & 0x0f); +} + +static struct sch5627_data *sch5627_update_device(struct device *dev) +{ + struct sch5627_data *data = dev_get_drvdata(dev); + struct sch5627_data *ret = data; + int i, val; + + mutex_lock(&data->update_lock); + + /* Cache the values for 1 second */ + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + for (i = 0; i < SCH5627_NO_TEMPS; i++) { + val = sch5627_read_virtual_reg12(data, + SCH5627_REG_TEMP_MSB[i], + SCH5627_REG_TEMP_LSN[i], + SCH5627_REG_TEMP_HIGH_NIBBLE[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp[i] = val; + } + + for (i = 0; i < SCH5627_NO_FANS; i++) { + val = sch5627_read_virtual_reg16(data, + SCH5627_REG_FAN[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->fan[i] = val; + } + + for (i = 0; i < SCH5627_NO_IN; i++) { + val = sch5627_read_virtual_reg12(data, + SCH5627_REG_IN_MSB[i], + SCH5627_REG_IN_LSN[i], + SCH5627_REG_IN_HIGH_NIBBLE[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->in[i] = val; + } + + data->last_updated = jiffies; + data->valid = 1; + } +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +static int __devinit sch5627_read_limits(struct sch5627_data *data) +{ + int i, val; + + for (i = 0; i < SCH5627_NO_TEMPS; i++) { + /* + * Note what SMSC calls ABS, is what lm_sensors calls max + * (aka high), and HIGH is what lm_sensors calls crit. + */ + val = sch5627_read_virtual_reg(data, SCH5627_REG_TEMP_ABS[i]); + if (val < 0) + return val; + data->temp_max[i] = val; + + val = sch5627_read_virtual_reg(data, SCH5627_REG_TEMP_HIGH[i]); + if (val < 0) + return val; + data->temp_crit[i] = val; + } + for (i = 0; i < SCH5627_NO_FANS; i++) { + val = sch5627_read_virtual_reg16(data, SCH5627_REG_FAN_MIN[i]); + if (val < 0) + return val; + data->fan_min[i] = val; + } + + return 0; +} + +static int reg_to_temp(u16 reg) +{ + return (reg * 625) / 10 - 64000; +} + +static int reg_to_temp_limit(u8 reg) +{ + return (reg - 64) * 1000; +} + +static int reg_to_rpm(u16 reg) +{ + if (reg == 0) + return -EIO; + if (reg == 0xffff) + return 0; + + return 5400540 / reg; +} + +static ssize_t show_name(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", DEVNAME); +} + +static ssize_t show_temp(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = sch5627_update_device(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = reg_to_temp(data->temp[attr->index]); + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_temp_fault(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = sch5627_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return snprintf(buf, PAGE_SIZE, "%d\n", data->temp[attr->index] == 0); +} + +static ssize_t show_temp_max(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = dev_get_drvdata(dev); + int val; + + val = reg_to_temp_limit(data->temp_max[attr->index]); + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_temp_crit(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = dev_get_drvdata(dev); + int val; + + val = reg_to_temp_limit(data->temp_crit[attr->index]); + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_fan(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = sch5627_update_device(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = reg_to_rpm(data->fan[attr->index]); + if (val < 0) + return val; + + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_fan_fault(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = sch5627_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return snprintf(buf, PAGE_SIZE, "%d\n", + data->fan[attr->index] == 0xffff); +} + +static ssize_t show_fan_min(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = dev_get_drvdata(dev); + int val = reg_to_rpm(data->fan_min[attr->index]); + if (val < 0) + return val; + + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_in(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = sch5627_update_device(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = DIV_ROUND_CLOSEST( + data->in[attr->index] * SCH5627_REG_IN_FACTOR[attr->index], + 10000); + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_in_label(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return snprintf(buf, PAGE_SIZE, "%s\n", + SCH5627_IN_LABELS[attr->index]); +} + +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_temp, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_input, S_IRUGO, show_temp, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO, show_temp, NULL, 7); +static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_temp_fault, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_fault, S_IRUGO, show_temp_fault, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_fault, S_IRUGO, show_temp_fault, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_fault, S_IRUGO, show_temp_fault, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_fault, S_IRUGO, show_temp_fault, NULL, 7); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_temp_max, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO, show_temp_max, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_max, S_IRUGO, show_temp_max, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_max, S_IRUGO, show_temp_max, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_max, S_IRUGO, show_temp_max, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_max, S_IRUGO, show_temp_max, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_max, S_IRUGO, show_temp_max, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_max, S_IRUGO, show_temp_max, NULL, 7); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp_crit, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO, show_temp_crit, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_crit, S_IRUGO, show_temp_crit, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_crit, S_IRUGO, show_temp_crit, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_crit, S_IRUGO, show_temp_crit, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_crit, S_IRUGO, show_temp_crit, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_crit, S_IRUGO, show_temp_crit, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_crit, S_IRUGO, show_temp_crit, NULL, 7); + +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3); +static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, show_fan_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_fault, S_IRUGO, show_fan_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_fault, S_IRUGO, show_fan_fault, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_fault, S_IRUGO, show_fan_fault, NULL, 3); +static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO, show_fan_min, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_min, S_IRUGO, show_fan_min, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_min, S_IRUGO, show_fan_min, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_min, S_IRUGO, show_fan_min, NULL, 3); + +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_in, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_in, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_in, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_in, NULL, 4); +static SENSOR_DEVICE_ATTR(in0_label, S_IRUGO, show_in_label, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_label, S_IRUGO, show_in_label, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_label, S_IRUGO, show_in_label, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_label, S_IRUGO, show_in_label, NULL, 3); + +static struct attribute *sch5627_attributes[] = { + &dev_attr_name.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp6_input.dev_attr.attr, + &sensor_dev_attr_temp7_input.dev_attr.attr, + &sensor_dev_attr_temp8_input.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + &sensor_dev_attr_temp4_fault.dev_attr.attr, + &sensor_dev_attr_temp5_fault.dev_attr.attr, + &sensor_dev_attr_temp6_fault.dev_attr.attr, + &sensor_dev_attr_temp7_fault.dev_attr.attr, + &sensor_dev_attr_temp8_fault.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp5_max.dev_attr.attr, + &sensor_dev_attr_temp6_max.dev_attr.attr, + &sensor_dev_attr_temp7_max.dev_attr.attr, + &sensor_dev_attr_temp8_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp4_crit.dev_attr.attr, + &sensor_dev_attr_temp5_crit.dev_attr.attr, + &sensor_dev_attr_temp6_crit.dev_attr.attr, + &sensor_dev_attr_temp7_crit.dev_attr.attr, + &sensor_dev_attr_temp8_crit.dev_attr.attr, + + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan1_fault.dev_attr.attr, + &sensor_dev_attr_fan2_fault.dev_attr.attr, + &sensor_dev_attr_fan3_fault.dev_attr.attr, + &sensor_dev_attr_fan4_fault.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan4_min.dev_attr.attr, + + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in0_label.dev_attr.attr, + &sensor_dev_attr_in1_label.dev_attr.attr, + &sensor_dev_attr_in2_label.dev_attr.attr, + &sensor_dev_attr_in3_label.dev_attr.attr, + /* No in4_label as in4 is a generic input pin */ + + NULL +}; + +static const struct attribute_group sch5627_group = { + .attrs = sch5627_attributes, +}; + +static int sch5627_remove(struct platform_device *pdev) +{ + struct sch5627_data *data = platform_get_drvdata(pdev); + + if (data->hwmon_dev) + hwmon_device_unregister(data->hwmon_dev); + + sysfs_remove_group(&pdev->dev.kobj, &sch5627_group); + platform_set_drvdata(pdev, NULL); + kfree(data); + + return 0; +} + +static int __devinit sch5627_probe(struct platform_device *pdev) +{ + struct sch5627_data *data; + int err, build_code, build_id, hwmon_rev, val; + + data = kzalloc(sizeof(struct sch5627_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->addr = platform_get_resource(pdev, IORESOURCE_IO, 0)->start; + mutex_init(&data->update_lock); + platform_set_drvdata(pdev, data); + + val = sch5627_read_virtual_reg(data, SCH5627_REG_HWMON_ID); + if (val < 0) { + err = val; + goto error; + } + if (val != SCH5627_HWMON_ID) { + pr_err("invalid %s id: 0x%02X (expected 0x%02X)\n", "hwmon", + val, SCH5627_HWMON_ID); + err = -ENODEV; + goto error; + } + + val = sch5627_read_virtual_reg(data, SCH5627_REG_COMPANY_ID); + if (val < 0) { + err = val; + goto error; + } + if (val != SCH5627_COMPANY_ID) { + pr_err("invalid %s id: 0x%02X (expected 0x%02X)\n", "company", + val, SCH5627_COMPANY_ID); + err = -ENODEV; + goto error; + } + + val = sch5627_read_virtual_reg(data, SCH5627_REG_PRIMARY_ID); + if (val < 0) { + err = val; + goto error; + } + if (val != SCH5627_PRIMARY_ID) { + pr_err("invalid %s id: 0x%02X (expected 0x%02X)\n", "primary", + val, SCH5627_PRIMARY_ID); + err = -ENODEV; + goto error; + } + + build_code = sch5627_read_virtual_reg(data, SCH5627_REG_BUILD_CODE); + if (build_code < 0) { + err = build_code; + goto error; + } + + build_id = sch5627_read_virtual_reg16(data, SCH5627_REG_BUILD_ID); + if (build_id < 0) { + err = build_id; + goto error; + } + + hwmon_rev = sch5627_read_virtual_reg(data, SCH5627_REG_HWMON_REV); + if (hwmon_rev < 0) { + err = hwmon_rev; + goto error; + } + + val = sch5627_read_virtual_reg(data, SCH5627_REG_CTRL); + if (val < 0) { + err = val; + goto error; + } + if (!(val & 0x01)) { + pr_err("hardware monitoring not enabled\n"); + err = -ENODEV; + goto error; + } + + /* + * Read limits, we do this only once as reading a register on + * the sch5627 is quite expensive (and they don't change). + */ + err = sch5627_read_limits(data); + if (err) + goto error; + + pr_info("firmware build: code 0x%02X, id 0x%04X, hwmon: rev 0x%02X\n", + build_code, build_id, hwmon_rev); + + /* Register sysfs interface files */ + err = sysfs_create_group(&pdev->dev.kobj, &sch5627_group); + if (err) + goto error; + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + data->hwmon_dev = NULL; + goto error; + } + + return 0; + +error: + sch5627_remove(pdev); + return err; +} + +static int __init sch5627_find(int sioaddr, unsigned short *address) +{ + u8 devid; + int err = superio_enter(sioaddr); + if (err) + return err; + + devid = superio_inb(sioaddr, SIO_REG_DEVID); + if (devid != SIO_SCH5627_ID) { + pr_debug("Unsupported device id: 0x%02x\n", + (unsigned int)devid); + err = -ENODEV; + goto exit; + } + + superio_select(sioaddr, SIO_SCH5627_EM_LD); + + if (!(superio_inb(sioaddr, SIO_REG_ENABLE) & 0x01)) { + pr_warn("Device not activated\n"); + err = -ENODEV; + goto exit; + } + + /* + * Warning the order of the low / high byte is the other way around + * as on most other superio devices!! + */ + *address = superio_inb(sioaddr, SIO_REG_ADDR) | + superio_inb(sioaddr, SIO_REG_ADDR + 1) << 8; + if (*address == 0) { + pr_warn("Base address not set\n"); + err = -ENODEV; + goto exit; + } + + pr_info("Found %s chip at %#hx\n", DEVNAME, *address); +exit: + superio_exit(sioaddr); + return err; +} + +static int __init sch5627_device_add(unsigned short address) +{ + struct resource res = { + .start = address, + .end = address + REGION_LENGTH - 1, + .flags = IORESOURCE_IO, + }; + int err; + + sch5627_pdev = platform_device_alloc(DRVNAME, address); + if (!sch5627_pdev) + return -ENOMEM; + + res.name = sch5627_pdev->name; + err = acpi_check_resource_conflict(&res); + if (err) + goto exit_device_put; + + err = platform_device_add_resources(sch5627_pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed\n"); + goto exit_device_put; + } + + err = platform_device_add(sch5627_pdev); + if (err) { + pr_err("Device addition failed\n"); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(sch5627_pdev); + + return err; +} + +static struct platform_driver sch5627_driver = { + .driver = { + .owner = THIS_MODULE, + .name = DRVNAME, + }, + .probe = sch5627_probe, + .remove = sch5627_remove, +}; + +static int __init sch5627_init(void) +{ + int err = -ENODEV; + unsigned short address; + + if (sch5627_find(0x4e, &address) && sch5627_find(0x2e, &address)) + goto exit; + + err = platform_driver_register(&sch5627_driver); + if (err) + goto exit; + + err = sch5627_device_add(address); + if (err) + goto exit_driver; + + return 0; + +exit_driver: + platform_driver_unregister(&sch5627_driver); +exit: + return err; +} + +static void __exit sch5627_exit(void) +{ + platform_device_unregister(sch5627_pdev); + platform_driver_unregister(&sch5627_driver); +} + +MODULE_DESCRIPTION("SMSC SCH5627 Hardware Monitoring Driver"); +MODULE_AUTHOR("Hans de Goede (hdegoede@redhat.com)"); +MODULE_LICENSE("GPL"); + +module_init(sch5627_init); +module_exit(sch5627_exit); From 8c22a8f57516275afcd81c84f3724ac08cf6aa7b Mon Sep 17 00:00:00 2001 From: Dirk Eibach Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: [PATCH 12/14] hwmon: Add support for Texas Instruments ADS1015 Signed-off-by: Dirk Eibach Signed-off-by: Jean Delvare --- .../devicetree/bindings/hwmon/ads1015.txt | 29 ++ Documentation/hwmon/ads1015 | 67 +++++ MAINTAINERS | 8 + drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/ads1015.c | 282 ++++++++++++++++++ include/linux/i2c/ads1015.h | 28 ++ 7 files changed, 425 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/ads1015.txt create mode 100644 Documentation/hwmon/ads1015 create mode 100644 drivers/hwmon/ads1015.c create mode 100644 include/linux/i2c/ads1015.h diff --git a/Documentation/devicetree/bindings/hwmon/ads1015.txt b/Documentation/devicetree/bindings/hwmon/ads1015.txt new file mode 100644 index 000000000000..0f30616384c5 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/ads1015.txt @@ -0,0 +1,29 @@ +ADS1015 (I2C) + +This device is a 12-bit A-D converter with 4 inputs. + +The inputs can be used single ended or in certain differential combinations. + +For configuration all possible combinations are mapped to 8 channels: +0: Voltage over AIN0 and AIN1. +1: Voltage over AIN0 and AIN3. +2: Voltage over AIN1 and AIN3. +3: Voltage over AIN2 and AIN3. +4: Voltage over AIN0 and GND. +5: Voltage over AIN1 and GND. +6: Voltage over AIN2 and GND. +7: Voltage over AIN3 and GND. + +Optional properties: + + - exported-channels : exported_channels is a bitmask that specifies which + channels should be accessable by the user. + +Example: +ads1015@49 { + compatible = "ti,ads1015"; + reg = <0x49>; + exported-channels = <0x14>; +}; + +In this example only channel 2 and 4 would be accessable by the user. diff --git a/Documentation/hwmon/ads1015 b/Documentation/hwmon/ads1015 new file mode 100644 index 000000000000..56ee7977b1a8 --- /dev/null +++ b/Documentation/hwmon/ads1015 @@ -0,0 +1,67 @@ +Kernel driver ads1015 +===================== + +Supported chips: + * Texas Instruments ADS1015 + Prefix: 'ads1015' + Datasheet: Publicly available at the Texas Instruments website : + http://focus.ti.com/lit/ds/symlink/ads1015.pdf + +Authors: + Dirk Eibach, Guntermann & Drunck GmbH + +Description +----------- + +This driver implements support for the Texas Instruments ADS1015. + +This device is a 12-bit A-D converter with 4 inputs. + +The inputs can be used single ended or in certain differential combinations. + +The inputs can be exported to 8 sysfs input files in0_input - in7_input: +in0: Voltage over AIN0 and AIN1. +in1: Voltage over AIN0 and AIN3. +in2: Voltage over AIN1 and AIN3. +in3: Voltage over AIN2 and AIN3. +in4: Voltage over AIN0 and GND. +in5: Voltage over AIN1 and GND. +in6: Voltage over AIN2 and GND. +in7: Voltage over AIN3 and GND. + +Which inputs are exported can be configured using platform data or devicetree. + +By default all inputs are exported. + +Platform Data +------------- + +In linux/i2c/ads1015.h platform data is defined as: + +struct ads1015_platform_data { + unsigned int exported_channels; +}; + +exported_channels is a bitmask that specifies which inputs should be exported. + +Example: +struct ads1015_platform_data data = { + .exported_channels = (1 << 2) | (1 << 4) +}; + +In this case only in2_input and in4_input would be created. + +Devicetree +---------- + +The ads1015 node may have an "exported-channels" property. +exported_channels is a bitmask that specifies which inputs should be exported. + +Example: +ads1015@49 { + compatible = "ti,ads1015"; + reg = <0x49>; + exported-channels = < 0x14 >; +}; + +In this case only in2_input and in4_input would be created. diff --git a/MAINTAINERS b/MAINTAINERS index b8a123f7e810..38077a656820 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -365,6 +365,14 @@ W: http://wiki-analog.com/ADP8860 S: Supported F: drivers/video/backlight/adp8860_bl.c +ADS1015 HARDWARE MONITOR DRIVER +M: Dirk Eibach +L: lm-sensors@lm-sensors.org +S: Maintained +F: Documentation/hwmon/ads1015 +F: drivers/hwmon/ads1015.c +F: include/linux/i2c/ads1015.h + ADT746X FAN DRIVER M: Colin Leroy S: Maintained diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 6fad9f082f67..e4bd13b3cd8b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -968,6 +968,16 @@ config SENSORS_SCH5627 This driver can also be built as a module. If so, the module will be called sch5627. +config SENSORS_ADS1015 + tristate "Texas Instruments ADS1015" + depends on I2C + help + If you say yes here you get support for Texas Instruments ADS1015 + 12-bit 4-input ADC device. + + This driver can also be built as a module. If so, the module + will be called ads1015. + config SENSORS_ADS7828 tristate "Texas Instruments ADS7828" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 4d0122c52219..54ca5939d028 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o obj-$(CONFIG_SENSORS_ADM1029) += adm1029.o obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o +obj-$(CONFIG_SENSORS_ADS1015) += ads1015.o obj-$(CONFIG_SENSORS_ADS7828) += ads7828.o obj-$(CONFIG_SENSORS_ADS7871) += ads7871.o obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o diff --git a/drivers/hwmon/ads1015.c b/drivers/hwmon/ads1015.c new file mode 100644 index 000000000000..9e1585c3152e --- /dev/null +++ b/drivers/hwmon/ads1015.c @@ -0,0 +1,282 @@ +/* + * ads1015.c - lm_sensors driver for ads1015 12-bit 4-input ADC + * (C) Copyright 2010 + * Dirk Eibach, Guntermann & Drunck GmbH + * + * Based on the ads7828 driver by Steve Hardy. + * + * Datasheet available at: http://focus.ti.com/lit/ds/symlink/ads1015.pdf + * + * 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 + +#include + +/* ADS1015 registers */ +enum { + ADS1015_CONVERSION = 0, + ADS1015_CONFIG = 1, +}; + +/* PGA fullscale voltages in mV */ +static const unsigned int fullscale_table[8] = { + 6144, 4096, 2048, 1024, 512, 256, 256, 256 }; + +#define ADS1015_CONFIG_CHANNELS 8 +#define ADS1015_DEFAULT_CHANNELS 0xff + +struct ads1015_data { + struct device *hwmon_dev; + struct mutex update_lock; /* mutex protect updates */ + struct attribute *attr_table[ADS1015_CONFIG_CHANNELS + 1]; + struct attribute_group attr_group; +}; + +static s32 ads1015_read_reg(struct i2c_client *client, unsigned int reg) +{ + s32 data = i2c_smbus_read_word_data(client, reg); + + return (data < 0) ? data : swab16(data); +} + +static s32 ads1015_write_reg(struct i2c_client *client, unsigned int reg, + u16 val) +{ + return i2c_smbus_write_word_data(client, reg, swab16(val)); +} + +static int ads1015_read_value(struct i2c_client *client, unsigned int channel, + int *value) +{ + u16 config; + s16 conversion; + unsigned int pga; + int fullscale; + unsigned int k; + struct ads1015_data *data = i2c_get_clientdata(client); + int res; + + mutex_lock(&data->update_lock); + + /* get fullscale voltage */ + res = ads1015_read_reg(client, ADS1015_CONFIG); + if (res < 0) + goto err_unlock; + config = res; + pga = (config >> 9) & 0x0007; + fullscale = fullscale_table[pga]; + + /* set channel and start single conversion */ + config &= ~(0x0007 << 12); + config |= (1 << 15) | (1 << 8) | (channel & 0x0007) << 12; + + /* wait until conversion finished */ + res = ads1015_write_reg(client, ADS1015_CONFIG, config); + if (res < 0) + goto err_unlock; + for (k = 0; k < 5; ++k) { + msleep(1); + res = ads1015_read_reg(client, ADS1015_CONFIG); + if (res < 0) + goto err_unlock; + config = res; + if (config & (1 << 15)) + break; + } + if (k == 5) { + res = -EIO; + goto err_unlock; + } + + res = ads1015_read_reg(client, ADS1015_CONVERSION); + if (res < 0) + goto err_unlock; + conversion = res; + + mutex_unlock(&data->update_lock); + + *value = DIV_ROUND_CLOSEST(conversion * fullscale, 0x7ff0); + + return 0; + +err_unlock: + mutex_unlock(&data->update_lock); + return res; +} + +/* sysfs callback function */ +static ssize_t show_in(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct i2c_client *client = to_i2c_client(dev); + int in; + int res; + + res = ads1015_read_value(client, attr->index, &in); + + return (res < 0) ? res : sprintf(buf, "%d\n", in); +} + +#define in_reg(offset)\ +static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, show_in,\ + NULL, offset) + +in_reg(0); +in_reg(1); +in_reg(2); +in_reg(3); +in_reg(4); +in_reg(5); +in_reg(6); +in_reg(7); + +static struct attribute *all_attributes[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, +}; + +/* + * Driver interface + */ + +static int ads1015_remove(struct i2c_client *client) +{ + struct ads1015_data *data = i2c_get_clientdata(client); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &data->attr_group); + kfree(data); + return 0; +} + +static unsigned int ads1015_get_exported_channels(struct i2c_client *client) +{ + struct ads1015_platform_data *pdata = dev_get_platdata(&client->dev); +#ifdef CONFIG_OF + struct device_node *np = client->dev.of_node; + const __be32 *of_channels; + int of_channels_size; +#endif + + /* prefer platform data */ + if (pdata) + return pdata->exported_channels; + +#ifdef CONFIG_OF + /* fallback on OF */ + of_channels = of_get_property(np, "exported-channels", + &of_channels_size); + if (of_channels && (of_channels_size == sizeof(*of_channels))) + return be32_to_cpup(of_channels); +#endif + + /* fallback on default configuration */ + return ADS1015_DEFAULT_CHANNELS; +} + +static int ads1015_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ads1015_data *data; + int err; + unsigned int exported_channels; + unsigned int k; + unsigned int n = 0; + + data = kzalloc(sizeof(struct ads1015_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* build sysfs attribute group */ + data->attr_group.attrs = data->attr_table; + exported_channels = ads1015_get_exported_channels(client); + for (k = 0; k < ADS1015_CONFIG_CHANNELS; ++k) { + if (!(exported_channels & (1<attr_table[n++] = all_attributes[k]; + } + err = sysfs_create_group(&client->dev.kobj, &data->attr_group); + if (err) + goto exit_free; + + 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, &data->attr_group); +exit_free: + kfree(data); +exit: + return err; +} + +static const struct i2c_device_id ads1015_id[] = { + { "ads1015", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ads1015_id); + +static struct i2c_driver ads1015_driver = { + .driver = { + .name = "ads1015", + }, + .probe = ads1015_probe, + .remove = ads1015_remove, + .id_table = ads1015_id, +}; + +static int __init sensors_ads1015_init(void) +{ + return i2c_add_driver(&ads1015_driver); +} + +static void __exit sensors_ads1015_exit(void) +{ + i2c_del_driver(&ads1015_driver); +} + +MODULE_AUTHOR("Dirk Eibach "); +MODULE_DESCRIPTION("ADS1015 driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_ads1015_init); +module_exit(sensors_ads1015_exit); diff --git a/include/linux/i2c/ads1015.h b/include/linux/i2c/ads1015.h new file mode 100644 index 000000000000..8541c6acfafd --- /dev/null +++ b/include/linux/i2c/ads1015.h @@ -0,0 +1,28 @@ +/* + * Platform Data for ADS1015 12-bit 4-input ADC + * (C) Copyright 2010 + * Dirk Eibach, Guntermann & Drunck GmbH + * + * 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. + */ + +#ifndef LINUX_ADS1015_H +#define LINUX_ADS1015_H + +struct ads1015_platform_data { + unsigned int exported_channels; +}; + +#endif /* LINUX_ADS1015_H */ From fdf241a8ed93236915c70717a4b6dfb856274496 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:37 +0100 Subject: [PATCH 13/14] hwmon: (ads1015) Drop dynamic attribute group It is cheaper to handle attributes individually. Signed-off-by: Jean Delvare Acked-by: Dirk Eibach --- drivers/hwmon/ads1015.c | 50 +++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/drivers/hwmon/ads1015.c b/drivers/hwmon/ads1015.c index 9e1585c3152e..fa02f20b79ff 100644 --- a/drivers/hwmon/ads1015.c +++ b/drivers/hwmon/ads1015.c @@ -51,8 +51,6 @@ static const unsigned int fullscale_table[8] = { struct ads1015_data { struct device *hwmon_dev; struct mutex update_lock; /* mutex protect updates */ - struct attribute *attr_table[ADS1015_CONFIG_CHANNELS + 1]; - struct attribute_group attr_group; }; static s32 ads1015_read_reg(struct i2c_client *client, unsigned int reg) @@ -141,28 +139,15 @@ static ssize_t show_in(struct device *dev, struct device_attribute *da, return (res < 0) ? res : sprintf(buf, "%d\n", in); } -#define in_reg(offset)\ -static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, show_in,\ - NULL, offset) - -in_reg(0); -in_reg(1); -in_reg(2); -in_reg(3); -in_reg(4); -in_reg(5); -in_reg(6); -in_reg(7); - -static struct attribute *all_attributes[] = { - &sensor_dev_attr_in0_input.dev_attr.attr, - &sensor_dev_attr_in1_input.dev_attr.attr, - &sensor_dev_attr_in2_input.dev_attr.attr, - &sensor_dev_attr_in3_input.dev_attr.attr, - &sensor_dev_attr_in4_input.dev_attr.attr, - &sensor_dev_attr_in5_input.dev_attr.attr, - &sensor_dev_attr_in6_input.dev_attr.attr, - &sensor_dev_attr_in7_input.dev_attr.attr, +static const struct sensor_device_attribute ads1015_in[] = { + SENSOR_ATTR(in0_input, S_IRUGO, show_in, NULL, 0), + SENSOR_ATTR(in1_input, S_IRUGO, show_in, NULL, 1), + SENSOR_ATTR(in2_input, S_IRUGO, show_in, NULL, 2), + SENSOR_ATTR(in3_input, S_IRUGO, show_in, NULL, 3), + SENSOR_ATTR(in4_input, S_IRUGO, show_in, NULL, 4), + SENSOR_ATTR(in5_input, S_IRUGO, show_in, NULL, 5), + SENSOR_ATTR(in6_input, S_IRUGO, show_in, NULL, 6), + SENSOR_ATTR(in7_input, S_IRUGO, show_in, NULL, 7), }; /* @@ -172,8 +157,11 @@ static struct attribute *all_attributes[] = { static int ads1015_remove(struct i2c_client *client) { struct ads1015_data *data = i2c_get_clientdata(client); + int k; + hwmon_device_unregister(data->hwmon_dev); - sysfs_remove_group(&client->dev.kobj, &data->attr_group); + for (k = 0; k < ADS1015_CONFIG_CHANNELS; ++k) + device_remove_file(&client->dev, &ads1015_in[k].dev_attr); kfree(data); return 0; } @@ -210,7 +198,6 @@ static int ads1015_probe(struct i2c_client *client, int err; unsigned int exported_channels; unsigned int k; - unsigned int n = 0; data = kzalloc(sizeof(struct ads1015_data), GFP_KERNEL); if (!data) { @@ -222,16 +209,14 @@ static int ads1015_probe(struct i2c_client *client, mutex_init(&data->update_lock); /* build sysfs attribute group */ - data->attr_group.attrs = data->attr_table; exported_channels = ads1015_get_exported_channels(client); for (k = 0; k < ADS1015_CONFIG_CHANNELS; ++k) { if (!(exported_channels & (1<attr_table[n++] = all_attributes[k]; + err = device_create_file(&client->dev, &ads1015_in[k].dev_attr); + if (err) + goto exit_free; } - err = sysfs_create_group(&client->dev.kobj, &data->attr_group); - if (err) - goto exit_free; data->hwmon_dev = hwmon_device_register(&client->dev); if (IS_ERR(data->hwmon_dev)) { @@ -242,7 +227,8 @@ static int ads1015_probe(struct i2c_client *client, return 0; exit_remove: - sysfs_remove_group(&client->dev.kobj, &data->attr_group); + for (k = 0; k < ADS1015_CONFIG_CHANNELS; ++k) + device_remove_file(&client->dev, &ads1015_in[k].dev_attr); exit_free: kfree(data); exit: From c0046867f34bb81ec3f237ebbc5241ae678b8379 Mon Sep 17 00:00:00 2001 From: Dirk Eibach Date: Mon, 21 Mar 2011 17:59:37 +0100 Subject: [PATCH 14/14] hwmon: (ads1015) Make gain and datarate configurable Configuration for ads1015 gain and datarate is possible via devicetree or platform data. This is a followup patch to previous ads1015 patches on Jean Delvares tree. Signed-off-by: Dirk Eibach Signed-off-by: Jean Delvare --- .../devicetree/bindings/hwmon/ads1015.txt | 80 +++++++--- Documentation/hwmon/ads1015 | 49 +++--- drivers/hwmon/ads1015.c | 149 +++++++++++++----- include/linux/i2c/ads1015.h | 10 +- 4 files changed, 207 insertions(+), 81 deletions(-) diff --git a/Documentation/devicetree/bindings/hwmon/ads1015.txt b/Documentation/devicetree/bindings/hwmon/ads1015.txt index 0f30616384c5..918a507d1159 100644 --- a/Documentation/devicetree/bindings/hwmon/ads1015.txt +++ b/Documentation/devicetree/bindings/hwmon/ads1015.txt @@ -5,25 +5,69 @@ This device is a 12-bit A-D converter with 4 inputs. The inputs can be used single ended or in certain differential combinations. For configuration all possible combinations are mapped to 8 channels: -0: Voltage over AIN0 and AIN1. -1: Voltage over AIN0 and AIN3. -2: Voltage over AIN1 and AIN3. -3: Voltage over AIN2 and AIN3. -4: Voltage over AIN0 and GND. -5: Voltage over AIN1 and GND. -6: Voltage over AIN2 and GND. -7: Voltage over AIN3 and GND. + 0: Voltage over AIN0 and AIN1. + 1: Voltage over AIN0 and AIN3. + 2: Voltage over AIN1 and AIN3. + 3: Voltage over AIN2 and AIN3. + 4: Voltage over AIN0 and GND. + 5: Voltage over AIN1 and GND. + 6: Voltage over AIN2 and GND. + 7: Voltage over AIN3 and GND. -Optional properties: +Each channel can be configured individually: + - pga is the programmable gain amplifier (values are full scale) + 0: +/- 6.144 V + 1: +/- 4.096 V + 2: +/- 2.048 V (default) + 3: +/- 1.024 V + 4: +/- 0.512 V + 5: +/- 0.256 V + - data_rate in samples per second + 0: 128 + 1: 250 + 2: 490 + 3: 920 + 4: 1600 (default) + 5: 2400 + 6: 3300 - - exported-channels : exported_channels is a bitmask that specifies which - channels should be accessable by the user. +1) The /ads1015 node -Example: -ads1015@49 { - compatible = "ti,ads1015"; - reg = <0x49>; - exported-channels = <0x14>; -}; + Required properties: -In this example only channel 2 and 4 would be accessable by the user. + - compatible : must be "ti,ads1015" + - reg : I2C bus address of the device + - #address-cells : must be <1> + - #size-cells : must be <0> + + The node contains child nodes for each channel that the platform uses. + + Example ADS1015 node: + + ads1015@49 { + compatible = "ti,ads1015"; + reg = <0x49>; + #address-cells = <1>; + #size-cells = <0>; + + [ child node definitions... ] + } + +2) channel nodes + + Required properties: + + - reg : the channel number + + Optional properties: + + - ti,gain : the programmable gain amplifier setting + - ti,datarate : the converter data rate + + Example ADS1015 channel node: + + channel@4 { + reg = <4>; + ti,gain = <3>; + ti,datarate = <5>; + }; diff --git a/Documentation/hwmon/ads1015 b/Documentation/hwmon/ads1015 index 56ee7977b1a8..f6fe9c203733 100644 --- a/Documentation/hwmon/ads1015 +++ b/Documentation/hwmon/ads1015 @@ -19,7 +19,7 @@ This device is a 12-bit A-D converter with 4 inputs. The inputs can be used single ended or in certain differential combinations. -The inputs can be exported to 8 sysfs input files in0_input - in7_input: +The inputs can be made available by 8 sysfs input files in0_input - in7_input: in0: Voltage over AIN0 and AIN1. in1: Voltage over AIN0 and AIN3. in2: Voltage over AIN1 and AIN3. @@ -29,39 +29,44 @@ in5: Voltage over AIN1 and GND. in6: Voltage over AIN2 and GND. in7: Voltage over AIN3 and GND. -Which inputs are exported can be configured using platform data or devicetree. +Which inputs are available can be configured using platform data or devicetree. By default all inputs are exported. Platform Data ------------- -In linux/i2c/ads1015.h platform data is defined as: - -struct ads1015_platform_data { - unsigned int exported_channels; -}; - -exported_channels is a bitmask that specifies which inputs should be exported. +In linux/i2c/ads1015.h platform data is defined, channel_data contains +configuration data for the used input combinations: +- pga is the programmable gain amplifier (values are full scale) + 0: +/- 6.144 V + 1: +/- 4.096 V + 2: +/- 2.048 V + 3: +/- 1.024 V + 4: +/- 0.512 V + 5: +/- 0.256 V +- data_rate in samples per second + 0: 128 + 1: 250 + 2: 490 + 3: 920 + 4: 1600 + 5: 2400 + 6: 3300 Example: struct ads1015_platform_data data = { - .exported_channels = (1 << 2) | (1 << 4) + .channel_data = { + [2] = { .enabled = true, .pga = 1, .data_rate = 0 }, + [4] = { .enabled = true, .pga = 4, .data_rate = 5 }, + } }; -In this case only in2_input and in4_input would be created. +In this case only in2_input (FS +/- 4.096 V, 128 SPS) and in4_input +(FS +/- 0.512 V, 2400 SPS) would be created. Devicetree ---------- -The ads1015 node may have an "exported-channels" property. -exported_channels is a bitmask that specifies which inputs should be exported. - -Example: -ads1015@49 { - compatible = "ti,ads1015"; - reg = <0x49>; - exported-channels = < 0x14 >; -}; - -In this case only in2_input and in4_input would be created. +Configuration is also possible via devicetree: +Documentation/devicetree/bindings/hwmon/ads1015.txt diff --git a/drivers/hwmon/ads1015.c b/drivers/hwmon/ads1015.c index fa02f20b79ff..e9beeda4cbe5 100644 --- a/drivers/hwmon/ads1015.c +++ b/drivers/hwmon/ads1015.c @@ -45,12 +45,18 @@ enum { static const unsigned int fullscale_table[8] = { 6144, 4096, 2048, 1024, 512, 256, 256, 256 }; -#define ADS1015_CONFIG_CHANNELS 8 +/* Data rates in samples per second */ +static const unsigned int data_rate_table[8] = { + 128, 250, 490, 920, 1600, 2400, 3300, 3300 }; + #define ADS1015_DEFAULT_CHANNELS 0xff +#define ADS1015_DEFAULT_PGA 2 +#define ADS1015_DEFAULT_DATA_RATE 4 struct ads1015_data { struct device *hwmon_dev; struct mutex update_lock; /* mutex protect updates */ + struct ads1015_channel_data channel_data[ADS1015_CHANNELS]; }; static s32 ads1015_read_reg(struct i2c_client *client, unsigned int reg) @@ -71,40 +77,42 @@ static int ads1015_read_value(struct i2c_client *client, unsigned int channel, { u16 config; s16 conversion; - unsigned int pga; - int fullscale; - unsigned int k; struct ads1015_data *data = i2c_get_clientdata(client); + unsigned int pga = data->channel_data[channel].pga; + int fullscale; + unsigned int data_rate = data->channel_data[channel].data_rate; + unsigned int conversion_time_ms; int res; mutex_lock(&data->update_lock); - /* get fullscale voltage */ + /* get channel parameters */ res = ads1015_read_reg(client, ADS1015_CONFIG); if (res < 0) goto err_unlock; config = res; - pga = (config >> 9) & 0x0007; fullscale = fullscale_table[pga]; + conversion_time_ms = DIV_ROUND_UP(1000, data_rate_table[data_rate]); - /* set channel and start single conversion */ - config &= ~(0x0007 << 12); - config |= (1 << 15) | (1 << 8) | (channel & 0x0007) << 12; + /* setup and start single conversion */ + config &= 0x001f; + config |= (1 << 15) | (1 << 8); + config |= (channel & 0x0007) << 12; + config |= (pga & 0x0007) << 9; + config |= (data_rate & 0x0007) << 5; - /* wait until conversion finished */ res = ads1015_write_reg(client, ADS1015_CONFIG, config); if (res < 0) goto err_unlock; - for (k = 0; k < 5; ++k) { - msleep(1); - res = ads1015_read_reg(client, ADS1015_CONFIG); - if (res < 0) - goto err_unlock; - config = res; - if (config & (1 << 15)) - break; - } - if (k == 5) { + + /* wait until conversion finished */ + msleep(conversion_time_ms); + res = ads1015_read_reg(client, ADS1015_CONFIG); + if (res < 0) + goto err_unlock; + config = res; + if (!(config & (1 << 15))) { + /* conversion not finished in time */ res = -EIO; goto err_unlock; } @@ -160,35 +168,97 @@ static int ads1015_remove(struct i2c_client *client) int k; hwmon_device_unregister(data->hwmon_dev); - for (k = 0; k < ADS1015_CONFIG_CHANNELS; ++k) + for (k = 0; k < ADS1015_CHANNELS; ++k) device_remove_file(&client->dev, &ads1015_in[k].dev_attr); kfree(data); return 0; } -static unsigned int ads1015_get_exported_channels(struct i2c_client *client) -{ - struct ads1015_platform_data *pdata = dev_get_platdata(&client->dev); #ifdef CONFIG_OF - struct device_node *np = client->dev.of_node; - const __be32 *of_channels; - int of_channels_size; +static int ads1015_get_channels_config_of(struct i2c_client *client) +{ + struct ads1015_data *data = i2c_get_clientdata(client); + struct device_node *node; + + if (!client->dev.of_node + || !of_get_next_child(client->dev.of_node, NULL)) + return -EINVAL; + + for_each_child_of_node(client->dev.of_node, node) { + const __be32 *property; + int len; + unsigned int channel; + unsigned int pga = ADS1015_DEFAULT_PGA; + unsigned int data_rate = ADS1015_DEFAULT_DATA_RATE; + + property = of_get_property(node, "reg", &len); + if (!property || len != sizeof(int)) { + dev_err(&client->dev, "invalid reg on %s\n", + node->full_name); + continue; + } + + channel = be32_to_cpup(property); + if (channel > ADS1015_CHANNELS) { + dev_err(&client->dev, + "invalid channel index %d on %s\n", + channel, node->full_name); + continue; + } + + property = of_get_property(node, "ti,gain", &len); + if (property && len == sizeof(int)) { + pga = be32_to_cpup(property); + if (pga > 6) { + dev_err(&client->dev, + "invalid gain on %s\n", + node->full_name); + } + } + + property = of_get_property(node, "ti,datarate", &len); + if (property && len == sizeof(int)) { + data_rate = be32_to_cpup(property); + if (data_rate > 7) { + dev_err(&client->dev, + "invalid data_rate on %s\n", + node->full_name); + } + } + + data->channel_data[channel].enabled = true; + data->channel_data[channel].pga = pga; + data->channel_data[channel].data_rate = data_rate; + } + + return 0; +} #endif +static void ads1015_get_channels_config(struct i2c_client *client) +{ + unsigned int k; + struct ads1015_data *data = i2c_get_clientdata(client); + struct ads1015_platform_data *pdata = dev_get_platdata(&client->dev); + /* prefer platform data */ - if (pdata) - return pdata->exported_channels; + if (pdata) { + memcpy(data->channel_data, pdata->channel_data, + sizeof(data->channel_data)); + return; + } #ifdef CONFIG_OF - /* fallback on OF */ - of_channels = of_get_property(np, "exported-channels", - &of_channels_size); - if (of_channels && (of_channels_size == sizeof(*of_channels))) - return be32_to_cpup(of_channels); + if (!ads1015_get_channels_config_of(client)) + return; #endif /* fallback on default configuration */ - return ADS1015_DEFAULT_CHANNELS; + for (k = 0; k < ADS1015_CHANNELS; ++k) { + data->channel_data[k].enabled = true; + data->channel_data[k].pga = ADS1015_DEFAULT_PGA; + data->channel_data[k].data_rate = ADS1015_DEFAULT_DATA_RATE; + } } static int ads1015_probe(struct i2c_client *client, @@ -196,7 +266,6 @@ static int ads1015_probe(struct i2c_client *client, { struct ads1015_data *data; int err; - unsigned int exported_channels; unsigned int k; data = kzalloc(sizeof(struct ads1015_data), GFP_KERNEL); @@ -209,9 +278,9 @@ static int ads1015_probe(struct i2c_client *client, mutex_init(&data->update_lock); /* build sysfs attribute group */ - exported_channels = ads1015_get_exported_channels(client); - for (k = 0; k < ADS1015_CONFIG_CHANNELS; ++k) { - if (!(exported_channels & (1<channel_data[k].enabled) continue; err = device_create_file(&client->dev, &ads1015_in[k].dev_attr); if (err) @@ -227,7 +296,7 @@ static int ads1015_probe(struct i2c_client *client, return 0; exit_remove: - for (k = 0; k < ADS1015_CONFIG_CHANNELS; ++k) + for (k = 0; k < ADS1015_CHANNELS; ++k) device_remove_file(&client->dev, &ads1015_in[k].dev_attr); exit_free: kfree(data); diff --git a/include/linux/i2c/ads1015.h b/include/linux/i2c/ads1015.h index 8541c6acfafd..d5aa2a045669 100644 --- a/include/linux/i2c/ads1015.h +++ b/include/linux/i2c/ads1015.h @@ -21,8 +21,16 @@ #ifndef LINUX_ADS1015_H #define LINUX_ADS1015_H +#define ADS1015_CHANNELS 8 + +struct ads1015_channel_data { + bool enabled; + unsigned int pga; + unsigned int data_rate; +}; + struct ads1015_platform_data { - unsigned int exported_channels; + struct ads1015_channel_data channel_data[ADS1015_CHANNELS]; }; #endif /* LINUX_ADS1015_H */