Commit 8c4b1721 authored by Rafael J. Wysocki's avatar Rafael J. Wysocki

Merge branches 'acpi-button', 'acpi-battery' and 'acpi-doc'

* acpi-button:
  ACPI / button: Add document for ACPI control method lid device restrictions
  ACPI / button: Fix an issue in button.lid_init_state=ignore mode

* acpi-battery:
  ACPI / battery: Add sysfs representation after checking _BST

* acpi-doc:
  ACPI / documentation: Use recommended name in GPIO property names
Special Usage Model of the ACPI Control Method Lid Device
Copyright (C) 2016, Intel Corporation
Author: Lv Zheng <lv.zheng@intel.com>
Abstract:
Platforms containing lids convey lid state (open/close) to OSPMs using a
control method lid device. To implement this, the AML tables issue
Notify(lid_device, 0x80) to notify the OSPMs whenever the lid state has
changed. The _LID control method for the lid device must be implemented to
report the "current" state of the lid as either "opened" or "closed".
For most platforms, both the _LID method and the lid notifications are
reliable. However, there are exceptions. In order to work with these
exceptional buggy platforms, special restrictions and expections should be
taken into account. This document describes the restrictions and the
expections of the Linux ACPI lid device driver.
1. Restrictions of the returning value of the _LID control method
The _LID control method is described to return the "current" lid state.
However the word of "current" has ambiguity, some buggy AML tables return
the lid state upon the last lid notification instead of returning the lid
state upon the last _LID evaluation. There won't be difference when the
_LID control method is evaluated during the runtime, the problem is its
initial returning value. When the AML tables implement this control method
with cached value, the initial returning value is likely not reliable.
There are platforms always retun "closed" as initial lid state.
2. Restrictions of the lid state change notifications
There are buggy AML tables never notifying when the lid device state is
changed to "opened". Thus the "opened" notification is not guaranteed. But
it is guaranteed that the AML tables always notify "closed" when the lid
state is changed to "closed". The "closed" notification is normally used to
trigger some system power saving operations on Windows. Since it is fully
tested, it is reliable from all AML tables.
3. Expections for the userspace users of the ACPI lid device driver
The ACPI button driver exports the lid state to the userspace via the
following file:
/proc/acpi/button/lid/LID0/state
This file actually calls the _LID control method described above. And given
the previous explanation, it is not reliable enough on some platforms. So
it is advised for the userspace program to not to solely rely on this file
to determine the actual lid state.
The ACPI button driver emits the following input event to the userspace:
SW_LID
The ACPI lid device driver is implemented to try to deliver the platform
triggered events to the userspace. However, given the fact that the buggy
firmware cannot make sure "opened"/"closed" events are paired, the ACPI
button driver uses the following 3 modes in order not to trigger issues.
If the userspace hasn't been prepared to ignore the unreliable "opened"
events and the unreliable initial state notification, Linux users can use
the following kernel parameters to handle the possible issues:
A. button.lid_init_state=method:
When this option is specified, the ACPI button driver reports the
initial lid state using the returning value of the _LID control method
and whether the "opened"/"closed" events are paired fully relies on the
firmware implementation.
This option can be used to fix some platforms where the returning value
of the _LID control method is reliable but the initial lid state
notification is missing.
This option is the default behavior during the period the userspace
isn't ready to handle the buggy AML tables.
B. button.lid_init_state=open:
When this option is specified, the ACPI button driver always reports the
initial lid state as "opened" and whether the "opened"/"closed" events
are paired fully relies on the firmware implementation.
This may fix some platforms where the returning value of the _LID
control method is not reliable and the initial lid state notification is
missing.
If the userspace has been prepared to ignore the unreliable "opened" events
and the unreliable initial state notification, Linux users should always
use the following kernel parameter:
C. button.lid_init_state=ignore:
When this option is specified, the ACPI button driver never reports the
initial lid state and there is a compensation mechanism implemented to
ensure that the reliable "closed" notifications can always be delievered
to the userspace by always pairing "closed" input events with complement
"opened" input events. But there is still no guarantee that the "opened"
notifications can be delivered to the userspace when the lid is actually
opens given that some AML tables do not send "opened" notifications
reliably.
In this mode, if everything is correctly implemented by the platform
firmware, the old userspace programs should still work. Otherwise, the
new userspace programs are required to work with the ACPI button driver.
This option will be the default behavior after the userspace is ready to
handle the buggy AML tables.
...@@ -28,8 +28,8 @@ index, like the ASL example below shows: ...@@ -28,8 +28,8 @@ index, like the ASL example below shows:
ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
Package () Package ()
{ {
Package () {"reset-gpio", Package() {^BTH, 1, 1, 0 }}, Package () {"reset-gpios", Package() {^BTH, 1, 1, 0 }},
Package () {"shutdown-gpio", Package() {^BTH, 0, 0, 0 }}, Package () {"shutdown-gpios", Package() {^BTH, 0, 0, 0 }},
} }
}) })
} }
...@@ -48,7 +48,7 @@ Since ACPI GpioIo() resource does not have a field saying whether it is ...@@ -48,7 +48,7 @@ Since ACPI GpioIo() resource does not have a field saying whether it is
active low or high, the "active_low" argument can be used here. Setting active low or high, the "active_low" argument can be used here. Setting
it to 1 marks the GPIO as active low. it to 1 marks the GPIO as active low.
In our Bluetooth example the "reset-gpio" refers to the second GpioIo() In our Bluetooth example the "reset-gpios" refers to the second GpioIo()
resource, second pin in that resource with the GPIO number of 31. resource, second pin in that resource with the GPIO number of 31.
ACPI GPIO Mappings Provided by Drivers ACPI GPIO Mappings Provided by Drivers
...@@ -83,8 +83,8 @@ static const struct acpi_gpio_params reset_gpio = { 1, 1, false }; ...@@ -83,8 +83,8 @@ static const struct acpi_gpio_params reset_gpio = { 1, 1, false };
static const struct acpi_gpio_params shutdown_gpio = { 0, 0, false }; static const struct acpi_gpio_params shutdown_gpio = { 0, 0, false };
static const struct acpi_gpio_mapping bluetooth_acpi_gpios[] = { static const struct acpi_gpio_mapping bluetooth_acpi_gpios[] = {
{ "reset-gpio", &reset_gpio, 1 }, { "reset-gpios", &reset_gpio, 1 },
{ "shutdown-gpio", &shutdown_gpio, 1 }, { "shutdown-gpios", &shutdown_gpio, 1 },
{ }, { },
}; };
......
...@@ -733,15 +733,17 @@ static int acpi_battery_update(struct acpi_battery *battery, bool resume) ...@@ -733,15 +733,17 @@ static int acpi_battery_update(struct acpi_battery *battery, bool resume)
return result; return result;
acpi_battery_init_alarm(battery); acpi_battery_init_alarm(battery);
} }
result = acpi_battery_get_state(battery);
if (result)
return result;
acpi_battery_quirks(battery);
if (!battery->bat) { if (!battery->bat) {
result = sysfs_add_battery(battery); result = sysfs_add_battery(battery);
if (result) if (result)
return result; return result;
} }
result = acpi_battery_get_state(battery);
if (result)
return result;
acpi_battery_quirks(battery);
/* /*
* Wakeup the system if battery is critical low * Wakeup the system if battery is critical low
......
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/ */
#define pr_fmt(fmt) "ACPI : button: " fmt
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/init.h> #include <linux/init.h>
...@@ -104,6 +106,8 @@ struct acpi_button { ...@@ -104,6 +106,8 @@ struct acpi_button {
struct input_dev *input; struct input_dev *input;
char phys[32]; /* for input device */ char phys[32]; /* for input device */
unsigned long pushed; unsigned long pushed;
int last_state;
ktime_t last_time;
bool suspended; bool suspended;
}; };
...@@ -111,6 +115,10 @@ static BLOCKING_NOTIFIER_HEAD(acpi_lid_notifier); ...@@ -111,6 +115,10 @@ static BLOCKING_NOTIFIER_HEAD(acpi_lid_notifier);
static struct acpi_device *lid_device; static struct acpi_device *lid_device;
static u8 lid_init_state = ACPI_BUTTON_LID_INIT_METHOD; static u8 lid_init_state = ACPI_BUTTON_LID_INIT_METHOD;
static unsigned long lid_report_interval __read_mostly = 500;
module_param(lid_report_interval, ulong, 0644);
MODULE_PARM_DESC(lid_report_interval, "Interval (ms) between lid key events");
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
FS Interface (/proc) FS Interface (/proc)
-------------------------------------------------------------------------- */ -------------------------------------------------------------------------- */
...@@ -134,10 +142,79 @@ static int acpi_lid_notify_state(struct acpi_device *device, int state) ...@@ -134,10 +142,79 @@ static int acpi_lid_notify_state(struct acpi_device *device, int state)
{ {
struct acpi_button *button = acpi_driver_data(device); struct acpi_button *button = acpi_driver_data(device);
int ret; int ret;
ktime_t next_report;
bool do_update;
/*
* In lid_init_state=ignore mode, if user opens/closes lid
* frequently with "open" missing, and "last_time" is also updated
* frequently, "close" cannot be delivered to the userspace.
* So "last_time" is only updated after a timeout or an actual
* switch.
*/
if (lid_init_state != ACPI_BUTTON_LID_INIT_IGNORE ||
button->last_state != !!state)
do_update = true;
else
do_update = false;
next_report = ktime_add(button->last_time,
ms_to_ktime(lid_report_interval));
if (button->last_state == !!state &&
ktime_after(ktime_get(), next_report)) {
/* Complain the buggy firmware */
pr_warn_once("The lid device is not compliant to SW_LID.\n");
/* input layer checks if event is redundant */ /*
input_report_switch(button->input, SW_LID, !state); * Send the unreliable complement switch event:
input_sync(button->input); *
* On most platforms, the lid device is reliable. However
* there are exceptions:
* 1. Platforms returning initial lid state as "close" by
* default after booting/resuming:
* https://bugzilla.kernel.org/show_bug.cgi?id=89211
* https://bugzilla.kernel.org/show_bug.cgi?id=106151
* 2. Platforms never reporting "open" events:
* https://bugzilla.kernel.org/show_bug.cgi?id=106941
* On these buggy platforms, the usage model of the ACPI
* lid device actually is:
* 1. The initial returning value of _LID may not be
* reliable.
* 2. The open event may not be reliable.
* 3. The close event is reliable.
*
* But SW_LID is typed as input switch event, the input
* layer checks if the event is redundant. Hence if the
* state is not switched, the userspace cannot see this
* platform triggered reliable event. By inserting a
* complement switch event, it then is guaranteed that the
* platform triggered reliable one can always be seen by
* the userspace.
*/
if (lid_init_state == ACPI_BUTTON_LID_INIT_IGNORE) {
do_update = true;
/*
* Do generate complement switch event for "close"
* as "close" is reliable and wrong "open" won't
* trigger unexpected behaviors.
* Do not generate complement switch event for
* "open" as "open" is not reliable and wrong
* "close" will trigger unexpected behaviors.
*/
if (!state) {
input_report_switch(button->input,
SW_LID, state);
input_sync(button->input);
}
}
}
/* Send the platform triggered reliable event */
if (do_update) {
input_report_switch(button->input, SW_LID, !state);
input_sync(button->input);
button->last_state = !!state;
button->last_time = ktime_get();
}
if (state) if (state)
pm_wakeup_event(&device->dev, 0); pm_wakeup_event(&device->dev, 0);
...@@ -411,6 +488,8 @@ static int acpi_button_add(struct acpi_device *device) ...@@ -411,6 +488,8 @@ static int acpi_button_add(struct acpi_device *device)
strcpy(name, ACPI_BUTTON_DEVICE_NAME_LID); strcpy(name, ACPI_BUTTON_DEVICE_NAME_LID);
sprintf(class, "%s/%s", sprintf(class, "%s/%s",
ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_LID); ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_LID);
button->last_state = !!acpi_lid_evaluate_state(device);
button->last_time = ktime_get();
} else { } else {
printk(KERN_ERR PREFIX "Unsupported hid [%s]\n", hid); printk(KERN_ERR PREFIX "Unsupported hid [%s]\n", hid);
error = -ENODEV; error = -ENODEV;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment