2007-01-26 16:04:30 +03:00
/*
* asus - laptop . c - Asus Laptop Support
*
*
* Copyright ( C ) 2002 - 2005 Julien Lerouge , 2003 - 2006 Karol Kozimor
2007-03-11 12:28:03 +03:00
* Copyright ( C ) 2006 - 2007 Corentin Chary
2007-01-26 16:04:30 +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
*
*
* The development page for this driver is located at
* http : //sourceforge.net/projects/acpi4asus/
*
* Credits :
* Pontus Fuchs - Helper functions , cleanup
* Johann Wiesner - Small compile fixes
* John Belmonte - ACPI code for Toshiba laptop was a good starting point .
* Eric Burghard - LED display support for W1N
* Josh Green - Light Sens support
* Thomas Tuttle - His first patch for led support was very helpfull
*
*/
# include <linux/autoconf.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/types.h>
# include <linux/err.h>
# include <linux/proc_fs.h>
2007-01-26 16:04:45 +03:00
# include <linux/backlight.h>
# include <linux/fb.h>
2007-01-26 16:04:35 +03:00
# include <linux/leds.h>
2007-01-26 16:04:30 +03:00
# include <linux/platform_device.h>
# include <acpi/acpi_drivers.h>
# include <acpi/acpi_bus.h>
# include <asm/uaccess.h>
2007-03-11 12:28:03 +03:00
# define ASUS_LAPTOP_VERSION "0.41"
2007-01-26 16:04:30 +03:00
# define ASUS_HOTK_NAME "Asus Laptop Support"
# define ASUS_HOTK_CLASS "hotkey"
# define ASUS_HOTK_DEVICE_NAME "Hotkey"
# define ASUS_HOTK_HID "ATK0100"
# define ASUS_HOTK_FILE "asus-laptop"
# define ASUS_HOTK_PREFIX "\\_SB.ATKD."
2007-01-26 16:04:45 +03:00
/*
* Some events we use , same for all Asus
*/
# define ATKD_BR_UP 0x10
# define ATKD_BR_DOWN 0x20
# define ATKD_LCD_ON 0x33
# define ATKD_LCD_OFF 0x34
2007-01-26 16:04:40 +03:00
/*
* Known bits returned by \ _SB . ATKD . HWRS
*/
# define WL_HWRS 0x80
# define BT_HWRS 0x100
2007-01-26 16:04:35 +03:00
/*
* Flags for hotk status
2007-01-26 16:04:40 +03:00
* WL_ON and BT_ON are also used for wireless_status ( )
2007-01-26 16:04:35 +03:00
*/
2007-01-26 16:04:40 +03:00
# define WL_ON 0x01 //internal Wifi
# define BT_ON 0x02 //internal Bluetooth
2007-01-26 16:04:35 +03:00
# define MLED_ON 0x04 //mail LED
# define TLED_ON 0x08 //touchpad LED
2007-01-30 09:46:43 +03:00
# define RLED_ON 0x10 //Record LED
# define PLED_ON 0x20 //Phone LED
2007-03-11 12:26:48 +03:00
# define GLED_ON 0x40 //Gaming LED
# define LCD_ON 0x80 //LCD backlight
2007-01-26 16:04:35 +03:00
2007-01-26 16:04:30 +03:00
# define ASUS_LOG ASUS_HOTK_FILE ": "
# define ASUS_ERR KERN_ERR ASUS_LOG
# define ASUS_WARNING KERN_WARNING ASUS_LOG
# define ASUS_NOTICE KERN_NOTICE ASUS_LOG
# define ASUS_INFO KERN_INFO ASUS_LOG
# define ASUS_DEBUG KERN_DEBUG ASUS_LOG
MODULE_AUTHOR ( " Julien Lerouge, Karol Kozimor, Corentin Chary " ) ;
MODULE_DESCRIPTION ( ASUS_HOTK_NAME ) ;
MODULE_LICENSE ( " GPL " ) ;
2007-03-11 12:27:33 +03:00
/* WAPF defines the behavior of the Fn+Fx wlan key
* The significance of values is yet to be found , but
* most of the time :
* 0x0 will do nothing
* 0x1 will allow to control the device with Fn + Fx key .
* 0x4 will send an ACPI event ( 0x88 ) while pressing the Fn + Fx key
* 0x5 like 0x1 or 0x4
* So , if something doesn ' t work as you want , just try other values = )
*/
static uint wapf = 1 ;
module_param ( wapf , uint , 0644 ) ;
MODULE_PARM_DESC ( wapf , " WAPF value " ) ;
2007-01-26 16:04:30 +03:00
# define ASUS_HANDLE(object, paths...) \
static acpi_handle object # # _handle = NULL ; \
static char * object # # _paths [ ] = { paths }
2007-01-26 16:04:35 +03:00
/* LED */
ASUS_HANDLE ( mled_set , ASUS_HOTK_PREFIX " MLED " ) ;
ASUS_HANDLE ( tled_set , ASUS_HOTK_PREFIX " TLED " ) ;
2007-01-30 09:46:43 +03:00
ASUS_HANDLE ( rled_set , ASUS_HOTK_PREFIX " RLED " ) ; /* W1JC */
ASUS_HANDLE ( pled_set , ASUS_HOTK_PREFIX " PLED " ) ; /* A7J */
2007-03-11 12:26:48 +03:00
ASUS_HANDLE ( gled_set , ASUS_HOTK_PREFIX " GLED " ) ; /* G1, G2 (probably) */
2007-01-26 16:04:35 +03:00
2007-01-26 16:04:55 +03:00
/* LEDD */
ASUS_HANDLE ( ledd_set , ASUS_HOTK_PREFIX " SLCM " ) ;
2007-01-26 16:04:40 +03:00
/* Bluetooth and WLAN
* WLED and BLED are not handled like other XLED , because in some dsdt
* they also control the WLAN / Bluetooth device .
*/
ASUS_HANDLE ( wl_switch , ASUS_HOTK_PREFIX " WLED " ) ;
ASUS_HANDLE ( bt_switch , ASUS_HOTK_PREFIX " BLED " ) ;
2007-01-30 09:46:43 +03:00
ASUS_HANDLE ( wireless_status , ASUS_HOTK_PREFIX " RSTS " ) ; /* All new models */
2007-01-26 16:04:40 +03:00
2007-01-26 16:04:45 +03:00
/* Brightness */
ASUS_HANDLE ( brightness_set , ASUS_HOTK_PREFIX " SPLV " ) ;
ASUS_HANDLE ( brightness_get , ASUS_HOTK_PREFIX " GPLV " ) ;
/* Backlight */
2007-01-30 09:46:43 +03:00
ASUS_HANDLE ( lcd_switch , " \\ _SB.PCI0.SBRG.EC0._Q10 " , /* All new models */
" \\ _SB.PCI0.ISA.EC0._Q10 " , /* A1x */
" \\ _SB.PCI0.PX40.ECD0._Q10 " , /* L3C */
" \\ _SB.PCI0.PX40.EC0.Q10 " , /* M1A */
" \\ _SB.PCI0.LPCB.EC0._Q10 " , /* P30 */
" \\ _SB.PCI0.PX40.Q10 " , /* S1x */
" \\ Q10 " ) ; /* A2x, L2D, L3D, M2E */
2007-01-26 16:04:45 +03:00
2007-01-26 16:04:49 +03:00
/* Display */
ASUS_HANDLE ( display_set , ASUS_HOTK_PREFIX " SDSP " ) ;
2007-01-30 09:46:43 +03:00
ASUS_HANDLE ( display_get , " \\ _SB.PCI0.P0P1.VGA.GETD " , /* A6B, A6K A6R A7D F3JM L4R M6R A3G
M6A M6V VX - 1 V6J V6V W3Z */
" \\ _SB.PCI0.P0P2.VGA.GETD " , /* A3E A4K, A4D A4L A6J A7J A8J Z71V M9V
2007-01-26 16:04:49 +03:00
S5A M5A z33A W1Jc W2V */
2007-01-30 09:46:43 +03:00
" \\ _SB.PCI0.P0P3.VGA.GETD " , /* A6V A6Q */
" \\ _SB.PCI0.P0PA.VGA.GETD " , /* A6T, A6M */
" \\ _SB.PCI0.PCI1.VGAC.NMAP " , /* L3C */
" \\ _SB.PCI0.VGA.GETD " , /* Z96F */
" \\ ACTD " , /* A2D */
" \\ ADVG " , /* A4G Z71A W1N W5A W5F M2N M3N M5N M6N S1N S5N */
" \\ DNXT " , /* P30 */
" \\ INFB " , /* A2H D1 L2D L3D L3H L2E L5D L5C M1A M2E L4L W3V */
" \\ SSTE " ) ; /* A3F A6F A3N A3L M6N W3N W6A */
ASUS_HANDLE ( ls_switch , ASUS_HOTK_PREFIX " ALSC " ) ; /* Z71A Z71V */
ASUS_HANDLE ( ls_level , ASUS_HOTK_PREFIX " ALSL " ) ; /* Z71A Z71V */
2007-01-26 16:04:58 +03:00
2007-01-26 16:04:30 +03:00
/*
* This is the main structure , we can use it to store anything interesting
* about the hotk device
*/
struct asus_hotk {
2007-01-30 09:46:43 +03:00
char * name ; //laptop name
2007-01-26 16:04:30 +03:00
struct acpi_device * device ; //the device we are in
acpi_handle handle ; //the handle of the hotk device
char status ; //status of the hotk, for LEDs, ...
2007-01-26 16:04:55 +03:00
u32 ledd_status ; //status of the LED display
2007-01-30 09:46:43 +03:00
u8 light_level ; //light sensor level
u8 light_switch ; //light sensor switch value
2007-01-26 16:04:30 +03:00
u16 event_count [ 128 ] ; //count for each event TODO make this better
} ;
/*
* This header is made available to allow proper configuration given model ,
* revision number , . . . this info cannot go in struct asus_hotk because it is
* available before the hotk
*/
static struct acpi_table_header * asus_info ;
/* The actual device the driver binds to */
static struct asus_hotk * hotk ;
/*
* The hotkey driver declaration
*/
static int asus_hotk_add ( struct acpi_device * device ) ;
static int asus_hotk_remove ( struct acpi_device * device , int type ) ;
static struct acpi_driver asus_hotk_driver = {
. name = ASUS_HOTK_NAME ,
. class = ASUS_HOTK_CLASS ,
. ids = ASUS_HOTK_HID ,
. ops = {
. add = asus_hotk_add ,
. remove = asus_hotk_remove ,
} ,
} ;
2007-01-26 16:04:45 +03:00
/* The backlight device /sys/class/backlight */
static struct backlight_device * asus_backlight_device ;
/*
* The backlight class declaration
*/
static int read_brightness ( struct backlight_device * bd ) ;
static int update_bl_status ( struct backlight_device * bd ) ;
2007-02-11 02:07:48 +03:00
static struct backlight_ops asusbl_ops = {
2007-01-30 09:46:43 +03:00
. get_brightness = read_brightness ,
. update_status = update_bl_status ,
2007-01-26 16:04:45 +03:00
} ;
2007-01-26 16:04:35 +03:00
/* These functions actually update the LED's, and are called from a
* workqueue . By doing this as separate work rather than when the LED
* subsystem asks , we avoid messing with the Asus ACPI stuff during a
* potentially bad time , such as a timer interrupt . */
static struct workqueue_struct * led_workqueue ;
# define ASUS_LED(object, ledname) \
static void object # # _led_set ( struct led_classdev * led_cdev , \
enum led_brightness value ) ; \
static void object # # _led_update ( struct work_struct * ignored ) ; \
static int object # # _led_wk ; \
2007-02-20 03:07:25 +03:00
static DECLARE_WORK ( object # # _led_work , object # # _led_update ) ; \
2007-01-26 16:04:35 +03:00
static struct led_classdev object # # _led = { \
. name = " asus: " ledname , \
. brightness_set = object # # _led_set , \
}
ASUS_LED ( mled , " mail " ) ;
ASUS_LED ( tled , " touchpad " ) ;
ASUS_LED ( rled , " record " ) ;
ASUS_LED ( pled , " phone " ) ;
2007-03-11 12:26:48 +03:00
ASUS_LED ( gled , " gaming " ) ;
2007-01-26 16:04:35 +03:00
2007-01-26 16:04:30 +03:00
/*
* This function evaluates an ACPI method , given an int as parameter , the
* method is searched within the scope of the handle , can be NULL . The output
* of the method is written is output , which can also be NULL
*
* returns 1 if write is successful , 0 else .
*/
static int write_acpi_int ( acpi_handle handle , const char * method , int val ,
struct acpi_buffer * output )
{
struct acpi_object_list params ; //list of input parameters (an int here)
union acpi_object in_obj ; //the only param we use
acpi_status status ;
params . count = 1 ;
params . pointer = & in_obj ;
in_obj . type = ACPI_TYPE_INTEGER ;
in_obj . integer . value = val ;
status = acpi_evaluate_object ( handle , ( char * ) method , & params , output ) ;
return ( status = = AE_OK ) ;
}
2007-01-30 09:46:43 +03:00
static int read_wireless_status ( int mask )
{
2007-03-11 12:25:38 +03:00
ulong status ;
acpi_status rv = AE_OK ;
2007-01-26 16:04:40 +03:00
if ( ! wireless_status_handle )
return ( hotk - > status & mask ) ? 1 : 0 ;
2007-03-11 12:25:38 +03:00
rv = acpi_evaluate_integer ( wireless_status_handle , NULL , NULL , & status ) ;
if ( ACPI_FAILURE ( rv ) )
2007-01-26 16:04:40 +03:00
printk ( ASUS_WARNING " Error reading Wireless status \n " ) ;
2007-03-11 12:25:38 +03:00
else
return ( status & mask ) ? 1 : 0 ;
2007-01-26 16:04:40 +03:00
return ( hotk - > status & mask ) ? 1 : 0 ;
}
2007-01-26 16:04:35 +03:00
/* Generic LED functions */
static int read_status ( int mask )
{
2007-01-26 16:04:40 +03:00
/* There is a special method for both wireless devices */
if ( mask = = BT_ON | | mask = = WL_ON )
return read_wireless_status ( mask ) ;
2007-01-26 16:04:35 +03:00
return ( hotk - > status & mask ) ? 1 : 0 ;
}
2007-03-11 12:26:12 +03:00
static void write_status ( acpi_handle handle , int out , int mask )
2007-01-26 16:04:35 +03:00
{
hotk - > status = ( out ) ? ( hotk - > status | mask ) : ( hotk - > status & ~ mask ) ;
2007-03-11 12:26:12 +03:00
switch ( mask ) {
case MLED_ON :
2007-01-26 16:04:35 +03:00
out = ! out & 0x1 ;
2007-03-11 12:26:12 +03:00
break ;
2007-03-11 12:26:48 +03:00
case GLED_ON :
out = ( out & 0x1 ) + 1 ;
break ;
2007-03-11 12:26:12 +03:00
default :
out & = 0x1 ;
break ;
}
2007-01-26 16:04:35 +03:00
if ( handle & & ! write_acpi_int ( handle , NULL , out , NULL ) )
2007-03-11 12:26:12 +03:00
printk ( ASUS_WARNING " write failed %x \n " , mask ) ;
2007-01-26 16:04:35 +03:00
}
/* /sys/class/led handlers */
2007-03-11 12:26:12 +03:00
# define ASUS_LED_HANDLER(object, mask) \
2007-01-26 16:04:35 +03:00
static void object # # _led_set ( struct led_classdev * led_cdev , \
enum led_brightness value ) \
{ \
object # # _led_wk = value ; \
queue_work ( led_workqueue , & object # # _led_work ) ; \
} \
static void object # # _led_update ( struct work_struct * ignored ) \
{ \
int value = object # # _led_wk ; \
2007-03-11 12:26:12 +03:00
write_status ( object # # _set_handle , value , ( mask ) ) ; \
2007-01-26 16:04:35 +03:00
}
2007-03-11 12:26:12 +03:00
ASUS_LED_HANDLER ( mled , MLED_ON ) ;
ASUS_LED_HANDLER ( pled , PLED_ON ) ;
ASUS_LED_HANDLER ( rled , RLED_ON ) ;
ASUS_LED_HANDLER ( tled , TLED_ON ) ;
2007-03-11 12:26:48 +03:00
ASUS_LED_HANDLER ( gled , GLED_ON ) ;
2007-01-26 16:04:35 +03:00
2007-01-26 16:04:45 +03:00
static int get_lcd_state ( void )
{
return read_status ( LCD_ON ) ;
}
static int set_lcd_state ( int value )
{
int lcd = 0 ;
acpi_status status = 0 ;
lcd = value ? 1 : 0 ;
if ( lcd = = get_lcd_state ( ) )
return 0 ;
2007-01-30 09:46:43 +03:00
if ( lcd_switch_handle ) {
2007-01-26 16:04:45 +03:00
status = acpi_evaluate_object ( lcd_switch_handle ,
NULL , NULL , NULL ) ;
if ( ACPI_FAILURE ( status ) )
printk ( ASUS_WARNING " Error switching LCD \n " ) ;
}
2007-03-11 12:26:12 +03:00
write_status ( NULL , lcd , LCD_ON ) ;
2007-01-26 16:04:45 +03:00
return 0 ;
}
static void lcd_blank ( int blank )
{
struct backlight_device * bd = asus_backlight_device ;
2007-01-30 09:46:43 +03:00
if ( bd ) {
2007-02-11 02:07:48 +03:00
bd - > props . power = blank ;
2007-02-09 01:25:09 +03:00
backlight_update_status ( bd ) ;
2007-01-26 16:04:45 +03:00
}
}
static int read_brightness ( struct backlight_device * bd )
{
2007-03-11 12:25:38 +03:00
ulong value ;
acpi_status rv = AE_OK ;
2007-01-26 16:04:45 +03:00
2007-03-11 12:25:38 +03:00
rv = acpi_evaluate_integer ( brightness_get_handle , NULL , NULL , & value ) ;
if ( ACPI_FAILURE ( rv ) )
2007-01-26 16:04:45 +03:00
printk ( ASUS_WARNING " Error reading brightness \n " ) ;
return value ;
}
static int set_brightness ( struct backlight_device * bd , int value )
{
int ret = 0 ;
value = ( 0 < value ) ? ( ( 15 < value ) ? 15 : value ) : 0 ;
/* 0 <= value <= 15 */
if ( ! write_acpi_int ( brightness_set_handle , NULL , value , NULL ) ) {
printk ( ASUS_WARNING " Error changing brightness \n " ) ;
ret = - EIO ;
}
return ret ;
}
static int update_bl_status ( struct backlight_device * bd )
{
int rv ;
2007-02-11 02:07:48 +03:00
int value = bd - > props . brightness ;
2007-01-26 16:04:45 +03:00
rv = set_brightness ( bd , value ) ;
2007-01-30 09:46:43 +03:00
if ( rv )
2007-01-26 16:04:45 +03:00
return rv ;
2007-02-11 02:07:48 +03:00
value = ( bd - > props . power = = FB_BLANK_UNBLANK ) ? 1 : 0 ;
2007-01-26 16:04:45 +03:00
return set_lcd_state ( value ) ;
}
2007-01-26 16:04:30 +03:00
/*
* Platform device handlers
*/
/*
* We write our info in page , we begin at offset off and cannot write more
* than count bytes . We set eof to 1 if we handle those 2 values . We return the
* number of bytes written in page
*/
static ssize_t show_infos ( struct device * dev ,
2007-01-30 09:46:43 +03:00
struct device_attribute * attr , char * page )
2007-01-26 16:04:30 +03:00
{
int len = 0 ;
2007-03-11 12:25:38 +03:00
ulong temp ;
2007-01-26 16:04:30 +03:00
char buf [ 16 ] ; //enough for all info
2007-03-11 12:25:38 +03:00
acpi_status rv = AE_OK ;
2007-01-26 16:04:30 +03:00
/*
* We use the easy way , we don ' t care of off and count , so we don ' t set eof
* to 1
*/
len + = sprintf ( page , ASUS_HOTK_NAME " " ASUS_LAPTOP_VERSION " \n " ) ;
len + = sprintf ( page + len , " Model reference : %s \n " , hotk - > name ) ;
/*
* The SFUN method probably allows the original driver to get the list
* of features supported by a given model . For now , 0x0100 or 0x0800
* bit signifies that the laptop is equipped with a Wi - Fi MiniPCI card .
* The significance of others is yet to be found .
*/
2007-03-11 12:25:38 +03:00
rv = acpi_evaluate_integer ( hotk - > handle , " SFUN " , NULL , & temp ) ;
if ( ! ACPI_FAILURE ( rv ) )
len + = sprintf ( page + len , " SFUN value : 0x%04x \n " ,
( uint ) temp ) ;
2007-01-26 16:04:30 +03:00
/*
* Another value for userspace : the ASYM method returns 0x02 for
* battery low and 0x04 for battery critical , its readings tend to be
* more accurate than those provided by _BST .
* Note : since not all the laptops provide this method , errors are
* silently ignored .
*/
2007-03-11 12:25:38 +03:00
rv = acpi_evaluate_integer ( hotk - > handle , " ASYM " , NULL , & temp ) ;
if ( ! ACPI_FAILURE ( rv ) )
len + = sprintf ( page + len , " ASYM value : 0x%04x \n " ,
( uint ) temp ) ;
2007-01-26 16:04:30 +03:00
if ( asus_info ) {
snprintf ( buf , 16 , " %d " , asus_info - > length ) ;
len + = sprintf ( page + len , " DSDT length : %s \n " , buf ) ;
snprintf ( buf , 16 , " %d " , asus_info - > checksum ) ;
len + = sprintf ( page + len , " DSDT checksum : %s \n " , buf ) ;
snprintf ( buf , 16 , " %d " , asus_info - > revision ) ;
len + = sprintf ( page + len , " DSDT revision : %s \n " , buf ) ;
snprintf ( buf , 7 , " %s " , asus_info - > oem_id ) ;
len + = sprintf ( page + len , " OEM id : %s \n " , buf ) ;
snprintf ( buf , 9 , " %s " , asus_info - > oem_table_id ) ;
len + = sprintf ( page + len , " OEM table id : %s \n " , buf ) ;
snprintf ( buf , 16 , " %x " , asus_info - > oem_revision ) ;
len + = sprintf ( page + len , " OEM revision : 0x%s \n " , buf ) ;
snprintf ( buf , 5 , " %s " , asus_info - > asl_compiler_id ) ;
len + = sprintf ( page + len , " ASL comp vendor id : %s \n " , buf ) ;
snprintf ( buf , 16 , " %x " , asus_info - > asl_compiler_revision ) ;
len + = sprintf ( page + len , " ASL comp revision : 0x%s \n " , buf ) ;
}
return len ;
}
static int parse_arg ( const char * buf , unsigned long count , int * val )
{
if ( ! count )
return 0 ;
if ( count > 31 )
return - EINVAL ;
if ( sscanf ( buf , " %i " , val ) ! = 1 )
return - EINVAL ;
return count ;
}
2007-01-26 16:04:40 +03:00
static ssize_t store_status ( const char * buf , size_t count ,
2007-03-11 12:26:12 +03:00
acpi_handle handle , int mask )
2007-01-26 16:04:40 +03:00
{
int rv , value ;
int out = 0 ;
rv = parse_arg ( buf , count , & value ) ;
if ( rv > 0 )
out = value ? 1 : 0 ;
2007-03-11 12:26:12 +03:00
write_status ( handle , out , mask ) ;
2007-01-26 16:04:40 +03:00
return rv ;
}
2007-01-26 16:04:55 +03:00
/*
* LEDD display
*/
static ssize_t show_ledd ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
return sprintf ( buf , " 0x%08x \n " , hotk - > ledd_status ) ;
}
static ssize_t store_ledd ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
int rv , value ;
rv = parse_arg ( buf , count , & value ) ;
if ( rv > 0 ) {
if ( ! write_acpi_int ( ledd_set_handle , NULL , value , NULL ) )
printk ( ASUS_WARNING " LED display write failed \n " ) ;
else
hotk - > ledd_status = ( u32 ) value ;
}
return rv ;
}
2007-01-26 16:04:40 +03:00
/*
* WLAN
*/
static ssize_t show_wlan ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
return sprintf ( buf , " %d \n " , read_status ( WL_ON ) ) ;
}
static ssize_t store_wlan ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
2007-03-11 12:26:12 +03:00
return store_status ( buf , count , wl_switch_handle , WL_ON ) ;
2007-01-26 16:04:40 +03:00
}
/*
* Bluetooth
*/
static ssize_t show_bluetooth ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
return sprintf ( buf , " %d \n " , read_status ( BT_ON ) ) ;
}
2007-01-30 09:46:43 +03:00
static ssize_t store_bluetooth ( struct device * dev ,
struct device_attribute * attr , const char * buf ,
size_t count )
2007-01-26 16:04:40 +03:00
{
2007-03-11 12:26:12 +03:00
return store_status ( buf , count , bt_switch_handle , BT_ON ) ;
2007-01-26 16:04:40 +03:00
}
2007-01-26 16:04:49 +03:00
/*
* Display
*/
static void set_display ( int value )
{
/* no sanity check needed for now */
if ( ! write_acpi_int ( display_set_handle , NULL , value , NULL ) )
printk ( ASUS_WARNING " Error setting display \n " ) ;
return ;
}
static int read_display ( void )
{
2007-03-11 12:25:38 +03:00
ulong value = 0 ;
acpi_status rv = AE_OK ;
2007-01-26 16:04:49 +03:00
/* In most of the case, we know how to set the display, but sometime
we can ' t read it */
2007-01-30 09:46:43 +03:00
if ( display_get_handle ) {
2007-03-11 12:25:38 +03:00
rv = acpi_evaluate_integer ( display_get_handle , NULL ,
NULL , & value ) ;
if ( ACPI_FAILURE ( rv ) )
2007-01-26 16:04:49 +03:00
printk ( ASUS_WARNING " Error reading display status \n " ) ;
}
value & = 0x0F ; /* needed for some models, shouldn't hurt others */
return value ;
}
2007-01-30 09:46:43 +03:00
2007-01-26 16:04:49 +03:00
/*
* Now , * this * one could be more user - friendly , but so far , no - one has
* complained . The significance of bits is the same as in store_disp ( )
*/
static ssize_t show_disp ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
return sprintf ( buf , " %d \n " , read_display ( ) ) ;
}
/*
* Experimental support for display switching . As of now : 1 should activate
* the LCD output , 2 should do for CRT , 4 for TV - Out and 8 for DVI .
* Any combination ( bitwise ) of these will suffice . I never actually tested 4
* displays hooked up simultaneously , so be warned . See the acpi4asus README
* for more info .
*/
static ssize_t store_disp ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
int rv , value ;
rv = parse_arg ( buf , count , & value ) ;
if ( rv > 0 )
set_display ( value ) ;
return rv ;
}
2007-01-26 16:04:58 +03:00
/*
* Light Sens
*/
static void set_light_sens_switch ( int value )
{
if ( ! write_acpi_int ( ls_switch_handle , NULL , value , NULL ) )
printk ( ASUS_WARNING " Error setting light sensor switch \n " ) ;
hotk - > light_switch = value ;
}
static ssize_t show_lssw ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
return sprintf ( buf , " %d \n " , hotk - > light_switch ) ;
}
static ssize_t store_lssw ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
int rv , value ;
rv = parse_arg ( buf , count , & value ) ;
if ( rv > 0 )
set_light_sens_switch ( value ? 1 : 0 ) ;
return rv ;
}
static void set_light_sens_level ( int value )
{
if ( ! write_acpi_int ( ls_level_handle , NULL , value , NULL ) )
printk ( ASUS_WARNING " Error setting light sensor level \n " ) ;
hotk - > light_level = value ;
}
static ssize_t show_lslvl ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
return sprintf ( buf , " %d \n " , hotk - > light_level ) ;
}
static ssize_t store_lslvl ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
int rv , value ;
rv = parse_arg ( buf , count , & value ) ;
if ( rv > 0 ) {
value = ( 0 < value ) ? ( ( 15 < value ) ? 15 : value ) : 0 ;
/* 0 <= value <= 15 */
set_light_sens_level ( value ) ;
}
return rv ;
}
2007-01-26 16:04:30 +03:00
static void asus_hotk_notify ( acpi_handle handle , u32 event , void * data )
{
/* TODO Find a better way to handle events count. */
if ( ! hotk )
return ;
2007-01-26 16:04:45 +03:00
/*
* We need to tell the backlight device when the backlight power is
* switched
*/
if ( event = = ATKD_LCD_ON ) {
2007-03-11 12:26:12 +03:00
write_status ( NULL , 1 , LCD_ON ) ;
2007-01-26 16:04:45 +03:00
lcd_blank ( FB_BLANK_UNBLANK ) ;
2007-01-30 09:46:43 +03:00
} else if ( event = = ATKD_LCD_OFF ) {
2007-03-11 12:26:12 +03:00
write_status ( NULL , 0 , LCD_ON ) ;
2007-01-26 16:04:45 +03:00
lcd_blank ( FB_BLANK_POWERDOWN ) ;
}
2007-01-26 16:04:30 +03:00
acpi_bus_generate_event ( hotk - > device , event ,
hotk - > event_count [ event % 128 ] + + ) ;
return ;
}
# define ASUS_CREATE_DEVICE_ATTR(_name) \
struct device_attribute dev_attr_ # # _name = { \
. attr = { \
. name = __stringify ( _name ) , \
. mode = 0 , \
. owner = THIS_MODULE } , \
. show = NULL , \
. store = NULL , \
}
# define ASUS_SET_DEVICE_ATTR(_name, _mode, _show, _store) \
do { \
dev_attr_ # # _name . attr . mode = _mode ; \
dev_attr_ # # _name . show = _show ; \
dev_attr_ # # _name . store = _store ; \
} while ( 0 )
static ASUS_CREATE_DEVICE_ATTR ( infos ) ;
2007-01-26 16:04:40 +03:00
static ASUS_CREATE_DEVICE_ATTR ( wlan ) ;
static ASUS_CREATE_DEVICE_ATTR ( bluetooth ) ;
2007-01-26 16:04:49 +03:00
static ASUS_CREATE_DEVICE_ATTR ( display ) ;
2007-01-26 16:04:55 +03:00
static ASUS_CREATE_DEVICE_ATTR ( ledd ) ;
2007-01-26 16:04:58 +03:00
static ASUS_CREATE_DEVICE_ATTR ( ls_switch ) ;
static ASUS_CREATE_DEVICE_ATTR ( ls_level ) ;
2007-01-26 16:04:30 +03:00
static struct attribute * asuspf_attributes [ ] = {
2007-01-30 09:46:43 +03:00
& dev_attr_infos . attr ,
& dev_attr_wlan . attr ,
& dev_attr_bluetooth . attr ,
& dev_attr_display . attr ,
& dev_attr_ledd . attr ,
& dev_attr_ls_switch . attr ,
& dev_attr_ls_level . attr ,
NULL
2007-01-26 16:04:30 +03:00
} ;
static struct attribute_group asuspf_attribute_group = {
2007-01-30 09:46:43 +03:00
. attrs = asuspf_attributes
2007-01-26 16:04:30 +03:00
} ;
static struct platform_driver asuspf_driver = {
2007-01-30 09:46:43 +03:00
. driver = {
. name = ASUS_HOTK_FILE ,
. owner = THIS_MODULE ,
}
2007-01-26 16:04:30 +03:00
} ;
static struct platform_device * asuspf_device ;
static void asus_hotk_add_fs ( void )
{
ASUS_SET_DEVICE_ATTR ( infos , 0444 , show_infos , NULL ) ;
2007-01-26 16:04:40 +03:00
if ( wl_switch_handle )
ASUS_SET_DEVICE_ATTR ( wlan , 0644 , show_wlan , store_wlan ) ;
if ( bt_switch_handle )
ASUS_SET_DEVICE_ATTR ( bluetooth , 0644 ,
show_bluetooth , store_bluetooth ) ;
2007-01-26 16:04:49 +03:00
if ( display_set_handle & & display_get_handle )
ASUS_SET_DEVICE_ATTR ( display , 0644 , show_disp , store_disp ) ;
2007-01-30 09:46:43 +03:00
else if ( display_set_handle )
2007-01-26 16:04:49 +03:00
ASUS_SET_DEVICE_ATTR ( display , 0200 , NULL , store_disp ) ;
2007-01-26 16:04:55 +03:00
if ( ledd_set_handle )
ASUS_SET_DEVICE_ATTR ( ledd , 0644 , show_ledd , store_ledd ) ;
2007-01-26 16:04:58 +03:00
if ( ls_switch_handle & & ls_level_handle ) {
ASUS_SET_DEVICE_ATTR ( ls_level , 0644 , show_lslvl , store_lslvl ) ;
ASUS_SET_DEVICE_ATTR ( ls_switch , 0644 , show_lssw , store_lssw ) ;
}
2007-01-26 16:04:30 +03:00
}
2007-01-30 09:46:43 +03:00
static int asus_handle_init ( char * name , acpi_handle * handle ,
2007-01-26 16:04:30 +03:00
char * * paths , int num_paths )
{
int i ;
acpi_status status ;
for ( i = 0 ; i < num_paths ; i + + ) {
status = acpi_get_handle ( NULL , paths [ i ] , handle ) ;
if ( ACPI_SUCCESS ( status ) )
return 0 ;
}
* handle = NULL ;
return - ENODEV ;
}
# define ASUS_HANDLE_INIT(object) \
asus_handle_init ( # object , & object # # _handle , object # # _paths , \
ARRAY_SIZE ( object # # _paths ) )
/*
* This function is used to initialize the hotk with right values . In this
* method , we can make all the detection we want , and modify the hotk struct
*/
static int asus_hotk_get_info ( void )
{
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER , NULL } ;
union acpi_object * model = NULL ;
2007-03-11 12:25:38 +03:00
ulong bsts_result , hwrs_result ;
2007-01-26 16:04:30 +03:00
char * string = NULL ;
acpi_status status ;
/*
* Get DSDT headers early enough to allow for differentiating between
* models , but late enough to allow acpi_bus_register_driver ( ) to fail
* before doing anything ACPI - specific . Should we encounter a machine ,
* which needs special handling ( i . e . its hotkey device has a different
* HID ) , this bit will be moved . A global variable asus_info contains
* the DSDT header .
*/
2007-02-03 10:13:53 +03:00
status = acpi_get_table ( ACPI_SIG_DSDT , 1 , & asus_info ) ;
2007-01-26 16:04:30 +03:00
if ( ACPI_FAILURE ( status ) )
printk ( ASUS_WARNING " Couldn't get the DSDT table header \n " ) ;
/* We have to write 0 on init this far for all ASUS models */
if ( ! write_acpi_int ( hotk - > handle , " INIT " , 0 , & buffer ) ) {
printk ( ASUS_ERR " Hotkey initialization failed \n " ) ;
return - ENODEV ;
}
/* This needs to be called for some laptops to init properly */
2007-03-11 12:25:38 +03:00
status =
acpi_evaluate_integer ( hotk - > handle , " BSTS " , NULL , & bsts_result ) ;
if ( ACPI_FAILURE ( status ) )
2007-01-26 16:04:30 +03:00
printk ( ASUS_WARNING " Error calling BSTS \n " ) ;
else if ( bsts_result )
printk ( ASUS_NOTICE " BSTS called, 0x%02x returned \n " ,
2007-03-11 12:25:38 +03:00
( uint ) bsts_result ) ;
2007-01-26 16:04:30 +03:00
2007-03-11 12:27:33 +03:00
/* This too ... */
write_acpi_int ( hotk - > handle , " CWAP " , wapf , NULL ) ;
2007-01-26 16:04:30 +03:00
/*
* Try to match the object returned by INIT to the specific model .
* Handle every possible object ( or the lack of thereof ) the DSDT
* writers might throw at us . When in trouble , we pass NULL to
* asus_model_match ( ) and try something completely different .
*/
if ( buffer . pointer ) {
model = buffer . pointer ;
switch ( model - > type ) {
case ACPI_TYPE_STRING :
string = model - > string . pointer ;
break ;
case ACPI_TYPE_BUFFER :
string = model - > buffer . pointer ;
break ;
default :
string = " " ;
break ;
}
}
hotk - > name = kstrdup ( string , GFP_KERNEL ) ;
if ( ! hotk - > name )
return - ENOMEM ;
2007-01-30 09:46:43 +03:00
if ( * string )
2007-01-26 16:04:30 +03:00
printk ( ASUS_NOTICE " %s model detected \n " , string ) ;
2007-01-26 16:04:35 +03:00
ASUS_HANDLE_INIT ( mled_set ) ;
ASUS_HANDLE_INIT ( tled_set ) ;
ASUS_HANDLE_INIT ( rled_set ) ;
ASUS_HANDLE_INIT ( pled_set ) ;
2007-03-11 12:26:48 +03:00
ASUS_HANDLE_INIT ( gled_set ) ;
2007-01-26 16:04:35 +03:00
2007-01-26 16:04:55 +03:00
ASUS_HANDLE_INIT ( ledd_set ) ;
2007-01-26 16:04:40 +03:00
/*
* The HWRS method return informations about the hardware .
* 0x80 bit is for WLAN , 0x100 for Bluetooth .
* The significance of others is yet to be found .
* If we don ' t find the method , we assume the device are present .
*/
2007-03-11 12:25:38 +03:00
status =
acpi_evaluate_integer ( hotk - > handle , " HRWS " , NULL , & hwrs_result ) ;
if ( ACPI_FAILURE ( status ) )
2007-01-26 16:04:40 +03:00
hwrs_result = WL_HWRS | BT_HWRS ;
2007-01-30 09:46:43 +03:00
if ( hwrs_result & WL_HWRS )
2007-01-26 16:04:40 +03:00
ASUS_HANDLE_INIT ( wl_switch ) ;
2007-01-30 09:46:43 +03:00
if ( hwrs_result & BT_HWRS )
2007-01-26 16:04:40 +03:00
ASUS_HANDLE_INIT ( bt_switch ) ;
ASUS_HANDLE_INIT ( wireless_status ) ;
2007-01-26 16:04:45 +03:00
ASUS_HANDLE_INIT ( brightness_set ) ;
ASUS_HANDLE_INIT ( brightness_get ) ;
ASUS_HANDLE_INIT ( lcd_switch ) ;
2007-01-26 16:04:49 +03:00
ASUS_HANDLE_INIT ( display_set ) ;
ASUS_HANDLE_INIT ( display_get ) ;
2007-01-26 16:04:58 +03:00
/* There is a lot of models with "ALSL", but a few get
a real light sens , so we need to check it . */
2007-01-30 09:46:43 +03:00
if ( ASUS_HANDLE_INIT ( ls_switch ) )
2007-01-26 16:04:58 +03:00
ASUS_HANDLE_INIT ( ls_level ) ;
2007-01-26 16:04:30 +03:00
kfree ( model ) ;
return AE_OK ;
}
static int asus_hotk_check ( void )
{
int result = 0 ;
result = acpi_bus_get_status ( hotk - > device ) ;
if ( result )
return result ;
if ( hotk - > device - > status . present ) {
result = asus_hotk_get_info ( ) ;
} else {
printk ( ASUS_ERR " Hotkey device not present, aborting \n " ) ;
return - EINVAL ;
}
return result ;
}
static int asus_hotk_found ;
static int asus_hotk_add ( struct acpi_device * device )
{
acpi_status status = AE_OK ;
int result ;
if ( ! device )
return - EINVAL ;
printk ( ASUS_NOTICE " Asus Laptop Support version %s \n " ,
ASUS_LAPTOP_VERSION ) ;
hotk = kmalloc ( sizeof ( struct asus_hotk ) , GFP_KERNEL ) ;
if ( ! hotk )
return - ENOMEM ;
memset ( hotk , 0 , sizeof ( struct asus_hotk ) ) ;
hotk - > handle = device - > handle ;
strcpy ( acpi_device_name ( device ) , ASUS_HOTK_DEVICE_NAME ) ;
strcpy ( acpi_device_class ( device ) , ASUS_HOTK_CLASS ) ;
acpi_driver_data ( device ) = hotk ;
hotk - > device = device ;
result = asus_hotk_check ( ) ;
if ( result )
goto end ;
asus_hotk_add_fs ( ) ;
/*
* We install the handler , it will receive the hotk in parameter , so , we
* could add other data to the hotk struct
*/
2007-05-06 16:46:41 +04:00
status = acpi_install_notify_handler ( hotk - > handle , ACPI_ALL_NOTIFY ,
2007-01-26 16:04:30 +03:00
asus_hotk_notify , hotk ) ;
if ( ACPI_FAILURE ( status ) )
printk ( ASUS_ERR " Error installing notify handler \n " ) ;
asus_hotk_found = 1 ;
2007-01-26 16:04:45 +03:00
/* WLED and BLED are on by default */
2007-03-11 12:26:12 +03:00
write_status ( bt_switch_handle , 1 , BT_ON ) ;
write_status ( wl_switch_handle , 1 , WL_ON ) ;
/* If the h/w switch is off, we need to check the real status */
write_status ( NULL , read_status ( BT_ON ) , BT_ON ) ;
write_status ( NULL , read_status ( WL_ON ) , WL_ON ) ;
2007-01-26 16:04:40 +03:00
2007-01-26 16:04:45 +03:00
/* LCD Backlight is on by default */
2007-03-11 12:26:12 +03:00
write_status ( NULL , 1 , LCD_ON ) ;
2007-01-26 16:04:45 +03:00
2007-01-26 16:04:55 +03:00
/* LED display is off by default */
hotk - > ledd_status = 0xFFF ;
2007-01-30 09:46:43 +03:00
/* Set initial values of light sensor and level */
hotk - > light_switch = 1 ; /* Default to light sensor disabled */
hotk - > light_level = 0 ; /* level 5 for sensor sensitivity */
2007-01-26 16:04:58 +03:00
2007-01-30 09:46:43 +03:00
if ( ls_switch_handle )
set_light_sens_switch ( hotk - > light_switch ) ;
2007-01-26 16:04:58 +03:00
2007-01-30 09:46:43 +03:00
if ( ls_level_handle )
set_light_sens_level ( hotk - > light_level ) ;
2007-01-26 16:04:58 +03:00
2007-01-26 16:04:30 +03:00
end :
if ( result ) {
kfree ( hotk - > name ) ;
kfree ( hotk ) ;
}
return result ;
}
static int asus_hotk_remove ( struct acpi_device * device , int type )
{
acpi_status status = 0 ;
if ( ! device | | ! acpi_driver_data ( device ) )
return - EINVAL ;
2007-05-06 16:46:41 +04:00
status = acpi_remove_notify_handler ( hotk - > handle , ACPI_ALL_NOTIFY ,
2007-01-26 16:04:30 +03:00
asus_hotk_notify ) ;
if ( ACPI_FAILURE ( status ) )
printk ( ASUS_ERR " Error removing notify handler \n " ) ;
kfree ( hotk - > name ) ;
kfree ( hotk ) ;
return 0 ;
}
2007-01-26 16:04:45 +03:00
static void asus_backlight_exit ( void )
{
2007-01-30 09:46:43 +03:00
if ( asus_backlight_device )
2007-01-26 16:04:45 +03:00
backlight_device_unregister ( asus_backlight_device ) ;
}
2007-01-26 16:04:35 +03:00
# define ASUS_LED_UNREGISTER(object) \
if ( object # # _led . class_dev \
& & ! IS_ERR ( object # # _led . class_dev ) ) \
led_classdev_unregister ( & object # # _led )
static void asus_led_exit ( void )
{
ASUS_LED_UNREGISTER ( mled ) ;
ASUS_LED_UNREGISTER ( tled ) ;
ASUS_LED_UNREGISTER ( pled ) ;
ASUS_LED_UNREGISTER ( rled ) ;
2007-03-11 12:26:48 +03:00
ASUS_LED_UNREGISTER ( gled ) ;
2007-01-26 16:04:35 +03:00
destroy_workqueue ( led_workqueue ) ;
}
2007-01-26 16:04:30 +03:00
static void __exit asus_laptop_exit ( void )
{
2007-01-26 16:04:45 +03:00
asus_backlight_exit ( ) ;
2007-01-26 16:04:35 +03:00
asus_led_exit ( ) ;
2007-01-26 16:04:30 +03:00
acpi_bus_unregister_driver ( & asus_hotk_driver ) ;
2007-01-30 09:46:43 +03:00
sysfs_remove_group ( & asuspf_device - > dev . kobj , & asuspf_attribute_group ) ;
platform_device_unregister ( asuspf_device ) ;
platform_driver_unregister ( & asuspf_driver ) ;
2007-01-26 16:04:30 +03:00
}
2007-01-30 09:46:43 +03:00
static int asus_backlight_init ( struct device * dev )
2007-01-26 16:04:45 +03:00
{
struct backlight_device * bd ;
2007-01-30 09:46:43 +03:00
if ( brightness_set_handle & & lcd_switch_handle ) {
bd = backlight_device_register ( ASUS_HOTK_FILE , dev ,
2007-02-11 02:07:48 +03:00
NULL , & asusbl_ops ) ;
2007-01-30 09:46:43 +03:00
if ( IS_ERR ( bd ) ) {
2007-01-26 16:04:45 +03:00
printk ( ASUS_ERR
" Could not register asus backlight device \n " ) ;
asus_backlight_device = NULL ;
return PTR_ERR ( bd ) ;
}
asus_backlight_device = bd ;
2007-02-11 02:07:48 +03:00
bd - > props . max_brightness = 15 ;
bd - > props . brightness = read_brightness ( NULL ) ;
bd - > props . power = FB_BLANK_UNBLANK ;
2007-02-09 01:25:09 +03:00
backlight_update_status ( bd ) ;
2007-01-26 16:04:45 +03:00
}
return 0 ;
}
2007-01-26 16:04:35 +03:00
static int asus_led_register ( acpi_handle handle ,
2007-01-30 09:46:43 +03:00
struct led_classdev * ldev , struct device * dev )
2007-01-26 16:04:35 +03:00
{
2007-01-30 09:46:43 +03:00
if ( ! handle )
2007-01-26 16:04:35 +03:00
return 0 ;
return led_classdev_register ( dev , ldev ) ;
}
2007-01-30 09:46:43 +03:00
2007-01-26 16:04:35 +03:00
# define ASUS_LED_REGISTER(object, device) \
asus_led_register ( object # # _set_handle , & object # # _led , device )
2007-01-30 09:46:43 +03:00
static int asus_led_init ( struct device * dev )
2007-01-26 16:04:35 +03:00
{
int rv ;
rv = ASUS_LED_REGISTER ( mled , dev ) ;
2007-01-30 09:46:43 +03:00
if ( rv )
2007-01-26 16:04:35 +03:00
return rv ;
rv = ASUS_LED_REGISTER ( tled , dev ) ;
2007-01-30 09:46:43 +03:00
if ( rv )
2007-01-26 16:04:35 +03:00
return rv ;
rv = ASUS_LED_REGISTER ( rled , dev ) ;
2007-01-30 09:46:43 +03:00
if ( rv )
2007-01-26 16:04:35 +03:00
return rv ;
rv = ASUS_LED_REGISTER ( pled , dev ) ;
2007-01-30 09:46:43 +03:00
if ( rv )
2007-01-26 16:04:35 +03:00
return rv ;
2007-03-11 12:26:48 +03:00
rv = ASUS_LED_REGISTER ( gled , dev ) ;
if ( rv )
return rv ;
2007-01-26 16:04:35 +03:00
led_workqueue = create_singlethread_workqueue ( " led_workqueue " ) ;
2007-01-30 09:46:43 +03:00
if ( ! led_workqueue )
2007-01-26 16:04:35 +03:00
return - ENOMEM ;
return 0 ;
}
2007-01-26 16:04:30 +03:00
static int __init asus_laptop_init ( void )
{
2007-01-26 16:04:35 +03:00
struct device * dev ;
2007-01-26 16:04:30 +03:00
int result ;
if ( acpi_disabled )
return - ENODEV ;
result = acpi_bus_register_driver ( & asus_hotk_driver ) ;
if ( result < 0 )
return result ;
/*
* This is a bit of a kludge . We only want this module loaded
* for ASUS systems , but there ' s currently no way to probe the
* ACPI namespace for ASUS HIDs . So we just return failure if
* we didn ' t find one , which will cause the module to be
* unloaded .
*/
if ( ! asus_hotk_found ) {
acpi_bus_unregister_driver ( & asus_hotk_driver ) ;
return - ENODEV ;
}
2007-01-26 16:04:35 +03:00
dev = acpi_get_physical_device ( hotk - > device - > handle ) ;
2007-01-26 16:04:45 +03:00
result = asus_backlight_init ( dev ) ;
2007-01-30 09:46:43 +03:00
if ( result )
2007-01-26 16:04:45 +03:00
goto fail_backlight ;
2007-01-26 16:04:35 +03:00
result = asus_led_init ( dev ) ;
2007-01-30 09:46:43 +03:00
if ( result )
2007-01-26 16:04:35 +03:00
goto fail_led ;
2007-01-30 09:46:43 +03:00
/* Register platform stuff */
2007-01-26 16:04:30 +03:00
result = platform_driver_register ( & asuspf_driver ) ;
2007-01-30 09:46:43 +03:00
if ( result )
goto fail_platform_driver ;
2007-01-26 16:04:30 +03:00
2007-01-30 09:46:43 +03:00
asuspf_device = platform_device_alloc ( ASUS_HOTK_FILE , - 1 ) ;
if ( ! asuspf_device ) {
result = - ENOMEM ;
goto fail_platform_device1 ;
}
2007-01-26 16:04:30 +03:00
2007-01-30 09:46:43 +03:00
result = platform_device_add ( asuspf_device ) ;
if ( result )
goto fail_platform_device2 ;
2007-01-26 16:04:30 +03:00
2007-01-30 09:46:43 +03:00
result = sysfs_create_group ( & asuspf_device - > dev . kobj ,
2007-01-26 16:04:30 +03:00
& asuspf_attribute_group ) ;
2007-01-30 09:46:43 +03:00
if ( result )
goto fail_sysfs ;
2007-01-26 16:04:30 +03:00
2007-01-30 09:46:43 +03:00
return 0 ;
2007-01-26 16:04:30 +03:00
2007-01-30 09:46:43 +03:00
fail_sysfs :
platform_device_del ( asuspf_device ) ;
2007-01-26 16:04:30 +03:00
2007-01-30 09:46:43 +03:00
fail_platform_device2 :
2007-01-26 16:04:30 +03:00
platform_device_put ( asuspf_device ) ;
2007-01-30 09:46:43 +03:00
fail_platform_device1 :
platform_driver_unregister ( & asuspf_driver ) ;
2007-01-26 16:04:30 +03:00
2007-01-30 09:46:43 +03:00
fail_platform_driver :
2007-01-26 16:04:35 +03:00
asus_led_exit ( ) ;
2007-01-30 09:46:43 +03:00
fail_led :
2007-01-26 16:04:45 +03:00
asus_backlight_exit ( ) ;
2007-01-30 09:46:43 +03:00
fail_backlight :
2007-01-26 16:04:30 +03:00
return result ;
}
module_init ( asus_laptop_init ) ;
module_exit ( asus_laptop_exit ) ;