2011-06-25 17:34:18 +01:00
/*
* Support for OLPC XO - 1.5 System Control Interrupts ( SCI )
*
* Copyright ( C ) 2009 - 2010 One Laptop per Child
*
* 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 .
*/
# include <linux/device.h>
# include <linux/slab.h>
# include <linux/workqueue.h>
# include <linux/power_supply.h>
2012-07-11 01:16:29 -07:00
# include <linux/olpc-ec.h>
2011-06-25 17:34:18 +01:00
2013-12-03 08:49:16 +08:00
# include <linux/acpi.h>
2011-06-25 17:34:18 +01:00
# include <asm/olpc.h>
# define DRV_NAME "olpc-xo15-sci"
# define PFX DRV_NAME ": "
# define XO15_SCI_CLASS DRV_NAME
# define XO15_SCI_DEVICE_NAME "OLPC XO-1.5 SCI"
2012-03-05 15:01:00 -08:00
static unsigned long xo15_sci_gpe ;
static bool lid_wake_on_close ;
/*
* The normal ACPI LID wakeup behavior is wake - on - open , but not
* wake - on - close . This is implemented as standard by the XO - 1.5 DSDT .
*
* We provide here a sysfs attribute that will additionally enable
* wake - on - close behavior . This is useful ( e . g . ) when we oportunistically
* suspend with the display running ; if the lid is then closed , we want to
* wake up to turn the display off .
*
* This is controlled through a custom method in the XO - 1.5 DSDT .
*/
static int set_lid_wake_behavior ( bool wake_on_close )
{
acpi_status status ;
2013-09-03 08:31:49 +08:00
status = acpi_execute_simple_method ( NULL , " \\ _SB.PCI0.LID.LIDW " , wake_on_close ) ;
2012-03-05 15:01:00 -08:00
if ( ACPI_FAILURE ( status ) ) {
pr_warning ( PFX " failed to set lid behavior \n " ) ;
return 1 ;
}
lid_wake_on_close = wake_on_close ;
return 0 ;
}
static ssize_t
lid_wake_on_close_show ( struct kobject * s , struct kobj_attribute * attr , char * buf )
{
return sprintf ( buf , " %u \n " , lid_wake_on_close ) ;
}
static ssize_t lid_wake_on_close_store ( struct kobject * s ,
struct kobj_attribute * attr ,
const char * buf , size_t n )
{
unsigned int val ;
if ( sscanf ( buf , " %u " , & val ) ! = 1 )
return - EINVAL ;
set_lid_wake_behavior ( ! ! val ) ;
return n ;
}
static struct kobj_attribute lid_wake_on_close_attr =
__ATTR ( lid_wake_on_close , 0644 ,
lid_wake_on_close_show ,
lid_wake_on_close_store ) ;
2011-06-25 17:34:18 +01:00
static void battery_status_changed ( void )
{
struct power_supply * psy = power_supply_get_by_name ( " olpc-battery " ) ;
if ( psy ) {
power_supply_changed ( psy ) ;
2015-03-12 08:44:16 +01:00
power_supply_put ( psy ) ;
2011-06-25 17:34:18 +01:00
}
}
static void ac_status_changed ( void )
{
struct power_supply * psy = power_supply_get_by_name ( " olpc-ac " ) ;
if ( psy ) {
power_supply_changed ( psy ) ;
2015-03-12 08:44:16 +01:00
power_supply_put ( psy ) ;
2011-06-25 17:34:18 +01:00
}
}
static void process_sci_queue ( void )
{
u16 data ;
int r ;
do {
r = olpc_ec_sci_query ( & data ) ;
if ( r | | ! data )
break ;
pr_debug ( PFX " SCI 0x%x received \n " , data ) ;
switch ( data ) {
case EC_SCI_SRC_BATERR :
case EC_SCI_SRC_BATSOC :
case EC_SCI_SRC_BATTERY :
case EC_SCI_SRC_BATCRIT :
battery_status_changed ( ) ;
break ;
case EC_SCI_SRC_ACPWR :
ac_status_changed ( ) ;
break ;
}
} while ( data ) ;
if ( r )
pr_err ( PFX " Failed to clear SCI queue " ) ;
}
static void process_sci_queue_work ( struct work_struct * work )
{
process_sci_queue ( ) ;
}
static DECLARE_WORK ( sci_work , process_sci_queue_work ) ;
static u32 xo15_sci_gpe_handler ( acpi_handle gpe_device , u32 gpe , void * context )
{
schedule_work ( & sci_work ) ;
return ACPI_INTERRUPT_HANDLED | ACPI_REENABLE_GPE ;
}
static int xo15_sci_add ( struct acpi_device * device )
{
unsigned long long tmp ;
acpi_status status ;
2012-03-05 15:01:00 -08:00
int r ;
2011-06-25 17:34:18 +01:00
if ( ! device )
return - EINVAL ;
strcpy ( acpi_device_name ( device ) , XO15_SCI_DEVICE_NAME ) ;
strcpy ( acpi_device_class ( device ) , XO15_SCI_CLASS ) ;
/* Get GPE bit assignment (EC events). */
status = acpi_evaluate_integer ( device - > handle , " _GPE " , NULL , & tmp ) ;
if ( ACPI_FAILURE ( status ) )
return - EINVAL ;
xo15_sci_gpe = tmp ;
status = acpi_install_gpe_handler ( NULL , xo15_sci_gpe ,
ACPI_GPE_EDGE_TRIGGERED ,
xo15_sci_gpe_handler , device ) ;
if ( ACPI_FAILURE ( status ) )
return - ENODEV ;
dev_info ( & device - > dev , " Initialized, GPE = 0x%lx \n " , xo15_sci_gpe ) ;
2012-03-05 15:01:00 -08:00
r = sysfs_create_file ( & device - > dev . kobj , & lid_wake_on_close_attr . attr ) ;
if ( r )
goto err_sysfs ;
2011-06-25 17:34:18 +01:00
/* Flush queue, and enable all SCI events */
process_sci_queue ( ) ;
olpc_ec_mask_write ( EC_SCI_SRC_ALL ) ;
acpi_enable_gpe ( NULL , xo15_sci_gpe ) ;
/* Enable wake-on-EC */
if ( device - > wakeup . flags . valid )
2011-07-24 18:34:30 +01:00
device_init_wakeup ( & device - > dev , true ) ;
2011-06-25 17:34:18 +01:00
return 0 ;
2012-03-05 15:01:00 -08:00
err_sysfs :
acpi_remove_gpe_handler ( NULL , xo15_sci_gpe , xo15_sci_gpe_handler ) ;
cancel_work_sync ( & sci_work ) ;
return r ;
2011-06-25 17:34:18 +01:00
}
2013-01-24 00:24:48 +01:00
static int xo15_sci_remove ( struct acpi_device * device )
2011-06-25 17:34:18 +01:00
{
acpi_disable_gpe ( NULL , xo15_sci_gpe ) ;
acpi_remove_gpe_handler ( NULL , xo15_sci_gpe , xo15_sci_gpe_handler ) ;
cancel_work_sync ( & sci_work ) ;
2012-03-05 15:01:00 -08:00
sysfs_remove_file ( & device - > dev . kobj , & lid_wake_on_close_attr . attr ) ;
2011-06-25 17:34:18 +01:00
return 0 ;
}
2016-11-26 15:27:06 +01:00
# ifdef CONFIG_PM_SLEEP
2012-07-12 22:36:28 +02:00
static int xo15_sci_resume ( struct device * dev )
2011-06-25 17:34:18 +01:00
{
/* Enable all EC events */
olpc_ec_mask_write ( EC_SCI_SRC_ALL ) ;
/* Power/battery status might have changed */
battery_status_changed ( ) ;
ac_status_changed ( ) ;
return 0 ;
}
2016-11-26 15:27:06 +01:00
# endif
2011-06-25 17:34:18 +01:00
2012-07-12 22:36:28 +02:00
static SIMPLE_DEV_PM_OPS ( xo15_sci_pm , NULL , xo15_sci_resume ) ;
2011-06-25 17:34:18 +01:00
static const struct acpi_device_id xo15_sci_device_ids [ ] = {
{ " XO15EC " , 0 } ,
{ " " , 0 } ,
} ;
static struct acpi_driver xo15_sci_drv = {
. name = DRV_NAME ,
. class = XO15_SCI_CLASS ,
. ids = xo15_sci_device_ids ,
. ops = {
. add = xo15_sci_add ,
. remove = xo15_sci_remove ,
} ,
2012-07-12 22:36:28 +02:00
. drv . pm = & xo15_sci_pm ,
2011-06-25 17:34:18 +01:00
} ;
static int __init xo15_sci_init ( void )
{
return acpi_bus_register_driver ( & xo15_sci_drv ) ;
}
device_initcall ( xo15_sci_init ) ;