blob: 5486ffc6686773ea63a0f0a27588c90c28b5d23d [file] [log] [blame]
/* Copyright 2016 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "acpi.h"
#include "console.h"
#include "gpio.h"
#include "hooks.h"
#include "host_command.h"
#include "lid_angle.h"
#include "stdbool.h"
#include "tablet_mode.h"
#include "timer.h"
#define CPRINTS(format, args...) cprints(CC_MOTION_LID, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_MOTION_LID, format, ## args)
/*
* Other code modules assume that notebook mode (i.e. tablet_mode = false) at
* startup
*/
static bool tablet_mode;
/*
* Console command can force the value of tablet_mode. If tablet_mode_force is
* true, the all external set call for tablet_mode are ignored.
*/
static bool tablet_mode_forced;
/* True if GMR sensor is reporting 360 degrees. */
static bool gmr_sensor_at_360;
/*
* True: all calls to tablet_set_mode are ignored and tablet_mode if forced to 0
* False: all calls to tablet_set_mode are honored
*/
static bool disabled;
int tablet_get_mode(void)
{
return tablet_mode;
}
static void notify_tablet_mode_change(void)
{
CPRINTS("tablet mode %sabled", tablet_mode ? "en" : "dis");
hook_notify(HOOK_TABLET_MODE_CHANGE);
/*
* When tablet mode changes, send an event to ACPI to retrieve
* tablet mode value and send an event to the kernel.
*/
if (IS_ENABLED(CONFIG_HOSTCMD_EVENTS))
host_set_single_event(EC_HOST_EVENT_MODE_CHANGE);
}
void tablet_set_mode(int mode)
{
/* If tablet_mode is forced via a console command, ignore set. */
if (tablet_mode_forced)
return;
if (tablet_mode == !!mode)
return;
if (disabled) {
CPRINTS("Tablet mode set while disabled (ignoring)!");
return;
}
if (gmr_sensor_at_360 && !mode) {
CPRINTS("Ignoring tablet mode exit while gmr sensor "
"reports 360-degree tablet mode.");
return;
}
tablet_mode = !!mode;
notify_tablet_mode_change();
}
void tablet_disable(void)
{
tablet_mode = false;
disabled = true;
}
/* This ifdef can be removed once we clean up past projects which do own init */
#ifdef CONFIG_GMR_TABLET_MODE
#ifndef GMR_TABLET_MODE_GPIO_L
#error GMR_TABLET_MODE_GPIO_L must be defined
#endif
#ifdef CONFIG_DPTF_MOTION_LID_NO_GMR_SENSOR
#error The board has GMR sensor
#endif
static void gmr_tablet_switch_interrupt_debounce(void)
{
gmr_sensor_at_360 = IS_ENABLED(CONFIG_GMR_TABLET_MODE_CUSTOM)
? board_sensor_at_360()
: !gpio_get_level(GMR_TABLET_MODE_GPIO_L);
/*
* DPTF table is updated only when the board enters/exits completely
* flipped tablet mode. If the board has no GMR sensor, we determine
* if the board is in completely-flipped tablet mode by lid angle
* calculation and update DPTF table when lid angle > 300 degrees.
*/
if (IS_ENABLED(CONFIG_HOSTCMD_X86) && IS_ENABLED(CONFIG_DPTF)) {
acpi_dptf_set_profile_num(gmr_sensor_at_360 ?
DPTF_PROFILE_FLIPPED_360_MODE :
DPTF_PROFILE_CLAMSHELL);
}
/*
* 1. Peripherals are disabled only when lid reaches 360 position (It's
* probably already disabled by motion_sense task). We deliberately do
* not enable peripherals when the lid is leaving 360 position. Instead,
* we let motion sense task enable it once it is reaches laptop zone
* (180 or less).
* 2. Similarly, tablet mode is set here when lid reaches 360
* position. It should already be set by motion lid driver. We
* deliberately do not clear tablet mode when lid is leaving 360
* position(if motion lid driver is used). Instead, we let motion lid
* driver to clear it when lid goes into laptop zone.
*/
if (!IS_ENABLED(CONFIG_LID_ANGLE) || gmr_sensor_at_360)
tablet_set_mode(gmr_sensor_at_360);
if (IS_ENABLED(CONFIG_LID_ANGLE_UPDATE) && gmr_sensor_at_360)
lid_angle_peripheral_enable(0);
}
DECLARE_DEFERRED(gmr_tablet_switch_interrupt_debounce);
/* Debounce time for gmr sensor tablet mode interrupt */
#define GMR_SENSOR_DEBOUNCE_US (30 * MSEC)
void gmr_tablet_switch_isr(enum gpio_signal signal)
{
hook_call_deferred(&gmr_tablet_switch_interrupt_debounce_data,
GMR_SENSOR_DEBOUNCE_US);
}
static void gmr_tablet_switch_init(void)
{
/* If this sub-system was disabled before initializing, honor that. */
if (disabled)
return;
gpio_enable_interrupt(GMR_TABLET_MODE_GPIO_L);
/*
* Ensure tablet mode is initialized according to the hardware state
* so that the cached state reflects reality.
*/
gmr_tablet_switch_interrupt_debounce();
}
DECLARE_HOOK(HOOK_INIT, gmr_tablet_switch_init, HOOK_PRIO_DEFAULT);
void gmr_tablet_switch_disable(void)
{
gpio_disable_interrupt(GMR_TABLET_MODE_GPIO_L);
/* Cancel any pending debounce calls */
hook_call_deferred(&gmr_tablet_switch_interrupt_debounce_data, -1);
tablet_disable();
}
#endif
static int command_settabletmode(int argc, char **argv)
{
if (argc != 2)
return EC_ERROR_PARAM_COUNT;
if (argv[1][0] == 'o' && argv[1][1] == 'n') {
tablet_mode = true;
tablet_mode_forced = true;
} else if (argv[1][0] == 'o' && argv[1][1] == 'f') {
tablet_mode = false;
tablet_mode_forced = true;
} else if (argv[1][0] == 'r') {
tablet_mode_forced = false;
} else {
return EC_ERROR_PARAM1;
}
notify_tablet_mode_change();
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(tabletmode, command_settabletmode,
"[on | off | reset]",
"Manually force tablet mode to on, off or reset.");