2009-01-10 03:41:01 +03:00
/*
* hp_accel . c - Interface between LIS3LV02DL driver and HP ACPI BIOS
*
* Copyright ( C ) 2007 - 2008 Yan Burman
* Copyright ( C ) 2008 Eric Piel
2009-01-16 00:51:24 +03:00
* Copyright ( C ) 2008 - 2009 Pavel Machek
2009-01-10 03:41:01 +03:00
*
* 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
*/
2010-10-20 10:51:35 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2009-01-10 03:41:01 +03:00
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/dmi.h>
# include <linux/module.h>
# include <linux/types.h>
# include <linux/platform_device.h>
# include <linux/interrupt.h>
# include <linux/delay.h>
# include <linux/wait.h>
# include <linux/poll.h>
# include <linux/freezer.h>
# include <linux/uaccess.h>
2009-01-16 00:51:23 +03:00
# include <linux/leds.h>
2009-01-10 03:41:01 +03:00
# include <acpi/acpi_drivers.h>
# include <asm/atomic.h>
# include "lis3lv02d.h"
# define DRIVER_NAME "lis3lv02d"
# define ACPI_MDPS_CLASS "accelerometer"
2009-01-16 00:51:24 +03:00
/* Delayed LEDs infrastructure ------------------------------------ */
/* Special LED class that can defer work */
struct delayed_led_classdev {
struct led_classdev led_classdev ;
struct work_struct work ;
enum led_brightness new_brightness ;
unsigned int led ; /* For driver */
void ( * set_brightness ) ( struct delayed_led_classdev * data , enum led_brightness value ) ;
} ;
static inline void delayed_set_status_worker ( struct work_struct * work )
{
struct delayed_led_classdev * data =
container_of ( work , struct delayed_led_classdev , work ) ;
data - > set_brightness ( data , data - > new_brightness ) ;
}
static inline void delayed_sysfs_set ( struct led_classdev * led_cdev ,
enum led_brightness brightness )
{
struct delayed_led_classdev * data = container_of ( led_cdev ,
struct delayed_led_classdev , led_classdev ) ;
data - > new_brightness = brightness ;
schedule_work ( & data - > work ) ;
}
/* HP-specific accelerometer driver ------------------------------------ */
2009-01-10 03:41:01 +03:00
/* For automatic insertion of the module */
static struct acpi_device_id lis3lv02d_device_ids [ ] = {
{ " HPQ0004 " , 0 } , /* HP Mobile Data Protection System PNP */
{ " " , 0 } ,
} ;
MODULE_DEVICE_TABLE ( acpi , lis3lv02d_device_ids ) ;
/**
* lis3lv02d_acpi_init - ACPI _INI method : initialize the device .
2009-04-01 02:24:32 +04:00
* @ lis3 : pointer to the device struct
2009-01-10 03:41:01 +03:00
*
2009-04-01 02:24:32 +04:00
* Returns 0 on success .
2009-01-10 03:41:01 +03:00
*/
2009-04-01 02:24:32 +04:00
int lis3lv02d_acpi_init ( struct lis3lv02d * lis3 )
2009-01-10 03:41:01 +03:00
{
2009-04-01 02:24:32 +04:00
struct acpi_device * dev = lis3 - > bus_priv ;
if ( acpi_evaluate_object ( dev - > handle , METHOD_NAME__INI ,
NULL , NULL ) ! = AE_OK )
return - EINVAL ;
return 0 ;
2009-01-10 03:41:01 +03:00
}
/**
* lis3lv02d_acpi_read - ACPI ALRD method : read a register
2009-04-01 02:24:32 +04:00
* @ lis3 : pointer to the device struct
2009-01-10 03:41:01 +03:00
* @ reg : the register to read
* @ ret : result of the operation
*
2009-04-01 02:24:32 +04:00
* Returns 0 on success .
2009-01-10 03:41:01 +03:00
*/
2009-04-01 02:24:32 +04:00
int lis3lv02d_acpi_read ( struct lis3lv02d * lis3 , int reg , u8 * ret )
2009-01-10 03:41:01 +03:00
{
2009-04-01 02:24:32 +04:00
struct acpi_device * dev = lis3 - > bus_priv ;
2009-01-10 03:41:01 +03:00
union acpi_object arg0 = { ACPI_TYPE_INTEGER } ;
struct acpi_object_list args = { 1 , & arg0 } ;
unsigned long long lret ;
acpi_status status ;
arg0 . integer . value = reg ;
2009-04-01 02:24:32 +04:00
status = acpi_evaluate_integer ( dev - > handle , " ALRD " , & args , & lret ) ;
2009-01-10 03:41:01 +03:00
* ret = lret ;
2009-04-01 02:24:32 +04:00
return ( status ! = AE_OK ) ? - EINVAL : 0 ;
2009-01-10 03:41:01 +03:00
}
/**
* lis3lv02d_acpi_write - ACPI ALWR method : write to a register
2009-04-01 02:24:32 +04:00
* @ lis3 : pointer to the device struct
2009-01-10 03:41:01 +03:00
* @ reg : the register to write to
* @ val : the value to write
*
2009-04-01 02:24:32 +04:00
* Returns 0 on success .
2009-01-10 03:41:01 +03:00
*/
2009-04-01 02:24:32 +04:00
int lis3lv02d_acpi_write ( struct lis3lv02d * lis3 , int reg , u8 val )
2009-01-10 03:41:01 +03:00
{
2009-04-01 02:24:32 +04:00
struct acpi_device * dev = lis3 - > bus_priv ;
2009-01-10 03:41:01 +03:00
unsigned long long ret ; /* Not used when writting */
union acpi_object in_obj [ 2 ] ;
struct acpi_object_list args = { 2 , in_obj } ;
in_obj [ 0 ] . type = ACPI_TYPE_INTEGER ;
in_obj [ 0 ] . integer . value = reg ;
in_obj [ 1 ] . type = ACPI_TYPE_INTEGER ;
in_obj [ 1 ] . integer . value = val ;
2009-04-01 02:24:32 +04:00
if ( acpi_evaluate_integer ( dev - > handle , " ALWR " , & args , & ret ) ! = AE_OK )
return - EINVAL ;
return 0 ;
2009-01-10 03:41:01 +03:00
}
static int lis3lv02d_dmi_matched ( const struct dmi_system_id * dmi )
{
2010-10-02 01:14:25 +04:00
lis3_dev . ac = * ( ( union axis_conversion * ) dmi - > driver_data ) ;
2010-10-20 10:51:35 +04:00
pr_info ( " hardware type %s found \n " , dmi - > ident ) ;
2009-01-10 03:41:01 +03:00
return 1 ;
}
/* Represents, for each axis seen by userspace, the corresponding hw axis (+1).
* If the value is negative , the opposite of the hw value is used . */
2010-10-02 01:14:25 +04:00
# define DEFINE_CONV(name, x, y, z) \
static union axis_conversion lis3lv02d_axis_ # # name = \
{ . as_array = { x , y , z } }
DEFINE_CONV ( normal , 1 , 2 , 3 ) ;
DEFINE_CONV ( y_inverted , 1 , - 2 , 3 ) ;
DEFINE_CONV ( x_inverted , - 1 , 2 , 3 ) ;
DEFINE_CONV ( z_inverted , 1 , 2 , - 3 ) ;
DEFINE_CONV ( xy_swap , 2 , 1 , 3 ) ;
DEFINE_CONV ( xy_rotated_left , - 2 , 1 , 3 ) ;
DEFINE_CONV ( xy_rotated_left_usd , - 2 , 1 , - 3 ) ;
DEFINE_CONV ( xy_swap_inverted , - 2 , - 1 , 3 ) ;
DEFINE_CONV ( xy_rotated_right , 2 , - 1 , 3 ) ;
DEFINE_CONV ( xy_swap_yz_inverted , 2 , - 1 , - 3 ) ;
2009-01-10 03:41:01 +03:00
# define AXIS_DMI_MATCH(_ident, _name, _axis) { \
. ident = _ident , \
. callback = lis3lv02d_dmi_matched , \
. matches = { \
DMI_MATCH ( DMI_PRODUCT_NAME , _name ) \
} , \
. driver_data = & lis3lv02d_axis_ # # _axis \
}
2009-02-19 01:48:25 +03:00
# define AXIS_DMI_MATCH2(_ident, _class1, _name1, \
_class2 , _name2 , \
_axis ) { \
. ident = _ident , \
. callback = lis3lv02d_dmi_matched , \
. matches = { \
DMI_MATCH ( DMI_ # # _class1 , _name1 ) , \
DMI_MATCH ( DMI_ # # _class2 , _name2 ) , \
} , \
. driver_data = & lis3lv02d_axis_ # # _axis \
}
2009-01-10 03:41:01 +03:00
static struct dmi_system_id lis3lv02d_dmi_ids [ ] = {
/* product names are truncated to match all kinds of a same model */
AXIS_DMI_MATCH ( " NC64x0 " , " HP Compaq nc64 " , x_inverted ) ,
AXIS_DMI_MATCH ( " NC84x0 " , " HP Compaq nc84 " , z_inverted ) ,
AXIS_DMI_MATCH ( " NX9420 " , " HP Compaq nx9420 " , x_inverted ) ,
AXIS_DMI_MATCH ( " NW9440 " , " HP Compaq nw9440 " , x_inverted ) ,
AXIS_DMI_MATCH ( " NC2510 " , " HP Compaq 2510 " , y_inverted ) ,
2009-06-17 02:34:16 +04:00
AXIS_DMI_MATCH ( " NC2710 " , " HP Compaq 2710 " , xy_swap ) ,
2009-01-10 03:41:01 +03:00
AXIS_DMI_MATCH ( " NC8510 " , " HP Compaq 8510 " , xy_swap_inverted ) ,
AXIS_DMI_MATCH ( " HP2133 " , " HP 2133 " , xy_rotated_left ) ,
2009-04-01 02:24:29 +04:00
AXIS_DMI_MATCH ( " HP2140 " , " HP 2140 " , xy_swap_inverted ) ,
2009-02-05 02:12:11 +03:00
AXIS_DMI_MATCH ( " NC653x " , " HP Compaq 653 " , xy_rotated_left_usd ) ,
2009-10-27 02:50:19 +03:00
AXIS_DMI_MATCH ( " NC6730b " , " HP Compaq 6730b " , xy_rotated_left_usd ) ,
AXIS_DMI_MATCH ( " NC6730s " , " HP Compaq 6730s " , xy_swap ) ,
2009-02-05 02:12:09 +03:00
AXIS_DMI_MATCH ( " NC651xx " , " HP Compaq 651 " , xy_rotated_right ) ,
2009-06-17 02:34:16 +04:00
AXIS_DMI_MATCH ( " NC6710x " , " HP Compaq 6710 " , xy_swap_yz_inverted ) ,
AXIS_DMI_MATCH ( " NC6715x " , " HP Compaq 6715 " , y_inverted ) ,
AXIS_DMI_MATCH ( " NC693xx " , " HP EliteBook 693 " , xy_rotated_right ) ,
2009-10-27 02:50:20 +03:00
AXIS_DMI_MATCH ( " NC693xx " , " HP EliteBook 853 " , xy_swap ) ,
2009-02-19 01:48:25 +03:00
/* Intel-based HP Pavilion dv5 */
AXIS_DMI_MATCH2 ( " HPDV5_I " ,
PRODUCT_NAME , " HP Pavilion dv5 " ,
BOARD_NAME , " 3603 " ,
x_inverted ) ,
/* AMD-based HP Pavilion dv5 */
AXIS_DMI_MATCH2 ( " HPDV5_A " ,
PRODUCT_NAME , " HP Pavilion dv5 " ,
BOARD_NAME , " 3600 " ,
y_inverted ) ,
2009-04-01 02:24:29 +04:00
AXIS_DMI_MATCH ( " DV7 " , " HP Pavilion dv7 " , x_inverted ) ,
hp_accel: axis conversion for hp compaq 8710w
I have a laptop HP Compaq 8710W, I compiled into my kernel the LIS3LV02DL
and HP_ACCEL module drivers. While loading it cannot recognize the laptop
model, so i am sending the necessary information to update the database of
axis orientations.
>When the laptop is horizontal the position reported is about 0 for X and Y
>and a positive value for Z
Yes, it is about 0,0,1000, the actual reading says: (-17,-26,1018);
> If the left side is elevated, X increases (becomes positive)
Yes, X goes toward to positive 1000.
>If the front side (where the touchpad is) is elevated, Y decreases (becomes negative)
No, Y goes toward to positive 1000.
>If the laptop is put upside-down, Z becomes negative
Yes, the laptop on a table Z gives 1000, and if upsidedown the Z reads
-1000.
So in few words the Y axis is inverted.
Cc: Eric Piel <eric.piel@tremplin-utc.net>
Signed-off-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2009-04-01 02:24:31 +04:00
AXIS_DMI_MATCH ( " HP8710 " , " HP Compaq 8710 " , y_inverted ) ,
2009-10-27 02:50:21 +03:00
AXIS_DMI_MATCH ( " HDX18 " , " HP HDX 18 " , x_inverted ) ,
2010-04-23 21:17:48 +04:00
AXIS_DMI_MATCH ( " HPB432x " , " HP ProBook 432 " , xy_rotated_left ) ,
AXIS_DMI_MATCH ( " HPB442x " , " HP ProBook 442 " , xy_rotated_left ) ,
AXIS_DMI_MATCH ( " HPB452x " , " HP ProBook 452 " , y_inverted ) ,
AXIS_DMI_MATCH ( " HPB522x " , " HP ProBook 522 " , xy_swap ) ,
2010-09-10 03:37:31 +04:00
AXIS_DMI_MATCH ( " HPB532x " , " HP ProBook 532 " , y_inverted ) ,
2010-08-27 04:21:43 +04:00
AXIS_DMI_MATCH ( " Mini510x " , " HP Mini 510 " , xy_rotated_left_usd ) ,
2009-01-10 03:41:01 +03:00
{ NULL , }
/* Laptop models without axis info (yet):
* " NC6910 " " HP Compaq 6910 "
* " NC2400 " " HP Compaq nc2400 "
* " NX74x0 " " HP Compaq nx74 "
* " NX6325 " " HP Compaq nx6325 "
* " NC4400 " " HP Compaq nc4400 "
*/
} ;
2009-01-16 00:51:24 +03:00
static void hpled_set ( struct delayed_led_classdev * led_cdev , enum led_brightness value )
2009-01-16 00:51:23 +03:00
{
2009-04-01 02:24:32 +04:00
struct acpi_device * dev = lis3_dev . bus_priv ;
2009-01-16 00:51:23 +03:00
unsigned long long ret ; /* Not used when writing */
union acpi_object in_obj [ 1 ] ;
struct acpi_object_list args = { 1 , in_obj } ;
in_obj [ 0 ] . type = ACPI_TYPE_INTEGER ;
2009-01-16 00:51:24 +03:00
in_obj [ 0 ] . integer . value = ! ! value ;
2009-01-16 00:51:23 +03:00
2009-04-01 02:24:32 +04:00
acpi_evaluate_integer ( dev - > handle , " ALED " , & args , & ret ) ;
2009-01-16 00:51:23 +03:00
}
2009-01-16 00:51:24 +03:00
static struct delayed_led_classdev hpled_led = {
. led_classdev = {
. name = " hp::hddprotect " ,
. default_trigger = " none " ,
. brightness_set = delayed_sysfs_set ,
. flags = LED_CORE_SUSPENDRESUME ,
} ,
. set_brightness = hpled_set ,
2009-01-16 00:51:23 +03:00
} ;
2009-01-10 03:41:01 +03:00
2009-02-19 01:48:23 +03:00
static acpi_status
lis3lv02d_get_resource ( struct acpi_resource * resource , void * context )
{
if ( resource - > type = = ACPI_RESOURCE_TYPE_EXTENDED_IRQ ) {
struct acpi_resource_extended_irq * irq ;
u32 * device_irq = context ;
irq = & resource - > data . extended_irq ;
* device_irq = irq - > interrupts [ 0 ] ;
}
return AE_OK ;
}
static void lis3lv02d_enum_resources ( struct acpi_device * device )
{
acpi_status status ;
status = acpi_walk_resources ( device - > handle , METHOD_NAME__CRS ,
2009-04-01 02:24:26 +04:00
lis3lv02d_get_resource , & lis3_dev . irq ) ;
2009-02-19 01:48:23 +03:00
if ( ACPI_FAILURE ( status ) )
printk ( KERN_DEBUG DRIVER_NAME " : Error getting resources \n " ) ;
}
2009-01-10 03:41:01 +03:00
static int lis3lv02d_add ( struct acpi_device * device )
{
2009-01-16 00:51:23 +03:00
int ret ;
2009-01-10 03:41:01 +03:00
if ( ! device )
return - EINVAL ;
2009-04-01 02:24:32 +04:00
lis3_dev . bus_priv = device ;
2009-04-01 02:24:26 +04:00
lis3_dev . init = lis3lv02d_acpi_init ;
lis3_dev . read = lis3lv02d_acpi_read ;
lis3_dev . write = lis3lv02d_acpi_write ;
2009-01-10 03:41:01 +03:00
strcpy ( acpi_device_name ( device ) , DRIVER_NAME ) ;
strcpy ( acpi_device_class ( device ) , ACPI_MDPS_CLASS ) ;
2009-04-01 02:24:26 +04:00
device - > driver_data = & lis3_dev ;
2009-01-10 03:41:01 +03:00
2009-04-01 02:24:32 +04:00
/* obtain IRQ number of our device from ACPI */
lis3lv02d_enum_resources ( device ) ;
2009-01-10 03:41:01 +03:00
/* If possible use a "standard" axes order */
2010-10-02 01:14:25 +04:00
if ( lis3_dev . ac . x & & lis3_dev . ac . y & & lis3_dev . ac . z ) {
2010-10-20 10:51:35 +04:00
pr_info ( " Using custom axes %d,%d,%d \n " ,
lis3_dev . ac . x , lis3_dev . ac . y , lis3_dev . ac . z ) ;
2010-10-02 01:14:25 +04:00
} else if ( dmi_check_system ( lis3lv02d_dmi_ids ) = = 0 ) {
2010-10-20 10:51:35 +04:00
pr_info ( " laptop model unknown, using default axes configuration \n " ) ;
2009-04-01 02:24:26 +04:00
lis3_dev . ac = lis3lv02d_axis_normal ;
2009-01-10 03:41:01 +03:00
}
2009-04-01 02:24:32 +04:00
/* call the core layer do its init */
ret = lis3lv02d_init_device ( & lis3_dev ) ;
2009-01-16 00:51:23 +03:00
if ( ret )
return ret ;
2009-04-01 02:24:32 +04:00
INIT_WORK ( & hpled_led . work , delayed_set_status_worker ) ;
ret = led_classdev_register ( NULL , & hpled_led . led_classdev ) ;
2009-01-16 00:51:23 +03:00
if ( ret ) {
2009-04-01 02:24:32 +04:00
lis3lv02d_joystick_disable ( ) ;
lis3lv02d_poweroff ( & lis3_dev ) ;
2009-01-16 00:51:24 +03:00
flush_work ( & hpled_led . work ) ;
2009-01-16 00:51:23 +03:00
return ret ;
}
return ret ;
2009-01-10 03:41:01 +03:00
}
static int lis3lv02d_remove ( struct acpi_device * device , int type )
{
if ( ! device )
return - EINVAL ;
lis3lv02d_joystick_disable ( ) ;
2009-04-01 02:24:32 +04:00
lis3lv02d_poweroff ( & lis3_dev ) ;
2009-01-10 03:41:01 +03:00
2009-01-16 00:51:24 +03:00
led_classdev_unregister ( & hpled_led . led_classdev ) ;
2010-05-12 01:07:03 +04:00
flush_work ( & hpled_led . work ) ;
2009-01-16 00:51:23 +03:00
2009-06-17 02:34:14 +04:00
return lis3lv02d_remove_fs ( & lis3_dev ) ;
2009-01-10 03:41:01 +03:00
}
# ifdef CONFIG_PM
static int lis3lv02d_suspend ( struct acpi_device * device , pm_message_t state )
{
/* make sure the device is off when we suspend */
2009-04-01 02:24:32 +04:00
lis3lv02d_poweroff ( & lis3_dev ) ;
2009-01-10 03:41:01 +03:00
return 0 ;
}
static int lis3lv02d_resume ( struct acpi_device * device )
{
2009-06-17 02:34:14 +04:00
lis3lv02d_poweron ( & lis3_dev ) ;
2009-01-10 03:41:01 +03:00
return 0 ;
}
# else
# define lis3lv02d_suspend NULL
# define lis3lv02d_resume NULL
# endif
/* For the HP MDPS aka 3D Driveguard */
static struct acpi_driver lis3lv02d_driver = {
. name = DRIVER_NAME ,
. class = ACPI_MDPS_CLASS ,
. ids = lis3lv02d_device_ids ,
. ops = {
. add = lis3lv02d_add ,
. remove = lis3lv02d_remove ,
. suspend = lis3lv02d_suspend ,
. resume = lis3lv02d_resume ,
}
} ;
static int __init lis3lv02d_init_module ( void )
{
int ret ;
if ( acpi_disabled )
return - ENODEV ;
ret = acpi_bus_register_driver ( & lis3lv02d_driver ) ;
if ( ret < 0 )
return ret ;
2010-10-20 10:51:35 +04:00
pr_info ( " driver loaded \n " ) ;
2009-01-10 03:41:01 +03:00
return 0 ;
}
static void __exit lis3lv02d_exit_module ( void )
{
acpi_bus_unregister_driver ( & lis3lv02d_driver ) ;
}
2009-01-16 00:51:23 +03:00
MODULE_DESCRIPTION ( " Glue between LIS3LV02Dx and HP ACPI BIOS and support for disk protection LED. " ) ;
2009-01-10 03:41:01 +03:00
MODULE_AUTHOR ( " Yan Burman, Eric Piel, Pavel Machek " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_init ( lis3lv02d_init_module ) ;
module_exit ( lis3lv02d_exit_module ) ;