Commit 76f7eba3 authored by Tim Crawford's avatar Tim Crawford Committed by Hans de Goede
Browse files

platform/x86: system76_acpi: Add battery charging thresholds



System76 laptops running open source EC firmware support configuring
charging thresholds through ACPI methods. Expose this functionality
through the standard sysfs entries charge_control_{start,end}_threshold.

Signed-off-by: default avatarTim Crawford <tcrawford@system76.com>
Link: https://lore.kernel.org/r/20211006202202.7479-4-tcrawford@system76.com


Reviewed-by: default avatarHans de Goede <hdegoede@redhat.com>
Signed-off-by: default avatarHans de Goede <hdegoede@redhat.com>
parent 0de30fc6
Loading
Loading
Loading
Loading
+157 −0
Original line number Diff line number Diff line
@@ -18,8 +18,12 @@
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/pci_ids.h>
#include <linux/power_supply.h>
#include <linux/sysfs.h>
#include <linux/types.h>

#include <acpi/battery.h>

struct system76_data {
	struct acpi_device *acpi_dev;
	struct led_classdev ap_led;
@@ -143,6 +147,154 @@ static int system76_set(struct system76_data *data, char *method, int value)
		return -1;
}

#define BATTERY_THRESHOLD_INVALID	0xFF

enum {
	THRESHOLD_START,
	THRESHOLD_END,
};

static ssize_t battery_get_threshold(int which, char *buf)
{
	struct acpi_object_list input;
	union acpi_object param;
	acpi_handle handle;
	acpi_status status;
	unsigned long long ret = BATTERY_THRESHOLD_INVALID;

	handle = ec_get_handle();
	if (!handle)
		return -ENODEV;

	input.count = 1;
	input.pointer = &param;
	// Start/stop selection
	param.type = ACPI_TYPE_INTEGER;
	param.integer.value = which;

	status = acpi_evaluate_integer(handle, "GBCT", &input, &ret);
	if (ACPI_FAILURE(status))
		return -EIO;
	if (ret == BATTERY_THRESHOLD_INVALID)
		return -EINVAL;

	return sysfs_emit(buf, "%d\n", (int)ret);
}

static ssize_t battery_set_threshold(int which, const char *buf, size_t count)
{
	struct acpi_object_list input;
	union acpi_object params[2];
	acpi_handle handle;
	acpi_status status;
	unsigned int value;
	int ret;

	handle = ec_get_handle();
	if (!handle)
		return -ENODEV;

	ret = kstrtouint(buf, 10, &value);
	if (ret)
		return ret;

	if (value > 100)
		return -EINVAL;

	input.count = 2;
	input.pointer = params;
	// Start/stop selection
	params[0].type = ACPI_TYPE_INTEGER;
	params[0].integer.value = which;
	// Threshold value
	params[1].type = ACPI_TYPE_INTEGER;
	params[1].integer.value = value;

	status = acpi_evaluate_object(handle, "SBCT", &input, NULL);
	if (ACPI_FAILURE(status))
		return -EIO;

	return count;
}

static ssize_t charge_control_start_threshold_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	return battery_get_threshold(THRESHOLD_START, buf);
}

static ssize_t charge_control_start_threshold_store(struct device *dev,
	struct device_attribute *attr, const char *buf, size_t count)
{
	return battery_set_threshold(THRESHOLD_START, buf, count);
}

static DEVICE_ATTR_RW(charge_control_start_threshold);

static ssize_t charge_control_end_threshold_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	return battery_get_threshold(THRESHOLD_END, buf);
}

static ssize_t charge_control_end_threshold_store(struct device *dev,
	struct device_attribute *attr, const char *buf, size_t count)
{
	return battery_set_threshold(THRESHOLD_END, buf, count);
}

static DEVICE_ATTR_RW(charge_control_end_threshold);

static struct attribute *system76_battery_attrs[] = {
	&dev_attr_charge_control_start_threshold.attr,
	&dev_attr_charge_control_end_threshold.attr,
	NULL,
};

ATTRIBUTE_GROUPS(system76_battery);

static int system76_battery_add(struct power_supply *battery)
{
	// System76 EC only supports 1 battery
	if (strcmp(battery->desc->name, "BAT0") != 0)
		return -ENODEV;

	if (device_add_groups(&battery->dev, system76_battery_groups))
		return -ENODEV;

	return 0;
}

static int system76_battery_remove(struct power_supply *battery)
{
	device_remove_groups(&battery->dev, system76_battery_groups);
	return 0;
}

static struct acpi_battery_hook system76_battery_hook = {
	.add_battery = system76_battery_add,
	.remove_battery = system76_battery_remove,
	.name = "System76 Battery Extension",
};

static void system76_battery_init(void)
{
	acpi_handle handle;

	handle = ec_get_handle();
	if (handle && acpi_has_method(handle, "GBCT"))
		battery_hook_register(&system76_battery_hook);
}

static void system76_battery_exit(void)
{
	acpi_handle handle;

	handle = ec_get_handle();
	if (handle && acpi_has_method(handle, "GBCT"))
		battery_hook_unregister(&system76_battery_hook);
}

// Get the airplane mode LED brightness
static enum led_brightness ap_led_get(struct led_classdev *led)
{
@@ -581,6 +733,8 @@ static int system76_add(struct acpi_device *acpi_dev)
	if (err)
		goto error;

	system76_battery_init();

	return 0;

error:
@@ -596,6 +750,9 @@ static int system76_remove(struct acpi_device *acpi_dev)
	struct system76_data *data;

	data = acpi_driver_data(acpi_dev);

	system76_battery_exit();

	if (data->kb_color >= 0)
		device_remove_file(data->kb_led.dev, &kb_led_color_dev_attr);