* [PATCH 0/3] platform/x86: system76_acpi: Sync DKMS module changes @ 2021-09-27 21:06 Tim Crawford 2021-09-27 21:06 ` [PATCH 1/3] platform/x86: system76_acpi: Report temperature and fan speed Tim Crawford ` (3 more replies) 0 siblings, 4 replies; 10+ messages in thread From: Tim Crawford @ 2021-09-27 21:06 UTC (permalink / raw) To: platform-driver-x86; +Cc: productdev Sync the in-tree system76_acpi module with changes made to the DKMS module [1]. This introduces/exposes new functionality implemented in the open source BIOS and EC firmware for System76 laptops. [1]: https://github.com/pop-os/system76-acpi-dkms Jeremy Soller (2): platform/x86: system76_acpi: Report temperature and fan speed platform/x86: system76_acpi: Replace Fn+F2 function for OLED models Tim Crawford (1): platform/x86: system76_acpi: Add battery charging thresholds drivers/platform/x86/system76_acpi.c | 356 ++++++++++++++++++++++++++- 1 file changed, 355 insertions(+), 1 deletion(-) -- 2.31.1 ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 1/3] platform/x86: system76_acpi: Report temperature and fan speed 2021-09-27 21:06 [PATCH 0/3] platform/x86: system76_acpi: Sync DKMS module changes Tim Crawford @ 2021-09-27 21:06 ` Tim Crawford 2021-09-27 21:06 ` [PATCH 2/3] platform/x86: system76_acpi: Replace Fn+F2 function for OLED models Tim Crawford ` (2 subsequent siblings) 3 siblings, 0 replies; 10+ messages in thread From: Tim Crawford @ 2021-09-27 21:06 UTC (permalink / raw) To: platform-driver-x86; +Cc: productdev, Jeremy Soller From: Jeremy Soller <jeremy@system76.com> Add a hwmon interface to report CPU/GPU temperature and fan speed. sensors now reports an ACPI interface with the entries: system76_acpi-acpi-0 Adapter: ACPI interface CPU fan: 0 RPM GPU fan: 0 RPM CPU temp: +47.0°C GPU temp: +0.0°C Signed-off-by: Jeremy Soller <jeremy@system76.com> Signed-off-by: Tim Crawford <tcrawford@system76.com> --- drivers/platform/x86/system76_acpi.c | 172 ++++++++++++++++++++++++++- 1 file changed, 171 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c index c14fd22ba196..11f0e42386ba 100644 --- a/drivers/platform/x86/system76_acpi.c +++ b/drivers/platform/x86/system76_acpi.c @@ -10,6 +10,8 @@ */ #include <linux/acpi.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/leds.h> @@ -24,6 +26,9 @@ 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; }; static const struct acpi_device_id device_ids[] = { @@ -68,6 +73,55 @@ static int system76_get(struct system76_data *data, char *method) return -1; } +// 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 (int)ret; + return -1; +} + +// 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 = (union acpi_object *)buf.pointer; + return 0; + } + + return -1; +} + +// 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 static int system76_set(struct system76_data *data, char *method, int value) { @@ -270,6 +324,112 @@ 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; + + if (type == hwmon_fan || type == hwmon_pwm) { + if (system76_name(data->nfan, channel)) + return 0444; + } else if (type == hwmon_temp) { + if (system76_name(data->ntmp, channel)) + return 0444; + } + + 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; + + if (type == hwmon_fan && attr == hwmon_fan_input) { + raw = system76_get_index(data, "GFAN", channel); + if (raw >= 0) { + *val = (long)((raw >> 8) & 0xFFFF); + return 0; + } + } else if (type == hwmon_pwm && attr == hwmon_pwm_input) { + raw = system76_get_index(data, "GFAN", channel); + if (raw >= 0) { + *val = (long)(raw & 0xFF); + return 0; + } + } else if (type == hwmon_temp && attr == hwmon_temp_input) { + raw = system76_get_index(data, "GTMP", channel); + if (raw >= 0) { + *val = (long)(raw * 1000); + return 0; + } + } + + return -EINVAL; +} + +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); + + if (type == hwmon_fan && attr == hwmon_fan_label) { + *str = system76_name(data->nfan, channel); + if (*str) + return 0; + } else if (type == hwmon_temp && attr == hwmon_temp_label) { + *str = system76_name(data->ntmp, channel); + if (*str) + return 0; + } + + return -EINVAL; +} + +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, +}; + // Handle ACPI notification static void system76_notify(struct acpi_device *acpi_dev, u32 event) { @@ -346,6 +506,14 @@ static int system76_add(struct acpi_device *acpi_dev) return err; } + system76_get_object(data, "NFAN", &data->nfan); + system76_get_object(data, "NTMP", &data->ntmp); + + data->therm = devm_hwmon_device_register_with_info(&acpi_dev->dev, + "system76_acpi", data, &thermal_chip_info, NULL); + if (IS_ERR(data->therm)) + return PTR_ERR(data->therm); + return 0; } @@ -359,9 +527,11 @@ static int system76_remove(struct acpi_device *acpi_dev) device_remove_file(data->kb_led.dev, &kb_led_color_dev_attr); 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; -- 2.31.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 2/3] platform/x86: system76_acpi: Replace Fn+F2 function for OLED models 2021-09-27 21:06 [PATCH 0/3] platform/x86: system76_acpi: Sync DKMS module changes Tim Crawford 2021-09-27 21:06 ` [PATCH 1/3] platform/x86: system76_acpi: Report temperature and fan speed Tim Crawford @ 2021-09-27 21:06 ` Tim Crawford 2021-09-28 16:40 ` Barnabás Pőcze 2021-09-27 21:06 ` [PATCH 3/3] platform/x86: system76_acpi: Add battery charging thresholds Tim Crawford 2021-09-28 22:15 ` [PATCH v2 0/3] platform/x86: system76_acpi: Sync DKMS module changes Tim Crawford 3 siblings, 1 reply; 10+ messages in thread From: Tim Crawford @ 2021-09-27 21:06 UTC (permalink / raw) To: platform-driver-x86; +Cc: productdev, Jeremy Soller From: Jeremy Soller <jeremy@system76.com> System76 laptops models with OLED displays do not support the default Fn+F2 behavior of turning the embedded display on and off. Some models instead introduce a new notify event that is used to lock the screen so the OS will put the display in a low power state. Signed-off-by: Jeremy Soller <jeremy@system76.com> Signed-off-by: Tim Crawford <tcrawford@system76.com> --- drivers/platform/x86/system76_acpi.c | 31 ++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c index 11f0e42386ba..06f6509980e2 100644 --- a/drivers/platform/x86/system76_acpi.c +++ b/drivers/platform/x86/system76_acpi.c @@ -13,6 +13,7 @@ #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> @@ -29,6 +30,7 @@ struct system76_data { struct device *therm; union acpi_object *nfan; union acpi_object *ntmp; + struct input_dev *input; }; static const struct acpi_device_id device_ids[] = { @@ -430,6 +432,15 @@ static const struct hwmon_chip_info thermal_chip_info = { .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) { @@ -452,6 +463,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; } } @@ -514,6 +528,23 @@ static int system76_add(struct acpi_device *acpi_dev) if (IS_ERR(data->therm)) return PTR_ERR(data->therm); + 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; + set_bit(EV_KEY, data->input->evbit); + set_bit(KEY_SCREENLOCK, data->input->keybit); + + err = input_register_device(data->input); + if (err) { + input_free_device(data->input); + return err; + } + return 0; } -- 2.31.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 2/3] platform/x86: system76_acpi: Replace Fn+F2 function for OLED models 2021-09-27 21:06 ` [PATCH 2/3] platform/x86: system76_acpi: Replace Fn+F2 function for OLED models Tim Crawford @ 2021-09-28 16:40 ` Barnabás Pőcze 0 siblings, 0 replies; 10+ messages in thread From: Barnabás Pőcze @ 2021-09-28 16:40 UTC (permalink / raw) To: Tim Crawford; +Cc: platform-driver-x86, productdev, Jeremy Soller Hi 2021. szeptember 27., hétfő 23:06 keltezéssel, Tim Crawford írta: > From: Jeremy Soller <jeremy@system76.com> > > System76 laptops models with OLED displays do not support the default > Fn+F2 behavior of turning the embedded display on and off. Some models > instead introduce a new notify event that is used to lock the screen so > the OS will put the display in a low power state. > > Signed-off-by: Jeremy Soller <jeremy@system76.com> > Signed-off-by: Tim Crawford <tcrawford@system76.com> > --- > drivers/platform/x86/system76_acpi.c | 31 ++++++++++++++++++++++++++++ > 1 file changed, 31 insertions(+) > > diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c > index 11f0e42386ba..06f6509980e2 100644 > --- a/drivers/platform/x86/system76_acpi.c > +++ b/drivers/platform/x86/system76_acpi.c > [...] > @@ -514,6 +528,23 @@ static int system76_add(struct acpi_device *acpi_dev) > if (IS_ERR(data->therm)) > return PTR_ERR(data->therm); > > + 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; > + set_bit(EV_KEY, data->input->evbit); > + set_bit(KEY_SCREENLOCK, data->input->keybit); You can use `input_set_capability()` instead of manually setting the bits. > + > + err = input_register_device(data->input); > + if (err) { > + input_free_device(data->input); > + return err; > + } > + > return 0; > } > > -- > 2.31.1 > Regards, Barnabás Pőcze ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 3/3] platform/x86: system76_acpi: Add battery charging thresholds 2021-09-27 21:06 [PATCH 0/3] platform/x86: system76_acpi: Sync DKMS module changes Tim Crawford 2021-09-27 21:06 ` [PATCH 1/3] platform/x86: system76_acpi: Report temperature and fan speed Tim Crawford 2021-09-27 21:06 ` [PATCH 2/3] platform/x86: system76_acpi: Replace Fn+F2 function for OLED models Tim Crawford @ 2021-09-27 21:06 ` Tim Crawford 2021-09-28 16:38 ` Barnabás Pőcze 2021-09-28 22:15 ` [PATCH v2 0/3] platform/x86: system76_acpi: Sync DKMS module changes Tim Crawford 3 siblings, 1 reply; 10+ messages in thread From: Tim Crawford @ 2021-09-27 21:06 UTC (permalink / raw) To: platform-driver-x86; +Cc: productdev 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> --- drivers/platform/x86/system76_acpi.c | 153 +++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c index 06f6509980e2..b42c4a384ba9 100644 --- a/drivers/platform/x86/system76_acpi.c +++ b/drivers/platform/x86/system76_acpi.c @@ -18,8 +18,11 @@ #include <linux/leds.h> #include <linux/module.h> #include <linux/pci_ids.h> +#include <linux/power_supply.h> #include <linux/types.h> +#include <acpi/battery.h> + struct system76_data { struct acpi_device *acpi_dev; struct led_classdev ap_led; @@ -144,6 +147,151 @@ 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 = ¶m; + // 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 sprintf(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 int system76_battery_add(struct power_supply *battery) +{ + // System76 EC only supports 1 battery + if (strcmp(battery->desc->name, "BAT0") != 0) + 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 system76_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 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) { @@ -545,6 +693,8 @@ static int system76_add(struct acpi_device *acpi_dev) return err; } + system76_battery_init(); + return 0; } @@ -554,6 +704,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); -- 2.31.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 3/3] platform/x86: system76_acpi: Add battery charging thresholds 2021-09-27 21:06 ` [PATCH 3/3] platform/x86: system76_acpi: Add battery charging thresholds Tim Crawford @ 2021-09-28 16:38 ` Barnabás Pőcze 0 siblings, 0 replies; 10+ messages in thread From: Barnabás Pőcze @ 2021-09-28 16:38 UTC (permalink / raw) To: Tim Crawford; +Cc: platform-driver-x86, productdev Hi 2021. szeptember 27., hétfő 23:06 keltezéssel, Tim Crawford írta: > 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> > --- > drivers/platform/x86/system76_acpi.c | 153 +++++++++++++++++++++++++++ > 1 file changed, 153 insertions(+) > > diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c > index 06f6509980e2..b42c4a384ba9 100644 > --- a/drivers/platform/x86/system76_acpi.c > +++ b/drivers/platform/x86/system76_acpi.c > [...] > +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 = ¶m; > + // 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 sprintf(buf, "%d\n", (int)ret); Please use `sysfs_emit()` for writing into sysfs buffers. > +} > [...] > +static int system76_battery_add(struct power_supply *battery) > +{ > + // System76 EC only supports 1 battery > + if (strcmp(battery->desc->name, "BAT0") != 0) > + return -ENODEV; > + > + device_create_file(&battery->dev, &dev_attr_charge_control_start_threshold); > + device_create_file(&battery->dev, &dev_attr_charge_control_end_threshold); You can use `device_{add,remove}_groups()` instead of manually adding them one by one. > + return 0; > +} > [...] Regards, Barnabás Pőcze ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 0/3] platform/x86: system76_acpi: Sync DKMS module changes 2021-09-27 21:06 [PATCH 0/3] platform/x86: system76_acpi: Sync DKMS module changes Tim Crawford ` (2 preceding siblings ...) 2021-09-27 21:06 ` [PATCH 3/3] platform/x86: system76_acpi: Add battery charging thresholds Tim Crawford @ 2021-09-28 22:15 ` Tim Crawford 2021-09-28 22:15 ` [PATCH v2 1/3] platform/x86: system76_acpi: Report temperature and fan speed Tim Crawford ` (2 more replies) 3 siblings, 3 replies; 10+ messages in thread From: Tim Crawford @ 2021-09-28 22:15 UTC (permalink / raw) To: platform-driver-x86; +Cc: productdev Sync the in-tree system76_acpi module with changes made to the DKMS module [1]. This introduces/exposes new functionality implemented in the open source BIOS and EC firmware for System76 laptops. v2: - Used input_set_capability() instead of setting bits directly in patch 2/3 - Used sysfs_emit() instead of sprintf() in patch 3/3 - Made use of device_{add,remove}_groups() in patch 3/3 [1]: https://github.com/pop-os/system76-acpi-dkms Jeremy Soller (2): platform/x86: system76_acpi: Report temperature and fan speed platform/x86: system76_acpi: Replace Fn+F2 function for OLED models Tim Crawford (1): platform/x86: system76_acpi: Add battery charging thresholds drivers/platform/x86/system76_acpi.c | 364 ++++++++++++++++++++++++++- 1 file changed, 363 insertions(+), 1 deletion(-) -- 2.31.1 ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 1/3] platform/x86: system76_acpi: Report temperature and fan speed 2021-09-28 22:15 ` [PATCH v2 0/3] platform/x86: system76_acpi: Sync DKMS module changes Tim Crawford @ 2021-09-28 22:15 ` Tim Crawford 2021-09-28 22:15 ` [PATCH v2 2/3] platform/x86: system76_acpi: Replace Fn+F2 function for OLED models Tim Crawford 2021-09-28 22:15 ` [PATCH v2 3/3] platform/x86: system76_acpi: Add battery charging thresholds Tim Crawford 2 siblings, 0 replies; 10+ messages in thread From: Tim Crawford @ 2021-09-28 22:15 UTC (permalink / raw) To: platform-driver-x86; +Cc: productdev, Jeremy Soller From: Jeremy Soller <jeremy@system76.com> Add a hwmon interface to report CPU/GPU temperature and fan speed. sensors now reports an ACPI interface with the entries: system76_acpi-acpi-0 Adapter: ACPI interface CPU fan: 0 RPM GPU fan: 0 RPM CPU temp: +47.0°C GPU temp: +0.0°C Signed-off-by: Jeremy Soller <jeremy@system76.com> Signed-off-by: Tim Crawford <tcrawford@system76.com> --- drivers/platform/x86/system76_acpi.c | 172 ++++++++++++++++++++++++++- 1 file changed, 171 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c index c14fd22ba196..11f0e42386ba 100644 --- a/drivers/platform/x86/system76_acpi.c +++ b/drivers/platform/x86/system76_acpi.c @@ -10,6 +10,8 @@ */ #include <linux/acpi.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/leds.h> @@ -24,6 +26,9 @@ 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; }; static const struct acpi_device_id device_ids[] = { @@ -68,6 +73,55 @@ static int system76_get(struct system76_data *data, char *method) return -1; } +// 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 (int)ret; + return -1; +} + +// 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 = (union acpi_object *)buf.pointer; + return 0; + } + + return -1; +} + +// 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 static int system76_set(struct system76_data *data, char *method, int value) { @@ -270,6 +324,112 @@ 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; + + if (type == hwmon_fan || type == hwmon_pwm) { + if (system76_name(data->nfan, channel)) + return 0444; + } else if (type == hwmon_temp) { + if (system76_name(data->ntmp, channel)) + return 0444; + } + + 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; + + if (type == hwmon_fan && attr == hwmon_fan_input) { + raw = system76_get_index(data, "GFAN", channel); + if (raw >= 0) { + *val = (long)((raw >> 8) & 0xFFFF); + return 0; + } + } else if (type == hwmon_pwm && attr == hwmon_pwm_input) { + raw = system76_get_index(data, "GFAN", channel); + if (raw >= 0) { + *val = (long)(raw & 0xFF); + return 0; + } + } else if (type == hwmon_temp && attr == hwmon_temp_input) { + raw = system76_get_index(data, "GTMP", channel); + if (raw >= 0) { + *val = (long)(raw * 1000); + return 0; + } + } + + return -EINVAL; +} + +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); + + if (type == hwmon_fan && attr == hwmon_fan_label) { + *str = system76_name(data->nfan, channel); + if (*str) + return 0; + } else if (type == hwmon_temp && attr == hwmon_temp_label) { + *str = system76_name(data->ntmp, channel); + if (*str) + return 0; + } + + return -EINVAL; +} + +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, +}; + // Handle ACPI notification static void system76_notify(struct acpi_device *acpi_dev, u32 event) { @@ -346,6 +506,14 @@ static int system76_add(struct acpi_device *acpi_dev) return err; } + system76_get_object(data, "NFAN", &data->nfan); + system76_get_object(data, "NTMP", &data->ntmp); + + data->therm = devm_hwmon_device_register_with_info(&acpi_dev->dev, + "system76_acpi", data, &thermal_chip_info, NULL); + if (IS_ERR(data->therm)) + return PTR_ERR(data->therm); + return 0; } @@ -359,9 +527,11 @@ static int system76_remove(struct acpi_device *acpi_dev) device_remove_file(data->kb_led.dev, &kb_led_color_dev_attr); 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; -- 2.31.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 2/3] platform/x86: system76_acpi: Replace Fn+F2 function for OLED models 2021-09-28 22:15 ` [PATCH v2 0/3] platform/x86: system76_acpi: Sync DKMS module changes Tim Crawford 2021-09-28 22:15 ` [PATCH v2 1/3] platform/x86: system76_acpi: Report temperature and fan speed Tim Crawford @ 2021-09-28 22:15 ` Tim Crawford 2021-09-28 22:15 ` [PATCH v2 3/3] platform/x86: system76_acpi: Add battery charging thresholds Tim Crawford 2 siblings, 0 replies; 10+ messages in thread From: Tim Crawford @ 2021-09-28 22:15 UTC (permalink / raw) To: platform-driver-x86; +Cc: productdev, Jeremy Soller From: Jeremy Soller <jeremy@system76.com> System76 laptops models with OLED displays do not support the default Fn+F2 behavior of turning the embedded display on and off. Some models instead introduce a new notify event that is used to lock the screen so the OS will put the display in a low power state. Signed-off-by: Jeremy Soller <jeremy@system76.com> Signed-off-by: Tim Crawford <tcrawford@system76.com> --- drivers/platform/x86/system76_acpi.c | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c index 11f0e42386ba..e7c86b543930 100644 --- a/drivers/platform/x86/system76_acpi.c +++ b/drivers/platform/x86/system76_acpi.c @@ -13,6 +13,7 @@ #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> @@ -29,6 +30,7 @@ struct system76_data { struct device *therm; union acpi_object *nfan; union acpi_object *ntmp; + struct input_dev *input; }; static const struct acpi_device_id device_ids[] = { @@ -430,6 +432,15 @@ static const struct hwmon_chip_info thermal_chip_info = { .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) { @@ -452,6 +463,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; } } @@ -514,6 +528,22 @@ static int system76_add(struct acpi_device *acpi_dev) if (IS_ERR(data->therm)) return PTR_ERR(data->therm); + 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) { + input_free_device(data->input); + return err; + } + return 0; } -- 2.31.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 3/3] platform/x86: system76_acpi: Add battery charging thresholds 2021-09-28 22:15 ` [PATCH v2 0/3] platform/x86: system76_acpi: Sync DKMS module changes Tim Crawford 2021-09-28 22:15 ` [PATCH v2 1/3] platform/x86: system76_acpi: Report temperature and fan speed Tim Crawford 2021-09-28 22:15 ` [PATCH v2 2/3] platform/x86: system76_acpi: Replace Fn+F2 function for OLED models Tim Crawford @ 2021-09-28 22:15 ` Tim Crawford 2 siblings, 0 replies; 10+ messages in thread From: Tim Crawford @ 2021-09-28 22:15 UTC (permalink / raw) To: platform-driver-x86; +Cc: productdev 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> --- drivers/platform/x86/system76_acpi.c | 162 +++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c index e7c86b543930..5c525c242211 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; @@ -144,6 +148,159 @@ 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 = ¶m; + // 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) { @@ -544,6 +701,8 @@ static int system76_add(struct acpi_device *acpi_dev) return err; } + system76_battery_init(); + return 0; } @@ -553,6 +712,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); -- 2.31.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
end of thread, other threads:[~2021-09-28 22:15 UTC | newest] Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2021-09-27 21:06 [PATCH 0/3] platform/x86: system76_acpi: Sync DKMS module changes Tim Crawford 2021-09-27 21:06 ` [PATCH 1/3] platform/x86: system76_acpi: Report temperature and fan speed Tim Crawford 2021-09-27 21:06 ` [PATCH 2/3] platform/x86: system76_acpi: Replace Fn+F2 function for OLED models Tim Crawford 2021-09-28 16:40 ` Barnabás Pőcze 2021-09-27 21:06 ` [PATCH 3/3] platform/x86: system76_acpi: Add battery charging thresholds Tim Crawford 2021-09-28 16:38 ` Barnabás Pőcze 2021-09-28 22:15 ` [PATCH v2 0/3] platform/x86: system76_acpi: Sync DKMS module changes Tim Crawford 2021-09-28 22:15 ` [PATCH v2 1/3] platform/x86: system76_acpi: Report temperature and fan speed Tim Crawford 2021-09-28 22:15 ` [PATCH v2 2/3] platform/x86: system76_acpi: Replace Fn+F2 function for OLED models Tim Crawford 2021-09-28 22:15 ` [PATCH v2 3/3] platform/x86: system76_acpi: Add battery charging thresholds Tim Crawford
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).