diff options
Diffstat (limited to 'drivers/platform/x86/system76_acpi.c')
| -rw-r--r-- | drivers/platform/x86/system76_acpi.c | 427 | 
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 = ¶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)  { @@ -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; | 
