2018-06-05 14:05:29 -07:00
// SPDX-License-Identifier: GPL-2.0
2018-05-23 12:00:47 -07:00
// Driver to detect Tablet Mode for ChromeOS convertible.
//
// Copyright (C) 2017 Google, Inc.
// Author: Gwendal Grignou <gwendal@chromium.org>
2018-06-05 14:05:29 -07:00
//
// On Chromebook using ACPI, this device listens for notification
// from GOOG0006 and issue method TBMC to retrieve the status.
//
// GOOG0006 issues the notification when it receives EC_HOST_EVENT_MODE_CHANGE
// from the EC.
// Method TBMC reads EC_ACPI_MEM_DEVICE_ORIENTATION byte from the shared
// memory region.
2017-01-30 15:47:22 -08:00
# include <linux/acpi.h>
# include <linux/input.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/printk.h>
# define DRV_NAME "chromeos_tbmc"
# define ACPI_DRV_NAME "GOOG0006"
static int chromeos_tbmc_query_switch ( struct acpi_device * adev ,
struct input_dev * idev )
{
unsigned long long state ;
acpi_status status ;
status = acpi_evaluate_integer ( adev - > handle , " TBMC " , NULL , & state ) ;
if ( ACPI_FAILURE ( status ) )
return - ENODEV ;
/* input layer checks if event is redundant */
input_report_switch ( idev , SW_TABLET_MODE , state ) ;
input_sync ( idev ) ;
return 0 ;
}
static __maybe_unused int chromeos_tbmc_resume ( struct device * dev )
{
struct acpi_device * adev = to_acpi_device ( dev ) ;
return chromeos_tbmc_query_switch ( adev , adev - > driver_data ) ;
}
static void chromeos_tbmc_notify ( struct acpi_device * adev , u32 event )
{
2019-08-30 16:14:04 -07:00
acpi_pm_wakeup_event ( & adev - > dev ) ;
2017-01-30 15:47:22 -08:00
switch ( event ) {
case 0x80 :
chromeos_tbmc_query_switch ( adev , adev - > driver_data ) ;
break ;
default :
dev_err ( & adev - > dev , " Unexpected event: 0x%08X \n " , event ) ;
}
}
static int chromeos_tbmc_open ( struct input_dev * idev )
{
struct acpi_device * adev = input_get_drvdata ( idev ) ;
return chromeos_tbmc_query_switch ( adev , idev ) ;
}
static int chromeos_tbmc_add ( struct acpi_device * adev )
{
struct input_dev * idev ;
struct device * dev = & adev - > dev ;
int ret ;
idev = devm_input_allocate_device ( dev ) ;
if ( ! idev )
return - ENOMEM ;
idev - > name = " Tablet Mode Switch " ;
idev - > phys = acpi_device_hid ( adev ) ;
idev - > id . bustype = BUS_HOST ;
idev - > id . version = 1 ;
idev - > id . product = 0 ;
idev - > open = chromeos_tbmc_open ;
input_set_drvdata ( idev , adev ) ;
adev - > driver_data = idev ;
input_set_capability ( idev , EV_SW , SW_TABLET_MODE ) ;
ret = input_register_device ( idev ) ;
if ( ret ) {
dev_err ( dev , " cannot register input device \n " ) ;
return ret ;
}
2019-08-30 16:14:04 -07:00
device_init_wakeup ( dev , true ) ;
2017-01-30 15:47:22 -08:00
return 0 ;
}
static const struct acpi_device_id chromeos_tbmc_acpi_device_ids [ ] = {
{ ACPI_DRV_NAME , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( acpi , chromeos_tbmc_acpi_device_ids ) ;
2018-10-08 15:20:41 -07:00
static SIMPLE_DEV_PM_OPS ( chromeos_tbmc_pm_ops , NULL ,
2017-01-30 15:47:22 -08:00
chromeos_tbmc_resume ) ;
static struct acpi_driver chromeos_tbmc_driver = {
. name = DRV_NAME ,
. class = DRV_NAME ,
. ids = chromeos_tbmc_acpi_device_ids ,
. ops = {
. add = chromeos_tbmc_add ,
. notify = chromeos_tbmc_notify ,
} ,
. drv . pm = & chromeos_tbmc_pm_ops ,
} ;
module_acpi_driver ( chromeos_tbmc_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_DESCRIPTION ( " ChromeOS ACPI tablet switch driver " ) ;