From bfcd415dcb8042084d560a69a7aebb4b1f98a4db Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:35 +0100 Subject: 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 diff --git a/Documentation/hwmon/w83795 b/Documentation/hwmon/w83795 new file mode 100644 index 0000000..9f16037 --- /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 -- cgit v0.10.2 From 396bd766a604b823369962ee0203c603b9c0bdec Mon Sep 17 00:00:00 2001 From: "Justin P. Mattock" Date: Mon, 21 Mar 2011 17:59:35 +0100 Subject: hwmon: (sht15) Spelling fix Remove one too many "n" in a word. Signed-off-by: Justin P. Mattock Acked-by: Jean Delvare diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c index a610e78..3ba7dd8 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); -- cgit v0.10.2 From ccd32e735de7a941906e093f8dca924bb05c5794 Mon Sep 17 00:00:00 2001 From: Vivien Didelot Date: Mon, 21 Mar 2011 17:59:35 +0100 Subject: 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 diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c index 3ba7dd8..1a9c32d 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; } -- cgit v0.10.2 From 39c3e721d65793373e7bc6f5dad0591ef6d09268 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:35 +0100 Subject: 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 diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 1bfb443..c48101c 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 8a238de..94bf274 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 -- cgit v0.10.2 From efcfed9bad88be8193ee6a1b8e72d7381e7b0e0e Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: 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 diff --git a/Documentation/hwmon/hpfall.c b/Documentation/hwmon/hpfall.c deleted file mode 100644 index a4a8fc5..0000000 --- a/Documentation/hwmon/hpfall.c +++ /dev/null @@ -1,146 +0,0 @@ -/* Disk protection for HP machines. - * - * Copyright 2008 Eric Piel - * Copyright 2009 Pavel Machek - * - * GPLv2. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -char unload_heads_path[64]; - -int set_unload_heads_path(char *device) -{ - char devname[64]; - - if (strlen(device) <= 5 || strncmp(device, "/dev/", 5) != 0) - return -EINVAL; - strncpy(devname, device + 5, sizeof(devname)); - - snprintf(unload_heads_path, sizeof(unload_heads_path), - "/sys/block/%s/device/unload_heads", devname); - return 0; -} -int valid_disk(void) -{ - int fd = open(unload_heads_path, O_RDONLY); - if (fd < 0) { - perror(unload_heads_path); - return 0; - } - - close(fd); - return 1; -} - -void write_int(char *path, int i) -{ - char buf[1024]; - int fd = open(path, O_RDWR); - if (fd < 0) { - perror("open"); - exit(1); - } - sprintf(buf, "%d", i); - if (write(fd, buf, strlen(buf)) != strlen(buf)) { - perror("write"); - exit(1); - } - close(fd); -} - -void set_led(int on) -{ - write_int("/sys/class/leds/hp::hddprotect/brightness", on); -} - -void protect(int seconds) -{ - write_int(unload_heads_path, seconds*1000); -} - -int on_ac(void) -{ -// /sys/class/power_supply/AC0/online -} - -int lid_open(void) -{ -// /proc/acpi/button/lid/LID/state -} - -void ignore_me(void) -{ - protect(0); - set_led(0); -} - -int main(int argc, char **argv) -{ - int fd, ret; - struct sched_param param; - - if (argc == 1) - ret = set_unload_heads_path("/dev/sda"); - else if (argc == 2) - ret = set_unload_heads_path(argv[1]); - else - ret = -EINVAL; - - if (ret || !valid_disk()) { - fprintf(stderr, "usage: %s (default: /dev/sda)\n", - argv[0]); - exit(1); - } - - fd = open("/dev/freefall", O_RDONLY); - if (fd < 0) { - perror("/dev/freefall"); - return EXIT_FAILURE; - } - - daemon(0, 0); - param.sched_priority = sched_get_priority_max(SCHED_FIFO); - sched_setscheduler(0, SCHED_FIFO, ¶m); - mlockall(MCL_CURRENT|MCL_FUTURE); - - signal(SIGALRM, ignore_me); - - for (;;) { - unsigned char count; - - ret = read(fd, &count, sizeof(count)); - alarm(0); - if ((ret == -1) && (errno == EINTR)) { - /* Alarm expired, time to unpark the heads */ - continue; - } - - if (ret != sizeof(count)) { - perror("read"); - break; - } - - protect(21); - set_led(1); - if (1 || on_ac() || lid_open()) - alarm(2); - else - alarm(20); - } - - close(fd); - return EXIT_SUCCESS; -} diff --git a/Documentation/hwmon/lis3lv02d b/Documentation/hwmon/lis3lv02d index 06534f2..f1a4ec8 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/laptops/hpfall.c b/Documentation/laptops/hpfall.c new file mode 100644 index 0000000..a4a8fc5 --- /dev/null +++ b/Documentation/laptops/hpfall.c @@ -0,0 +1,146 @@ +/* Disk protection for HP machines. + * + * Copyright 2008 Eric Piel + * Copyright 2009 Pavel Machek + * + * GPLv2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char unload_heads_path[64]; + +int set_unload_heads_path(char *device) +{ + char devname[64]; + + if (strlen(device) <= 5 || strncmp(device, "/dev/", 5) != 0) + return -EINVAL; + strncpy(devname, device + 5, sizeof(devname)); + + snprintf(unload_heads_path, sizeof(unload_heads_path), + "/sys/block/%s/device/unload_heads", devname); + return 0; +} +int valid_disk(void) +{ + int fd = open(unload_heads_path, O_RDONLY); + if (fd < 0) { + perror(unload_heads_path); + return 0; + } + + close(fd); + return 1; +} + +void write_int(char *path, int i) +{ + char buf[1024]; + int fd = open(path, O_RDWR); + if (fd < 0) { + perror("open"); + exit(1); + } + sprintf(buf, "%d", i); + if (write(fd, buf, strlen(buf)) != strlen(buf)) { + perror("write"); + exit(1); + } + close(fd); +} + +void set_led(int on) +{ + write_int("/sys/class/leds/hp::hddprotect/brightness", on); +} + +void protect(int seconds) +{ + write_int(unload_heads_path, seconds*1000); +} + +int on_ac(void) +{ +// /sys/class/power_supply/AC0/online +} + +int lid_open(void) +{ +// /proc/acpi/button/lid/LID/state +} + +void ignore_me(void) +{ + protect(0); + set_led(0); +} + +int main(int argc, char **argv) +{ + int fd, ret; + struct sched_param param; + + if (argc == 1) + ret = set_unload_heads_path("/dev/sda"); + else if (argc == 2) + ret = set_unload_heads_path(argv[1]); + else + ret = -EINVAL; + + if (ret || !valid_disk()) { + fprintf(stderr, "usage: %s (default: /dev/sda)\n", + argv[0]); + exit(1); + } + + fd = open("/dev/freefall", O_RDONLY); + if (fd < 0) { + perror("/dev/freefall"); + return EXIT_FAILURE; + } + + daemon(0, 0); + param.sched_priority = sched_get_priority_max(SCHED_FIFO); + sched_setscheduler(0, SCHED_FIFO, ¶m); + mlockall(MCL_CURRENT|MCL_FUTURE); + + signal(SIGALRM, ignore_me); + + for (;;) { + unsigned char count; + + ret = read(fd, &count, sizeof(count)); + alarm(0); + if ((ret == -1) && (errno == EINTR)) { + /* Alarm expired, time to unpark the heads */ + continue; + } + + if (ret != sizeof(count)) { + perror("read"); + break; + } + + protect(21); + set_led(1); + if (1 || on_ac() || lid_open()) + alarm(2); + else + alarm(20); + } + + close(fd); + return EXIT_SUCCESS; +} diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index c48101c..3f8c895 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 94bf274..55ba906 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/hwmon/hp_accel.c b/drivers/hwmon/hp_accel.c deleted file mode 100644 index 3d21fa2..0000000 --- a/drivers/hwmon/hp_accel.c +++ /dev/null @@ -1,405 +0,0 @@ -/* - * hp_accel.c - Interface between LIS3LV02DL driver and HP ACPI BIOS - * - * Copyright (C) 2007-2008 Yan Burman - * Copyright (C) 2008 Eric Piel - * Copyright (C) 2008-2009 Pavel Machek - * - * 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 -#include -#include -#include -#include "lis3lv02d.h" - -#define DRIVER_NAME "lis3lv02d" -#define ACPI_MDPS_CLASS "accelerometer" - -/* Delayed LEDs infrastructure ------------------------------------ */ - -/* Special LED class that can defer work */ -struct delayed_led_classdev { - struct led_classdev led_classdev; - struct work_struct work; - enum led_brightness new_brightness; - - unsigned int led; /* For driver */ - void (*set_brightness)(struct delayed_led_classdev *data, enum led_brightness value); -}; - -static inline void delayed_set_status_worker(struct work_struct *work) -{ - struct delayed_led_classdev *data = - container_of(work, struct delayed_led_classdev, work); - - data->set_brightness(data, data->new_brightness); -} - -static inline void delayed_sysfs_set(struct led_classdev *led_cdev, - enum led_brightness brightness) -{ - struct delayed_led_classdev *data = container_of(led_cdev, - struct delayed_led_classdev, led_classdev); - data->new_brightness = brightness; - schedule_work(&data->work); -} - -/* HP-specific accelerometer driver ------------------------------------ */ - -/* For automatic insertion of the module */ -static struct acpi_device_id lis3lv02d_device_ids[] = { - {"HPQ0004", 0}, /* HP Mobile Data Protection System PNP */ - {"", 0}, -}; -MODULE_DEVICE_TABLE(acpi, lis3lv02d_device_ids); - - -/** - * lis3lv02d_acpi_init - ACPI _INI method: initialize the device. - * @lis3: pointer to the device struct - * - * Returns 0 on success. - */ -int lis3lv02d_acpi_init(struct lis3lv02d *lis3) -{ - struct acpi_device *dev = lis3->bus_priv; - if (acpi_evaluate_object(dev->handle, METHOD_NAME__INI, - NULL, NULL) != AE_OK) - return -EINVAL; - - return 0; -} - -/** - * lis3lv02d_acpi_read - ACPI ALRD method: read a register - * @lis3: pointer to the device struct - * @reg: the register to read - * @ret: result of the operation - * - * Returns 0 on success. - */ -int lis3lv02d_acpi_read(struct lis3lv02d *lis3, int reg, u8 *ret) -{ - struct acpi_device *dev = lis3->bus_priv; - union acpi_object arg0 = { ACPI_TYPE_INTEGER }; - struct acpi_object_list args = { 1, &arg0 }; - unsigned long long lret; - acpi_status status; - - arg0.integer.value = reg; - - status = acpi_evaluate_integer(dev->handle, "ALRD", &args, &lret); - *ret = lret; - return (status != AE_OK) ? -EINVAL : 0; -} - -/** - * lis3lv02d_acpi_write - ACPI ALWR method: write to a register - * @lis3: pointer to the device struct - * @reg: the register to write to - * @val: the value to write - * - * Returns 0 on success. - */ -int lis3lv02d_acpi_write(struct lis3lv02d *lis3, int reg, u8 val) -{ - struct acpi_device *dev = lis3->bus_priv; - unsigned long long ret; /* Not used when writting */ - union acpi_object in_obj[2]; - struct acpi_object_list args = { 2, in_obj }; - - in_obj[0].type = ACPI_TYPE_INTEGER; - in_obj[0].integer.value = reg; - in_obj[1].type = ACPI_TYPE_INTEGER; - in_obj[1].integer.value = val; - - if (acpi_evaluate_integer(dev->handle, "ALWR", &args, &ret) != AE_OK) - return -EINVAL; - - return 0; -} - -static int lis3lv02d_dmi_matched(const struct dmi_system_id *dmi) -{ - lis3_dev.ac = *((union axis_conversion *)dmi->driver_data); - pr_info("hardware type %s found\n", dmi->ident); - - return 1; -} - -/* Represents, for each axis seen by userspace, the corresponding hw axis (+1). - * If the value is negative, the opposite of the hw value is used. */ -#define DEFINE_CONV(name, x, y, z) \ - static union axis_conversion lis3lv02d_axis_##name = \ - { .as_array = { x, y, z } } -DEFINE_CONV(normal, 1, 2, 3); -DEFINE_CONV(y_inverted, 1, -2, 3); -DEFINE_CONV(x_inverted, -1, 2, 3); -DEFINE_CONV(z_inverted, 1, 2, -3); -DEFINE_CONV(xy_swap, 2, 1, 3); -DEFINE_CONV(xy_rotated_left, -2, 1, 3); -DEFINE_CONV(xy_rotated_left_usd, -2, 1, -3); -DEFINE_CONV(xy_swap_inverted, -2, -1, 3); -DEFINE_CONV(xy_rotated_right, 2, -1, 3); -DEFINE_CONV(xy_swap_yz_inverted, 2, -1, -3); - -#define AXIS_DMI_MATCH(_ident, _name, _axis) { \ - .ident = _ident, \ - .callback = lis3lv02d_dmi_matched, \ - .matches = { \ - DMI_MATCH(DMI_PRODUCT_NAME, _name) \ - }, \ - .driver_data = &lis3lv02d_axis_##_axis \ -} - -#define AXIS_DMI_MATCH2(_ident, _class1, _name1, \ - _class2, _name2, \ - _axis) { \ - .ident = _ident, \ - .callback = lis3lv02d_dmi_matched, \ - .matches = { \ - DMI_MATCH(DMI_##_class1, _name1), \ - DMI_MATCH(DMI_##_class2, _name2), \ - }, \ - .driver_data = &lis3lv02d_axis_##_axis \ -} -static struct dmi_system_id lis3lv02d_dmi_ids[] = { - /* product names are truncated to match all kinds of a same model */ - AXIS_DMI_MATCH("NC64x0", "HP Compaq nc64", x_inverted), - AXIS_DMI_MATCH("NC84x0", "HP Compaq nc84", z_inverted), - AXIS_DMI_MATCH("NX9420", "HP Compaq nx9420", x_inverted), - AXIS_DMI_MATCH("NW9440", "HP Compaq nw9440", x_inverted), - AXIS_DMI_MATCH("NC2510", "HP Compaq 2510", y_inverted), - AXIS_DMI_MATCH("NC2710", "HP Compaq 2710", xy_swap), - AXIS_DMI_MATCH("NC8510", "HP Compaq 8510", xy_swap_inverted), - AXIS_DMI_MATCH("HP2133", "HP 2133", xy_rotated_left), - AXIS_DMI_MATCH("HP2140", "HP 2140", xy_swap_inverted), - AXIS_DMI_MATCH("NC653x", "HP Compaq 653", xy_rotated_left_usd), - AXIS_DMI_MATCH("NC6730b", "HP Compaq 6730b", xy_rotated_left_usd), - AXIS_DMI_MATCH("NC6730s", "HP Compaq 6730s", xy_swap), - AXIS_DMI_MATCH("NC651xx", "HP Compaq 651", xy_rotated_right), - AXIS_DMI_MATCH("NC6710x", "HP Compaq 6710", xy_swap_yz_inverted), - AXIS_DMI_MATCH("NC6715x", "HP Compaq 6715", y_inverted), - AXIS_DMI_MATCH("NC693xx", "HP EliteBook 693", xy_rotated_right), - AXIS_DMI_MATCH("NC693xx", "HP EliteBook 853", xy_swap), - /* Intel-based HP Pavilion dv5 */ - AXIS_DMI_MATCH2("HPDV5_I", - PRODUCT_NAME, "HP Pavilion dv5", - BOARD_NAME, "3603", - x_inverted), - /* AMD-based HP Pavilion dv5 */ - AXIS_DMI_MATCH2("HPDV5_A", - PRODUCT_NAME, "HP Pavilion dv5", - BOARD_NAME, "3600", - y_inverted), - AXIS_DMI_MATCH("DV7", "HP Pavilion dv7", x_inverted), - AXIS_DMI_MATCH("HP8710", "HP Compaq 8710", y_inverted), - AXIS_DMI_MATCH("HDX18", "HP HDX 18", x_inverted), - AXIS_DMI_MATCH("HPB432x", "HP ProBook 432", xy_rotated_left), - AXIS_DMI_MATCH("HPB442x", "HP ProBook 442", xy_rotated_left), - AXIS_DMI_MATCH("HPB452x", "HP ProBook 452", y_inverted), - AXIS_DMI_MATCH("HPB522x", "HP ProBook 522", xy_swap), - AXIS_DMI_MATCH("HPB532x", "HP ProBook 532", y_inverted), - AXIS_DMI_MATCH("Mini510x", "HP Mini 510", xy_rotated_left_usd), - { NULL, } -/* Laptop models without axis info (yet): - * "NC6910" "HP Compaq 6910" - * "NC2400" "HP Compaq nc2400" - * "NX74x0" "HP Compaq nx74" - * "NX6325" "HP Compaq nx6325" - * "NC4400" "HP Compaq nc4400" - */ -}; - -static void hpled_set(struct delayed_led_classdev *led_cdev, enum led_brightness value) -{ - struct acpi_device *dev = lis3_dev.bus_priv; - unsigned long long ret; /* Not used when writing */ - union acpi_object in_obj[1]; - struct acpi_object_list args = { 1, in_obj }; - - in_obj[0].type = ACPI_TYPE_INTEGER; - in_obj[0].integer.value = !!value; - - acpi_evaluate_integer(dev->handle, "ALED", &args, &ret); -} - -static struct delayed_led_classdev hpled_led = { - .led_classdev = { - .name = "hp::hddprotect", - .default_trigger = "none", - .brightness_set = delayed_sysfs_set, - .flags = LED_CORE_SUSPENDRESUME, - }, - .set_brightness = hpled_set, -}; - -static acpi_status -lis3lv02d_get_resource(struct acpi_resource *resource, void *context) -{ - if (resource->type == ACPI_RESOURCE_TYPE_EXTENDED_IRQ) { - struct acpi_resource_extended_irq *irq; - u32 *device_irq = context; - - irq = &resource->data.extended_irq; - *device_irq = irq->interrupts[0]; - } - - return AE_OK; -} - -static void lis3lv02d_enum_resources(struct acpi_device *device) -{ - acpi_status status; - - status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, - lis3lv02d_get_resource, &lis3_dev.irq); - if (ACPI_FAILURE(status)) - printk(KERN_DEBUG DRIVER_NAME ": Error getting resources\n"); -} - -static int lis3lv02d_add(struct acpi_device *device) -{ - int ret; - - if (!device) - return -EINVAL; - - lis3_dev.bus_priv = device; - lis3_dev.init = lis3lv02d_acpi_init; - lis3_dev.read = lis3lv02d_acpi_read; - lis3_dev.write = lis3lv02d_acpi_write; - strcpy(acpi_device_name(device), DRIVER_NAME); - strcpy(acpi_device_class(device), ACPI_MDPS_CLASS); - device->driver_data = &lis3_dev; - - /* obtain IRQ number of our device from ACPI */ - lis3lv02d_enum_resources(device); - - /* If possible use a "standard" axes order */ - if (lis3_dev.ac.x && lis3_dev.ac.y && lis3_dev.ac.z) { - pr_info("Using custom axes %d,%d,%d\n", - lis3_dev.ac.x, lis3_dev.ac.y, lis3_dev.ac.z); - } else if (dmi_check_system(lis3lv02d_dmi_ids) == 0) { - pr_info("laptop model unknown, using default axes configuration\n"); - lis3_dev.ac = lis3lv02d_axis_normal; - } - - /* call the core layer do its init */ - ret = lis3lv02d_init_device(&lis3_dev); - if (ret) - return ret; - - INIT_WORK(&hpled_led.work, delayed_set_status_worker); - ret = led_classdev_register(NULL, &hpled_led.led_classdev); - if (ret) { - lis3lv02d_joystick_disable(); - lis3lv02d_poweroff(&lis3_dev); - flush_work(&hpled_led.work); - return ret; - } - - return ret; -} - -static int lis3lv02d_remove(struct acpi_device *device, int type) -{ - if (!device) - return -EINVAL; - - lis3lv02d_joystick_disable(); - lis3lv02d_poweroff(&lis3_dev); - - led_classdev_unregister(&hpled_led.led_classdev); - flush_work(&hpled_led.work); - - return lis3lv02d_remove_fs(&lis3_dev); -} - - -#ifdef CONFIG_PM -static int lis3lv02d_suspend(struct acpi_device *device, pm_message_t state) -{ - /* make sure the device is off when we suspend */ - lis3lv02d_poweroff(&lis3_dev); - return 0; -} - -static int lis3lv02d_resume(struct acpi_device *device) -{ - lis3lv02d_poweron(&lis3_dev); - return 0; -} -#else -#define lis3lv02d_suspend NULL -#define lis3lv02d_resume NULL -#endif - -/* For the HP MDPS aka 3D Driveguard */ -static struct acpi_driver lis3lv02d_driver = { - .name = DRIVER_NAME, - .class = ACPI_MDPS_CLASS, - .ids = lis3lv02d_device_ids, - .ops = { - .add = lis3lv02d_add, - .remove = lis3lv02d_remove, - .suspend = lis3lv02d_suspend, - .resume = lis3lv02d_resume, - } -}; - -static int __init lis3lv02d_init_module(void) -{ - int ret; - - if (acpi_disabled) - return -ENODEV; - - ret = acpi_bus_register_driver(&lis3lv02d_driver); - if (ret < 0) - return ret; - - pr_info("driver loaded\n"); - - return 0; -} - -static void __exit lis3lv02d_exit_module(void) -{ - acpi_bus_unregister_driver(&lis3lv02d_driver); -} - -MODULE_DESCRIPTION("Glue between LIS3LV02Dx and HP ACPI BIOS and support for disk protection LED."); -MODULE_AUTHOR("Yan Burman, Eric Piel, Pavel Machek"); -MODULE_LICENSE("GPL"); - -module_init(lis3lv02d_init_module); -module_exit(lis3lv02d_exit_module); - diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index a59af5b..222dfb7 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 4ec4ff8..299aefb 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/platform/x86/hp_accel.c b/drivers/platform/x86/hp_accel.c new file mode 100644 index 0000000..23f09c1 --- /dev/null +++ b/drivers/platform/x86/hp_accel.c @@ -0,0 +1,404 @@ +/* + * hp_accel.c - Interface between LIS3LV02DL driver and HP ACPI BIOS + * + * Copyright (C) 2007-2008 Yan Burman + * Copyright (C) 2008 Eric Piel + * Copyright (C) 2008-2009 Pavel Machek + * + * 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 +#include +#include +#include +#include "../../hwmon/lis3lv02d.h" + +#define DRIVER_NAME "lis3lv02d" +#define ACPI_MDPS_CLASS "accelerometer" + +/* Delayed LEDs infrastructure ------------------------------------ */ + +/* Special LED class that can defer work */ +struct delayed_led_classdev { + struct led_classdev led_classdev; + struct work_struct work; + enum led_brightness new_brightness; + + unsigned int led; /* For driver */ + void (*set_brightness)(struct delayed_led_classdev *data, enum led_brightness value); +}; + +static inline void delayed_set_status_worker(struct work_struct *work) +{ + struct delayed_led_classdev *data = + container_of(work, struct delayed_led_classdev, work); + + data->set_brightness(data, data->new_brightness); +} + +static inline void delayed_sysfs_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct delayed_led_classdev *data = container_of(led_cdev, + struct delayed_led_classdev, led_classdev); + data->new_brightness = brightness; + schedule_work(&data->work); +} + +/* HP-specific accelerometer driver ------------------------------------ */ + +/* For automatic insertion of the module */ +static struct acpi_device_id lis3lv02d_device_ids[] = { + {"HPQ0004", 0}, /* HP Mobile Data Protection System PNP */ + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, lis3lv02d_device_ids); + + +/** + * lis3lv02d_acpi_init - ACPI _INI method: initialize the device. + * @lis3: pointer to the device struct + * + * Returns 0 on success. + */ +int lis3lv02d_acpi_init(struct lis3lv02d *lis3) +{ + struct acpi_device *dev = lis3->bus_priv; + if (acpi_evaluate_object(dev->handle, METHOD_NAME__INI, + NULL, NULL) != AE_OK) + return -EINVAL; + + return 0; +} + +/** + * lis3lv02d_acpi_read - ACPI ALRD method: read a register + * @lis3: pointer to the device struct + * @reg: the register to read + * @ret: result of the operation + * + * Returns 0 on success. + */ +int lis3lv02d_acpi_read(struct lis3lv02d *lis3, int reg, u8 *ret) +{ + struct acpi_device *dev = lis3->bus_priv; + union acpi_object arg0 = { ACPI_TYPE_INTEGER }; + struct acpi_object_list args = { 1, &arg0 }; + unsigned long long lret; + acpi_status status; + + arg0.integer.value = reg; + + status = acpi_evaluate_integer(dev->handle, "ALRD", &args, &lret); + *ret = lret; + return (status != AE_OK) ? -EINVAL : 0; +} + +/** + * lis3lv02d_acpi_write - ACPI ALWR method: write to a register + * @lis3: pointer to the device struct + * @reg: the register to write to + * @val: the value to write + * + * Returns 0 on success. + */ +int lis3lv02d_acpi_write(struct lis3lv02d *lis3, int reg, u8 val) +{ + struct acpi_device *dev = lis3->bus_priv; + unsigned long long ret; /* Not used when writting */ + union acpi_object in_obj[2]; + struct acpi_object_list args = { 2, in_obj }; + + in_obj[0].type = ACPI_TYPE_INTEGER; + in_obj[0].integer.value = reg; + in_obj[1].type = ACPI_TYPE_INTEGER; + in_obj[1].integer.value = val; + + if (acpi_evaluate_integer(dev->handle, "ALWR", &args, &ret) != AE_OK) + return -EINVAL; + + return 0; +} + +static int lis3lv02d_dmi_matched(const struct dmi_system_id *dmi) +{ + lis3_dev.ac = *((union axis_conversion *)dmi->driver_data); + pr_info("hardware type %s found\n", dmi->ident); + + return 1; +} + +/* Represents, for each axis seen by userspace, the corresponding hw axis (+1). + * If the value is negative, the opposite of the hw value is used. */ +#define DEFINE_CONV(name, x, y, z) \ + static union axis_conversion lis3lv02d_axis_##name = \ + { .as_array = { x, y, z } } +DEFINE_CONV(normal, 1, 2, 3); +DEFINE_CONV(y_inverted, 1, -2, 3); +DEFINE_CONV(x_inverted, -1, 2, 3); +DEFINE_CONV(z_inverted, 1, 2, -3); +DEFINE_CONV(xy_swap, 2, 1, 3); +DEFINE_CONV(xy_rotated_left, -2, 1, 3); +DEFINE_CONV(xy_rotated_left_usd, -2, 1, -3); +DEFINE_CONV(xy_swap_inverted, -2, -1, 3); +DEFINE_CONV(xy_rotated_right, 2, -1, 3); +DEFINE_CONV(xy_swap_yz_inverted, 2, -1, -3); + +#define AXIS_DMI_MATCH(_ident, _name, _axis) { \ + .ident = _ident, \ + .callback = lis3lv02d_dmi_matched, \ + .matches = { \ + DMI_MATCH(DMI_PRODUCT_NAME, _name) \ + }, \ + .driver_data = &lis3lv02d_axis_##_axis \ +} + +#define AXIS_DMI_MATCH2(_ident, _class1, _name1, \ + _class2, _name2, \ + _axis) { \ + .ident = _ident, \ + .callback = lis3lv02d_dmi_matched, \ + .matches = { \ + DMI_MATCH(DMI_##_class1, _name1), \ + DMI_MATCH(DMI_##_class2, _name2), \ + }, \ + .driver_data = &lis3lv02d_axis_##_axis \ +} +static struct dmi_system_id lis3lv02d_dmi_ids[] = { + /* product names are truncated to match all kinds of a same model */ + AXIS_DMI_MATCH("NC64x0", "HP Compaq nc64", x_inverted), + AXIS_DMI_MATCH("NC84x0", "HP Compaq nc84", z_inverted), + AXIS_DMI_MATCH("NX9420", "HP Compaq nx9420", x_inverted), + AXIS_DMI_MATCH("NW9440", "HP Compaq nw9440", x_inverted), + AXIS_DMI_MATCH("NC2510", "HP Compaq 2510", y_inverted), + AXIS_DMI_MATCH("NC2710", "HP Compaq 2710", xy_swap), + AXIS_DMI_MATCH("NC8510", "HP Compaq 8510", xy_swap_inverted), + AXIS_DMI_MATCH("HP2133", "HP 2133", xy_rotated_left), + AXIS_DMI_MATCH("HP2140", "HP 2140", xy_swap_inverted), + AXIS_DMI_MATCH("NC653x", "HP Compaq 653", xy_rotated_left_usd), + AXIS_DMI_MATCH("NC6730b", "HP Compaq 6730b", xy_rotated_left_usd), + AXIS_DMI_MATCH("NC6730s", "HP Compaq 6730s", xy_swap), + AXIS_DMI_MATCH("NC651xx", "HP Compaq 651", xy_rotated_right), + AXIS_DMI_MATCH("NC6710x", "HP Compaq 6710", xy_swap_yz_inverted), + AXIS_DMI_MATCH("NC6715x", "HP Compaq 6715", y_inverted), + AXIS_DMI_MATCH("NC693xx", "HP EliteBook 693", xy_rotated_right), + AXIS_DMI_MATCH("NC693xx", "HP EliteBook 853", xy_swap), + /* Intel-based HP Pavilion dv5 */ + AXIS_DMI_MATCH2("HPDV5_I", + PRODUCT_NAME, "HP Pavilion dv5", + BOARD_NAME, "3603", + x_inverted), + /* AMD-based HP Pavilion dv5 */ + AXIS_DMI_MATCH2("HPDV5_A", + PRODUCT_NAME, "HP Pavilion dv5", + BOARD_NAME, "3600", + y_inverted), + AXIS_DMI_MATCH("DV7", "HP Pavilion dv7", x_inverted), + AXIS_DMI_MATCH("HP8710", "HP Compaq 8710", y_inverted), + AXIS_DMI_MATCH("HDX18", "HP HDX 18", x_inverted), + AXIS_DMI_MATCH("HPB432x", "HP ProBook 432", xy_rotated_left), + AXIS_DMI_MATCH("HPB442x", "HP ProBook 442", xy_rotated_left), + AXIS_DMI_MATCH("HPB452x", "HP ProBook 452", y_inverted), + AXIS_DMI_MATCH("HPB522x", "HP ProBook 522", xy_swap), + AXIS_DMI_MATCH("HPB532x", "HP ProBook 532", y_inverted), + AXIS_DMI_MATCH("Mini510x", "HP Mini 510", xy_rotated_left_usd), + { NULL, } +/* Laptop models without axis info (yet): + * "NC6910" "HP Compaq 6910" + * "NC2400" "HP Compaq nc2400" + * "NX74x0" "HP Compaq nx74" + * "NX6325" "HP Compaq nx6325" + * "NC4400" "HP Compaq nc4400" + */ +}; + +static void hpled_set(struct delayed_led_classdev *led_cdev, enum led_brightness value) +{ + struct acpi_device *dev = lis3_dev.bus_priv; + unsigned long long ret; /* Not used when writing */ + union acpi_object in_obj[1]; + struct acpi_object_list args = { 1, in_obj }; + + in_obj[0].type = ACPI_TYPE_INTEGER; + in_obj[0].integer.value = !!value; + + acpi_evaluate_integer(dev->handle, "ALED", &args, &ret); +} + +static struct delayed_led_classdev hpled_led = { + .led_classdev = { + .name = "hp::hddprotect", + .default_trigger = "none", + .brightness_set = delayed_sysfs_set, + .flags = LED_CORE_SUSPENDRESUME, + }, + .set_brightness = hpled_set, +}; + +static acpi_status +lis3lv02d_get_resource(struct acpi_resource *resource, void *context) +{ + if (resource->type == ACPI_RESOURCE_TYPE_EXTENDED_IRQ) { + struct acpi_resource_extended_irq *irq; + u32 *device_irq = context; + + irq = &resource->data.extended_irq; + *device_irq = irq->interrupts[0]; + } + + return AE_OK; +} + +static void lis3lv02d_enum_resources(struct acpi_device *device) +{ + acpi_status status; + + status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, + lis3lv02d_get_resource, &lis3_dev.irq); + if (ACPI_FAILURE(status)) + printk(KERN_DEBUG DRIVER_NAME ": Error getting resources\n"); +} + +static int lis3lv02d_add(struct acpi_device *device) +{ + int ret; + + if (!device) + return -EINVAL; + + lis3_dev.bus_priv = device; + lis3_dev.init = lis3lv02d_acpi_init; + lis3_dev.read = lis3lv02d_acpi_read; + lis3_dev.write = lis3lv02d_acpi_write; + strcpy(acpi_device_name(device), DRIVER_NAME); + strcpy(acpi_device_class(device), ACPI_MDPS_CLASS); + device->driver_data = &lis3_dev; + + /* obtain IRQ number of our device from ACPI */ + lis3lv02d_enum_resources(device); + + /* If possible use a "standard" axes order */ + if (lis3_dev.ac.x && lis3_dev.ac.y && lis3_dev.ac.z) { + pr_info("Using custom axes %d,%d,%d\n", + lis3_dev.ac.x, lis3_dev.ac.y, lis3_dev.ac.z); + } else if (dmi_check_system(lis3lv02d_dmi_ids) == 0) { + pr_info("laptop model unknown, using default axes configuration\n"); + lis3_dev.ac = lis3lv02d_axis_normal; + } + + /* call the core layer do its init */ + ret = lis3lv02d_init_device(&lis3_dev); + if (ret) + return ret; + + INIT_WORK(&hpled_led.work, delayed_set_status_worker); + ret = led_classdev_register(NULL, &hpled_led.led_classdev); + if (ret) { + lis3lv02d_joystick_disable(); + lis3lv02d_poweroff(&lis3_dev); + flush_work(&hpled_led.work); + return ret; + } + + return ret; +} + +static int lis3lv02d_remove(struct acpi_device *device, int type) +{ + if (!device) + return -EINVAL; + + lis3lv02d_joystick_disable(); + lis3lv02d_poweroff(&lis3_dev); + + led_classdev_unregister(&hpled_led.led_classdev); + flush_work(&hpled_led.work); + + return lis3lv02d_remove_fs(&lis3_dev); +} + + +#ifdef CONFIG_PM +static int lis3lv02d_suspend(struct acpi_device *device, pm_message_t state) +{ + /* make sure the device is off when we suspend */ + lis3lv02d_poweroff(&lis3_dev); + return 0; +} + +static int lis3lv02d_resume(struct acpi_device *device) +{ + lis3lv02d_poweron(&lis3_dev); + return 0; +} +#else +#define lis3lv02d_suspend NULL +#define lis3lv02d_resume NULL +#endif + +/* For the HP MDPS aka 3D Driveguard */ +static struct acpi_driver lis3lv02d_driver = { + .name = DRIVER_NAME, + .class = ACPI_MDPS_CLASS, + .ids = lis3lv02d_device_ids, + .ops = { + .add = lis3lv02d_add, + .remove = lis3lv02d_remove, + .suspend = lis3lv02d_suspend, + .resume = lis3lv02d_resume, + } +}; + +static int __init lis3lv02d_init_module(void) +{ + int ret; + + if (acpi_disabled) + return -ENODEV; + + ret = acpi_bus_register_driver(&lis3lv02d_driver); + if (ret < 0) + return ret; + + pr_info("driver loaded\n"); + + return 0; +} + +static void __exit lis3lv02d_exit_module(void) +{ + acpi_bus_unregister_driver(&lis3lv02d_driver); +} + +MODULE_DESCRIPTION("Glue between LIS3LV02Dx and HP ACPI BIOS and support for disk protection LED."); +MODULE_AUTHOR("Yan Burman, Eric Piel, Pavel Machek"); +MODULE_LICENSE("GPL"); + +module_init(lis3lv02d_init_module); +module_exit(lis3lv02d_exit_module); -- cgit v0.10.2 From ff606677f6a47c63329cf8e6c7cf978c29f2d736 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: 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 diff --git a/Documentation/hwmon/lis3lv02d b/Documentation/hwmon/lis3lv02d deleted file mode 100644 index f1a4ec8..0000000 --- a/Documentation/hwmon/lis3lv02d +++ /dev/null @@ -1,92 +0,0 @@ -Kernel driver lis3lv02d -======================= - -Supported chips: - - * STMicroelectronics LIS3LV02DL, LIS3LV02DQ (12 bits precision) - * STMicroelectronics LIS302DL, LIS3L02DQ, LIS331DL (8 bits) - -Authors: - Yan Burman - Eric Piel - - -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/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). - -Sysfs attributes under /sys/devices/platform/lis3lv02d/: -position - 3D position that the accelerometer reports. Format: "(x,y,z)" -rate - read reports the sampling rate of the accelerometer device in HZ. - write changes sampling rate of the accelerometer device. - Only values which are supported by HW are accepted. -selftest - performs selftest for the chip as specified by chip manufacturer. - -This driver also provides an absolute input class device, allowing -the laptop to act as a pinball machine-esque joystick. Joystick device can be -calibrated. Joystick device can be in two different modes. -By default output values are scaled between -32768 .. 32767. In joystick raw -mode, joystick and sysfs position entry have the same scale. There can be -small difference due to input system fuzziness feature. -Events are also available as input event device. - -Selftest is meant only for hardware diagnostic purposes. It is not meant to be -used during normal operations. Position data is not corrupted during selftest -but interrupt behaviour is not guaranteed to work reliably. In test mode, the -sensing element is internally moved little bit. Selftest measures difference -between normal mode and test mode. Chip specifications tell the acceptance -limit for each type of the chip. Limits are provided via platform data -to allow adjustment of the limits without a change to the actual driver. -Seltest returns either "OK x y z" or "FAIL x y z" where x, y and z are -measured difference between modes. Axes are not remapped in selftest mode. -Measurement values are provided to help HW diagnostic applications to make -final decision. - -On HP laptops, if the led infrastructure is activated, support for a led -indicating disk protection will be provided as /sys/class/leds/hp::hddprotect. - -Another feature of the driver is misc device called "freefall" that -acts similar to /dev/rtc and reacts on free-fall interrupts received -from the device. It supports blocking operations, poll/select and -fasync operation modes. You must read 1 bytes from the device. The -result is number of free-fall interrupts since the last successful -read (or 255 if number of interrupts would not fit). See the hpfall.c -file for an example on using the device. - - -Axes orientation ----------------- - -For better compatibility between the various laptops. The values reported by -the accelerometer are converted into a "standard" organisation of the axes -(aka "can play neverball out of the box"): - * When the laptop is horizontal the position reported is about 0 for X and Y - and a positive value for Z - * If the left side is elevated, X increases (becomes positive) - * If the front side (where the touchpad is) is elevated, Y decreases - (becomes negative) - * If the laptop is put upside-down, Z becomes negative - -If your laptop model is not recognized (cf "dmesg"), you can send an -email to the maintainer to add it to the database. When reporting a new -laptop, please include the output of "dmidecode" plus the value of -/sys/devices/platform/lis3lv02d/position in these four cases. - -Q&A ---- - -Q: How do I safely simulate freefall? I have an HP "portable -workstation" which has about 3.5kg and a plastic case, so letting it -fall to the ground is out of question... - -A: The sensor is pretty sensitive, so your hands can do it. Lift it -into free space, follow the fall with your hands for like 10 -centimeters. That should be enough to trigger the detection. diff --git a/Documentation/misc-devices/lis3lv02d b/Documentation/misc-devices/lis3lv02d new file mode 100644 index 0000000..f1a4ec8 --- /dev/null +++ b/Documentation/misc-devices/lis3lv02d @@ -0,0 +1,92 @@ +Kernel driver lis3lv02d +======================= + +Supported chips: + + * STMicroelectronics LIS3LV02DL, LIS3LV02DQ (12 bits precision) + * STMicroelectronics LIS302DL, LIS3L02DQ, LIS331DL (8 bits) + +Authors: + Yan Burman + Eric Piel + + +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/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). + +Sysfs attributes under /sys/devices/platform/lis3lv02d/: +position - 3D position that the accelerometer reports. Format: "(x,y,z)" +rate - read reports the sampling rate of the accelerometer device in HZ. + write changes sampling rate of the accelerometer device. + Only values which are supported by HW are accepted. +selftest - performs selftest for the chip as specified by chip manufacturer. + +This driver also provides an absolute input class device, allowing +the laptop to act as a pinball machine-esque joystick. Joystick device can be +calibrated. Joystick device can be in two different modes. +By default output values are scaled between -32768 .. 32767. In joystick raw +mode, joystick and sysfs position entry have the same scale. There can be +small difference due to input system fuzziness feature. +Events are also available as input event device. + +Selftest is meant only for hardware diagnostic purposes. It is not meant to be +used during normal operations. Position data is not corrupted during selftest +but interrupt behaviour is not guaranteed to work reliably. In test mode, the +sensing element is internally moved little bit. Selftest measures difference +between normal mode and test mode. Chip specifications tell the acceptance +limit for each type of the chip. Limits are provided via platform data +to allow adjustment of the limits without a change to the actual driver. +Seltest returns either "OK x y z" or "FAIL x y z" where x, y and z are +measured difference between modes. Axes are not remapped in selftest mode. +Measurement values are provided to help HW diagnostic applications to make +final decision. + +On HP laptops, if the led infrastructure is activated, support for a led +indicating disk protection will be provided as /sys/class/leds/hp::hddprotect. + +Another feature of the driver is misc device called "freefall" that +acts similar to /dev/rtc and reacts on free-fall interrupts received +from the device. It supports blocking operations, poll/select and +fasync operation modes. You must read 1 bytes from the device. The +result is number of free-fall interrupts since the last successful +read (or 255 if number of interrupts would not fit). See the hpfall.c +file for an example on using the device. + + +Axes orientation +---------------- + +For better compatibility between the various laptops. The values reported by +the accelerometer are converted into a "standard" organisation of the axes +(aka "can play neverball out of the box"): + * When the laptop is horizontal the position reported is about 0 for X and Y + and a positive value for Z + * If the left side is elevated, X increases (becomes positive) + * If the front side (where the touchpad is) is elevated, Y decreases + (becomes negative) + * If the laptop is put upside-down, Z becomes negative + +If your laptop model is not recognized (cf "dmesg"), you can send an +email to the maintainer to add it to the database. When reporting a new +laptop, please include the output of "dmidecode" plus the value of +/sys/devices/platform/lis3lv02d/position in these four cases. + +Q&A +--- + +Q: How do I safely simulate freefall? I have an HP "portable +workstation" which has about 3.5kg and a plastic case, so letting it +fall to the ground is out of question... + +A: The sensor is pretty sensitive, so your hands can do it. Lift it +into free space, follow the fall with your hands for like 10 +centimeters. That should be enough to trigger the detection. diff --git a/MAINTAINERS b/MAINTAINERS index c7a41b1..4edb2a8 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 3f8c895..47621ab 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 55ba906..c068f82 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/hwmon/lis3lv02d.c b/drivers/hwmon/lis3lv02d.c deleted file mode 100644 index d805e8e..0000000 --- a/drivers/hwmon/lis3lv02d.c +++ /dev/null @@ -1,1000 +0,0 @@ -/* - * lis3lv02d.c - ST LIS3LV02DL accelerometer driver - * - * Copyright (C) 2007-2008 Yan Burman - * Copyright (C) 2008 Eric Piel - * Copyright (C) 2008-2009 Pavel Machek - * - * 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 -#include -#include -#include -#include -#include -#include "lis3lv02d.h" - -#define DRIVER_NAME "lis3lv02d" - -/* joystick device poll interval in milliseconds */ -#define MDPS_POLL_INTERVAL 50 -#define MDPS_POLL_MIN 0 -#define MDPS_POLL_MAX 2000 - -#define LIS3_SYSFS_POWERDOWN_DELAY 5000 /* In milliseconds */ - -#define SELFTEST_OK 0 -#define SELFTEST_FAIL -1 -#define SELFTEST_IRQ -2 - -#define IRQ_LINE0 0 -#define IRQ_LINE1 1 - -/* - * The sensor can also generate interrupts (DRDY) but it's pretty pointless - * because they are generated even if the data do not change. So it's better - * to keep the interrupt for the free-fall event. The values are updated at - * 40Hz (at the lowest frequency), but as it can be pretty time consuming on - * some low processor, we poll the sensor only at 20Hz... enough for the - * joystick. - */ - -#define LIS3_PWRON_DELAY_WAI_12B (5000) -#define LIS3_PWRON_DELAY_WAI_8B (3000) - -/* - * LIS3LV02D spec says 1024 LSBs corresponds 1 G -> 1LSB is 1000/1024 mG - * LIS302D spec says: 18 mG / digit - * LIS3_ACCURACY is used to increase accuracy of the intermediate - * calculation results. - */ -#define LIS3_ACCURACY 1024 -/* Sensitivity values for -2G +2G scale */ -#define LIS3_SENSITIVITY_12B ((LIS3_ACCURACY * 1000) / 1024) -#define LIS3_SENSITIVITY_8B (18 * LIS3_ACCURACY) - -#define LIS3_DEFAULT_FUZZ_12B 3 -#define LIS3_DEFAULT_FLAT_12B 3 -#define LIS3_DEFAULT_FUZZ_8B 1 -#define LIS3_DEFAULT_FLAT_8B 1 - -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 - * over the axis array size - */ -static int param_set_axis(const char *val, const struct kernel_param *kp) -{ - int ret = param_set_int(val, kp); - if (!ret) { - int val = *(int *)kp->arg; - if (val < 0) - val = -val; - if (!val || val > 3) - return -EINVAL; - } - return ret; -} - -static struct kernel_param_ops param_ops_axis = { - .set = param_set_axis, - .get = param_get_int, -}; - -module_param_array_named(axes, lis3_dev.ac.as_array, axis, NULL, 0644); -MODULE_PARM_DESC(axes, "Axis-mapping for x,y,z directions"); - -static s16 lis3lv02d_read_8(struct lis3lv02d *lis3, int reg) -{ - s8 lo; - if (lis3->read(lis3, reg, &lo) < 0) - return 0; - - return lo; -} - -static s16 lis3lv02d_read_12(struct lis3lv02d *lis3, int reg) -{ - u8 lo, hi; - - lis3->read(lis3, reg - 1, &lo); - lis3->read(lis3, reg, &hi); - /* In "12 bit right justified" mode, bit 6, bit 7, bit 8 = bit 5 */ - return (s16)((hi << 8) | lo); -} - -/** - * lis3lv02d_get_axis - For the given axis, give the value converted - * @axis: 1,2,3 - can also be negative - * @hw_values: raw values returned by the hardware - * - * Returns the converted value. - */ -static inline int lis3lv02d_get_axis(s8 axis, int hw_values[3]) -{ - if (axis > 0) - return hw_values[axis - 1]; - else - return -hw_values[-axis - 1]; -} - -/** - * lis3lv02d_get_xyz - Get X, Y and Z axis values from the accelerometer - * @lis3: pointer to the device struct - * @x: where to store the X axis value - * @y: where to store the Y axis value - * @z: where to store the Z axis value - * - * Note that 40Hz input device can eat up about 10% CPU at 800MHZ - */ -static void lis3lv02d_get_xyz(struct lis3lv02d *lis3, int *x, int *y, int *z) -{ - int position[3]; - int i; - - if (lis3->blkread) { - if (lis3_dev.whoami == WAI_12B) { - u16 data[3]; - lis3->blkread(lis3, OUTX_L, 6, (u8 *)data); - for (i = 0; i < 3; i++) - position[i] = (s16)le16_to_cpu(data[i]); - } else { - u8 data[5]; - /* Data: x, dummy, y, dummy, z */ - lis3->blkread(lis3, OUTX, 5, data); - for (i = 0; i < 3; i++) - position[i] = (s8)data[i * 2]; - } - } else { - position[0] = lis3->read_data(lis3, OUTX); - position[1] = lis3->read_data(lis3, OUTY); - position[2] = lis3->read_data(lis3, OUTZ); - } - - for (i = 0; i < 3; i++) - position[i] = (position[i] * lis3->scale) / LIS3_ACCURACY; - - *x = lis3lv02d_get_axis(lis3->ac.x, position); - *y = lis3lv02d_get_axis(lis3->ac.y, position); - *z = lis3lv02d_get_axis(lis3->ac.z, position); -} - -/* conversion btw sampling rate and the register values */ -static int lis3_12_rates[4] = {40, 160, 640, 2560}; -static int lis3_8_rates[2] = {100, 400}; -static int lis3_3dc_rates[16] = {0, 1, 10, 25, 50, 100, 200, 400, 1600, 5000}; - -/* ODR is Output Data Rate */ -static int lis3lv02d_get_odr(void) -{ - u8 ctrl; - int shift; - - lis3_dev.read(&lis3_dev, CTRL_REG1, &ctrl); - ctrl &= lis3_dev.odr_mask; - shift = ffs(lis3_dev.odr_mask) - 1; - return lis3_dev.odrs[(ctrl >> shift)]; -} - -static int lis3lv02d_set_odr(int rate) -{ - u8 ctrl; - int i, len, shift; - - if (!rate) - return -EINVAL; - - lis3_dev.read(&lis3_dev, CTRL_REG1, &ctrl); - ctrl &= ~lis3_dev.odr_mask; - len = 1 << hweight_long(lis3_dev.odr_mask); /* # of possible values */ - shift = ffs(lis3_dev.odr_mask) - 1; - - for (i = 0; i < len; i++) - if (lis3_dev.odrs[i] == rate) { - lis3_dev.write(&lis3_dev, CTRL_REG1, - ctrl | (i << shift)); - return 0; - } - return -EINVAL; -} - -static int lis3lv02d_selftest(struct lis3lv02d *lis3, s16 results[3]) -{ - u8 ctlreg, reg; - s16 x, y, z; - u8 selftest; - int ret; - u8 ctrl_reg_data; - unsigned char irq_cfg; - - mutex_lock(&lis3->mutex); - - irq_cfg = lis3->irq_cfg; - if (lis3_dev.whoami == WAI_8B) { - lis3->data_ready_count[IRQ_LINE0] = 0; - lis3->data_ready_count[IRQ_LINE1] = 0; - - /* Change interrupt cfg to data ready for selftest */ - atomic_inc(&lis3_dev.wake_thread); - lis3->irq_cfg = LIS3_IRQ1_DATA_READY | LIS3_IRQ2_DATA_READY; - lis3->read(lis3, CTRL_REG3, &ctrl_reg_data); - lis3->write(lis3, CTRL_REG3, (ctrl_reg_data & - ~(LIS3_IRQ1_MASK | LIS3_IRQ2_MASK)) | - (LIS3_IRQ1_DATA_READY | LIS3_IRQ2_DATA_READY)); - } - - if (lis3_dev.whoami == WAI_3DC) { - ctlreg = CTRL_REG4; - selftest = CTRL4_ST0; - } else { - ctlreg = CTRL_REG1; - if (lis3_dev.whoami == WAI_12B) - selftest = CTRL1_ST; - else - selftest = CTRL1_STP; - } - - lis3->read(lis3, ctlreg, ®); - lis3->write(lis3, ctlreg, (reg | selftest)); - msleep(lis3->pwron_delay / lis3lv02d_get_odr()); - - /* Read directly to avoid axis remap */ - x = lis3->read_data(lis3, OUTX); - y = lis3->read_data(lis3, OUTY); - z = lis3->read_data(lis3, OUTZ); - - /* back to normal settings */ - lis3->write(lis3, ctlreg, reg); - msleep(lis3->pwron_delay / lis3lv02d_get_odr()); - - results[0] = x - lis3->read_data(lis3, OUTX); - results[1] = y - lis3->read_data(lis3, OUTY); - results[2] = z - lis3->read_data(lis3, OUTZ); - - ret = 0; - - if (lis3_dev.whoami == WAI_8B) { - /* Restore original interrupt configuration */ - atomic_dec(&lis3_dev.wake_thread); - lis3->write(lis3, CTRL_REG3, ctrl_reg_data); - lis3->irq_cfg = irq_cfg; - - if ((irq_cfg & LIS3_IRQ1_MASK) && - lis3->data_ready_count[IRQ_LINE0] < 2) { - ret = SELFTEST_IRQ; - goto fail; - } - - if ((irq_cfg & LIS3_IRQ2_MASK) && - lis3->data_ready_count[IRQ_LINE1] < 2) { - ret = SELFTEST_IRQ; - goto fail; - } - } - - if (lis3->pdata) { - int i; - for (i = 0; i < 3; i++) { - /* Check against selftest acceptance limits */ - if ((results[i] < lis3->pdata->st_min_limits[i]) || - (results[i] > lis3->pdata->st_max_limits[i])) { - ret = SELFTEST_FAIL; - goto fail; - } - } - } - - /* test passed */ -fail: - mutex_unlock(&lis3->mutex); - return ret; -} - -/* - * Order of registers in the list affects to order of the restore process. - * Perhaps it is a good idea to set interrupt enable register as a last one - * after all other configurations - */ -static u8 lis3_wai8_regs[] = { FF_WU_CFG_1, FF_WU_THS_1, FF_WU_DURATION_1, - FF_WU_CFG_2, FF_WU_THS_2, FF_WU_DURATION_2, - CLICK_CFG, CLICK_SRC, CLICK_THSY_X, CLICK_THSZ, - CLICK_TIMELIMIT, CLICK_LATENCY, CLICK_WINDOW, - CTRL_REG1, CTRL_REG2, CTRL_REG3}; - -static u8 lis3_wai12_regs[] = {FF_WU_CFG, FF_WU_THS_L, FF_WU_THS_H, - FF_WU_DURATION, DD_CFG, DD_THSI_L, DD_THSI_H, - DD_THSE_L, DD_THSE_H, - CTRL_REG1, CTRL_REG3, CTRL_REG2}; - -static inline void lis3_context_save(struct lis3lv02d *lis3) -{ - int i; - for (i = 0; i < lis3->regs_size; i++) - lis3->read(lis3, lis3->regs[i], &lis3->reg_cache[i]); - lis3->regs_stored = true; -} - -static inline void lis3_context_restore(struct lis3lv02d *lis3) -{ - int i; - if (lis3->regs_stored) - for (i = 0; i < lis3->regs_size; i++) - lis3->write(lis3, lis3->regs[i], lis3->reg_cache[i]); -} - -void lis3lv02d_poweroff(struct lis3lv02d *lis3) -{ - if (lis3->reg_ctrl) - lis3_context_save(lis3); - /* disable X,Y,Z axis and power down */ - lis3->write(lis3, CTRL_REG1, 0x00); - if (lis3->reg_ctrl) - lis3->reg_ctrl(lis3, LIS3_REG_OFF); -} -EXPORT_SYMBOL_GPL(lis3lv02d_poweroff); - -void lis3lv02d_poweron(struct lis3lv02d *lis3) -{ - u8 reg; - - lis3->init(lis3); - - /* - * Common configuration - * BDU: (12 bits sensors only) LSB and MSB values are not updated until - * both have been read. So the value read will always be correct. - * Set BOOT bit to refresh factory tuning values. - */ - lis3->read(lis3, CTRL_REG2, ®); - if (lis3->whoami == WAI_12B) - reg |= CTRL2_BDU | CTRL2_BOOT; - else - reg |= CTRL2_BOOT_8B; - lis3->write(lis3, CTRL_REG2, reg); - - /* LIS3 power on delay is quite long */ - msleep(lis3->pwron_delay / lis3lv02d_get_odr()); - - if (lis3->reg_ctrl) - lis3_context_restore(lis3); -} -EXPORT_SYMBOL_GPL(lis3lv02d_poweron); - - -static void lis3lv02d_joystick_poll(struct input_polled_dev *pidev) -{ - int x, y, z; - - mutex_lock(&lis3_dev.mutex); - lis3lv02d_get_xyz(&lis3_dev, &x, &y, &z); - input_report_abs(pidev->input, ABS_X, x); - input_report_abs(pidev->input, ABS_Y, y); - input_report_abs(pidev->input, ABS_Z, z); - input_sync(pidev->input); - mutex_unlock(&lis3_dev.mutex); -} - -static void lis3lv02d_joystick_open(struct input_polled_dev *pidev) -{ - if (lis3_dev.pm_dev) - pm_runtime_get_sync(lis3_dev.pm_dev); - - if (lis3_dev.pdata && lis3_dev.whoami == WAI_8B && lis3_dev.idev) - atomic_set(&lis3_dev.wake_thread, 1); - /* - * Update coordinates for the case where poll interval is 0 and - * the chip in running purely under interrupt control - */ - lis3lv02d_joystick_poll(pidev); -} - -static void lis3lv02d_joystick_close(struct input_polled_dev *pidev) -{ - atomic_set(&lis3_dev.wake_thread, 0); - if (lis3_dev.pm_dev) - pm_runtime_put(lis3_dev.pm_dev); -} - -static irqreturn_t lis302dl_interrupt(int irq, void *dummy) -{ - if (!test_bit(0, &lis3_dev.misc_opened)) - goto out; - - /* - * Be careful: on some HP laptops the bios force DD when on battery and - * the lid is closed. This leads to interrupts as soon as a little move - * is done. - */ - atomic_inc(&lis3_dev.count); - - wake_up_interruptible(&lis3_dev.misc_wait); - kill_fasync(&lis3_dev.async_queue, SIGIO, POLL_IN); -out: - if (atomic_read(&lis3_dev.wake_thread)) - return IRQ_WAKE_THREAD; - return IRQ_HANDLED; -} - -static void lis302dl_interrupt_handle_click(struct lis3lv02d *lis3) -{ - struct input_dev *dev = lis3->idev->input; - u8 click_src; - - mutex_lock(&lis3->mutex); - lis3->read(lis3, CLICK_SRC, &click_src); - - if (click_src & CLICK_SINGLE_X) { - input_report_key(dev, lis3->mapped_btns[0], 1); - input_report_key(dev, lis3->mapped_btns[0], 0); - } - - if (click_src & CLICK_SINGLE_Y) { - input_report_key(dev, lis3->mapped_btns[1], 1); - input_report_key(dev, lis3->mapped_btns[1], 0); - } - - if (click_src & CLICK_SINGLE_Z) { - input_report_key(dev, lis3->mapped_btns[2], 1); - input_report_key(dev, lis3->mapped_btns[2], 0); - } - input_sync(dev); - mutex_unlock(&lis3->mutex); -} - -static inline void lis302dl_data_ready(struct lis3lv02d *lis3, int index) -{ - int dummy; - - /* Dummy read to ack interrupt */ - lis3lv02d_get_xyz(lis3, &dummy, &dummy, &dummy); - lis3->data_ready_count[index]++; -} - -static irqreturn_t lis302dl_interrupt_thread1_8b(int irq, void *data) -{ - struct lis3lv02d *lis3 = data; - u8 irq_cfg = lis3->irq_cfg & LIS3_IRQ1_MASK; - - if (irq_cfg == LIS3_IRQ1_CLICK) - lis302dl_interrupt_handle_click(lis3); - else if (unlikely(irq_cfg == LIS3_IRQ1_DATA_READY)) - lis302dl_data_ready(lis3, IRQ_LINE0); - else - lis3lv02d_joystick_poll(lis3->idev); - - return IRQ_HANDLED; -} - -static irqreturn_t lis302dl_interrupt_thread2_8b(int irq, void *data) -{ - struct lis3lv02d *lis3 = data; - u8 irq_cfg = lis3->irq_cfg & LIS3_IRQ2_MASK; - - if (irq_cfg == LIS3_IRQ2_CLICK) - lis302dl_interrupt_handle_click(lis3); - else if (unlikely(irq_cfg == LIS3_IRQ2_DATA_READY)) - lis302dl_data_ready(lis3, IRQ_LINE1); - else - lis3lv02d_joystick_poll(lis3->idev); - - return IRQ_HANDLED; -} - -static int lis3lv02d_misc_open(struct inode *inode, struct file *file) -{ - if (test_and_set_bit(0, &lis3_dev.misc_opened)) - return -EBUSY; /* already open */ - - if (lis3_dev.pm_dev) - pm_runtime_get_sync(lis3_dev.pm_dev); - - atomic_set(&lis3_dev.count, 0); - return 0; -} - -static int lis3lv02d_misc_release(struct inode *inode, struct file *file) -{ - fasync_helper(-1, file, 0, &lis3_dev.async_queue); - clear_bit(0, &lis3_dev.misc_opened); /* release the device */ - if (lis3_dev.pm_dev) - pm_runtime_put(lis3_dev.pm_dev); - return 0; -} - -static ssize_t lis3lv02d_misc_read(struct file *file, char __user *buf, - size_t count, loff_t *pos) -{ - DECLARE_WAITQUEUE(wait, current); - u32 data; - unsigned char byte_data; - ssize_t retval = 1; - - if (count < 1) - return -EINVAL; - - add_wait_queue(&lis3_dev.misc_wait, &wait); - while (true) { - set_current_state(TASK_INTERRUPTIBLE); - data = atomic_xchg(&lis3_dev.count, 0); - if (data) - break; - - if (file->f_flags & O_NONBLOCK) { - retval = -EAGAIN; - goto out; - } - - if (signal_pending(current)) { - retval = -ERESTARTSYS; - goto out; - } - - schedule(); - } - - if (data < 255) - byte_data = data; - else - byte_data = 255; - - /* make sure we are not going into copy_to_user() with - * TASK_INTERRUPTIBLE state */ - set_current_state(TASK_RUNNING); - if (copy_to_user(buf, &byte_data, sizeof(byte_data))) - retval = -EFAULT; - -out: - __set_current_state(TASK_RUNNING); - remove_wait_queue(&lis3_dev.misc_wait, &wait); - - return retval; -} - -static unsigned int lis3lv02d_misc_poll(struct file *file, poll_table *wait) -{ - poll_wait(file, &lis3_dev.misc_wait, wait); - if (atomic_read(&lis3_dev.count)) - return POLLIN | POLLRDNORM; - return 0; -} - -static int lis3lv02d_misc_fasync(int fd, struct file *file, int on) -{ - return fasync_helper(fd, file, on, &lis3_dev.async_queue); -} - -static const struct file_operations lis3lv02d_misc_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .read = lis3lv02d_misc_read, - .open = lis3lv02d_misc_open, - .release = lis3lv02d_misc_release, - .poll = lis3lv02d_misc_poll, - .fasync = lis3lv02d_misc_fasync, -}; - -static struct miscdevice lis3lv02d_misc_device = { - .minor = MISC_DYNAMIC_MINOR, - .name = "freefall", - .fops = &lis3lv02d_misc_fops, -}; - -int lis3lv02d_joystick_enable(void) -{ - struct input_dev *input_dev; - int err; - int max_val, fuzz, flat; - int btns[] = {BTN_X, BTN_Y, BTN_Z}; - - if (lis3_dev.idev) - return -EINVAL; - - lis3_dev.idev = input_allocate_polled_device(); - if (!lis3_dev.idev) - return -ENOMEM; - - lis3_dev.idev->poll = lis3lv02d_joystick_poll; - lis3_dev.idev->open = lis3lv02d_joystick_open; - lis3_dev.idev->close = lis3lv02d_joystick_close; - lis3_dev.idev->poll_interval = MDPS_POLL_INTERVAL; - lis3_dev.idev->poll_interval_min = MDPS_POLL_MIN; - lis3_dev.idev->poll_interval_max = MDPS_POLL_MAX; - input_dev = lis3_dev.idev->input; - - input_dev->name = "ST LIS3LV02DL Accelerometer"; - input_dev->phys = DRIVER_NAME "/input0"; - input_dev->id.bustype = BUS_HOST; - input_dev->id.vendor = 0; - input_dev->dev.parent = &lis3_dev.pdev->dev; - - set_bit(EV_ABS, input_dev->evbit); - max_val = (lis3_dev.mdps_max_val * lis3_dev.scale) / LIS3_ACCURACY; - if (lis3_dev.whoami == WAI_12B) { - fuzz = LIS3_DEFAULT_FUZZ_12B; - flat = LIS3_DEFAULT_FLAT_12B; - } else { - fuzz = LIS3_DEFAULT_FUZZ_8B; - flat = LIS3_DEFAULT_FLAT_8B; - } - fuzz = (fuzz * lis3_dev.scale) / LIS3_ACCURACY; - flat = (flat * lis3_dev.scale) / LIS3_ACCURACY; - - input_set_abs_params(input_dev, ABS_X, -max_val, max_val, fuzz, flat); - input_set_abs_params(input_dev, ABS_Y, -max_val, max_val, fuzz, flat); - input_set_abs_params(input_dev, ABS_Z, -max_val, max_val, fuzz, flat); - - lis3_dev.mapped_btns[0] = lis3lv02d_get_axis(abs(lis3_dev.ac.x), btns); - lis3_dev.mapped_btns[1] = lis3lv02d_get_axis(abs(lis3_dev.ac.y), btns); - lis3_dev.mapped_btns[2] = lis3lv02d_get_axis(abs(lis3_dev.ac.z), btns); - - err = input_register_polled_device(lis3_dev.idev); - if (err) { - input_free_polled_device(lis3_dev.idev); - lis3_dev.idev = NULL; - } - - return err; -} -EXPORT_SYMBOL_GPL(lis3lv02d_joystick_enable); - -void lis3lv02d_joystick_disable(void) -{ - if (lis3_dev.irq) - free_irq(lis3_dev.irq, &lis3_dev); - if (lis3_dev.pdata && lis3_dev.pdata->irq2) - free_irq(lis3_dev.pdata->irq2, &lis3_dev); - - if (!lis3_dev.idev) - return; - - if (lis3_dev.irq) - misc_deregister(&lis3lv02d_misc_device); - input_unregister_polled_device(lis3_dev.idev); - input_free_polled_device(lis3_dev.idev); - lis3_dev.idev = NULL; -} -EXPORT_SYMBOL_GPL(lis3lv02d_joystick_disable); - -/* Sysfs stuff */ -static void lis3lv02d_sysfs_poweron(struct lis3lv02d *lis3) -{ - /* - * SYSFS functions are fast visitors so put-call - * immediately after the get-call. However, keep - * chip running for a while and schedule delayed - * suspend. This way periodic sysfs calls doesn't - * suffer from relatively long power up time. - */ - - if (lis3->pm_dev) { - pm_runtime_get_sync(lis3->pm_dev); - pm_runtime_put_noidle(lis3->pm_dev); - pm_schedule_suspend(lis3->pm_dev, LIS3_SYSFS_POWERDOWN_DELAY); - } -} - -static ssize_t lis3lv02d_selftest_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - s16 values[3]; - - static const char ok[] = "OK"; - static const char fail[] = "FAIL"; - static const char irq[] = "FAIL_IRQ"; - const char *res; - - lis3lv02d_sysfs_poweron(&lis3_dev); - switch (lis3lv02d_selftest(&lis3_dev, values)) { - case SELFTEST_FAIL: - res = fail; - break; - case SELFTEST_IRQ: - res = irq; - break; - case SELFTEST_OK: - default: - res = ok; - break; - } - return sprintf(buf, "%s %d %d %d\n", res, - values[0], values[1], values[2]); -} - -static ssize_t lis3lv02d_position_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int x, y, z; - - lis3lv02d_sysfs_poweron(&lis3_dev); - mutex_lock(&lis3_dev.mutex); - lis3lv02d_get_xyz(&lis3_dev, &x, &y, &z); - mutex_unlock(&lis3_dev.mutex); - return sprintf(buf, "(%d,%d,%d)\n", x, y, z); -} - -static ssize_t lis3lv02d_rate_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - lis3lv02d_sysfs_poweron(&lis3_dev); - return sprintf(buf, "%d\n", lis3lv02d_get_odr()); -} - -static ssize_t lis3lv02d_rate_set(struct device *dev, - struct device_attribute *attr, const char *buf, - size_t count) -{ - unsigned long rate; - - if (strict_strtoul(buf, 0, &rate)) - return -EINVAL; - - lis3lv02d_sysfs_poweron(&lis3_dev); - if (lis3lv02d_set_odr(rate)) - return -EINVAL; - - return count; -} - -static DEVICE_ATTR(selftest, S_IRUSR, lis3lv02d_selftest_show, NULL); -static DEVICE_ATTR(position, S_IRUGO, lis3lv02d_position_show, NULL); -static DEVICE_ATTR(rate, S_IRUGO | S_IWUSR, lis3lv02d_rate_show, - lis3lv02d_rate_set); - -static struct attribute *lis3lv02d_attributes[] = { - &dev_attr_selftest.attr, - &dev_attr_position.attr, - &dev_attr_rate.attr, - NULL -}; - -static struct attribute_group lis3lv02d_attribute_group = { - .attrs = lis3lv02d_attributes -}; - - -static int lis3lv02d_add_fs(struct lis3lv02d *lis3) -{ - lis3->pdev = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0); - if (IS_ERR(lis3->pdev)) - return PTR_ERR(lis3->pdev); - - return sysfs_create_group(&lis3->pdev->dev.kobj, &lis3lv02d_attribute_group); -} - -int lis3lv02d_remove_fs(struct lis3lv02d *lis3) -{ - sysfs_remove_group(&lis3->pdev->dev.kobj, &lis3lv02d_attribute_group); - platform_device_unregister(lis3->pdev); - if (lis3->pm_dev) { - /* Barrier after the sysfs remove */ - pm_runtime_barrier(lis3->pm_dev); - - /* SYSFS may have left chip running. Turn off if necessary */ - if (!pm_runtime_suspended(lis3->pm_dev)) - lis3lv02d_poweroff(&lis3_dev); - - pm_runtime_disable(lis3->pm_dev); - pm_runtime_set_suspended(lis3->pm_dev); - } - kfree(lis3->reg_cache); - return 0; -} -EXPORT_SYMBOL_GPL(lis3lv02d_remove_fs); - -static void lis3lv02d_8b_configure(struct lis3lv02d *dev, - struct lis3lv02d_platform_data *p) -{ - int err; - int ctrl2 = p->hipass_ctrl; - - if (p->click_flags) { - dev->write(dev, CLICK_CFG, p->click_flags); - dev->write(dev, CLICK_TIMELIMIT, p->click_time_limit); - dev->write(dev, CLICK_LATENCY, p->click_latency); - dev->write(dev, CLICK_WINDOW, p->click_window); - dev->write(dev, CLICK_THSZ, p->click_thresh_z & 0xf); - dev->write(dev, CLICK_THSY_X, - (p->click_thresh_x & 0xf) | - (p->click_thresh_y << 4)); - - if (dev->idev) { - struct input_dev *input_dev = lis3_dev.idev->input; - input_set_capability(input_dev, EV_KEY, BTN_X); - input_set_capability(input_dev, EV_KEY, BTN_Y); - input_set_capability(input_dev, EV_KEY, BTN_Z); - } - } - - if (p->wakeup_flags) { - dev->write(dev, FF_WU_CFG_1, p->wakeup_flags); - dev->write(dev, FF_WU_THS_1, p->wakeup_thresh & 0x7f); - /* pdata value + 1 to keep this backward compatible*/ - dev->write(dev, FF_WU_DURATION_1, p->duration1 + 1); - ctrl2 ^= HP_FF_WU1; /* Xor to keep compatible with old pdata*/ - } - - if (p->wakeup_flags2) { - dev->write(dev, FF_WU_CFG_2, p->wakeup_flags2); - dev->write(dev, FF_WU_THS_2, p->wakeup_thresh2 & 0x7f); - /* pdata value + 1 to keep this backward compatible*/ - dev->write(dev, FF_WU_DURATION_2, p->duration2 + 1); - ctrl2 ^= HP_FF_WU2; /* Xor to keep compatible with old pdata*/ - } - /* Configure hipass filters */ - dev->write(dev, CTRL_REG2, ctrl2); - - if (p->irq2) { - err = request_threaded_irq(p->irq2, - NULL, - lis302dl_interrupt_thread2_8b, - IRQF_TRIGGER_RISING | IRQF_ONESHOT | - (p->irq_flags2 & IRQF_TRIGGER_MASK), - DRIVER_NAME, &lis3_dev); - if (err < 0) - pr_err("No second IRQ. Limited functionality\n"); - } -} - -/* - * Initialise the accelerometer and the various subsystems. - * Should be rather independent of the bus system. - */ -int lis3lv02d_init_device(struct lis3lv02d *dev) -{ - int err; - irq_handler_t thread_fn; - int irq_flags = 0; - - dev->whoami = lis3lv02d_read_8(dev, WHO_AM_I); - - switch (dev->whoami) { - case WAI_12B: - pr_info("12 bits sensor found\n"); - dev->read_data = lis3lv02d_read_12; - dev->mdps_max_val = 2048; - dev->pwron_delay = LIS3_PWRON_DELAY_WAI_12B; - dev->odrs = lis3_12_rates; - dev->odr_mask = CTRL1_DF0 | CTRL1_DF1; - dev->scale = LIS3_SENSITIVITY_12B; - dev->regs = lis3_wai12_regs; - dev->regs_size = ARRAY_SIZE(lis3_wai12_regs); - break; - case WAI_8B: - pr_info("8 bits sensor found\n"); - dev->read_data = lis3lv02d_read_8; - dev->mdps_max_val = 128; - dev->pwron_delay = LIS3_PWRON_DELAY_WAI_8B; - dev->odrs = lis3_8_rates; - dev->odr_mask = CTRL1_DR; - dev->scale = LIS3_SENSITIVITY_8B; - dev->regs = lis3_wai8_regs; - dev->regs_size = ARRAY_SIZE(lis3_wai8_regs); - break; - case WAI_3DC: - pr_info("8 bits 3DC sensor found\n"); - dev->read_data = lis3lv02d_read_8; - dev->mdps_max_val = 128; - dev->pwron_delay = LIS3_PWRON_DELAY_WAI_8B; - dev->odrs = lis3_3dc_rates; - dev->odr_mask = CTRL1_ODR0|CTRL1_ODR1|CTRL1_ODR2|CTRL1_ODR3; - dev->scale = LIS3_SENSITIVITY_8B; - break; - default: - pr_err("unknown sensor type 0x%X\n", dev->whoami); - return -EINVAL; - } - - dev->reg_cache = kzalloc(max(sizeof(lis3_wai8_regs), - sizeof(lis3_wai12_regs)), GFP_KERNEL); - - if (dev->reg_cache == NULL) { - printk(KERN_ERR DRIVER_NAME "out of memory\n"); - return -ENOMEM; - } - - mutex_init(&dev->mutex); - atomic_set(&dev->wake_thread, 0); - - lis3lv02d_add_fs(dev); - lis3lv02d_poweron(dev); - - if (dev->pm_dev) { - pm_runtime_set_active(dev->pm_dev); - pm_runtime_enable(dev->pm_dev); - } - - if (lis3lv02d_joystick_enable()) - pr_err("joystick initialization failed\n"); - - /* passing in platform specific data is purely optional and only - * used by the SPI transport layer at the moment */ - if (dev->pdata) { - struct lis3lv02d_platform_data *p = dev->pdata; - - if (dev->whoami == WAI_8B) - lis3lv02d_8b_configure(dev, p); - - irq_flags = p->irq_flags1 & IRQF_TRIGGER_MASK; - - dev->irq_cfg = p->irq_cfg; - if (p->irq_cfg) - dev->write(dev, CTRL_REG3, p->irq_cfg); - - if (p->default_rate) - lis3lv02d_set_odr(p->default_rate); - } - - /* bail if we did not get an IRQ from the bus layer */ - if (!dev->irq) { - pr_debug("No IRQ. Disabling /dev/freefall\n"); - goto out; - } - - /* - * The sensor can generate interrupts for free-fall and direction - * detection (distinguishable with FF_WU_SRC and DD_SRC) but to keep - * the things simple and _fast_ we activate it only for free-fall, so - * no need to read register (very slow with ACPI). For the same reason, - * we forbid shared interrupts. - * - * IRQF_TRIGGER_RISING seems pointless on HP laptops because the - * io-apic is not configurable (and generates a warning) but I keep it - * in case of support for other hardware. - */ - if (dev->pdata && dev->whoami == WAI_8B) - thread_fn = lis302dl_interrupt_thread1_8b; - else - thread_fn = NULL; - - err = request_threaded_irq(dev->irq, lis302dl_interrupt, - thread_fn, - IRQF_TRIGGER_RISING | IRQF_ONESHOT | - irq_flags, - DRIVER_NAME, &lis3_dev); - - if (err < 0) { - pr_err("Cannot get IRQ\n"); - goto out; - } - - if (misc_register(&lis3lv02d_misc_device)) - pr_err("misc_register failed\n"); -out: - return 0; -} -EXPORT_SYMBOL_GPL(lis3lv02d_init_device); - -MODULE_DESCRIPTION("ST LIS3LV02Dx three-axis digital accelerometer driver"); -MODULE_AUTHOR("Yan Burman, Eric Piel, Pavel Machek"); -MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lis3lv02d.h b/drivers/hwmon/lis3lv02d.h deleted file mode 100644 index a193958..0000000 --- a/drivers/hwmon/lis3lv02d.h +++ /dev/null @@ -1,291 +0,0 @@ -/* - * lis3lv02d.h - ST LIS3LV02DL accelerometer driver - * - * Copyright (C) 2007-2008 Yan Burman - * Copyright (C) 2008-2009 Eric Piel - * - * 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 - */ -#include -#include -#include - -/* - * This driver tries to support the "digital" accelerometer chips from - * STMicroelectronics such as LIS3LV02DL, LIS302DL, LIS3L02DQ, LIS331DL, - * LIS35DE, or LIS202DL. They are very similar in terms of programming, with - * almost the same registers. In addition to differing on physical properties, - * they differ on the number of axes (2/3), precision (8/12 bits), and special - * features (freefall detection, click...). Unfortunately, not all the - * differences can be probed via a register. - * They can be connected either via I²C or SPI. - */ - -#include - -enum lis3_reg { - WHO_AM_I = 0x0F, - OFFSET_X = 0x16, - OFFSET_Y = 0x17, - OFFSET_Z = 0x18, - GAIN_X = 0x19, - GAIN_Y = 0x1A, - GAIN_Z = 0x1B, - CTRL_REG1 = 0x20, - CTRL_REG2 = 0x21, - CTRL_REG3 = 0x22, - CTRL_REG4 = 0x23, - HP_FILTER_RESET = 0x23, - STATUS_REG = 0x27, - OUTX_L = 0x28, - OUTX_H = 0x29, - OUTX = 0x29, - OUTY_L = 0x2A, - OUTY_H = 0x2B, - OUTY = 0x2B, - OUTZ_L = 0x2C, - OUTZ_H = 0x2D, - OUTZ = 0x2D, -}; - -enum lis302d_reg { - FF_WU_CFG_1 = 0x30, - FF_WU_SRC_1 = 0x31, - FF_WU_THS_1 = 0x32, - FF_WU_DURATION_1 = 0x33, - FF_WU_CFG_2 = 0x34, - FF_WU_SRC_2 = 0x35, - FF_WU_THS_2 = 0x36, - FF_WU_DURATION_2 = 0x37, - CLICK_CFG = 0x38, - CLICK_SRC = 0x39, - CLICK_THSY_X = 0x3B, - CLICK_THSZ = 0x3C, - CLICK_TIMELIMIT = 0x3D, - CLICK_LATENCY = 0x3E, - CLICK_WINDOW = 0x3F, -}; - -enum lis3lv02d_reg { - FF_WU_CFG = 0x30, - FF_WU_SRC = 0x31, - FF_WU_ACK = 0x32, - FF_WU_THS_L = 0x34, - FF_WU_THS_H = 0x35, - FF_WU_DURATION = 0x36, - DD_CFG = 0x38, - DD_SRC = 0x39, - DD_ACK = 0x3A, - DD_THSI_L = 0x3C, - DD_THSI_H = 0x3D, - DD_THSE_L = 0x3E, - DD_THSE_H = 0x3F, -}; - -enum lis3_who_am_i { - WAI_3DC = 0x33, /* 8 bits: LIS3DC, HP3DC */ - WAI_12B = 0x3A, /* 12 bits: LIS3LV02D[LQ]... */ - WAI_8B = 0x3B, /* 8 bits: LIS[23]02D[LQ]... */ - WAI_6B = 0x52, /* 6 bits: LIS331DLF - not supported */ -}; - -enum lis3lv02d_ctrl1_12b { - CTRL1_Xen = 0x01, - CTRL1_Yen = 0x02, - CTRL1_Zen = 0x04, - CTRL1_ST = 0x08, - CTRL1_DF0 = 0x10, - CTRL1_DF1 = 0x20, - CTRL1_PD0 = 0x40, - CTRL1_PD1 = 0x80, -}; - -/* Delta to ctrl1_12b version */ -enum lis3lv02d_ctrl1_8b { - CTRL1_STM = 0x08, - CTRL1_STP = 0x10, - CTRL1_FS = 0x20, - CTRL1_PD = 0x40, - CTRL1_DR = 0x80, -}; - -enum lis3lv02d_ctrl1_3dc { - CTRL1_ODR0 = 0x10, - CTRL1_ODR1 = 0x20, - CTRL1_ODR2 = 0x40, - CTRL1_ODR3 = 0x80, -}; - -enum lis3lv02d_ctrl2 { - CTRL2_DAS = 0x01, - CTRL2_SIM = 0x02, - CTRL2_DRDY = 0x04, - CTRL2_IEN = 0x08, - CTRL2_BOOT = 0x10, - CTRL2_BLE = 0x20, - CTRL2_BDU = 0x40, /* Block Data Update */ - CTRL2_FS = 0x80, /* Full Scale selection */ -}; - -enum lis3lv02d_ctrl4_3dc { - CTRL4_SIM = 0x01, - CTRL4_ST0 = 0x02, - CTRL4_ST1 = 0x04, - CTRL4_FS0 = 0x10, - CTRL4_FS1 = 0x20, -}; - -enum lis302d_ctrl2 { - HP_FF_WU2 = 0x08, - HP_FF_WU1 = 0x04, - CTRL2_BOOT_8B = 0x40, -}; - -enum lis3lv02d_ctrl3 { - CTRL3_CFS0 = 0x01, - CTRL3_CFS1 = 0x02, - CTRL3_FDS = 0x10, - CTRL3_HPFF = 0x20, - CTRL3_HPDD = 0x40, - CTRL3_ECK = 0x80, -}; - -enum lis3lv02d_status_reg { - STATUS_XDA = 0x01, - STATUS_YDA = 0x02, - STATUS_ZDA = 0x04, - STATUS_XYZDA = 0x08, - STATUS_XOR = 0x10, - STATUS_YOR = 0x20, - STATUS_ZOR = 0x40, - STATUS_XYZOR = 0x80, -}; - -enum lis3lv02d_ff_wu_cfg { - FF_WU_CFG_XLIE = 0x01, - FF_WU_CFG_XHIE = 0x02, - FF_WU_CFG_YLIE = 0x04, - FF_WU_CFG_YHIE = 0x08, - FF_WU_CFG_ZLIE = 0x10, - FF_WU_CFG_ZHIE = 0x20, - FF_WU_CFG_LIR = 0x40, - FF_WU_CFG_AOI = 0x80, -}; - -enum lis3lv02d_ff_wu_src { - FF_WU_SRC_XL = 0x01, - FF_WU_SRC_XH = 0x02, - FF_WU_SRC_YL = 0x04, - FF_WU_SRC_YH = 0x08, - FF_WU_SRC_ZL = 0x10, - FF_WU_SRC_ZH = 0x20, - FF_WU_SRC_IA = 0x40, -}; - -enum lis3lv02d_dd_cfg { - DD_CFG_XLIE = 0x01, - DD_CFG_XHIE = 0x02, - DD_CFG_YLIE = 0x04, - DD_CFG_YHIE = 0x08, - DD_CFG_ZLIE = 0x10, - DD_CFG_ZHIE = 0x20, - DD_CFG_LIR = 0x40, - DD_CFG_IEND = 0x80, -}; - -enum lis3lv02d_dd_src { - DD_SRC_XL = 0x01, - DD_SRC_XH = 0x02, - DD_SRC_YL = 0x04, - DD_SRC_YH = 0x08, - DD_SRC_ZL = 0x10, - DD_SRC_ZH = 0x20, - DD_SRC_IA = 0x40, -}; - -enum lis3lv02d_click_src_8b { - CLICK_SINGLE_X = 0x01, - CLICK_DOUBLE_X = 0x02, - CLICK_SINGLE_Y = 0x04, - CLICK_DOUBLE_Y = 0x08, - CLICK_SINGLE_Z = 0x10, - CLICK_DOUBLE_Z = 0x20, - CLICK_IA = 0x40, -}; - -enum lis3lv02d_reg_state { - LIS3_REG_OFF = 0x00, - LIS3_REG_ON = 0x01, -}; - -union axis_conversion { - struct { - int x, y, z; - }; - int as_array[3]; - -}; - -struct lis3lv02d { - void *bus_priv; /* used by the bus layer only */ - struct device *pm_dev; /* for pm_runtime purposes */ - int (*init) (struct lis3lv02d *lis3); - int (*write) (struct lis3lv02d *lis3, int reg, u8 val); - int (*read) (struct lis3lv02d *lis3, int reg, u8 *ret); - int (*blkread) (struct lis3lv02d *lis3, int reg, int len, u8 *ret); - int (*reg_ctrl) (struct lis3lv02d *lis3, bool state); - - int *odrs; /* Supported output data rates */ - u8 *regs; /* Regs to store / restore */ - int regs_size; - u8 *reg_cache; - bool regs_stored; - u8 odr_mask; /* ODR bit mask */ - u8 whoami; /* indicates measurement precision */ - s16 (*read_data) (struct lis3lv02d *lis3, int reg); - int mdps_max_val; - int pwron_delay; - int scale; /* - * relationship between 1 LBS and mG - * (1/1000th of earth gravity) - */ - - struct input_polled_dev *idev; /* input device */ - struct platform_device *pdev; /* platform device */ - struct regulator_bulk_data regulators[2]; - atomic_t count; /* interrupt count after last read */ - union axis_conversion ac; /* hw -> logical axis */ - int mapped_btns[3]; - - u32 irq; /* IRQ number */ - struct fasync_struct *async_queue; /* queue for the misc device */ - wait_queue_head_t misc_wait; /* Wait queue for the misc device */ - unsigned long misc_opened; /* bit0: whether the device is open */ - int data_ready_count[2]; - atomic_t wake_thread; - unsigned char irq_cfg; - - struct lis3lv02d_platform_data *pdata; /* for passing board config */ - struct mutex mutex; /* Serialize poll and selftest */ -}; - -int lis3lv02d_init_device(struct lis3lv02d *lis3); -int lis3lv02d_joystick_enable(void); -void lis3lv02d_joystick_disable(void); -void lis3lv02d_poweroff(struct lis3lv02d *lis3); -void lis3lv02d_poweron(struct lis3lv02d *lis3); -int lis3lv02d_remove_fs(struct lis3lv02d *lis3); - -extern struct lis3lv02d lis3_dev; diff --git a/drivers/hwmon/lis3lv02d_i2c.c b/drivers/hwmon/lis3lv02d_i2c.c deleted file mode 100644 index 8853afc..0000000 --- a/drivers/hwmon/lis3lv02d_i2c.c +++ /dev/null @@ -1,279 +0,0 @@ -/* - * drivers/hwmon/lis3lv02d_i2c.c - * - * Implements I2C interface for lis3lv02d (STMicroelectronics) accelerometer. - * Driver is based on corresponding SPI driver written by Daniel Mack - * (lis3lv02d_spi.c (C) 2009 Daniel Mack ). - * - * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). - * - * Contact: Samu Onkalo - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - * 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., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA - */ - -#include -#include -#include -#include -#include -#include -#include -#include "lis3lv02d.h" - -#define DRV_NAME "lis3lv02d_i2c" - -static const char reg_vdd[] = "Vdd"; -static const char reg_vdd_io[] = "Vdd_IO"; - -static int lis3_reg_ctrl(struct lis3lv02d *lis3, bool state) -{ - int ret; - if (state == LIS3_REG_OFF) { - ret = regulator_bulk_disable(ARRAY_SIZE(lis3->regulators), - lis3->regulators); - } else { - ret = regulator_bulk_enable(ARRAY_SIZE(lis3->regulators), - lis3->regulators); - /* Chip needs time to wakeup. Not mentioned in datasheet */ - usleep_range(10000, 20000); - } - return ret; -} - -static inline s32 lis3_i2c_write(struct lis3lv02d *lis3, int reg, u8 value) -{ - struct i2c_client *c = lis3->bus_priv; - return i2c_smbus_write_byte_data(c, reg, value); -} - -static inline s32 lis3_i2c_read(struct lis3lv02d *lis3, int reg, u8 *v) -{ - struct i2c_client *c = lis3->bus_priv; - *v = i2c_smbus_read_byte_data(c, reg); - return 0; -} - -static inline s32 lis3_i2c_blockread(struct lis3lv02d *lis3, int reg, int len, - u8 *v) -{ - struct i2c_client *c = lis3->bus_priv; - reg |= (1 << 7); /* 7th bit enables address auto incrementation */ - return i2c_smbus_read_i2c_block_data(c, reg, len, v); -} - -static int lis3_i2c_init(struct lis3lv02d *lis3) -{ - u8 reg; - int ret; - - if (lis3->reg_ctrl) - lis3_reg_ctrl(lis3, LIS3_REG_ON); - - lis3->read(lis3, WHO_AM_I, ®); - if (reg != lis3->whoami) - printk(KERN_ERR "lis3: power on failure\n"); - - /* power up the device */ - ret = lis3->read(lis3, CTRL_REG1, ®); - if (ret < 0) - return ret; - - reg |= CTRL1_PD0 | CTRL1_Xen | CTRL1_Yen | CTRL1_Zen; - return lis3->write(lis3, CTRL_REG1, reg); -} - -/* Default axis mapping but it can be overwritten by platform data */ -static union axis_conversion lis3lv02d_axis_map = - { .as_array = { LIS3_DEV_X, LIS3_DEV_Y, LIS3_DEV_Z } }; - -static int __devinit lis3lv02d_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - int ret = 0; - struct lis3lv02d_platform_data *pdata = client->dev.platform_data; - - if (pdata) { - /* Regulator control is optional */ - if (pdata->driver_features & LIS3_USE_REGULATOR_CTRL) - lis3_dev.reg_ctrl = lis3_reg_ctrl; - - if ((pdata->driver_features & LIS3_USE_BLOCK_READ) && - (i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_I2C_BLOCK))) - lis3_dev.blkread = lis3_i2c_blockread; - - if (pdata->axis_x) - lis3lv02d_axis_map.x = pdata->axis_x; - - if (pdata->axis_y) - lis3lv02d_axis_map.y = pdata->axis_y; - - if (pdata->axis_z) - lis3lv02d_axis_map.z = pdata->axis_z; - - if (pdata->setup_resources) - ret = pdata->setup_resources(); - - if (ret) - goto fail; - } - - if (lis3_dev.reg_ctrl) { - lis3_dev.regulators[0].supply = reg_vdd; - lis3_dev.regulators[1].supply = reg_vdd_io; - ret = regulator_bulk_get(&client->dev, - ARRAY_SIZE(lis3_dev.regulators), - lis3_dev.regulators); - if (ret < 0) - goto fail; - } - - lis3_dev.pdata = pdata; - lis3_dev.bus_priv = client; - lis3_dev.init = lis3_i2c_init; - lis3_dev.read = lis3_i2c_read; - lis3_dev.write = lis3_i2c_write; - lis3_dev.irq = client->irq; - lis3_dev.ac = lis3lv02d_axis_map; - lis3_dev.pm_dev = &client->dev; - - i2c_set_clientdata(client, &lis3_dev); - - /* Provide power over the init call */ - if (lis3_dev.reg_ctrl) - lis3_reg_ctrl(&lis3_dev, LIS3_REG_ON); - - ret = lis3lv02d_init_device(&lis3_dev); - - if (lis3_dev.reg_ctrl) - lis3_reg_ctrl(&lis3_dev, LIS3_REG_OFF); - - if (ret == 0) - return 0; -fail: - if (pdata && pdata->release_resources) - pdata->release_resources(); - return ret; -} - -static int __devexit lis3lv02d_i2c_remove(struct i2c_client *client) -{ - struct lis3lv02d *lis3 = i2c_get_clientdata(client); - struct lis3lv02d_platform_data *pdata = client->dev.platform_data; - - if (pdata && pdata->release_resources) - pdata->release_resources(); - - lis3lv02d_joystick_disable(); - lis3lv02d_remove_fs(&lis3_dev); - - if (lis3_dev.reg_ctrl) - regulator_bulk_free(ARRAY_SIZE(lis3->regulators), - lis3_dev.regulators); - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int lis3lv02d_i2c_suspend(struct device *dev) -{ - struct i2c_client *client = container_of(dev, struct i2c_client, dev); - struct lis3lv02d *lis3 = i2c_get_clientdata(client); - - if (!lis3->pdata || !lis3->pdata->wakeup_flags) - lis3lv02d_poweroff(lis3); - return 0; -} - -static int lis3lv02d_i2c_resume(struct device *dev) -{ - struct i2c_client *client = container_of(dev, struct i2c_client, dev); - struct lis3lv02d *lis3 = i2c_get_clientdata(client); - - /* - * pm_runtime documentation says that devices should always - * be powered on at resume. Pm_runtime turns them off after system - * wide resume is complete. - */ - if (!lis3->pdata || !lis3->pdata->wakeup_flags || - pm_runtime_suspended(dev)) - lis3lv02d_poweron(lis3); - - return 0; -} -#endif /* CONFIG_PM_SLEEP */ - -#ifdef CONFIG_PM_RUNTIME -static int lis3_i2c_runtime_suspend(struct device *dev) -{ - struct i2c_client *client = container_of(dev, struct i2c_client, dev); - struct lis3lv02d *lis3 = i2c_get_clientdata(client); - - lis3lv02d_poweroff(lis3); - return 0; -} - -static int lis3_i2c_runtime_resume(struct device *dev) -{ - struct i2c_client *client = container_of(dev, struct i2c_client, dev); - struct lis3lv02d *lis3 = i2c_get_clientdata(client); - - lis3lv02d_poweron(lis3); - return 0; -} -#endif /* CONFIG_PM_RUNTIME */ - -static const struct i2c_device_id lis3lv02d_id[] = { - {"lis3lv02d", 0 }, - {} -}; - -MODULE_DEVICE_TABLE(i2c, lis3lv02d_id); - -static const struct dev_pm_ops lis3_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(lis3lv02d_i2c_suspend, - lis3lv02d_i2c_resume) - SET_RUNTIME_PM_OPS(lis3_i2c_runtime_suspend, - lis3_i2c_runtime_resume, - NULL) -}; - -static struct i2c_driver lis3lv02d_i2c_driver = { - .driver = { - .name = DRV_NAME, - .owner = THIS_MODULE, - .pm = &lis3_pm_ops, - }, - .probe = lis3lv02d_i2c_probe, - .remove = __devexit_p(lis3lv02d_i2c_remove), - .id_table = lis3lv02d_id, -}; - -static int __init lis3lv02d_init(void) -{ - return i2c_add_driver(&lis3lv02d_i2c_driver); -} - -static void __exit lis3lv02d_exit(void) -{ - i2c_del_driver(&lis3lv02d_i2c_driver); -} - -MODULE_AUTHOR("Nokia Corporation"); -MODULE_DESCRIPTION("lis3lv02d I2C interface"); -MODULE_LICENSE("GPL"); - -module_init(lis3lv02d_init); -module_exit(lis3lv02d_exit); diff --git a/drivers/hwmon/lis3lv02d_spi.c b/drivers/hwmon/lis3lv02d_spi.c deleted file mode 100644 index c1f8a8f..0000000 --- a/drivers/hwmon/lis3lv02d_spi.c +++ /dev/null @@ -1,145 +0,0 @@ -/* - * lis3lv02d_spi - SPI glue layer for lis3lv02d - * - * Copyright (c) 2009 Daniel Mack - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * publishhed by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "lis3lv02d.h" - -#define DRV_NAME "lis3lv02d_spi" -#define LIS3_SPI_READ 0x80 - -static int lis3_spi_read(struct lis3lv02d *lis3, int reg, u8 *v) -{ - struct spi_device *spi = lis3->bus_priv; - int ret = spi_w8r8(spi, reg | LIS3_SPI_READ); - if (ret < 0) - return -EINVAL; - - *v = (u8) ret; - return 0; -} - -static int lis3_spi_write(struct lis3lv02d *lis3, int reg, u8 val) -{ - u8 tmp[2] = { reg, val }; - struct spi_device *spi = lis3->bus_priv; - return spi_write(spi, tmp, sizeof(tmp)); -} - -static int lis3_spi_init(struct lis3lv02d *lis3) -{ - u8 reg; - int ret; - - /* power up the device */ - ret = lis3->read(lis3, CTRL_REG1, ®); - if (ret < 0) - return ret; - - reg |= CTRL1_PD0 | CTRL1_Xen | CTRL1_Yen | CTRL1_Zen; - return lis3->write(lis3, CTRL_REG1, reg); -} - -static union axis_conversion lis3lv02d_axis_normal = - { .as_array = { 1, 2, 3 } }; - -static int __devinit lis302dl_spi_probe(struct spi_device *spi) -{ - int ret; - - spi->bits_per_word = 8; - spi->mode = SPI_MODE_0; - ret = spi_setup(spi); - if (ret < 0) - return ret; - - lis3_dev.bus_priv = spi; - lis3_dev.init = lis3_spi_init; - lis3_dev.read = lis3_spi_read; - lis3_dev.write = lis3_spi_write; - lis3_dev.irq = spi->irq; - lis3_dev.ac = lis3lv02d_axis_normal; - lis3_dev.pdata = spi->dev.platform_data; - spi_set_drvdata(spi, &lis3_dev); - - return lis3lv02d_init_device(&lis3_dev); -} - -static int __devexit lis302dl_spi_remove(struct spi_device *spi) -{ - struct lis3lv02d *lis3 = spi_get_drvdata(spi); - lis3lv02d_joystick_disable(); - lis3lv02d_poweroff(lis3); - - return lis3lv02d_remove_fs(&lis3_dev); -} - -#ifdef CONFIG_PM_SLEEP -static int lis3lv02d_spi_suspend(struct device *dev) -{ - struct spi_device *spi = to_spi_device(dev); - struct lis3lv02d *lis3 = spi_get_drvdata(spi); - - if (!lis3->pdata || !lis3->pdata->wakeup_flags) - lis3lv02d_poweroff(&lis3_dev); - - return 0; -} - -static int lis3lv02d_spi_resume(struct device *dev) -{ - struct spi_device *spi = to_spi_device(dev); - struct lis3lv02d *lis3 = spi_get_drvdata(spi); - - if (!lis3->pdata || !lis3->pdata->wakeup_flags) - lis3lv02d_poweron(lis3); - - return 0; -} -#endif - -static SIMPLE_DEV_PM_OPS(lis3lv02d_spi_pm, lis3lv02d_spi_suspend, - lis3lv02d_spi_resume); - -static struct spi_driver lis302dl_spi_driver = { - .driver = { - .name = DRV_NAME, - .owner = THIS_MODULE, - .pm = &lis3lv02d_spi_pm, - }, - .probe = lis302dl_spi_probe, - .remove = __devexit_p(lis302dl_spi_remove), -}; - -static int __init lis302dl_init(void) -{ - return spi_register_driver(&lis302dl_spi_driver); -} - -static void __exit lis302dl_exit(void) -{ - spi_unregister_driver(&lis302dl_spi_driver); -} - -module_init(lis302dl_init); -module_exit(lis302dl_exit); - -MODULE_AUTHOR("Daniel Mack "); -MODULE_DESCRIPTION("lis3lv02d SPI glue layer"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("spi:" DRV_NAME); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index b7d5ef2..203500d 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 98009cc..804f421 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 0000000..8f474e6 --- /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 0000000..4bf58b1 --- /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/misc/lis3lv02d/lis3lv02d.c b/drivers/misc/lis3lv02d/lis3lv02d.c new file mode 100644 index 0000000..b928bc1 --- /dev/null +++ b/drivers/misc/lis3lv02d/lis3lv02d.c @@ -0,0 +1,999 @@ +/* + * lis3lv02d.c - ST LIS3LV02DL accelerometer driver + * + * Copyright (C) 2007-2008 Yan Burman + * Copyright (C) 2008 Eric Piel + * Copyright (C) 2008-2009 Pavel Machek + * + * 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 +#include +#include +#include +#include +#include +#include "lis3lv02d.h" + +#define DRIVER_NAME "lis3lv02d" + +/* joystick device poll interval in milliseconds */ +#define MDPS_POLL_INTERVAL 50 +#define MDPS_POLL_MIN 0 +#define MDPS_POLL_MAX 2000 + +#define LIS3_SYSFS_POWERDOWN_DELAY 5000 /* In milliseconds */ + +#define SELFTEST_OK 0 +#define SELFTEST_FAIL -1 +#define SELFTEST_IRQ -2 + +#define IRQ_LINE0 0 +#define IRQ_LINE1 1 + +/* + * The sensor can also generate interrupts (DRDY) but it's pretty pointless + * because they are generated even if the data do not change. So it's better + * to keep the interrupt for the free-fall event. The values are updated at + * 40Hz (at the lowest frequency), but as it can be pretty time consuming on + * some low processor, we poll the sensor only at 20Hz... enough for the + * joystick. + */ + +#define LIS3_PWRON_DELAY_WAI_12B (5000) +#define LIS3_PWRON_DELAY_WAI_8B (3000) + +/* + * LIS3LV02D spec says 1024 LSBs corresponds 1 G -> 1LSB is 1000/1024 mG + * LIS302D spec says: 18 mG / digit + * LIS3_ACCURACY is used to increase accuracy of the intermediate + * calculation results. + */ +#define LIS3_ACCURACY 1024 +/* Sensitivity values for -2G +2G scale */ +#define LIS3_SENSITIVITY_12B ((LIS3_ACCURACY * 1000) / 1024) +#define LIS3_SENSITIVITY_8B (18 * LIS3_ACCURACY) + +#define LIS3_DEFAULT_FUZZ_12B 3 +#define LIS3_DEFAULT_FLAT_12B 3 +#define LIS3_DEFAULT_FUZZ_8B 1 +#define LIS3_DEFAULT_FLAT_8B 1 + +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 + * over the axis array size + */ +static int param_set_axis(const char *val, const struct kernel_param *kp) +{ + int ret = param_set_int(val, kp); + if (!ret) { + int val = *(int *)kp->arg; + if (val < 0) + val = -val; + if (!val || val > 3) + return -EINVAL; + } + return ret; +} + +static struct kernel_param_ops param_ops_axis = { + .set = param_set_axis, + .get = param_get_int, +}; + +module_param_array_named(axes, lis3_dev.ac.as_array, axis, NULL, 0644); +MODULE_PARM_DESC(axes, "Axis-mapping for x,y,z directions"); + +static s16 lis3lv02d_read_8(struct lis3lv02d *lis3, int reg) +{ + s8 lo; + if (lis3->read(lis3, reg, &lo) < 0) + return 0; + + return lo; +} + +static s16 lis3lv02d_read_12(struct lis3lv02d *lis3, int reg) +{ + u8 lo, hi; + + lis3->read(lis3, reg - 1, &lo); + lis3->read(lis3, reg, &hi); + /* In "12 bit right justified" mode, bit 6, bit 7, bit 8 = bit 5 */ + return (s16)((hi << 8) | lo); +} + +/** + * lis3lv02d_get_axis - For the given axis, give the value converted + * @axis: 1,2,3 - can also be negative + * @hw_values: raw values returned by the hardware + * + * Returns the converted value. + */ +static inline int lis3lv02d_get_axis(s8 axis, int hw_values[3]) +{ + if (axis > 0) + return hw_values[axis - 1]; + else + return -hw_values[-axis - 1]; +} + +/** + * lis3lv02d_get_xyz - Get X, Y and Z axis values from the accelerometer + * @lis3: pointer to the device struct + * @x: where to store the X axis value + * @y: where to store the Y axis value + * @z: where to store the Z axis value + * + * Note that 40Hz input device can eat up about 10% CPU at 800MHZ + */ +static void lis3lv02d_get_xyz(struct lis3lv02d *lis3, int *x, int *y, int *z) +{ + int position[3]; + int i; + + if (lis3->blkread) { + if (lis3_dev.whoami == WAI_12B) { + u16 data[3]; + lis3->blkread(lis3, OUTX_L, 6, (u8 *)data); + for (i = 0; i < 3; i++) + position[i] = (s16)le16_to_cpu(data[i]); + } else { + u8 data[5]; + /* Data: x, dummy, y, dummy, z */ + lis3->blkread(lis3, OUTX, 5, data); + for (i = 0; i < 3; i++) + position[i] = (s8)data[i * 2]; + } + } else { + position[0] = lis3->read_data(lis3, OUTX); + position[1] = lis3->read_data(lis3, OUTY); + position[2] = lis3->read_data(lis3, OUTZ); + } + + for (i = 0; i < 3; i++) + position[i] = (position[i] * lis3->scale) / LIS3_ACCURACY; + + *x = lis3lv02d_get_axis(lis3->ac.x, position); + *y = lis3lv02d_get_axis(lis3->ac.y, position); + *z = lis3lv02d_get_axis(lis3->ac.z, position); +} + +/* conversion btw sampling rate and the register values */ +static int lis3_12_rates[4] = {40, 160, 640, 2560}; +static int lis3_8_rates[2] = {100, 400}; +static int lis3_3dc_rates[16] = {0, 1, 10, 25, 50, 100, 200, 400, 1600, 5000}; + +/* ODR is Output Data Rate */ +static int lis3lv02d_get_odr(void) +{ + u8 ctrl; + int shift; + + lis3_dev.read(&lis3_dev, CTRL_REG1, &ctrl); + ctrl &= lis3_dev.odr_mask; + shift = ffs(lis3_dev.odr_mask) - 1; + return lis3_dev.odrs[(ctrl >> shift)]; +} + +static int lis3lv02d_set_odr(int rate) +{ + u8 ctrl; + int i, len, shift; + + if (!rate) + return -EINVAL; + + lis3_dev.read(&lis3_dev, CTRL_REG1, &ctrl); + ctrl &= ~lis3_dev.odr_mask; + len = 1 << hweight_long(lis3_dev.odr_mask); /* # of possible values */ + shift = ffs(lis3_dev.odr_mask) - 1; + + for (i = 0; i < len; i++) + if (lis3_dev.odrs[i] == rate) { + lis3_dev.write(&lis3_dev, CTRL_REG1, + ctrl | (i << shift)); + return 0; + } + return -EINVAL; +} + +static int lis3lv02d_selftest(struct lis3lv02d *lis3, s16 results[3]) +{ + u8 ctlreg, reg; + s16 x, y, z; + u8 selftest; + int ret; + u8 ctrl_reg_data; + unsigned char irq_cfg; + + mutex_lock(&lis3->mutex); + + irq_cfg = lis3->irq_cfg; + if (lis3_dev.whoami == WAI_8B) { + lis3->data_ready_count[IRQ_LINE0] = 0; + lis3->data_ready_count[IRQ_LINE1] = 0; + + /* Change interrupt cfg to data ready for selftest */ + atomic_inc(&lis3_dev.wake_thread); + lis3->irq_cfg = LIS3_IRQ1_DATA_READY | LIS3_IRQ2_DATA_READY; + lis3->read(lis3, CTRL_REG3, &ctrl_reg_data); + lis3->write(lis3, CTRL_REG3, (ctrl_reg_data & + ~(LIS3_IRQ1_MASK | LIS3_IRQ2_MASK)) | + (LIS3_IRQ1_DATA_READY | LIS3_IRQ2_DATA_READY)); + } + + if (lis3_dev.whoami == WAI_3DC) { + ctlreg = CTRL_REG4; + selftest = CTRL4_ST0; + } else { + ctlreg = CTRL_REG1; + if (lis3_dev.whoami == WAI_12B) + selftest = CTRL1_ST; + else + selftest = CTRL1_STP; + } + + lis3->read(lis3, ctlreg, ®); + lis3->write(lis3, ctlreg, (reg | selftest)); + msleep(lis3->pwron_delay / lis3lv02d_get_odr()); + + /* Read directly to avoid axis remap */ + x = lis3->read_data(lis3, OUTX); + y = lis3->read_data(lis3, OUTY); + z = lis3->read_data(lis3, OUTZ); + + /* back to normal settings */ + lis3->write(lis3, ctlreg, reg); + msleep(lis3->pwron_delay / lis3lv02d_get_odr()); + + results[0] = x - lis3->read_data(lis3, OUTX); + results[1] = y - lis3->read_data(lis3, OUTY); + results[2] = z - lis3->read_data(lis3, OUTZ); + + ret = 0; + + if (lis3_dev.whoami == WAI_8B) { + /* Restore original interrupt configuration */ + atomic_dec(&lis3_dev.wake_thread); + lis3->write(lis3, CTRL_REG3, ctrl_reg_data); + lis3->irq_cfg = irq_cfg; + + if ((irq_cfg & LIS3_IRQ1_MASK) && + lis3->data_ready_count[IRQ_LINE0] < 2) { + ret = SELFTEST_IRQ; + goto fail; + } + + if ((irq_cfg & LIS3_IRQ2_MASK) && + lis3->data_ready_count[IRQ_LINE1] < 2) { + ret = SELFTEST_IRQ; + goto fail; + } + } + + if (lis3->pdata) { + int i; + for (i = 0; i < 3; i++) { + /* Check against selftest acceptance limits */ + if ((results[i] < lis3->pdata->st_min_limits[i]) || + (results[i] > lis3->pdata->st_max_limits[i])) { + ret = SELFTEST_FAIL; + goto fail; + } + } + } + + /* test passed */ +fail: + mutex_unlock(&lis3->mutex); + return ret; +} + +/* + * Order of registers in the list affects to order of the restore process. + * Perhaps it is a good idea to set interrupt enable register as a last one + * after all other configurations + */ +static u8 lis3_wai8_regs[] = { FF_WU_CFG_1, FF_WU_THS_1, FF_WU_DURATION_1, + FF_WU_CFG_2, FF_WU_THS_2, FF_WU_DURATION_2, + CLICK_CFG, CLICK_SRC, CLICK_THSY_X, CLICK_THSZ, + CLICK_TIMELIMIT, CLICK_LATENCY, CLICK_WINDOW, + CTRL_REG1, CTRL_REG2, CTRL_REG3}; + +static u8 lis3_wai12_regs[] = {FF_WU_CFG, FF_WU_THS_L, FF_WU_THS_H, + FF_WU_DURATION, DD_CFG, DD_THSI_L, DD_THSI_H, + DD_THSE_L, DD_THSE_H, + CTRL_REG1, CTRL_REG3, CTRL_REG2}; + +static inline void lis3_context_save(struct lis3lv02d *lis3) +{ + int i; + for (i = 0; i < lis3->regs_size; i++) + lis3->read(lis3, lis3->regs[i], &lis3->reg_cache[i]); + lis3->regs_stored = true; +} + +static inline void lis3_context_restore(struct lis3lv02d *lis3) +{ + int i; + if (lis3->regs_stored) + for (i = 0; i < lis3->regs_size; i++) + lis3->write(lis3, lis3->regs[i], lis3->reg_cache[i]); +} + +void lis3lv02d_poweroff(struct lis3lv02d *lis3) +{ + if (lis3->reg_ctrl) + lis3_context_save(lis3); + /* disable X,Y,Z axis and power down */ + lis3->write(lis3, CTRL_REG1, 0x00); + if (lis3->reg_ctrl) + lis3->reg_ctrl(lis3, LIS3_REG_OFF); +} +EXPORT_SYMBOL_GPL(lis3lv02d_poweroff); + +void lis3lv02d_poweron(struct lis3lv02d *lis3) +{ + u8 reg; + + lis3->init(lis3); + + /* + * Common configuration + * BDU: (12 bits sensors only) LSB and MSB values are not updated until + * both have been read. So the value read will always be correct. + * Set BOOT bit to refresh factory tuning values. + */ + lis3->read(lis3, CTRL_REG2, ®); + if (lis3->whoami == WAI_12B) + reg |= CTRL2_BDU | CTRL2_BOOT; + else + reg |= CTRL2_BOOT_8B; + lis3->write(lis3, CTRL_REG2, reg); + + /* LIS3 power on delay is quite long */ + msleep(lis3->pwron_delay / lis3lv02d_get_odr()); + + if (lis3->reg_ctrl) + lis3_context_restore(lis3); +} +EXPORT_SYMBOL_GPL(lis3lv02d_poweron); + + +static void lis3lv02d_joystick_poll(struct input_polled_dev *pidev) +{ + int x, y, z; + + mutex_lock(&lis3_dev.mutex); + lis3lv02d_get_xyz(&lis3_dev, &x, &y, &z); + input_report_abs(pidev->input, ABS_X, x); + input_report_abs(pidev->input, ABS_Y, y); + input_report_abs(pidev->input, ABS_Z, z); + input_sync(pidev->input); + mutex_unlock(&lis3_dev.mutex); +} + +static void lis3lv02d_joystick_open(struct input_polled_dev *pidev) +{ + if (lis3_dev.pm_dev) + pm_runtime_get_sync(lis3_dev.pm_dev); + + if (lis3_dev.pdata && lis3_dev.whoami == WAI_8B && lis3_dev.idev) + atomic_set(&lis3_dev.wake_thread, 1); + /* + * Update coordinates for the case where poll interval is 0 and + * the chip in running purely under interrupt control + */ + lis3lv02d_joystick_poll(pidev); +} + +static void lis3lv02d_joystick_close(struct input_polled_dev *pidev) +{ + atomic_set(&lis3_dev.wake_thread, 0); + if (lis3_dev.pm_dev) + pm_runtime_put(lis3_dev.pm_dev); +} + +static irqreturn_t lis302dl_interrupt(int irq, void *dummy) +{ + if (!test_bit(0, &lis3_dev.misc_opened)) + goto out; + + /* + * Be careful: on some HP laptops the bios force DD when on battery and + * the lid is closed. This leads to interrupts as soon as a little move + * is done. + */ + atomic_inc(&lis3_dev.count); + + wake_up_interruptible(&lis3_dev.misc_wait); + kill_fasync(&lis3_dev.async_queue, SIGIO, POLL_IN); +out: + if (atomic_read(&lis3_dev.wake_thread)) + return IRQ_WAKE_THREAD; + return IRQ_HANDLED; +} + +static void lis302dl_interrupt_handle_click(struct lis3lv02d *lis3) +{ + struct input_dev *dev = lis3->idev->input; + u8 click_src; + + mutex_lock(&lis3->mutex); + lis3->read(lis3, CLICK_SRC, &click_src); + + if (click_src & CLICK_SINGLE_X) { + input_report_key(dev, lis3->mapped_btns[0], 1); + input_report_key(dev, lis3->mapped_btns[0], 0); + } + + if (click_src & CLICK_SINGLE_Y) { + input_report_key(dev, lis3->mapped_btns[1], 1); + input_report_key(dev, lis3->mapped_btns[1], 0); + } + + if (click_src & CLICK_SINGLE_Z) { + input_report_key(dev, lis3->mapped_btns[2], 1); + input_report_key(dev, lis3->mapped_btns[2], 0); + } + input_sync(dev); + mutex_unlock(&lis3->mutex); +} + +static inline void lis302dl_data_ready(struct lis3lv02d *lis3, int index) +{ + int dummy; + + /* Dummy read to ack interrupt */ + lis3lv02d_get_xyz(lis3, &dummy, &dummy, &dummy); + lis3->data_ready_count[index]++; +} + +static irqreturn_t lis302dl_interrupt_thread1_8b(int irq, void *data) +{ + struct lis3lv02d *lis3 = data; + u8 irq_cfg = lis3->irq_cfg & LIS3_IRQ1_MASK; + + if (irq_cfg == LIS3_IRQ1_CLICK) + lis302dl_interrupt_handle_click(lis3); + else if (unlikely(irq_cfg == LIS3_IRQ1_DATA_READY)) + lis302dl_data_ready(lis3, IRQ_LINE0); + else + lis3lv02d_joystick_poll(lis3->idev); + + return IRQ_HANDLED; +} + +static irqreturn_t lis302dl_interrupt_thread2_8b(int irq, void *data) +{ + struct lis3lv02d *lis3 = data; + u8 irq_cfg = lis3->irq_cfg & LIS3_IRQ2_MASK; + + if (irq_cfg == LIS3_IRQ2_CLICK) + lis302dl_interrupt_handle_click(lis3); + else if (unlikely(irq_cfg == LIS3_IRQ2_DATA_READY)) + lis302dl_data_ready(lis3, IRQ_LINE1); + else + lis3lv02d_joystick_poll(lis3->idev); + + return IRQ_HANDLED; +} + +static int lis3lv02d_misc_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &lis3_dev.misc_opened)) + return -EBUSY; /* already open */ + + if (lis3_dev.pm_dev) + pm_runtime_get_sync(lis3_dev.pm_dev); + + atomic_set(&lis3_dev.count, 0); + return 0; +} + +static int lis3lv02d_misc_release(struct inode *inode, struct file *file) +{ + fasync_helper(-1, file, 0, &lis3_dev.async_queue); + clear_bit(0, &lis3_dev.misc_opened); /* release the device */ + if (lis3_dev.pm_dev) + pm_runtime_put(lis3_dev.pm_dev); + return 0; +} + +static ssize_t lis3lv02d_misc_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + DECLARE_WAITQUEUE(wait, current); + u32 data; + unsigned char byte_data; + ssize_t retval = 1; + + if (count < 1) + return -EINVAL; + + add_wait_queue(&lis3_dev.misc_wait, &wait); + while (true) { + set_current_state(TASK_INTERRUPTIBLE); + data = atomic_xchg(&lis3_dev.count, 0); + if (data) + break; + + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto out; + } + + if (signal_pending(current)) { + retval = -ERESTARTSYS; + goto out; + } + + schedule(); + } + + if (data < 255) + byte_data = data; + else + byte_data = 255; + + /* make sure we are not going into copy_to_user() with + * TASK_INTERRUPTIBLE state */ + set_current_state(TASK_RUNNING); + if (copy_to_user(buf, &byte_data, sizeof(byte_data))) + retval = -EFAULT; + +out: + __set_current_state(TASK_RUNNING); + remove_wait_queue(&lis3_dev.misc_wait, &wait); + + return retval; +} + +static unsigned int lis3lv02d_misc_poll(struct file *file, poll_table *wait) +{ + poll_wait(file, &lis3_dev.misc_wait, wait); + if (atomic_read(&lis3_dev.count)) + return POLLIN | POLLRDNORM; + return 0; +} + +static int lis3lv02d_misc_fasync(int fd, struct file *file, int on) +{ + return fasync_helper(fd, file, on, &lis3_dev.async_queue); +} + +static const struct file_operations lis3lv02d_misc_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = lis3lv02d_misc_read, + .open = lis3lv02d_misc_open, + .release = lis3lv02d_misc_release, + .poll = lis3lv02d_misc_poll, + .fasync = lis3lv02d_misc_fasync, +}; + +static struct miscdevice lis3lv02d_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "freefall", + .fops = &lis3lv02d_misc_fops, +}; + +int lis3lv02d_joystick_enable(void) +{ + struct input_dev *input_dev; + int err; + int max_val, fuzz, flat; + int btns[] = {BTN_X, BTN_Y, BTN_Z}; + + if (lis3_dev.idev) + return -EINVAL; + + lis3_dev.idev = input_allocate_polled_device(); + if (!lis3_dev.idev) + return -ENOMEM; + + lis3_dev.idev->poll = lis3lv02d_joystick_poll; + lis3_dev.idev->open = lis3lv02d_joystick_open; + lis3_dev.idev->close = lis3lv02d_joystick_close; + lis3_dev.idev->poll_interval = MDPS_POLL_INTERVAL; + lis3_dev.idev->poll_interval_min = MDPS_POLL_MIN; + lis3_dev.idev->poll_interval_max = MDPS_POLL_MAX; + input_dev = lis3_dev.idev->input; + + input_dev->name = "ST LIS3LV02DL Accelerometer"; + input_dev->phys = DRIVER_NAME "/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0; + input_dev->dev.parent = &lis3_dev.pdev->dev; + + set_bit(EV_ABS, input_dev->evbit); + max_val = (lis3_dev.mdps_max_val * lis3_dev.scale) / LIS3_ACCURACY; + if (lis3_dev.whoami == WAI_12B) { + fuzz = LIS3_DEFAULT_FUZZ_12B; + flat = LIS3_DEFAULT_FLAT_12B; + } else { + fuzz = LIS3_DEFAULT_FUZZ_8B; + flat = LIS3_DEFAULT_FLAT_8B; + } + fuzz = (fuzz * lis3_dev.scale) / LIS3_ACCURACY; + flat = (flat * lis3_dev.scale) / LIS3_ACCURACY; + + input_set_abs_params(input_dev, ABS_X, -max_val, max_val, fuzz, flat); + input_set_abs_params(input_dev, ABS_Y, -max_val, max_val, fuzz, flat); + input_set_abs_params(input_dev, ABS_Z, -max_val, max_val, fuzz, flat); + + lis3_dev.mapped_btns[0] = lis3lv02d_get_axis(abs(lis3_dev.ac.x), btns); + lis3_dev.mapped_btns[1] = lis3lv02d_get_axis(abs(lis3_dev.ac.y), btns); + lis3_dev.mapped_btns[2] = lis3lv02d_get_axis(abs(lis3_dev.ac.z), btns); + + err = input_register_polled_device(lis3_dev.idev); + if (err) { + input_free_polled_device(lis3_dev.idev); + lis3_dev.idev = NULL; + } + + return err; +} +EXPORT_SYMBOL_GPL(lis3lv02d_joystick_enable); + +void lis3lv02d_joystick_disable(void) +{ + if (lis3_dev.irq) + free_irq(lis3_dev.irq, &lis3_dev); + if (lis3_dev.pdata && lis3_dev.pdata->irq2) + free_irq(lis3_dev.pdata->irq2, &lis3_dev); + + if (!lis3_dev.idev) + return; + + if (lis3_dev.irq) + misc_deregister(&lis3lv02d_misc_device); + input_unregister_polled_device(lis3_dev.idev); + input_free_polled_device(lis3_dev.idev); + lis3_dev.idev = NULL; +} +EXPORT_SYMBOL_GPL(lis3lv02d_joystick_disable); + +/* Sysfs stuff */ +static void lis3lv02d_sysfs_poweron(struct lis3lv02d *lis3) +{ + /* + * SYSFS functions are fast visitors so put-call + * immediately after the get-call. However, keep + * chip running for a while and schedule delayed + * suspend. This way periodic sysfs calls doesn't + * suffer from relatively long power up time. + */ + + if (lis3->pm_dev) { + pm_runtime_get_sync(lis3->pm_dev); + pm_runtime_put_noidle(lis3->pm_dev); + pm_schedule_suspend(lis3->pm_dev, LIS3_SYSFS_POWERDOWN_DELAY); + } +} + +static ssize_t lis3lv02d_selftest_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + s16 values[3]; + + static const char ok[] = "OK"; + static const char fail[] = "FAIL"; + static const char irq[] = "FAIL_IRQ"; + const char *res; + + lis3lv02d_sysfs_poweron(&lis3_dev); + switch (lis3lv02d_selftest(&lis3_dev, values)) { + case SELFTEST_FAIL: + res = fail; + break; + case SELFTEST_IRQ: + res = irq; + break; + case SELFTEST_OK: + default: + res = ok; + break; + } + return sprintf(buf, "%s %d %d %d\n", res, + values[0], values[1], values[2]); +} + +static ssize_t lis3lv02d_position_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int x, y, z; + + lis3lv02d_sysfs_poweron(&lis3_dev); + mutex_lock(&lis3_dev.mutex); + lis3lv02d_get_xyz(&lis3_dev, &x, &y, &z); + mutex_unlock(&lis3_dev.mutex); + return sprintf(buf, "(%d,%d,%d)\n", x, y, z); +} + +static ssize_t lis3lv02d_rate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + lis3lv02d_sysfs_poweron(&lis3_dev); + return sprintf(buf, "%d\n", lis3lv02d_get_odr()); +} + +static ssize_t lis3lv02d_rate_set(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + unsigned long rate; + + if (strict_strtoul(buf, 0, &rate)) + return -EINVAL; + + lis3lv02d_sysfs_poweron(&lis3_dev); + if (lis3lv02d_set_odr(rate)) + return -EINVAL; + + return count; +} + +static DEVICE_ATTR(selftest, S_IRUSR, lis3lv02d_selftest_show, NULL); +static DEVICE_ATTR(position, S_IRUGO, lis3lv02d_position_show, NULL); +static DEVICE_ATTR(rate, S_IRUGO | S_IWUSR, lis3lv02d_rate_show, + lis3lv02d_rate_set); + +static struct attribute *lis3lv02d_attributes[] = { + &dev_attr_selftest.attr, + &dev_attr_position.attr, + &dev_attr_rate.attr, + NULL +}; + +static struct attribute_group lis3lv02d_attribute_group = { + .attrs = lis3lv02d_attributes +}; + + +static int lis3lv02d_add_fs(struct lis3lv02d *lis3) +{ + lis3->pdev = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0); + if (IS_ERR(lis3->pdev)) + return PTR_ERR(lis3->pdev); + + return sysfs_create_group(&lis3->pdev->dev.kobj, &lis3lv02d_attribute_group); +} + +int lis3lv02d_remove_fs(struct lis3lv02d *lis3) +{ + sysfs_remove_group(&lis3->pdev->dev.kobj, &lis3lv02d_attribute_group); + platform_device_unregister(lis3->pdev); + if (lis3->pm_dev) { + /* Barrier after the sysfs remove */ + pm_runtime_barrier(lis3->pm_dev); + + /* SYSFS may have left chip running. Turn off if necessary */ + if (!pm_runtime_suspended(lis3->pm_dev)) + lis3lv02d_poweroff(&lis3_dev); + + pm_runtime_disable(lis3->pm_dev); + pm_runtime_set_suspended(lis3->pm_dev); + } + kfree(lis3->reg_cache); + return 0; +} +EXPORT_SYMBOL_GPL(lis3lv02d_remove_fs); + +static void lis3lv02d_8b_configure(struct lis3lv02d *dev, + struct lis3lv02d_platform_data *p) +{ + int err; + int ctrl2 = p->hipass_ctrl; + + if (p->click_flags) { + dev->write(dev, CLICK_CFG, p->click_flags); + dev->write(dev, CLICK_TIMELIMIT, p->click_time_limit); + dev->write(dev, CLICK_LATENCY, p->click_latency); + dev->write(dev, CLICK_WINDOW, p->click_window); + dev->write(dev, CLICK_THSZ, p->click_thresh_z & 0xf); + dev->write(dev, CLICK_THSY_X, + (p->click_thresh_x & 0xf) | + (p->click_thresh_y << 4)); + + if (dev->idev) { + struct input_dev *input_dev = lis3_dev.idev->input; + input_set_capability(input_dev, EV_KEY, BTN_X); + input_set_capability(input_dev, EV_KEY, BTN_Y); + input_set_capability(input_dev, EV_KEY, BTN_Z); + } + } + + if (p->wakeup_flags) { + dev->write(dev, FF_WU_CFG_1, p->wakeup_flags); + dev->write(dev, FF_WU_THS_1, p->wakeup_thresh & 0x7f); + /* pdata value + 1 to keep this backward compatible*/ + dev->write(dev, FF_WU_DURATION_1, p->duration1 + 1); + ctrl2 ^= HP_FF_WU1; /* Xor to keep compatible with old pdata*/ + } + + if (p->wakeup_flags2) { + dev->write(dev, FF_WU_CFG_2, p->wakeup_flags2); + dev->write(dev, FF_WU_THS_2, p->wakeup_thresh2 & 0x7f); + /* pdata value + 1 to keep this backward compatible*/ + dev->write(dev, FF_WU_DURATION_2, p->duration2 + 1); + ctrl2 ^= HP_FF_WU2; /* Xor to keep compatible with old pdata*/ + } + /* Configure hipass filters */ + dev->write(dev, CTRL_REG2, ctrl2); + + if (p->irq2) { + err = request_threaded_irq(p->irq2, + NULL, + lis302dl_interrupt_thread2_8b, + IRQF_TRIGGER_RISING | IRQF_ONESHOT | + (p->irq_flags2 & IRQF_TRIGGER_MASK), + DRIVER_NAME, &lis3_dev); + if (err < 0) + pr_err("No second IRQ. Limited functionality\n"); + } +} + +/* + * Initialise the accelerometer and the various subsystems. + * Should be rather independent of the bus system. + */ +int lis3lv02d_init_device(struct lis3lv02d *dev) +{ + int err; + irq_handler_t thread_fn; + int irq_flags = 0; + + dev->whoami = lis3lv02d_read_8(dev, WHO_AM_I); + + switch (dev->whoami) { + case WAI_12B: + pr_info("12 bits sensor found\n"); + dev->read_data = lis3lv02d_read_12; + dev->mdps_max_val = 2048; + dev->pwron_delay = LIS3_PWRON_DELAY_WAI_12B; + dev->odrs = lis3_12_rates; + dev->odr_mask = CTRL1_DF0 | CTRL1_DF1; + dev->scale = LIS3_SENSITIVITY_12B; + dev->regs = lis3_wai12_regs; + dev->regs_size = ARRAY_SIZE(lis3_wai12_regs); + break; + case WAI_8B: + pr_info("8 bits sensor found\n"); + dev->read_data = lis3lv02d_read_8; + dev->mdps_max_val = 128; + dev->pwron_delay = LIS3_PWRON_DELAY_WAI_8B; + dev->odrs = lis3_8_rates; + dev->odr_mask = CTRL1_DR; + dev->scale = LIS3_SENSITIVITY_8B; + dev->regs = lis3_wai8_regs; + dev->regs_size = ARRAY_SIZE(lis3_wai8_regs); + break; + case WAI_3DC: + pr_info("8 bits 3DC sensor found\n"); + dev->read_data = lis3lv02d_read_8; + dev->mdps_max_val = 128; + dev->pwron_delay = LIS3_PWRON_DELAY_WAI_8B; + dev->odrs = lis3_3dc_rates; + dev->odr_mask = CTRL1_ODR0|CTRL1_ODR1|CTRL1_ODR2|CTRL1_ODR3; + dev->scale = LIS3_SENSITIVITY_8B; + break; + default: + pr_err("unknown sensor type 0x%X\n", dev->whoami); + return -EINVAL; + } + + dev->reg_cache = kzalloc(max(sizeof(lis3_wai8_regs), + sizeof(lis3_wai12_regs)), GFP_KERNEL); + + if (dev->reg_cache == NULL) { + printk(KERN_ERR DRIVER_NAME "out of memory\n"); + return -ENOMEM; + } + + mutex_init(&dev->mutex); + atomic_set(&dev->wake_thread, 0); + + lis3lv02d_add_fs(dev); + lis3lv02d_poweron(dev); + + if (dev->pm_dev) { + pm_runtime_set_active(dev->pm_dev); + pm_runtime_enable(dev->pm_dev); + } + + if (lis3lv02d_joystick_enable()) + pr_err("joystick initialization failed\n"); + + /* passing in platform specific data is purely optional and only + * used by the SPI transport layer at the moment */ + if (dev->pdata) { + struct lis3lv02d_platform_data *p = dev->pdata; + + if (dev->whoami == WAI_8B) + lis3lv02d_8b_configure(dev, p); + + irq_flags = p->irq_flags1 & IRQF_TRIGGER_MASK; + + dev->irq_cfg = p->irq_cfg; + if (p->irq_cfg) + dev->write(dev, CTRL_REG3, p->irq_cfg); + + if (p->default_rate) + lis3lv02d_set_odr(p->default_rate); + } + + /* bail if we did not get an IRQ from the bus layer */ + if (!dev->irq) { + pr_debug("No IRQ. Disabling /dev/freefall\n"); + goto out; + } + + /* + * The sensor can generate interrupts for free-fall and direction + * detection (distinguishable with FF_WU_SRC and DD_SRC) but to keep + * the things simple and _fast_ we activate it only for free-fall, so + * no need to read register (very slow with ACPI). For the same reason, + * we forbid shared interrupts. + * + * IRQF_TRIGGER_RISING seems pointless on HP laptops because the + * io-apic is not configurable (and generates a warning) but I keep it + * in case of support for other hardware. + */ + if (dev->pdata && dev->whoami == WAI_8B) + thread_fn = lis302dl_interrupt_thread1_8b; + else + thread_fn = NULL; + + err = request_threaded_irq(dev->irq, lis302dl_interrupt, + thread_fn, + IRQF_TRIGGER_RISING | IRQF_ONESHOT | + irq_flags, + DRIVER_NAME, &lis3_dev); + + if (err < 0) { + pr_err("Cannot get IRQ\n"); + goto out; + } + + if (misc_register(&lis3lv02d_misc_device)) + pr_err("misc_register failed\n"); +out: + return 0; +} +EXPORT_SYMBOL_GPL(lis3lv02d_init_device); + +MODULE_DESCRIPTION("ST LIS3LV02Dx three-axis digital accelerometer driver"); +MODULE_AUTHOR("Yan Burman, Eric Piel, Pavel Machek"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/lis3lv02d/lis3lv02d.h b/drivers/misc/lis3lv02d/lis3lv02d.h new file mode 100644 index 0000000..a193958 --- /dev/null +++ b/drivers/misc/lis3lv02d/lis3lv02d.h @@ -0,0 +1,291 @@ +/* + * lis3lv02d.h - ST LIS3LV02DL accelerometer driver + * + * Copyright (C) 2007-2008 Yan Burman + * Copyright (C) 2008-2009 Eric Piel + * + * 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 + */ +#include +#include +#include + +/* + * This driver tries to support the "digital" accelerometer chips from + * STMicroelectronics such as LIS3LV02DL, LIS302DL, LIS3L02DQ, LIS331DL, + * LIS35DE, or LIS202DL. They are very similar in terms of programming, with + * almost the same registers. In addition to differing on physical properties, + * they differ on the number of axes (2/3), precision (8/12 bits), and special + * features (freefall detection, click...). Unfortunately, not all the + * differences can be probed via a register. + * They can be connected either via I²C or SPI. + */ + +#include + +enum lis3_reg { + WHO_AM_I = 0x0F, + OFFSET_X = 0x16, + OFFSET_Y = 0x17, + OFFSET_Z = 0x18, + GAIN_X = 0x19, + GAIN_Y = 0x1A, + GAIN_Z = 0x1B, + CTRL_REG1 = 0x20, + CTRL_REG2 = 0x21, + CTRL_REG3 = 0x22, + CTRL_REG4 = 0x23, + HP_FILTER_RESET = 0x23, + STATUS_REG = 0x27, + OUTX_L = 0x28, + OUTX_H = 0x29, + OUTX = 0x29, + OUTY_L = 0x2A, + OUTY_H = 0x2B, + OUTY = 0x2B, + OUTZ_L = 0x2C, + OUTZ_H = 0x2D, + OUTZ = 0x2D, +}; + +enum lis302d_reg { + FF_WU_CFG_1 = 0x30, + FF_WU_SRC_1 = 0x31, + FF_WU_THS_1 = 0x32, + FF_WU_DURATION_1 = 0x33, + FF_WU_CFG_2 = 0x34, + FF_WU_SRC_2 = 0x35, + FF_WU_THS_2 = 0x36, + FF_WU_DURATION_2 = 0x37, + CLICK_CFG = 0x38, + CLICK_SRC = 0x39, + CLICK_THSY_X = 0x3B, + CLICK_THSZ = 0x3C, + CLICK_TIMELIMIT = 0x3D, + CLICK_LATENCY = 0x3E, + CLICK_WINDOW = 0x3F, +}; + +enum lis3lv02d_reg { + FF_WU_CFG = 0x30, + FF_WU_SRC = 0x31, + FF_WU_ACK = 0x32, + FF_WU_THS_L = 0x34, + FF_WU_THS_H = 0x35, + FF_WU_DURATION = 0x36, + DD_CFG = 0x38, + DD_SRC = 0x39, + DD_ACK = 0x3A, + DD_THSI_L = 0x3C, + DD_THSI_H = 0x3D, + DD_THSE_L = 0x3E, + DD_THSE_H = 0x3F, +}; + +enum lis3_who_am_i { + WAI_3DC = 0x33, /* 8 bits: LIS3DC, HP3DC */ + WAI_12B = 0x3A, /* 12 bits: LIS3LV02D[LQ]... */ + WAI_8B = 0x3B, /* 8 bits: LIS[23]02D[LQ]... */ + WAI_6B = 0x52, /* 6 bits: LIS331DLF - not supported */ +}; + +enum lis3lv02d_ctrl1_12b { + CTRL1_Xen = 0x01, + CTRL1_Yen = 0x02, + CTRL1_Zen = 0x04, + CTRL1_ST = 0x08, + CTRL1_DF0 = 0x10, + CTRL1_DF1 = 0x20, + CTRL1_PD0 = 0x40, + CTRL1_PD1 = 0x80, +}; + +/* Delta to ctrl1_12b version */ +enum lis3lv02d_ctrl1_8b { + CTRL1_STM = 0x08, + CTRL1_STP = 0x10, + CTRL1_FS = 0x20, + CTRL1_PD = 0x40, + CTRL1_DR = 0x80, +}; + +enum lis3lv02d_ctrl1_3dc { + CTRL1_ODR0 = 0x10, + CTRL1_ODR1 = 0x20, + CTRL1_ODR2 = 0x40, + CTRL1_ODR3 = 0x80, +}; + +enum lis3lv02d_ctrl2 { + CTRL2_DAS = 0x01, + CTRL2_SIM = 0x02, + CTRL2_DRDY = 0x04, + CTRL2_IEN = 0x08, + CTRL2_BOOT = 0x10, + CTRL2_BLE = 0x20, + CTRL2_BDU = 0x40, /* Block Data Update */ + CTRL2_FS = 0x80, /* Full Scale selection */ +}; + +enum lis3lv02d_ctrl4_3dc { + CTRL4_SIM = 0x01, + CTRL4_ST0 = 0x02, + CTRL4_ST1 = 0x04, + CTRL4_FS0 = 0x10, + CTRL4_FS1 = 0x20, +}; + +enum lis302d_ctrl2 { + HP_FF_WU2 = 0x08, + HP_FF_WU1 = 0x04, + CTRL2_BOOT_8B = 0x40, +}; + +enum lis3lv02d_ctrl3 { + CTRL3_CFS0 = 0x01, + CTRL3_CFS1 = 0x02, + CTRL3_FDS = 0x10, + CTRL3_HPFF = 0x20, + CTRL3_HPDD = 0x40, + CTRL3_ECK = 0x80, +}; + +enum lis3lv02d_status_reg { + STATUS_XDA = 0x01, + STATUS_YDA = 0x02, + STATUS_ZDA = 0x04, + STATUS_XYZDA = 0x08, + STATUS_XOR = 0x10, + STATUS_YOR = 0x20, + STATUS_ZOR = 0x40, + STATUS_XYZOR = 0x80, +}; + +enum lis3lv02d_ff_wu_cfg { + FF_WU_CFG_XLIE = 0x01, + FF_WU_CFG_XHIE = 0x02, + FF_WU_CFG_YLIE = 0x04, + FF_WU_CFG_YHIE = 0x08, + FF_WU_CFG_ZLIE = 0x10, + FF_WU_CFG_ZHIE = 0x20, + FF_WU_CFG_LIR = 0x40, + FF_WU_CFG_AOI = 0x80, +}; + +enum lis3lv02d_ff_wu_src { + FF_WU_SRC_XL = 0x01, + FF_WU_SRC_XH = 0x02, + FF_WU_SRC_YL = 0x04, + FF_WU_SRC_YH = 0x08, + FF_WU_SRC_ZL = 0x10, + FF_WU_SRC_ZH = 0x20, + FF_WU_SRC_IA = 0x40, +}; + +enum lis3lv02d_dd_cfg { + DD_CFG_XLIE = 0x01, + DD_CFG_XHIE = 0x02, + DD_CFG_YLIE = 0x04, + DD_CFG_YHIE = 0x08, + DD_CFG_ZLIE = 0x10, + DD_CFG_ZHIE = 0x20, + DD_CFG_LIR = 0x40, + DD_CFG_IEND = 0x80, +}; + +enum lis3lv02d_dd_src { + DD_SRC_XL = 0x01, + DD_SRC_XH = 0x02, + DD_SRC_YL = 0x04, + DD_SRC_YH = 0x08, + DD_SRC_ZL = 0x10, + DD_SRC_ZH = 0x20, + DD_SRC_IA = 0x40, +}; + +enum lis3lv02d_click_src_8b { + CLICK_SINGLE_X = 0x01, + CLICK_DOUBLE_X = 0x02, + CLICK_SINGLE_Y = 0x04, + CLICK_DOUBLE_Y = 0x08, + CLICK_SINGLE_Z = 0x10, + CLICK_DOUBLE_Z = 0x20, + CLICK_IA = 0x40, +}; + +enum lis3lv02d_reg_state { + LIS3_REG_OFF = 0x00, + LIS3_REG_ON = 0x01, +}; + +union axis_conversion { + struct { + int x, y, z; + }; + int as_array[3]; + +}; + +struct lis3lv02d { + void *bus_priv; /* used by the bus layer only */ + struct device *pm_dev; /* for pm_runtime purposes */ + int (*init) (struct lis3lv02d *lis3); + int (*write) (struct lis3lv02d *lis3, int reg, u8 val); + int (*read) (struct lis3lv02d *lis3, int reg, u8 *ret); + int (*blkread) (struct lis3lv02d *lis3, int reg, int len, u8 *ret); + int (*reg_ctrl) (struct lis3lv02d *lis3, bool state); + + int *odrs; /* Supported output data rates */ + u8 *regs; /* Regs to store / restore */ + int regs_size; + u8 *reg_cache; + bool regs_stored; + u8 odr_mask; /* ODR bit mask */ + u8 whoami; /* indicates measurement precision */ + s16 (*read_data) (struct lis3lv02d *lis3, int reg); + int mdps_max_val; + int pwron_delay; + int scale; /* + * relationship between 1 LBS and mG + * (1/1000th of earth gravity) + */ + + struct input_polled_dev *idev; /* input device */ + struct platform_device *pdev; /* platform device */ + struct regulator_bulk_data regulators[2]; + atomic_t count; /* interrupt count after last read */ + union axis_conversion ac; /* hw -> logical axis */ + int mapped_btns[3]; + + u32 irq; /* IRQ number */ + struct fasync_struct *async_queue; /* queue for the misc device */ + wait_queue_head_t misc_wait; /* Wait queue for the misc device */ + unsigned long misc_opened; /* bit0: whether the device is open */ + int data_ready_count[2]; + atomic_t wake_thread; + unsigned char irq_cfg; + + struct lis3lv02d_platform_data *pdata; /* for passing board config */ + struct mutex mutex; /* Serialize poll and selftest */ +}; + +int lis3lv02d_init_device(struct lis3lv02d *lis3); +int lis3lv02d_joystick_enable(void); +void lis3lv02d_joystick_disable(void); +void lis3lv02d_poweroff(struct lis3lv02d *lis3); +void lis3lv02d_poweron(struct lis3lv02d *lis3); +int lis3lv02d_remove_fs(struct lis3lv02d *lis3); + +extern struct lis3lv02d lis3_dev; diff --git a/drivers/misc/lis3lv02d/lis3lv02d_i2c.c b/drivers/misc/lis3lv02d/lis3lv02d_i2c.c new file mode 100644 index 0000000..b20dfb4 --- /dev/null +++ b/drivers/misc/lis3lv02d/lis3lv02d_i2c.c @@ -0,0 +1,279 @@ +/* + * drivers/hwmon/lis3lv02d_i2c.c + * + * Implements I2C interface for lis3lv02d (STMicroelectronics) accelerometer. + * Driver is based on corresponding SPI driver written by Daniel Mack + * (lis3lv02d_spi.c (C) 2009 Daniel Mack ). + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + * + * Contact: Samu Onkalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include "lis3lv02d.h" + +#define DRV_NAME "lis3lv02d_i2c" + +static const char reg_vdd[] = "Vdd"; +static const char reg_vdd_io[] = "Vdd_IO"; + +static int lis3_reg_ctrl(struct lis3lv02d *lis3, bool state) +{ + int ret; + if (state == LIS3_REG_OFF) { + ret = regulator_bulk_disable(ARRAY_SIZE(lis3->regulators), + lis3->regulators); + } else { + ret = regulator_bulk_enable(ARRAY_SIZE(lis3->regulators), + lis3->regulators); + /* Chip needs time to wakeup. Not mentioned in datasheet */ + usleep_range(10000, 20000); + } + return ret; +} + +static inline s32 lis3_i2c_write(struct lis3lv02d *lis3, int reg, u8 value) +{ + struct i2c_client *c = lis3->bus_priv; + return i2c_smbus_write_byte_data(c, reg, value); +} + +static inline s32 lis3_i2c_read(struct lis3lv02d *lis3, int reg, u8 *v) +{ + struct i2c_client *c = lis3->bus_priv; + *v = i2c_smbus_read_byte_data(c, reg); + return 0; +} + +static inline s32 lis3_i2c_blockread(struct lis3lv02d *lis3, int reg, int len, + u8 *v) +{ + struct i2c_client *c = lis3->bus_priv; + reg |= (1 << 7); /* 7th bit enables address auto incrementation */ + return i2c_smbus_read_i2c_block_data(c, reg, len, v); +} + +static int lis3_i2c_init(struct lis3lv02d *lis3) +{ + u8 reg; + int ret; + + if (lis3->reg_ctrl) + lis3_reg_ctrl(lis3, LIS3_REG_ON); + + lis3->read(lis3, WHO_AM_I, ®); + if (reg != lis3->whoami) + printk(KERN_ERR "lis3: power on failure\n"); + + /* power up the device */ + ret = lis3->read(lis3, CTRL_REG1, ®); + if (ret < 0) + return ret; + + reg |= CTRL1_PD0 | CTRL1_Xen | CTRL1_Yen | CTRL1_Zen; + return lis3->write(lis3, CTRL_REG1, reg); +} + +/* Default axis mapping but it can be overwritten by platform data */ +static union axis_conversion lis3lv02d_axis_map = + { .as_array = { LIS3_DEV_X, LIS3_DEV_Y, LIS3_DEV_Z } }; + +static int __devinit lis3lv02d_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct lis3lv02d_platform_data *pdata = client->dev.platform_data; + + if (pdata) { + /* Regulator control is optional */ + if (pdata->driver_features & LIS3_USE_REGULATOR_CTRL) + lis3_dev.reg_ctrl = lis3_reg_ctrl; + + if ((pdata->driver_features & LIS3_USE_BLOCK_READ) && + (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_I2C_BLOCK))) + lis3_dev.blkread = lis3_i2c_blockread; + + if (pdata->axis_x) + lis3lv02d_axis_map.x = pdata->axis_x; + + if (pdata->axis_y) + lis3lv02d_axis_map.y = pdata->axis_y; + + if (pdata->axis_z) + lis3lv02d_axis_map.z = pdata->axis_z; + + if (pdata->setup_resources) + ret = pdata->setup_resources(); + + if (ret) + goto fail; + } + + if (lis3_dev.reg_ctrl) { + lis3_dev.regulators[0].supply = reg_vdd; + lis3_dev.regulators[1].supply = reg_vdd_io; + ret = regulator_bulk_get(&client->dev, + ARRAY_SIZE(lis3_dev.regulators), + lis3_dev.regulators); + if (ret < 0) + goto fail; + } + + lis3_dev.pdata = pdata; + lis3_dev.bus_priv = client; + lis3_dev.init = lis3_i2c_init; + lis3_dev.read = lis3_i2c_read; + lis3_dev.write = lis3_i2c_write; + lis3_dev.irq = client->irq; + lis3_dev.ac = lis3lv02d_axis_map; + lis3_dev.pm_dev = &client->dev; + + i2c_set_clientdata(client, &lis3_dev); + + /* Provide power over the init call */ + if (lis3_dev.reg_ctrl) + lis3_reg_ctrl(&lis3_dev, LIS3_REG_ON); + + ret = lis3lv02d_init_device(&lis3_dev); + + if (lis3_dev.reg_ctrl) + lis3_reg_ctrl(&lis3_dev, LIS3_REG_OFF); + + if (ret == 0) + return 0; +fail: + if (pdata && pdata->release_resources) + pdata->release_resources(); + return ret; +} + +static int __devexit lis3lv02d_i2c_remove(struct i2c_client *client) +{ + struct lis3lv02d *lis3 = i2c_get_clientdata(client); + struct lis3lv02d_platform_data *pdata = client->dev.platform_data; + + if (pdata && pdata->release_resources) + pdata->release_resources(); + + lis3lv02d_joystick_disable(); + lis3lv02d_remove_fs(&lis3_dev); + + if (lis3_dev.reg_ctrl) + regulator_bulk_free(ARRAY_SIZE(lis3->regulators), + lis3_dev.regulators); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int lis3lv02d_i2c_suspend(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct lis3lv02d *lis3 = i2c_get_clientdata(client); + + if (!lis3->pdata || !lis3->pdata->wakeup_flags) + lis3lv02d_poweroff(lis3); + return 0; +} + +static int lis3lv02d_i2c_resume(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct lis3lv02d *lis3 = i2c_get_clientdata(client); + + /* + * pm_runtime documentation says that devices should always + * be powered on at resume. Pm_runtime turns them off after system + * wide resume is complete. + */ + if (!lis3->pdata || !lis3->pdata->wakeup_flags || + pm_runtime_suspended(dev)) + lis3lv02d_poweron(lis3); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM_RUNTIME +static int lis3_i2c_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct lis3lv02d *lis3 = i2c_get_clientdata(client); + + lis3lv02d_poweroff(lis3); + return 0; +} + +static int lis3_i2c_runtime_resume(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct lis3lv02d *lis3 = i2c_get_clientdata(client); + + lis3lv02d_poweron(lis3); + return 0; +} +#endif /* CONFIG_PM_RUNTIME */ + +static const struct i2c_device_id lis3lv02d_id[] = { + {"lis3lv02d", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, lis3lv02d_id); + +static const struct dev_pm_ops lis3_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(lis3lv02d_i2c_suspend, + lis3lv02d_i2c_resume) + SET_RUNTIME_PM_OPS(lis3_i2c_runtime_suspend, + lis3_i2c_runtime_resume, + NULL) +}; + +static struct i2c_driver lis3lv02d_i2c_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .pm = &lis3_pm_ops, + }, + .probe = lis3lv02d_i2c_probe, + .remove = __devexit_p(lis3lv02d_i2c_remove), + .id_table = lis3lv02d_id, +}; + +static int __init lis3lv02d_init(void) +{ + return i2c_add_driver(&lis3lv02d_i2c_driver); +} + +static void __exit lis3lv02d_exit(void) +{ + i2c_del_driver(&lis3lv02d_i2c_driver); +} + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("lis3lv02d I2C interface"); +MODULE_LICENSE("GPL"); + +module_init(lis3lv02d_init); +module_exit(lis3lv02d_exit); diff --git a/drivers/misc/lis3lv02d/lis3lv02d_spi.c b/drivers/misc/lis3lv02d/lis3lv02d_spi.c new file mode 100644 index 0000000..c1f8a8f --- /dev/null +++ b/drivers/misc/lis3lv02d/lis3lv02d_spi.c @@ -0,0 +1,145 @@ +/* + * lis3lv02d_spi - SPI glue layer for lis3lv02d + * + * Copyright (c) 2009 Daniel Mack + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * publishhed by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lis3lv02d.h" + +#define DRV_NAME "lis3lv02d_spi" +#define LIS3_SPI_READ 0x80 + +static int lis3_spi_read(struct lis3lv02d *lis3, int reg, u8 *v) +{ + struct spi_device *spi = lis3->bus_priv; + int ret = spi_w8r8(spi, reg | LIS3_SPI_READ); + if (ret < 0) + return -EINVAL; + + *v = (u8) ret; + return 0; +} + +static int lis3_spi_write(struct lis3lv02d *lis3, int reg, u8 val) +{ + u8 tmp[2] = { reg, val }; + struct spi_device *spi = lis3->bus_priv; + return spi_write(spi, tmp, sizeof(tmp)); +} + +static int lis3_spi_init(struct lis3lv02d *lis3) +{ + u8 reg; + int ret; + + /* power up the device */ + ret = lis3->read(lis3, CTRL_REG1, ®); + if (ret < 0) + return ret; + + reg |= CTRL1_PD0 | CTRL1_Xen | CTRL1_Yen | CTRL1_Zen; + return lis3->write(lis3, CTRL_REG1, reg); +} + +static union axis_conversion lis3lv02d_axis_normal = + { .as_array = { 1, 2, 3 } }; + +static int __devinit lis302dl_spi_probe(struct spi_device *spi) +{ + int ret; + + spi->bits_per_word = 8; + spi->mode = SPI_MODE_0; + ret = spi_setup(spi); + if (ret < 0) + return ret; + + lis3_dev.bus_priv = spi; + lis3_dev.init = lis3_spi_init; + lis3_dev.read = lis3_spi_read; + lis3_dev.write = lis3_spi_write; + lis3_dev.irq = spi->irq; + lis3_dev.ac = lis3lv02d_axis_normal; + lis3_dev.pdata = spi->dev.platform_data; + spi_set_drvdata(spi, &lis3_dev); + + return lis3lv02d_init_device(&lis3_dev); +} + +static int __devexit lis302dl_spi_remove(struct spi_device *spi) +{ + struct lis3lv02d *lis3 = spi_get_drvdata(spi); + lis3lv02d_joystick_disable(); + lis3lv02d_poweroff(lis3); + + return lis3lv02d_remove_fs(&lis3_dev); +} + +#ifdef CONFIG_PM_SLEEP +static int lis3lv02d_spi_suspend(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct lis3lv02d *lis3 = spi_get_drvdata(spi); + + if (!lis3->pdata || !lis3->pdata->wakeup_flags) + lis3lv02d_poweroff(&lis3_dev); + + return 0; +} + +static int lis3lv02d_spi_resume(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct lis3lv02d *lis3 = spi_get_drvdata(spi); + + if (!lis3->pdata || !lis3->pdata->wakeup_flags) + lis3lv02d_poweron(lis3); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(lis3lv02d_spi_pm, lis3lv02d_spi_suspend, + lis3lv02d_spi_resume); + +static struct spi_driver lis302dl_spi_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .pm = &lis3lv02d_spi_pm, + }, + .probe = lis302dl_spi_probe, + .remove = __devexit_p(lis302dl_spi_remove), +}; + +static int __init lis302dl_init(void) +{ + return spi_register_driver(&lis302dl_spi_driver); +} + +static void __exit lis302dl_exit(void) +{ + spi_unregister_driver(&lis302dl_spi_driver); +} + +module_init(lis302dl_init); +module_exit(lis302dl_exit); + +MODULE_AUTHOR("Daniel Mack "); +MODULE_DESCRIPTION("lis3lv02d SPI glue layer"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("spi:" DRV_NAME); diff --git a/drivers/platform/x86/hp_accel.c b/drivers/platform/x86/hp_accel.c index 23f09c1..0a52711 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" -- cgit v0.10.2 From 96b4b9bfea28e38c38d26ca47e82ac0fbe2f28b6 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: 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 diff --git a/drivers/platform/x86/hp_accel.c b/drivers/platform/x86/hp_accel.c index 0a52711..1b52d00 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 ------------------------------------ */ -- cgit v0.10.2 From 05e82fe40faee8499b4e3ba12fddaaf013d84203 Mon Sep 17 00:00:00 2001 From: Len Sorensen Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: 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 diff --git a/Documentation/hwmon/lm75 b/Documentation/hwmon/lm75 index 8e6356f..a179040 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 47621ab..19d72bc 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 f36eb80..638dd05 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; } -- cgit v0.10.2 From e76f67b5babc65cd620d395a1fd231409808df90 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: 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 diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index 638dd05..ef902d5 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) -- cgit v0.10.2 From 93d0cc588345a9de304285781e010e298f74e06e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: hwmon: (abituguru*) Update my email address Signed-off-by: Hans de Goede Signed-off-by: Jean Delvare diff --git a/MAINTAINERS b/MAINTAINERS index 4edb2a8..666287a 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 8f07a9d..0e05aa1 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 48d21e2..034cebf 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"); -- cgit v0.10.2 From a98d506c08ffe754fa013c7f70c4d578b991fb4b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: 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 diff --git a/Documentation/hwmon/sch5627 b/Documentation/hwmon/sch5627 new file mode 100644 index 0000000..446a054 --- /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 666287a..b8a123f 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 19d72bc..6fad9f0 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 c068f82..4d0122c 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 0000000..9a51dcc --- /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); -- cgit v0.10.2 From 8c22a8f57516275afcd81c84f3724ac08cf6aa7b Mon Sep 17 00:00:00 2001 From: Dirk Eibach Date: Mon, 21 Mar 2011 17:59:36 +0100 Subject: hwmon: Add support for Texas Instruments ADS1015 Signed-off-by: Dirk Eibach Signed-off-by: Jean Delvare diff --git a/Documentation/devicetree/bindings/hwmon/ads1015.txt b/Documentation/devicetree/bindings/hwmon/ads1015.txt new file mode 100644 index 0000000..0f30616 --- /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 0000000..56ee797 --- /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 b8a123f..38077a6 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 6fad9f0..e4bd13b 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 4d0122c..54ca593 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 0000000..9e1585c --- /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 0000000..8541c6a --- /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 */ -- cgit v0.10.2 From fdf241a8ed93236915c70717a4b6dfb856274496 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Mon, 21 Mar 2011 17:59:37 +0100 Subject: hwmon: (ads1015) Drop dynamic attribute group It is cheaper to handle attributes individually. Signed-off-by: Jean Delvare Acked-by: Dirk Eibach diff --git a/drivers/hwmon/ads1015.c b/drivers/hwmon/ads1015.c index 9e1585c..fa02f20 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: -- cgit v0.10.2 From c0046867f34bb81ec3f237ebbc5241ae678b8379 Mon Sep 17 00:00:00 2001 From: Dirk Eibach Date: Mon, 21 Mar 2011 17:59:37 +0100 Subject: 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 diff --git a/Documentation/devicetree/bindings/hwmon/ads1015.txt b/Documentation/devicetree/bindings/hwmon/ads1015.txt index 0f30616..918a507 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. - -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. + 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. + +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 + +1) The /ads1015 node + + Required properties: + + - 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 56ee797..f6fe9c2 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 fa02f20..e9beeda 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 8541c6a..d5aa2a0 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 */ -- cgit v0.10.2