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