Skip to content

Commit

Permalink
platform/x86: huawei-wmi: Add battery charging thresholds
Browse files Browse the repository at this point in the history
Control battery charge thresholds through the battery API and driver's
attributes.

Setting battery charging thresholds can introduce a race condition with
MACH-WX9 where two or more threads are trying to read/write values
from/to EC memory.

Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
  • Loading branch information
aymanbagabas authored and andy-shev committed Oct 15, 2019
1 parent 090ddd7 commit 355a070
Show file tree
Hide file tree
Showing 2 changed files with 213 additions and 0 deletions.
1 change: 1 addition & 0 deletions drivers/platform/x86/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,7 @@ config INTEL_ATOMISP2_PM

config HUAWEI_WMI
tristate "Huawei WMI laptop extras driver"
depends on ACPI_BATTERY
depends on ACPI_WMI
depends on INPUT
select INPUT_SPARSEKMAP
Expand Down
212 changes: 212 additions & 0 deletions drivers/platform/x86/huawei-wmi.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@
*/

#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/dmi.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/sysfs.h>
#include <linux/wmi.h>
#include <acpi/battery.h>

/*
* Huawei WMI GUIDs
Expand Down Expand Up @@ -49,10 +53,13 @@ struct quirk_entry {
static struct quirk_entry *quirks;

struct huawei_wmi {
bool battery_available;

struct input_dev *idev[2];
struct led_classdev cdev;
struct platform_device *pdev;

struct mutex battery_lock;
struct mutex wmi_lock;
};

Expand Down Expand Up @@ -300,6 +307,208 @@ static void huawei_wmi_leds_setup(struct device *dev)
devm_led_classdev_register(dev, &huawei->cdev);
}

/* Battery protection */

static int huawei_wmi_battery_get(int *start, int *end)
{
u8 ret[0x100];
int err, i;

err = huawei_wmi_cmd(BATTERY_THRESH_GET, ret, 0x100);
if (err)
return err;

/* Find the last two non-zero values. Return status is ignored. */
i = 0xff;
do {
if (start)
*start = ret[i-1];
if (end)
*end = ret[i];
} while (i > 2 && !ret[i--]);

return 0;
}

static int huawei_wmi_battery_set(int start, int end)
{
union hwmi_arg arg;
int err;

if (start < 0 || end > 100)
return -EINVAL;

arg.cmd = BATTERY_THRESH_SET;
arg.args[2] = start;
arg.args[3] = end;

/* This is an edge case were some models turn battery protection
* off without changing their thresholds values. We clear the
* values before turning off protection. Sometimes we need a sleep delay to
* make sure these values make their way to EC memory.
*/
if (quirks && quirks->battery_reset && start == 0 && end == 100) {
err = huawei_wmi_battery_set(0, 0);
if (err)
return err;

msleep(1000);
}

err = huawei_wmi_cmd(arg.cmd, NULL, 0);

return err;
}

static ssize_t charge_control_start_threshold_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int err, start;

err = huawei_wmi_battery_get(&start, NULL);
if (err)
return err;

return sprintf(buf, "%d\n", start);
}

static ssize_t charge_control_end_threshold_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int err, end;

err = huawei_wmi_battery_get(NULL, &end);
if (err)
return err;

return sprintf(buf, "%d\n", end);
}

static ssize_t charge_control_thresholds_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int err, start, end;

err = huawei_wmi_battery_get(&start, &end);
if (err)
return err;

return sprintf(buf, "%d %d\n", start, end);
}

static ssize_t charge_control_start_threshold_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
int err, start, end;

err = huawei_wmi_battery_get(NULL, &end);
if (err)
return err;

if (sscanf(buf, "%d", &start) != 1)
return -EINVAL;

err = huawei_wmi_battery_set(start, end);
if (err)
return err;

return size;
}

static ssize_t charge_control_end_threshold_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
int err, start, end;

err = huawei_wmi_battery_get(&start, NULL);
if (err)
return err;

if (sscanf(buf, "%d", &end) != 1)
return -EINVAL;

err = huawei_wmi_battery_set(start, end);
if (err)
return err;

return size;
}

static ssize_t charge_control_thresholds_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
int err, start, end;

if (sscanf(buf, "%d %d", &start, &end) != 2)
return -EINVAL;

err = huawei_wmi_battery_set(start, end);
if (err)
return err;

return size;
}

static DEVICE_ATTR_RW(charge_control_start_threshold);
static DEVICE_ATTR_RW(charge_control_end_threshold);
static DEVICE_ATTR_RW(charge_control_thresholds);

static int huawei_wmi_battery_add(struct power_supply *battery)
{
/* Huawei laptops come with one battery only */
if (strcmp(battery->desc->name, "BAT") != 1)
return -ENODEV;

device_create_file(&battery->dev, &dev_attr_charge_control_start_threshold);
device_create_file(&battery->dev, &dev_attr_charge_control_end_threshold);

return 0;
}

static int huawei_wmi_battery_remove(struct power_supply *battery)
{
device_remove_file(&battery->dev, &dev_attr_charge_control_start_threshold);
device_remove_file(&battery->dev, &dev_attr_charge_control_end_threshold);

return 0;
}

static struct acpi_battery_hook huawei_wmi_battery_hook = {
.add_battery = huawei_wmi_battery_add,
.remove_battery = huawei_wmi_battery_remove,
.name = "Huawei Battery Extension"
};

static void huawei_wmi_battery_setup(struct device *dev)
{
struct huawei_wmi *huawei = dev_get_drvdata(dev);

huawei->battery_available = true;
if (huawei_wmi_battery_get(NULL, NULL)) {
huawei->battery_available = false;
return;
}

battery_hook_register(&huawei_wmi_battery_hook);
device_create_file(dev, &dev_attr_charge_control_thresholds);
}

static void huawei_wmi_battery_exit(struct device *dev)
{
struct huawei_wmi *huawei = dev_get_drvdata(dev);

if (huawei->battery_available) {
battery_hook_unregister(&huawei_wmi_battery_hook);
device_remove_file(dev, &dev_attr_charge_control_thresholds);
}
}

/* Input */

static void huawei_wmi_process_key(struct input_dev *idev, int code)
Expand Down Expand Up @@ -420,8 +629,10 @@ static int huawei_wmi_probe(struct platform_device *pdev)

if (wmi_has_guid(HWMI_METHOD_GUID)) {
mutex_init(&huawei_wmi->wmi_lock);
mutex_init(&huawei_wmi->battery_lock);

huawei_wmi_leds_setup(&pdev->dev);
huawei_wmi_battery_setup(&pdev->dev);
}

return 0;
Expand All @@ -439,6 +650,7 @@ static int huawei_wmi_remove(struct platform_device *pdev)
}

if (wmi_has_guid(HWMI_METHOD_GUID)) {
huawei_wmi_battery_exit(&pdev->dev);
}

return 0;
Expand Down

0 comments on commit 355a070

Please sign in to comment.