12025cf9eSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
23e8c4d31SAmit Kucheria /*
33e8c4d31SAmit Kucheria  * ACPI INT3403 thermal driver
43e8c4d31SAmit Kucheria  * Copyright (c) 2013, Intel Corporation.
53e8c4d31SAmit Kucheria  */
63e8c4d31SAmit Kucheria 
73e8c4d31SAmit Kucheria #include <linux/kernel.h>
83e8c4d31SAmit Kucheria #include <linux/module.h>
93e8c4d31SAmit Kucheria #include <linux/init.h>
103e8c4d31SAmit Kucheria #include <linux/types.h>
113e8c4d31SAmit Kucheria #include <linux/acpi.h>
123e8c4d31SAmit Kucheria #include <linux/thermal.h>
133e8c4d31SAmit Kucheria #include <linux/platform_device.h>
143e8c4d31SAmit Kucheria #include "int340x_thermal_zone.h"
153e8c4d31SAmit Kucheria 
163e8c4d31SAmit Kucheria #define INT3403_TYPE_SENSOR		0x03
173e8c4d31SAmit Kucheria #define INT3403_TYPE_CHARGER		0x0B
183e8c4d31SAmit Kucheria #define INT3403_TYPE_BATTERY		0x0C
193e8c4d31SAmit Kucheria #define INT3403_PERF_CHANGED_EVENT	0x80
203e8c4d31SAmit Kucheria #define INT3403_PERF_TRIP_POINT_CHANGED	0x81
213e8c4d31SAmit Kucheria #define INT3403_THERMAL_EVENT		0x90
223e8c4d31SAmit Kucheria 
233e8c4d31SAmit Kucheria /* Preserved structure for future expandbility */
243e8c4d31SAmit Kucheria struct int3403_sensor {
253e8c4d31SAmit Kucheria 	struct int34x_thermal_zone *int340x_zone;
263e8c4d31SAmit Kucheria };
273e8c4d31SAmit Kucheria 
283e8c4d31SAmit Kucheria struct int3403_performance_state {
293e8c4d31SAmit Kucheria 	u64 performance;
303e8c4d31SAmit Kucheria 	u64 power;
313e8c4d31SAmit Kucheria 	u64 latency;
323e8c4d31SAmit Kucheria 	u64 linear;
333e8c4d31SAmit Kucheria 	u64 control;
343e8c4d31SAmit Kucheria 	u64 raw_performace;
353e8c4d31SAmit Kucheria 	char *raw_unit;
363e8c4d31SAmit Kucheria 	int reserved;
373e8c4d31SAmit Kucheria };
383e8c4d31SAmit Kucheria 
393e8c4d31SAmit Kucheria struct int3403_cdev {
403e8c4d31SAmit Kucheria 	struct thermal_cooling_device *cdev;
413e8c4d31SAmit Kucheria 	unsigned long max_state;
423e8c4d31SAmit Kucheria };
433e8c4d31SAmit Kucheria 
443e8c4d31SAmit Kucheria struct int3403_priv {
453e8c4d31SAmit Kucheria 	struct platform_device *pdev;
463e8c4d31SAmit Kucheria 	struct acpi_device *adev;
473e8c4d31SAmit Kucheria 	unsigned long long type;
483e8c4d31SAmit Kucheria 	void *priv;
493e8c4d31SAmit Kucheria };
503e8c4d31SAmit Kucheria 
int3403_notify(acpi_handle handle,u32 event,void * data)513e8c4d31SAmit Kucheria static void int3403_notify(acpi_handle handle,
523e8c4d31SAmit Kucheria 		u32 event, void *data)
533e8c4d31SAmit Kucheria {
543e8c4d31SAmit Kucheria 	struct int3403_priv *priv = data;
553e8c4d31SAmit Kucheria 	struct int3403_sensor *obj;
563e8c4d31SAmit Kucheria 
573e8c4d31SAmit Kucheria 	if (!priv)
583e8c4d31SAmit Kucheria 		return;
593e8c4d31SAmit Kucheria 
603e8c4d31SAmit Kucheria 	obj = priv->priv;
613e8c4d31SAmit Kucheria 	if (priv->type != INT3403_TYPE_SENSOR || !obj)
623e8c4d31SAmit Kucheria 		return;
633e8c4d31SAmit Kucheria 
643e8c4d31SAmit Kucheria 	switch (event) {
653e8c4d31SAmit Kucheria 	case INT3403_PERF_CHANGED_EVENT:
663e8c4d31SAmit Kucheria 		break;
673e8c4d31SAmit Kucheria 	case INT3403_THERMAL_EVENT:
683e8c4d31SAmit Kucheria 		int340x_thermal_zone_device_update(obj->int340x_zone,
693e8c4d31SAmit Kucheria 						   THERMAL_TRIP_VIOLATED);
703e8c4d31SAmit Kucheria 		break;
713e8c4d31SAmit Kucheria 	case INT3403_PERF_TRIP_POINT_CHANGED:
72*b1bf9dbfSRafael J. Wysocki 		int340x_thermal_update_trips(obj->int340x_zone);
733e8c4d31SAmit Kucheria 		int340x_thermal_zone_device_update(obj->int340x_zone,
743e8c4d31SAmit Kucheria 						   THERMAL_TRIP_CHANGED);
753e8c4d31SAmit Kucheria 		break;
763e8c4d31SAmit Kucheria 	default:
77f3d7fb38SAlex Hung 		dev_dbg(&priv->pdev->dev, "Unsupported event [0x%x]\n", event);
783e8c4d31SAmit Kucheria 		break;
793e8c4d31SAmit Kucheria 	}
803e8c4d31SAmit Kucheria }
813e8c4d31SAmit Kucheria 
int3403_sensor_add(struct int3403_priv * priv)823e8c4d31SAmit Kucheria static int int3403_sensor_add(struct int3403_priv *priv)
833e8c4d31SAmit Kucheria {
843e8c4d31SAmit Kucheria 	int result = 0;
853e8c4d31SAmit Kucheria 	struct int3403_sensor *obj;
863e8c4d31SAmit Kucheria 
873e8c4d31SAmit Kucheria 	obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL);
883e8c4d31SAmit Kucheria 	if (!obj)
893e8c4d31SAmit Kucheria 		return -ENOMEM;
903e8c4d31SAmit Kucheria 
913e8c4d31SAmit Kucheria 	priv->priv = obj;
923e8c4d31SAmit Kucheria 
933e8c4d31SAmit Kucheria 	obj->int340x_zone = int340x_thermal_zone_add(priv->adev, NULL);
943e8c4d31SAmit Kucheria 	if (IS_ERR(obj->int340x_zone))
953e8c4d31SAmit Kucheria 		return PTR_ERR(obj->int340x_zone);
963e8c4d31SAmit Kucheria 
973e8c4d31SAmit Kucheria 	result = acpi_install_notify_handler(priv->adev->handle,
983e8c4d31SAmit Kucheria 			ACPI_DEVICE_NOTIFY, int3403_notify,
993e8c4d31SAmit Kucheria 			(void *)priv);
1003e8c4d31SAmit Kucheria 	if (result)
1013e8c4d31SAmit Kucheria 		goto err_free_obj;
1023e8c4d31SAmit Kucheria 
1033e8c4d31SAmit Kucheria 	return 0;
1043e8c4d31SAmit Kucheria 
1053e8c4d31SAmit Kucheria  err_free_obj:
1063e8c4d31SAmit Kucheria 	int340x_thermal_zone_remove(obj->int340x_zone);
1073e8c4d31SAmit Kucheria 	return result;
1083e8c4d31SAmit Kucheria }
1093e8c4d31SAmit Kucheria 
int3403_sensor_remove(struct int3403_priv * priv)1103e8c4d31SAmit Kucheria static int int3403_sensor_remove(struct int3403_priv *priv)
1113e8c4d31SAmit Kucheria {
1123e8c4d31SAmit Kucheria 	struct int3403_sensor *obj = priv->priv;
1133e8c4d31SAmit Kucheria 
1143e8c4d31SAmit Kucheria 	acpi_remove_notify_handler(priv->adev->handle,
1153e8c4d31SAmit Kucheria 				   ACPI_DEVICE_NOTIFY, int3403_notify);
1163e8c4d31SAmit Kucheria 	int340x_thermal_zone_remove(obj->int340x_zone);
1173e8c4d31SAmit Kucheria 
1183e8c4d31SAmit Kucheria 	return 0;
1193e8c4d31SAmit Kucheria }
1203e8c4d31SAmit Kucheria 
1213e8c4d31SAmit Kucheria /* INT3403 Cooling devices */
int3403_get_max_state(struct thermal_cooling_device * cdev,unsigned long * state)1223e8c4d31SAmit Kucheria static int int3403_get_max_state(struct thermal_cooling_device *cdev,
1233e8c4d31SAmit Kucheria 				 unsigned long *state)
1243e8c4d31SAmit Kucheria {
1253e8c4d31SAmit Kucheria 	struct int3403_priv *priv = cdev->devdata;
1263e8c4d31SAmit Kucheria 	struct int3403_cdev *obj = priv->priv;
1273e8c4d31SAmit Kucheria 
1283e8c4d31SAmit Kucheria 	*state = obj->max_state;
1293e8c4d31SAmit Kucheria 	return 0;
1303e8c4d31SAmit Kucheria }
1313e8c4d31SAmit Kucheria 
int3403_get_cur_state(struct thermal_cooling_device * cdev,unsigned long * state)1323e8c4d31SAmit Kucheria static int int3403_get_cur_state(struct thermal_cooling_device *cdev,
1333e8c4d31SAmit Kucheria 				 unsigned long *state)
1343e8c4d31SAmit Kucheria {
1353e8c4d31SAmit Kucheria 	struct int3403_priv *priv = cdev->devdata;
1363e8c4d31SAmit Kucheria 	unsigned long long level;
1373e8c4d31SAmit Kucheria 	acpi_status status;
1383e8c4d31SAmit Kucheria 
1393e8c4d31SAmit Kucheria 	status = acpi_evaluate_integer(priv->adev->handle, "PPPC", NULL, &level);
1403e8c4d31SAmit Kucheria 	if (ACPI_SUCCESS(status)) {
1413e8c4d31SAmit Kucheria 		*state = level;
1423e8c4d31SAmit Kucheria 		return 0;
1433e8c4d31SAmit Kucheria 	} else
1443e8c4d31SAmit Kucheria 		return -EINVAL;
1453e8c4d31SAmit Kucheria }
1463e8c4d31SAmit Kucheria 
1473e8c4d31SAmit Kucheria static int
int3403_set_cur_state(struct thermal_cooling_device * cdev,unsigned long state)1483e8c4d31SAmit Kucheria int3403_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
1493e8c4d31SAmit Kucheria {
1503e8c4d31SAmit Kucheria 	struct int3403_priv *priv = cdev->devdata;
1513e8c4d31SAmit Kucheria 	acpi_status status;
1523e8c4d31SAmit Kucheria 
1533e8c4d31SAmit Kucheria 	status = acpi_execute_simple_method(priv->adev->handle, "SPPC", state);
1543e8c4d31SAmit Kucheria 	if (ACPI_SUCCESS(status))
1553e8c4d31SAmit Kucheria 		return 0;
1563e8c4d31SAmit Kucheria 	else
1573e8c4d31SAmit Kucheria 		return -EINVAL;
1583e8c4d31SAmit Kucheria }
1593e8c4d31SAmit Kucheria 
1603e8c4d31SAmit Kucheria static const struct thermal_cooling_device_ops int3403_cooling_ops = {
1613e8c4d31SAmit Kucheria 	.get_max_state = int3403_get_max_state,
1623e8c4d31SAmit Kucheria 	.get_cur_state = int3403_get_cur_state,
1633e8c4d31SAmit Kucheria 	.set_cur_state = int3403_set_cur_state,
1643e8c4d31SAmit Kucheria };
1653e8c4d31SAmit Kucheria 
int3403_cdev_add(struct int3403_priv * priv)1663e8c4d31SAmit Kucheria static int int3403_cdev_add(struct int3403_priv *priv)
1673e8c4d31SAmit Kucheria {
1683e8c4d31SAmit Kucheria 	int result = 0;
1693e8c4d31SAmit Kucheria 	acpi_status status;
1703e8c4d31SAmit Kucheria 	struct int3403_cdev *obj;
1713e8c4d31SAmit Kucheria 	struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
1723e8c4d31SAmit Kucheria 	union acpi_object *p;
1733e8c4d31SAmit Kucheria 
1743e8c4d31SAmit Kucheria 	obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL);
1753e8c4d31SAmit Kucheria 	if (!obj)
1763e8c4d31SAmit Kucheria 		return -ENOMEM;
1773e8c4d31SAmit Kucheria 
1783e8c4d31SAmit Kucheria 	status = acpi_evaluate_object(priv->adev->handle, "PPSS", NULL, &buf);
1793e8c4d31SAmit Kucheria 	if (ACPI_FAILURE(status))
1803e8c4d31SAmit Kucheria 		return -ENODEV;
1813e8c4d31SAmit Kucheria 
1823e8c4d31SAmit Kucheria 	p = buf.pointer;
1833e8c4d31SAmit Kucheria 	if (!p || (p->type != ACPI_TYPE_PACKAGE)) {
1844c8a342cSRishi Gupta 		pr_warn("Invalid PPSS data\n");
1853e8c4d31SAmit Kucheria 		kfree(buf.pointer);
1863e8c4d31SAmit Kucheria 		return -EFAULT;
1873e8c4d31SAmit Kucheria 	}
1883e8c4d31SAmit Kucheria 
1893e8c4d31SAmit Kucheria 	priv->priv = obj;
1903e8c4d31SAmit Kucheria 	obj->max_state = p->package.count - 1;
1913e8c4d31SAmit Kucheria 	obj->cdev =
1923e8c4d31SAmit Kucheria 		thermal_cooling_device_register(acpi_device_bid(priv->adev),
1933e8c4d31SAmit Kucheria 				priv, &int3403_cooling_ops);
1943e8c4d31SAmit Kucheria 	if (IS_ERR(obj->cdev))
1953e8c4d31SAmit Kucheria 		result = PTR_ERR(obj->cdev);
1963e8c4d31SAmit Kucheria 
1973e8c4d31SAmit Kucheria 	kfree(buf.pointer);
1983e8c4d31SAmit Kucheria 	/* TODO: add ACPI notification support */
1993e8c4d31SAmit Kucheria 
2003e8c4d31SAmit Kucheria 	return result;
2013e8c4d31SAmit Kucheria }
2023e8c4d31SAmit Kucheria 
int3403_cdev_remove(struct int3403_priv * priv)2033e8c4d31SAmit Kucheria static int int3403_cdev_remove(struct int3403_priv *priv)
2043e8c4d31SAmit Kucheria {
2053e8c4d31SAmit Kucheria 	struct int3403_cdev *obj = priv->priv;
2063e8c4d31SAmit Kucheria 
2073e8c4d31SAmit Kucheria 	thermal_cooling_device_unregister(obj->cdev);
2083e8c4d31SAmit Kucheria 	return 0;
2093e8c4d31SAmit Kucheria }
2103e8c4d31SAmit Kucheria 
int3403_add(struct platform_device * pdev)2113e8c4d31SAmit Kucheria static int int3403_add(struct platform_device *pdev)
2123e8c4d31SAmit Kucheria {
2133e8c4d31SAmit Kucheria 	struct int3403_priv *priv;
2143e8c4d31SAmit Kucheria 	int result = 0;
2156eb61a93SZhang Rui 	unsigned long long tmp;
2163e8c4d31SAmit Kucheria 	acpi_status status;
2173e8c4d31SAmit Kucheria 
2183e8c4d31SAmit Kucheria 	priv = devm_kzalloc(&pdev->dev, sizeof(struct int3403_priv),
2193e8c4d31SAmit Kucheria 			    GFP_KERNEL);
2203e8c4d31SAmit Kucheria 	if (!priv)
2213e8c4d31SAmit Kucheria 		return -ENOMEM;
2223e8c4d31SAmit Kucheria 
2233e8c4d31SAmit Kucheria 	priv->pdev = pdev;
2243e8c4d31SAmit Kucheria 	priv->adev = ACPI_COMPANION(&(pdev->dev));
2253e8c4d31SAmit Kucheria 	if (!priv->adev) {
2263e8c4d31SAmit Kucheria 		result = -EINVAL;
2273e8c4d31SAmit Kucheria 		goto err;
2283e8c4d31SAmit Kucheria 	}
2293e8c4d31SAmit Kucheria 
2303e8c4d31SAmit Kucheria 
2313e8c4d31SAmit Kucheria 	status = acpi_evaluate_integer(priv->adev->handle, "_TMP",
2323e8c4d31SAmit Kucheria 				       NULL, &tmp);
2333e8c4d31SAmit Kucheria 	if (ACPI_FAILURE(status)) {
2346eb61a93SZhang Rui 		status = acpi_evaluate_integer(priv->adev->handle, "PTYP",
2356eb61a93SZhang Rui 				       NULL, &priv->type);
2366eb61a93SZhang Rui 		if (ACPI_FAILURE(status)) {
2373e8c4d31SAmit Kucheria 			result = -EINVAL;
2383e8c4d31SAmit Kucheria 			goto err;
2396eb61a93SZhang Rui 		}
2403e8c4d31SAmit Kucheria 	} else {
2413e8c4d31SAmit Kucheria 		priv->type = INT3403_TYPE_SENSOR;
2423e8c4d31SAmit Kucheria 	}
2433e8c4d31SAmit Kucheria 
2443e8c4d31SAmit Kucheria 	platform_set_drvdata(pdev, priv);
2453e8c4d31SAmit Kucheria 	switch (priv->type) {
2463e8c4d31SAmit Kucheria 	case INT3403_TYPE_SENSOR:
2473e8c4d31SAmit Kucheria 		result = int3403_sensor_add(priv);
2483e8c4d31SAmit Kucheria 		break;
2493e8c4d31SAmit Kucheria 	case INT3403_TYPE_CHARGER:
2503e8c4d31SAmit Kucheria 	case INT3403_TYPE_BATTERY:
2513e8c4d31SAmit Kucheria 		result = int3403_cdev_add(priv);
2523e8c4d31SAmit Kucheria 		break;
2533e8c4d31SAmit Kucheria 	default:
2543e8c4d31SAmit Kucheria 		result = -EINVAL;
2553e8c4d31SAmit Kucheria 	}
2563e8c4d31SAmit Kucheria 
2573e8c4d31SAmit Kucheria 	if (result)
2583e8c4d31SAmit Kucheria 		goto err;
2593e8c4d31SAmit Kucheria 	return result;
2603e8c4d31SAmit Kucheria 
2613e8c4d31SAmit Kucheria err:
2623e8c4d31SAmit Kucheria 	return result;
2633e8c4d31SAmit Kucheria }
2643e8c4d31SAmit Kucheria 
int3403_remove(struct platform_device * pdev)2653e8c4d31SAmit Kucheria static int int3403_remove(struct platform_device *pdev)
2663e8c4d31SAmit Kucheria {
2673e8c4d31SAmit Kucheria 	struct int3403_priv *priv = platform_get_drvdata(pdev);
2683e8c4d31SAmit Kucheria 
2693e8c4d31SAmit Kucheria 	switch (priv->type) {
2703e8c4d31SAmit Kucheria 	case INT3403_TYPE_SENSOR:
2713e8c4d31SAmit Kucheria 		int3403_sensor_remove(priv);
2723e8c4d31SAmit Kucheria 		break;
2733e8c4d31SAmit Kucheria 	case INT3403_TYPE_CHARGER:
2743e8c4d31SAmit Kucheria 	case INT3403_TYPE_BATTERY:
2753e8c4d31SAmit Kucheria 		int3403_cdev_remove(priv);
2763e8c4d31SAmit Kucheria 		break;
2773e8c4d31SAmit Kucheria 	default:
2783e8c4d31SAmit Kucheria 		break;
2793e8c4d31SAmit Kucheria 	}
2803e8c4d31SAmit Kucheria 
2813e8c4d31SAmit Kucheria 	return 0;
2823e8c4d31SAmit Kucheria }
2833e8c4d31SAmit Kucheria 
2843e8c4d31SAmit Kucheria static const struct acpi_device_id int3403_device_ids[] = {
2853e8c4d31SAmit Kucheria 	{"INT3403", 0},
28626d8bec1SGayatri Kammela 	{"INTC1043", 0},
28767698880SSrinivas Pandruvada 	{"INTC1046", 0},
288657b95d3SSumeet Pawnikar 	{"INTC1062", 0},
289a95be874SSrinivas Pandruvada 	{"INTC10A1", 0},
2903e8c4d31SAmit Kucheria 	{"", 0},
2913e8c4d31SAmit Kucheria };
2923e8c4d31SAmit Kucheria MODULE_DEVICE_TABLE(acpi, int3403_device_ids);
2933e8c4d31SAmit Kucheria 
2943e8c4d31SAmit Kucheria static struct platform_driver int3403_driver = {
2953e8c4d31SAmit Kucheria 	.probe = int3403_add,
2963e8c4d31SAmit Kucheria 	.remove = int3403_remove,
2973e8c4d31SAmit Kucheria 	.driver = {
2983e8c4d31SAmit Kucheria 		.name = "int3403 thermal",
2993e8c4d31SAmit Kucheria 		.acpi_match_table = int3403_device_ids,
3003e8c4d31SAmit Kucheria 	},
3013e8c4d31SAmit Kucheria };
3023e8c4d31SAmit Kucheria 
3033e8c4d31SAmit Kucheria module_platform_driver(int3403_driver);
3043e8c4d31SAmit Kucheria 
3053e8c4d31SAmit Kucheria MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
3063e8c4d31SAmit Kucheria MODULE_LICENSE("GPL v2");
3073e8c4d31SAmit Kucheria MODULE_DESCRIPTION("ACPI INT3403 thermal driver");
308