2020-01-24 15:18:32 -08:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2020 Google LLC
*
* This driver serves as the receiver of cros_ec PD host events .
*/
# include <linux/acpi.h>
# include <linux/module.h>
# include <linux/platform_data/cros_ec_proto.h>
# include <linux/platform_data/cros_usbpd_notify.h>
# include <linux/platform_device.h>
# define DRV_NAME "cros-usbpd-notify"
2020-03-16 01:28:32 -07:00
# define DRV_NAME_PLAT_ACPI "cros-usbpd-notify-acpi"
2020-01-24 15:18:32 -08:00
# define ACPI_DRV_NAME "GOOG0003"
static BLOCKING_NOTIFIER_HEAD ( cros_usbpd_notifier_list ) ;
2020-03-16 01:28:30 -07:00
struct cros_usbpd_notify_data {
struct device * dev ;
struct cros_ec_device * ec ;
struct notifier_block nb ;
} ;
2020-01-24 15:18:32 -08:00
/**
* cros_usbpd_register_notify - Register a notifier callback for PD events .
* @ nb : Notifier block pointer to register
*
* On ACPI platforms this corresponds to host events on the ECPD
* " GOOG0003 " ACPI device . On non - ACPI platforms this will filter mkbp events
* for USB PD events .
*
* Return : 0 on success or negative error code .
*/
int cros_usbpd_register_notify ( struct notifier_block * nb )
{
return blocking_notifier_chain_register ( & cros_usbpd_notifier_list ,
nb ) ;
}
EXPORT_SYMBOL_GPL ( cros_usbpd_register_notify ) ;
/**
* cros_usbpd_unregister_notify - Unregister notifier callback for PD events .
* @ nb : Notifier block pointer to unregister
*
* Unregister a notifier callback that was previously registered with
* cros_usbpd_register_notify ( ) .
*/
void cros_usbpd_unregister_notify ( struct notifier_block * nb )
{
blocking_notifier_chain_unregister ( & cros_usbpd_notifier_list , nb ) ;
}
EXPORT_SYMBOL_GPL ( cros_usbpd_unregister_notify ) ;
2020-03-16 01:28:34 -07:00
/**
* cros_ec_pd_command - Send a command to the EC .
*
* @ ec_dev : EC device
* @ command : EC command
* @ outdata : EC command output data
* @ outsize : Size of outdata
* @ indata : EC command input data
* @ insize : Size of indata
*
* Return : > = 0 on success , negative error number on failure .
*/
static int cros_ec_pd_command ( struct cros_ec_device * ec_dev ,
int command ,
uint8_t * outdata ,
int outsize ,
uint8_t * indata ,
int insize )
{
struct cros_ec_command * msg ;
int ret ;
msg = kzalloc ( sizeof ( * msg ) + max ( insize , outsize ) , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
msg - > command = command ;
msg - > outsize = outsize ;
msg - > insize = insize ;
if ( outsize )
memcpy ( msg - > data , outdata , outsize ) ;
ret = cros_ec_cmd_xfer_status ( ec_dev , msg ) ;
if ( ret < 0 )
goto error ;
if ( insize )
memcpy ( indata , msg - > data , insize ) ;
error :
kfree ( msg ) ;
return ret ;
}
static void cros_usbpd_get_event_and_notify ( struct device * dev ,
struct cros_ec_device * ec_dev )
{
struct ec_response_host_event_status host_event_status ;
u32 event = 0 ;
int ret ;
/*
* We still send a 0 event out to older devices which don ' t
* have the updated device heirarchy .
*/
if ( ! ec_dev ) {
dev_dbg ( dev ,
" EC device inaccessible; sending 0 event status. \n " ) ;
goto send_notify ;
}
/* Check for PD host events on EC. */
ret = cros_ec_pd_command ( ec_dev , EC_CMD_PD_HOST_EVENT_STATUS ,
NULL , 0 ,
( uint8_t * ) & host_event_status ,
sizeof ( host_event_status ) ) ;
if ( ret < 0 ) {
dev_warn ( dev , " Can't get host event status (err: %d) \n " , ret ) ;
goto send_notify ;
}
event = host_event_status . status ;
send_notify :
blocking_notifier_call_chain ( & cros_usbpd_notifier_list , event , NULL ) ;
}
2020-01-24 15:18:32 -08:00
# ifdef CONFIG_ACPI
2020-03-16 01:28:32 -07:00
static void cros_usbpd_notify_acpi ( acpi_handle device , u32 event , void * data )
2020-01-24 15:18:32 -08:00
{
2020-03-16 01:28:34 -07:00
struct cros_usbpd_notify_data * pdnotify = data ;
cros_usbpd_get_event_and_notify ( pdnotify - > dev , pdnotify - > ec ) ;
2020-03-16 01:28:32 -07:00
}
static int cros_usbpd_notify_probe_acpi ( struct platform_device * pdev )
{
struct cros_usbpd_notify_data * pdnotify ;
struct device * dev = & pdev - > dev ;
struct acpi_device * adev ;
struct cros_ec_device * ec_dev ;
acpi_status status ;
adev = ACPI_COMPANION ( dev ) ;
pdnotify = devm_kzalloc ( dev , sizeof ( * pdnotify ) , GFP_KERNEL ) ;
if ( ! pdnotify )
return - ENOMEM ;
/* Get the EC device pointer needed to talk to the EC. */
ec_dev = dev_get_drvdata ( dev - > parent ) ;
if ( ! ec_dev ) {
/*
* We continue even for older devices which don ' t have the
* correct device heirarchy , namely , GOOG0003 is a child
* of GOOG0004 .
*/
dev_warn ( dev , " Couldn't get Chrome EC device pointer. \n " ) ;
}
pdnotify - > dev = dev ;
pdnotify - > ec = ec_dev ;
status = acpi_install_notify_handler ( adev - > handle ,
ACPI_ALL_NOTIFY ,
cros_usbpd_notify_acpi ,
pdnotify ) ;
if ( ACPI_FAILURE ( status ) ) {
dev_warn ( dev , " Failed to register notify handler %08x \n " ,
status ) ;
return - EINVAL ;
}
2020-01-24 15:18:32 -08:00
return 0 ;
}
2020-03-16 01:28:32 -07:00
static int cros_usbpd_notify_remove_acpi ( struct platform_device * pdev )
2020-01-24 15:18:32 -08:00
{
2020-03-16 01:28:32 -07:00
struct device * dev = & pdev - > dev ;
struct acpi_device * adev = ACPI_COMPANION ( dev ) ;
acpi_remove_notify_handler ( adev - > handle , ACPI_ALL_NOTIFY ,
cros_usbpd_notify_acpi ) ;
return 0 ;
2020-01-24 15:18:32 -08:00
}
static const struct acpi_device_id cros_usbpd_notify_acpi_device_ids [ ] = {
{ ACPI_DRV_NAME , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( acpi , cros_usbpd_notify_acpi_device_ids ) ;
2020-03-16 01:28:32 -07:00
static struct platform_driver cros_usbpd_notify_acpi_driver = {
. driver = {
. name = DRV_NAME_PLAT_ACPI ,
. acpi_match_table = cros_usbpd_notify_acpi_device_ids ,
2020-01-24 15:18:32 -08:00
} ,
2020-03-16 01:28:32 -07:00
. probe = cros_usbpd_notify_probe_acpi ,
. remove = cros_usbpd_notify_remove_acpi ,
2020-01-24 15:18:32 -08:00
} ;
# endif /* CONFIG_ACPI */
static int cros_usbpd_notify_plat ( struct notifier_block * nb ,
unsigned long queued_during_suspend ,
void * data )
{
2020-03-16 01:28:34 -07:00
struct cros_usbpd_notify_data * pdnotify = container_of ( nb ,
struct cros_usbpd_notify_data , nb ) ;
2020-01-24 15:18:32 -08:00
struct cros_ec_device * ec_dev = ( struct cros_ec_device * ) data ;
u32 host_event = cros_ec_get_host_event ( ec_dev ) ;
if ( ! host_event )
2020-03-04 15:21:08 -08:00
return NOTIFY_DONE ;
2020-01-24 15:18:32 -08:00
2021-04-14 14:45:24 +08:00
if ( host_event & ( EC_HOST_EVENT_MASK ( EC_HOST_EVENT_PD_MCU ) |
EC_HOST_EVENT_MASK ( EC_HOST_EVENT_USB_MUX ) ) ) {
2020-03-16 01:28:34 -07:00
cros_usbpd_get_event_and_notify ( pdnotify - > dev , ec_dev ) ;
2020-01-24 15:18:32 -08:00
return NOTIFY_OK ;
}
return NOTIFY_DONE ;
}
static int cros_usbpd_notify_probe_plat ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct cros_ec_dev * ecdev = dev_get_drvdata ( dev - > parent ) ;
2020-03-16 01:28:30 -07:00
struct cros_usbpd_notify_data * pdnotify ;
2020-01-24 15:18:32 -08:00
int ret ;
2020-03-16 01:28:30 -07:00
pdnotify = devm_kzalloc ( dev , sizeof ( * pdnotify ) , GFP_KERNEL ) ;
if ( ! pdnotify )
2020-01-24 15:18:32 -08:00
return - ENOMEM ;
2020-03-16 01:28:30 -07:00
pdnotify - > dev = dev ;
pdnotify - > ec = ecdev - > ec_dev ;
pdnotify - > nb . notifier_call = cros_usbpd_notify_plat ;
dev_set_drvdata ( dev , pdnotify ) ;
2020-01-24 15:18:32 -08:00
ret = blocking_notifier_chain_register ( & ecdev - > ec_dev - > event_notifier ,
2020-03-16 01:28:30 -07:00
& pdnotify - > nb ) ;
2020-01-24 15:18:32 -08:00
if ( ret < 0 ) {
dev_err ( dev , " Failed to register notifier \n " ) ;
return ret ;
}
return 0 ;
}
static int cros_usbpd_notify_remove_plat ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct cros_ec_dev * ecdev = dev_get_drvdata ( dev - > parent ) ;
2020-03-16 01:28:30 -07:00
struct cros_usbpd_notify_data * pdnotify =
( struct cros_usbpd_notify_data * ) dev_get_drvdata ( dev ) ;
2020-01-24 15:18:32 -08:00
2020-03-16 01:28:30 -07:00
blocking_notifier_chain_unregister ( & ecdev - > ec_dev - > event_notifier ,
& pdnotify - > nb ) ;
2020-01-24 15:18:32 -08:00
return 0 ;
}
static struct platform_driver cros_usbpd_notify_plat_driver = {
. driver = {
. name = DRV_NAME ,
} ,
. probe = cros_usbpd_notify_probe_plat ,
. remove = cros_usbpd_notify_remove_plat ,
} ;
static int __init cros_usbpd_notify_init ( void )
{
int ret ;
ret = platform_driver_register ( & cros_usbpd_notify_plat_driver ) ;
if ( ret < 0 )
return ret ;
# ifdef CONFIG_ACPI
2020-03-16 01:28:32 -07:00
platform_driver_register ( & cros_usbpd_notify_acpi_driver ) ;
2020-01-24 15:18:32 -08:00
# endif
return 0 ;
}
static void __exit cros_usbpd_notify_exit ( void )
{
# ifdef CONFIG_ACPI
2020-03-16 01:28:32 -07:00
platform_driver_unregister ( & cros_usbpd_notify_acpi_driver ) ;
2020-01-24 15:18:32 -08:00
# endif
platform_driver_unregister ( & cros_usbpd_notify_plat_driver ) ;
}
module_init ( cros_usbpd_notify_init ) ;
module_exit ( cros_usbpd_notify_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " ChromeOS power delivery notifier device " ) ;
MODULE_AUTHOR ( " Jon Flatley <jflat@chromium.org> " ) ;
MODULE_ALIAS ( " platform: " DRV_NAME ) ;