2005-11-20 08:50:06 +03:00
/*
* Wistron laptop button driver
* Copyright ( C ) 2005 Miloslav Trmac < mitr @ volny . cz >
2005-11-20 08:50:37 +03:00
* Copyright ( C ) 2005 Bernhard Rosenkraenzer < bero @ arklinux . org >
2005-11-20 08:50:06 +03:00
*
* You can redistribute and / or modify this program under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU General
* Public License for more details .
*
* You should have received a copy of the GNU General Public License along
* with this program ; if not , write to the Free Software Foundation , Inc . ,
* 59 Temple Place Suite 330 , Boston , MA 02111 - 1307 , USA .
*/
# include <asm/io.h>
# include <linux/dmi.h>
# include <linux/init.h>
# include <linux/input.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
# include <linux/mc146818rtc.h>
# include <linux/module.h>
# include <linux/preempt.h>
# include <linux/string.h>
# include <linux/timer.h>
# include <linux/types.h>
/*
* Number of attempts to read data from queue per poll ;
* the queue can hold up to 31 entries
*/
# define MAX_POLL_ITERATIONS 64
# define POLL_FREQUENCY 10 /* Number of polls per second */
# if POLL_FREQUENCY > HZ
# error "POLL_FREQUENCY too high"
# endif
2005-11-20 08:50:37 +03:00
/* BIOS subsystem IDs */
# define WIFI 0x35
# define BLUETOOTH 0x34
2005-11-20 08:50:06 +03:00
MODULE_AUTHOR ( " Miloslav Trmac <mitr@volny.cz> " ) ;
MODULE_DESCRIPTION ( " Wistron laptop button driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_VERSION ( " 0.1 " ) ;
static int force ; /* = 0; */
module_param ( force , bool , 0 ) ;
MODULE_PARM_DESC ( force , " Load even if computer is not in database " ) ;
static char * keymap_name ; /* = NULL; */
module_param_named ( keymap , keymap_name , charp , 0 ) ;
MODULE_PARM_DESC ( keymap , " Keymap name, if it can't be autodetected " ) ;
/* BIOS interface implementation */
static void __iomem * bios_entry_point ; /* BIOS routine entry point */
static void __iomem * bios_code_map_base ;
static void __iomem * bios_data_map_base ;
static u8 cmos_address ;
struct regs {
u32 eax , ebx , ecx ;
} ;
static void call_bios ( struct regs * regs )
{
unsigned long flags ;
preempt_disable ( ) ;
local_irq_save ( flags ) ;
asm volatile ( " pushl %%ebp; "
" movl %7, %%ebp; "
" call *%6; "
" popl %%ebp "
: " =a " ( regs - > eax ) , " =b " ( regs - > ebx ) , " =c " ( regs - > ecx )
: " 0 " ( regs - > eax ) , " 1 " ( regs - > ebx ) , " 2 " ( regs - > ecx ) ,
" m " ( bios_entry_point ) , " m " ( bios_data_map_base )
: " edx " , " edi " , " esi " , " memory " ) ;
local_irq_restore ( flags ) ;
preempt_enable ( ) ;
}
static size_t __init locate_wistron_bios ( void __iomem * base )
{
static const unsigned char __initdata signature [ ] =
{ 0x42 , 0x21 , 0x55 , 0x30 } ;
size_t offset ;
for ( offset = 0 ; offset < 0x10000 ; offset + = 0x10 ) {
if ( check_signature ( base + offset , signature ,
sizeof ( signature ) ) ! = 0 )
return offset ;
}
return - 1 ;
}
static int __init map_bios ( void )
{
void __iomem * base ;
size_t offset ;
u32 entry_point ;
base = ioremap ( 0xF0000 , 0x10000 ) ; /* Can't fail */
offset = locate_wistron_bios ( base ) ;
if ( offset < 0 ) {
printk ( KERN_ERR " wistron_btns: BIOS entry point not found \n " ) ;
iounmap ( base ) ;
return - ENODEV ;
}
entry_point = readl ( base + offset + 5 ) ;
printk ( KERN_DEBUG
" wistron_btns: BIOS signature found at %p, entry point %08X \n " ,
base + offset , entry_point ) ;
if ( entry_point > = 0xF0000 ) {
bios_code_map_base = base ;
bios_entry_point = bios_code_map_base + ( entry_point & 0xFFFF ) ;
} else {
iounmap ( base ) ;
bios_code_map_base = ioremap ( entry_point & ~ 0x3FFF , 0x4000 ) ;
if ( bios_code_map_base = = NULL ) {
printk ( KERN_ERR
" wistron_btns: Can't map BIOS code at %08X \n " ,
entry_point & ~ 0x3FFF ) ;
goto err ;
}
bios_entry_point = bios_code_map_base + ( entry_point & 0x3FFF ) ;
}
/* The Windows driver maps 0x10000 bytes, we keep only one page... */
bios_data_map_base = ioremap ( 0x400 , 0xc00 ) ;
if ( bios_data_map_base = = NULL ) {
printk ( KERN_ERR " wistron_btns: Can't map BIOS data \n " ) ;
goto err_code ;
}
return 0 ;
err_code :
iounmap ( bios_code_map_base ) ;
err :
return - ENOMEM ;
}
static void __exit unmap_bios ( void )
{
iounmap ( bios_code_map_base ) ;
iounmap ( bios_data_map_base ) ;
}
/* BIOS calls */
static u16 bios_pop_queue ( void )
{
struct regs regs ;
memset ( & regs , 0 , sizeof ( regs ) ) ;
regs . eax = 0x9610 ;
regs . ebx = 0x061C ;
regs . ecx = 0x0000 ;
call_bios ( & regs ) ;
return regs . eax ;
}
static void __init bios_attach ( void )
{
struct regs regs ;
memset ( & regs , 0 , sizeof ( regs ) ) ;
regs . eax = 0x9610 ;
regs . ebx = 0x012E ;
call_bios ( & regs ) ;
}
static void __exit bios_detach ( void )
{
struct regs regs ;
memset ( & regs , 0 , sizeof ( regs ) ) ;
regs . eax = 0x9610 ;
regs . ebx = 0x002E ;
call_bios ( & regs ) ;
}
static u8 __init bios_get_cmos_address ( void )
{
struct regs regs ;
memset ( & regs , 0 , sizeof ( regs ) ) ;
regs . eax = 0x9610 ;
regs . ebx = 0x051C ;
call_bios ( & regs ) ;
return regs . ecx ;
}
2005-11-20 08:50:37 +03:00
static u16 __init bios_get_default_setting ( u8 subsys )
2005-11-20 08:50:06 +03:00
{
struct regs regs ;
memset ( & regs , 0 , sizeof ( regs ) ) ;
regs . eax = 0x9610 ;
2005-11-20 08:50:37 +03:00
regs . ebx = 0x0200 | subsys ;
2005-11-20 08:50:06 +03:00
call_bios ( & regs ) ;
return regs . eax ;
}
2005-11-20 08:50:37 +03:00
static void bios_set_state ( u8 subsys , int enable )
2005-11-20 08:50:06 +03:00
{
struct regs regs ;
memset ( & regs , 0 , sizeof ( regs ) ) ;
regs . eax = 0x9610 ;
2005-11-20 08:50:37 +03:00
regs . ebx = ( enable ? 0x0100 : 0x0000 ) | subsys ;
2005-11-20 08:50:06 +03:00
call_bios ( & regs ) ;
}
2005-11-20 08:50:37 +03:00
/* Hardware database */
2005-11-20 08:50:06 +03:00
struct key_entry {
char type ; /* See KE_* below */
u8 code ;
unsigned keycode ; /* For KE_KEY */
} ;
2005-11-20 08:50:37 +03:00
enum { KE_END , KE_KEY , KE_WIFI , KE_BLUETOOTH } ;
2005-11-20 08:50:06 +03:00
static const struct key_entry * keymap ; /* = NULL; Current key map */
static int have_wifi ;
2005-11-20 08:50:37 +03:00
static int have_bluetooth ;
2005-11-20 08:50:06 +03:00
static int __init dmi_matched ( struct dmi_system_id * dmi )
{
const struct key_entry * key ;
keymap = dmi - > driver_data ;
for ( key = keymap ; key - > type ! = KE_END ; key + + ) {
if ( key - > type = = KE_WIFI ) {
have_wifi = 1 ;
break ;
2005-11-20 08:50:37 +03:00
} else if ( key - > type = = KE_BLUETOOTH ) {
have_bluetooth = 1 ;
break ;
2005-11-20 08:50:06 +03:00
}
}
return 1 ;
}
static struct key_entry keymap_empty [ ] = {
{ KE_END , 0 }
} ;
static struct key_entry keymap_fs_amilo_pro_v2000 [ ] = {
{ KE_KEY , 0x01 , KEY_HELP } ,
{ KE_KEY , 0x11 , KEY_PROG1 } ,
{ KE_KEY , 0x12 , KEY_PROG2 } ,
{ KE_WIFI , 0x30 , 0 } ,
{ KE_KEY , 0x31 , KEY_MAIL } ,
{ KE_KEY , 0x36 , KEY_WWW } ,
{ KE_END , 0 }
} ;
static struct key_entry keymap_wistron_ms2141 [ ] = {
{ KE_KEY , 0x11 , KEY_PROG1 } ,
{ KE_KEY , 0x12 , KEY_PROG2 } ,
{ KE_WIFI , 0x30 , 0 } ,
{ KE_KEY , 0x22 , KEY_REWIND } ,
{ KE_KEY , 0x23 , KEY_FORWARD } ,
{ KE_KEY , 0x24 , KEY_PLAYPAUSE } ,
{ KE_KEY , 0x25 , KEY_STOPCD } ,
{ KE_KEY , 0x31 , KEY_MAIL } ,
{ KE_KEY , 0x36 , KEY_WWW } ,
{ KE_END , 0 }
} ;
2005-11-20 08:50:37 +03:00
static struct key_entry keymap_acer_aspire_1500 [ ] = {
{ KE_KEY , 0x11 , KEY_PROG1 } ,
{ KE_KEY , 0x12 , KEY_PROG2 } ,
{ KE_WIFI , 0x30 , 0 } ,
{ KE_KEY , 0x31 , KEY_MAIL } ,
{ KE_KEY , 0x36 , KEY_WWW } ,
{ KE_BLUETOOTH , 0x44 , 0 } ,
{ KE_END , 0 }
} ;
2005-11-20 08:50:06 +03:00
/*
* If your machine is not here ( which is currently rather likely ) , please send
* a list of buttons and their key codes ( reported when loading this module
* with force = 1 ) and the output of dmidecode to $ MODULE_AUTHOR .
*/
static struct dmi_system_id dmi_ids [ ] = {
{
. callback = dmi_matched ,
. ident = " Fujitsu-Siemens Amilo Pro V2000 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " FUJITSU SIEMENS " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " AMILO Pro V2000 " ) ,
} ,
. driver_data = keymap_fs_amilo_pro_v2000
} ,
2005-11-20 08:50:37 +03:00
{
. callback = dmi_matched ,
. ident = " Acer Aspire 1500 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Acer " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Aspire 1500 " ) ,
} ,
. driver_data = keymap_acer_aspire_1500
} ,
2005-11-20 08:50:06 +03:00
{ 0 , }
} ;
static int __init select_keymap ( void )
{
if ( keymap_name ! = NULL ) {
if ( strcmp ( keymap_name , " 1557/MS2141 " ) = = 0 )
keymap = keymap_wistron_ms2141 ;
else {
printk ( KERN_ERR " wistron_btns: Keymap unknown \n " ) ;
return - EINVAL ;
}
}
dmi_check_system ( dmi_ids ) ;
if ( keymap = = NULL ) {
if ( ! force ) {
printk ( KERN_ERR " wistron_btns: System unknown \n " ) ;
return - ENODEV ;
}
keymap = keymap_empty ;
}
return 0 ;
}
/* Input layer interface */
static struct input_dev input_dev = {
. name = " Wistron laptop buttons " ,
} ;
static void __init setup_input_dev ( void )
{
const struct key_entry * key ;
for ( key = keymap ; key - > type ! = KE_END ; key + + ) {
if ( key - > type = = KE_KEY ) {
input_dev . evbit [ LONG ( EV_KEY ) ] = BIT ( EV_KEY ) ;
input_dev . keybit [ LONG ( key - > keycode ) ]
| = BIT ( key - > keycode ) ;
}
}
input_register_device ( & input_dev ) ;
}
static void report_key ( unsigned keycode )
{
input_report_key ( & input_dev , keycode , 1 ) ;
input_sync ( & input_dev ) ;
input_report_key ( & input_dev , keycode , 0 ) ;
input_sync ( & input_dev ) ;
}
/* Driver core */
static int wifi_enabled ;
2005-11-20 08:50:37 +03:00
static int bluetooth_enabled ;
2005-11-20 08:50:06 +03:00
static void poll_bios ( unsigned long ) ;
static struct timer_list poll_timer = TIMER_INITIALIZER ( poll_bios , 0 , 0 ) ;
static void handle_key ( u8 code )
{
const struct key_entry * key ;
for ( key = keymap ; key - > type ! = KE_END ; key + + ) {
if ( code = = key - > code ) {
switch ( key - > type ) {
case KE_KEY :
report_key ( key - > keycode ) ;
break ;
case KE_WIFI :
if ( have_wifi ) {
wifi_enabled = ! wifi_enabled ;
2005-11-20 08:50:37 +03:00
bios_set_state ( WIFI , wifi_enabled ) ;
}
break ;
case KE_BLUETOOTH :
if ( have_bluetooth ) {
bluetooth_enabled = ! bluetooth_enabled ;
bios_set_state ( BLUETOOTH , bluetooth_enabled ) ;
2005-11-20 08:50:06 +03:00
}
break ;
case KE_END :
default :
BUG ( ) ;
}
return ;
}
}
printk ( KERN_NOTICE " wistron_btns: Unknown key code %02X \n " , code ) ;
}
static void poll_bios ( unsigned long discard )
{
u8 qlen ;
u16 val ;
for ( ; ; ) {
qlen = CMOS_READ ( cmos_address ) ;
if ( qlen = = 0 )
break ;
val = bios_pop_queue ( ) ;
if ( val ! = 0 & & ! discard )
handle_key ( ( u8 ) val ) ;
}
mod_timer ( & poll_timer , jiffies + HZ / POLL_FREQUENCY ) ;
}
static int __init wb_module_init ( void )
{
int err ;
err = select_keymap ( ) ;
if ( err )
return err ;
err = map_bios ( ) ;
if ( err )
return err ;
bios_attach ( ) ;
cmos_address = bios_get_cmos_address ( ) ;
if ( have_wifi ) {
2005-11-20 08:50:37 +03:00
u16 wifi = bios_get_default_setting ( WIFI ) ;
if ( wifi & 1 )
wifi_enabled = ( wifi & 2 ) ? 1 : 0 ;
else
2005-11-20 08:50:06 +03:00
have_wifi = 0 ;
2005-11-20 08:50:37 +03:00
2005-11-20 08:50:06 +03:00
if ( have_wifi )
2005-11-20 08:50:37 +03:00
bios_set_state ( WIFI , wifi_enabled ) ;
}
if ( have_bluetooth ) {
u16 bt = bios_get_default_setting ( BLUETOOTH ) ;
if ( bt & 1 )
bluetooth_enabled = ( bt & 2 ) ? 1 : 0 ;
else
have_bluetooth = 0 ;
if ( have_bluetooth )
bios_set_state ( BLUETOOTH , bluetooth_enabled ) ;
2005-11-20 08:50:06 +03:00
}
setup_input_dev ( ) ;
poll_bios ( 1 ) ; /* Flush stale event queue and arm timer */
return 0 ;
}
static void __exit wb_module_exit ( void )
{
del_timer_sync ( & poll_timer ) ;
input_unregister_device ( & input_dev ) ;
bios_detach ( ) ;
unmap_bios ( ) ;
}
module_init ( wb_module_init ) ;
module_exit ( wb_module_exit ) ;