diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2010-10-26 03:25:31 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2010-10-26 03:25:31 +0400 |
commit | b20f9e5bddddb5ef0d743d6e0d409ffc8cf9fc56 (patch) | |
tree | 7654b53ebb9af77b5daeab696d1d48069ae631a1 | |
parent | 5260562754c0aa4b95eebb1f851eaccce7286365 (diff) | |
parent | b11e7b3f3b56119194234085d42a633ceabd6aba (diff) | |
download | linux-b20f9e5bddddb5ef0d743d6e0d409ffc8cf9fc56.tar.xz |
Merge branch 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/staging
* 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/staging: (24 commits)
hwmon: lis3: Release resources in case of failure
hwmon: lis3: Short explanations of platform data fields
hwmon: lis3: Enhance lis3 selftest with IRQ line test
hwmon: lis3: use block read to access data registers
hwmon: lis3: Adjust fuzziness for 8 bit device
hwmon: lis3: New parameters to platform data
hwmon: lis3: restore axis enabled bits
hwmon: lis3: Power on corrections
hwmon: lis3: Update coordinates at polled device open
hwmon: lis3: Cleanup interrupt handling
hwmon: lis3: regulator control
hwmon: lis3: pm_runtime support
Kirkwood: add fan support for Network Space Max v2
hwmon: add generic GPIO fan driver
hwmon: (coretemp) fix reading of microcode revision (v2)
hwmon: ({core, pkg, via-cpu}temp) remove unnecessary CONFIG_HOTPLUG_CPU ifdefs
hwmon: (pkgtemp) align driver initialization style with coretemp
hwmon: LTC4261 Hardware monitoring driver
hwmon: (lis3) add axes module parameter for custom axis-mapping
hwmon: (hp_accel) Add HP Mini 510x family support
...
-rw-r--r-- | Documentation/hwmon/ltc4261 | 63 | ||||
-rw-r--r-- | MAINTAINERS | 7 | ||||
-rw-r--r-- | arch/arm/mach-kirkwood/netspace_v2-setup.c | 43 | ||||
-rw-r--r-- | drivers/hwmon/Kconfig | 20 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 2 | ||||
-rw-r--r-- | drivers/hwmon/coretemp.c | 28 | ||||
-rw-r--r-- | drivers/hwmon/gpio-fan.c | 558 | ||||
-rw-r--r-- | drivers/hwmon/hp_accel.c | 32 | ||||
-rw-r--r-- | drivers/hwmon/lis3lv02d.c | 364 | ||||
-rw-r--r-- | drivers/hwmon/lis3lv02d.h | 47 | ||||
-rw-r--r-- | drivers/hwmon/lis3lv02d_i2c.c | 132 | ||||
-rw-r--r-- | drivers/hwmon/lis3lv02d_spi.c | 5 | ||||
-rw-r--r-- | drivers/hwmon/ltc4261.c | 315 | ||||
-rw-r--r-- | drivers/hwmon/pkgtemp.c | 32 | ||||
-rw-r--r-- | drivers/hwmon/via-cputemp.c | 11 | ||||
-rw-r--r-- | include/linux/gpio-fan.h | 36 | ||||
-rw-r--r-- | include/linux/lis3lv02d.h | 55 |
17 files changed, 1619 insertions, 131 deletions
diff --git a/Documentation/hwmon/ltc4261 b/Documentation/hwmon/ltc4261 new file mode 100644 index 000000000000..eba2e2c4b94d --- /dev/null +++ b/Documentation/hwmon/ltc4261 @@ -0,0 +1,63 @@ +Kernel driver ltc4261 +===================== + +Supported chips: + * Linear Technology LTC4261 + Prefix: 'ltc4261' + Addresses scanned: - + Datasheet: + http://cds.linear.com/docs/Datasheet/42612fb.pdf + +Author: Guenter Roeck <guenter.roeck@ericsson.com> + + +Description +----------- + +The LTC4261/LTC4261-2 negative voltage Hot Swap controllers allow a board +to be safely inserted and removed from a live backplane. + + +Usage Notes +----------- + +This driver does not probe for LTC4261 devices, since there is no register +which can be safely used to identify the chip. You will have to instantiate +the devices explicitly. + +Example: the following will load the driver for an LTC4261 at address 0x10 +on I2C bus #1: +$ modprobe ltc4261 +$ echo ltc4261 0x10 > /sys/bus/i2c/devices/i2c-1/new_device + + +Sysfs entries +------------- + +Voltage readings provided by this driver are reported as obtained from the ADC +registers. If a set of voltage divider resistors is installed, calculate the +real voltage by multiplying the reported value with (R1+R2)/R2, where R1 is the +value of the divider resistor against the measured voltage and R2 is the value +of the divider resistor against Ground. + +Current reading provided by this driver is reported as obtained from the ADC +Current Sense register. The reported value assumes that a 1 mOhm sense resistor +is installed. If a different sense resistor is installed, calculate the real +current by dividing the reported value by the sense resistor value in mOhm. + +The chip has two voltage sensors, but only one set of voltage alarm status bits. +In many many designs, those alarms are associated with the ADIN2 sensor, due to +the proximity of the ADIN2 pin to the OV pin. ADIN2 is, however, not available +on all chip variants. To ensure that the alarm condition is reported to the user, +report it with both voltage sensors. + +in1_input ADIN2 voltage (mV) +in1_min_alarm ADIN/ADIN2 Undervoltage alarm +in1_max_alarm ADIN/ADIN2 Overvoltage alarm + +in2_input ADIN voltage (mV) +in2_min_alarm ADIN/ADIN2 Undervoltage alarm +in2_max_alarm ADIN/ADIN2 Overvoltage alarm + +curr1_input SENSE current (mA) +curr1_alarm SENSE overcurrent alarm diff --git a/MAINTAINERS b/MAINTAINERS index 541451050b36..146b8a068a4e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3765,6 +3765,13 @@ L: linux-scsi@vger.kernel.org S: Maintained F: drivers/scsi/sym53c8xx_2/ +LTC4261 HARDWARE MONITOR DRIVER +M: Guenter Roeck <linux@roeck-us.net> +L: lm-sensors@lm-sensors.org +S: Maintained +F: Documentation/hwmon/ltc4261 +F: drivers/hwmon/ltc4261.c + LTP (Linux Test Project) M: Rishikesh K Rajak <risrajak@linux.vnet.ibm.com> M: Garrett Cooper <yanegomi@gmail.com> diff --git a/arch/arm/mach-kirkwood/netspace_v2-setup.c b/arch/arm/mach-kirkwood/netspace_v2-setup.c index 5e286441b8f4..5ea66f1f4178 100644 --- a/arch/arm/mach-kirkwood/netspace_v2-setup.c +++ b/arch/arm/mach-kirkwood/netspace_v2-setup.c @@ -30,6 +30,7 @@ #include <linux/gpio.h> #include <linux/gpio_keys.h> #include <linux/leds.h> +#include <linux/gpio-fan.h> #include <asm/mach-types.h> #include <asm/mach/arch.h> #include <mach/kirkwood.h> @@ -137,6 +138,46 @@ static struct platform_device netspace_v2_leds = { }; /***************************************************************************** + * GPIO fan + ****************************************************************************/ + +/* Designed for fan 40x40x16: ADDA AD0412LB-D50 6000rpm@12v */ +static struct gpio_fan_speed netspace_max_v2_fan_speed[] = { + { 0, 0 }, + { 1500, 15 }, + { 1700, 14 }, + { 1800, 13 }, + { 2100, 12 }, + { 3100, 11 }, + { 3300, 10 }, + { 4300, 9 }, + { 5500, 8 }, +}; + +static unsigned netspace_max_v2_fan_ctrl[] = { 22, 7, 33, 23 }; + +static struct gpio_fan_alarm netspace_max_v2_fan_alarm = { + .gpio = 25, + .active_low = 1, +}; + +static struct gpio_fan_platform_data netspace_max_v2_fan_data = { + .num_ctrl = ARRAY_SIZE(netspace_max_v2_fan_ctrl), + .ctrl = netspace_max_v2_fan_ctrl, + .alarm = &netspace_max_v2_fan_alarm, + .num_speed = ARRAY_SIZE(netspace_max_v2_fan_speed), + .speed = netspace_max_v2_fan_speed, +}; + +static struct platform_device netspace_max_v2_gpio_fan = { + .name = "gpio-fan", + .id = -1, + .dev = { + .platform_data = &netspace_max_v2_fan_data, + }, +}; + +/***************************************************************************** * General Setup ****************************************************************************/ @@ -205,6 +246,8 @@ static void __init netspace_v2_init(void) platform_device_register(&netspace_v2_leds); platform_device_register(&netspace_v2_gpio_leds); platform_device_register(&netspace_v2_gpio_buttons); + if (machine_is_netspace_max_v2()) + platform_device_register(&netspace_max_v2_gpio_fan); if (gpio_request(NETSPACE_V2_GPIO_POWER_OFF, "power-off") == 0 && gpio_direction_output(NETSPACE_V2_GPIO_POWER_OFF, 0) == 0) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index e382da3122b7..c357c835eb1e 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -399,6 +399,15 @@ config SENSORS_GL520SM This driver can also be built as a module. If so, the module will be called gl520sm. +config SENSORS_GPIO_FAN + tristate "GPIO fan" + depends on GENERIC_GPIO + help + If you say yes here you get support for fans connected to GPIO lines. + + This driver can also be built as a module. If so, the module + will be called gpio-fan. + config SENSORS_CORETEMP tristate "Intel Core/Core2/Atom temperature sensor" depends on X86 && PCI && EXPERIMENTAL @@ -654,6 +663,17 @@ config SENSORS_LTC4245 This driver can also be built as a module. If so, the module will be called ltc4245. +config SENSORS_LTC4261 + tristate "Linear Technology LTC4261" + depends on I2C && EXPERIMENTAL + default n + help + If you say yes here you get support for Linear Technology LTC4261 + Negative Voltage Hot Swap Controller I2C interface. + + This driver can also be built as a module. If so, the module will + be called ltc4261. + config SENSORS_LM95241 tristate "National Semiconductor LM95241 sensor chip" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index ec9cb735c898..d30f0f6870e0 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o obj-$(CONFIG_SENSORS_G760A) += g760a.o obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o +obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o @@ -79,6 +80,7 @@ obj-$(CONFIG_SENSORS_LM93) += lm93.o obj-$(CONFIG_SENSORS_LM95241) += lm95241.o obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o +obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o obj-$(CONFIG_SENSORS_MAX1111) += max1111.o obj-$(CONFIG_SENSORS_MAX1619) += max1619.o obj-$(CONFIG_SENSORS_MAX6650) += max6650.o diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index a23b17a78ace..42de98d73ff5 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -21,7 +21,6 @@ */ #include <linux/module.h> -#include <linux/delay.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/jiffies.h> @@ -280,11 +279,9 @@ static int __devinit get_tjmax(struct cpuinfo_x86 *c, u32 id, case 0x1a: dev_warn(dev, "TjMax is assumed as 100 C!\n"); return 100000; - break; case 0x17: case 0x1c: /* Atom CPUs */ return adjust_tjmax(c, id, dev); - break; default: dev_warn(dev, "CPU (model=0x%x) is not supported yet," " using default TjMax of 100C.\n", c->x86_model); @@ -292,6 +289,15 @@ static int __devinit get_tjmax(struct cpuinfo_x86 *c, u32 id, } } +static void __devinit get_ucode_rev_on_cpu(void *edx) +{ + u32 eax; + + wrmsr(MSR_IA32_UCODE_REV, 0, 0); + sync_core(); + rdmsr(MSR_IA32_UCODE_REV, eax, *(u32 *)edx); +} + static int __devinit coretemp_probe(struct platform_device *pdev) { struct coretemp_data *data; @@ -327,8 +333,15 @@ static int __devinit coretemp_probe(struct platform_device *pdev) if ((c->x86_model == 0xe) && (c->x86_mask < 0xc)) { /* check for microcode update */ - rdmsr_on_cpu(data->id, MSR_IA32_UCODE_REV, &eax, &edx); - if (edx < 0x39) { + err = smp_call_function_single(data->id, get_ucode_rev_on_cpu, + &edx, 1); + if (err) { + dev_err(&pdev->dev, + "Cannot determine microcode revision of " + "CPU#%u (%d)!\n", data->id, err); + err = -ENODEV; + goto exit_free; + } else if (edx < 0x39) { err = -ENODEV; dev_err(&pdev->dev, "Errata AE18 not fixed, update BIOS or " @@ -490,7 +503,7 @@ exit: return err; } -static void coretemp_device_remove(unsigned int cpu) +static void __cpuinit coretemp_device_remove(unsigned int cpu) { struct pdev_entry *p; unsigned int i; @@ -569,9 +582,8 @@ exit: static void __exit coretemp_exit(void) { struct pdev_entry *p, *n; -#ifdef CONFIG_HOTPLUG_CPU + unregister_hotcpu_notifier(&coretemp_cpu_notifier); -#endif mutex_lock(&pdev_list_mutex); list_for_each_entry_safe(p, n, &pdev_list, list) { platform_device_unregister(p->pdev); diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c new file mode 100644 index 000000000000..aa701a183707 --- /dev/null +++ b/drivers/hwmon/gpio-fan.c @@ -0,0 +1,558 @@ +/* + * gpio-fan.c - Hwmon driver for fans connected to GPIO lines. + * + * Copyright (C) 2010 LaCie + * + * Author: Simon Guinot <sguinot@lacie.com> + * + * 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 <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/hwmon.h> +#include <linux/gpio.h> +#include <linux/gpio-fan.h> + +struct gpio_fan_data { + struct platform_device *pdev; + struct device *hwmon_dev; + struct mutex lock; /* lock GPIOs operations. */ + int num_ctrl; + unsigned *ctrl; + int num_speed; + struct gpio_fan_speed *speed; + int speed_index; +#ifdef CONFIG_PM + int resume_speed; +#endif + bool pwm_enable; + struct gpio_fan_alarm *alarm; + struct work_struct alarm_work; +}; + +/* + * Alarm GPIO. + */ + +static void fan_alarm_notify(struct work_struct *ws) +{ + struct gpio_fan_data *fan_data = + container_of(ws, struct gpio_fan_data, alarm_work); + + sysfs_notify(&fan_data->pdev->dev.kobj, NULL, "fan1_alarm"); + kobject_uevent(&fan_data->pdev->dev.kobj, KOBJ_CHANGE); +} + +static irqreturn_t fan_alarm_irq_handler(int irq, void *dev_id) +{ + struct gpio_fan_data *fan_data = dev_id; + + schedule_work(&fan_data->alarm_work); + + return IRQ_NONE; +} + +static ssize_t show_fan_alarm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + struct gpio_fan_alarm *alarm = fan_data->alarm; + int value = gpio_get_value(alarm->gpio); + + if (alarm->active_low) + value = !value; + + return sprintf(buf, "%d\n", value); +} + +static DEVICE_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL); + +static int fan_alarm_init(struct gpio_fan_data *fan_data, + struct gpio_fan_alarm *alarm) +{ + int err; + int alarm_irq; + struct platform_device *pdev = fan_data->pdev; + + fan_data->alarm = alarm; + + err = gpio_request(alarm->gpio, "GPIO fan alarm"); + if (err) + return err; + + err = gpio_direction_input(alarm->gpio); + if (err) + goto err_free_gpio; + + err = device_create_file(&pdev->dev, &dev_attr_fan1_alarm); + if (err) + goto err_free_gpio; + + /* + * If the alarm GPIO don't support interrupts, just leave + * without initializing the fail notification support. + */ + alarm_irq = gpio_to_irq(alarm->gpio); + if (alarm_irq < 0) + return 0; + + INIT_WORK(&fan_data->alarm_work, fan_alarm_notify); + set_irq_type(alarm_irq, IRQ_TYPE_EDGE_BOTH); + err = request_irq(alarm_irq, fan_alarm_irq_handler, IRQF_SHARED, + "GPIO fan alarm", fan_data); + if (err) + goto err_free_sysfs; + + return 0; + +err_free_sysfs: + device_remove_file(&pdev->dev, &dev_attr_fan1_alarm); +err_free_gpio: + gpio_free(alarm->gpio); + + return err; +} + +static void fan_alarm_free(struct gpio_fan_data *fan_data) +{ + struct platform_device *pdev = fan_data->pdev; + int alarm_irq = gpio_to_irq(fan_data->alarm->gpio); + + if (alarm_irq >= 0) + free_irq(alarm_irq, fan_data); + device_remove_file(&pdev->dev, &dev_attr_fan1_alarm); + gpio_free(fan_data->alarm->gpio); +} + +/* + * Control GPIOs. + */ + +/* Must be called with fan_data->lock held, except during initialization. */ +static void __set_fan_ctrl(struct gpio_fan_data *fan_data, int ctrl_val) +{ + int i; + + for (i = 0; i < fan_data->num_ctrl; i++) + gpio_set_value(fan_data->ctrl[i], (ctrl_val >> i) & 1); +} + +static int __get_fan_ctrl(struct gpio_fan_data *fan_data) +{ + int i; + int ctrl_val = 0; + + for (i = 0; i < fan_data->num_ctrl; i++) { + int value; + + value = gpio_get_value(fan_data->ctrl[i]); + ctrl_val |= (value << i); + } + return ctrl_val; +} + +/* Must be called with fan_data->lock held, except during initialization. */ +static void set_fan_speed(struct gpio_fan_data *fan_data, int speed_index) +{ + if (fan_data->speed_index == speed_index) + return; + + __set_fan_ctrl(fan_data, fan_data->speed[speed_index].ctrl_val); + fan_data->speed_index = speed_index; +} + +static int get_fan_speed_index(struct gpio_fan_data *fan_data) +{ + int ctrl_val = __get_fan_ctrl(fan_data); + int i; + + for (i = 0; i < fan_data->num_speed; i++) + if (fan_data->speed[i].ctrl_val == ctrl_val) + return i; + + dev_warn(&fan_data->pdev->dev, + "missing speed array entry for GPIO value 0x%x\n", ctrl_val); + + return -EINVAL; +} + +static int rpm_to_speed_index(struct gpio_fan_data *fan_data, int rpm) +{ + struct gpio_fan_speed *speed = fan_data->speed; + int i; + + for (i = 0; i < fan_data->num_speed; i++) + if (speed[i].rpm >= rpm) + return i; + + return fan_data->num_speed - 1; +} + +static ssize_t show_pwm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + u8 pwm = fan_data->speed_index * 255 / (fan_data->num_speed - 1); + + return sprintf(buf, "%d\n", pwm); +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + unsigned long pwm; + int speed_index; + int ret = count; + + if (strict_strtoul(buf, 10, &pwm) || pwm > 255) + return -EINVAL; + + mutex_lock(&fan_data->lock); + + if (!fan_data->pwm_enable) { + ret = -EPERM; + goto exit_unlock; + } + + speed_index = DIV_ROUND_UP(pwm * (fan_data->num_speed - 1), 255); + set_fan_speed(fan_data, speed_index); + +exit_unlock: + mutex_unlock(&fan_data->lock); + + return ret; +} + +static ssize_t show_pwm_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", fan_data->pwm_enable); +} + +static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + unsigned long val; + + if (strict_strtoul(buf, 10, &val) || val > 1) + return -EINVAL; + + if (fan_data->pwm_enable == val) + return count; + + mutex_lock(&fan_data->lock); + + fan_data->pwm_enable = val; + + /* Disable manual control mode: set fan at full speed. */ + if (val == 0) + set_fan_speed(fan_data, fan_data->num_speed - 1); + + mutex_unlock(&fan_data->lock); + + return count; +} + +static ssize_t show_pwm_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "0\n"); +} + +static ssize_t show_rpm_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", fan_data->speed[0].rpm); +} + +static ssize_t show_rpm_max(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", + fan_data->speed[fan_data->num_speed - 1].rpm); +} + +static ssize_t show_rpm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", fan_data->speed[fan_data->speed_index].rpm); +} + +static ssize_t set_rpm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + unsigned long rpm; + int ret = count; + + if (strict_strtoul(buf, 10, &rpm)) + return -EINVAL; + + mutex_lock(&fan_data->lock); + + if (!fan_data->pwm_enable) { + ret = -EPERM; + goto exit_unlock; + } + + set_fan_speed(fan_data, rpm_to_speed_index(fan_data, rpm)); + +exit_unlock: + mutex_unlock(&fan_data->lock); + + return ret; +} + +static DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm); +static DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, + show_pwm_enable, set_pwm_enable); +static DEVICE_ATTR(pwm1_mode, S_IRUGO, show_pwm_mode, NULL); +static DEVICE_ATTR(fan1_min, S_IRUGO, show_rpm_min, NULL); +static DEVICE_ATTR(fan1_max, S_IRUGO, show_rpm_max, NULL); +static DEVICE_ATTR(fan1_input, S_IRUGO, show_rpm, NULL); +static DEVICE_ATTR(fan1_target, S_IRUGO | S_IWUSR, show_rpm, set_rpm); + +static struct attribute *gpio_fan_ctrl_attributes[] = { + &dev_attr_pwm1.attr, + &dev_attr_pwm1_enable.attr, + &dev_attr_pwm1_mode.attr, + &dev_attr_fan1_input.attr, + &dev_attr_fan1_target.attr, + &dev_attr_fan1_min.attr, + &dev_attr_fan1_max.attr, + NULL +}; + +static const struct attribute_group gpio_fan_ctrl_group = { + .attrs = gpio_fan_ctrl_attributes, +}; + +static int fan_ctrl_init(struct gpio_fan_data *fan_data, + struct gpio_fan_platform_data *pdata) +{ + struct platform_device *pdev = fan_data->pdev; + int num_ctrl = pdata->num_ctrl; + unsigned *ctrl = pdata->ctrl; + int i, err; + + for (i = 0; i < num_ctrl; i++) { + err = gpio_request(ctrl[i], "GPIO fan control"); + if (err) + goto err_free_gpio; + + err = gpio_direction_output(ctrl[i], gpio_get_value(ctrl[i])); + if (err) { + gpio_free(ctrl[i]); + goto err_free_gpio; + } + } + + err = sysfs_create_group(&pdev->dev.kobj, &gpio_fan_ctrl_group); + if (err) + goto err_free_gpio; + + fan_data->num_ctrl = num_ctrl; + fan_data->ctrl = ctrl; + fan_data->num_speed = pdata->num_speed; + fan_data->speed = pdata->speed; + fan_data->pwm_enable = true; /* Enable manual fan speed control. */ + fan_data->speed_index = get_fan_speed_index(fan_data); + if (fan_data->speed_index < 0) { + err = -ENODEV; + goto err_free_gpio; + } + + return 0; + +err_free_gpio: + for (i = i - 1; i >= 0; i--) + gpio_free(ctrl[i]); + + return err; +} + +static void fan_ctrl_free(struct gpio_fan_data *fan_data) +{ + struct platform_device *pdev = fan_data->pdev; + int i; + + sysfs_remove_group(&pdev->dev.kobj, &gpio_fan_ctrl_group); + for (i = 0; i < fan_data->num_ctrl; i++) + gpio_free(fan_data->ctrl[i]); +} + +/* + * Platform driver. + */ + +static ssize_t show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "gpio-fan\n"); +} + +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + +static int __devinit gpio_fan_probe(struct platform_device *pdev) +{ + int err; + struct gpio_fan_data *fan_data; + struct gpio_fan_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return -EINVAL; + + fan_data = kzalloc(sizeof(struct gpio_fan_data), GFP_KERNEL); + if (!fan_data) + return -ENOMEM; + + fan_data->pdev = pdev; + platform_set_drvdata(pdev, fan_data); + mutex_init(&fan_data->lock); + + /* Configure alarm GPIO if available. */ + if (pdata->alarm) { + err = fan_alarm_init(fan_data, pdata->alarm); + if (err) + goto err_free_data; + } + + /* Configure control GPIOs if available. */ + if (pdata->ctrl && pdata->num_ctrl > 0) { + if (!pdata->speed || pdata->num_speed <= 1) { + err = -EINVAL; + goto err_free_alarm; + } + err = fan_ctrl_init(fan_data, pdata); + if (err) + goto err_free_alarm; + } + + err = device_create_file(&pdev->dev, &dev_attr_name); + if (err) + goto err_free_ctrl; + + /* Make this driver part of hwmon class. */ + fan_data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(fan_data->hwmon_dev)) { + err = PTR_ERR(fan_data->hwmon_dev); + goto err_remove_name; + } + + dev_info(&pdev->dev, "GPIO fan initialized\n"); + + return 0; + +err_remove_name: + device_remove_file(&pdev->dev, &dev_attr_name); +err_free_ctrl: + if (fan_data->ctrl) + fan_ctrl_free(fan_data); +err_free_alarm: + if (fan_data->alarm) + fan_alarm_free(fan_data); +err_free_data: + platform_set_drvdata(pdev, NULL); + kfree(fan_data); + + return err; +} + +static int __devexit gpio_fan_remove(struct platform_device *pdev) +{ + struct gpio_fan_data *fan_data = platform_get_drvdata(pdev); + + hwmon_device_unregister(fan_data->hwmon_dev); + device_remove_file(&pdev->dev, &dev_attr_name); + if (fan_data->alarm) + fan_alarm_free(fan_data); + if (fan_data->ctrl) + fan_ctrl_free(fan_data); + kfree(fan_data); + + return 0; +} + +#ifdef CONFIG_PM +static int gpio_fan_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct gpio_fan_data *fan_data = platform_get_drvdata(pdev); + + if (fan_data->ctrl) { + fan_data->resume_speed = fan_data->speed_index; + set_fan_speed(fan_data, 0); + } + + return 0; +} + +static int gpio_fan_resume(struct platform_device *pdev) +{ + struct gpio_fan_data *fan_data = platform_get_drvdata(pdev); + + if (fan_data->ctrl) + set_fan_speed(fan_data, fan_data->resume_speed); + + return 0; +} +#else +#define gpio_fan_suspend NULL +#define gpio_fan_resume NULL +#endif + +static struct platform_driver gpio_fan_driver = { + .probe = gpio_fan_probe, + .remove = __devexit_p(gpio_fan_remove), + .suspend = gpio_fan_suspend, + .resume = gpio_fan_resume, + .driver = { + .name = "gpio-fan", + }, +}; + +static int __init gpio_fan_init(void) +{ + return platform_driver_register(&gpio_fan_driver); +} + +static void __exit gpio_fan_exit(void) +{ + platform_driver_unregister(&gpio_fan_driver); +} + +module_init(gpio_fan_init); +module_exit(gpio_fan_exit); + +MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>"); +MODULE_DESCRIPTION("GPIO FAN driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:gpio-fan"); diff --git a/drivers/hwmon/hp_accel.c b/drivers/hwmon/hp_accel.c index 36e957532230..a56a78412fcb 100644 --- a/drivers/hwmon/hp_accel.c +++ b/drivers/hwmon/hp_accel.c @@ -146,7 +146,7 @@ int lis3lv02d_acpi_write(struct lis3lv02d *lis3, int reg, u8 val) static int lis3lv02d_dmi_matched(const struct dmi_system_id *dmi) { - lis3_dev.ac = *((struct axis_conversion *)dmi->driver_data); + lis3_dev.ac = *((union axis_conversion *)dmi->driver_data); printk(KERN_INFO DRIVER_NAME ": hardware type %s found.\n", dmi->ident); return 1; @@ -154,16 +154,19 @@ static int lis3lv02d_dmi_matched(const struct dmi_system_id *dmi) /* 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. */ -static struct axis_conversion lis3lv02d_axis_normal = {1, 2, 3}; -static struct axis_conversion lis3lv02d_axis_y_inverted = {1, -2, 3}; -static struct axis_conversion lis3lv02d_axis_x_inverted = {-1, 2, 3}; -static struct axis_conversion lis3lv02d_axis_z_inverted = {1, 2, -3}; -static struct axis_conversion lis3lv02d_axis_xy_swap = {2, 1, 3}; -static struct axis_conversion lis3lv02d_axis_xy_rotated_left = {-2, 1, 3}; -static struct axis_conversion lis3lv02d_axis_xy_rotated_left_usd = {-2, 1, -3}; -static struct axis_conversion lis3lv02d_axis_xy_swap_inverted = {-2, -1, 3}; -static struct axis_conversion lis3lv02d_axis_xy_rotated_right = {2, -1, 3}; -static struct axis_conversion lis3lv02d_axis_xy_swap_yz_inverted = {2, -1, -3}; +#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, \ @@ -222,7 +225,7 @@ static struct dmi_system_id lis3lv02d_dmi_ids[] = { 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("Mini5102", "HP Mini 5102", xy_rotated_left_usd), + AXIS_DMI_MATCH("Mini510x", "HP Mini 510", xy_rotated_left_usd), { NULL, } /* Laptop models without axis info (yet): * "NC6910" "HP Compaq 6910" @@ -299,7 +302,10 @@ static int lis3lv02d_add(struct acpi_device *device) lis3lv02d_enum_resources(device); /* If possible use a "standard" axes order */ - if (dmi_check_system(lis3lv02d_dmi_ids) == 0) { + if (lis3_dev.ac.x && lis3_dev.ac.y && lis3_dev.ac.z) { + printk(KERN_INFO DRIVER_NAME ": 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) { printk(KERN_INFO DRIVER_NAME ": laptop model unknown, " "using default axes configuration\n"); lis3_dev.ac = lis3lv02d_axis_normal; diff --git a/drivers/hwmon/lis3lv02d.c b/drivers/hwmon/lis3lv02d.c index fc591ae53107..0cee73a6124e 100644 --- a/drivers/hwmon/lis3lv02d.c +++ b/drivers/hwmon/lis3lv02d.c @@ -31,9 +31,11 @@ #include <linux/delay.h> #include <linux/wait.h> #include <linux/poll.h> +#include <linux/slab.h> #include <linux/freezer.h> #include <linux/uaccess.h> #include <linux/miscdevice.h> +#include <linux/pm_runtime.h> #include <asm/atomic.h> #include "lis3lv02d.h" @@ -43,6 +45,16 @@ #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 @@ -66,8 +78,10 @@ #define LIS3_SENSITIVITY_12B ((LIS3_ACCURACY * 1000) / 1024) #define LIS3_SENSITIVITY_8B (18 * LIS3_ACCURACY) -#define LIS3_DEFAULT_FUZZ 3 -#define LIS3_DEFAULT_FLAT 3 +#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), @@ -75,6 +89,30 @@ struct lis3lv02d lis3_dev = { 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; @@ -123,9 +161,24 @@ static void lis3lv02d_get_xyz(struct lis3lv02d *lis3, int *x, int *y, int *z) int position[3]; int i; - position[0] = lis3->read_data(lis3, OUTX); - position[1] = lis3->read_data(lis3, OUTY); - position[2] = lis3->read_data(lis3, OUTZ); + 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; @@ -138,6 +191,7 @@ static void lis3lv02d_get_xyz(struct lis3lv02d *lis3, int *x, int *y, int *z) /* 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) @@ -156,6 +210,9 @@ 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 */ @@ -172,19 +229,42 @@ static int lis3lv02d_set_odr(int rate) static int lis3lv02d_selftest(struct lis3lv02d *lis3, s16 results[3]) { - u8 reg; + u8 ctlreg, reg; s16 x, y, z; u8 selftest; int ret; + u8 ctrl_reg_data; + unsigned char irq_cfg; mutex_lock(&lis3->mutex); - if (lis3_dev.whoami == WAI_12B) - selftest = CTRL1_ST; - else - selftest = CTRL1_STP; - lis3->read(lis3, CTRL_REG1, ®); - lis3->write(lis3, CTRL_REG1, (reg | selftest)); + 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 */ @@ -193,7 +273,7 @@ static int lis3lv02d_selftest(struct lis3lv02d *lis3, s16 results[3]) z = lis3->read_data(lis3, OUTZ); /* back to normal settings */ - lis3->write(lis3, CTRL_REG1, reg); + lis3->write(lis3, ctlreg, reg); msleep(lis3->pwron_delay / lis3lv02d_get_odr()); results[0] = x - lis3->read_data(lis3, OUTX); @@ -201,13 +281,33 @@ static int lis3lv02d_selftest(struct lis3lv02d *lis3, s16 results[3]) 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 = -EIO; + ret = SELFTEST_FAIL; goto fail; } } @@ -219,10 +319,46 @@ fail: 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); @@ -232,19 +368,24 @@ void lis3lv02d_poweron(struct lis3lv02d *lis3) lis3->init(lis3); - /* LIS3 power on delay is quite long */ - msleep(lis3->pwron_delay / lis3lv02d_get_odr()); - /* * 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. */ - if (lis3->whoami == WAI_12B) { - lis3->read(lis3, CTRL_REG2, ®); - reg |= CTRL2_BDU; - lis3->write(lis3, CTRL_REG2, reg); - } + 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); @@ -262,6 +403,27 @@ static void lis3lv02d_joystick_poll(struct input_polled_dev *pidev) 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)) @@ -277,8 +439,7 @@ static irqreturn_t lis302dl_interrupt(int irq, void *dummy) wake_up_interruptible(&lis3_dev.misc_wait); kill_fasync(&lis3_dev.async_queue, SIGIO, POLL_IN); out: - if (lis3_dev.pdata && lis3_dev.whoami == WAI_8B && lis3_dev.idev && - lis3_dev.idev->input->users) + if (atomic_read(&lis3_dev.wake_thread)) return IRQ_WAKE_THREAD; return IRQ_HANDLED; } @@ -309,44 +470,41 @@ static void lis302dl_interrupt_handle_click(struct lis3lv02d *lis3) mutex_unlock(&lis3->mutex); } -static void lis302dl_interrupt_handle_ff_wu(struct lis3lv02d *lis3) +static inline void lis302dl_data_ready(struct lis3lv02d *lis3, int index) { - u8 wu1_src; - u8 wu2_src; - - lis3->read(lis3, FF_WU_SRC_1, &wu1_src); - lis3->read(lis3, FF_WU_SRC_2, &wu2_src); + int dummy; - wu1_src = wu1_src & FF_WU_SRC_IA ? wu1_src : 0; - wu2_src = wu2_src & FF_WU_SRC_IA ? wu2_src : 0; - - /* joystick poll is internally protected by the lis3->mutex. */ - if (wu1_src || wu2_src) - lis3lv02d_joystick_poll(lis3_dev.idev); + /* 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 ((lis3->pdata->irq_cfg & LIS3_IRQ1_MASK) == LIS3_IRQ1_CLICK) + 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 - lis302dl_interrupt_handle_ff_wu(lis3); + 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 ((lis3->pdata->irq_cfg & LIS3_IRQ2_MASK) == LIS3_IRQ2_CLICK) + 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 - lis302dl_interrupt_handle_ff_wu(lis3); + lis3lv02d_joystick_poll(lis3->idev); return IRQ_HANDLED; } @@ -356,6 +514,9 @@ 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; } @@ -364,6 +525,8 @@ 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; } @@ -460,6 +623,8 @@ int lis3lv02d_joystick_enable(void) 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; @@ -473,8 +638,16 @@ int lis3lv02d_joystick_enable(void) set_bit(EV_ABS, input_dev->evbit); max_val = (lis3_dev.mdps_max_val * lis3_dev.scale) / LIS3_ACCURACY; - fuzz = (LIS3_DEFAULT_FUZZ * lis3_dev.scale) / LIS3_ACCURACY; - flat = (LIS3_DEFAULT_FLAT * 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); @@ -512,14 +685,47 @@ void lis3lv02d_joystick_disable(void) 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) { - int result; s16 values[3]; - result = lis3lv02d_selftest(&lis3_dev, values); - return sprintf(buf, "%s %d %d %d\n", result == 0 ? "OK" : "FAIL", + 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]); } @@ -528,6 +734,7 @@ static ssize_t lis3lv02d_position_show(struct device *dev, { 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); @@ -537,6 +744,7 @@ static ssize_t lis3lv02d_position_show(struct device *dev, 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()); } @@ -549,6 +757,7 @@ static ssize_t lis3lv02d_rate_set(struct device *dev, if (strict_strtoul(buf, 0, &rate)) return -EINVAL; + lis3lv02d_sysfs_poweron(&lis3_dev); if (lis3lv02d_set_odr(rate)) return -EINVAL; @@ -585,6 +794,18 @@ 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); @@ -616,16 +837,16 @@ static void lis3lv02d_8b_configure(struct lis3lv02d *dev, 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); - /* default to 2.5ms for now */ - dev->write(dev, FF_WU_DURATION_1, 1); + /* 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); - /* default to 2.5ms for now */ - dev->write(dev, FF_WU_DURATION_2, 1); + /* 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 */ @@ -635,8 +856,8 @@ static void lis3lv02d_8b_configure(struct lis3lv02d *dev, err = request_threaded_irq(p->irq2, NULL, lis302dl_interrupt_thread2_8b, - IRQF_TRIGGER_RISING | - IRQF_ONESHOT, + IRQF_TRIGGER_RISING | IRQF_ONESHOT | + (p->irq_flags2 & IRQF_TRIGGER_MASK), DRIVER_NAME, &lis3_dev); if (err < 0) printk(KERN_ERR DRIVER_NAME @@ -652,6 +873,7 @@ 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); @@ -664,6 +886,8 @@ int lis3lv02d_init_device(struct lis3lv02d *dev) 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: printk(KERN_INFO DRIVER_NAME ": 8 bits sensor found\n"); @@ -673,6 +897,17 @@ int lis3lv02d_init_device(struct lis3lv02d *dev) 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: + printk(KERN_INFO DRIVER_NAME ": 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: printk(KERN_ERR DRIVER_NAME @@ -680,11 +915,25 @@ int lis3lv02d_init_device(struct lis3lv02d *dev) 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()) printk(KERN_ERR DRIVER_NAME ": joystick initialization failed\n"); @@ -696,8 +945,14 @@ int lis3lv02d_init_device(struct lis3lv02d *dev) 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 */ @@ -725,7 +980,8 @@ int lis3lv02d_init_device(struct lis3lv02d *dev) err = request_threaded_irq(dev->irq, lis302dl_interrupt, thread_fn, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, + IRQF_TRIGGER_RISING | IRQF_ONESHOT | + irq_flags, DRIVER_NAME, &lis3_dev); if (err < 0) { diff --git a/drivers/hwmon/lis3lv02d.h b/drivers/hwmon/lis3lv02d.h index 854091380e33..a1939589eb2c 100644 --- a/drivers/hwmon/lis3lv02d.h +++ b/drivers/hwmon/lis3lv02d.h @@ -20,6 +20,7 @@ */ #include <linux/platform_device.h> #include <linux/input-polldev.h> +#include <linux/regulator/consumer.h> /* * This driver tries to support the "digital" accelerometer chips from @@ -45,6 +46,7 @@ enum lis3_reg { CTRL_REG1 = 0x20, CTRL_REG2 = 0x21, CTRL_REG3 = 0x22, + CTRL_REG4 = 0x23, HP_FILTER_RESET = 0x23, STATUS_REG = 0x27, OUTX_L = 0x28, @@ -93,6 +95,7 @@ enum lis3lv02d_reg { }; 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 */ @@ -118,6 +121,13 @@ enum lis3lv02d_ctrl1_8b { 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, @@ -129,9 +139,18 @@ enum lis3lv02d_ctrl2 { 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 { @@ -206,19 +225,33 @@ enum lis3lv02d_click_src_8b { CLICK_IA = 0x40, }; -struct axis_conversion { - s8 x; - s8 y; - s8 z; +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); @@ -231,14 +264,18 @@ struct lis3lv02d { 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 */ - struct axis_conversion ac; /* hw -> logical axis */ + 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 */ diff --git a/drivers/hwmon/lis3lv02d_i2c.c b/drivers/hwmon/lis3lv02d_i2c.c index 8e5933b72d19..9f4bae07f719 100644 --- a/drivers/hwmon/lis3lv02d_i2c.c +++ b/drivers/hwmon/lis3lv02d_i2c.c @@ -29,10 +29,30 @@ #include <linux/init.h> #include <linux/err.h> #include <linux/i2c.h> +#include <linux/pm_runtime.h> +#include <linux/delay.h> #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; @@ -46,24 +66,38 @@ static inline s32 lis3_i2c_read(struct lis3lv02d *lis3, int reg, u8 *v) 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; + 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 struct axis_conversion lis3lv02d_axis_map = { LIS3_DEV_X, - LIS3_DEV_Y, - LIS3_DEV_Z }; +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) @@ -72,6 +106,15 @@ static int __devinit lis3lv02d_i2c_probe(struct i2c_client *client, 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; @@ -88,6 +131,16 @@ static int __devinit lis3lv02d_i2c_probe(struct i2c_client *client, 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; @@ -95,10 +148,24 @@ static int __devinit lis3lv02d_i2c_probe(struct i2c_client *client, 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; } @@ -111,14 +178,18 @@ static int __devexit lis3lv02d_i2c_remove(struct i2c_client *client) pdata->release_resources(); lis3lv02d_joystick_disable(); - lis3lv02d_poweroff(lis3); + lis3lv02d_remove_fs(&lis3_dev); - return 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 -static int lis3lv02d_i2c_suspend(struct i2c_client *client, pm_message_t mesg) +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) @@ -126,18 +197,21 @@ static int lis3lv02d_i2c_suspend(struct i2c_client *client, pm_message_t mesg) return 0; } -static int lis3lv02d_i2c_resume(struct i2c_client *client) +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); - if (!lis3->pdata || !lis3->pdata->wakeup_flags) + /* + * 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; -} -static void lis3lv02d_i2c_shutdown(struct i2c_client *client) -{ - lis3lv02d_i2c_suspend(client, PMSG_SUSPEND); + return 0; } #else #define lis3lv02d_i2c_suspend NULL @@ -145,6 +219,24 @@ static void lis3lv02d_i2c_shutdown(struct i2c_client *client) #define lis3lv02d_i2c_shutdown NULL #endif +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; +} + static const struct i2c_device_id lis3lv02d_id[] = { {"lis3lv02d", 0 }, {} @@ -152,14 +244,20 @@ static const struct i2c_device_id lis3lv02d_id[] = { 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, }, - .suspend = lis3lv02d_i2c_suspend, - .shutdown = lis3lv02d_i2c_shutdown, - .resume = lis3lv02d_i2c_resume, .probe = lis3lv02d_i2c_probe, .remove = __devexit_p(lis3lv02d_i2c_remove), .id_table = lis3lv02d_id, diff --git a/drivers/hwmon/lis3lv02d_spi.c b/drivers/hwmon/lis3lv02d_spi.c index b9be5e3a22b3..2549de1de4e2 100644 --- a/drivers/hwmon/lis3lv02d_spi.c +++ b/drivers/hwmon/lis3lv02d_spi.c @@ -50,11 +50,12 @@ static int lis3_spi_init(struct lis3lv02d *lis3) if (ret < 0) return ret; - reg |= CTRL1_PD0; + reg |= CTRL1_PD0 | CTRL1_Xen | CTRL1_Yen | CTRL1_Zen; return lis3->write(lis3, CTRL_REG1, reg); } -static struct axis_conversion lis3lv02d_axis_normal = { 1, 2, 3 }; +static union axis_conversion lis3lv02d_axis_normal = + { .as_array = { 1, 2, 3 } }; static int __devinit lis302dl_spi_probe(struct spi_device *spi) { diff --git a/drivers/hwmon/ltc4261.c b/drivers/hwmon/ltc4261.c new file mode 100644 index 000000000000..267626178678 --- /dev/null +++ b/drivers/hwmon/ltc4261.c @@ -0,0 +1,315 @@ +/* + * Driver for Linear Technology LTC4261 I2C Negative Voltage Hot Swap Controller + * + * Copyright (C) 2010 Ericsson AB. + * + * Derived from: + * + * Driver for Linear Technology LTC4245 I2C Multiple Supply Hot Swap Controller + * Copyright (C) 2008 Ira W. Snyder <iws@ovro.caltech.edu> + * + * Datasheet: http://cds.linear.com/docs/Datasheet/42612fb.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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> + +/* chip registers */ +#define LTC4261_STATUS 0x00 /* readonly */ +#define LTC4261_FAULT 0x01 +#define LTC4261_ALERT 0x02 +#define LTC4261_CONTROL 0x03 +#define LTC4261_SENSE_H 0x04 +#define LTC4261_SENSE_L 0x05 +#define LTC4261_ADIN2_H 0x06 +#define LTC4261_ADIN2_L 0x07 +#define LTC4261_ADIN_H 0x08 +#define LTC4261_ADIN_L 0x09 + +/* + * Fault register bits + */ +#define FAULT_OV (1<<0) +#define FAULT_UV (1<<1) +#define FAULT_OC (1<<2) + +struct ltc4261_data { + struct device *hwmon_dev; + + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + + /* Registers */ + u8 regs[10]; +}; + +static struct ltc4261_data *ltc4261_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ltc4261_data *data = i2c_get_clientdata(client); + struct ltc4261_data *ret = data; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ / 4) || !data->valid) { + int i; + + /* Read registers -- 0x00 to 0x09 */ + for (i = 0; i < ARRAY_SIZE(data->regs); i++) { + int val; + + val = i2c_smbus_read_byte_data(client, i); + if (unlikely(val < 0)) { + dev_dbg(dev, + "Failed to read ADC value: error %d", + val); + ret = ERR_PTR(val); + goto abort; + } + data->regs[i] = val; + } + data->last_updated = jiffies; + data->valid = 1; + } +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +/* Return the voltage from the given register in mV or mA */ +static int ltc4261_get_value(struct ltc4261_data *data, u8 reg) +{ + u32 val; + + val = (data->regs[reg] << 2) + (data->regs[reg + 1] >> 6); + + switch (reg) { + case LTC4261_ADIN_H: + case LTC4261_ADIN2_H: + /* 2.5mV resolution. Convert to mV. */ + val = val * 25 / 10; + break; + case LTC4261_SENSE_H: + /* + * 62.5uV resolution. Convert to current as measured with + * an 1 mOhm sense resistor, in mA. If a different sense + * resistor is installed, calculate the actual current by + * dividing the reported current by the sense resistor value + * in mOhm. + */ + val = val * 625 / 10; + break; + default: + /* If we get here, the developer messed up */ + WARN_ON_ONCE(1); + val = 0; + break; + } + + return val; +} + +static ssize_t ltc4261_show_value(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ltc4261_data *data = ltc4261_update_device(dev); + int value; + + if (IS_ERR(data)) + return PTR_ERR(data); + + value = ltc4261_get_value(data, attr->index); + return snprintf(buf, PAGE_SIZE, "%d\n", value); +} + +static ssize_t ltc4261_show_bool(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); + struct ltc4261_data *data = ltc4261_update_device(dev); + u8 fault; + + if (IS_ERR(data)) + return PTR_ERR(data); + + fault = data->regs[LTC4261_FAULT] & attr->index; + if (fault) /* Clear reported faults in chip register */ + i2c_smbus_write_byte_data(client, LTC4261_FAULT, ~fault); + + return snprintf(buf, PAGE_SIZE, "%d\n", fault ? 1 : 0); +} + +/* + * These macros are used below in constructing device attribute objects + * for use with sysfs_create_group() to make a sysfs device file + * for each register. + */ + +#define LTC4261_VALUE(name, ltc4261_cmd_idx) \ + static SENSOR_DEVICE_ATTR(name, S_IRUGO, \ + ltc4261_show_value, NULL, ltc4261_cmd_idx) + +#define LTC4261_BOOL(name, mask) \ + static SENSOR_DEVICE_ATTR(name, S_IRUGO, \ + ltc4261_show_bool, NULL, (mask)) + +/* + * Input voltages. + */ +LTC4261_VALUE(in1_input, LTC4261_ADIN_H); +LTC4261_VALUE(in2_input, LTC4261_ADIN2_H); + +/* + * Voltage alarms. The chip has only one set of voltage alarm status bits, + * triggered by input voltage alarms. In many designs, those alarms are + * associated with the ADIN2 sensor, due to the proximity of the ADIN2 pin + * to the OV pin. ADIN2 is, however, not available on all chip variants. + * To ensure that the alarm condition is reported to the user, report it + * with both voltage sensors. + */ +LTC4261_BOOL(in1_min_alarm, FAULT_UV); +LTC4261_BOOL(in1_max_alarm, FAULT_OV); +LTC4261_BOOL(in2_min_alarm, FAULT_UV); +LTC4261_BOOL(in2_max_alarm, FAULT_OV); + +/* Currents (via sense resistor) */ +LTC4261_VALUE(curr1_input, LTC4261_SENSE_H); + +/* Overcurrent alarm */ +LTC4261_BOOL(curr1_max_alarm, FAULT_OC); + +static struct attribute *ltc4261_attributes[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min_alarm.dev_attr.attr, + &sensor_dev_attr_in1_max_alarm.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min_alarm.dev_attr.attr, + &sensor_dev_attr_in2_max_alarm.dev_attr.attr, + + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_curr1_max_alarm.dev_attr.attr, + + NULL, +}; + +static const struct attribute_group ltc4261_group = { + .attrs = ltc4261_attributes, +}; + +static int ltc4261_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct ltc4261_data *data; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + if (i2c_smbus_read_byte_data(client, LTC4261_STATUS) < 0) { + dev_err(&client->dev, "Failed to read register %d:%02x:%02x\n", + adapter->id, client->addr, LTC4261_STATUS); + return -ENODEV; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto out_kzalloc; + } + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* Clear faults */ + i2c_smbus_write_byte_data(client, LTC4261_FAULT, 0x00); + + /* Register sysfs hooks */ + ret = sysfs_create_group(&client->dev.kobj, <c4261_group); + if (ret) + goto out_sysfs_create_group; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + goto out_hwmon_device_register; + } + + return 0; + +out_hwmon_device_register: + sysfs_remove_group(&client->dev.kobj, <c4261_group); +out_sysfs_create_group: + kfree(data); +out_kzalloc: + return ret; +} + +static int ltc4261_remove(struct i2c_client *client) +{ + struct ltc4261_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, <c4261_group); + + kfree(data); + + return 0; +} + +static const struct i2c_device_id ltc4261_id[] = { + {"ltc4261", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ltc4261_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver ltc4261_driver = { + .driver = { + .name = "ltc4261", + }, + .probe = ltc4261_probe, + .remove = ltc4261_remove, + .id_table = ltc4261_id, +}; + +static int __init ltc4261_init(void) +{ + return i2c_add_driver(<c4261_driver); +} + +static void __exit ltc4261_exit(void) +{ + i2c_del_driver(<c4261_driver); +} + +MODULE_AUTHOR("Guenter Roeck <guenter.roeck@ericsson.com>"); +MODULE_DESCRIPTION("LTC4261 driver"); +MODULE_LICENSE("GPL"); + +module_init(ltc4261_init); +module_exit(ltc4261_exit); diff --git a/drivers/hwmon/pkgtemp.c b/drivers/hwmon/pkgtemp.c index f11903936c8b..0798210590bc 100644 --- a/drivers/hwmon/pkgtemp.c +++ b/drivers/hwmon/pkgtemp.c @@ -21,7 +21,6 @@ */ #include <linux/module.h> -#include <linux/delay.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/jiffies.h> @@ -35,6 +34,7 @@ #include <linux/cpu.h> #include <asm/msr.h> #include <asm/processor.h> +#include <asm/smp.h> #define DRVNAME "pkgtemp" @@ -339,8 +339,7 @@ exit: return err; } -#ifdef CONFIG_HOTPLUG_CPU -static void pkgtemp_device_remove(unsigned int cpu) +static void __cpuinit pkgtemp_device_remove(unsigned int cpu) { struct pdev_entry *p; unsigned int i; @@ -387,12 +386,10 @@ static int __cpuinit pkgtemp_cpu_callback(struct notifier_block *nfb, static struct notifier_block pkgtemp_cpu_notifier __refdata = { .notifier_call = pkgtemp_cpu_callback, }; -#endif /* !CONFIG_HOTPLUG_CPU */ static int __init pkgtemp_init(void) { int i, err = -ENODEV; - struct pdev_entry *p, *n; /* quick check if we run Intel */ if (cpu_data(0).x86_vendor != X86_VENDOR_INTEL) @@ -402,31 +399,23 @@ static int __init pkgtemp_init(void) if (err) goto exit; - for_each_online_cpu(i) { - err = pkgtemp_device_add(i); - if (err) - goto exit_devices_unreg; - } + for_each_online_cpu(i) + pkgtemp_device_add(i); + +#ifndef CONFIG_HOTPLUG_CPU if (list_empty(&pdev_list)) { err = -ENODEV; goto exit_driver_unreg; } +#endif -#ifdef CONFIG_HOTPLUG_CPU register_hotcpu_notifier(&pkgtemp_cpu_notifier); -#endif return 0; -exit_devices_unreg: - mutex_lock(&pdev_list_mutex); - list_for_each_entry_safe(p, n, &pdev_list, list) { - platform_device_unregister(p->pdev); - list_del(&p->list); - kfree(p); - } - mutex_unlock(&pdev_list_mutex); +#ifndef CONFIG_HOTPLUG_CPU exit_driver_unreg: platform_driver_unregister(&pkgtemp_driver); +#endif exit: return err; } @@ -434,9 +423,8 @@ exit: static void __exit pkgtemp_exit(void) { struct pdev_entry *p, *n; -#ifdef CONFIG_HOTPLUG_CPU + unregister_hotcpu_notifier(&pkgtemp_cpu_notifier); -#endif mutex_lock(&pdev_list_mutex); list_for_each_entry_safe(p, n, &pdev_list, list) { platform_device_unregister(p->pdev); diff --git a/drivers/hwmon/via-cputemp.c b/drivers/hwmon/via-cputemp.c index ffb793af680b..ec7fad747adc 100644 --- a/drivers/hwmon/via-cputemp.c +++ b/drivers/hwmon/via-cputemp.c @@ -22,10 +22,8 @@ */ #include <linux/module.h> -#include <linux/delay.h> #include <linux/init.h> #include <linux/slab.h> -#include <linux/jiffies.h> #include <linux/hwmon.h> #include <linux/sysfs.h> #include <linux/hwmon-sysfs.h> @@ -237,8 +235,7 @@ exit: return err; } -#ifdef CONFIG_HOTPLUG_CPU -static void via_cputemp_device_remove(unsigned int cpu) +static void __cpuinit via_cputemp_device_remove(unsigned int cpu) { struct pdev_entry *p, *n; mutex_lock(&pdev_list_mutex); @@ -272,7 +269,6 @@ static int __cpuinit via_cputemp_cpu_callback(struct notifier_block *nfb, static struct notifier_block via_cputemp_cpu_notifier __refdata = { .notifier_call = via_cputemp_cpu_callback, }; -#endif /* !CONFIG_HOTPLUG_CPU */ static int __init via_cputemp_init(void) { @@ -313,9 +309,7 @@ static int __init via_cputemp_init(void) goto exit_driver_unreg; } -#ifdef CONFIG_HOTPLUG_CPU register_hotcpu_notifier(&via_cputemp_cpu_notifier); -#endif return 0; exit_devices_unreg: @@ -335,9 +329,8 @@ exit: static void __exit via_cputemp_exit(void) { struct pdev_entry *p, *n; -#ifdef CONFIG_HOTPLUG_CPU + unregister_hotcpu_notifier(&via_cputemp_cpu_notifier); -#endif mutex_lock(&pdev_list_mutex); list_for_each_entry_safe(p, n, &pdev_list, list) { platform_device_unregister(p->pdev); diff --git a/include/linux/gpio-fan.h b/include/linux/gpio-fan.h new file mode 100644 index 000000000000..096659169215 --- /dev/null +++ b/include/linux/gpio-fan.h @@ -0,0 +1,36 @@ +/* + * include/linux/gpio-fan.h + * + * Platform data structure for GPIO fan driver + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef __LINUX_GPIO_FAN_H +#define __LINUX_GPIO_FAN_H + +struct gpio_fan_alarm { + unsigned gpio; + unsigned active_low; +}; + +struct gpio_fan_speed { + int rpm; + int ctrl_val; +}; + +struct gpio_fan_platform_data { + int num_ctrl; + unsigned *ctrl; /* fan control GPIOs. */ + struct gpio_fan_alarm *alarm; /* fan alarm GPIO. */ + /* + * Speed conversion array: rpm from/to GPIO bit field. + * This array _must_ be sorted in ascending rpm order. + */ + int num_speed; + struct gpio_fan_speed *speed; +}; + +#endif /* __LINUX_GPIO_FAN_H */ diff --git a/include/linux/lis3lv02d.h b/include/linux/lis3lv02d.h index 0e8a346424bb..d4292c8431e0 100644 --- a/include/linux/lis3lv02d.h +++ b/include/linux/lis3lv02d.h @@ -1,6 +1,52 @@ #ifndef __LIS3LV02D_H_ #define __LIS3LV02D_H_ +/** + * struct lis3lv02d_platform_data - lis3 chip family platform data + * @click_flags: Click detection unit configuration + * @click_thresh_x: Click detection unit x axis threshold + * @click_thresh_y: Click detection unit y axis threshold + * @click_thresh_z: Click detection unit z axis threshold + * @click_time_limit: Click detection unit time parameter + * @click_latency: Click detection unit latency parameter + * @click_window: Click detection unit window parameter + * @irq_cfg: On chip irq source and type configuration (click / + * data available / wake up, open drain, polarity) + * @irq_flags1: Additional irq triggering flags for irq channel 0 + * @irq_flags2: Additional irq triggering flags for irq channel 1 + * @duration1: Wake up unit 1 duration parameter + * @duration2: Wake up unit 2 duration parameter + * @wakeup_flags: Wake up unit 1 flags + * @wakeup_thresh: Wake up unit 1 threshold value + * @wakeup_flags2: Wake up unit 2 flags + * @wakeup_thresh2: Wake up unit 2 threshold value + * @hipass_ctrl: High pass filter control (enable / disable, cut off + * frequency) + * @axis_x: Sensor orientation remapping for x-axis + * @axis_y: Sensor orientation remapping for y-axis + * @axis_z: Sensor orientation remapping for z-axis + * @driver_features: Enable bits for different features. Disabled by default + * @default_rate: Default sampling rate. 0 means reset default + * @setup_resources: Interrupt line setup call back function + * @release_resources: Interrupt line release call back function + * @st_min_limits[3]: Selftest acceptance minimum values + * @st_max_limits[3]: Selftest acceptance maximum values + * @irq2: Irq line 2 number + * + * Platform data is used to setup the sensor chip. Meaning of the different + * chip features can be found from the data sheet. It is publicly available + * at www.st.com web pages. Currently the platform data is used + * only for the 8 bit device. The 8 bit device has two wake up / free fall + * detection units and click detection unit. There are plenty of ways to + * configure the chip which makes is quite hard to explain deeper meaning of + * the fields here. Behaviour of the detection blocks varies heavily depending + * on the configuration. For example, interrupt detection block can use high + * pass filtered data which makes it react to the changes in the acceleration. + * Irq_flags can be used to enable interrupt detection on the both edges. + * With proper chip configuration this produces interrupt when some trigger + * starts and when it goes away. + */ + struct lis3lv02d_platform_data { /* please note: the 'click' feature is only supported for * LIS[32]02DL variants of the chip and will be ignored for @@ -36,7 +82,10 @@ struct lis3lv02d_platform_data { #define LIS3_IRQ_OPEN_DRAIN (1 << 6) #define LIS3_IRQ_ACTIVE_LOW (1 << 7) unsigned char irq_cfg; - + unsigned char irq_flags1; /* Additional irq edge / level flags */ + unsigned char irq_flags2; /* Additional irq edge / level flags */ + unsigned char duration1; + unsigned char duration2; #define LIS3_WAKEUP_X_LO (1 << 0) #define LIS3_WAKEUP_X_HI (1 << 1) #define LIS3_WAKEUP_Y_LO (1 << 2) @@ -64,6 +113,10 @@ struct lis3lv02d_platform_data { s8 axis_x; s8 axis_y; s8 axis_z; +#define LIS3_USE_REGULATOR_CTRL 0x01 +#define LIS3_USE_BLOCK_READ 0x02 + u16 driver_features; + int default_rate; int (*setup_resources)(void); int (*release_resources)(void); /* Limits for selftest are specified in chip data sheet */ |