2013-02-26 02:08:37 +04:00
/*
* ChromeOS EC multi - function device
*
* Copyright ( C ) 2012 Google , Inc
*
* This software is licensed under the terms of the GNU General Public
* License version 2 , as published by the Free Software Foundation , and
* may be copied , distributed , and modified under those terms .
*
* 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 .
*
* The ChromeOS EC multi function device is used to mux all the requests
* to the EC device for its multiple features : keyboard controller ,
* battery charging and regulator control , firmware update .
*/
2015-05-20 12:31:28 +03:00
# include <linux/of_platform.h>
2013-02-26 02:08:37 +04:00
# include <linux/interrupt.h>
# include <linux/slab.h>
2013-03-20 12:46:15 +04:00
# include <linux/module.h>
2013-02-26 02:08:37 +04:00
# include <linux/mfd/core.h>
# include <linux/mfd/cros_ec.h>
2016-12-16 20:57:37 +03:00
# include <linux/suspend.h>
2016-08-10 20:05:24 +03:00
# include <asm/unaligned.h>
2014-09-18 19:18:56 +04:00
2015-06-09 14:04:47 +03:00
# define CROS_EC_DEV_EC_INDEX 0
# define CROS_EC_DEV_PD_INDEX 1
2015-06-15 16:39:39 +03:00
static struct cros_ec_platform ec_p = {
2015-06-09 14:04:47 +03:00
. ec_name = CROS_EC_DEV_NAME ,
. cmd_offset = EC_CMD_PASSTHRU_OFFSET ( CROS_EC_DEV_EC_INDEX ) ,
} ;
2015-06-15 16:39:39 +03:00
static struct cros_ec_platform pd_p = {
2015-06-09 14:04:47 +03:00
. ec_name = CROS_EC_DEV_PD_NAME ,
. cmd_offset = EC_CMD_PASSTHRU_OFFSET ( CROS_EC_DEV_PD_INDEX ) ,
} ;
2015-06-15 16:39:39 +03:00
static const struct mfd_cell ec_cell = {
2017-11-20 19:15:25 +03:00
. name = " cros-ec-dev " ,
2015-06-09 14:04:47 +03:00
. platform_data = & ec_p ,
. pdata_size = sizeof ( ec_p ) ,
} ;
2015-06-15 16:39:39 +03:00
static const struct mfd_cell ec_pd_cell = {
2017-11-20 19:15:25 +03:00
. name = " cros-ec-dev " ,
2015-06-09 14:04:47 +03:00
. platform_data = & pd_p ,
. pdata_size = sizeof ( pd_p ) ,
2013-02-26 02:08:37 +04:00
} ;
2016-08-10 20:05:24 +03:00
static irqreturn_t ec_irq_thread ( int irq , void * data )
{
struct cros_ec_device * ec_dev = data ;
2017-02-14 22:58:02 +03:00
bool wake_event = true ;
2016-08-10 20:05:24 +03:00
int ret ;
2017-02-14 22:58:02 +03:00
ret = cros_ec_get_next_event ( ec_dev , & wake_event ) ;
/*
* Signal only if wake host events or any interrupt if
* cros_ec_get_next_event ( ) returned an error ( default value for
* wake_event is true )
*/
if ( wake_event & & device_may_wakeup ( ec_dev - > dev ) )
2016-08-10 20:05:24 +03:00
pm_wakeup_event ( ec_dev - > dev , 0 ) ;
if ( ret > 0 )
blocking_notifier_call_chain ( & ec_dev - > event_notifier ,
0 , ec_dev ) ;
return IRQ_HANDLED ;
}
2016-12-16 20:57:37 +03:00
static int cros_ec_sleep_event ( struct cros_ec_device * ec_dev , u8 sleep_event )
{
struct {
struct cros_ec_command msg ;
struct ec_params_host_sleep_event req ;
} __packed buf ;
memset ( & buf , 0 , sizeof ( buf ) ) ;
buf . req . sleep_event = sleep_event ;
buf . msg . command = EC_CMD_HOST_SLEEP_EVENT ;
buf . msg . version = 0 ;
buf . msg . outsize = sizeof ( buf . req ) ;
return cros_ec_cmd_xfer ( ec_dev , & buf . msg ) ;
}
2013-02-26 02:08:37 +04:00
int cros_ec_register ( struct cros_ec_device * ec_dev )
{
struct device * dev = ec_dev - > dev ;
int err = 0 ;
2016-08-10 20:05:24 +03:00
BLOCKING_INIT_NOTIFIER_HEAD ( & ec_dev - > event_notifier ) ;
2015-06-09 14:04:45 +03:00
ec_dev - > max_request = sizeof ( struct ec_params_hello ) ;
ec_dev - > max_response = sizeof ( struct ec_response_get_protocol_info ) ;
ec_dev - > max_passthru = 0 ;
ec_dev - > din = devm_kzalloc ( dev , ec_dev - > din_size , GFP_KERNEL ) ;
if ( ! ec_dev - > din )
return - ENOMEM ;
ec_dev - > dout = devm_kzalloc ( dev , ec_dev - > dout_size , GFP_KERNEL ) ;
if ( ! ec_dev - > dout )
return - ENOMEM ;
2013-02-26 02:08:37 +04:00
2014-09-18 19:18:57 +04:00
mutex_init ( & ec_dev - > lock ) ;
2018-04-18 13:23:58 +03:00
err = cros_ec_query_all ( ec_dev ) ;
if ( err ) {
dev_err ( dev , " Cannot identify the EC: error %d \n " , err ) ;
return err ;
}
2015-06-09 14:04:45 +03:00
2016-08-10 20:05:24 +03:00
if ( ec_dev - > irq ) {
2018-04-18 13:23:59 +03:00
err = devm_request_threaded_irq ( dev , ec_dev - > irq , NULL ,
ec_irq_thread , IRQF_TRIGGER_LOW | IRQF_ONESHOT ,
" chromeos-ec " , ec_dev ) ;
2016-08-10 20:05:24 +03:00
if ( err ) {
dev_err ( dev , " Failed to request IRQ %d: %d " ,
ec_dev - > irq , err ) ;
return err ;
}
}
2015-06-09 14:04:47 +03:00
err = mfd_add_devices ( ec_dev - > dev , PLATFORM_DEVID_AUTO , & ec_cell , 1 ,
2013-02-26 02:08:37 +04:00
NULL , ec_dev - > irq , NULL ) ;
if ( err ) {
2015-06-09 14:04:47 +03:00
dev_err ( dev ,
" Failed to register Embedded Controller subdevice %d \n " ,
err ) ;
2018-04-18 13:23:59 +03:00
return err ;
2013-02-26 02:08:37 +04:00
}
2015-06-09 14:04:47 +03:00
if ( ec_dev - > max_passthru ) {
/*
* Register a PD device as well on top of this device .
* We make the following assumptions :
* - behind an EC , we have a pd
* - only one device added .
* - the EC is responsive at init time ( it is not true for a
* sensor hub .
*/
err = mfd_add_devices ( ec_dev - > dev , PLATFORM_DEVID_AUTO ,
& ec_pd_cell , 1 , NULL , ec_dev - > irq , NULL ) ;
if ( err ) {
dev_err ( dev ,
" Failed to register Power Delivery subdevice %d \n " ,
err ) ;
2018-04-18 13:23:59 +03:00
return err ;
2015-06-09 14:04:47 +03:00
}
}
2015-05-20 12:31:28 +03:00
if ( IS_ENABLED ( CONFIG_OF ) & & dev - > of_node ) {
2017-05-29 18:45:55 +03:00
err = devm_of_platform_populate ( dev ) ;
2015-05-20 12:31:28 +03:00
if ( err ) {
mfd_remove_devices ( dev ) ;
dev_err ( dev , " Failed to register sub-devices \n " ) ;
2018-04-18 13:23:59 +03:00
return err ;
2015-05-20 12:31:28 +03:00
}
}
2016-12-16 20:57:37 +03:00
/*
* Clear sleep event - this will fail harmlessly on platforms that
* don ' t implement the sleep event host command .
*/
err = cros_ec_sleep_event ( ec_dev , 0 ) ;
if ( err < 0 )
dev_dbg ( ec_dev - > dev , " Error %d clearing sleep event to ec " ,
err ) ;
2014-06-18 22:14:03 +04:00
dev_info ( dev , " Chrome EC device registered \n " ) ;
2013-02-26 02:08:37 +04:00
return 0 ;
}
2013-03-20 12:46:15 +04:00
EXPORT_SYMBOL ( cros_ec_register ) ;
2013-02-26 02:08:37 +04:00
int cros_ec_remove ( struct cros_ec_device * ec_dev )
{
mfd_remove_devices ( ec_dev - > dev ) ;
return 0 ;
}
2013-03-20 12:46:15 +04:00
EXPORT_SYMBOL ( cros_ec_remove ) ;
2013-02-26 02:08:37 +04:00
# ifdef CONFIG_PM_SLEEP
int cros_ec_suspend ( struct cros_ec_device * ec_dev )
{
struct device * dev = ec_dev - > dev ;
2016-12-16 20:57:37 +03:00
int ret ;
u8 sleep_event ;
2018-02-23 00:30:52 +03:00
sleep_event = ( ! IS_ENABLED ( CONFIG_ACPI ) | | pm_suspend_via_firmware ( ) ) ?
HOST_SLEEP_EVENT_S3_SUSPEND :
HOST_SLEEP_EVENT_S0IX_SUSPEND ;
2016-12-16 20:57:37 +03:00
ret = cros_ec_sleep_event ( ec_dev , sleep_event ) ;
if ( ret < 0 )
dev_dbg ( ec_dev - > dev , " Error %d sending suspend event to ec " ,
ret ) ;
2013-02-26 02:08:37 +04:00
if ( device_may_wakeup ( dev ) )
ec_dev - > wake_enabled = ! enable_irq_wake ( ec_dev - > irq ) ;
disable_irq ( ec_dev - > irq ) ;
ec_dev - > was_wake_device = ec_dev - > wake_enabled ;
2016-12-16 20:57:36 +03:00
ec_dev - > suspended = true ;
2013-02-26 02:08:37 +04:00
return 0 ;
}
2013-03-20 12:46:15 +04:00
EXPORT_SYMBOL ( cros_ec_suspend ) ;
2013-02-26 02:08:37 +04:00
2018-05-30 22:22:04 +03:00
static void cros_ec_report_events_during_suspend ( struct cros_ec_device * ec_dev )
2016-08-10 20:05:24 +03:00
{
2017-02-14 22:58:02 +03:00
while ( cros_ec_get_next_event ( ec_dev , NULL ) > 0 )
2016-08-10 20:05:24 +03:00
blocking_notifier_call_chain ( & ec_dev - > event_notifier ,
1 , ec_dev ) ;
}
2013-02-26 02:08:37 +04:00
int cros_ec_resume ( struct cros_ec_device * ec_dev )
{
2016-12-16 20:57:37 +03:00
int ret ;
u8 sleep_event ;
2016-12-16 20:57:36 +03:00
ec_dev - > suspended = false ;
2013-02-26 02:08:37 +04:00
enable_irq ( ec_dev - > irq ) ;
2017-01-13 18:04:32 +03:00
sleep_event = ( ! IS_ENABLED ( CONFIG_ACPI ) | | pm_suspend_via_firmware ( ) ) ?
HOST_SLEEP_EVENT_S3_RESUME :
HOST_SLEEP_EVENT_S0IX_RESUME ;
2016-12-16 20:57:37 +03:00
ret = cros_ec_sleep_event ( ec_dev , sleep_event ) ;
if ( ret < 0 )
dev_dbg ( ec_dev - > dev , " Error %d sending resume event to ec " ,
ret ) ;
2013-02-26 02:08:37 +04:00
if ( ec_dev - > wake_enabled ) {
disable_irq_wake ( ec_dev - > irq ) ;
ec_dev - > wake_enabled = 0 ;
}
2018-05-30 22:22:04 +03:00
/*
* Let the mfd devices know about events that occur during
* suspend . This way the clients know what to do with them .
*/
cros_ec_report_events_during_suspend ( ec_dev ) ;
2013-02-26 02:08:37 +04:00
return 0 ;
}
2013-03-20 12:46:15 +04:00
EXPORT_SYMBOL ( cros_ec_resume ) ;
2013-02-26 02:08:37 +04:00
# endif
2014-04-17 23:32:17 +04:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " ChromeOS EC core driver " ) ;