mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-11-01 17:08:10 +00:00
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 ...
This commit is contained in:
commit
b20f9e5bdd
17 changed files with 1620 additions and 132 deletions
63
Documentation/hwmon/ltc4261
Normal file
63
Documentation/hwmon/ltc4261
Normal file
|
@ -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
|
|
@ -3765,6 +3765,13 @@ L: linux-scsi@vger.kernel.org
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: drivers/scsi/sym53c8xx_2/
|
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)
|
LTP (Linux Test Project)
|
||||||
M: Rishikesh K Rajak <risrajak@linux.vnet.ibm.com>
|
M: Rishikesh K Rajak <risrajak@linux.vnet.ibm.com>
|
||||||
M: Garrett Cooper <yanegomi@gmail.com>
|
M: Garrett Cooper <yanegomi@gmail.com>
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include <linux/gpio.h>
|
#include <linux/gpio.h>
|
||||||
#include <linux/gpio_keys.h>
|
#include <linux/gpio_keys.h>
|
||||||
#include <linux/leds.h>
|
#include <linux/leds.h>
|
||||||
|
#include <linux/gpio-fan.h>
|
||||||
#include <asm/mach-types.h>
|
#include <asm/mach-types.h>
|
||||||
#include <asm/mach/arch.h>
|
#include <asm/mach/arch.h>
|
||||||
#include <mach/kirkwood.h>
|
#include <mach/kirkwood.h>
|
||||||
|
@ -136,6 +137,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
|
* General Setup
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
@ -205,6 +246,8 @@ static void __init netspace_v2_init(void)
|
||||||
platform_device_register(&netspace_v2_leds);
|
platform_device_register(&netspace_v2_leds);
|
||||||
platform_device_register(&netspace_v2_gpio_leds);
|
platform_device_register(&netspace_v2_gpio_leds);
|
||||||
platform_device_register(&netspace_v2_gpio_buttons);
|
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 &&
|
if (gpio_request(NETSPACE_V2_GPIO_POWER_OFF, "power-off") == 0 &&
|
||||||
gpio_direction_output(NETSPACE_V2_GPIO_POWER_OFF, 0) == 0)
|
gpio_direction_output(NETSPACE_V2_GPIO_POWER_OFF, 0) == 0)
|
||||||
|
|
|
@ -399,6 +399,15 @@ config SENSORS_GL520SM
|
||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called gl520sm.
|
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
|
config SENSORS_CORETEMP
|
||||||
tristate "Intel Core/Core2/Atom temperature sensor"
|
tristate "Intel Core/Core2/Atom temperature sensor"
|
||||||
depends on X86 && PCI && EXPERIMENTAL
|
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
|
This driver can also be built as a module. If so, the module will
|
||||||
be called ltc4245.
|
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
|
config SENSORS_LM95241
|
||||||
tristate "National Semiconductor LM95241 sensor chip"
|
tristate "National Semiconductor LM95241 sensor chip"
|
||||||
depends on I2C
|
depends on I2C
|
||||||
|
|
|
@ -51,6 +51,7 @@ obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o
|
||||||
obj-$(CONFIG_SENSORS_G760A) += g760a.o
|
obj-$(CONFIG_SENSORS_G760A) += g760a.o
|
||||||
obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
|
obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
|
||||||
obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.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_ULTRA45) += ultra45_env.o
|
||||||
obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o
|
obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o
|
||||||
obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.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_LM95241) += lm95241.o
|
||||||
obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o
|
obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o
|
||||||
obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o
|
obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o
|
||||||
|
obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o
|
||||||
obj-$(CONFIG_SENSORS_MAX1111) += max1111.o
|
obj-$(CONFIG_SENSORS_MAX1111) += max1111.o
|
||||||
obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
|
obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
|
||||||
obj-$(CONFIG_SENSORS_MAX6650) += max6650.o
|
obj-$(CONFIG_SENSORS_MAX6650) += max6650.o
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/delay.h>
|
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/jiffies.h>
|
#include <linux/jiffies.h>
|
||||||
|
@ -280,11 +279,9 @@ static int __devinit get_tjmax(struct cpuinfo_x86 *c, u32 id,
|
||||||
case 0x1a:
|
case 0x1a:
|
||||||
dev_warn(dev, "TjMax is assumed as 100 C!\n");
|
dev_warn(dev, "TjMax is assumed as 100 C!\n");
|
||||||
return 100000;
|
return 100000;
|
||||||
break;
|
|
||||||
case 0x17:
|
case 0x17:
|
||||||
case 0x1c: /* Atom CPUs */
|
case 0x1c: /* Atom CPUs */
|
||||||
return adjust_tjmax(c, id, dev);
|
return adjust_tjmax(c, id, dev);
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
dev_warn(dev, "CPU (model=0x%x) is not supported yet,"
|
dev_warn(dev, "CPU (model=0x%x) is not supported yet,"
|
||||||
" using default TjMax of 100C.\n", c->x86_model);
|
" 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)
|
static int __devinit coretemp_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct coretemp_data *data;
|
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)) {
|
if ((c->x86_model == 0xe) && (c->x86_mask < 0xc)) {
|
||||||
/* check for microcode update */
|
/* check for microcode update */
|
||||||
rdmsr_on_cpu(data->id, MSR_IA32_UCODE_REV, &eax, &edx);
|
err = smp_call_function_single(data->id, get_ucode_rev_on_cpu,
|
||||||
if (edx < 0x39) {
|
&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;
|
err = -ENODEV;
|
||||||
dev_err(&pdev->dev,
|
dev_err(&pdev->dev,
|
||||||
"Errata AE18 not fixed, update BIOS or "
|
"Errata AE18 not fixed, update BIOS or "
|
||||||
|
@ -490,7 +503,7 @@ static int __cpuinit coretemp_device_add(unsigned int cpu)
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void coretemp_device_remove(unsigned int cpu)
|
static void __cpuinit coretemp_device_remove(unsigned int cpu)
|
||||||
{
|
{
|
||||||
struct pdev_entry *p;
|
struct pdev_entry *p;
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
|
@ -569,9 +582,8 @@ static int __init coretemp_init(void)
|
||||||
static void __exit coretemp_exit(void)
|
static void __exit coretemp_exit(void)
|
||||||
{
|
{
|
||||||
struct pdev_entry *p, *n;
|
struct pdev_entry *p, *n;
|
||||||
#ifdef CONFIG_HOTPLUG_CPU
|
|
||||||
unregister_hotcpu_notifier(&coretemp_cpu_notifier);
|
unregister_hotcpu_notifier(&coretemp_cpu_notifier);
|
||||||
#endif
|
|
||||||
mutex_lock(&pdev_list_mutex);
|
mutex_lock(&pdev_list_mutex);
|
||||||
list_for_each_entry_safe(p, n, &pdev_list, list) {
|
list_for_each_entry_safe(p, n, &pdev_list, list) {
|
||||||
platform_device_unregister(p->pdev);
|
platform_device_unregister(p->pdev);
|
||||||
|
|
558
drivers/hwmon/gpio-fan.c
Normal file
558
drivers/hwmon/gpio-fan.c
Normal file
|
@ -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");
|
|
@ -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)
|
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);
|
printk(KERN_INFO DRIVER_NAME ": hardware type %s found.\n", dmi->ident);
|
||||||
|
|
||||||
return 1;
|
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).
|
/* 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. */
|
* If the value is negative, the opposite of the hw value is used. */
|
||||||
static struct axis_conversion lis3lv02d_axis_normal = {1, 2, 3};
|
#define DEFINE_CONV(name, x, y, z) \
|
||||||
static struct axis_conversion lis3lv02d_axis_y_inverted = {1, -2, 3};
|
static union axis_conversion lis3lv02d_axis_##name = \
|
||||||
static struct axis_conversion lis3lv02d_axis_x_inverted = {-1, 2, 3};
|
{ .as_array = { x, y, z } }
|
||||||
static struct axis_conversion lis3lv02d_axis_z_inverted = {1, 2, -3};
|
DEFINE_CONV(normal, 1, 2, 3);
|
||||||
static struct axis_conversion lis3lv02d_axis_xy_swap = {2, 1, 3};
|
DEFINE_CONV(y_inverted, 1, -2, 3);
|
||||||
static struct axis_conversion lis3lv02d_axis_xy_rotated_left = {-2, 1, 3};
|
DEFINE_CONV(x_inverted, -1, 2, 3);
|
||||||
static struct axis_conversion lis3lv02d_axis_xy_rotated_left_usd = {-2, 1, -3};
|
DEFINE_CONV(z_inverted, 1, 2, -3);
|
||||||
static struct axis_conversion lis3lv02d_axis_xy_swap_inverted = {-2, -1, 3};
|
DEFINE_CONV(xy_swap, 2, 1, 3);
|
||||||
static struct axis_conversion lis3lv02d_axis_xy_rotated_right = {2, -1, 3};
|
DEFINE_CONV(xy_rotated_left, -2, 1, 3);
|
||||||
static struct axis_conversion lis3lv02d_axis_xy_swap_yz_inverted = {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) { \
|
#define AXIS_DMI_MATCH(_ident, _name, _axis) { \
|
||||||
.ident = _ident, \
|
.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("HPB452x", "HP ProBook 452", y_inverted),
|
||||||
AXIS_DMI_MATCH("HPB522x", "HP ProBook 522", xy_swap),
|
AXIS_DMI_MATCH("HPB522x", "HP ProBook 522", xy_swap),
|
||||||
AXIS_DMI_MATCH("HPB532x", "HP ProBook 532", y_inverted),
|
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, }
|
{ NULL, }
|
||||||
/* Laptop models without axis info (yet):
|
/* Laptop models without axis info (yet):
|
||||||
* "NC6910" "HP Compaq 6910"
|
* "NC6910" "HP Compaq 6910"
|
||||||
|
@ -299,7 +302,10 @@ static int lis3lv02d_add(struct acpi_device *device)
|
||||||
lis3lv02d_enum_resources(device);
|
lis3lv02d_enum_resources(device);
|
||||||
|
|
||||||
/* If possible use a "standard" axes order */
|
/* 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, "
|
printk(KERN_INFO DRIVER_NAME ": laptop model unknown, "
|
||||||
"using default axes configuration\n");
|
"using default axes configuration\n");
|
||||||
lis3_dev.ac = lis3lv02d_axis_normal;
|
lis3_dev.ac = lis3lv02d_axis_normal;
|
||||||
|
|
|
@ -31,9 +31,11 @@
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/wait.h>
|
#include <linux/wait.h>
|
||||||
#include <linux/poll.h>
|
#include <linux/poll.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
#include <linux/freezer.h>
|
#include <linux/freezer.h>
|
||||||
#include <linux/uaccess.h>
|
#include <linux/uaccess.h>
|
||||||
#include <linux/miscdevice.h>
|
#include <linux/miscdevice.h>
|
||||||
|
#include <linux/pm_runtime.h>
|
||||||
#include <asm/atomic.h>
|
#include <asm/atomic.h>
|
||||||
#include "lis3lv02d.h"
|
#include "lis3lv02d.h"
|
||||||
|
|
||||||
|
@ -43,6 +45,16 @@
|
||||||
#define MDPS_POLL_INTERVAL 50
|
#define MDPS_POLL_INTERVAL 50
|
||||||
#define MDPS_POLL_MIN 0
|
#define MDPS_POLL_MIN 0
|
||||||
#define MDPS_POLL_MAX 2000
|
#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
|
* 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
|
* 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_12B ((LIS3_ACCURACY * 1000) / 1024)
|
||||||
#define LIS3_SENSITIVITY_8B (18 * LIS3_ACCURACY)
|
#define LIS3_SENSITIVITY_8B (18 * LIS3_ACCURACY)
|
||||||
|
|
||||||
#define LIS3_DEFAULT_FUZZ 3
|
#define LIS3_DEFAULT_FUZZ_12B 3
|
||||||
#define LIS3_DEFAULT_FLAT 3
|
#define LIS3_DEFAULT_FLAT_12B 3
|
||||||
|
#define LIS3_DEFAULT_FUZZ_8B 1
|
||||||
|
#define LIS3_DEFAULT_FLAT_8B 1
|
||||||
|
|
||||||
struct lis3lv02d lis3_dev = {
|
struct lis3lv02d lis3_dev = {
|
||||||
.misc_wait = __WAIT_QUEUE_HEAD_INITIALIZER(lis3_dev.misc_wait),
|
.misc_wait = __WAIT_QUEUE_HEAD_INITIALIZER(lis3_dev.misc_wait),
|
||||||
|
@ -75,6 +89,30 @@ struct lis3lv02d lis3_dev = {
|
||||||
|
|
||||||
EXPORT_SYMBOL_GPL(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)
|
static s16 lis3lv02d_read_8(struct lis3lv02d *lis3, int reg)
|
||||||
{
|
{
|
||||||
s8 lo;
|
s8 lo;
|
||||||
|
@ -123,9 +161,24 @@ static void lis3lv02d_get_xyz(struct lis3lv02d *lis3, int *x, int *y, int *z)
|
||||||
int position[3];
|
int position[3];
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
position[0] = lis3->read_data(lis3, OUTX);
|
if (lis3->blkread) {
|
||||||
position[1] = lis3->read_data(lis3, OUTY);
|
if (lis3_dev.whoami == WAI_12B) {
|
||||||
position[2] = lis3->read_data(lis3, OUTZ);
|
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++)
|
for (i = 0; i < 3; i++)
|
||||||
position[i] = (position[i] * lis3->scale) / LIS3_ACCURACY;
|
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 */
|
/* conversion btw sampling rate and the register values */
|
||||||
static int lis3_12_rates[4] = {40, 160, 640, 2560};
|
static int lis3_12_rates[4] = {40, 160, 640, 2560};
|
||||||
static int lis3_8_rates[2] = {100, 400};
|
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 */
|
/* ODR is Output Data Rate */
|
||||||
static int lis3lv02d_get_odr(void)
|
static int lis3lv02d_get_odr(void)
|
||||||
|
@ -156,6 +210,9 @@ static int lis3lv02d_set_odr(int rate)
|
||||||
u8 ctrl;
|
u8 ctrl;
|
||||||
int i, len, shift;
|
int i, len, shift;
|
||||||
|
|
||||||
|
if (!rate)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
lis3_dev.read(&lis3_dev, CTRL_REG1, &ctrl);
|
lis3_dev.read(&lis3_dev, CTRL_REG1, &ctrl);
|
||||||
ctrl &= ~lis3_dev.odr_mask;
|
ctrl &= ~lis3_dev.odr_mask;
|
||||||
len = 1 << hweight_long(lis3_dev.odr_mask); /* # of possible values */
|
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])
|
static int lis3lv02d_selftest(struct lis3lv02d *lis3, s16 results[3])
|
||||||
{
|
{
|
||||||
u8 reg;
|
u8 ctlreg, reg;
|
||||||
s16 x, y, z;
|
s16 x, y, z;
|
||||||
u8 selftest;
|
u8 selftest;
|
||||||
int ret;
|
int ret;
|
||||||
|
u8 ctrl_reg_data;
|
||||||
|
unsigned char irq_cfg;
|
||||||
|
|
||||||
mutex_lock(&lis3->mutex);
|
mutex_lock(&lis3->mutex);
|
||||||
if (lis3_dev.whoami == WAI_12B)
|
|
||||||
selftest = CTRL1_ST;
|
|
||||||
else
|
|
||||||
selftest = CTRL1_STP;
|
|
||||||
|
|
||||||
lis3->read(lis3, CTRL_REG1, ®);
|
irq_cfg = lis3->irq_cfg;
|
||||||
lis3->write(lis3, CTRL_REG1, (reg | selftest));
|
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());
|
msleep(lis3->pwron_delay / lis3lv02d_get_odr());
|
||||||
|
|
||||||
/* Read directly to avoid axis remap */
|
/* 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);
|
z = lis3->read_data(lis3, OUTZ);
|
||||||
|
|
||||||
/* back to normal settings */
|
/* back to normal settings */
|
||||||
lis3->write(lis3, CTRL_REG1, reg);
|
lis3->write(lis3, ctlreg, reg);
|
||||||
msleep(lis3->pwron_delay / lis3lv02d_get_odr());
|
msleep(lis3->pwron_delay / lis3lv02d_get_odr());
|
||||||
|
|
||||||
results[0] = x - lis3->read_data(lis3, OUTX);
|
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);
|
results[2] = z - lis3->read_data(lis3, OUTZ);
|
||||||
|
|
||||||
ret = 0;
|
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) {
|
if (lis3->pdata) {
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < 3; i++) {
|
for (i = 0; i < 3; i++) {
|
||||||
/* Check against selftest acceptance limits */
|
/* Check against selftest acceptance limits */
|
||||||
if ((results[i] < lis3->pdata->st_min_limits[i]) ||
|
if ((results[i] < lis3->pdata->st_min_limits[i]) ||
|
||||||
(results[i] > lis3->pdata->st_max_limits[i])) {
|
(results[i] > lis3->pdata->st_max_limits[i])) {
|
||||||
ret = -EIO;
|
ret = SELFTEST_FAIL;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,10 +319,46 @@ static int lis3lv02d_selftest(struct lis3lv02d *lis3, s16 results[3])
|
||||||
return ret;
|
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)
|
void lis3lv02d_poweroff(struct lis3lv02d *lis3)
|
||||||
{
|
{
|
||||||
|
if (lis3->reg_ctrl)
|
||||||
|
lis3_context_save(lis3);
|
||||||
/* disable X,Y,Z axis and power down */
|
/* disable X,Y,Z axis and power down */
|
||||||
lis3->write(lis3, CTRL_REG1, 0x00);
|
lis3->write(lis3, CTRL_REG1, 0x00);
|
||||||
|
if (lis3->reg_ctrl)
|
||||||
|
lis3->reg_ctrl(lis3, LIS3_REG_OFF);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(lis3lv02d_poweroff);
|
EXPORT_SYMBOL_GPL(lis3lv02d_poweroff);
|
||||||
|
|
||||||
|
@ -232,19 +368,24 @@ void lis3lv02d_poweron(struct lis3lv02d *lis3)
|
||||||
|
|
||||||
lis3->init(lis3);
|
lis3->init(lis3);
|
||||||
|
|
||||||
/* LIS3 power on delay is quite long */
|
|
||||||
msleep(lis3->pwron_delay / lis3lv02d_get_odr());
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Common configuration
|
* Common configuration
|
||||||
* BDU: (12 bits sensors only) LSB and MSB values are not updated until
|
* 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.
|
* 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, ®);
|
||||||
lis3->read(lis3, CTRL_REG2, ®);
|
if (lis3->whoami == WAI_12B)
|
||||||
reg |= CTRL2_BDU;
|
reg |= CTRL2_BDU | CTRL2_BOOT;
|
||||||
lis3->write(lis3, CTRL_REG2, reg);
|
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);
|
EXPORT_SYMBOL_GPL(lis3lv02d_poweron);
|
||||||
|
|
||||||
|
@ -262,6 +403,27 @@ static void lis3lv02d_joystick_poll(struct input_polled_dev *pidev)
|
||||||
mutex_unlock(&lis3_dev.mutex);
|
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)
|
static irqreturn_t lis302dl_interrupt(int irq, void *dummy)
|
||||||
{
|
{
|
||||||
if (!test_bit(0, &lis3_dev.misc_opened))
|
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);
|
wake_up_interruptible(&lis3_dev.misc_wait);
|
||||||
kill_fasync(&lis3_dev.async_queue, SIGIO, POLL_IN);
|
kill_fasync(&lis3_dev.async_queue, SIGIO, POLL_IN);
|
||||||
out:
|
out:
|
||||||
if (lis3_dev.pdata && lis3_dev.whoami == WAI_8B && lis3_dev.idev &&
|
if (atomic_read(&lis3_dev.wake_thread))
|
||||||
lis3_dev.idev->input->users)
|
|
||||||
return IRQ_WAKE_THREAD;
|
return IRQ_WAKE_THREAD;
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
@ -309,44 +470,41 @@ static void lis302dl_interrupt_handle_click(struct lis3lv02d *lis3)
|
||||||
mutex_unlock(&lis3->mutex);
|
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;
|
int dummy;
|
||||||
u8 wu2_src;
|
|
||||||
|
|
||||||
lis3->read(lis3, FF_WU_SRC_1, &wu1_src);
|
/* Dummy read to ack interrupt */
|
||||||
lis3->read(lis3, FF_WU_SRC_2, &wu2_src);
|
lis3lv02d_get_xyz(lis3, &dummy, &dummy, &dummy);
|
||||||
|
lis3->data_ready_count[index]++;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static irqreturn_t lis302dl_interrupt_thread1_8b(int irq, void *data)
|
static irqreturn_t lis302dl_interrupt_thread1_8b(int irq, void *data)
|
||||||
{
|
{
|
||||||
|
|
||||||
struct lis3lv02d *lis3 = 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);
|
lis302dl_interrupt_handle_click(lis3);
|
||||||
|
else if (unlikely(irq_cfg == LIS3_IRQ1_DATA_READY))
|
||||||
|
lis302dl_data_ready(lis3, IRQ_LINE0);
|
||||||
else
|
else
|
||||||
lis302dl_interrupt_handle_ff_wu(lis3);
|
lis3lv02d_joystick_poll(lis3->idev);
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
static irqreturn_t lis302dl_interrupt_thread2_8b(int irq, void *data)
|
static irqreturn_t lis302dl_interrupt_thread2_8b(int irq, void *data)
|
||||||
{
|
{
|
||||||
|
|
||||||
struct lis3lv02d *lis3 = 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);
|
lis302dl_interrupt_handle_click(lis3);
|
||||||
|
else if (unlikely(irq_cfg == LIS3_IRQ2_DATA_READY))
|
||||||
|
lis302dl_data_ready(lis3, IRQ_LINE1);
|
||||||
else
|
else
|
||||||
lis302dl_interrupt_handle_ff_wu(lis3);
|
lis3lv02d_joystick_poll(lis3->idev);
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
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))
|
if (test_and_set_bit(0, &lis3_dev.misc_opened))
|
||||||
return -EBUSY; /* already open */
|
return -EBUSY; /* already open */
|
||||||
|
|
||||||
|
if (lis3_dev.pm_dev)
|
||||||
|
pm_runtime_get_sync(lis3_dev.pm_dev);
|
||||||
|
|
||||||
atomic_set(&lis3_dev.count, 0);
|
atomic_set(&lis3_dev.count, 0);
|
||||||
return 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);
|
fasync_helper(-1, file, 0, &lis3_dev.async_queue);
|
||||||
clear_bit(0, &lis3_dev.misc_opened); /* release the device */
|
clear_bit(0, &lis3_dev.misc_opened); /* release the device */
|
||||||
|
if (lis3_dev.pm_dev)
|
||||||
|
pm_runtime_put(lis3_dev.pm_dev);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,6 +623,8 @@ int lis3lv02d_joystick_enable(void)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
lis3_dev.idev->poll = lis3lv02d_joystick_poll;
|
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 = MDPS_POLL_INTERVAL;
|
||||||
lis3_dev.idev->poll_interval_min = MDPS_POLL_MIN;
|
lis3_dev.idev->poll_interval_min = MDPS_POLL_MIN;
|
||||||
lis3_dev.idev->poll_interval_max = MDPS_POLL_MAX;
|
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);
|
set_bit(EV_ABS, input_dev->evbit);
|
||||||
max_val = (lis3_dev.mdps_max_val * lis3_dev.scale) / LIS3_ACCURACY;
|
max_val = (lis3_dev.mdps_max_val * lis3_dev.scale) / LIS3_ACCURACY;
|
||||||
fuzz = (LIS3_DEFAULT_FUZZ * lis3_dev.scale) / LIS3_ACCURACY;
|
if (lis3_dev.whoami == WAI_12B) {
|
||||||
flat = (LIS3_DEFAULT_FLAT * lis3_dev.scale) / LIS3_ACCURACY;
|
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_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_Y, -max_val, max_val, fuzz, flat);
|
||||||
input_set_abs_params(input_dev, ABS_Z, -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);
|
EXPORT_SYMBOL_GPL(lis3lv02d_joystick_disable);
|
||||||
|
|
||||||
/* Sysfs stuff */
|
/* 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,
|
static ssize_t lis3lv02d_selftest_show(struct device *dev,
|
||||||
struct device_attribute *attr, char *buf)
|
struct device_attribute *attr, char *buf)
|
||||||
{
|
{
|
||||||
int result;
|
|
||||||
s16 values[3];
|
s16 values[3];
|
||||||
|
|
||||||
result = lis3lv02d_selftest(&lis3_dev, values);
|
static const char ok[] = "OK";
|
||||||
return sprintf(buf, "%s %d %d %d\n", result == 0 ? "OK" : "FAIL",
|
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]);
|
values[0], values[1], values[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -528,6 +734,7 @@ static ssize_t lis3lv02d_position_show(struct device *dev,
|
||||||
{
|
{
|
||||||
int x, y, z;
|
int x, y, z;
|
||||||
|
|
||||||
|
lis3lv02d_sysfs_poweron(&lis3_dev);
|
||||||
mutex_lock(&lis3_dev.mutex);
|
mutex_lock(&lis3_dev.mutex);
|
||||||
lis3lv02d_get_xyz(&lis3_dev, &x, &y, &z);
|
lis3lv02d_get_xyz(&lis3_dev, &x, &y, &z);
|
||||||
mutex_unlock(&lis3_dev.mutex);
|
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,
|
static ssize_t lis3lv02d_rate_show(struct device *dev,
|
||||||
struct device_attribute *attr, char *buf)
|
struct device_attribute *attr, char *buf)
|
||||||
{
|
{
|
||||||
|
lis3lv02d_sysfs_poweron(&lis3_dev);
|
||||||
return sprintf(buf, "%d\n", lis3lv02d_get_odr());
|
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))
|
if (strict_strtoul(buf, 0, &rate))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
|
lis3lv02d_sysfs_poweron(&lis3_dev);
|
||||||
if (lis3lv02d_set_odr(rate))
|
if (lis3lv02d_set_odr(rate))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
|
@ -585,6 +794,18 @@ int lis3lv02d_remove_fs(struct lis3lv02d *lis3)
|
||||||
{
|
{
|
||||||
sysfs_remove_group(&lis3->pdev->dev.kobj, &lis3lv02d_attribute_group);
|
sysfs_remove_group(&lis3->pdev->dev.kobj, &lis3lv02d_attribute_group);
|
||||||
platform_device_unregister(lis3->pdev);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(lis3lv02d_remove_fs);
|
EXPORT_SYMBOL_GPL(lis3lv02d_remove_fs);
|
||||||
|
@ -616,16 +837,16 @@ static void lis3lv02d_8b_configure(struct lis3lv02d *dev,
|
||||||
if (p->wakeup_flags) {
|
if (p->wakeup_flags) {
|
||||||
dev->write(dev, FF_WU_CFG_1, p->wakeup_flags);
|
dev->write(dev, FF_WU_CFG_1, p->wakeup_flags);
|
||||||
dev->write(dev, FF_WU_THS_1, p->wakeup_thresh & 0x7f);
|
dev->write(dev, FF_WU_THS_1, p->wakeup_thresh & 0x7f);
|
||||||
/* default to 2.5ms for now */
|
/* pdata value + 1 to keep this backward compatible*/
|
||||||
dev->write(dev, FF_WU_DURATION_1, 1);
|
dev->write(dev, FF_WU_DURATION_1, p->duration1 + 1);
|
||||||
ctrl2 ^= HP_FF_WU1; /* Xor to keep compatible with old pdata*/
|
ctrl2 ^= HP_FF_WU1; /* Xor to keep compatible with old pdata*/
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p->wakeup_flags2) {
|
if (p->wakeup_flags2) {
|
||||||
dev->write(dev, FF_WU_CFG_2, p->wakeup_flags2);
|
dev->write(dev, FF_WU_CFG_2, p->wakeup_flags2);
|
||||||
dev->write(dev, FF_WU_THS_2, p->wakeup_thresh2 & 0x7f);
|
dev->write(dev, FF_WU_THS_2, p->wakeup_thresh2 & 0x7f);
|
||||||
/* default to 2.5ms for now */
|
/* pdata value + 1 to keep this backward compatible*/
|
||||||
dev->write(dev, FF_WU_DURATION_2, 1);
|
dev->write(dev, FF_WU_DURATION_2, p->duration2 + 1);
|
||||||
ctrl2 ^= HP_FF_WU2; /* Xor to keep compatible with old pdata*/
|
ctrl2 ^= HP_FF_WU2; /* Xor to keep compatible with old pdata*/
|
||||||
}
|
}
|
||||||
/* Configure hipass filters */
|
/* Configure hipass filters */
|
||||||
|
@ -635,8 +856,8 @@ static void lis3lv02d_8b_configure(struct lis3lv02d *dev,
|
||||||
err = request_threaded_irq(p->irq2,
|
err = request_threaded_irq(p->irq2,
|
||||||
NULL,
|
NULL,
|
||||||
lis302dl_interrupt_thread2_8b,
|
lis302dl_interrupt_thread2_8b,
|
||||||
IRQF_TRIGGER_RISING |
|
IRQF_TRIGGER_RISING | IRQF_ONESHOT |
|
||||||
IRQF_ONESHOT,
|
(p->irq_flags2 & IRQF_TRIGGER_MASK),
|
||||||
DRIVER_NAME, &lis3_dev);
|
DRIVER_NAME, &lis3_dev);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
printk(KERN_ERR DRIVER_NAME
|
printk(KERN_ERR DRIVER_NAME
|
||||||
|
@ -652,6 +873,7 @@ int lis3lv02d_init_device(struct lis3lv02d *dev)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
irq_handler_t thread_fn;
|
irq_handler_t thread_fn;
|
||||||
|
int irq_flags = 0;
|
||||||
|
|
||||||
dev->whoami = lis3lv02d_read_8(dev, WHO_AM_I);
|
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->odrs = lis3_12_rates;
|
||||||
dev->odr_mask = CTRL1_DF0 | CTRL1_DF1;
|
dev->odr_mask = CTRL1_DF0 | CTRL1_DF1;
|
||||||
dev->scale = LIS3_SENSITIVITY_12B;
|
dev->scale = LIS3_SENSITIVITY_12B;
|
||||||
|
dev->regs = lis3_wai12_regs;
|
||||||
|
dev->regs_size = ARRAY_SIZE(lis3_wai12_regs);
|
||||||
break;
|
break;
|
||||||
case WAI_8B:
|
case WAI_8B:
|
||||||
printk(KERN_INFO DRIVER_NAME ": 8 bits sensor found\n");
|
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->odrs = lis3_8_rates;
|
||||||
dev->odr_mask = CTRL1_DR;
|
dev->odr_mask = CTRL1_DR;
|
||||||
dev->scale = LIS3_SENSITIVITY_8B;
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
printk(KERN_ERR DRIVER_NAME
|
printk(KERN_ERR DRIVER_NAME
|
||||||
|
@ -680,11 +915,25 @@ int lis3lv02d_init_device(struct lis3lv02d *dev)
|
||||||
return -EINVAL;
|
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);
|
mutex_init(&dev->mutex);
|
||||||
|
atomic_set(&dev->wake_thread, 0);
|
||||||
|
|
||||||
lis3lv02d_add_fs(dev);
|
lis3lv02d_add_fs(dev);
|
||||||
lis3lv02d_poweron(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())
|
if (lis3lv02d_joystick_enable())
|
||||||
printk(KERN_ERR DRIVER_NAME ": joystick initialization failed\n");
|
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)
|
if (dev->whoami == WAI_8B)
|
||||||
lis3lv02d_8b_configure(dev, p);
|
lis3lv02d_8b_configure(dev, p);
|
||||||
|
|
||||||
|
irq_flags = p->irq_flags1 & IRQF_TRIGGER_MASK;
|
||||||
|
|
||||||
|
dev->irq_cfg = p->irq_cfg;
|
||||||
if (p->irq_cfg)
|
if (p->irq_cfg)
|
||||||
dev->write(dev, CTRL_REG3, 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 */
|
/* 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,
|
err = request_threaded_irq(dev->irq, lis302dl_interrupt,
|
||||||
thread_fn,
|
thread_fn,
|
||||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
IRQF_TRIGGER_RISING | IRQF_ONESHOT |
|
||||||
|
irq_flags,
|
||||||
DRIVER_NAME, &lis3_dev);
|
DRIVER_NAME, &lis3_dev);
|
||||||
|
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
*/
|
*/
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/input-polldev.h>
|
#include <linux/input-polldev.h>
|
||||||
|
#include <linux/regulator/consumer.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This driver tries to support the "digital" accelerometer chips from
|
* This driver tries to support the "digital" accelerometer chips from
|
||||||
|
@ -45,6 +46,7 @@ enum lis3_reg {
|
||||||
CTRL_REG1 = 0x20,
|
CTRL_REG1 = 0x20,
|
||||||
CTRL_REG2 = 0x21,
|
CTRL_REG2 = 0x21,
|
||||||
CTRL_REG3 = 0x22,
|
CTRL_REG3 = 0x22,
|
||||||
|
CTRL_REG4 = 0x23,
|
||||||
HP_FILTER_RESET = 0x23,
|
HP_FILTER_RESET = 0x23,
|
||||||
STATUS_REG = 0x27,
|
STATUS_REG = 0x27,
|
||||||
OUTX_L = 0x28,
|
OUTX_L = 0x28,
|
||||||
|
@ -93,6 +95,7 @@ enum lis3lv02d_reg {
|
||||||
};
|
};
|
||||||
|
|
||||||
enum lis3_who_am_i {
|
enum lis3_who_am_i {
|
||||||
|
WAI_3DC = 0x33, /* 8 bits: LIS3DC, HP3DC */
|
||||||
WAI_12B = 0x3A, /* 12 bits: LIS3LV02D[LQ]... */
|
WAI_12B = 0x3A, /* 12 bits: LIS3LV02D[LQ]... */
|
||||||
WAI_8B = 0x3B, /* 8 bits: LIS[23]02D[LQ]... */
|
WAI_8B = 0x3B, /* 8 bits: LIS[23]02D[LQ]... */
|
||||||
WAI_6B = 0x52, /* 6 bits: LIS331DLF - not supported */
|
WAI_6B = 0x52, /* 6 bits: LIS331DLF - not supported */
|
||||||
|
@ -118,6 +121,13 @@ enum lis3lv02d_ctrl1_8b {
|
||||||
CTRL1_DR = 0x80,
|
CTRL1_DR = 0x80,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum lis3lv02d_ctrl1_3dc {
|
||||||
|
CTRL1_ODR0 = 0x10,
|
||||||
|
CTRL1_ODR1 = 0x20,
|
||||||
|
CTRL1_ODR2 = 0x40,
|
||||||
|
CTRL1_ODR3 = 0x80,
|
||||||
|
};
|
||||||
|
|
||||||
enum lis3lv02d_ctrl2 {
|
enum lis3lv02d_ctrl2 {
|
||||||
CTRL2_DAS = 0x01,
|
CTRL2_DAS = 0x01,
|
||||||
CTRL2_SIM = 0x02,
|
CTRL2_SIM = 0x02,
|
||||||
|
@ -129,9 +139,18 @@ enum lis3lv02d_ctrl2 {
|
||||||
CTRL2_FS = 0x80, /* Full Scale selection */
|
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 {
|
enum lis302d_ctrl2 {
|
||||||
HP_FF_WU2 = 0x08,
|
HP_FF_WU2 = 0x08,
|
||||||
HP_FF_WU1 = 0x04,
|
HP_FF_WU1 = 0x04,
|
||||||
|
CTRL2_BOOT_8B = 0x40,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum lis3lv02d_ctrl3 {
|
enum lis3lv02d_ctrl3 {
|
||||||
|
@ -206,19 +225,33 @@ enum lis3lv02d_click_src_8b {
|
||||||
CLICK_IA = 0x40,
|
CLICK_IA = 0x40,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct axis_conversion {
|
enum lis3lv02d_reg_state {
|
||||||
s8 x;
|
LIS3_REG_OFF = 0x00,
|
||||||
s8 y;
|
LIS3_REG_ON = 0x01,
|
||||||
s8 z;
|
};
|
||||||
|
|
||||||
|
union axis_conversion {
|
||||||
|
struct {
|
||||||
|
int x, y, z;
|
||||||
|
};
|
||||||
|
int as_array[3];
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct lis3lv02d {
|
struct lis3lv02d {
|
||||||
void *bus_priv; /* used by the bus layer only */
|
void *bus_priv; /* used by the bus layer only */
|
||||||
|
struct device *pm_dev; /* for pm_runtime purposes */
|
||||||
int (*init) (struct lis3lv02d *lis3);
|
int (*init) (struct lis3lv02d *lis3);
|
||||||
int (*write) (struct lis3lv02d *lis3, int reg, u8 val);
|
int (*write) (struct lis3lv02d *lis3, int reg, u8 val);
|
||||||
int (*read) (struct lis3lv02d *lis3, int reg, u8 *ret);
|
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 */
|
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 odr_mask; /* ODR bit mask */
|
||||||
u8 whoami; /* indicates measurement precision */
|
u8 whoami; /* indicates measurement precision */
|
||||||
s16 (*read_data) (struct lis3lv02d *lis3, int reg);
|
s16 (*read_data) (struct lis3lv02d *lis3, int reg);
|
||||||
|
@ -231,14 +264,18 @@ struct lis3lv02d {
|
||||||
|
|
||||||
struct input_polled_dev *idev; /* input device */
|
struct input_polled_dev *idev; /* input device */
|
||||||
struct platform_device *pdev; /* platform device */
|
struct platform_device *pdev; /* platform device */
|
||||||
|
struct regulator_bulk_data regulators[2];
|
||||||
atomic_t count; /* interrupt count after last read */
|
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];
|
int mapped_btns[3];
|
||||||
|
|
||||||
u32 irq; /* IRQ number */
|
u32 irq; /* IRQ number */
|
||||||
struct fasync_struct *async_queue; /* queue for the misc device */
|
struct fasync_struct *async_queue; /* queue for the misc device */
|
||||||
wait_queue_head_t misc_wait; /* Wait 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 */
|
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 lis3lv02d_platform_data *pdata; /* for passing board config */
|
||||||
struct mutex mutex; /* Serialize poll and selftest */
|
struct mutex mutex; /* Serialize poll and selftest */
|
||||||
|
|
|
@ -29,10 +29,30 @@
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/i2c.h>
|
#include <linux/i2c.h>
|
||||||
|
#include <linux/pm_runtime.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
#include "lis3lv02d.h"
|
#include "lis3lv02d.h"
|
||||||
|
|
||||||
#define DRV_NAME "lis3lv02d_i2c"
|
#define DRV_NAME "lis3lv02d_i2c"
|
||||||
|
|
||||||
|
static const char reg_vdd[] = "Vdd";
|
||||||
|
static const char reg_vdd_io[] = "Vdd_IO";
|
||||||
|
|
||||||
|
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)
|
static inline s32 lis3_i2c_write(struct lis3lv02d *lis3, int reg, u8 value)
|
||||||
{
|
{
|
||||||
struct i2c_client *c = lis3->bus_priv;
|
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;
|
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)
|
static int lis3_i2c_init(struct lis3lv02d *lis3)
|
||||||
{
|
{
|
||||||
u8 reg;
|
u8 reg;
|
||||||
int ret;
|
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 */
|
/* power up the device */
|
||||||
ret = lis3->read(lis3, CTRL_REG1, ®);
|
ret = lis3->read(lis3, CTRL_REG1, ®);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
reg |= CTRL1_PD0;
|
reg |= CTRL1_PD0 | CTRL1_Xen | CTRL1_Yen | CTRL1_Zen;
|
||||||
return lis3->write(lis3, CTRL_REG1, reg);
|
return lis3->write(lis3, CTRL_REG1, reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Default axis mapping but it can be overwritten by platform data */
|
/* Default axis mapping but it can be overwritten by platform data */
|
||||||
static struct axis_conversion lis3lv02d_axis_map = { LIS3_DEV_X,
|
static union axis_conversion lis3lv02d_axis_map =
|
||||||
LIS3_DEV_Y,
|
{ .as_array = { LIS3_DEV_X, LIS3_DEV_Y, LIS3_DEV_Z } };
|
||||||
LIS3_DEV_Z };
|
|
||||||
|
|
||||||
static int __devinit lis3lv02d_i2c_probe(struct i2c_client *client,
|
static int __devinit lis3lv02d_i2c_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
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;
|
struct lis3lv02d_platform_data *pdata = client->dev.platform_data;
|
||||||
|
|
||||||
if (pdata) {
|
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)
|
if (pdata->axis_x)
|
||||||
lis3lv02d_axis_map.x = 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;
|
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.pdata = pdata;
|
||||||
lis3_dev.bus_priv = client;
|
lis3_dev.bus_priv = client;
|
||||||
lis3_dev.init = lis3_i2c_init;
|
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.write = lis3_i2c_write;
|
||||||
lis3_dev.irq = client->irq;
|
lis3_dev.irq = client->irq;
|
||||||
lis3_dev.ac = lis3lv02d_axis_map;
|
lis3_dev.ac = lis3lv02d_axis_map;
|
||||||
|
lis3_dev.pm_dev = &client->dev;
|
||||||
|
|
||||||
i2c_set_clientdata(client, &lis3_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);
|
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:
|
fail:
|
||||||
|
if (pdata && pdata->release_resources)
|
||||||
|
pdata->release_resources();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,14 +178,18 @@ static int __devexit lis3lv02d_i2c_remove(struct i2c_client *client)
|
||||||
pdata->release_resources();
|
pdata->release_resources();
|
||||||
|
|
||||||
lis3lv02d_joystick_disable();
|
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
|
#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);
|
struct lis3lv02d *lis3 = i2c_get_clientdata(client);
|
||||||
|
|
||||||
if (!lis3->pdata || !lis3->pdata->wakeup_flags)
|
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;
|
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);
|
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);
|
lis3lv02d_poweron(lis3);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void lis3lv02d_i2c_shutdown(struct i2c_client *client)
|
return 0;
|
||||||
{
|
|
||||||
lis3lv02d_i2c_suspend(client, PMSG_SUSPEND);
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
#define lis3lv02d_i2c_suspend NULL
|
#define lis3lv02d_i2c_suspend NULL
|
||||||
|
@ -145,6 +219,24 @@ static void lis3lv02d_i2c_shutdown(struct i2c_client *client)
|
||||||
#define lis3lv02d_i2c_shutdown NULL
|
#define lis3lv02d_i2c_shutdown NULL
|
||||||
#endif
|
#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[] = {
|
static const struct i2c_device_id lis3lv02d_id[] = {
|
||||||
{"lis3lv02d", 0 },
|
{"lis3lv02d", 0 },
|
||||||
{}
|
{}
|
||||||
|
@ -152,14 +244,20 @@ static const struct i2c_device_id lis3lv02d_id[] = {
|
||||||
|
|
||||||
MODULE_DEVICE_TABLE(i2c, 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 = {
|
static struct i2c_driver lis3lv02d_i2c_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = DRV_NAME,
|
.name = DRV_NAME,
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
|
.pm = &lis3_pm_ops,
|
||||||
},
|
},
|
||||||
.suspend = lis3lv02d_i2c_suspend,
|
|
||||||
.shutdown = lis3lv02d_i2c_shutdown,
|
|
||||||
.resume = lis3lv02d_i2c_resume,
|
|
||||||
.probe = lis3lv02d_i2c_probe,
|
.probe = lis3lv02d_i2c_probe,
|
||||||
.remove = __devexit_p(lis3lv02d_i2c_remove),
|
.remove = __devexit_p(lis3lv02d_i2c_remove),
|
||||||
.id_table = lis3lv02d_id,
|
.id_table = lis3lv02d_id,
|
||||||
|
|
|
@ -50,11 +50,12 @@ static int lis3_spi_init(struct lis3lv02d *lis3)
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
reg |= CTRL1_PD0;
|
reg |= CTRL1_PD0 | CTRL1_Xen | CTRL1_Yen | CTRL1_Zen;
|
||||||
return lis3->write(lis3, CTRL_REG1, reg);
|
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)
|
static int __devinit lis302dl_spi_probe(struct spi_device *spi)
|
||||||
{
|
{
|
||||||
|
|
315
drivers/hwmon/ltc4261.c
Normal file
315
drivers/hwmon/ltc4261.c
Normal file
|
@ -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);
|
|
@ -21,7 +21,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/delay.h>
|
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/jiffies.h>
|
#include <linux/jiffies.h>
|
||||||
|
@ -35,6 +34,7 @@
|
||||||
#include <linux/cpu.h>
|
#include <linux/cpu.h>
|
||||||
#include <asm/msr.h>
|
#include <asm/msr.h>
|
||||||
#include <asm/processor.h>
|
#include <asm/processor.h>
|
||||||
|
#include <asm/smp.h>
|
||||||
|
|
||||||
#define DRVNAME "pkgtemp"
|
#define DRVNAME "pkgtemp"
|
||||||
|
|
||||||
|
@ -339,8 +339,7 @@ static int __cpuinit pkgtemp_device_add(unsigned int cpu)
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_HOTPLUG_CPU
|
static void __cpuinit pkgtemp_device_remove(unsigned int cpu)
|
||||||
static void pkgtemp_device_remove(unsigned int cpu)
|
|
||||||
{
|
{
|
||||||
struct pdev_entry *p;
|
struct pdev_entry *p;
|
||||||
unsigned int i;
|
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 = {
|
static struct notifier_block pkgtemp_cpu_notifier __refdata = {
|
||||||
.notifier_call = pkgtemp_cpu_callback,
|
.notifier_call = pkgtemp_cpu_callback,
|
||||||
};
|
};
|
||||||
#endif /* !CONFIG_HOTPLUG_CPU */
|
|
||||||
|
|
||||||
static int __init pkgtemp_init(void)
|
static int __init pkgtemp_init(void)
|
||||||
{
|
{
|
||||||
int i, err = -ENODEV;
|
int i, err = -ENODEV;
|
||||||
struct pdev_entry *p, *n;
|
|
||||||
|
|
||||||
/* quick check if we run Intel */
|
/* quick check if we run Intel */
|
||||||
if (cpu_data(0).x86_vendor != X86_VENDOR_INTEL)
|
if (cpu_data(0).x86_vendor != X86_VENDOR_INTEL)
|
||||||
|
@ -402,31 +399,23 @@ static int __init pkgtemp_init(void)
|
||||||
if (err)
|
if (err)
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
||||||
for_each_online_cpu(i) {
|
for_each_online_cpu(i)
|
||||||
err = pkgtemp_device_add(i);
|
pkgtemp_device_add(i);
|
||||||
if (err)
|
|
||||||
goto exit_devices_unreg;
|
#ifndef CONFIG_HOTPLUG_CPU
|
||||||
}
|
|
||||||
if (list_empty(&pdev_list)) {
|
if (list_empty(&pdev_list)) {
|
||||||
err = -ENODEV;
|
err = -ENODEV;
|
||||||
goto exit_driver_unreg;
|
goto exit_driver_unreg;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_HOTPLUG_CPU
|
|
||||||
register_hotcpu_notifier(&pkgtemp_cpu_notifier);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
register_hotcpu_notifier(&pkgtemp_cpu_notifier);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
exit_devices_unreg:
|
#ifndef CONFIG_HOTPLUG_CPU
|
||||||
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);
|
|
||||||
exit_driver_unreg:
|
exit_driver_unreg:
|
||||||
platform_driver_unregister(&pkgtemp_driver);
|
platform_driver_unregister(&pkgtemp_driver);
|
||||||
|
#endif
|
||||||
exit:
|
exit:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
@ -434,9 +423,8 @@ static int __init pkgtemp_init(void)
|
||||||
static void __exit pkgtemp_exit(void)
|
static void __exit pkgtemp_exit(void)
|
||||||
{
|
{
|
||||||
struct pdev_entry *p, *n;
|
struct pdev_entry *p, *n;
|
||||||
#ifdef CONFIG_HOTPLUG_CPU
|
|
||||||
unregister_hotcpu_notifier(&pkgtemp_cpu_notifier);
|
unregister_hotcpu_notifier(&pkgtemp_cpu_notifier);
|
||||||
#endif
|
|
||||||
mutex_lock(&pdev_list_mutex);
|
mutex_lock(&pdev_list_mutex);
|
||||||
list_for_each_entry_safe(p, n, &pdev_list, list) {
|
list_for_each_entry_safe(p, n, &pdev_list, list) {
|
||||||
platform_device_unregister(p->pdev);
|
platform_device_unregister(p->pdev);
|
||||||
|
|
|
@ -22,10 +22,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/delay.h>
|
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/jiffies.h>
|
|
||||||
#include <linux/hwmon.h>
|
#include <linux/hwmon.h>
|
||||||
#include <linux/sysfs.h>
|
#include <linux/sysfs.h>
|
||||||
#include <linux/hwmon-sysfs.h>
|
#include <linux/hwmon-sysfs.h>
|
||||||
|
@ -237,8 +235,7 @@ static int __cpuinit via_cputemp_device_add(unsigned int cpu)
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_HOTPLUG_CPU
|
static void __cpuinit via_cputemp_device_remove(unsigned int cpu)
|
||||||
static void via_cputemp_device_remove(unsigned int cpu)
|
|
||||||
{
|
{
|
||||||
struct pdev_entry *p, *n;
|
struct pdev_entry *p, *n;
|
||||||
mutex_lock(&pdev_list_mutex);
|
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 = {
|
static struct notifier_block via_cputemp_cpu_notifier __refdata = {
|
||||||
.notifier_call = via_cputemp_cpu_callback,
|
.notifier_call = via_cputemp_cpu_callback,
|
||||||
};
|
};
|
||||||
#endif /* !CONFIG_HOTPLUG_CPU */
|
|
||||||
|
|
||||||
static int __init via_cputemp_init(void)
|
static int __init via_cputemp_init(void)
|
||||||
{
|
{
|
||||||
|
@ -313,9 +309,7 @@ static int __init via_cputemp_init(void)
|
||||||
goto exit_driver_unreg;
|
goto exit_driver_unreg;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_HOTPLUG_CPU
|
|
||||||
register_hotcpu_notifier(&via_cputemp_cpu_notifier);
|
register_hotcpu_notifier(&via_cputemp_cpu_notifier);
|
||||||
#endif
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
exit_devices_unreg:
|
exit_devices_unreg:
|
||||||
|
@ -335,9 +329,8 @@ static int __init via_cputemp_init(void)
|
||||||
static void __exit via_cputemp_exit(void)
|
static void __exit via_cputemp_exit(void)
|
||||||
{
|
{
|
||||||
struct pdev_entry *p, *n;
|
struct pdev_entry *p, *n;
|
||||||
#ifdef CONFIG_HOTPLUG_CPU
|
|
||||||
unregister_hotcpu_notifier(&via_cputemp_cpu_notifier);
|
unregister_hotcpu_notifier(&via_cputemp_cpu_notifier);
|
||||||
#endif
|
|
||||||
mutex_lock(&pdev_list_mutex);
|
mutex_lock(&pdev_list_mutex);
|
||||||
list_for_each_entry_safe(p, n, &pdev_list, list) {
|
list_for_each_entry_safe(p, n, &pdev_list, list) {
|
||||||
platform_device_unregister(p->pdev);
|
platform_device_unregister(p->pdev);
|
||||||
|
|
36
include/linux/gpio-fan.h
Normal file
36
include/linux/gpio-fan.h
Normal file
|
@ -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 */
|
|
@ -1,6 +1,52 @@
|
||||||
#ifndef __LIS3LV02D_H_
|
#ifndef __LIS3LV02D_H_
|
||||||
#define __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 {
|
struct lis3lv02d_platform_data {
|
||||||
/* please note: the 'click' feature is only supported for
|
/* please note: the 'click' feature is only supported for
|
||||||
* LIS[32]02DL variants of the chip and will be ignored 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_OPEN_DRAIN (1 << 6)
|
||||||
#define LIS3_IRQ_ACTIVE_LOW (1 << 7)
|
#define LIS3_IRQ_ACTIVE_LOW (1 << 7)
|
||||||
unsigned char irq_cfg;
|
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_LO (1 << 0)
|
||||||
#define LIS3_WAKEUP_X_HI (1 << 1)
|
#define LIS3_WAKEUP_X_HI (1 << 1)
|
||||||
#define LIS3_WAKEUP_Y_LO (1 << 2)
|
#define LIS3_WAKEUP_Y_LO (1 << 2)
|
||||||
|
@ -64,6 +113,10 @@ struct lis3lv02d_platform_data {
|
||||||
s8 axis_x;
|
s8 axis_x;
|
||||||
s8 axis_y;
|
s8 axis_y;
|
||||||
s8 axis_z;
|
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 (*setup_resources)(void);
|
||||||
int (*release_resources)(void);
|
int (*release_resources)(void);
|
||||||
/* Limits for selftest are specified in chip data sheet */
|
/* Limits for selftest are specified in chip data sheet */
|
||||||
|
|
Loading…
Reference in a new issue