2014-04-04 22:15:42 +04:00
/*
* Alienware AlienFX control
*
* Copyright ( C ) 2014 Dell Inc < mario_limonciello @ dell . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/acpi.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/dmi.h>
# include <linux/acpi.h>
# include <linux/leds.h>
# define LEGACY_CONTROL_GUID "A90597CE-A997-11DA-B012-B622A1EF5492"
# define LEGACY_POWER_CONTROL_GUID "A80593CE-A997-11DA-B012-B622A1EF5492"
# define WMAX_CONTROL_GUID "A70591CE-A997-11DA-B012-B622A1EF5492"
# define WMAX_METHOD_HDMI_SOURCE 0x1
# define WMAX_METHOD_HDMI_STATUS 0x2
# define WMAX_METHOD_BRIGHTNESS 0x3
# define WMAX_METHOD_ZONE_CONTROL 0x4
2014-05-08 00:08:10 +04:00
# define WMAX_METHOD_HDMI_CABLE 0x5
2016-02-03 00:38:54 +03:00
# define WMAX_METHOD_AMPLIFIER_CABLE 0x6
2016-02-03 00:38:55 +03:00
# define WMAX_METHOD_DEEP_SLEEP_CONTROL 0x0B
# define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C
2014-04-04 22:15:42 +04:00
MODULE_AUTHOR ( " Mario Limonciello <mario_limonciello@dell.com> " ) ;
MODULE_DESCRIPTION ( " Alienware special feature control " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " wmi: " LEGACY_CONTROL_GUID ) ;
MODULE_ALIAS ( " wmi: " WMAX_CONTROL_GUID ) ;
enum INTERFACE_FLAGS {
LEGACY ,
WMAX ,
} ;
enum LEGACY_CONTROL_STATES {
LEGACY_RUNNING = 1 ,
LEGACY_BOOTING = 0 ,
LEGACY_SUSPEND = 3 ,
} ;
enum WMAX_CONTROL_STATES {
WMAX_RUNNING = 0xFF ,
WMAX_BOOTING = 0 ,
WMAX_SUSPEND = 3 ,
} ;
struct quirk_entry {
u8 num_zones ;
2014-07-24 08:19:23 +04:00
u8 hdmi_mux ;
2016-02-03 00:38:54 +03:00
u8 amplifier ;
2016-02-03 00:38:55 +03:00
u8 deepslp ;
2014-04-04 22:15:42 +04:00
} ;
static struct quirk_entry * quirks ;
static struct quirk_entry quirk_unknown = {
. num_zones = 2 ,
2014-07-24 08:19:23 +04:00
. hdmi_mux = 0 ,
2016-02-03 00:38:54 +03:00
. amplifier = 0 ,
2016-02-03 00:38:55 +03:00
. deepslp = 0 ,
2014-04-04 22:15:42 +04:00
} ;
2016-02-03 00:38:53 +03:00
static struct quirk_entry quirk_x51_r1_r2 = {
2014-04-04 22:15:42 +04:00
. num_zones = 3 ,
2016-02-03 00:38:54 +03:00
. hdmi_mux = 0 ,
. amplifier = 0 ,
2016-02-03 00:38:55 +03:00
. deepslp = 0 ,
2014-07-24 08:19:23 +04:00
} ;
2016-02-03 00:38:53 +03:00
static struct quirk_entry quirk_x51_r3 = {
. num_zones = 4 ,
. hdmi_mux = 0 ,
2016-02-03 00:38:54 +03:00
. amplifier = 1 ,
2016-02-03 00:38:55 +03:00
. deepslp = 0 ,
2016-02-03 00:38:53 +03:00
} ;
2014-07-24 08:19:23 +04:00
static struct quirk_entry quirk_asm100 = {
. num_zones = 2 ,
. hdmi_mux = 1 ,
2016-02-03 00:38:54 +03:00
. amplifier = 0 ,
2016-02-03 00:38:55 +03:00
. deepslp = 0 ,
2014-04-04 22:15:42 +04:00
} ;
2016-02-03 00:38:56 +03:00
static struct quirk_entry quirk_asm200 = {
. num_zones = 2 ,
. hdmi_mux = 1 ,
. amplifier = 0 ,
. deepslp = 1 ,
} ;
static struct quirk_entry quirk_asm201 = {
. num_zones = 2 ,
. hdmi_mux = 1 ,
. amplifier = 1 ,
. deepslp = 1 ,
} ;
2014-07-16 21:43:07 +04:00
static int __init dmi_matched ( const struct dmi_system_id * dmi )
2014-04-04 22:15:42 +04:00
{
quirks = dmi - > driver_data ;
return 1 ;
}
2014-07-16 21:43:07 +04:00
static const struct dmi_system_id alienware_quirks [ ] __initconst = {
2014-04-04 22:15:42 +04:00
{
. callback = dmi_matched ,
2016-02-03 00:38:53 +03:00
. ident = " Alienware X51 R3 " ,
2014-04-04 22:15:42 +04:00
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Alienware " ) ,
2016-02-03 00:38:53 +03:00
DMI_MATCH ( DMI_PRODUCT_NAME , " Alienware X51 R3 " ) ,
2014-04-04 22:15:42 +04:00
} ,
2016-02-03 00:38:53 +03:00
. driver_data = & quirk_x51_r3 ,
2014-04-04 22:15:42 +04:00
} ,
{
. callback = dmi_matched ,
. ident = " Alienware X51 R2 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Alienware " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Alienware X51 R2 " ) ,
} ,
2016-02-03 00:38:53 +03:00
. driver_data = & quirk_x51_r1_r2 ,
} ,
{
. callback = dmi_matched ,
. ident = " Alienware X51 R1 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Alienware " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Alienware X51 " ) ,
} ,
. driver_data = & quirk_x51_r1_r2 ,
2014-04-04 22:15:42 +04:00
} ,
2014-07-24 08:19:23 +04:00
{
2016-02-03 00:38:52 +03:00
. callback = dmi_matched ,
. ident = " Alienware ASM100 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Alienware " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " ASM100 " ) ,
} ,
. driver_data = & quirk_asm100 ,
} ,
2016-02-03 00:38:56 +03:00
{
. callback = dmi_matched ,
. ident = " Alienware ASM200 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Alienware " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " ASM200 " ) ,
} ,
. driver_data = & quirk_asm200 ,
} ,
{
. callback = dmi_matched ,
. ident = " Alienware ASM201 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Alienware " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " ASM201 " ) ,
} ,
. driver_data = & quirk_asm201 ,
} ,
2014-04-04 22:15:42 +04:00
{ }
} ;
struct color_platform {
u8 blue ;
u8 green ;
u8 red ;
} __packed ;
struct platform_zone {
u8 location ;
struct device_attribute * attr ;
struct color_platform colors ;
} ;
struct wmax_brightness_args {
u32 led_mask ;
u32 percentage ;
} ;
2016-02-03 00:38:54 +03:00
struct wmax_basic_args {
2014-04-04 22:15:42 +04:00
u8 arg ;
} ;
struct legacy_led_args {
struct color_platform colors ;
u8 brightness ;
u8 state ;
} __packed ;
struct wmax_led_args {
u32 led_mask ;
struct color_platform colors ;
u8 state ;
} __packed ;
static struct platform_device * platform_device ;
static struct device_attribute * zone_dev_attrs ;
static struct attribute * * zone_attrs ;
static struct platform_zone * zone_data ;
static struct platform_driver platform_driver = {
. driver = {
. name = " alienware-wmi " ,
}
} ;
static struct attribute_group zone_attribute_group = {
. name = " rgb_zones " ,
} ;
static u8 interface ;
static u8 lighting_control_state ;
static u8 global_brightness ;
/*
* Helpers used for zone control
2016-02-03 00:38:57 +03:00
*/
2014-04-04 22:15:42 +04:00
static int parse_rgb ( const char * buf , struct platform_zone * zone )
{
long unsigned int rgb ;
int ret ;
union color_union {
struct color_platform cp ;
int package ;
} repackager ;
ret = kstrtoul ( buf , 16 , & rgb ) ;
if ( ret )
return ret ;
/* RGB triplet notation is 24-bit hexadecimal */
if ( rgb > 0xFFFFFF )
return - EINVAL ;
repackager . package = rgb & 0x0f0f0f0f ;
pr_debug ( " alienware-wmi: r: %d g:%d b: %d \n " ,
repackager . cp . red , repackager . cp . green , repackager . cp . blue ) ;
zone - > colors = repackager . cp ;
return 0 ;
}
static struct platform_zone * match_zone ( struct device_attribute * attr )
{
int i ;
for ( i = 0 ; i < quirks - > num_zones ; i + + ) {
if ( ( struct device_attribute * ) zone_data [ i ] . attr = = attr ) {
pr_debug ( " alienware-wmi: matched zone location: %d \n " ,
zone_data [ i ] . location ) ;
return & zone_data [ i ] ;
}
}
return NULL ;
}
/*
* Individual RGB zone control
2016-02-03 00:38:57 +03:00
*/
2014-04-04 22:15:42 +04:00
static int alienware_update_led ( struct platform_zone * zone )
{
int method_id ;
acpi_status status ;
char * guid ;
struct acpi_buffer input ;
struct legacy_led_args legacy_args ;
2016-02-03 00:38:54 +03:00
struct wmax_led_args wmax_basic_args ;
2014-04-04 22:15:42 +04:00
if ( interface = = WMAX ) {
2016-02-03 00:38:54 +03:00
wmax_basic_args . led_mask = 1 < < zone - > location ;
wmax_basic_args . colors = zone - > colors ;
wmax_basic_args . state = lighting_control_state ;
2014-04-04 22:15:42 +04:00
guid = WMAX_CONTROL_GUID ;
method_id = WMAX_METHOD_ZONE_CONTROL ;
2016-02-03 00:38:54 +03:00
input . length = ( acpi_size ) sizeof ( wmax_basic_args ) ;
input . pointer = & wmax_basic_args ;
2014-04-04 22:15:42 +04:00
} else {
legacy_args . colors = zone - > colors ;
legacy_args . brightness = global_brightness ;
legacy_args . state = 0 ;
if ( lighting_control_state = = LEGACY_BOOTING | |
lighting_control_state = = LEGACY_SUSPEND ) {
guid = LEGACY_POWER_CONTROL_GUID ;
legacy_args . state = lighting_control_state ;
} else
guid = LEGACY_CONTROL_GUID ;
method_id = zone - > location + 1 ;
input . length = ( acpi_size ) sizeof ( legacy_args ) ;
input . pointer = & legacy_args ;
}
pr_debug ( " alienware-wmi: guid %s method %d \n " , guid , method_id ) ;
status = wmi_evaluate_method ( guid , 1 , method_id , & input , NULL ) ;
if ( ACPI_FAILURE ( status ) )
pr_err ( " alienware-wmi: zone set failure: %u \n " , status ) ;
return ACPI_FAILURE ( status ) ;
}
static ssize_t zone_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
struct platform_zone * target_zone ;
target_zone = match_zone ( attr ) ;
if ( target_zone = = NULL )
return sprintf ( buf , " red: -1, green: -1, blue: -1 \n " ) ;
return sprintf ( buf , " red: %d, green: %d, blue: %d \n " ,
target_zone - > colors . red ,
target_zone - > colors . green , target_zone - > colors . blue ) ;
}
static ssize_t zone_set ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
struct platform_zone * target_zone ;
int ret ;
target_zone = match_zone ( attr ) ;
if ( target_zone = = NULL ) {
pr_err ( " alienware-wmi: invalid target zone \n " ) ;
return 1 ;
}
ret = parse_rgb ( buf , target_zone ) ;
if ( ret )
return ret ;
ret = alienware_update_led ( target_zone ) ;
return ret ? ret : count ;
}
/*
* LED Brightness ( Global )
2016-02-03 00:38:57 +03:00
*/
2014-04-04 22:15:42 +04:00
static int wmax_brightness ( int brightness )
{
acpi_status status ;
struct acpi_buffer input ;
struct wmax_brightness_args args = {
. led_mask = 0xFF ,
. percentage = brightness ,
} ;
input . length = ( acpi_size ) sizeof ( args ) ;
input . pointer = & args ;
status = wmi_evaluate_method ( WMAX_CONTROL_GUID , 1 ,
WMAX_METHOD_BRIGHTNESS , & input , NULL ) ;
if ( ACPI_FAILURE ( status ) )
pr_err ( " alienware-wmi: brightness set failure: %u \n " , status ) ;
return ACPI_FAILURE ( status ) ;
}
static void global_led_set ( struct led_classdev * led_cdev ,
enum led_brightness brightness )
{
int ret ;
global_brightness = brightness ;
if ( interface = = WMAX )
ret = wmax_brightness ( brightness ) ;
else
ret = alienware_update_led ( & zone_data [ 0 ] ) ;
if ( ret )
pr_err ( " LED brightness update failed \n " ) ;
}
static enum led_brightness global_led_get ( struct led_classdev * led_cdev )
{
return global_brightness ;
}
static struct led_classdev global_led = {
. brightness_set = global_led_set ,
. brightness_get = global_led_get ,
. name = " alienware::global_brightness " ,
} ;
/*
* Lighting control state device attribute ( Global )
2016-02-03 00:38:57 +03:00
*/
2014-04-04 22:15:42 +04:00
static ssize_t show_control_state ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
if ( lighting_control_state = = LEGACY_BOOTING )
return scnprintf ( buf , PAGE_SIZE , " [booting] running suspend \n " ) ;
else if ( lighting_control_state = = LEGACY_SUSPEND )
return scnprintf ( buf , PAGE_SIZE , " booting running [suspend] \n " ) ;
return scnprintf ( buf , PAGE_SIZE , " booting [running] suspend \n " ) ;
}
static ssize_t store_control_state ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
long unsigned int val ;
if ( strcmp ( buf , " booting \n " ) = = 0 )
val = LEGACY_BOOTING ;
else if ( strcmp ( buf , " suspend \n " ) = = 0 )
val = LEGACY_SUSPEND ;
else if ( interface = = LEGACY )
val = LEGACY_RUNNING ;
else
val = WMAX_RUNNING ;
lighting_control_state = val ;
pr_debug ( " alienware-wmi: updated control state to %d \n " ,
lighting_control_state ) ;
return count ;
}
static DEVICE_ATTR ( lighting_control_state , 0644 , show_control_state ,
store_control_state ) ;
static int alienware_zone_init ( struct platform_device * dev )
{
int i ;
char buffer [ 10 ] ;
char * name ;
if ( interface = = WMAX ) {
lighting_control_state = WMAX_RUNNING ;
} else if ( interface = = LEGACY ) {
lighting_control_state = LEGACY_RUNNING ;
}
2014-05-08 00:08:09 +04:00
global_led . max_brightness = 0x0F ;
2014-04-04 22:15:42 +04:00
global_brightness = global_led . max_brightness ;
/*
* - zone_dev_attrs num_zones + 1 is for individual zones and then
* null terminated
* - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs +
* the lighting control + null terminated
* - zone_data num_zones is for the distinct zones
*/
zone_dev_attrs =
kzalloc ( sizeof ( struct device_attribute ) * ( quirks - > num_zones + 1 ) ,
GFP_KERNEL ) ;
2014-04-04 23:40:20 +04:00
if ( ! zone_dev_attrs )
return - ENOMEM ;
2014-04-04 22:15:42 +04:00
zone_attrs =
kzalloc ( sizeof ( struct attribute * ) * ( quirks - > num_zones + 2 ) ,
GFP_KERNEL ) ;
2014-04-04 23:40:20 +04:00
if ( ! zone_attrs )
return - ENOMEM ;
2014-04-04 22:15:42 +04:00
zone_data =
kzalloc ( sizeof ( struct platform_zone ) * ( quirks - > num_zones ) ,
GFP_KERNEL ) ;
2014-04-04 23:40:20 +04:00
if ( ! zone_data )
return - ENOMEM ;
2014-04-04 22:15:42 +04:00
for ( i = 0 ; i < quirks - > num_zones ; i + + ) {
sprintf ( buffer , " zone%02X " , i ) ;
name = kstrdup ( buffer , GFP_KERNEL ) ;
if ( name = = NULL )
return 1 ;
sysfs_attr_init ( & zone_dev_attrs [ i ] . attr ) ;
zone_dev_attrs [ i ] . attr . name = name ;
zone_dev_attrs [ i ] . attr . mode = 0644 ;
zone_dev_attrs [ i ] . show = zone_show ;
zone_dev_attrs [ i ] . store = zone_set ;
zone_data [ i ] . location = i ;
zone_attrs [ i ] = & zone_dev_attrs [ i ] . attr ;
zone_data [ i ] . attr = & zone_dev_attrs [ i ] ;
}
zone_attrs [ quirks - > num_zones ] = & dev_attr_lighting_control_state . attr ;
zone_attribute_group . attrs = zone_attrs ;
led_classdev_register ( & dev - > dev , & global_led ) ;
return sysfs_create_group ( & dev - > dev . kobj , & zone_attribute_group ) ;
}
static void alienware_zone_exit ( struct platform_device * dev )
{
sysfs_remove_group ( & dev - > dev . kobj , & zone_attribute_group ) ;
led_classdev_unregister ( & global_led ) ;
if ( zone_dev_attrs ) {
int i ;
for ( i = 0 ; i < quirks - > num_zones ; i + + )
kfree ( zone_dev_attrs [ i ] . attr . name ) ;
}
kfree ( zone_dev_attrs ) ;
kfree ( zone_data ) ;
kfree ( zone_attrs ) ;
}
2016-02-03 00:38:54 +03:00
static acpi_status alienware_wmax_command ( struct wmax_basic_args * in_args ,
2014-05-08 00:08:10 +04:00
u32 command , int * out_data )
2014-04-04 22:15:42 +04:00
{
acpi_status status ;
union acpi_object * obj ;
2014-05-08 00:08:10 +04:00
struct acpi_buffer input ;
struct acpi_buffer output ;
input . length = ( acpi_size ) sizeof ( * in_args ) ;
input . pointer = in_args ;
if ( out_data ! = NULL ) {
output . length = ACPI_ALLOCATE_BUFFER ;
output . pointer = NULL ;
status = wmi_evaluate_method ( WMAX_CONTROL_GUID , 1 ,
command , & input , & output ) ;
} else
status = wmi_evaluate_method ( WMAX_CONTROL_GUID , 1 ,
command , & input , NULL ) ;
if ( ACPI_SUCCESS ( status ) & & out_data ! = NULL ) {
obj = ( union acpi_object * ) output . pointer ;
if ( obj & & obj - > type = = ACPI_TYPE_INTEGER )
* out_data = ( u32 ) obj - > integer . value ;
}
return status ;
}
2016-02-03 00:38:54 +03:00
/*
* The HDMI mux sysfs node indicates the status of the HDMI input mux .
* It can toggle between standard system GPU output and HDMI input .
*/
2014-05-08 00:08:10 +04:00
static ssize_t show_hdmi_cable ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
acpi_status status ;
u32 out_data ;
2016-02-03 00:38:54 +03:00
struct wmax_basic_args in_args = {
2014-04-04 22:15:42 +04:00
. arg = 0 ,
} ;
2014-05-08 00:08:10 +04:00
status =
2016-02-03 00:38:54 +03:00
alienware_wmax_command ( & in_args , WMAX_METHOD_HDMI_CABLE ,
2014-05-08 00:08:10 +04:00
( u32 * ) & out_data ) ;
if ( ACPI_SUCCESS ( status ) ) {
if ( out_data = = 0 )
return scnprintf ( buf , PAGE_SIZE ,
" [unconnected] connected unknown \n " ) ;
else if ( out_data = = 1 )
return scnprintf ( buf , PAGE_SIZE ,
" unconnected [connected] unknown \n " ) ;
}
pr_err ( " alienware-wmi: unknown HDMI cable status: %d \n " , status ) ;
return scnprintf ( buf , PAGE_SIZE , " unconnected connected [unknown] \n " ) ;
}
static ssize_t show_hdmi_source ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
acpi_status status ;
u32 out_data ;
2016-02-03 00:38:54 +03:00
struct wmax_basic_args in_args = {
2014-05-08 00:08:10 +04:00
. arg = 0 ,
} ;
status =
2016-02-03 00:38:54 +03:00
alienware_wmax_command ( & in_args , WMAX_METHOD_HDMI_STATUS ,
2014-05-08 00:08:10 +04:00
( u32 * ) & out_data ) ;
2014-04-04 22:15:42 +04:00
if ( ACPI_SUCCESS ( status ) ) {
2014-05-08 00:08:10 +04:00
if ( out_data = = 1 )
2014-04-04 22:15:42 +04:00
return scnprintf ( buf , PAGE_SIZE ,
" [input] gpu unknown \n " ) ;
2014-05-08 00:08:10 +04:00
else if ( out_data = = 2 )
2014-04-04 22:15:42 +04:00
return scnprintf ( buf , PAGE_SIZE ,
" input [gpu] unknown \n " ) ;
}
2014-05-08 00:08:10 +04:00
pr_err ( " alienware-wmi: unknown HDMI source status: %d \n " , out_data ) ;
2014-04-04 22:15:42 +04:00
return scnprintf ( buf , PAGE_SIZE , " input gpu [unknown] \n " ) ;
}
2014-05-08 00:08:10 +04:00
static ssize_t toggle_hdmi_source ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
2014-04-04 22:15:42 +04:00
{
acpi_status status ;
2016-02-03 00:38:54 +03:00
struct wmax_basic_args args ;
2014-04-04 22:15:42 +04:00
if ( strcmp ( buf , " gpu \n " ) = = 0 )
args . arg = 1 ;
else if ( strcmp ( buf , " input \n " ) = = 0 )
args . arg = 2 ;
else
args . arg = 3 ;
pr_debug ( " alienware-wmi: setting hdmi to %d : %s " , args . arg , buf ) ;
2014-05-08 00:08:10 +04:00
2016-02-03 00:38:54 +03:00
status = alienware_wmax_command ( & args , WMAX_METHOD_HDMI_SOURCE , NULL ) ;
2014-05-08 00:08:10 +04:00
2014-04-04 22:15:42 +04:00
if ( ACPI_FAILURE ( status ) )
pr_err ( " alienware-wmi: HDMI toggle failed: results: %u \n " ,
status ) ;
return count ;
}
2014-05-08 00:08:10 +04:00
static DEVICE_ATTR ( cable , S_IRUGO , show_hdmi_cable , NULL ) ;
static DEVICE_ATTR ( source , S_IRUGO | S_IWUSR , show_hdmi_source ,
toggle_hdmi_source ) ;
static struct attribute * hdmi_attrs [ ] = {
& dev_attr_cable . attr ,
& dev_attr_source . attr ,
NULL ,
} ;
2014-04-04 22:15:42 +04:00
2014-05-08 00:08:10 +04:00
static struct attribute_group hdmi_attribute_group = {
. name = " hdmi " ,
. attrs = hdmi_attrs ,
} ;
static void remove_hdmi ( struct platform_device * dev )
2014-04-04 22:15:42 +04:00
{
2014-07-24 08:19:23 +04:00
if ( quirks - > hdmi_mux > 0 )
sysfs_remove_group ( & dev - > dev . kobj , & hdmi_attribute_group ) ;
2014-04-04 22:15:42 +04:00
}
2014-05-08 00:08:10 +04:00
static int create_hdmi ( struct platform_device * dev )
2014-04-04 22:15:42 +04:00
{
2014-05-08 00:08:10 +04:00
int ret ;
ret = sysfs_create_group ( & dev - > dev . kobj , & hdmi_attribute_group ) ;
2014-04-04 22:15:42 +04:00
if ( ret )
2016-02-03 00:38:57 +03:00
remove_hdmi ( dev ) ;
2014-04-04 22:15:42 +04:00
return ret ;
}
2016-02-03 00:38:54 +03:00
/*
* Alienware GFX amplifier support
* - Currently supports reading cable status
* - Leaving expansion room to possibly support dock / undock events later
*/
static ssize_t show_amplifier_status ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
acpi_status status ;
u32 out_data ;
struct wmax_basic_args in_args = {
. arg = 0 ,
} ;
status =
alienware_wmax_command ( & in_args , WMAX_METHOD_AMPLIFIER_CABLE ,
( u32 * ) & out_data ) ;
if ( ACPI_SUCCESS ( status ) ) {
if ( out_data = = 0 )
return scnprintf ( buf , PAGE_SIZE ,
" [unconnected] connected unknown \n " ) ;
else if ( out_data = = 1 )
return scnprintf ( buf , PAGE_SIZE ,
" unconnected [connected] unknown \n " ) ;
}
pr_err ( " alienware-wmi: unknown amplifier cable status: %d \n " , status ) ;
return scnprintf ( buf , PAGE_SIZE , " unconnected connected [unknown] \n " ) ;
}
static DEVICE_ATTR ( status , S_IRUGO , show_amplifier_status , NULL ) ;
static struct attribute * amplifier_attrs [ ] = {
& dev_attr_status . attr ,
NULL ,
} ;
static struct attribute_group amplifier_attribute_group = {
. name = " amplifier " ,
. attrs = amplifier_attrs ,
} ;
static void remove_amplifier ( struct platform_device * dev )
{
if ( quirks - > amplifier > 0 )
sysfs_remove_group ( & dev - > dev . kobj , & amplifier_attribute_group ) ;
}
static int create_amplifier ( struct platform_device * dev )
{
int ret ;
ret = sysfs_create_group ( & dev - > dev . kobj , & amplifier_attribute_group ) ;
if ( ret )
remove_amplifier ( dev ) ;
return ret ;
}
2016-02-03 00:38:55 +03:00
/*
* Deep Sleep Control support
* - Modifies BIOS setting for deep sleep control allowing extra wakeup events
*/
static ssize_t show_deepsleep_status ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
acpi_status status ;
u32 out_data ;
struct wmax_basic_args in_args = {
. arg = 0 ,
} ;
status = alienware_wmax_command ( & in_args , WMAX_METHOD_DEEP_SLEEP_STATUS ,
( u32 * ) & out_data ) ;
if ( ACPI_SUCCESS ( status ) ) {
if ( out_data = = 0 )
return scnprintf ( buf , PAGE_SIZE ,
" [disabled] s5 s5_s4 \n " ) ;
else if ( out_data = = 1 )
return scnprintf ( buf , PAGE_SIZE ,
" disabled [s5] s5_s4 \n " ) ;
else if ( out_data = = 2 )
return scnprintf ( buf , PAGE_SIZE ,
" disabled s5 [s5_s4] \n " ) ;
}
pr_err ( " alienware-wmi: unknown deep sleep status: %d \n " , status ) ;
return scnprintf ( buf , PAGE_SIZE , " disabled s5 s5_s4 [unknown] \n " ) ;
}
static ssize_t toggle_deepsleep ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
acpi_status status ;
struct wmax_basic_args args ;
if ( strcmp ( buf , " disabled \n " ) = = 0 )
args . arg = 0 ;
else if ( strcmp ( buf , " s5 \n " ) = = 0 )
args . arg = 1 ;
else
args . arg = 2 ;
pr_debug ( " alienware-wmi: setting deep sleep to %d : %s " , args . arg , buf ) ;
status = alienware_wmax_command ( & args , WMAX_METHOD_DEEP_SLEEP_CONTROL ,
NULL ) ;
if ( ACPI_FAILURE ( status ) )
pr_err ( " alienware-wmi: deep sleep control failed: results: %u \n " ,
status ) ;
return count ;
}
static DEVICE_ATTR ( deepsleep , S_IRUGO | S_IWUSR , show_deepsleep_status , toggle_deepsleep ) ;
static struct attribute * deepsleep_attrs [ ] = {
& dev_attr_deepsleep . attr ,
NULL ,
} ;
static struct attribute_group deepsleep_attribute_group = {
. name = " deepsleep " ,
. attrs = deepsleep_attrs ,
} ;
static void remove_deepsleep ( struct platform_device * dev )
{
if ( quirks - > deepslp > 0 )
sysfs_remove_group ( & dev - > dev . kobj , & deepsleep_attribute_group ) ;
}
static int create_deepsleep ( struct platform_device * dev )
{
int ret ;
ret = sysfs_create_group ( & dev - > dev . kobj , & deepsleep_attribute_group ) ;
if ( ret )
remove_deepsleep ( dev ) ;
return ret ;
}
2014-04-04 22:15:42 +04:00
static int __init alienware_wmi_init ( void )
{
int ret ;
if ( wmi_has_guid ( LEGACY_CONTROL_GUID ) )
interface = LEGACY ;
else if ( wmi_has_guid ( WMAX_CONTROL_GUID ) )
interface = WMAX ;
else {
pr_warn ( " alienware-wmi: No known WMI GUID found \n " ) ;
return - ENODEV ;
}
dmi_check_system ( alienware_quirks ) ;
if ( quirks = = NULL )
quirks = & quirk_unknown ;
ret = platform_driver_register ( & platform_driver ) ;
if ( ret )
goto fail_platform_driver ;
platform_device = platform_device_alloc ( " alienware-wmi " , - 1 ) ;
if ( ! platform_device ) {
ret = - ENOMEM ;
goto fail_platform_device1 ;
}
ret = platform_device_add ( platform_device ) ;
if ( ret )
goto fail_platform_device2 ;
2014-07-24 08:19:23 +04:00
if ( quirks - > hdmi_mux > 0 ) {
2014-05-08 00:08:10 +04:00
ret = create_hdmi ( platform_device ) ;
2014-04-04 22:15:42 +04:00
if ( ret )
goto fail_prep_hdmi ;
}
2016-02-03 00:38:54 +03:00
if ( quirks - > amplifier > 0 ) {
ret = create_amplifier ( platform_device ) ;
if ( ret )
goto fail_prep_amplifier ;
}
2016-02-03 00:38:55 +03:00
if ( quirks - > deepslp > 0 ) {
ret = create_deepsleep ( platform_device ) ;
if ( ret )
goto fail_prep_deepsleep ;
}
2014-04-04 22:15:42 +04:00
ret = alienware_zone_init ( platform_device ) ;
if ( ret )
goto fail_prep_zones ;
return 0 ;
fail_prep_zones :
alienware_zone_exit ( platform_device ) ;
2016-02-03 00:38:55 +03:00
fail_prep_deepsleep :
2016-02-03 00:38:54 +03:00
fail_prep_amplifier :
2014-04-04 22:15:42 +04:00
fail_prep_hdmi :
platform_device_del ( platform_device ) ;
fail_platform_device2 :
platform_device_put ( platform_device ) ;
fail_platform_device1 :
platform_driver_unregister ( & platform_driver ) ;
fail_platform_driver :
return ret ;
}
module_init ( alienware_wmi_init ) ;
static void __exit alienware_wmi_exit ( void )
{
if ( platform_device ) {
2014-04-04 23:40:20 +04:00
alienware_zone_exit ( platform_device ) ;
remove_hdmi ( platform_device ) ;
2014-04-04 22:15:42 +04:00
platform_device_unregister ( platform_device ) ;
platform_driver_unregister ( & platform_driver ) ;
}
}
module_exit ( alienware_wmi_exit ) ;