summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorTim Crawford <tcrawford@system76.com>2021-10-06 14:22:01 -0600
committerHans de Goede <hdegoede@redhat.com>2021-10-19 16:31:09 +0200
commit76f7eba3e0a248af4cc4f302d95031fa2fb65fab (patch)
tree903a12b201bf9d7e05e714c8d71217e171e5f476 /drivers
parent0de30fc684b3883be73602b7557661951319a9b9 (diff)
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: Tim Crawford <tcrawford@system76.com> Link: https://lore.kernel.org/r/20211006202202.7479-4-tcrawford@system76.com Reviewed-by: Hans de Goede <hdegoede@redhat.com> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/platform/x86/system76_acpi.c157
1 files changed, 157 insertions, 0 deletions
diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c
index 9e525b51a267..70d0490e23f5 100644
--- a/drivers/platform/x86/system76_acpi.c
+++ b/drivers/platform/x86/system76_acpi.c
@@ -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);