aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/platform/x86/system76_acpi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/platform/x86/system76_acpi.c')
-rw-r--r--drivers/platform/x86/system76_acpi.c427
1 files changed, 411 insertions, 16 deletions
diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c
index c14fd22ba196..8b292ee95a14 100644
--- a/drivers/platform/x86/system76_acpi.c
+++ b/drivers/platform/x86/system76_acpi.c
@@ -10,13 +10,20 @@
*/
#include <linux/acpi.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
#include <linux/init.h>
+#include <linux/input.h>
#include <linux/kernel.h>
#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;
@@ -24,6 +31,10 @@ struct system76_data {
enum led_brightness kb_brightness;
enum led_brightness kb_toggle_brightness;
int kb_color;
+ struct device *therm;
+ union acpi_object *nfan;
+ union acpi_object *ntmp;
+ struct input_dev *input;
};
static const struct acpi_device_id device_ids[] = {
@@ -63,9 +74,57 @@ static int system76_get(struct system76_data *data, char *method)
handle = acpi_device_handle(data->acpi_dev);
status = acpi_evaluate_integer(handle, method, NULL, &ret);
if (ACPI_SUCCESS(status))
- return (int)ret;
- else
- return -1;
+ return ret;
+ return -ENODEV;
+}
+
+// Get a System76 ACPI device value by name with index
+static int system76_get_index(struct system76_data *data, char *method, int index)
+{
+ union acpi_object obj;
+ struct acpi_object_list obj_list;
+ acpi_handle handle;
+ acpi_status status;
+ unsigned long long ret = 0;
+
+ obj.type = ACPI_TYPE_INTEGER;
+ obj.integer.value = index;
+ obj_list.count = 1;
+ obj_list.pointer = &obj;
+
+ handle = acpi_device_handle(data->acpi_dev);
+ status = acpi_evaluate_integer(handle, method, &obj_list, &ret);
+ if (ACPI_SUCCESS(status))
+ return ret;
+ return -ENODEV;
+}
+
+// Get a System76 ACPI device object by name
+static int system76_get_object(struct system76_data *data, char *method, union acpi_object **obj)
+{
+ acpi_handle handle;
+ acpi_status status;
+ struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
+
+ handle = acpi_device_handle(data->acpi_dev);
+ status = acpi_evaluate_object(handle, method, NULL, &buf);
+ if (ACPI_SUCCESS(status)) {
+ *obj = buf.pointer;
+ return 0;
+ }
+
+ return -ENODEV;
+}
+
+// Get a name from a System76 ACPI device object
+static char *system76_name(union acpi_object *obj, int index)
+{
+ if (obj && obj->type == ACPI_TYPE_PACKAGE && index <= obj->package.count) {
+ if (obj->package.elements[index].type == ACPI_TYPE_STRING)
+ return obj->package.elements[index].string.pointer;
+ }
+
+ return NULL;
}
// Set a System76 ACPI device value by name
@@ -88,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)
{
@@ -141,7 +348,7 @@ static ssize_t kb_led_color_show(
led = (struct led_classdev *)dev->driver_data;
data = container_of(led, struct system76_data, kb_led);
- return sprintf(buf, "%06X\n", data->kb_color);
+ return sysfs_emit(buf, "%06X\n", data->kb_color);
}
// Set the keyboard LED color
@@ -169,7 +376,7 @@ static ssize_t kb_led_color_store(
return size;
}
-static const struct device_attribute kb_led_color_dev_attr = {
+static struct device_attribute dev_attr_kb_led_color = {
.attr = {
.name = "color",
.mode = 0644,
@@ -178,6 +385,13 @@ static const struct device_attribute kb_led_color_dev_attr = {
.store = kb_led_color_store,
};
+static struct attribute *system76_kb_led_color_attrs[] = {
+ &dev_attr_kb_led_color.attr,
+ NULL,
+};
+
+ATTRIBUTE_GROUPS(system76_kb_led_color);
+
// Notify that the keyboard LED was changed by hardware
static void kb_led_notify(struct system76_data *data)
{
@@ -270,6 +484,155 @@ static void kb_led_hotkey_color(struct system76_data *data)
kb_led_notify(data);
}
+static umode_t thermal_is_visible(const void *drvdata, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct system76_data *data = drvdata;
+
+ switch (type) {
+ case hwmon_fan:
+ case hwmon_pwm:
+ if (system76_name(data->nfan, channel))
+ return 0444;
+ break;
+
+ case hwmon_temp:
+ if (system76_name(data->ntmp, channel))
+ return 0444;
+ break;
+
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+static int thermal_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+ int channel, long *val)
+{
+ struct system76_data *data = dev_get_drvdata(dev);
+ int raw;
+
+ switch (type) {
+ case hwmon_fan:
+ if (attr == hwmon_fan_input) {
+ raw = system76_get_index(data, "GFAN", channel);
+ if (raw < 0)
+ return raw;
+ *val = (raw >> 8) & 0xFFFF;
+ return 0;
+ }
+ break;
+
+ case hwmon_pwm:
+ if (attr == hwmon_pwm_input) {
+ raw = system76_get_index(data, "GFAN", channel);
+ if (raw < 0)
+ return raw;
+ *val = raw & 0xFF;
+ return 0;
+ }
+ break;
+
+ case hwmon_temp:
+ if (attr == hwmon_temp_input) {
+ raw = system76_get_index(data, "GTMP", channel);
+ if (raw < 0)
+ return raw;
+ *val = raw * 1000;
+ return 0;
+ }
+ break;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static int thermal_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+ int channel, const char **str)
+{
+ struct system76_data *data = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_fan:
+ if (attr == hwmon_fan_label) {
+ *str = system76_name(data->nfan, channel);
+ if (*str)
+ return 0;
+ }
+ break;
+
+ case hwmon_temp:
+ if (attr == hwmon_temp_label) {
+ *str = system76_name(data->ntmp, channel);
+ if (*str)
+ return 0;
+ }
+ break;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static const struct hwmon_ops thermal_ops = {
+ .is_visible = thermal_is_visible,
+ .read = thermal_read,
+ .read_string = thermal_read_string,
+};
+
+// Allocate up to 8 fans and temperatures
+static const struct hwmon_channel_info *thermal_channel_info[] = {
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ NULL
+};
+
+static const struct hwmon_chip_info thermal_chip_info = {
+ .ops = &thermal_ops,
+ .info = thermal_channel_info,
+};
+
+static void input_key(struct system76_data *data, unsigned int code)
+{
+ input_report_key(data->input, code, 1);
+ input_sync(data->input);
+
+ input_report_key(data->input, code, 0);
+ input_sync(data->input);
+}
+
// Handle ACPI notification
static void system76_notify(struct acpi_device *acpi_dev, u32 event)
{
@@ -292,6 +655,9 @@ static void system76_notify(struct acpi_device *acpi_dev, u32 event)
case 0x84:
kb_led_hotkey_color(data);
break;
+ case 0x85:
+ input_key(data, KEY_SCREENLOCK);
+ break;
}
}
@@ -326,6 +692,7 @@ static int system76_add(struct acpi_device *acpi_dev)
data->kb_led.brightness_set_blocking = kb_led_set;
if (acpi_has_method(acpi_device_handle(data->acpi_dev), "SKBC")) {
data->kb_led.max_brightness = 255;
+ data->kb_led.groups = system76_kb_led_color_groups;
data->kb_toggle_brightness = 72;
data->kb_color = 0xffffff;
system76_set(data, "SKBC", data->kb_color);
@@ -337,16 +704,42 @@ static int system76_add(struct acpi_device *acpi_dev)
if (err)
return err;
- if (data->kb_color >= 0) {
- err = device_create_file(
- data->kb_led.dev,
- &kb_led_color_dev_attr
- );
- if (err)
- return err;
- }
+ data->input = devm_input_allocate_device(&acpi_dev->dev);
+ if (!data->input)
+ return -ENOMEM;
+
+ data->input->name = "System76 ACPI Hotkeys";
+ data->input->phys = "system76_acpi/input0";
+ data->input->id.bustype = BUS_HOST;
+ data->input->dev.parent = &acpi_dev->dev;
+ input_set_capability(data->input, EV_KEY, KEY_SCREENLOCK);
+
+ err = input_register_device(data->input);
+ if (err)
+ goto error;
+
+ err = system76_get_object(data, "NFAN", &data->nfan);
+ if (err)
+ goto error;
+
+ err = system76_get_object(data, "NTMP", &data->ntmp);
+ if (err)
+ goto error;
+
+ data->therm = devm_hwmon_device_register_with_info(&acpi_dev->dev,
+ "system76_acpi", data, &thermal_chip_info, NULL);
+ err = PTR_ERR_OR_ZERO(data->therm);
+ if (err)
+ goto error;
+
+ system76_battery_init();
return 0;
+
+error:
+ kfree(data->ntmp);
+ kfree(data->nfan);
+ return err;
}
// Remove a System76 ACPI device
@@ -355,13 +748,15 @@ static int system76_remove(struct acpi_device *acpi_dev)
struct system76_data *data;
data = acpi_driver_data(acpi_dev);
- if (data->kb_color >= 0)
- device_remove_file(data->kb_led.dev, &kb_led_color_dev_attr);
- devm_led_classdev_unregister(&acpi_dev->dev, &data->ap_led);
+ system76_battery_exit();
+ devm_led_classdev_unregister(&acpi_dev->dev, &data->ap_led);
devm_led_classdev_unregister(&acpi_dev->dev, &data->kb_led);
+ kfree(data->nfan);
+ kfree(data->ntmp);
+
system76_get(data, "FINI");
return 0;