2011-03-11 12:41:19 -05:00
/*
* Samsung Laptop driver
*
* Copyright ( C ) 2009 , 2011 Greg Kroah - Hartman ( gregkh @ suse . de )
* Copyright ( C ) 2009 , 2011 Novell Inc .
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation .
*
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/module.h>
# include <linux/delay.h>
# include <linux/pci.h>
# include <linux/backlight.h>
2011-11-26 11:00:08 +01:00
# include <linux/leds.h>
2011-03-11 12:41:19 -05:00
# include <linux/fb.h>
# include <linux/dmi.h>
# include <linux/platform_device.h>
# include <linux/rfkill.h>
2011-11-26 11:00:00 +01:00
# include <linux/acpi.h>
2011-11-26 11:00:03 +01:00
# include <linux/seq_file.h>
# include <linux/debugfs.h>
2012-03-20 09:53:06 +01:00
# include <linux/ctype.h>
2011-03-11 12:41:19 -05:00
/*
* This driver is needed because a number of Samsung laptops do not hook
* their control settings through ACPI . So we have to poke around in the
* BIOS to do things like brightness values , and " special " key controls .
*/
/*
* We have 0 - 8 as valid brightness levels . The specs say that level 0 should
* be reserved by the BIOS ( which really doesn ' t make much sense ) , we tell
* userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
*/
# define MAX_BRIGHT 0x07
# define SABI_IFACE_MAIN 0x00
# define SABI_IFACE_SUB 0x02
# define SABI_IFACE_COMPLETE 0x04
# define SABI_IFACE_DATA 0x05
2011-11-26 11:00:09 +01:00
# define WL_STATUS_WLAN 0x0
# define WL_STATUS_BT 0x2
2011-11-26 11:00:02 +01:00
/* Structure get/set data using sabi */
struct sabi_data {
union {
struct {
u32 d0 ;
u32 d1 ;
u16 d2 ;
u8 d3 ;
} ;
u8 data [ 11 ] ;
} ;
2011-03-11 12:41:19 -05:00
} ;
struct sabi_header_offsets {
u8 port ;
u8 re_mem ;
u8 iface_func ;
u8 en_mem ;
u8 data_offset ;
u8 data_segment ;
} ;
struct sabi_commands {
/*
* Brightness is 0 - 8 , as described above .
* Value 0 is for the BIOS to use
*/
2011-11-26 11:00:02 +01:00
u16 get_brightness ;
u16 set_brightness ;
2011-03-11 12:41:19 -05:00
/*
* first byte :
* 0x00 - wireless is off
* 0x01 - wireless is on
* second byte :
* 0x02 - 3 G is off
* 0x03 - 3 G is on
* TODO , verify 3 G is correct , that doesn ' t seem right . . .
*/
2011-11-26 11:00:02 +01:00
u16 get_wireless_button ;
u16 set_wireless_button ;
2011-03-11 12:41:19 -05:00
/* 0 is off, 1 is on */
2011-11-26 11:00:02 +01:00
u16 get_backlight ;
u16 set_backlight ;
2011-03-11 12:41:19 -05:00
/*
* 0x80 or 0x00 - no action
* 0x81 - recovery key pressed
*/
2011-11-26 11:00:02 +01:00
u16 get_recovery_mode ;
u16 set_recovery_mode ;
2011-03-11 12:41:19 -05:00
/*
* on seclinux : 0 is low , 1 is high ,
* on swsmi : 0 is normal , 1 is silent , 2 is turbo
*/
2011-11-26 11:00:02 +01:00
u16 get_performance_level ;
u16 set_performance_level ;
2011-03-11 12:41:19 -05:00
2011-11-26 11:00:05 +01:00
/* 0x80 is off, 0x81 is on */
u16 get_battery_life_extender ;
u16 set_battery_life_extender ;
2011-11-26 11:00:06 +01:00
/* 0x80 is off, 0x81 is on */
u16 get_usb_charge ;
u16 set_usb_charge ;
2011-11-26 11:00:09 +01:00
/* the first byte is for bluetooth and the third one is for wlan */
u16 get_wireless_status ;
u16 set_wireless_status ;
2011-11-26 11:00:08 +01:00
/* 0x81 to read, (0x82 | level << 8) to set, 0xaabb to enable */
u16 kbd_backlight ;
2011-03-11 12:41:19 -05:00
/*
* Tell the BIOS that Linux is running on this machine .
* 81 is on , 80 is off
*/
2011-11-26 11:00:02 +01:00
u16 set_linux ;
2011-03-11 12:41:19 -05:00
} ;
struct sabi_performance_level {
const char * name ;
2011-11-26 11:00:02 +01:00
u16 value ;
2011-03-11 12:41:19 -05:00
} ;
struct sabi_config {
2011-11-26 11:00:09 +01:00
int sabi_version ;
2011-03-11 12:41:19 -05:00
const char * test_string ;
u16 main_function ;
const struct sabi_header_offsets header_offsets ;
const struct sabi_commands commands ;
const struct sabi_performance_level performance_levels [ 4 ] ;
u8 min_brightness ;
u8 max_brightness ;
} ;
static const struct sabi_config sabi_configs [ ] = {
{
2011-11-26 11:00:09 +01:00
/* I don't know if it is really 2, but it it is
* less than 3 anyway */
. sabi_version = 2 ,
2011-03-11 12:41:19 -05:00
. test_string = " SECLINUX " ,
. main_function = 0x4c49 ,
. header_offsets = {
. port = 0x00 ,
. re_mem = 0x02 ,
. iface_func = 0x03 ,
. en_mem = 0x04 ,
. data_offset = 0x05 ,
. data_segment = 0x07 ,
} ,
. commands = {
. get_brightness = 0x00 ,
. set_brightness = 0x01 ,
. get_wireless_button = 0x02 ,
. set_wireless_button = 0x03 ,
. get_backlight = 0x04 ,
. set_backlight = 0x05 ,
. get_recovery_mode = 0x06 ,
. set_recovery_mode = 0x07 ,
. get_performance_level = 0x08 ,
. set_performance_level = 0x09 ,
2011-11-26 11:00:05 +01:00
. get_battery_life_extender = 0xFFFF ,
. set_battery_life_extender = 0xFFFF ,
2011-11-26 11:00:06 +01:00
. get_usb_charge = 0xFFFF ,
. set_usb_charge = 0xFFFF ,
2011-11-26 11:00:09 +01:00
. get_wireless_status = 0xFFFF ,
. set_wireless_status = 0xFFFF ,
2011-11-26 11:00:08 +01:00
. kbd_backlight = 0xFFFF ,
2011-03-11 12:41:19 -05:00
. set_linux = 0x0a ,
} ,
. performance_levels = {
{
. name = " silent " ,
. value = 0 ,
} ,
{
. name = " normal " ,
. value = 1 ,
} ,
{ } ,
} ,
. min_brightness = 1 ,
. max_brightness = 8 ,
} ,
{
2011-11-26 11:00:09 +01:00
. sabi_version = 3 ,
2011-03-11 12:41:19 -05:00
. test_string = " SwSmi@ " ,
. main_function = 0x5843 ,
. header_offsets = {
. port = 0x00 ,
. re_mem = 0x04 ,
. iface_func = 0x02 ,
. en_mem = 0x03 ,
. data_offset = 0x05 ,
. data_segment = 0x07 ,
} ,
. commands = {
. get_brightness = 0x10 ,
. set_brightness = 0x11 ,
. get_wireless_button = 0x12 ,
. set_wireless_button = 0x13 ,
. get_backlight = 0x2d ,
. set_backlight = 0x2e ,
. get_recovery_mode = 0xff ,
. set_recovery_mode = 0xff ,
. get_performance_level = 0x31 ,
. set_performance_level = 0x32 ,
2011-11-26 11:00:05 +01:00
. get_battery_life_extender = 0x65 ,
. set_battery_life_extender = 0x66 ,
2011-11-26 11:00:06 +01:00
. get_usb_charge = 0x67 ,
. set_usb_charge = 0x68 ,
2011-11-26 11:00:09 +01:00
. get_wireless_status = 0x69 ,
. set_wireless_status = 0x6a ,
2011-11-26 11:00:08 +01:00
. kbd_backlight = 0x78 ,
2011-03-11 12:41:19 -05:00
. set_linux = 0xff ,
} ,
. performance_levels = {
{
. name = " normal " ,
. value = 0 ,
} ,
{
. name = " silent " ,
. value = 1 ,
} ,
{
. name = " overclock " ,
. value = 2 ,
} ,
{ } ,
} ,
. min_brightness = 0 ,
. max_brightness = 8 ,
} ,
{ } ,
} ;
2011-11-26 11:00:03 +01:00
/*
* samsung - laptop / - debugfs root directory
* f0000_segment - dump f0000 segment
* command - current command
* data - current data
* d0 , d1 , d2 , d3 - data fields
* call - call SABI using command and data
*
* This allow to call arbitrary sabi commands wihout
* modifying the driver at all .
* For example , setting the keyboard backlight brightness to 5
*
* echo 0x78 > command
* echo 0x0582 > d0
* echo 0 > d1
* echo 0 > d2
* echo 0 > d3
* cat call
*/
struct samsung_laptop_debug {
struct dentry * root ;
struct sabi_data data ;
u16 command ;
struct debugfs_blob_wrapper f0000_wrapper ;
struct debugfs_blob_wrapper data_wrapper ;
2011-11-26 11:00:11 +01:00
struct debugfs_blob_wrapper sdiag_wrapper ;
2011-11-26 11:00:03 +01:00
} ;
2011-11-26 11:00:09 +01:00
struct samsung_laptop ;
struct samsung_rfkill {
struct samsung_laptop * samsung ;
struct rfkill * rfkill ;
enum rfkill_type type ;
} ;
2011-11-26 10:59:58 +01:00
struct samsung_laptop {
const struct sabi_config * config ;
2011-03-11 12:41:19 -05:00
2011-11-26 10:59:58 +01:00
void __iomem * sabi ;
void __iomem * sabi_iface ;
void __iomem * f0000_segment ;
struct mutex sabi_mutex ;
2011-11-26 10:59:59 +01:00
struct platform_device * platform_device ;
2011-11-26 10:59:58 +01:00
struct backlight_device * backlight_device ;
2011-11-26 11:00:09 +01:00
struct samsung_rfkill wlan ;
struct samsung_rfkill bluetooth ;
2011-11-26 10:59:58 +01:00
2011-11-26 11:00:08 +01:00
struct led_classdev kbd_led ;
int kbd_led_wk ;
struct workqueue_struct * led_workqueue ;
struct work_struct kbd_led_work ;
2011-11-26 11:00:03 +01:00
struct samsung_laptop_debug debug ;
2011-11-26 11:00:00 +01:00
bool handle_backlight ;
2011-11-26 10:59:58 +01:00
bool has_stepping_quirk ;
2011-11-26 11:00:11 +01:00
char sdiag [ 64 ] ;
2011-11-26 10:59:58 +01:00
} ;
2011-11-26 10:59:59 +01:00
2011-03-11 12:41:19 -05:00
2012-01-13 09:32:20 +10:30
static bool force ;
2011-03-11 12:41:19 -05:00
module_param ( force , bool , 0 ) ;
MODULE_PARM_DESC ( force ,
" Disable the DMI check and forces the driver to be loaded " ) ;
2012-01-13 09:32:20 +10:30
static bool debug ;
2011-03-11 12:41:19 -05:00
module_param ( debug , bool , S_IRUGO | S_IWUSR ) ;
MODULE_PARM_DESC ( debug , " Debug enabled or not " ) ;
2011-11-26 11:00:02 +01:00
static int sabi_command ( struct samsung_laptop * samsung , u16 command ,
struct sabi_data * in ,
struct sabi_data * out )
2011-03-11 12:41:19 -05:00
{
2011-11-26 10:59:58 +01:00
const struct sabi_config * config = samsung - > config ;
2011-11-26 11:00:02 +01:00
int ret = 0 ;
2011-11-26 10:59:58 +01:00
u16 port = readw ( samsung - > sabi + config - > header_offsets . port ) ;
2011-03-11 12:41:19 -05:00
u8 complete , iface_data ;
2011-11-26 10:59:58 +01:00
mutex_lock ( & samsung - > sabi_mutex ) ;
2011-03-11 12:41:19 -05:00
2011-11-26 11:00:02 +01:00
if ( debug ) {
if ( in )
2011-11-26 11:00:12 +01:00
pr_info ( " SABI command:0x%04x "
" data:{0x%08x, 0x%08x, 0x%04x, 0x%02x} " ,
2011-11-26 11:00:02 +01:00
command , in - > d0 , in - > d1 , in - > d2 , in - > d3 ) ;
else
2011-11-26 11:00:12 +01:00
pr_info ( " SABI command:0x%04x " , command ) ;
2011-11-26 11:00:02 +01:00
}
2011-03-11 12:41:19 -05:00
/* enable memory to be able to write to it */
2011-11-26 10:59:58 +01:00
outb ( readb ( samsung - > sabi + config - > header_offsets . en_mem ) , port ) ;
2011-03-11 12:41:19 -05:00
/* write out the command */
2011-11-26 10:59:58 +01:00
writew ( config - > main_function , samsung - > sabi_iface + SABI_IFACE_MAIN ) ;
writew ( command , samsung - > sabi_iface + SABI_IFACE_SUB ) ;
writeb ( 0 , samsung - > sabi_iface + SABI_IFACE_COMPLETE ) ;
2011-11-26 11:00:02 +01:00
if ( in ) {
writel ( in - > d0 , samsung - > sabi_iface + SABI_IFACE_DATA ) ;
writel ( in - > d1 , samsung - > sabi_iface + SABI_IFACE_DATA + 4 ) ;
writew ( in - > d2 , samsung - > sabi_iface + SABI_IFACE_DATA + 8 ) ;
writeb ( in - > d3 , samsung - > sabi_iface + SABI_IFACE_DATA + 10 ) ;
}
2011-11-26 10:59:58 +01:00
outb ( readb ( samsung - > sabi + config - > header_offsets . iface_func ) , port ) ;
2011-03-11 12:41:19 -05:00
/* write protect memory to make it safe */
2011-11-26 10:59:58 +01:00
outb ( readb ( samsung - > sabi + config - > header_offsets . re_mem ) , port ) ;
2011-03-11 12:41:19 -05:00
/* see if the command actually succeeded */
2011-11-26 10:59:58 +01:00
complete = readb ( samsung - > sabi_iface + SABI_IFACE_COMPLETE ) ;
iface_data = readb ( samsung - > sabi_iface + SABI_IFACE_DATA ) ;
2011-11-26 11:00:12 +01:00
/* iface_data = 0xFF happens when a command is not known
* so we only add a warning in debug mode since we will
* probably issue some unknown command at startup to find
* out which features are supported */
if ( complete ! = 0xaa | | ( iface_data = = 0xff & & debug ) )
2011-11-26 11:00:02 +01:00
pr_warn ( " SABI command 0x%04x failed with "
" completion flag 0x%02x and interface data 0x%02x " ,
command , complete , iface_data ) ;
2011-11-26 11:00:12 +01:00
if ( complete ! = 0xaa | | iface_data = = 0xff ) {
2011-11-26 11:00:02 +01:00
ret = - EINVAL ;
2011-03-11 12:41:19 -05:00
goto exit ;
}
2011-11-26 11:00:02 +01:00
if ( out ) {
out - > d0 = readl ( samsung - > sabi_iface + SABI_IFACE_DATA ) ;
out - > d1 = readl ( samsung - > sabi_iface + SABI_IFACE_DATA + 4 ) ;
out - > d2 = readw ( samsung - > sabi_iface + SABI_IFACE_DATA + 2 ) ;
out - > d3 = readb ( samsung - > sabi_iface + SABI_IFACE_DATA + 1 ) ;
}
if ( debug & & out ) {
2011-11-26 11:00:12 +01:00
pr_info ( " SABI return data:{0x%08x, 0x%08x, 0x%04x, 0x%02x} " ,
2011-11-26 11:00:02 +01:00
out - > d0 , out - > d1 , out - > d2 , out - > d3 ) ;
}
2011-03-11 12:41:19 -05:00
exit :
2011-11-26 10:59:58 +01:00
mutex_unlock ( & samsung - > sabi_mutex ) ;
2011-11-26 11:00:02 +01:00
return ret ;
2011-03-11 12:41:19 -05:00
}
2011-11-26 11:00:02 +01:00
/* simple wrappers usable with most commands */
static int sabi_set_commandb ( struct samsung_laptop * samsung ,
u16 command , u8 data )
2011-03-11 12:41:19 -05:00
{
2012-03-20 09:53:05 +01:00
struct sabi_data in = { { { . d0 = 0 , . d1 = 0 , . d2 = 0 , . d3 = 0 } } } ;
2011-03-11 12:41:19 -05:00
2011-11-26 11:00:02 +01:00
in . data [ 0 ] = data ;
return sabi_command ( samsung , command , & in , NULL ) ;
2011-03-11 12:41:19 -05:00
}
2011-11-26 10:59:59 +01:00
static int read_brightness ( struct samsung_laptop * samsung )
2011-03-11 12:41:19 -05:00
{
2011-11-26 10:59:58 +01:00
const struct sabi_config * config = samsung - > config ;
const struct sabi_commands * commands = & samsung - > config - > commands ;
2011-11-26 11:00:02 +01:00
struct sabi_data sretval ;
2011-03-11 12:41:19 -05:00
int user_brightness = 0 ;
int retval ;
2011-11-26 11:00:02 +01:00
retval = sabi_command ( samsung , commands - > get_brightness ,
NULL , & sretval ) ;
if ( retval )
return retval ;
user_brightness = sretval . data [ 0 ] ;
if ( user_brightness > config - > min_brightness )
user_brightness - = config - > min_brightness ;
else
user_brightness = 0 ;
2011-03-11 12:41:19 -05:00
return user_brightness ;
}
2011-11-26 10:59:59 +01:00
static void set_brightness ( struct samsung_laptop * samsung , u8 user_brightness )
2011-03-11 12:41:19 -05:00
{
2011-11-26 10:59:58 +01:00
const struct sabi_config * config = samsung - > config ;
const struct sabi_commands * commands = & samsung - > config - > commands ;
u8 user_level = user_brightness + config - > min_brightness ;
2011-03-11 12:41:19 -05:00
2011-11-26 10:59:58 +01:00
if ( samsung - > has_stepping_quirk & & user_level ! = 0 ) {
2011-09-20 09:16:13 -07:00
/*
* short circuit if the specified level is what ' s already set
* to prevent the screen from flickering needlessly
*/
2011-11-26 10:59:59 +01:00
if ( user_brightness = = read_brightness ( samsung ) )
2011-09-20 09:16:13 -07:00
return ;
2011-11-26 11:00:02 +01:00
sabi_set_commandb ( samsung , commands - > set_brightness , 0 ) ;
2011-09-20 09:16:13 -07:00
}
2011-11-26 11:00:02 +01:00
sabi_set_commandb ( samsung , commands - > set_brightness , user_level ) ;
2011-03-11 12:41:19 -05:00
}
static int get_brightness ( struct backlight_device * bd )
{
2011-11-26 10:59:59 +01:00
struct samsung_laptop * samsung = bl_get_data ( bd ) ;
return read_brightness ( samsung ) ;
2011-03-11 12:41:19 -05:00
}
2011-11-26 10:59:59 +01:00
static void check_for_stepping_quirk ( struct samsung_laptop * samsung )
2011-09-20 09:16:13 -07:00
{
2011-11-26 10:59:59 +01:00
int initial_level ;
int check_level ;
int orig_level = read_brightness ( samsung ) ;
2011-09-20 09:16:13 -07:00
/*
* Some laptops exhibit the strange behaviour of stepping toward
* ( rather than setting ) the brightness except when changing to / from
* brightness level 0. This behaviour is checked for here and worked
* around in set_brightness .
*/
2011-10-13 06:42:01 -04:00
if ( orig_level = = 0 )
2011-11-26 10:59:59 +01:00
set_brightness ( samsung , 1 ) ;
2011-10-13 06:42:01 -04:00
2011-11-26 10:59:59 +01:00
initial_level = read_brightness ( samsung ) ;
2011-10-13 06:42:01 -04:00
2011-09-20 09:16:13 -07:00
if ( initial_level < = 2 )
check_level = initial_level + 2 ;
else
check_level = initial_level - 2 ;
2011-11-26 10:59:58 +01:00
samsung - > has_stepping_quirk = false ;
2011-11-26 10:59:59 +01:00
set_brightness ( samsung , check_level ) ;
2011-09-20 09:16:13 -07:00
2011-11-26 10:59:59 +01:00
if ( read_brightness ( samsung ) ! = check_level ) {
2011-11-26 10:59:58 +01:00
samsung - > has_stepping_quirk = true ;
2011-09-20 09:16:13 -07:00
pr_info ( " enabled workaround for brightness stepping quirk \n " ) ;
}
2011-11-26 10:59:59 +01:00
set_brightness ( samsung , orig_level ) ;
2011-09-20 09:16:13 -07:00
}
2011-03-11 12:41:19 -05:00
static int update_status ( struct backlight_device * bd )
{
2011-11-26 10:59:59 +01:00
struct samsung_laptop * samsung = bl_get_data ( bd ) ;
2011-11-26 10:59:58 +01:00
const struct sabi_commands * commands = & samsung - > config - > commands ;
2011-11-26 10:59:59 +01:00
set_brightness ( samsung , bd - > props . brightness ) ;
2011-03-11 12:41:19 -05:00
if ( bd - > props . power = = FB_BLANK_UNBLANK )
2011-11-26 11:00:02 +01:00
sabi_set_commandb ( samsung , commands - > set_backlight , 1 ) ;
2011-03-11 12:41:19 -05:00
else
2011-11-26 11:00:02 +01:00
sabi_set_commandb ( samsung , commands - > set_backlight , 0 ) ;
2011-11-26 10:59:59 +01:00
2011-03-11 12:41:19 -05:00
return 0 ;
}
static const struct backlight_ops backlight_ops = {
. get_brightness = get_brightness ,
. update_status = update_status ,
} ;
2011-11-26 11:00:09 +01:00
static int seclinux_rfkill_set ( void * data , bool blocked )
2011-03-11 12:41:19 -05:00
{
2011-12-15 08:27:39 +01:00
struct samsung_rfkill * srfkill = data ;
struct samsung_laptop * samsung = srfkill - > samsung ;
2011-11-26 10:59:58 +01:00
const struct sabi_commands * commands = & samsung - > config - > commands ;
2011-11-26 11:00:09 +01:00
return sabi_set_commandb ( samsung , commands - > set_wireless_button ,
! blocked ) ;
}
static struct rfkill_ops seclinux_rfkill_ops = {
. set_block = seclinux_rfkill_set ,
} ;
static int swsmi_wireless_status ( struct samsung_laptop * samsung ,
struct sabi_data * data )
{
const struct sabi_commands * commands = & samsung - > config - > commands ;
return sabi_command ( samsung , commands - > get_wireless_status ,
NULL , data ) ;
}
static int swsmi_rfkill_set ( void * priv , bool blocked )
{
struct samsung_rfkill * srfkill = priv ;
struct samsung_laptop * samsung = srfkill - > samsung ;
const struct sabi_commands * commands = & samsung - > config - > commands ;
struct sabi_data data ;
int ret , i ;
ret = swsmi_wireless_status ( samsung , & data ) ;
if ( ret )
return ret ;
/* Don't set the state for non-present devices */
for ( i = 0 ; i < 4 ; i + + )
if ( data . data [ i ] = = 0x02 )
data . data [ 1 ] = 0 ;
if ( srfkill - > type = = RFKILL_TYPE_WLAN )
data . data [ WL_STATUS_WLAN ] = ! blocked ;
else if ( srfkill - > type = = RFKILL_TYPE_BLUETOOTH )
data . data [ WL_STATUS_BT ] = ! blocked ;
return sabi_command ( samsung , commands - > set_wireless_status ,
& data , & data ) ;
}
static void swsmi_rfkill_query ( struct rfkill * rfkill , void * priv )
{
struct samsung_rfkill * srfkill = priv ;
struct samsung_laptop * samsung = srfkill - > samsung ;
struct sabi_data data ;
int ret ;
ret = swsmi_wireless_status ( samsung , & data ) ;
if ( ret )
return ;
if ( srfkill - > type = = RFKILL_TYPE_WLAN )
ret = data . data [ WL_STATUS_WLAN ] ;
else if ( srfkill - > type = = RFKILL_TYPE_BLUETOOTH )
ret = data . data [ WL_STATUS_BT ] ;
2011-03-11 12:41:19 -05:00
else
2011-11-26 11:00:09 +01:00
return ;
2011-03-11 12:41:19 -05:00
2011-11-26 11:00:09 +01:00
rfkill_set_sw_state ( rfkill , ! ret ) ;
2011-03-11 12:41:19 -05:00
}
2011-11-26 11:00:09 +01:00
static struct rfkill_ops swsmi_rfkill_ops = {
. set_block = swsmi_rfkill_set ,
. query = swsmi_rfkill_query ,
2011-03-11 12:41:19 -05:00
} ;
static ssize_t get_performance_level ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
2011-11-26 10:59:59 +01:00
struct samsung_laptop * samsung = dev_get_drvdata ( dev ) ;
2011-11-26 10:59:58 +01:00
const struct sabi_config * config = samsung - > config ;
2011-11-26 10:59:59 +01:00
const struct sabi_commands * commands = & config - > commands ;
2011-11-26 11:00:02 +01:00
struct sabi_data sretval ;
2011-03-11 12:41:19 -05:00
int retval ;
int i ;
/* Read the state */
2011-11-26 11:00:02 +01:00
retval = sabi_command ( samsung , commands - > get_performance_level ,
NULL , & sretval ) ;
2011-03-11 12:41:19 -05:00
if ( retval )
return retval ;
/* The logic is backwards, yeah, lots of fun... */
2011-11-26 10:59:58 +01:00
for ( i = 0 ; config - > performance_levels [ i ] . name ; + + i ) {
2011-11-26 11:00:02 +01:00
if ( sretval . data [ 0 ] = = config - > performance_levels [ i ] . value )
2011-11-26 10:59:58 +01:00
return sprintf ( buf , " %s \n " , config - > performance_levels [ i ] . name ) ;
2011-03-11 12:41:19 -05:00
}
return sprintf ( buf , " %s \n " , " unknown " ) ;
}
static ssize_t set_performance_level ( struct device * dev ,
struct device_attribute * attr , const char * buf ,
size_t count )
{
2011-11-26 10:59:59 +01:00
struct samsung_laptop * samsung = dev_get_drvdata ( dev ) ;
2011-11-26 10:59:58 +01:00
const struct sabi_config * config = samsung - > config ;
2011-11-26 10:59:59 +01:00
const struct sabi_commands * commands = & config - > commands ;
int i ;
2011-11-26 10:59:58 +01:00
2011-11-26 10:59:59 +01:00
if ( count < 1 )
return count ;
for ( i = 0 ; config - > performance_levels [ i ] . name ; + + i ) {
const struct sabi_performance_level * level =
& config - > performance_levels [ i ] ;
if ( ! strncasecmp ( level - > name , buf , strlen ( level - > name ) ) ) {
2011-11-26 11:00:02 +01:00
sabi_set_commandb ( samsung ,
commands - > set_performance_level ,
level - > value ) ;
2011-11-26 10:59:59 +01:00
break ;
2011-03-11 12:41:19 -05:00
}
}
2011-11-26 10:59:59 +01:00
if ( ! config - > performance_levels [ i ] . name )
return - EINVAL ;
2011-03-11 12:41:19 -05:00
return count ;
}
2011-11-26 10:59:59 +01:00
2011-03-11 12:41:19 -05:00
static DEVICE_ATTR ( performance_level , S_IWUSR | S_IRUGO ,
get_performance_level , set_performance_level ) ;
2011-11-26 11:00:05 +01:00
static int read_battery_life_extender ( struct samsung_laptop * samsung )
{
const struct sabi_commands * commands = & samsung - > config - > commands ;
struct sabi_data data ;
int retval ;
if ( commands - > get_battery_life_extender = = 0xFFFF )
return - ENODEV ;
memset ( & data , 0 , sizeof ( data ) ) ;
data . data [ 0 ] = 0x80 ;
retval = sabi_command ( samsung , commands - > get_battery_life_extender ,
& data , & data ) ;
if ( retval )
return retval ;
if ( data . data [ 0 ] ! = 0 & & data . data [ 0 ] ! = 1 )
return - ENODEV ;
return data . data [ 0 ] ;
}
static int write_battery_life_extender ( struct samsung_laptop * samsung ,
int enabled )
{
const struct sabi_commands * commands = & samsung - > config - > commands ;
struct sabi_data data ;
memset ( & data , 0 , sizeof ( data ) ) ;
data . data [ 0 ] = 0x80 | enabled ;
return sabi_command ( samsung , commands - > set_battery_life_extender ,
& data , NULL ) ;
}
static ssize_t get_battery_life_extender ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
struct samsung_laptop * samsung = dev_get_drvdata ( dev ) ;
int ret ;
ret = read_battery_life_extender ( samsung ) ;
if ( ret < 0 )
return ret ;
return sprintf ( buf , " %d \n " , ret ) ;
}
static ssize_t set_battery_life_extender ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
struct samsung_laptop * samsung = dev_get_drvdata ( dev ) ;
int ret , value ;
if ( ! count | | sscanf ( buf , " %i " , & value ) ! = 1 )
return - EINVAL ;
ret = write_battery_life_extender ( samsung , ! ! value ) ;
if ( ret < 0 )
return ret ;
return count ;
}
static DEVICE_ATTR ( battery_life_extender , S_IWUSR | S_IRUGO ,
get_battery_life_extender , set_battery_life_extender ) ;
2011-11-26 11:00:06 +01:00
static int read_usb_charge ( struct samsung_laptop * samsung )
{
const struct sabi_commands * commands = & samsung - > config - > commands ;
struct sabi_data data ;
int retval ;
if ( commands - > get_usb_charge = = 0xFFFF )
return - ENODEV ;
memset ( & data , 0 , sizeof ( data ) ) ;
data . data [ 0 ] = 0x80 ;
retval = sabi_command ( samsung , commands - > get_usb_charge ,
& data , & data ) ;
if ( retval )
return retval ;
if ( data . data [ 0 ] ! = 0 & & data . data [ 0 ] ! = 1 )
return - ENODEV ;
return data . data [ 0 ] ;
}
static int write_usb_charge ( struct samsung_laptop * samsung ,
int enabled )
{
const struct sabi_commands * commands = & samsung - > config - > commands ;
struct sabi_data data ;
memset ( & data , 0 , sizeof ( data ) ) ;
data . data [ 0 ] = 0x80 | enabled ;
return sabi_command ( samsung , commands - > set_usb_charge ,
& data , NULL ) ;
}
static ssize_t get_usb_charge ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
struct samsung_laptop * samsung = dev_get_drvdata ( dev ) ;
int ret ;
ret = read_usb_charge ( samsung ) ;
if ( ret < 0 )
return ret ;
return sprintf ( buf , " %d \n " , ret ) ;
}
static ssize_t set_usb_charge ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
struct samsung_laptop * samsung = dev_get_drvdata ( dev ) ;
int ret , value ;
if ( ! count | | sscanf ( buf , " %i " , & value ) ! = 1 )
return - EINVAL ;
ret = write_usb_charge ( samsung , ! ! value ) ;
if ( ret < 0 )
return ret ;
return count ;
}
static DEVICE_ATTR ( usb_charge , S_IWUSR | S_IRUGO ,
get_usb_charge , set_usb_charge ) ;
2011-11-26 11:00:01 +01:00
static struct attribute * platform_attributes [ ] = {
& dev_attr_performance_level . attr ,
2011-11-26 11:00:05 +01:00
& dev_attr_battery_life_extender . attr ,
2011-11-26 11:00:06 +01:00
& dev_attr_usb_charge . attr ,
2011-11-26 11:00:01 +01:00
NULL
} ;
2011-03-11 12:41:19 -05:00
2011-11-26 10:59:59 +01:00
static int find_signature ( void __iomem * memcheck , const char * testStr )
{
int i = 0 ;
int loca ;
for ( loca = 0 ; loca < 0xffff ; loca + + ) {
char temp = readb ( memcheck + loca ) ;
if ( temp = = testStr [ i ] ) {
if ( i = = strlen ( testStr ) - 1 )
break ;
+ + i ;
} else {
i = 0 ;
}
}
return loca ;
}
static void samsung_rfkill_exit ( struct samsung_laptop * samsung )
{
2011-11-26 11:00:09 +01:00
if ( samsung - > wlan . rfkill ) {
rfkill_unregister ( samsung - > wlan . rfkill ) ;
rfkill_destroy ( samsung - > wlan . rfkill ) ;
samsung - > wlan . rfkill = NULL ;
}
if ( samsung - > bluetooth . rfkill ) {
rfkill_unregister ( samsung - > bluetooth . rfkill ) ;
rfkill_destroy ( samsung - > bluetooth . rfkill ) ;
samsung - > bluetooth . rfkill = NULL ;
2011-11-26 10:59:59 +01:00
}
}
2011-11-26 11:00:09 +01:00
static int samsung_new_rfkill ( struct samsung_laptop * samsung ,
struct samsung_rfkill * arfkill ,
const char * name , enum rfkill_type type ,
const struct rfkill_ops * ops ,
int blocked )
2011-11-26 10:59:59 +01:00
{
2011-11-26 11:00:09 +01:00
struct rfkill * * rfkill = & arfkill - > rfkill ;
int ret ;
2011-11-26 10:59:59 +01:00
2011-11-26 11:00:09 +01:00
arfkill - > type = type ;
arfkill - > samsung = samsung ;
* rfkill = rfkill_alloc ( name , & samsung - > platform_device - > dev ,
type , ops , arfkill ) ;
if ( ! * rfkill )
return - EINVAL ;
if ( blocked ! = - 1 )
rfkill_init_sw_state ( * rfkill , blocked ) ;
ret = rfkill_register ( * rfkill ) ;
if ( ret ) {
rfkill_destroy ( * rfkill ) ;
* rfkill = NULL ;
return ret ;
2011-11-26 10:59:59 +01:00
}
2011-11-26 11:00:09 +01:00
return 0 ;
}
2011-11-26 10:59:59 +01:00
2011-11-26 11:00:09 +01:00
static int __init samsung_rfkill_init_seclinux ( struct samsung_laptop * samsung )
{
return samsung_new_rfkill ( samsung , & samsung - > wlan , " samsung-wlan " ,
RFKILL_TYPE_WLAN , & seclinux_rfkill_ops , - 1 ) ;
}
static int __init samsung_rfkill_init_swsmi ( struct samsung_laptop * samsung )
{
struct sabi_data data ;
int ret ;
ret = swsmi_wireless_status ( samsung , & data ) ;
2011-12-15 08:27:39 +01:00
if ( ret ) {
/* Some swsmi laptops use the old seclinux way to control
* wireless devices */
if ( ret = = - EINVAL )
ret = samsung_rfkill_init_seclinux ( samsung ) ;
2011-11-26 11:00:09 +01:00
return ret ;
2011-12-15 08:27:39 +01:00
}
2011-11-26 11:00:09 +01:00
/* 0x02 seems to mean that the device is no present/available */
if ( data . data [ WL_STATUS_WLAN ] ! = 0x02 )
ret = samsung_new_rfkill ( samsung , & samsung - > wlan ,
" samsung-wlan " ,
RFKILL_TYPE_WLAN ,
& swsmi_rfkill_ops ,
! data . data [ WL_STATUS_WLAN ] ) ;
if ( ret )
goto exit ;
if ( data . data [ WL_STATUS_BT ] ! = 0x02 )
ret = samsung_new_rfkill ( samsung , & samsung - > bluetooth ,
" samsung-bluetooth " ,
RFKILL_TYPE_BLUETOOTH ,
& swsmi_rfkill_ops ,
! data . data [ WL_STATUS_BT ] ) ;
if ( ret )
goto exit ;
exit :
if ( ret )
samsung_rfkill_exit ( samsung ) ;
return ret ;
}
static int __init samsung_rfkill_init ( struct samsung_laptop * samsung )
{
if ( samsung - > config - > sabi_version = = 2 )
return samsung_rfkill_init_seclinux ( samsung ) ;
if ( samsung - > config - > sabi_version = = 3 )
return samsung_rfkill_init_swsmi ( samsung ) ;
2011-11-26 10:59:59 +01:00
return 0 ;
}
2011-11-26 11:00:08 +01:00
static int kbd_backlight_enable ( struct samsung_laptop * samsung )
{
const struct sabi_commands * commands = & samsung - > config - > commands ;
struct sabi_data data ;
int retval ;
if ( commands - > kbd_backlight = = 0xFFFF )
return - ENODEV ;
memset ( & data , 0 , sizeof ( data ) ) ;
data . d0 = 0xaabb ;
retval = sabi_command ( samsung , commands - > kbd_backlight ,
& data , & data ) ;
if ( retval )
return retval ;
if ( data . d0 ! = 0xccdd )
return - ENODEV ;
return 0 ;
}
static int kbd_backlight_read ( struct samsung_laptop * samsung )
{
const struct sabi_commands * commands = & samsung - > config - > commands ;
struct sabi_data data ;
int retval ;
memset ( & data , 0 , sizeof ( data ) ) ;
data . data [ 0 ] = 0x81 ;
retval = sabi_command ( samsung , commands - > kbd_backlight ,
& data , & data ) ;
if ( retval )
return retval ;
return data . data [ 0 ] ;
}
static int kbd_backlight_write ( struct samsung_laptop * samsung , int brightness )
{
const struct sabi_commands * commands = & samsung - > config - > commands ;
struct sabi_data data ;
memset ( & data , 0 , sizeof ( data ) ) ;
data . d0 = 0x82 | ( ( brightness & 0xFF ) < < 8 ) ;
return sabi_command ( samsung , commands - > kbd_backlight ,
& data , NULL ) ;
}
static void kbd_led_update ( struct work_struct * work )
{
struct samsung_laptop * samsung ;
samsung = container_of ( work , struct samsung_laptop , kbd_led_work ) ;
kbd_backlight_write ( samsung , samsung - > kbd_led_wk ) ;
}
static void kbd_led_set ( struct led_classdev * led_cdev ,
enum led_brightness value )
{
struct samsung_laptop * samsung ;
samsung = container_of ( led_cdev , struct samsung_laptop , kbd_led ) ;
if ( value > samsung - > kbd_led . max_brightness )
value = samsung - > kbd_led . max_brightness ;
else if ( value < 0 )
value = 0 ;
samsung - > kbd_led_wk = value ;
queue_work ( samsung - > led_workqueue , & samsung - > kbd_led_work ) ;
}
static enum led_brightness kbd_led_get ( struct led_classdev * led_cdev )
{
struct samsung_laptop * samsung ;
samsung = container_of ( led_cdev , struct samsung_laptop , kbd_led ) ;
return kbd_backlight_read ( samsung ) ;
}
static void samsung_leds_exit ( struct samsung_laptop * samsung )
{
if ( ! IS_ERR_OR_NULL ( samsung - > kbd_led . dev ) )
led_classdev_unregister ( & samsung - > kbd_led ) ;
if ( samsung - > led_workqueue )
destroy_workqueue ( samsung - > led_workqueue ) ;
}
static int __init samsung_leds_init ( struct samsung_laptop * samsung )
{
int ret = 0 ;
samsung - > led_workqueue = create_singlethread_workqueue ( " led_workqueue " ) ;
if ( ! samsung - > led_workqueue )
return - ENOMEM ;
if ( kbd_backlight_enable ( samsung ) > = 0 ) {
INIT_WORK ( & samsung - > kbd_led_work , kbd_led_update ) ;
samsung - > kbd_led . name = " samsung::kbd_backlight " ;
samsung - > kbd_led . brightness_set = kbd_led_set ;
samsung - > kbd_led . brightness_get = kbd_led_get ;
samsung - > kbd_led . max_brightness = 8 ;
ret = led_classdev_register ( & samsung - > platform_device - > dev ,
& samsung - > kbd_led ) ;
}
if ( ret )
samsung_leds_exit ( samsung ) ;
return ret ;
}
2011-11-26 10:59:59 +01:00
static void samsung_backlight_exit ( struct samsung_laptop * samsung )
{
if ( samsung - > backlight_device ) {
backlight_device_unregister ( samsung - > backlight_device ) ;
samsung - > backlight_device = NULL ;
}
}
static int __init samsung_backlight_init ( struct samsung_laptop * samsung )
{
struct backlight_device * bd ;
struct backlight_properties props ;
2011-11-26 11:00:00 +01:00
if ( ! samsung - > handle_backlight )
return 0 ;
2011-11-26 10:59:59 +01:00
memset ( & props , 0 , sizeof ( struct backlight_properties ) ) ;
props . type = BACKLIGHT_PLATFORM ;
props . max_brightness = samsung - > config - > max_brightness -
samsung - > config - > min_brightness ;
bd = backlight_device_register ( " samsung " ,
& samsung - > platform_device - > dev ,
samsung , & backlight_ops ,
& props ) ;
if ( IS_ERR ( bd ) )
return PTR_ERR ( bd ) ;
samsung - > backlight_device = bd ;
samsung - > backlight_device - > props . brightness = read_brightness ( samsung ) ;
samsung - > backlight_device - > props . power = FB_BLANK_UNBLANK ;
backlight_update_status ( samsung - > backlight_device ) ;
return 0 ;
}
2012-03-20 09:53:07 +01:00
static umode_t samsung_sysfs_is_visible ( struct kobject * kobj ,
2011-11-26 11:00:01 +01:00
struct attribute * attr , int idx )
{
struct device * dev = container_of ( kobj , struct device , kobj ) ;
struct platform_device * pdev = to_platform_device ( dev ) ;
struct samsung_laptop * samsung = platform_get_drvdata ( pdev ) ;
bool ok = true ;
if ( attr = = & dev_attr_performance_level . attr )
ok = ! ! samsung - > config - > performance_levels [ 0 ] . name ;
2011-11-26 11:00:05 +01:00
if ( attr = = & dev_attr_battery_life_extender . attr )
ok = ! ! ( read_battery_life_extender ( samsung ) > = 0 ) ;
2011-11-26 11:00:06 +01:00
if ( attr = = & dev_attr_usb_charge . attr )
ok = ! ! ( read_usb_charge ( samsung ) > = 0 ) ;
2011-11-26 11:00:01 +01:00
return ok ? attr - > mode : 0 ;
}
static struct attribute_group platform_attribute_group = {
. is_visible = samsung_sysfs_is_visible ,
. attrs = platform_attributes
} ;
2011-11-26 10:59:59 +01:00
static void samsung_sysfs_exit ( struct samsung_laptop * samsung )
{
2011-11-26 11:00:01 +01:00
struct platform_device * device = samsung - > platform_device ;
sysfs_remove_group ( & device - > dev . kobj , & platform_attribute_group ) ;
2011-11-26 10:59:59 +01:00
}
static int __init samsung_sysfs_init ( struct samsung_laptop * samsung )
{
2011-11-26 11:00:01 +01:00
struct platform_device * device = samsung - > platform_device ;
return sysfs_create_group ( & device - > dev . kobj , & platform_attribute_group ) ;
2011-11-26 10:59:59 +01:00
}
2011-11-26 11:00:03 +01:00
static int show_call ( struct seq_file * m , void * data )
{
struct samsung_laptop * samsung = m - > private ;
struct sabi_data * sdata = & samsung - > debug . data ;
int ret ;
seq_printf ( m , " SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x} \n " ,
samsung - > debug . command ,
sdata - > d0 , sdata - > d1 , sdata - > d2 , sdata - > d3 ) ;
ret = sabi_command ( samsung , samsung - > debug . command , sdata , sdata ) ;
if ( ret ) {
seq_printf ( m , " SABI command 0x%04x failed \n " ,
samsung - > debug . command ) ;
return ret ;
}
seq_printf ( m , " SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x} \n " ,
sdata - > d0 , sdata - > d1 , sdata - > d2 , sdata - > d3 ) ;
return 0 ;
}
static int samsung_debugfs_open ( struct inode * inode , struct file * file )
{
return single_open ( file , show_call , inode - > i_private ) ;
}
static const struct file_operations samsung_laptop_call_io_ops = {
. owner = THIS_MODULE ,
. open = samsung_debugfs_open ,
. read = seq_read ,
. llseek = seq_lseek ,
. release = single_release ,
} ;
static void samsung_debugfs_exit ( struct samsung_laptop * samsung )
{
debugfs_remove_recursive ( samsung - > debug . root ) ;
}
static int samsung_debugfs_init ( struct samsung_laptop * samsung )
{
struct dentry * dent ;
samsung - > debug . root = debugfs_create_dir ( " samsung-laptop " , NULL ) ;
if ( ! samsung - > debug . root ) {
pr_err ( " failed to create debugfs directory " ) ;
goto error_debugfs ;
}
samsung - > debug . f0000_wrapper . data = samsung - > f0000_segment ;
samsung - > debug . f0000_wrapper . size = 0xffff ;
samsung - > debug . data_wrapper . data = & samsung - > debug . data ;
samsung - > debug . data_wrapper . size = sizeof ( samsung - > debug . data ) ;
2011-11-26 11:00:11 +01:00
samsung - > debug . sdiag_wrapper . data = samsung - > sdiag ;
samsung - > debug . sdiag_wrapper . size = strlen ( samsung - > sdiag ) ;
2011-11-26 11:00:03 +01:00
dent = debugfs_create_u16 ( " command " , S_IRUGO | S_IWUSR ,
samsung - > debug . root , & samsung - > debug . command ) ;
if ( ! dent )
goto error_debugfs ;
dent = debugfs_create_u32 ( " d0 " , S_IRUGO | S_IWUSR , samsung - > debug . root ,
& samsung - > debug . data . d0 ) ;
if ( ! dent )
goto error_debugfs ;
dent = debugfs_create_u32 ( " d1 " , S_IRUGO | S_IWUSR , samsung - > debug . root ,
& samsung - > debug . data . d1 ) ;
if ( ! dent )
goto error_debugfs ;
dent = debugfs_create_u16 ( " d2 " , S_IRUGO | S_IWUSR , samsung - > debug . root ,
& samsung - > debug . data . d2 ) ;
if ( ! dent )
goto error_debugfs ;
dent = debugfs_create_u8 ( " d3 " , S_IRUGO | S_IWUSR , samsung - > debug . root ,
& samsung - > debug . data . d3 ) ;
if ( ! dent )
goto error_debugfs ;
dent = debugfs_create_blob ( " data " , S_IRUGO | S_IWUSR ,
samsung - > debug . root ,
& samsung - > debug . data_wrapper ) ;
if ( ! dent )
goto error_debugfs ;
dent = debugfs_create_blob ( " f0000_segment " , S_IRUSR | S_IWUSR ,
samsung - > debug . root ,
& samsung - > debug . f0000_wrapper ) ;
if ( ! dent )
goto error_debugfs ;
dent = debugfs_create_file ( " call " , S_IFREG | S_IRUGO ,
samsung - > debug . root , samsung ,
& samsung_laptop_call_io_ops ) ;
if ( ! dent )
goto error_debugfs ;
2011-11-26 11:00:11 +01:00
dent = debugfs_create_blob ( " sdiag " , S_IRUGO | S_IWUSR ,
samsung - > debug . root ,
& samsung - > debug . sdiag_wrapper ) ;
if ( ! dent )
goto error_debugfs ;
2011-11-26 11:00:03 +01:00
return 0 ;
error_debugfs :
samsung_debugfs_exit ( samsung ) ;
return - ENOMEM ;
}
2011-11-26 10:59:59 +01:00
static void samsung_sabi_exit ( struct samsung_laptop * samsung )
{
const struct sabi_config * config = samsung - > config ;
/* Turn off "Linux" mode in the BIOS */
if ( config & & config - > commands . set_linux ! = 0xff )
2011-11-26 11:00:02 +01:00
sabi_set_commandb ( samsung , config - > commands . set_linux , 0x80 ) ;
2011-11-26 10:59:59 +01:00
if ( samsung - > sabi_iface ) {
iounmap ( samsung - > sabi_iface ) ;
samsung - > sabi_iface = NULL ;
}
if ( samsung - > f0000_segment ) {
iounmap ( samsung - > f0000_segment ) ;
samsung - > f0000_segment = NULL ;
}
samsung - > config = NULL ;
}
2011-11-26 11:00:04 +01:00
static __init void samsung_sabi_infos ( struct samsung_laptop * samsung , int loca ,
unsigned int ifaceP )
2011-11-26 10:59:59 +01:00
{
const struct sabi_config * config = samsung - > config ;
printk ( KERN_DEBUG " This computer supports SABI==%x \n " ,
loca + 0xf0000 - 6 ) ;
2011-11-26 11:00:04 +01:00
2011-11-26 10:59:59 +01:00
printk ( KERN_DEBUG " SABI header: \n " ) ;
printk ( KERN_DEBUG " SMI Port Number = 0x%04x \n " ,
readw ( samsung - > sabi + config - > header_offsets . port ) ) ;
printk ( KERN_DEBUG " SMI Interface Function = 0x%02x \n " ,
readb ( samsung - > sabi + config - > header_offsets . iface_func ) ) ;
printk ( KERN_DEBUG " SMI enable memory buffer = 0x%02x \n " ,
readb ( samsung - > sabi + config - > header_offsets . en_mem ) ) ;
printk ( KERN_DEBUG " SMI restore memory buffer = 0x%02x \n " ,
readb ( samsung - > sabi + config - > header_offsets . re_mem ) ) ;
printk ( KERN_DEBUG " SABI data offset = 0x%04x \n " ,
readw ( samsung - > sabi + config - > header_offsets . data_offset ) ) ;
printk ( KERN_DEBUG " SABI data segment = 0x%04x \n " ,
readw ( samsung - > sabi + config - > header_offsets . data_segment ) ) ;
2011-11-26 11:00:04 +01:00
printk ( KERN_DEBUG " SABI pointer = 0x%08x \n " , ifaceP ) ;
2011-11-26 10:59:59 +01:00
}
2011-11-26 11:00:11 +01:00
static void __init samsung_sabi_diag ( struct samsung_laptop * samsung )
{
int loca = find_signature ( samsung - > f0000_segment , " SDiaG@ " ) ;
int i ;
if ( loca = = 0xffff )
return ;
/* Example:
* Ident : @ SDiaG @ 686 XX - N90X3A / 966 - SEC - 07 HL - S90X3A
*
* Product name : 90 X3A
* BIOS Version : 07 HL
*/
loca + = 1 ;
for ( i = 0 ; loca < 0xffff & & i < sizeof ( samsung - > sdiag ) - 1 ; loca + + ) {
char temp = readb ( samsung - > f0000_segment + loca ) ;
if ( isalnum ( temp ) | | temp = = ' / ' | | temp = = ' - ' )
samsung - > sdiag [ i + + ] = temp ;
else
break ;
}
if ( debug & & samsung - > sdiag [ 0 ] )
pr_info ( " sdiag: %s " , samsung - > sdiag ) ;
}
2011-11-26 10:59:59 +01:00
static int __init samsung_sabi_init ( struct samsung_laptop * samsung )
{
const struct sabi_config * config = NULL ;
const struct sabi_commands * commands ;
unsigned int ifaceP ;
int ret = 0 ;
int i ;
int loca ;
samsung - > f0000_segment = ioremap_nocache ( 0xf0000 , 0xffff ) ;
if ( ! samsung - > f0000_segment ) {
2011-11-26 11:00:10 +01:00
if ( debug | | force )
pr_err ( " Can't map the segment at 0xf0000 \n " ) ;
2011-11-26 10:59:59 +01:00
ret = - EINVAL ;
goto exit ;
}
2011-11-26 11:00:11 +01:00
samsung_sabi_diag ( samsung ) ;
2011-11-26 10:59:59 +01:00
/* Try to find one of the signatures in memory to find the header */
for ( i = 0 ; sabi_configs [ i ] . test_string ! = 0 ; + + i ) {
samsung - > config = & sabi_configs [ i ] ;
loca = find_signature ( samsung - > f0000_segment ,
samsung - > config - > test_string ) ;
if ( loca ! = 0xffff )
break ;
}
if ( loca = = 0xffff ) {
2011-11-26 11:00:10 +01:00
if ( debug | | force )
pr_err ( " This computer does not support SABI \n " ) ;
2011-11-26 10:59:59 +01:00
ret = - ENODEV ;
goto exit ;
}
config = samsung - > config ;
commands = & config - > commands ;
/* point to the SMI port Number */
loca + = 1 ;
samsung - > sabi = ( samsung - > f0000_segment + loca ) ;
/* Get a pointer to the SABI Interface */
ifaceP = ( readw ( samsung - > sabi + config - > header_offsets . data_segment ) & 0x0ffff ) < < 4 ;
ifaceP + = readw ( samsung - > sabi + config - > header_offsets . data_offset ) & 0x0ffff ;
2011-11-26 11:00:04 +01:00
if ( debug )
samsung_sabi_infos ( samsung , loca , ifaceP ) ;
2011-11-26 10:59:59 +01:00
samsung - > sabi_iface = ioremap_nocache ( ifaceP , 16 ) ;
if ( ! samsung - > sabi_iface ) {
pr_err ( " Can't remap %x \n " , ifaceP ) ;
ret = - EINVAL ;
goto exit ;
}
/* Turn on "Linux" mode in the BIOS */
if ( commands - > set_linux ! = 0xff ) {
2011-11-26 11:00:02 +01:00
int retval = sabi_set_commandb ( samsung ,
commands - > set_linux , 0x81 ) ;
2011-11-26 10:59:59 +01:00
if ( retval ) {
pr_warn ( " Linux mode was not set! \n " ) ;
ret = - ENODEV ;
goto exit ;
}
}
/* Check for stepping quirk */
2011-11-26 11:00:00 +01:00
if ( samsung - > handle_backlight )
check_for_stepping_quirk ( samsung ) ;
2011-11-26 10:59:59 +01:00
2011-11-26 11:00:12 +01:00
pr_info ( " detected SABI interface: %s \n " ,
samsung - > config - > test_string ) ;
2011-11-26 10:59:59 +01:00
exit :
if ( ret )
samsung_sabi_exit ( samsung ) ;
return ret ;
}
static void samsung_platform_exit ( struct samsung_laptop * samsung )
{
if ( samsung - > platform_device ) {
platform_device_unregister ( samsung - > platform_device ) ;
samsung - > platform_device = NULL ;
}
}
static int __init samsung_platform_init ( struct samsung_laptop * samsung )
{
struct platform_device * pdev ;
pdev = platform_device_register_simple ( " samsung " , - 1 , NULL , 0 ) ;
if ( IS_ERR ( pdev ) )
return PTR_ERR ( pdev ) ;
samsung - > platform_device = pdev ;
platform_set_drvdata ( samsung - > platform_device , samsung ) ;
return 0 ;
}
2011-03-11 12:41:19 -05:00
static struct dmi_system_id __initdata samsung_dmi_table [ ] = {
{
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR ,
" SAMSUNG ELECTRONICS CO., LTD. " ) ,
2011-11-26 11:00:10 +01:00
DMI_MATCH ( DMI_CHASSIS_TYPE , " 8 " ) , /* Portable */
2011-03-11 12:41:19 -05:00
} ,
} ,
{
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR ,
" SAMSUNG ELECTRONICS CO., LTD. " ) ,
2011-11-26 11:00:10 +01:00
DMI_MATCH ( DMI_CHASSIS_TYPE , " 9 " ) , /* Laptop */
2011-03-11 12:41:19 -05:00
} ,
} ,
2011-07-20 22:57:44 +02:00
{
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR ,
" SAMSUNG ELECTRONICS CO., LTD. " ) ,
2011-11-26 11:00:10 +01:00
DMI_MATCH ( DMI_CHASSIS_TYPE , " 10 " ) , /* Notebook */
2011-07-20 22:57:44 +02:00
} ,
} ,
2011-09-20 09:16:15 -07:00
{
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR ,
" SAMSUNG ELECTRONICS CO., LTD. " ) ,
2011-11-26 11:00:10 +01:00
DMI_MATCH ( DMI_CHASSIS_TYPE , " 14 " ) , /* Sub-Notebook */
2011-09-20 09:16:15 -07:00
} ,
2011-09-20 09:16:14 -07:00
} ,
2011-03-11 12:41:19 -05:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( dmi , samsung_dmi_table ) ;
2011-11-26 10:59:59 +01:00
static struct platform_device * samsung_platform_device ;
2011-03-11 12:41:19 -05:00
static int __init samsung_init ( void )
{
2011-11-26 10:59:59 +01:00
struct samsung_laptop * samsung ;
int ret ;
2011-03-11 12:41:19 -05:00
if ( ! force & & ! dmi_check_system ( samsung_dmi_table ) )
return - ENODEV ;
2011-11-26 10:59:58 +01:00
samsung = kzalloc ( sizeof ( * samsung ) , GFP_KERNEL ) ;
if ( ! samsung )
return - ENOMEM ;
mutex_init ( & samsung - > sabi_mutex ) ;
2011-11-26 11:00:00 +01:00
samsung - > handle_backlight = true ;
# ifdef CONFIG_ACPI
/* Don't handle backlight here if the acpi video already handle it */
2011-11-26 11:00:10 +01:00
if ( acpi_video_backlight_support ( ) )
2011-11-26 11:00:00 +01:00
samsung - > handle_backlight = false ;
# endif
2011-11-26 10:59:59 +01:00
ret = samsung_platform_init ( samsung ) ;
if ( ret )
goto error_platform ;
ret = samsung_sabi_init ( samsung ) ;
if ( ret )
goto error_sabi ;
2011-11-26 11:00:10 +01:00
# ifdef CONFIG_ACPI
/* Only log that if we are really on a sabi platform */
if ( acpi_video_backlight_support ( ) )
pr_info ( " Backlight controlled by ACPI video driver \n " ) ;
# endif
2011-11-26 10:59:59 +01:00
ret = samsung_sysfs_init ( samsung ) ;
if ( ret )
goto error_sysfs ;
ret = samsung_backlight_init ( samsung ) ;
if ( ret )
goto error_backlight ;
ret = samsung_rfkill_init ( samsung ) ;
if ( ret )
goto error_rfkill ;
2011-11-26 11:00:08 +01:00
ret = samsung_leds_init ( samsung ) ;
if ( ret )
goto error_leds ;
2011-11-26 11:00:03 +01:00
ret = samsung_debugfs_init ( samsung ) ;
if ( ret )
goto error_debugfs ;
2011-11-26 10:59:59 +01:00
samsung_platform_device = samsung - > platform_device ;
return ret ;
2011-11-26 11:00:03 +01:00
error_debugfs :
2011-11-26 11:00:08 +01:00
samsung_leds_exit ( samsung ) ;
error_leds :
2011-11-26 11:00:03 +01:00
samsung_rfkill_exit ( samsung ) ;
2011-11-26 10:59:59 +01:00
error_rfkill :
samsung_backlight_exit ( samsung ) ;
error_backlight :
samsung_sysfs_exit ( samsung ) ;
error_sysfs :
samsung_sabi_exit ( samsung ) ;
error_sabi :
samsung_platform_exit ( samsung ) ;
error_platform :
2011-11-26 10:59:58 +01:00
kfree ( samsung ) ;
2011-11-26 10:59:59 +01:00
return ret ;
2011-03-11 12:41:19 -05:00
}
static void __exit samsung_exit ( void )
{
2011-11-26 10:59:59 +01:00
struct samsung_laptop * samsung ;
samsung = platform_get_drvdata ( samsung_platform_device ) ;
2011-11-26 11:00:03 +01:00
samsung_debugfs_exit ( samsung ) ;
2011-11-26 11:00:08 +01:00
samsung_leds_exit ( samsung ) ;
2011-11-26 10:59:59 +01:00
samsung_rfkill_exit ( samsung ) ;
samsung_backlight_exit ( samsung ) ;
samsung_sysfs_exit ( samsung ) ;
samsung_sabi_exit ( samsung ) ;
samsung_platform_exit ( samsung ) ;
2011-11-26 10:59:58 +01:00
kfree ( samsung ) ;
2011-11-26 10:59:59 +01:00
samsung_platform_device = NULL ;
2011-03-11 12:41:19 -05:00
}
module_init ( samsung_init ) ;
module_exit ( samsung_exit ) ;
MODULE_AUTHOR ( " Greg Kroah-Hartman <gregkh@suse.de> " ) ;
MODULE_DESCRIPTION ( " Samsung Backlight driver " ) ;
MODULE_LICENSE ( " GPL " ) ;