2019-05-13 09:56:37 +02:00
// SPDX-License-Identifier: GPL-2.0+
/*
* Driver for the OLPC XO - 1.75 Embedded Controller .
*
* The EC protocol is documented at :
* http : //wiki.laptop.org/go/XO_1.75_HOST_to_EC_Protocol
*
* Copyright ( C ) 2010 One Laptop per Child Foundation .
* Copyright ( C ) 2018 Lubomir Rintel < lkundrak @ v3 . sk >
*/
# include <linux/completion.h>
# include <linux/ctype.h>
# include <linux/delay.h>
# include <linux/gpio/consumer.h>
# include <linux/input.h>
# include <linux/kfifo.h>
# include <linux/module.h>
# include <linux/olpc-ec.h>
# include <linux/platform_device.h>
# include <linux/power_supply.h>
# include <linux/reboot.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
# include <linux/spi/spi.h>
struct ec_cmd_t {
u8 cmd ;
u8 bytes_returned ;
} ;
enum ec_chan_t {
CHAN_NONE = 0 ,
CHAN_SWITCH ,
CHAN_CMD_RESP ,
CHAN_KEYBOARD ,
CHAN_TOUCHPAD ,
CHAN_EVENT ,
CHAN_DEBUG ,
CHAN_CMD_ERROR ,
} ;
/*
* EC events
*/
# define EVENT_AC_CHANGE 1 /* AC plugged/unplugged */
# define EVENT_BATTERY_STATUS 2 /* Battery low/full/error/gone */
# define EVENT_BATTERY_CRITICAL 3 /* Battery critical voltage */
# define EVENT_BATTERY_SOC_CHANGE 4 /* 1% SOC Change */
# define EVENT_BATTERY_ERROR 5 /* Abnormal error, query for cause */
# define EVENT_POWER_PRESSED 6 /* Power button was pressed */
# define EVENT_POWER_PRESS_WAKE 7 /* Woken up with a power button */
# define EVENT_TIMED_HOST_WAKE 8 /* Host wake timer */
# define EVENT_OLS_HIGH_LIMIT 9 /* OLS crossed dark threshold */
# define EVENT_OLS_LOW_LIMIT 10 /* OLS crossed light threshold */
/*
* EC commands
* ( from http : //dev.laptop.org/git/users/rsmith/ec-1.75/tree/ec_cmd.h)
*/
# define CMD_GET_API_VERSION 0x08 /* out: u8 */
# define CMD_READ_VOLTAGE 0x10 /* out: u16, *9.76/32, mV */
# define CMD_READ_CURRENT 0x11 /* out: s16, *15.625/120, mA */
# define CMD_READ_ACR 0x12 /* out: s16, *6250/15, uAh */
# define CMD_READ_BATT_TEMPERATURE 0x13 /* out: u16, *100/256, deg C */
# define CMD_READ_AMBIENT_TEMPERATURE 0x14 /* unimplemented, no hardware */
# define CMD_READ_BATTERY_STATUS 0x15 /* out: u8, bitmask */
# define CMD_READ_SOC 0x16 /* out: u8, percentage */
# define CMD_READ_GAUGE_ID 0x17 /* out: u8 * 8 */
# define CMD_READ_GAUGE_DATA 0x18 /* in: u8 addr, out: u8 data */
# define CMD_READ_BOARD_ID 0x19 /* out: u16 (platform id) */
# define CMD_READ_BATT_ERR_CODE 0x1f /* out: u8, error bitmask */
# define CMD_SET_DCON_POWER 0x26 /* in: u8 */
# define CMD_RESET_EC 0x28 /* none */
# define CMD_READ_BATTERY_TYPE 0x2c /* out: u8 */
# define CMD_SET_AUTOWAK 0x33 /* out: u8 */
# define CMD_SET_EC_WAKEUP_TIMER 0x36 /* in: u32, out: ? */
# define CMD_READ_EXT_SCI_MASK 0x37 /* ? */
# define CMD_WRITE_EXT_SCI_MASK 0x38 /* ? */
# define CMD_CLEAR_EC_WAKEUP_TIMER 0x39 /* none */
# define CMD_ENABLE_RUNIN_DISCHARGE 0x3B /* none */
# define CMD_DISABLE_RUNIN_DISCHARGE 0x3C /* none */
# define CMD_READ_MPPT_ACTIVE 0x3d /* out: u8 */
# define CMD_READ_MPPT_LIMIT 0x3e /* out: u8 */
# define CMD_SET_MPPT_LIMIT 0x3f /* in: u8 */
# define CMD_DISABLE_MPPT 0x40 /* none */
# define CMD_ENABLE_MPPT 0x41 /* none */
# define CMD_READ_VIN 0x42 /* out: u16 */
# define CMD_EXT_SCI_QUERY 0x43 /* ? */
# define RSP_KEYBOARD_DATA 0x48 /* ? */
# define RSP_TOUCHPAD_DATA 0x49 /* ? */
# define CMD_GET_FW_VERSION 0x4a /* out: u8 * 16 */
# define CMD_POWER_CYCLE 0x4b /* none */
# define CMD_POWER_OFF 0x4c /* none */
# define CMD_RESET_EC_SOFT 0x4d /* none */
# define CMD_READ_GAUGE_U16 0x4e /* ? */
# define CMD_ENABLE_MOUSE 0x4f /* ? */
# define CMD_ECHO 0x52 /* in: u8 * 5, out: u8 * 5 */
# define CMD_GET_FW_DATE 0x53 /* out: u8 * 16 */
# define CMD_GET_FW_USER 0x54 /* out: u8 * 16 */
# define CMD_TURN_OFF_POWER 0x55 /* none (same as 0x4c) */
# define CMD_READ_OLS 0x56 /* out: u16 */
# define CMD_OLS_SMT_LEDON 0x57 /* none */
# define CMD_OLS_SMT_LEDOFF 0x58 /* none */
# define CMD_START_OLS_ASSY 0x59 /* none */
# define CMD_STOP_OLS_ASSY 0x5a /* none */
# define CMD_OLS_SMTTEST_STOP 0x5b /* none */
# define CMD_READ_VIN_SCALED 0x5c /* out: u16 */
# define CMD_READ_BAT_MIN_W 0x5d /* out: u16 */
# define CMD_READ_BAR_MAX_W 0x5e /* out: u16 */
# define CMD_RESET_BAT_MINMAX_W 0x5f /* none */
# define CMD_READ_LOCATION 0x60 /* in: u16 addr, out: u8 data */
# define CMD_WRITE_LOCATION 0x61 /* in: u16 addr, u8 data */
# define CMD_KEYBOARD_CMD 0x62 /* in: u8, out: ? */
# define CMD_TOUCHPAD_CMD 0x63 /* in: u8, out: ? */
# define CMD_GET_FW_HASH 0x64 /* out: u8 * 16 */
# define CMD_SUSPEND_HINT 0x65 /* in: u8 */
# define CMD_ENABLE_WAKE_TIMER 0x66 /* in: u8 */
# define CMD_SET_WAKE_TIMER 0x67 /* in: 32 */
# define CMD_ENABLE_WAKE_AUTORESET 0x68 /* in: u8 */
# define CMD_OLS_SET_LIMITS 0x69 /* in: u16, u16 */
# define CMD_OLS_GET_LIMITS 0x6a /* out: u16, u16 */
# define CMD_OLS_SET_CEILING 0x6b /* in: u16 */
# define CMD_OLS_GET_CEILING 0x6c /* out: u16 */
/*
* Accepted EC commands , and how many bytes they return . There are plenty
* of EC commands that are no longer implemented , or are implemented only on
* certain older boards .
*/
static const struct ec_cmd_t olpc_xo175_ec_cmds [ ] = {
{ CMD_GET_API_VERSION , 1 } ,
{ CMD_READ_VOLTAGE , 2 } ,
{ CMD_READ_CURRENT , 2 } ,
{ CMD_READ_ACR , 2 } ,
{ CMD_READ_BATT_TEMPERATURE , 2 } ,
{ CMD_READ_BATTERY_STATUS , 1 } ,
{ CMD_READ_SOC , 1 } ,
{ CMD_READ_GAUGE_ID , 8 } ,
{ CMD_READ_GAUGE_DATA , 1 } ,
{ CMD_READ_BOARD_ID , 2 } ,
{ CMD_READ_BATT_ERR_CODE , 1 } ,
{ CMD_SET_DCON_POWER , 0 } ,
{ CMD_RESET_EC , 0 } ,
{ CMD_READ_BATTERY_TYPE , 1 } ,
{ CMD_ENABLE_RUNIN_DISCHARGE , 0 } ,
{ CMD_DISABLE_RUNIN_DISCHARGE , 0 } ,
{ CMD_READ_MPPT_ACTIVE , 1 } ,
{ CMD_READ_MPPT_LIMIT , 1 } ,
{ CMD_SET_MPPT_LIMIT , 0 } ,
{ CMD_DISABLE_MPPT , 0 } ,
{ CMD_ENABLE_MPPT , 0 } ,
{ CMD_READ_VIN , 2 } ,
{ CMD_GET_FW_VERSION , 16 } ,
{ CMD_POWER_CYCLE , 0 } ,
{ CMD_POWER_OFF , 0 } ,
{ CMD_RESET_EC_SOFT , 0 } ,
{ CMD_ECHO , 5 } ,
{ CMD_GET_FW_DATE , 16 } ,
{ CMD_GET_FW_USER , 16 } ,
{ CMD_TURN_OFF_POWER , 0 } ,
{ CMD_READ_OLS , 2 } ,
{ CMD_OLS_SMT_LEDON , 0 } ,
{ CMD_OLS_SMT_LEDOFF , 0 } ,
{ CMD_START_OLS_ASSY , 0 } ,
{ CMD_STOP_OLS_ASSY , 0 } ,
{ CMD_OLS_SMTTEST_STOP , 0 } ,
{ CMD_READ_VIN_SCALED , 2 } ,
{ CMD_READ_BAT_MIN_W , 2 } ,
{ CMD_READ_BAR_MAX_W , 2 } ,
{ CMD_RESET_BAT_MINMAX_W , 0 } ,
{ CMD_READ_LOCATION , 1 } ,
{ CMD_WRITE_LOCATION , 0 } ,
{ CMD_GET_FW_HASH , 16 } ,
{ CMD_SUSPEND_HINT , 0 } ,
{ CMD_ENABLE_WAKE_TIMER , 0 } ,
{ CMD_SET_WAKE_TIMER , 0 } ,
{ CMD_ENABLE_WAKE_AUTORESET , 0 } ,
{ CMD_OLS_SET_LIMITS , 0 } ,
{ CMD_OLS_GET_LIMITS , 4 } ,
{ CMD_OLS_SET_CEILING , 0 } ,
{ CMD_OLS_GET_CEILING , 2 } ,
{ CMD_READ_EXT_SCI_MASK , 2 } ,
{ CMD_WRITE_EXT_SCI_MASK , 0 } ,
{ }
} ;
# define EC_MAX_CMD_DATA_LEN 5
# define EC_MAX_RESP_LEN 16
# define LOG_BUF_SIZE 128
# define PM_WAKEUP_TIME 1000
# define EC_ALL_EVENTS GENMASK(15, 0)
enum ec_state_t {
CMD_STATE_IDLE = 0 ,
CMD_STATE_WAITING_FOR_SWITCH ,
CMD_STATE_CMD_IN_TX_FIFO ,
CMD_STATE_CMD_SENT ,
CMD_STATE_RESP_RECEIVED ,
CMD_STATE_ERROR_RECEIVED ,
} ;
struct olpc_xo175_ec_cmd {
u8 command ;
u8 nr_args ;
u8 data_len ;
u8 args [ EC_MAX_CMD_DATA_LEN ] ;
} ;
struct olpc_xo175_ec_resp {
u8 channel ;
u8 byte ;
} ;
struct olpc_xo175_ec {
bool suspended ;
/* SPI related stuff. */
struct spi_device * spi ;
struct spi_transfer xfer ;
struct spi_message msg ;
union {
struct olpc_xo175_ec_cmd cmd ;
struct olpc_xo175_ec_resp resp ;
} tx_buf , rx_buf ;
/* GPIO for the CMD signals. */
struct gpio_desc * gpio_cmd ;
/* Command handling related state. */
spinlock_t cmd_state_lock ;
int cmd_state ;
bool cmd_running ;
struct completion cmd_done ;
struct olpc_xo175_ec_cmd cmd ;
u8 resp_data [ EC_MAX_RESP_LEN ] ;
int expected_resp_len ;
int resp_len ;
/* Power button. */
struct input_dev * pwrbtn ;
/* Debug handling. */
char logbuf [ LOG_BUF_SIZE ] ;
int logbuf_len ;
} ;
static struct platform_device * olpc_ec ;
static int olpc_xo175_ec_resp_len ( u8 cmd )
{
const struct ec_cmd_t * p ;
for ( p = olpc_xo175_ec_cmds ; p - > cmd ; p + + ) {
if ( p - > cmd = = cmd )
return p - > bytes_returned ;
}
return - EINVAL ;
}
static void olpc_xo175_ec_flush_logbuf ( struct olpc_xo175_ec * priv )
{
dev_dbg ( & priv - > spi - > dev , " got debug string [%*pE] \n " ,
priv - > logbuf_len , priv - > logbuf ) ;
priv - > logbuf_len = 0 ;
}
static void olpc_xo175_ec_complete ( void * arg ) ;
static void olpc_xo175_ec_send_command ( struct olpc_xo175_ec * priv , void * cmd ,
size_t cmdlen )
{
int ret ;
memcpy ( & priv - > tx_buf , cmd , cmdlen ) ;
priv - > xfer . len = cmdlen ;
spi_message_init_with_transfers ( & priv - > msg , & priv - > xfer , 1 ) ;
priv - > msg . complete = olpc_xo175_ec_complete ;
priv - > msg . context = priv ;
ret = spi_async ( priv - > spi , & priv - > msg ) ;
if ( ret )
dev_err ( & priv - > spi - > dev , " spi_async() failed %d \n " , ret ) ;
}
static void olpc_xo175_ec_read_packet ( struct olpc_xo175_ec * priv )
{
u8 nonce [ ] = { 0xA5 , 0x5A } ;
olpc_xo175_ec_send_command ( priv , nonce , sizeof ( nonce ) ) ;
}
static void olpc_xo175_ec_complete ( void * arg )
{
struct olpc_xo175_ec * priv = arg ;
struct device * dev = & priv - > spi - > dev ;
struct power_supply * psy ;
unsigned long flags ;
u8 channel ;
u8 byte ;
int ret ;
ret = priv - > msg . status ;
if ( ret ) {
dev_err ( dev , " SPI transfer failed: %d \n " , ret ) ;
spin_lock_irqsave ( & priv - > cmd_state_lock , flags ) ;
if ( priv - > cmd_running ) {
priv - > resp_len = 0 ;
priv - > cmd_state = CMD_STATE_ERROR_RECEIVED ;
complete ( & priv - > cmd_done ) ;
}
spin_unlock_irqrestore ( & priv - > cmd_state_lock , flags ) ;
if ( ret ! = - EINTR )
olpc_xo175_ec_read_packet ( priv ) ;
return ;
}
channel = priv - > rx_buf . resp . channel ;
byte = priv - > rx_buf . resp . byte ;
switch ( channel ) {
case CHAN_NONE :
spin_lock_irqsave ( & priv - > cmd_state_lock , flags ) ;
if ( ! priv - > cmd_running ) {
/* We can safely ignore these */
dev_err ( dev , " spurious FIFO read packet \n " ) ;
spin_unlock_irqrestore ( & priv - > cmd_state_lock , flags ) ;
return ;
}
priv - > cmd_state = CMD_STATE_CMD_SENT ;
if ( ! priv - > expected_resp_len )
complete ( & priv - > cmd_done ) ;
olpc_xo175_ec_read_packet ( priv ) ;
spin_unlock_irqrestore ( & priv - > cmd_state_lock , flags ) ;
return ;
case CHAN_SWITCH :
spin_lock_irqsave ( & priv - > cmd_state_lock , flags ) ;
if ( ! priv - > cmd_running ) {
/* Just go with the flow */
dev_err ( dev , " spurious SWITCH packet \n " ) ;
memset ( & priv - > cmd , 0 , sizeof ( priv - > cmd ) ) ;
priv - > cmd . command = CMD_ECHO ;
}
priv - > cmd_state = CMD_STATE_CMD_IN_TX_FIFO ;
/* Throw command into TxFIFO */
gpiod_set_value_cansleep ( priv - > gpio_cmd , 0 ) ;
olpc_xo175_ec_send_command ( priv , & priv - > cmd , sizeof ( priv - > cmd ) ) ;
spin_unlock_irqrestore ( & priv - > cmd_state_lock , flags ) ;
return ;
case CHAN_CMD_RESP :
spin_lock_irqsave ( & priv - > cmd_state_lock , flags ) ;
if ( ! priv - > cmd_running ) {
dev_err ( dev , " spurious response packet \n " ) ;
} else if ( priv - > resp_len > = priv - > expected_resp_len ) {
dev_err ( dev , " too many response packets \n " ) ;
} else {
priv - > resp_data [ priv - > resp_len + + ] = byte ;
if ( priv - > resp_len = = priv - > expected_resp_len ) {
priv - > cmd_state = CMD_STATE_RESP_RECEIVED ;
complete ( & priv - > cmd_done ) ;
}
}
spin_unlock_irqrestore ( & priv - > cmd_state_lock , flags ) ;
break ;
case CHAN_CMD_ERROR :
spin_lock_irqsave ( & priv - > cmd_state_lock , flags ) ;
if ( ! priv - > cmd_running ) {
dev_err ( dev , " spurious cmd error packet \n " ) ;
} else {
priv - > resp_data [ 0 ] = byte ;
priv - > resp_len = 1 ;
priv - > cmd_state = CMD_STATE_ERROR_RECEIVED ;
complete ( & priv - > cmd_done ) ;
}
spin_unlock_irqrestore ( & priv - > cmd_state_lock , flags ) ;
break ;
case CHAN_KEYBOARD :
dev_warn ( dev , " keyboard is not supported \n " ) ;
break ;
case CHAN_TOUCHPAD :
dev_warn ( dev , " touchpad is not supported \n " ) ;
break ;
case CHAN_EVENT :
dev_dbg ( dev , " got event %.2x \n " , byte ) ;
switch ( byte ) {
case EVENT_AC_CHANGE :
2019-12-21 08:17:51 +01:00
psy = power_supply_get_by_name ( " olpc_ac " ) ;
2019-05-13 09:56:37 +02:00
if ( psy ) {
power_supply_changed ( psy ) ;
power_supply_put ( psy ) ;
}
break ;
case EVENT_BATTERY_STATUS :
case EVENT_BATTERY_CRITICAL :
case EVENT_BATTERY_SOC_CHANGE :
case EVENT_BATTERY_ERROR :
2019-12-21 08:17:51 +01:00
psy = power_supply_get_by_name ( " olpc_battery " ) ;
2019-05-13 09:56:37 +02:00
if ( psy ) {
power_supply_changed ( psy ) ;
power_supply_put ( psy ) ;
}
break ;
case EVENT_POWER_PRESSED :
input_report_key ( priv - > pwrbtn , KEY_POWER , 1 ) ;
input_sync ( priv - > pwrbtn ) ;
input_report_key ( priv - > pwrbtn , KEY_POWER , 0 ) ;
input_sync ( priv - > pwrbtn ) ;
/* fall through */
case EVENT_POWER_PRESS_WAKE :
case EVENT_TIMED_HOST_WAKE :
pm_wakeup_event ( priv - > pwrbtn - > dev . parent ,
PM_WAKEUP_TIME ) ;
break ;
default :
dev_dbg ( dev , " ignored unknown event %.2x \n " , byte ) ;
break ;
}
break ;
case CHAN_DEBUG :
if ( byte = = ' \n ' ) {
olpc_xo175_ec_flush_logbuf ( priv ) ;
} else if ( isprint ( byte ) ) {
priv - > logbuf [ priv - > logbuf_len + + ] = byte ;
if ( priv - > logbuf_len = = LOG_BUF_SIZE )
olpc_xo175_ec_flush_logbuf ( priv ) ;
}
break ;
default :
dev_warn ( dev , " unknown channel: %d, %.2x \n " , channel , byte ) ;
break ;
}
/* Most non-command packets get the TxFIFO refilled and an ACK. */
olpc_xo175_ec_read_packet ( priv ) ;
}
/*
* This function is protected with a mutex . We can safely assume that
* there will be only one instance of this function running at a time .
* One of the ways in which we enforce this is by waiting until we get
* all response bytes back from the EC , rather than just the number that
* the caller requests ( otherwise , we might start a new command while an
* old command ' s response bytes are still incoming ) .
*/
static int olpc_xo175_ec_cmd ( u8 cmd , u8 * inbuf , size_t inlen , u8 * resp ,
size_t resp_len , void * ec_cb_arg )
{
struct olpc_xo175_ec * priv = ec_cb_arg ;
struct device * dev = & priv - > spi - > dev ;
unsigned long flags ;
size_t nr_bytes ;
int ret = 0 ;
dev_dbg ( dev , " CMD %x, %zd bytes expected \n " , cmd , resp_len ) ;
if ( inlen > 5 ) {
dev_err ( dev , " command len %zd too big! \n " , resp_len ) ;
return - EOVERFLOW ;
}
/* Suspending in the middle of an EC command hoses things badly! */
if ( WARN_ON ( priv - > suspended ) )
return - EBUSY ;
/* Ensure a valid command and return bytes */
ret = olpc_xo175_ec_resp_len ( cmd ) ;
if ( ret < 0 ) {
dev_err_ratelimited ( dev , " unknown command 0x%x \n " , cmd ) ;
/*
* Assume the best in our callers , and allow unknown commands
* through . I ' m not the charitable type , but it was beaten
* into me . Just maintain a minimum standard of sanity .
*/
if ( resp_len > sizeof ( priv - > resp_data ) ) {
dev_err ( dev , " response too big: %zd! \n " , resp_len ) ;
return - EOVERFLOW ;
}
nr_bytes = resp_len ;
} else {
nr_bytes = ( size_t ) ret ;
2019-05-29 10:34:03 +02:00
ret = 0 ;
2019-05-13 09:56:37 +02:00
}
resp_len = min ( resp_len , nr_bytes ) ;
spin_lock_irqsave ( & priv - > cmd_state_lock , flags ) ;
/* Initialize the state machine */
init_completion ( & priv - > cmd_done ) ;
priv - > cmd_running = true ;
priv - > cmd_state = CMD_STATE_WAITING_FOR_SWITCH ;
memset ( & priv - > cmd , 0 , sizeof ( priv - > cmd ) ) ;
priv - > cmd . command = cmd ;
priv - > cmd . nr_args = inlen ;
priv - > cmd . data_len = 0 ;
memcpy ( priv - > cmd . args , inbuf , inlen ) ;
priv - > expected_resp_len = nr_bytes ;
priv - > resp_len = 0 ;
/* Tickle the cmd gpio to get things started */
gpiod_set_value_cansleep ( priv - > gpio_cmd , 1 ) ;
spin_unlock_irqrestore ( & priv - > cmd_state_lock , flags ) ;
/* The irq handler should do the rest */
if ( ! wait_for_completion_timeout ( & priv - > cmd_done ,
msecs_to_jiffies ( 4000 ) ) ) {
dev_err ( dev , " EC cmd error: timeout in STATE %d \n " ,
priv - > cmd_state ) ;
gpiod_set_value_cansleep ( priv - > gpio_cmd , 0 ) ;
spi_slave_abort ( priv - > spi ) ;
olpc_xo175_ec_read_packet ( priv ) ;
return - ETIMEDOUT ;
}
spin_lock_irqsave ( & priv - > cmd_state_lock , flags ) ;
/* Deal with the results. */
if ( priv - > cmd_state = = CMD_STATE_ERROR_RECEIVED ) {
/* EC-provided error is in the single response byte */
dev_err ( dev , " command 0x%x returned error 0x%x \n " ,
cmd , priv - > resp_data [ 0 ] ) ;
ret = - EREMOTEIO ;
} else if ( priv - > resp_len ! = nr_bytes ) {
dev_err ( dev , " command 0x%x returned %d bytes, expected %zd bytes \n " ,
cmd , priv - > resp_len , nr_bytes ) ;
ret = - EREMOTEIO ;
} else {
/*
* We may have 8 bytes in priv - > resp , but we only care about
* what we ' ve been asked for . If the caller asked for only 2
* bytes , give them that . We ' ve guaranteed that
* resp_len < = priv - > resp_len and priv - > resp_len = = nr_bytes .
*/
memcpy ( resp , priv - > resp_data , resp_len ) ;
}
/* This should already be low, but just in case. */
gpiod_set_value_cansleep ( priv - > gpio_cmd , 0 ) ;
priv - > cmd_running = false ;
spin_unlock_irqrestore ( & priv - > cmd_state_lock , flags ) ;
return ret ;
}
static int olpc_xo175_ec_set_event_mask ( unsigned int mask )
{
u8 args [ 2 ] ;
args [ 0 ] = mask > > 0 ;
args [ 1 ] = mask > > 8 ;
return olpc_ec_cmd ( CMD_WRITE_EXT_SCI_MASK , args , 2 , NULL , 0 ) ;
}
static void olpc_xo175_ec_power_off ( void )
{
while ( 1 ) {
olpc_ec_cmd ( CMD_POWER_OFF , NULL , 0 , NULL , 0 ) ;
mdelay ( 1000 ) ;
}
}
static int __maybe_unused olpc_xo175_ec_suspend ( struct device * dev )
{
struct olpc_xo175_ec * priv = dev_get_drvdata ( dev ) ;
static struct {
u8 suspend ;
u32 suspend_count ;
} __packed hintargs ;
static unsigned int suspend_count ;
/*
* SOC_SLEEP is not wired to the EC on B3 and earlier boards .
* This command lets the EC know instead . The suspend count doesn ' t seem
* to be used anywhere but in the EC debug output .
*/
hintargs . suspend = 1 ;
hintargs . suspend_count = suspend_count + + ;
olpc_ec_cmd ( CMD_SUSPEND_HINT , ( void * ) & hintargs , sizeof ( hintargs ) ,
NULL , 0 ) ;
/*
* After we ' ve sent the suspend hint , don ' t allow further EC commands
* to be run until we ' ve resumed . Userspace tasks should be frozen ,
* but kernel threads and interrupts could still schedule EC commands .
*/
priv - > suspended = true ;
return 0 ;
}
static int __maybe_unused olpc_xo175_ec_resume_noirq ( struct device * dev )
{
struct olpc_xo175_ec * priv = dev_get_drvdata ( dev ) ;
priv - > suspended = false ;
return 0 ;
}
static int __maybe_unused olpc_xo175_ec_resume ( struct device * dev )
{
u8 x = 0 ;
/*
* The resume hint is only needed if no other commands are
* being sent during resume . all it does is tell the EC
* the SoC is definitely awake .
*/
olpc_ec_cmd ( CMD_SUSPEND_HINT , & x , 1 , NULL , 0 ) ;
/* Enable all EC events while we're awake */
olpc_xo175_ec_set_event_mask ( EC_ALL_EVENTS ) ;
return 0 ;
}
static struct olpc_ec_driver olpc_xo175_ec_driver = {
. ec_cmd = olpc_xo175_ec_cmd ,
} ;
static int olpc_xo175_ec_remove ( struct spi_device * spi )
{
if ( pm_power_off = = olpc_xo175_ec_power_off )
pm_power_off = NULL ;
spi_slave_abort ( spi ) ;
platform_device_unregister ( olpc_ec ) ;
olpc_ec = NULL ;
return 0 ;
}
static int olpc_xo175_ec_probe ( struct spi_device * spi )
{
struct olpc_xo175_ec * priv ;
int ret ;
if ( olpc_ec ) {
dev_err ( & spi - > dev , " OLPC EC already registered. \n " ) ;
return - EBUSY ;
}
priv = devm_kzalloc ( & spi - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
priv - > gpio_cmd = devm_gpiod_get ( & spi - > dev , " cmd " , GPIOD_OUT_LOW ) ;
if ( IS_ERR ( priv - > gpio_cmd ) ) {
dev_err ( & spi - > dev , " failed to get cmd gpio: %ld \n " ,
PTR_ERR ( priv - > gpio_cmd ) ) ;
return PTR_ERR ( priv - > gpio_cmd ) ;
}
priv - > spi = spi ;
spin_lock_init ( & priv - > cmd_state_lock ) ;
priv - > cmd_state = CMD_STATE_IDLE ;
init_completion ( & priv - > cmd_done ) ;
priv - > logbuf_len = 0 ;
/* Set up power button input device */
priv - > pwrbtn = devm_input_allocate_device ( & spi - > dev ) ;
if ( ! priv - > pwrbtn )
return - ENOMEM ;
priv - > pwrbtn - > name = " Power Button " ;
priv - > pwrbtn - > dev . parent = & spi - > dev ;
input_set_capability ( priv - > pwrbtn , EV_KEY , KEY_POWER ) ;
ret = input_register_device ( priv - > pwrbtn ) ;
if ( ret ) {
dev_err ( & spi - > dev , " error registering input device: %d \n " , ret ) ;
return ret ;
}
spi_set_drvdata ( spi , priv ) ;
priv - > xfer . rx_buf = & priv - > rx_buf ;
priv - > xfer . tx_buf = & priv - > tx_buf ;
olpc_xo175_ec_read_packet ( priv ) ;
olpc_ec_driver_register ( & olpc_xo175_ec_driver , priv ) ;
olpc_ec = platform_device_register_resndata ( & spi - > dev , " olpc-ec " , - 1 ,
NULL , 0 , NULL , 0 ) ;
/* Enable all EC events while we're awake */
olpc_xo175_ec_set_event_mask ( EC_ALL_EVENTS ) ;
if ( pm_power_off = = NULL )
pm_power_off = olpc_xo175_ec_power_off ;
dev_info ( & spi - > dev , " OLPC XO-1.75 Embedded Controller driver \n " ) ;
return 0 ;
}
static const struct dev_pm_ops olpc_xo175_ec_pm_ops = {
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS ( NULL , olpc_xo175_ec_resume_noirq )
SET_RUNTIME_PM_OPS ( olpc_xo175_ec_suspend , olpc_xo175_ec_resume , NULL )
} ;
static const struct of_device_id olpc_xo175_ec_of_match [ ] = {
{ . compatible = " olpc,xo1.75-ec " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , olpc_xo175_ec_of_match ) ;
2019-07-23 11:20:22 +02:00
static const struct spi_device_id olpc_xo175_ec_id_table [ ] = {
{ " xo1.75-ec " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( spi , olpc_xo175_ec_id_table ) ;
2019-05-13 09:56:37 +02:00
static struct spi_driver olpc_xo175_ec_spi_driver = {
. driver = {
. name = " olpc-xo175-ec " ,
. of_match_table = olpc_xo175_ec_of_match ,
. pm = & olpc_xo175_ec_pm_ops ,
} ,
. probe = olpc_xo175_ec_probe ,
. remove = olpc_xo175_ec_remove ,
} ;
module_spi_driver ( olpc_xo175_ec_spi_driver ) ;
MODULE_DESCRIPTION ( " OLPC XO-1.75 Embedded Controller driver " ) ;
MODULE_AUTHOR ( " Lennert Buytenhek <buytenh@wantstofly.org> " ) ; /* Functionality */
MODULE_AUTHOR ( " Lubomir Rintel <lkundrak@v3.sk> " ) ; /* Bugs */
MODULE_LICENSE ( " GPL " ) ;