Commit 95563d45 authored by Jeremy Soller's avatar Jeremy Soller Committed by Hans de Goede
Browse files

platform/x86: system76_acpi: Report temperature and fan speed



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: default avatarJeremy Soller <jeremy@system76.com>
Signed-off-by: default avatarTim Crawford <tcrawford@system76.com>
Link: https://lore.kernel.org/r/20211006202202.7479-2-tcrawford@system76.com


Reviewed-by: default avatarHans de Goede <hdegoede@redhat.com>
Signed-off-by: default avatarHans de Goede <hdegoede@redhat.com>
parent 249606d3
Loading
Loading
Loading
Loading
+218 −4
Original line number Diff line number Diff line
@@ -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[] = {
@@ -63,9 +68,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
@@ -270,6 +323,146 @@ 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,
};

// Handle ACPI notification
static void system76_notify(struct acpi_device *acpi_dev, u32 event)
{
@@ -346,7 +539,26 @@ static int system76_add(struct acpi_device *acpi_dev)
			return err;
	}

	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;

	return 0;

error:
	kfree(data->ntmp);
	kfree(data->nfan);
	return err;
}

// Remove a System76 ACPI device
@@ -359,9 +571,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;