Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4384b8fe authored by Lan Tianyu's avatar Lan Tianyu Committed by Zhang Rui
Browse files

Thermal: introduce int3403 thermal driver



ACPI INT3403 device object can be used to retrieve temperature date
from temperature sensors present in the system, and to expose
device' performance control.

The previous INT3403 thermal driver supports temperature reporting only,
thus remove it and introduce this new & enhanced one.

Signed-off-by: default avatarLan Tianyu <tianyu.lan@intel.com>
Signed-off-by: default avatarZhang Rui <rui.zhang@intel.com>
parent 77e337c6
Loading
Loading
Loading
Loading
+0 −15
Original line number Diff line number Diff line
@@ -207,21 +207,6 @@ config X86_PKG_TEMP_THERMAL
	  two trip points which can be set by user to get notifications via thermal
	  notification methods.

config ACPI_INT3403_THERMAL
	tristate "ACPI INT3403 thermal driver"
	depends on X86 && ACPI
	help
	  Newer laptops and tablets that use ACPI may have thermal sensors
	  outside the core CPU/SOC for thermal safety reasons. These
	  temperature sensors are also exposed for the OS to use via the so
	  called INT3403 ACPI object. This driver will, on devices that have
	  such sensors, expose the temperature information from these sensors
	  to userspace via the normal thermal framework. This means that a wide
	  range of applications and GUI widgets can show this information to
	  the user or use this information for making decisions. For example,
	  the Intel Thermal Daemon can use this information to allow the user
	  to select his laptop to run without turning on the fans.

config INTEL_SOC_DTS_THERMAL
	tristate "Intel SoCs DTS thermal driver"
	depends on X86 && IOSF_MBI
+0 −1
Original line number Diff line number Diff line
@@ -31,6 +31,5 @@ obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o
obj-$(CONFIG_X86_PKG_TEMP_THERMAL)	+= x86_pkg_temp_thermal.o
obj-$(CONFIG_INTEL_SOC_DTS_THERMAL)	+= intel_soc_dts_thermal.o
obj-$(CONFIG_TI_SOC_THERMAL)	+= ti-soc-thermal/
obj-$(CONFIG_ACPI_INT3403_THERMAL)	+= int3403_thermal.o
obj-$(CONFIG_INT340X_THERMAL)  += int340x_thermal/
obj-$(CONFIG_ST_THERMAL)	+= st/
+1 −0
Original line number Diff line number Diff line
obj-$(CONFIG_INT340X_THERMAL)	+= int3400_thermal.o
obj-$(CONFIG_INT340X_THERMAL)	+= int3402_thermal.o
obj-$(CONFIG_INT340X_THERMAL)	+= int3403_thermal.o
+477 −0
Original line number Diff line number Diff line
@@ -18,8 +18,11 @@
#include <linux/types.h>
#include <linux/acpi.h>
#include <linux/thermal.h>
#include <linux/platform_device.h>

#define INT3403_TYPE_SENSOR		0x03
#define INT3403_TYPE_CHARGER		0x0B
#define INT3403_TYPE_BATTERY		0x0C
#define INT3403_PERF_CHANGED_EVENT	0x80
#define INT3403_THERMAL_EVENT		0x90

@@ -27,9 +30,6 @@
#define KELVIN_OFFSET	2732
#define MILLI_CELSIUS_TO_DECI_KELVIN(t, off) (((t) / 100) + (off))

#define ACPI_INT3403_CLASS		"int3403"
#define ACPI_INT3403_FILE_STATE		"state"

struct int3403_sensor {
	struct thermal_zone_device *tzone;
	unsigned long *thresholds;
@@ -37,12 +37,37 @@ struct int3403_sensor {
	int		crit_trip_id;
	unsigned long	psv_temp;
	int		psv_trip_id;

};

struct int3403_performance_state {
	u64 performance;
	u64 power;
	u64 latency;
	u64 linear;
	u64 control;
	u64 raw_performace;
	char *raw_unit;
	int reserved;
};

struct int3403_cdev {
	struct thermal_cooling_device *cdev;
	unsigned long max_state;
};

struct int3403_priv {
	struct platform_device *pdev;
	struct acpi_device *adev;
	unsigned long long type;
	void *priv;
};

static int sys_get_curr_temp(struct thermal_zone_device *tzone,
				unsigned long *temp)
{
	struct acpi_device *device = tzone->devdata;
	struct int3403_priv *priv = tzone->devdata;
	struct acpi_device *device = priv->adev;
	unsigned long long tmp;
	acpi_status status;

@@ -58,7 +83,8 @@ static int sys_get_curr_temp(struct thermal_zone_device *tzone,
static int sys_get_trip_hyst(struct thermal_zone_device *tzone,
		int trip, unsigned long *temp)
{
	struct acpi_device *device = tzone->devdata;
	struct int3403_priv *priv = tzone->devdata;
	struct acpi_device *device = priv->adev;
	unsigned long long hyst;
	acpi_status status;

@@ -66,13 +92,7 @@ static int sys_get_trip_hyst(struct thermal_zone_device *tzone,
	if (ACPI_FAILURE(status))
		return -EIO;

	/*
	 * Thermal hysteresis represents a temperature difference.
	 * Kelvin and Celsius have same degree size. So the
	 * conversion here between tenths of degree Kelvin unit
	 * and Milli-Celsius unit is just to multiply 100.
	 */
	*temp = hyst * 100;
	*temp = DECI_KELVIN_TO_MILLI_CELSIUS(hyst, KELVIN_OFFSET);

	return 0;
}
@@ -80,8 +100,11 @@ static int sys_get_trip_hyst(struct thermal_zone_device *tzone,
static int sys_get_trip_temp(struct thermal_zone_device *tzone,
		int trip, unsigned long *temp)
{
	struct acpi_device *device = tzone->devdata;
	struct int3403_sensor *obj = acpi_driver_data(device);
	struct int3403_priv *priv = tzone->devdata;
	struct int3403_sensor *obj = priv->priv;

	if (priv->type != INT3403_TYPE_SENSOR || !obj)
		return -EINVAL;

	if (trip == obj->crit_trip_id)
		*temp = obj->crit_temp;
@@ -91,7 +114,7 @@ static int sys_get_trip_temp(struct thermal_zone_device *tzone,
		/*
		 * get_trip_temp is a mandatory callback but
		 * PATx method doesn't return any value, so return
		 * cached value, which was last set from user space.
		 * cached value, which was last set from user space
		 */
		*temp = obj->thresholds[trip];
	}
@@ -102,8 +125,8 @@ static int sys_get_trip_temp(struct thermal_zone_device *tzone,
static int sys_get_trip_type(struct thermal_zone_device *thermal,
		int trip, enum thermal_trip_type *type)
{
	struct acpi_device *device = thermal->devdata;
	struct int3403_sensor *obj = acpi_driver_data(device);
	struct int3403_priv *priv = thermal->devdata;
	struct int3403_sensor *obj = priv->priv;

	/* Mandatory callback, may not mean much here */
	if (trip == obj->crit_trip_id)
@@ -117,11 +140,12 @@ static int sys_get_trip_type(struct thermal_zone_device *thermal,
int sys_set_trip_temp(struct thermal_zone_device *tzone, int trip,
							unsigned long temp)
{
	struct acpi_device *device = tzone->devdata;
	struct int3403_priv *priv = tzone->devdata;
	struct acpi_device *device = priv->adev;
	struct int3403_sensor *obj = priv->priv;
	acpi_status status;
	char name[10];
	int ret = 0;
	struct int3403_sensor *obj = acpi_driver_data(device);

	snprintf(name, sizeof(name), "PAT%d", trip);
	if (acpi_has_method(device->handle, name)) {
@@ -148,15 +172,22 @@ static struct thermal_zone_device_ops tzone_ops = {
	.get_trip_hyst =  sys_get_trip_hyst,
};

static void acpi_thermal_notify(struct acpi_device *device, u32 event)
static struct thermal_zone_params int3403_thermal_params = {
	.governor_name = "user_space",
	.no_hwmon = true,
};

static void int3403_notify(acpi_handle handle,
		u32 event, void *data)
{
	struct int3403_priv *priv = data;
	struct int3403_sensor *obj;

	if (!device)
	if (!priv)
		return;

	obj = acpi_driver_data(device);
	if (!obj)
	obj = priv->priv;
	if (priv->type != INT3403_TYPE_SENSOR || !obj)
		return;

	switch (event) {
@@ -166,7 +197,7 @@ static void acpi_thermal_notify(struct acpi_device *device, u32 event)
		thermal_zone_device_update(obj->tzone);
		break;
	default:
		dev_err(&device->dev, "Unsupported event [0x%x]\n", event);
		dev_err(&priv->pdev->dev, "Unsupported event [0x%x]\n", event);
		break;
	}
}
@@ -199,97 +230,247 @@ static int sys_get_trip_psv(struct acpi_device *device, unsigned long *temp)
	return 0;
}

static int acpi_int3403_add(struct acpi_device *device)
static int int3403_sensor_add(struct int3403_priv *priv)
{
	int result = 0;
	unsigned long long ptyp;
	acpi_status status;
	struct int3403_sensor *obj;
	unsigned long long trip_cnt;
	int trip_mask = 0;

	if (!device)
		return -EINVAL;

	status = acpi_evaluate_integer(device->handle, "PTYP", NULL, &ptyp);
	if (ACPI_FAILURE(status))
		return -EINVAL;

	if (ptyp != INT3403_TYPE_SENSOR)
		return -EINVAL;

	obj = devm_kzalloc(&device->dev, sizeof(*obj), GFP_KERNEL);
	obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL);
	if (!obj)
		return -ENOMEM;

	device->driver_data = obj;
	priv->priv = obj;

	status = acpi_evaluate_integer(device->handle, "PATC", NULL,
	status = acpi_evaluate_integer(priv->adev->handle, "PATC", NULL,
						&trip_cnt);
	if (ACPI_FAILURE(status))
		trip_cnt = 0;

	if (trip_cnt) {
		/* We have to cache, thresholds can't be readback */
		obj->thresholds = devm_kzalloc(&device->dev,
		obj->thresholds = devm_kzalloc(&priv->pdev->dev,
					sizeof(*obj->thresholds) * trip_cnt,
					GFP_KERNEL);
		if (!obj->thresholds)
			return -ENOMEM;
		if (!obj->thresholds) {
			result = -ENOMEM;
			goto err_free_obj;
		}
		trip_mask = BIT(trip_cnt) - 1;
	}

	obj->psv_trip_id = -1;
	if (!sys_get_trip_psv(device, &obj->psv_temp))
	if (!sys_get_trip_psv(priv->adev, &obj->psv_temp))
		obj->psv_trip_id = trip_cnt++;

	obj->crit_trip_id = -1;
	if (!sys_get_trip_crt(device, &obj->crit_temp))
	if (!sys_get_trip_crt(priv->adev, &obj->crit_temp))
		obj->crit_trip_id = trip_cnt++;

	obj->tzone = thermal_zone_device_register(acpi_device_bid(device),
				trip_cnt, trip_mask, device, &tzone_ops,
				NULL, 0, 0);
	obj->tzone = thermal_zone_device_register(acpi_device_bid(priv->adev),
				trip_cnt, trip_mask, priv, &tzone_ops,
				&int3403_thermal_params, 0, 0);
	if (IS_ERR(obj->tzone)) {
		result = PTR_ERR(obj->tzone);
		return result;
		obj->tzone = NULL;
		goto err_free_obj;
	}

	strcpy(acpi_device_name(device), "INT3403");
	strcpy(acpi_device_class(device), ACPI_INT3403_CLASS);
	result = acpi_install_notify_handler(priv->adev->handle,
			ACPI_DEVICE_NOTIFY, int3403_notify,
			(void *)priv);
	if (result)
		goto err_free_obj;

	return 0;

 err_free_obj:
	if (obj->tzone)
		thermal_zone_device_unregister(obj->tzone);
	return result;
}

static int acpi_int3403_remove(struct acpi_device *device)
static int int3403_sensor_remove(struct int3403_priv *priv)
{
	struct int3403_sensor *obj;
	struct int3403_sensor *obj = priv->priv;

	obj = acpi_driver_data(device);
	thermal_zone_device_unregister(obj->tzone);
	return 0;
}

/* INT3403 Cooling devices */
static int int3403_get_max_state(struct thermal_cooling_device *cdev,
				 unsigned long *state)
{
	struct int3403_priv *priv = cdev->devdata;
	struct int3403_cdev *obj = priv->priv;

	*state = obj->max_state;
	return 0;
}

static int int3403_get_cur_state(struct thermal_cooling_device *cdev,
				 unsigned long *state)
{
	struct int3403_priv *priv = cdev->devdata;
	unsigned long long level;
	acpi_status status;

	status = acpi_evaluate_integer(priv->adev->handle, "PPPC", NULL, &level);
	if (ACPI_SUCCESS(status)) {
		*state = level;
		return 0;
	} else
		return -EINVAL;
}

static int
int3403_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
{
	struct int3403_priv *priv = cdev->devdata;
	acpi_status status;

	status = acpi_execute_simple_method(priv->adev->handle, "SPPC", state);
	if (ACPI_SUCCESS(status))
		return 0;
	else
		return -EINVAL;
}

static const struct thermal_cooling_device_ops int3403_cooling_ops = {
	.get_max_state = int3403_get_max_state,
	.get_cur_state = int3403_get_cur_state,
	.set_cur_state = int3403_set_cur_state,
};

static int int3403_cdev_add(struct int3403_priv *priv)
{
	int result = 0;
	acpi_status status;
	struct int3403_cdev *obj;
	struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
	union acpi_object *p;

	obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL);
	if (!obj)
		return -ENOMEM;

	status = acpi_evaluate_object(priv->adev->handle, "PPSS", NULL, &buf);
	if (ACPI_FAILURE(status))
		return -ENODEV;

	p = buf.pointer;
	if (!p || (p->type != ACPI_TYPE_PACKAGE)) {
		printk(KERN_WARNING "Invalid PPSS data\n");
		return -EFAULT;
	}

	obj->max_state = p->package.count - 1;
	obj->cdev =
		thermal_cooling_device_register(acpi_device_bid(priv->adev),
				priv, &int3403_cooling_ops);
	if (IS_ERR(obj->cdev))
		result = PTR_ERR(obj->cdev);

	priv->priv = obj;

	/* TODO: add ACPI notification support */

	return result;
}

static int int3403_cdev_remove(struct int3403_priv *priv)
{
	struct int3403_cdev *obj = priv->priv;

	thermal_cooling_device_unregister(obj->cdev);
	return 0;
}

static int int3403_add(struct platform_device *pdev)
{
	struct int3403_priv *priv;
	int result = 0;
	acpi_status status;

	priv = devm_kzalloc(&pdev->dev, sizeof(struct int3403_priv),
			    GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	priv->pdev = pdev;
	priv->adev = ACPI_COMPANION(&(pdev->dev));
	if (!priv->adev) {
		result = -EINVAL;
		goto err;
	}

	status = acpi_evaluate_integer(priv->adev->handle, "PTYP",
				       NULL, &priv->type);
	if (ACPI_FAILURE(status)) {
		result = -EINVAL;
		goto err;
	}

	platform_set_drvdata(pdev, priv);
	switch (priv->type) {
	case INT3403_TYPE_SENSOR:
		result = int3403_sensor_add(priv);
		break;
	case INT3403_TYPE_CHARGER:
	case INT3403_TYPE_BATTERY:
		result = int3403_cdev_add(priv);
		break;
	default:
		result = -EINVAL;
	}

	if (result)
		goto err;
	return result;

err:
	return result;
}

static int int3403_remove(struct platform_device *pdev)
{
	struct int3403_priv *priv = platform_get_drvdata(pdev);

	switch (priv->type) {
	case INT3403_TYPE_SENSOR:
		int3403_sensor_remove(priv);
		break;
	case INT3403_TYPE_CHARGER:
	case INT3403_TYPE_BATTERY:
		int3403_cdev_remove(priv);
		break;
	default:
		break;
	}

	return 0;
}

ACPI_MODULE_NAME("int3403");
static const struct acpi_device_id int3403_device_ids[] = {
	{"INT3403", 0},
	{"", 0},
};
MODULE_DEVICE_TABLE(acpi, int3403_device_ids);

static struct acpi_driver acpi_int3403_driver = {
	.name = "INT3403",
	.class = ACPI_INT3403_CLASS,
	.ids = int3403_device_ids,
	.ops = {
		.add = acpi_int3403_add,
		.remove = acpi_int3403_remove,
		.notify = acpi_thermal_notify,
static struct platform_driver int3403_driver = {
	.probe = int3403_add,
	.remove = int3403_remove,
	.driver = {
		.name = "int3403 thermal",
		.owner  = THIS_MODULE,
		.acpi_match_table = int3403_device_ids,
	},
};

module_acpi_driver(acpi_int3403_driver);
module_platform_driver(int3403_driver);

MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
MODULE_LICENSE("GPL v2");