2018-03-15 04:28:25 +03:00
// SPDX-License-Identifier: GPL-2.0+
/*
* HID driver for Google Hammer device .
*
* Copyright ( c ) 2017 Google Inc .
* Author : Wei - Ning Huang < wnhuang @ google . 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 .
*/
2018-10-05 21:41:14 +03:00
# include <linux/acpi.h>
2018-03-15 04:28:25 +03:00
# include <linux/hid.h>
# include <linux/leds.h>
2018-10-05 21:41:14 +03:00
# include <linux/mfd/cros_ec.h>
# include <linux/mfd/cros_ec_commands.h>
2018-03-15 04:28:25 +03:00
# include <linux/module.h>
2018-10-05 21:41:14 +03:00
# include <linux/platform_device.h>
# include <linux/pm_wakeup.h>
# include <asm/unaligned.h>
2018-03-15 04:28:25 +03:00
# include "hid-ids.h"
2018-10-05 21:41:14 +03:00
/*
* C ( hrome ) B ( ase ) A ( ttached ) S ( witch ) - switch exported by Chrome EC and reporting
* state of the " Whiskers " base - attached or detached . Whiskers USB device also
* reports position of the keyboard - folded or not . Combining base state and
* position allows us to generate proper " Tablet mode " events .
*/
struct cbas_ec {
struct device * dev ; /* The platform device (EC) */
struct input_dev * input ;
bool base_present ;
struct notifier_block notifier ;
} ;
2018-03-15 04:28:25 +03:00
2018-10-05 21:41:14 +03:00
static struct cbas_ec cbas_ec ;
static DEFINE_SPINLOCK ( cbas_ec_lock ) ;
static DEFINE_MUTEX ( cbas_ec_reglock ) ;
static bool cbas_parse_base_state ( const void * data )
{
u32 switches = get_unaligned_le32 ( data ) ;
return ! ! ( switches & BIT ( EC_MKBP_BASE_ATTACHED ) ) ;
}
static int cbas_ec_query_base ( struct cros_ec_device * ec_dev , bool get_state ,
bool * state )
{
struct ec_params_mkbp_info * params ;
struct cros_ec_command * msg ;
int ret ;
msg = kzalloc ( sizeof ( * msg ) + max ( sizeof ( u32 ) , sizeof ( * params ) ) ,
GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
msg - > command = EC_CMD_MKBP_INFO ;
msg - > version = 1 ;
msg - > outsize = sizeof ( * params ) ;
msg - > insize = sizeof ( u32 ) ;
params = ( struct ec_params_mkbp_info * ) msg - > data ;
params - > info_type = get_state ?
EC_MKBP_INFO_CURRENT : EC_MKBP_INFO_SUPPORTED ;
params - > event_type = EC_MKBP_EVENT_SWITCH ;
ret = cros_ec_cmd_xfer_status ( ec_dev , msg ) ;
if ( ret > = 0 ) {
if ( ret ! = sizeof ( u32 ) ) {
dev_warn ( ec_dev - > dev , " wrong result size: %d != %zu \n " ,
ret , sizeof ( u32 ) ) ;
ret = - EPROTO ;
} else {
* state = cbas_parse_base_state ( msg - > data ) ;
ret = 0 ;
}
}
kfree ( msg ) ;
return ret ;
}
static int cbas_ec_notify ( struct notifier_block * nb ,
unsigned long queued_during_suspend ,
void * _notify )
{
struct cros_ec_device * ec = _notify ;
unsigned long flags ;
bool base_present ;
if ( ec - > event_data . event_type = = EC_MKBP_EVENT_SWITCH ) {
base_present = cbas_parse_base_state (
& ec - > event_data . data . switches ) ;
dev_dbg ( cbas_ec . dev ,
" %s: base: %d \n " , __func__ , base_present ) ;
if ( device_may_wakeup ( cbas_ec . dev ) | |
! queued_during_suspend ) {
pm_wakeup_event ( cbas_ec . dev , 0 ) ;
spin_lock_irqsave ( & cbas_ec_lock , flags ) ;
/*
* While input layer dedupes the events , we do not want
* to disrupt the state reported by the base by
* overriding it with state reported by the LID . Only
* report changes , as we assume that on attach the base
* is not folded .
*/
if ( base_present ! = cbas_ec . base_present ) {
input_report_switch ( cbas_ec . input ,
SW_TABLET_MODE ,
! base_present ) ;
input_sync ( cbas_ec . input ) ;
cbas_ec . base_present = base_present ;
}
spin_unlock_irqrestore ( & cbas_ec_lock , flags ) ;
}
}
return NOTIFY_OK ;
}
static __maybe_unused int cbas_ec_resume ( struct device * dev )
{
struct cros_ec_device * ec = dev_get_drvdata ( dev - > parent ) ;
bool base_present ;
int error ;
error = cbas_ec_query_base ( ec , true , & base_present ) ;
if ( error ) {
dev_warn ( dev , " failed to fetch base state on resume: %d \n " ,
error ) ;
} else {
spin_lock_irq ( & cbas_ec_lock ) ;
cbas_ec . base_present = base_present ;
/*
* Only report if base is disconnected . If base is connected ,
* it will resend its state on resume , and we ' ll update it
* in hammer_event ( ) .
*/
if ( ! cbas_ec . base_present ) {
input_report_switch ( cbas_ec . input , SW_TABLET_MODE , 1 ) ;
input_sync ( cbas_ec . input ) ;
}
spin_unlock_irq ( & cbas_ec_lock ) ;
}
return 0 ;
}
2018-10-09 11:43:39 +03:00
static SIMPLE_DEV_PM_OPS ( cbas_ec_pm_ops , NULL , cbas_ec_resume ) ;
2018-10-05 21:41:14 +03:00
static void cbas_ec_set_input ( struct input_dev * input )
{
/* Take the lock so hammer_event() does not race with us here */
spin_lock_irq ( & cbas_ec_lock ) ;
cbas_ec . input = input ;
spin_unlock_irq ( & cbas_ec_lock ) ;
}
static int __cbas_ec_probe ( struct platform_device * pdev )
{
struct cros_ec_device * ec = dev_get_drvdata ( pdev - > dev . parent ) ;
struct input_dev * input ;
bool base_supported ;
int error ;
error = cbas_ec_query_base ( ec , false , & base_supported ) ;
if ( error )
return error ;
if ( ! base_supported )
return - ENXIO ;
input = devm_input_allocate_device ( & pdev - > dev ) ;
if ( ! input )
return - ENOMEM ;
input - > name = " Whiskers Tablet Mode Switch " ;
input - > id . bustype = BUS_HOST ;
input_set_capability ( input , EV_SW , SW_TABLET_MODE ) ;
error = input_register_device ( input ) ;
if ( error ) {
dev_err ( & pdev - > dev , " cannot register input device: %d \n " ,
error ) ;
return error ;
}
/* Seed the state */
error = cbas_ec_query_base ( ec , true , & cbas_ec . base_present ) ;
if ( error ) {
dev_err ( & pdev - > dev , " cannot query base state: %d \n " , error ) ;
return error ;
}
input_report_switch ( input , SW_TABLET_MODE , ! cbas_ec . base_present ) ;
cbas_ec_set_input ( input ) ;
cbas_ec . dev = & pdev - > dev ;
cbas_ec . notifier . notifier_call = cbas_ec_notify ;
error = blocking_notifier_chain_register ( & ec - > event_notifier ,
& cbas_ec . notifier ) ;
if ( error ) {
dev_err ( & pdev - > dev , " cannot register notifier: %d \n " , error ) ;
cbas_ec_set_input ( NULL ) ;
return error ;
}
device_init_wakeup ( & pdev - > dev , true ) ;
return 0 ;
}
static int cbas_ec_probe ( struct platform_device * pdev )
{
int retval ;
mutex_lock ( & cbas_ec_reglock ) ;
if ( cbas_ec . input ) {
retval = - EBUSY ;
goto out ;
}
retval = __cbas_ec_probe ( pdev ) ;
out :
mutex_unlock ( & cbas_ec_reglock ) ;
return retval ;
}
static int cbas_ec_remove ( struct platform_device * pdev )
{
struct cros_ec_device * ec = dev_get_drvdata ( pdev - > dev . parent ) ;
mutex_lock ( & cbas_ec_reglock ) ;
blocking_notifier_chain_unregister ( & ec - > event_notifier ,
& cbas_ec . notifier ) ;
cbas_ec_set_input ( NULL ) ;
mutex_unlock ( & cbas_ec_reglock ) ;
return 0 ;
}
static const struct acpi_device_id cbas_ec_acpi_ids [ ] = {
{ " GOOG000B " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( acpi , cbas_ec_acpi_ids ) ;
static struct platform_driver cbas_ec_driver = {
. probe = cbas_ec_probe ,
. remove = cbas_ec_remove ,
. driver = {
. name = " cbas_ec " ,
. acpi_match_table = ACPI_PTR ( cbas_ec_acpi_ids ) ,
. pm = & cbas_ec_pm_ops ,
} ,
} ;
# define MAX_BRIGHTNESS 100
2018-03-15 04:28:25 +03:00
struct hammer_kbd_leds {
struct led_classdev cdev ;
struct hid_device * hdev ;
u8 buf [ 2 ] ____cacheline_aligned ;
} ;
static int hammer_kbd_brightness_set_blocking ( struct led_classdev * cdev ,
enum led_brightness br )
{
struct hammer_kbd_leds * led = container_of ( cdev ,
struct hammer_kbd_leds ,
cdev ) ;
int ret ;
led - > buf [ 0 ] = 0 ;
led - > buf [ 1 ] = br ;
2018-03-28 09:16:10 +03:00
/*
* Request USB HID device to be in Full On mode , so that sending
* hardware output report and hardware raw request won ' t fail .
*/
ret = hid_hw_power ( led - > hdev , PM_HINT_FULLON ) ;
if ( ret < 0 ) {
hid_err ( led - > hdev , " failed: device not resumed %d \n " , ret ) ;
return ret ;
}
2018-03-15 04:28:25 +03:00
ret = hid_hw_output_report ( led - > hdev , led - > buf , sizeof ( led - > buf ) ) ;
if ( ret = = - ENOSYS )
ret = hid_hw_raw_request ( led - > hdev , 0 , led - > buf ,
sizeof ( led - > buf ) ,
HID_OUTPUT_REPORT ,
HID_REQ_SET_REPORT ) ;
if ( ret < 0 )
hid_err ( led - > hdev , " failed to set keyboard backlight: %d \n " ,
ret ) ;
2018-03-28 09:16:10 +03:00
/* Request USB HID device back to Normal Mode. */
hid_hw_power ( led - > hdev , PM_HINT_NORMAL ) ;
2018-03-15 04:28:25 +03:00
return ret ;
}
static int hammer_register_leds ( struct hid_device * hdev )
{
struct hammer_kbd_leds * kbd_backlight ;
kbd_backlight = devm_kzalloc ( & hdev - > dev ,
sizeof ( * kbd_backlight ) ,
GFP_KERNEL ) ;
if ( ! kbd_backlight )
return - ENOMEM ;
kbd_backlight - > hdev = hdev ;
kbd_backlight - > cdev . name = " hammer::kbd_backlight " ;
kbd_backlight - > cdev . max_brightness = MAX_BRIGHTNESS ;
kbd_backlight - > cdev . brightness_set_blocking =
hammer_kbd_brightness_set_blocking ;
kbd_backlight - > cdev . flags = LED_HW_PLUGGABLE ;
/* Set backlight to 0% initially. */
hammer_kbd_brightness_set_blocking ( & kbd_backlight - > cdev , 0 ) ;
return devm_led_classdev_register ( & hdev - > dev , & kbd_backlight - > cdev ) ;
}
2018-10-05 21:41:14 +03:00
# define HID_UP_GOOGLEVENDOR 0xffd10000
# define HID_VD_KBD_FOLDED 0x00000019
# define WHISKERS_KBD_FOLDED (HID_UP_GOOGLEVENDOR | HID_VD_KBD_FOLDED)
/* HID usage for keyboard backlight (Alphanumeric display brightness) */
# define HID_AD_BRIGHTNESS 0x00140046
static int hammer_input_mapping ( struct hid_device * hdev , struct hid_input * hi ,
struct hid_field * field ,
struct hid_usage * usage ,
unsigned long * * bit , int * max )
2018-03-15 04:28:25 +03:00
{
2018-10-05 21:41:14 +03:00
if ( hdev - > product = = USB_DEVICE_ID_GOOGLE_WHISKERS & &
usage - > hid = = WHISKERS_KBD_FOLDED ) {
/*
* We do not want to have this usage mapped as it will get
* mixed in with " base attached " signal and delivered over
* separate input device for tablet switch mode .
*/
return - 1 ;
}
return 0 ;
}
static int hammer_event ( struct hid_device * hid , struct hid_field * field ,
struct hid_usage * usage , __s32 value )
{
unsigned long flags ;
if ( hid - > product = = USB_DEVICE_ID_GOOGLE_WHISKERS & &
usage - > hid = = WHISKERS_KBD_FOLDED ) {
spin_lock_irqsave ( & cbas_ec_lock , flags ) ;
hid_dbg ( hid , " %s: base: %d, folded: %d \n " , __func__ ,
cbas_ec . base_present , value ) ;
/*
* We should not get event if base is detached , but in case
* we happen to service HID and EC notifications out of order
* let ' s still check the " base present " flag .
*/
if ( cbas_ec . input & & cbas_ec . base_present ) {
input_report_switch ( cbas_ec . input ,
SW_TABLET_MODE , value ) ;
input_sync ( cbas_ec . input ) ;
}
spin_unlock_irqrestore ( & cbas_ec_lock , flags ) ;
return 1 ; /* We handled this event */
}
return 0 ;
}
static bool hammer_is_keyboard_interface ( struct hid_device * hdev )
{
struct hid_report_enum * re = & hdev - > report_enum [ HID_INPUT_REPORT ] ;
2018-03-15 04:28:25 +03:00
struct hid_report * report ;
2018-10-05 21:41:14 +03:00
list_for_each_entry ( report , & re - > report_list , list )
if ( report - > application = = HID_GD_KEYBOARD )
return true ;
2018-03-15 04:28:25 +03:00
2018-10-05 21:41:14 +03:00
return false ;
}
static bool hammer_has_backlight_control ( struct hid_device * hdev )
{
struct hid_report_enum * re = & hdev - > report_enum [ HID_OUTPUT_REPORT ] ;
struct hid_report * report ;
int i , j ;
2018-03-15 04:28:25 +03:00
2018-10-05 21:41:14 +03:00
list_for_each_entry ( report , & re - > report_list , list ) {
if ( report - > application ! = HID_GD_KEYBOARD )
continue ;
2018-03-15 04:28:25 +03:00
2018-10-05 21:41:14 +03:00
for ( i = 0 ; i < report - > maxfield ; i + + ) {
struct hid_field * field = report - > field [ i ] ;
for ( j = 0 ; j < field - > maxusage ; j + + )
if ( field - > usage [ j ] . hid = = HID_AD_BRIGHTNESS )
return true ;
}
}
return false ;
}
static int hammer_probe ( struct hid_device * hdev ,
const struct hid_device_id * id )
{
int error ;
/*
* We always want to poll for , and handle tablet mode events from
* Whiskers , even when nobody has opened the input device . This also
* prevents the hid core from dropping early tablet mode events from
* the device .
*/
if ( hdev - > product = = USB_DEVICE_ID_GOOGLE_WHISKERS & &
hammer_is_keyboard_interface ( hdev ) )
hdev - > quirks | = HID_QUIRK_ALWAYS_POLL ;
error = hid_parse ( hdev ) ;
if ( error )
return error ;
error = hid_hw_start ( hdev , HID_CONNECT_DEFAULT ) ;
if ( error )
return error ;
if ( hammer_has_backlight_control ( hdev ) ) {
error = hammer_register_leds ( hdev ) ;
if ( error )
2018-03-15 04:28:25 +03:00
hid_warn ( hdev ,
" Failed to register keyboard backlight: %d \n " ,
2018-10-05 21:41:14 +03:00
error ) ;
2018-03-15 04:28:25 +03:00
}
return 0 ;
}
2018-10-05 21:41:14 +03:00
2018-03-15 04:28:25 +03:00
static const struct hid_device_id hammer_devices [ ] = {
{ HID_DEVICE ( BUS_USB , HID_GROUP_GENERIC ,
USB_VENDOR_ID_GOOGLE , USB_DEVICE_ID_GOOGLE_HAMMER ) } ,
{ HID_DEVICE ( BUS_USB , HID_GROUP_GENERIC ,
USB_VENDOR_ID_GOOGLE , USB_DEVICE_ID_GOOGLE_STAFF ) } ,
{ HID_DEVICE ( BUS_USB , HID_GROUP_GENERIC ,
USB_VENDOR_ID_GOOGLE , USB_DEVICE_ID_GOOGLE_WAND ) } ,
2018-06-08 03:14:50 +03:00
{ HID_DEVICE ( BUS_USB , HID_GROUP_GENERIC ,
USB_VENDOR_ID_GOOGLE , USB_DEVICE_ID_GOOGLE_WHISKERS ) } ,
2018-03-15 04:28:25 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( hid , hammer_devices ) ;
static struct hid_driver hammer_driver = {
. name = " hammer " ,
. id_table = hammer_devices ,
2018-10-05 21:41:14 +03:00
. probe = hammer_probe ,
. input_mapping = hammer_input_mapping ,
. event = hammer_event ,
2018-03-15 04:28:25 +03:00
} ;
2018-10-05 21:41:14 +03:00
static int __init hammer_init ( void )
{
int error ;
error = platform_driver_register ( & cbas_ec_driver ) ;
if ( error )
return error ;
error = hid_register_driver ( & hammer_driver ) ;
if ( error ) {
platform_driver_unregister ( & cbas_ec_driver ) ;
return error ;
}
return 0 ;
}
module_init ( hammer_init ) ;
static void __exit hammer_exit ( void )
{
hid_unregister_driver ( & hammer_driver ) ;
platform_driver_unregister ( & cbas_ec_driver ) ;
}
module_exit ( hammer_exit ) ;
2018-03-15 04:28:25 +03:00
MODULE_LICENSE ( " GPL " ) ;