1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
23e8c4d31SAmit Kucheria /*
33e8c4d31SAmit Kucheria  * INT3400 thermal driver
43e8c4d31SAmit Kucheria  *
53e8c4d31SAmit Kucheria  * Copyright (C) 2014, Intel Corporation
63e8c4d31SAmit Kucheria  * Authors: Zhang Rui <rui.zhang@intel.com>
73e8c4d31SAmit Kucheria  */
83e8c4d31SAmit Kucheria 
93e8c4d31SAmit Kucheria #include <linux/module.h>
103e8c4d31SAmit Kucheria #include <linux/platform_device.h>
113e8c4d31SAmit Kucheria #include <linux/acpi.h>
123e8c4d31SAmit Kucheria #include <linux/thermal.h>
133e8c4d31SAmit Kucheria #include "acpi_thermal_rel.h"
143e8c4d31SAmit Kucheria 
153e8c4d31SAmit Kucheria #define INT3400_THERMAL_TABLE_CHANGED 0x83
16006f006fSMatthew Garrett #define INT3400_ODVP_CHANGED 0x88
173e8c4d31SAmit Kucheria 
183e8c4d31SAmit Kucheria enum int3400_thermal_uuid {
193e8c4d31SAmit Kucheria 	INT3400_THERMAL_PASSIVE_1,
203e8c4d31SAmit Kucheria 	INT3400_THERMAL_ACTIVE,
213e8c4d31SAmit Kucheria 	INT3400_THERMAL_CRITICAL,
2216fc8ecaSMatthew Garrett 	INT3400_THERMAL_ADAPTIVE_PERFORMANCE,
2316fc8ecaSMatthew Garrett 	INT3400_THERMAL_EMERGENCY_CALL_MODE,
2416fc8ecaSMatthew Garrett 	INT3400_THERMAL_PASSIVE_2,
2516fc8ecaSMatthew Garrett 	INT3400_THERMAL_POWER_BOSS,
2616fc8ecaSMatthew Garrett 	INT3400_THERMAL_VIRTUAL_SENSOR,
2716fc8ecaSMatthew Garrett 	INT3400_THERMAL_COOLING_MODE,
2816fc8ecaSMatthew Garrett 	INT3400_THERMAL_HARDWARE_DUTY_CYCLING,
293e8c4d31SAmit Kucheria 	INT3400_THERMAL_MAXIMUM_UUID,
303e8c4d31SAmit Kucheria };
313e8c4d31SAmit Kucheria 
323e8c4d31SAmit Kucheria static char *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = {
333e8c4d31SAmit Kucheria 	"42A441D6-AE6A-462b-A84B-4A8CE79027D3",
343e8c4d31SAmit Kucheria 	"3A95C389-E4B8-4629-A526-C52C88626BAE",
353e8c4d31SAmit Kucheria 	"97C68AE7-15FA-499c-B8C9-5DA81D606E0A",
3616fc8ecaSMatthew Garrett 	"63BE270F-1C11-48FD-A6F7-3AF253FF3E2D",
3716fc8ecaSMatthew Garrett 	"5349962F-71E6-431D-9AE8-0A635B710AEE",
3816fc8ecaSMatthew Garrett 	"9E04115A-AE87-4D1C-9500-0F3E340BFE75",
3916fc8ecaSMatthew Garrett 	"F5A35014-C209-46A4-993A-EB56DE7530A1",
4016fc8ecaSMatthew Garrett 	"6ED722A7-9240-48A5-B479-31EEF723D7CF",
4116fc8ecaSMatthew Garrett 	"16CAF1B7-DD38-40ED-B1C1-1B8A1913D531",
4216fc8ecaSMatthew Garrett 	"BE84BABF-C4D4-403D-B495-3128FD44dAC1",
433e8c4d31SAmit Kucheria };
443e8c4d31SAmit Kucheria 
45006f006fSMatthew Garrett struct odvp_attr;
46006f006fSMatthew Garrett 
473e8c4d31SAmit Kucheria struct int3400_thermal_priv {
483e8c4d31SAmit Kucheria 	struct acpi_device *adev;
49006f006fSMatthew Garrett 	struct platform_device *pdev;
503e8c4d31SAmit Kucheria 	struct thermal_zone_device *thermal;
513e8c4d31SAmit Kucheria 	int art_count;
523e8c4d31SAmit Kucheria 	struct art *arts;
533e8c4d31SAmit Kucheria 	int trt_count;
543e8c4d31SAmit Kucheria 	struct trt *trts;
553e8c4d31SAmit Kucheria 	u8 uuid_bitmap;
563e8c4d31SAmit Kucheria 	int rel_misc_dev_res;
573e8c4d31SAmit Kucheria 	int current_uuid_index;
580ba13c76SMatthew Garrett 	char *data_vault;
59006f006fSMatthew Garrett 	int odvp_count;
60006f006fSMatthew Garrett 	int *odvp;
61006f006fSMatthew Garrett 	struct odvp_attr *odvp_attrs;
62006f006fSMatthew Garrett };
63006f006fSMatthew Garrett 
64006f006fSMatthew Garrett static int evaluate_odvp(struct int3400_thermal_priv *priv);
65006f006fSMatthew Garrett 
66006f006fSMatthew Garrett struct odvp_attr {
67006f006fSMatthew Garrett 	int odvp;
68006f006fSMatthew Garrett 	struct int3400_thermal_priv *priv;
69006f006fSMatthew Garrett 	struct kobj_attribute attr;
700ba13c76SMatthew Garrett };
710ba13c76SMatthew Garrett 
720ba13c76SMatthew Garrett static ssize_t data_vault_read(struct file *file, struct kobject *kobj,
730ba13c76SMatthew Garrett 	     struct bin_attribute *attr, char *buf, loff_t off, size_t count)
740ba13c76SMatthew Garrett {
750ba13c76SMatthew Garrett 	memcpy(buf, attr->private + off, count);
760ba13c76SMatthew Garrett 	return count;
770ba13c76SMatthew Garrett }
780ba13c76SMatthew Garrett 
790ba13c76SMatthew Garrett static BIN_ATTR_RO(data_vault, 0);
800ba13c76SMatthew Garrett 
810ba13c76SMatthew Garrett static struct bin_attribute *data_attributes[] = {
820ba13c76SMatthew Garrett 	&bin_attr_data_vault,
830ba13c76SMatthew Garrett 	NULL,
840ba13c76SMatthew Garrett };
850ba13c76SMatthew Garrett 
860ba13c76SMatthew Garrett static const struct attribute_group data_attribute_group = {
870ba13c76SMatthew Garrett 	.bin_attrs = data_attributes,
883e8c4d31SAmit Kucheria };
893e8c4d31SAmit Kucheria 
903e8c4d31SAmit Kucheria static ssize_t available_uuids_show(struct device *dev,
913e8c4d31SAmit Kucheria 				    struct device_attribute *attr,
923e8c4d31SAmit Kucheria 				    char *buf)
933e8c4d31SAmit Kucheria {
943e8c4d31SAmit Kucheria 	struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
953e8c4d31SAmit Kucheria 	int i;
963e8c4d31SAmit Kucheria 	int length = 0;
973e8c4d31SAmit Kucheria 
988d485da0SMatthew Garrett 	if (!priv->uuid_bitmap)
998d485da0SMatthew Garrett 		return sprintf(buf, "UNKNOWN\n");
1008d485da0SMatthew Garrett 
1013e8c4d31SAmit Kucheria 	for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) {
1023e8c4d31SAmit Kucheria 		if (priv->uuid_bitmap & (1 << i))
1033e8c4d31SAmit Kucheria 			if (PAGE_SIZE - length > 0)
104f21431f2STakashi Iwai 				length += scnprintf(&buf[length],
1053e8c4d31SAmit Kucheria 						   PAGE_SIZE - length,
1063e8c4d31SAmit Kucheria 						   "%s\n",
1073e8c4d31SAmit Kucheria 						   int3400_thermal_uuids[i]);
1083e8c4d31SAmit Kucheria 	}
1093e8c4d31SAmit Kucheria 
1103e8c4d31SAmit Kucheria 	return length;
1113e8c4d31SAmit Kucheria }
1123e8c4d31SAmit Kucheria 
1133e8c4d31SAmit Kucheria static ssize_t current_uuid_show(struct device *dev,
1143e8c4d31SAmit Kucheria 				 struct device_attribute *devattr, char *buf)
1153e8c4d31SAmit Kucheria {
1163e8c4d31SAmit Kucheria 	struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
1173e8c4d31SAmit Kucheria 
1188d485da0SMatthew Garrett 	if (priv->current_uuid_index == -1)
1198d485da0SMatthew Garrett 		return sprintf(buf, "INVALID\n");
1208d485da0SMatthew Garrett 
1213e8c4d31SAmit Kucheria 	return sprintf(buf, "%s\n",
1223e8c4d31SAmit Kucheria 		       int3400_thermal_uuids[priv->current_uuid_index]);
1233e8c4d31SAmit Kucheria }
1243e8c4d31SAmit Kucheria 
1253e8c4d31SAmit Kucheria static ssize_t current_uuid_store(struct device *dev,
1263e8c4d31SAmit Kucheria 				  struct device_attribute *attr,
1273e8c4d31SAmit Kucheria 				  const char *buf, size_t count)
1283e8c4d31SAmit Kucheria {
1293e8c4d31SAmit Kucheria 	struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
1303e8c4d31SAmit Kucheria 	int i;
1313e8c4d31SAmit Kucheria 
1323e8c4d31SAmit Kucheria 	for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) {
1338d485da0SMatthew Garrett 		if (!strncmp(buf, int3400_thermal_uuids[i],
1348d485da0SMatthew Garrett 			     sizeof(int3400_thermal_uuids[i]) - 1)) {
1358d485da0SMatthew Garrett 			/*
1368d485da0SMatthew Garrett 			 * If we have a list of supported UUIDs, make sure
1378d485da0SMatthew Garrett 			 * this one is supported.
1388d485da0SMatthew Garrett 			 */
1398d485da0SMatthew Garrett 			if (priv->uuid_bitmap &&
1408d485da0SMatthew Garrett 			    !(priv->uuid_bitmap & (1 << i)))
1418d485da0SMatthew Garrett 				return -EINVAL;
1428d485da0SMatthew Garrett 
1433e8c4d31SAmit Kucheria 			priv->current_uuid_index = i;
1443e8c4d31SAmit Kucheria 			return count;
1453e8c4d31SAmit Kucheria 		}
1463e8c4d31SAmit Kucheria 	}
1473e8c4d31SAmit Kucheria 
1483e8c4d31SAmit Kucheria 	return -EINVAL;
1493e8c4d31SAmit Kucheria }
1503e8c4d31SAmit Kucheria 
1513e8c4d31SAmit Kucheria static DEVICE_ATTR_RW(current_uuid);
1523e8c4d31SAmit Kucheria static DEVICE_ATTR_RO(available_uuids);
1533e8c4d31SAmit Kucheria static struct attribute *uuid_attrs[] = {
1543e8c4d31SAmit Kucheria 	&dev_attr_available_uuids.attr,
1553e8c4d31SAmit Kucheria 	&dev_attr_current_uuid.attr,
1563e8c4d31SAmit Kucheria 	NULL
1573e8c4d31SAmit Kucheria };
1583e8c4d31SAmit Kucheria 
1593e8c4d31SAmit Kucheria static const struct attribute_group uuid_attribute_group = {
1603e8c4d31SAmit Kucheria 	.attrs = uuid_attrs,
1613e8c4d31SAmit Kucheria 	.name = "uuids"
1623e8c4d31SAmit Kucheria };
1633e8c4d31SAmit Kucheria 
1643e8c4d31SAmit Kucheria static int int3400_thermal_get_uuids(struct int3400_thermal_priv *priv)
1653e8c4d31SAmit Kucheria {
1663e8c4d31SAmit Kucheria 	struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL};
1673e8c4d31SAmit Kucheria 	union acpi_object *obja, *objb;
1683e8c4d31SAmit Kucheria 	int i, j;
1693e8c4d31SAmit Kucheria 	int result = 0;
1703e8c4d31SAmit Kucheria 	acpi_status status;
1713e8c4d31SAmit Kucheria 
1723e8c4d31SAmit Kucheria 	status = acpi_evaluate_object(priv->adev->handle, "IDSP", NULL, &buf);
1733e8c4d31SAmit Kucheria 	if (ACPI_FAILURE(status))
1743e8c4d31SAmit Kucheria 		return -ENODEV;
1753e8c4d31SAmit Kucheria 
1763e8c4d31SAmit Kucheria 	obja = (union acpi_object *)buf.pointer;
1773e8c4d31SAmit Kucheria 	if (obja->type != ACPI_TYPE_PACKAGE) {
1783e8c4d31SAmit Kucheria 		result = -EINVAL;
1793e8c4d31SAmit Kucheria 		goto end;
1803e8c4d31SAmit Kucheria 	}
1813e8c4d31SAmit Kucheria 
1823e8c4d31SAmit Kucheria 	for (i = 0; i < obja->package.count; i++) {
1833e8c4d31SAmit Kucheria 		objb = &obja->package.elements[i];
1843e8c4d31SAmit Kucheria 		if (objb->type != ACPI_TYPE_BUFFER) {
1853e8c4d31SAmit Kucheria 			result = -EINVAL;
1863e8c4d31SAmit Kucheria 			goto end;
1873e8c4d31SAmit Kucheria 		}
1883e8c4d31SAmit Kucheria 
1893e8c4d31SAmit Kucheria 		/* UUID must be 16 bytes */
1903e8c4d31SAmit Kucheria 		if (objb->buffer.length != 16) {
1913e8c4d31SAmit Kucheria 			result = -EINVAL;
1923e8c4d31SAmit Kucheria 			goto end;
1933e8c4d31SAmit Kucheria 		}
1943e8c4d31SAmit Kucheria 
1953e8c4d31SAmit Kucheria 		for (j = 0; j < INT3400_THERMAL_MAXIMUM_UUID; j++) {
1963e8c4d31SAmit Kucheria 			guid_t guid;
1973e8c4d31SAmit Kucheria 
1983e8c4d31SAmit Kucheria 			guid_parse(int3400_thermal_uuids[j], &guid);
1993e8c4d31SAmit Kucheria 			if (guid_equal((guid_t *)objb->buffer.pointer, &guid)) {
2003e8c4d31SAmit Kucheria 				priv->uuid_bitmap |= (1 << j);
2013e8c4d31SAmit Kucheria 				break;
2023e8c4d31SAmit Kucheria 			}
2033e8c4d31SAmit Kucheria 		}
2043e8c4d31SAmit Kucheria 	}
2053e8c4d31SAmit Kucheria 
2063e8c4d31SAmit Kucheria end:
2073e8c4d31SAmit Kucheria 	kfree(buf.pointer);
2083e8c4d31SAmit Kucheria 	return result;
2093e8c4d31SAmit Kucheria }
2103e8c4d31SAmit Kucheria 
2113e8c4d31SAmit Kucheria static int int3400_thermal_run_osc(acpi_handle handle,
2123e8c4d31SAmit Kucheria 				enum int3400_thermal_uuid uuid, bool enable)
2133e8c4d31SAmit Kucheria {
2143e8c4d31SAmit Kucheria 	u32 ret, buf[2];
2153e8c4d31SAmit Kucheria 	acpi_status status;
2163e8c4d31SAmit Kucheria 	int result = 0;
2173e8c4d31SAmit Kucheria 	struct acpi_osc_context context = {
2183e8c4d31SAmit Kucheria 		.uuid_str = int3400_thermal_uuids[uuid],
2193e8c4d31SAmit Kucheria 		.rev = 1,
2203e8c4d31SAmit Kucheria 		.cap.length = 8,
2213e8c4d31SAmit Kucheria 	};
2223e8c4d31SAmit Kucheria 
2233e8c4d31SAmit Kucheria 	buf[OSC_QUERY_DWORD] = 0;
2243e8c4d31SAmit Kucheria 	buf[OSC_SUPPORT_DWORD] = enable;
2253e8c4d31SAmit Kucheria 
2263e8c4d31SAmit Kucheria 	context.cap.pointer = buf;
2273e8c4d31SAmit Kucheria 
2283e8c4d31SAmit Kucheria 	status = acpi_run_osc(handle, &context);
2293e8c4d31SAmit Kucheria 	if (ACPI_SUCCESS(status)) {
2303e8c4d31SAmit Kucheria 		ret = *((u32 *)(context.ret.pointer + 4));
2313e8c4d31SAmit Kucheria 		if (ret != enable)
2323e8c4d31SAmit Kucheria 			result = -EPERM;
2333e8c4d31SAmit Kucheria 	} else
2343e8c4d31SAmit Kucheria 		result = -EPERM;
2353e8c4d31SAmit Kucheria 
2363e8c4d31SAmit Kucheria 	kfree(context.ret.pointer);
237006f006fSMatthew Garrett 
2383e8c4d31SAmit Kucheria 	return result;
2393e8c4d31SAmit Kucheria }
2403e8c4d31SAmit Kucheria 
241006f006fSMatthew Garrett static ssize_t odvp_show(struct kobject *kobj, struct kobj_attribute *attr,
242006f006fSMatthew Garrett 			 char *buf)
243006f006fSMatthew Garrett {
244006f006fSMatthew Garrett 	struct odvp_attr *odvp_attr;
245006f006fSMatthew Garrett 
246006f006fSMatthew Garrett 	odvp_attr = container_of(attr, struct odvp_attr, attr);
247006f006fSMatthew Garrett 
248006f006fSMatthew Garrett 	return sprintf(buf, "%d\n", odvp_attr->priv->odvp[odvp_attr->odvp]);
249006f006fSMatthew Garrett }
250006f006fSMatthew Garrett 
251006f006fSMatthew Garrett static void cleanup_odvp(struct int3400_thermal_priv *priv)
252006f006fSMatthew Garrett {
253006f006fSMatthew Garrett 	int i;
254006f006fSMatthew Garrett 
255006f006fSMatthew Garrett 	if (priv->odvp_attrs) {
256006f006fSMatthew Garrett 		for (i = 0; i < priv->odvp_count; i++) {
257006f006fSMatthew Garrett 			sysfs_remove_file(&priv->pdev->dev.kobj,
258006f006fSMatthew Garrett 					  &priv->odvp_attrs[i].attr.attr);
259006f006fSMatthew Garrett 			kfree(priv->odvp_attrs[i].attr.attr.name);
260006f006fSMatthew Garrett 		}
261006f006fSMatthew Garrett 		kfree(priv->odvp_attrs);
262006f006fSMatthew Garrett 	}
263006f006fSMatthew Garrett 	kfree(priv->odvp);
264006f006fSMatthew Garrett 	priv->odvp_count = 0;
265006f006fSMatthew Garrett }
266006f006fSMatthew Garrett 
267006f006fSMatthew Garrett static int evaluate_odvp(struct int3400_thermal_priv *priv)
268006f006fSMatthew Garrett {
269006f006fSMatthew Garrett 	struct acpi_buffer odvp = { ACPI_ALLOCATE_BUFFER, NULL };
270006f006fSMatthew Garrett 	union acpi_object *obj = NULL;
271006f006fSMatthew Garrett 	acpi_status status;
272006f006fSMatthew Garrett 	int i, ret;
273006f006fSMatthew Garrett 
274006f006fSMatthew Garrett 	status = acpi_evaluate_object(priv->adev->handle, "ODVP", NULL, &odvp);
275006f006fSMatthew Garrett 	if (ACPI_FAILURE(status)) {
276006f006fSMatthew Garrett 		ret = -EINVAL;
277006f006fSMatthew Garrett 		goto out_err;
278006f006fSMatthew Garrett 	}
279006f006fSMatthew Garrett 
280006f006fSMatthew Garrett 	obj = odvp.pointer;
281006f006fSMatthew Garrett 	if (obj->type != ACPI_TYPE_PACKAGE) {
282006f006fSMatthew Garrett 		ret = -EINVAL;
283006f006fSMatthew Garrett 		goto out_err;
284006f006fSMatthew Garrett 	}
285006f006fSMatthew Garrett 
286006f006fSMatthew Garrett 	if (priv->odvp == NULL) {
287006f006fSMatthew Garrett 		priv->odvp_count = obj->package.count;
288006f006fSMatthew Garrett 		priv->odvp = kmalloc_array(priv->odvp_count, sizeof(int),
289006f006fSMatthew Garrett 				     GFP_KERNEL);
290006f006fSMatthew Garrett 		if (!priv->odvp) {
291006f006fSMatthew Garrett 			ret = -ENOMEM;
292006f006fSMatthew Garrett 			goto out_err;
293006f006fSMatthew Garrett 		}
294006f006fSMatthew Garrett 	}
295006f006fSMatthew Garrett 
296006f006fSMatthew Garrett 	if (priv->odvp_attrs == NULL) {
297006f006fSMatthew Garrett 		priv->odvp_attrs = kcalloc(priv->odvp_count,
298006f006fSMatthew Garrett 					   sizeof(struct odvp_attr),
299006f006fSMatthew Garrett 					   GFP_KERNEL);
300006f006fSMatthew Garrett 		if (!priv->odvp_attrs) {
301006f006fSMatthew Garrett 			ret = -ENOMEM;
302006f006fSMatthew Garrett 			goto out_err;
303006f006fSMatthew Garrett 		}
304006f006fSMatthew Garrett 		for (i = 0; i < priv->odvp_count; i++) {
305006f006fSMatthew Garrett 			struct odvp_attr *odvp = &priv->odvp_attrs[i];
306006f006fSMatthew Garrett 
307006f006fSMatthew Garrett 			sysfs_attr_init(&odvp->attr.attr);
308006f006fSMatthew Garrett 			odvp->priv = priv;
309006f006fSMatthew Garrett 			odvp->odvp = i;
310006f006fSMatthew Garrett 			odvp->attr.attr.name = kasprintf(GFP_KERNEL,
311006f006fSMatthew Garrett 							 "odvp%d", i);
312006f006fSMatthew Garrett 
313006f006fSMatthew Garrett 			if (!odvp->attr.attr.name) {
314006f006fSMatthew Garrett 				ret = -ENOMEM;
315006f006fSMatthew Garrett 				goto out_err;
316006f006fSMatthew Garrett 			}
317006f006fSMatthew Garrett 			odvp->attr.attr.mode = 0444;
318006f006fSMatthew Garrett 			odvp->attr.show = odvp_show;
319006f006fSMatthew Garrett 			odvp->attr.store = NULL;
320006f006fSMatthew Garrett 			ret = sysfs_create_file(&priv->pdev->dev.kobj,
321006f006fSMatthew Garrett 						&odvp->attr.attr);
322006f006fSMatthew Garrett 			if (ret)
323006f006fSMatthew Garrett 				goto out_err;
324006f006fSMatthew Garrett 		}
325006f006fSMatthew Garrett 	}
326006f006fSMatthew Garrett 
327006f006fSMatthew Garrett 	for (i = 0; i < obj->package.count; i++) {
328006f006fSMatthew Garrett 		if (obj->package.elements[i].type == ACPI_TYPE_INTEGER)
329006f006fSMatthew Garrett 			priv->odvp[i] = obj->package.elements[i].integer.value;
330006f006fSMatthew Garrett 	}
331006f006fSMatthew Garrett 
332006f006fSMatthew Garrett 	kfree(obj);
333006f006fSMatthew Garrett 	return 0;
334006f006fSMatthew Garrett 
335006f006fSMatthew Garrett out_err:
336006f006fSMatthew Garrett 	cleanup_odvp(priv);
337006f006fSMatthew Garrett 	kfree(obj);
338006f006fSMatthew Garrett 	return ret;
339006f006fSMatthew Garrett }
340006f006fSMatthew Garrett 
3413e8c4d31SAmit Kucheria static void int3400_notify(acpi_handle handle,
3423e8c4d31SAmit Kucheria 			u32 event,
3433e8c4d31SAmit Kucheria 			void *data)
3443e8c4d31SAmit Kucheria {
3453e8c4d31SAmit Kucheria 	struct int3400_thermal_priv *priv = data;
3463e8c4d31SAmit Kucheria 	char *thermal_prop[5];
3473e8c4d31SAmit Kucheria 
3483e8c4d31SAmit Kucheria 	if (!priv)
3493e8c4d31SAmit Kucheria 		return;
3503e8c4d31SAmit Kucheria 
3513e8c4d31SAmit Kucheria 	switch (event) {
3523e8c4d31SAmit Kucheria 	case INT3400_THERMAL_TABLE_CHANGED:
3533e8c4d31SAmit Kucheria 		thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s",
3543e8c4d31SAmit Kucheria 				priv->thermal->type);
3553e8c4d31SAmit Kucheria 		thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d",
3563e8c4d31SAmit Kucheria 				priv->thermal->temperature);
3573e8c4d31SAmit Kucheria 		thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP=");
3583e8c4d31SAmit Kucheria 		thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d",
3593e8c4d31SAmit Kucheria 				THERMAL_TABLE_CHANGED);
3603e8c4d31SAmit Kucheria 		thermal_prop[4] = NULL;
3613e8c4d31SAmit Kucheria 		kobject_uevent_env(&priv->thermal->device.kobj, KOBJ_CHANGE,
3623e8c4d31SAmit Kucheria 				thermal_prop);
3633e8c4d31SAmit Kucheria 		break;
364006f006fSMatthew Garrett 	case INT3400_ODVP_CHANGED:
365006f006fSMatthew Garrett 		evaluate_odvp(priv);
366006f006fSMatthew Garrett 		break;
3673e8c4d31SAmit Kucheria 	default:
3683e8c4d31SAmit Kucheria 		/* Ignore unknown notification codes sent to INT3400 device */
3693e8c4d31SAmit Kucheria 		break;
3703e8c4d31SAmit Kucheria 	}
3713e8c4d31SAmit Kucheria }
3723e8c4d31SAmit Kucheria 
3733e8c4d31SAmit Kucheria static int int3400_thermal_get_temp(struct thermal_zone_device *thermal,
3743e8c4d31SAmit Kucheria 			int *temp)
3753e8c4d31SAmit Kucheria {
3763e8c4d31SAmit Kucheria 	*temp = 20 * 1000; /* faked temp sensor with 20C */
3773e8c4d31SAmit Kucheria 	return 0;
3783e8c4d31SAmit Kucheria }
3793e8c4d31SAmit Kucheria 
3803e8c4d31SAmit Kucheria static int int3400_thermal_set_mode(struct thermal_zone_device *thermal,
3813e8c4d31SAmit Kucheria 				enum thermal_device_mode mode)
3823e8c4d31SAmit Kucheria {
3833e8c4d31SAmit Kucheria 	struct int3400_thermal_priv *priv = thermal->devdata;
3843e8c4d31SAmit Kucheria 	int result = 0;
3853e8c4d31SAmit Kucheria 
3863e8c4d31SAmit Kucheria 	if (!priv)
3873e8c4d31SAmit Kucheria 		return -EINVAL;
3883e8c4d31SAmit Kucheria 
3891595d887SAndrzej Pietrasiewicz 	if (mode != THERMAL_DEVICE_ENABLED &&
3901595d887SAndrzej Pietrasiewicz 	    mode != THERMAL_DEVICE_DISABLED)
3913e8c4d31SAmit Kucheria 		return -EINVAL;
3923e8c4d31SAmit Kucheria 
3937f4957beSAndrzej Pietrasiewicz 	if (mode != thermal->mode)
3943e8c4d31SAmit Kucheria 		result = int3400_thermal_run_osc(priv->adev->handle,
3953e8c4d31SAmit Kucheria 						priv->current_uuid_index,
3961595d887SAndrzej Pietrasiewicz 						mode == THERMAL_DEVICE_ENABLED);
3977f4957beSAndrzej Pietrasiewicz 
398006f006fSMatthew Garrett 
399006f006fSMatthew Garrett 	evaluate_odvp(priv);
400006f006fSMatthew Garrett 
4013e8c4d31SAmit Kucheria 	return result;
4023e8c4d31SAmit Kucheria }
4033e8c4d31SAmit Kucheria 
4043e8c4d31SAmit Kucheria static struct thermal_zone_device_ops int3400_thermal_ops = {
4053e8c4d31SAmit Kucheria 	.get_temp = int3400_thermal_get_temp,
40679799562SAndrzej Pietrasiewicz 	.set_mode = int3400_thermal_set_mode,
4073e8c4d31SAmit Kucheria };
4083e8c4d31SAmit Kucheria 
4093e8c4d31SAmit Kucheria static struct thermal_zone_params int3400_thermal_params = {
4103e8c4d31SAmit Kucheria 	.governor_name = "user_space",
4113e8c4d31SAmit Kucheria 	.no_hwmon = true,
4123e8c4d31SAmit Kucheria };
4133e8c4d31SAmit Kucheria 
4140ba13c76SMatthew Garrett static void int3400_setup_gddv(struct int3400_thermal_priv *priv)
4150ba13c76SMatthew Garrett {
4160ba13c76SMatthew Garrett 	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
4170ba13c76SMatthew Garrett 	union acpi_object *obj;
4180ba13c76SMatthew Garrett 	acpi_status status;
4190ba13c76SMatthew Garrett 
4200ba13c76SMatthew Garrett 	status = acpi_evaluate_object(priv->adev->handle, "GDDV", NULL,
4210ba13c76SMatthew Garrett 				      &buffer);
4220ba13c76SMatthew Garrett 	if (ACPI_FAILURE(status) || !buffer.length)
4230ba13c76SMatthew Garrett 		return;
4240ba13c76SMatthew Garrett 
4250ba13c76SMatthew Garrett 	obj = buffer.pointer;
4260ba13c76SMatthew Garrett 	if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 1
4270ba13c76SMatthew Garrett 	    || obj->package.elements[0].type != ACPI_TYPE_BUFFER) {
4280ba13c76SMatthew Garrett 		kfree(buffer.pointer);
4290ba13c76SMatthew Garrett 		return;
4300ba13c76SMatthew Garrett 	}
4310ba13c76SMatthew Garrett 
4320ba13c76SMatthew Garrett 	priv->data_vault = kmemdup(obj->package.elements[0].buffer.pointer,
4330ba13c76SMatthew Garrett 				   obj->package.elements[0].buffer.length,
4340ba13c76SMatthew Garrett 				   GFP_KERNEL);
4350ba13c76SMatthew Garrett 	bin_attr_data_vault.private = priv->data_vault;
4360ba13c76SMatthew Garrett 	bin_attr_data_vault.size = obj->package.elements[0].buffer.length;
4370ba13c76SMatthew Garrett 	kfree(buffer.pointer);
4380ba13c76SMatthew Garrett }
4390ba13c76SMatthew Garrett 
4403e8c4d31SAmit Kucheria static int int3400_thermal_probe(struct platform_device *pdev)
4413e8c4d31SAmit Kucheria {
4423e8c4d31SAmit Kucheria 	struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
4433e8c4d31SAmit Kucheria 	struct int3400_thermal_priv *priv;
4443e8c4d31SAmit Kucheria 	int result;
4453e8c4d31SAmit Kucheria 
4463e8c4d31SAmit Kucheria 	if (!adev)
4473e8c4d31SAmit Kucheria 		return -ENODEV;
4483e8c4d31SAmit Kucheria 
4493e8c4d31SAmit Kucheria 	priv = kzalloc(sizeof(struct int3400_thermal_priv), GFP_KERNEL);
4503e8c4d31SAmit Kucheria 	if (!priv)
4513e8c4d31SAmit Kucheria 		return -ENOMEM;
4523e8c4d31SAmit Kucheria 
453006f006fSMatthew Garrett 	priv->pdev = pdev;
4543e8c4d31SAmit Kucheria 	priv->adev = adev;
4553e8c4d31SAmit Kucheria 
4563e8c4d31SAmit Kucheria 	result = int3400_thermal_get_uuids(priv);
4578d485da0SMatthew Garrett 
4588d485da0SMatthew Garrett 	/* Missing IDSP isn't fatal */
4598d485da0SMatthew Garrett 	if (result && result != -ENODEV)
4603e8c4d31SAmit Kucheria 		goto free_priv;
4613e8c4d31SAmit Kucheria 
4628d485da0SMatthew Garrett 	priv->current_uuid_index = -1;
4638d485da0SMatthew Garrett 
4643e8c4d31SAmit Kucheria 	result = acpi_parse_art(priv->adev->handle, &priv->art_count,
4653e8c4d31SAmit Kucheria 				&priv->arts, true);
4663e8c4d31SAmit Kucheria 	if (result)
4673e8c4d31SAmit Kucheria 		dev_dbg(&pdev->dev, "_ART table parsing error\n");
4683e8c4d31SAmit Kucheria 
4693e8c4d31SAmit Kucheria 	result = acpi_parse_trt(priv->adev->handle, &priv->trt_count,
4703e8c4d31SAmit Kucheria 				&priv->trts, true);
4713e8c4d31SAmit Kucheria 	if (result)
4723e8c4d31SAmit Kucheria 		dev_dbg(&pdev->dev, "_TRT table parsing error\n");
4733e8c4d31SAmit Kucheria 
4743e8c4d31SAmit Kucheria 	platform_set_drvdata(pdev, priv);
4753e8c4d31SAmit Kucheria 
4760ba13c76SMatthew Garrett 	int3400_setup_gddv(priv);
4770ba13c76SMatthew Garrett 
478006f006fSMatthew Garrett 	evaluate_odvp(priv);
479006f006fSMatthew Garrett 
4803e8c4d31SAmit Kucheria 	priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0,
4813e8c4d31SAmit Kucheria 						priv, &int3400_thermal_ops,
4823e8c4d31SAmit Kucheria 						&int3400_thermal_params, 0, 0);
4833e8c4d31SAmit Kucheria 	if (IS_ERR(priv->thermal)) {
4843e8c4d31SAmit Kucheria 		result = PTR_ERR(priv->thermal);
4853e8c4d31SAmit Kucheria 		goto free_art_trt;
4863e8c4d31SAmit Kucheria 	}
4873e8c4d31SAmit Kucheria 
4883e8c4d31SAmit Kucheria 	priv->rel_misc_dev_res = acpi_thermal_rel_misc_device_add(
4893e8c4d31SAmit Kucheria 							priv->adev->handle);
4903e8c4d31SAmit Kucheria 
4913e8c4d31SAmit Kucheria 	result = sysfs_create_group(&pdev->dev.kobj, &uuid_attribute_group);
4923e8c4d31SAmit Kucheria 	if (result)
4933e8c4d31SAmit Kucheria 		goto free_rel_misc;
4943e8c4d31SAmit Kucheria 
4950ba13c76SMatthew Garrett 	if (priv->data_vault) {
4960ba13c76SMatthew Garrett 		result = sysfs_create_group(&pdev->dev.kobj,
4970ba13c76SMatthew Garrett 					    &data_attribute_group);
4980ba13c76SMatthew Garrett 		if (result)
4990ba13c76SMatthew Garrett 			goto free_uuid;
5000ba13c76SMatthew Garrett 	}
5010ba13c76SMatthew Garrett 
5023e8c4d31SAmit Kucheria 	result = acpi_install_notify_handler(
5033e8c4d31SAmit Kucheria 			priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify,
5043e8c4d31SAmit Kucheria 			(void *)priv);
5053e8c4d31SAmit Kucheria 	if (result)
5063e8c4d31SAmit Kucheria 		goto free_sysfs;
5073e8c4d31SAmit Kucheria 
5083e8c4d31SAmit Kucheria 	return 0;
5093e8c4d31SAmit Kucheria 
5103e8c4d31SAmit Kucheria free_sysfs:
511006f006fSMatthew Garrett 	cleanup_odvp(priv);
512006f006fSMatthew Garrett 	if (priv->data_vault) {
5130ba13c76SMatthew Garrett 		sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group);
514006f006fSMatthew Garrett 		kfree(priv->data_vault);
515006f006fSMatthew Garrett 	}
5160ba13c76SMatthew Garrett free_uuid:
5173e8c4d31SAmit Kucheria 	sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
5183e8c4d31SAmit Kucheria free_rel_misc:
5193e8c4d31SAmit Kucheria 	if (!priv->rel_misc_dev_res)
5203e8c4d31SAmit Kucheria 		acpi_thermal_rel_misc_device_remove(priv->adev->handle);
5213e8c4d31SAmit Kucheria 	thermal_zone_device_unregister(priv->thermal);
5223e8c4d31SAmit Kucheria free_art_trt:
5233e8c4d31SAmit Kucheria 	kfree(priv->trts);
5243e8c4d31SAmit Kucheria 	kfree(priv->arts);
5253e8c4d31SAmit Kucheria free_priv:
5263e8c4d31SAmit Kucheria 	kfree(priv);
5273e8c4d31SAmit Kucheria 	return result;
5283e8c4d31SAmit Kucheria }
5293e8c4d31SAmit Kucheria 
5303e8c4d31SAmit Kucheria static int int3400_thermal_remove(struct platform_device *pdev)
5313e8c4d31SAmit Kucheria {
5323e8c4d31SAmit Kucheria 	struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);
5333e8c4d31SAmit Kucheria 
5343e8c4d31SAmit Kucheria 	acpi_remove_notify_handler(
5353e8c4d31SAmit Kucheria 			priv->adev->handle, ACPI_DEVICE_NOTIFY,
5363e8c4d31SAmit Kucheria 			int3400_notify);
5373e8c4d31SAmit Kucheria 
538006f006fSMatthew Garrett 	cleanup_odvp(priv);
539006f006fSMatthew Garrett 
5403e8c4d31SAmit Kucheria 	if (!priv->rel_misc_dev_res)
5413e8c4d31SAmit Kucheria 		acpi_thermal_rel_misc_device_remove(priv->adev->handle);
5423e8c4d31SAmit Kucheria 
5430ba13c76SMatthew Garrett 	if (priv->data_vault)
5440ba13c76SMatthew Garrett 		sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group);
5453e8c4d31SAmit Kucheria 	sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
5463e8c4d31SAmit Kucheria 	thermal_zone_device_unregister(priv->thermal);
5470ba13c76SMatthew Garrett 	kfree(priv->data_vault);
5483e8c4d31SAmit Kucheria 	kfree(priv->trts);
5493e8c4d31SAmit Kucheria 	kfree(priv->arts);
5503e8c4d31SAmit Kucheria 	kfree(priv);
5513e8c4d31SAmit Kucheria 	return 0;
5523e8c4d31SAmit Kucheria }
5533e8c4d31SAmit Kucheria 
5543e8c4d31SAmit Kucheria static const struct acpi_device_id int3400_thermal_match[] = {
5553e8c4d31SAmit Kucheria 	{"INT3400", 0},
55626d8bec1SGayatri Kammela 	{"INTC1040", 0},
5573e8c4d31SAmit Kucheria 	{}
5583e8c4d31SAmit Kucheria };
5593e8c4d31SAmit Kucheria 
5603e8c4d31SAmit Kucheria MODULE_DEVICE_TABLE(acpi, int3400_thermal_match);
5613e8c4d31SAmit Kucheria 
5623e8c4d31SAmit Kucheria static struct platform_driver int3400_thermal_driver = {
5633e8c4d31SAmit Kucheria 	.probe = int3400_thermal_probe,
5643e8c4d31SAmit Kucheria 	.remove = int3400_thermal_remove,
5653e8c4d31SAmit Kucheria 	.driver = {
5663e8c4d31SAmit Kucheria 		   .name = "int3400 thermal",
5673e8c4d31SAmit Kucheria 		   .acpi_match_table = ACPI_PTR(int3400_thermal_match),
5683e8c4d31SAmit Kucheria 		   },
5693e8c4d31SAmit Kucheria };
5703e8c4d31SAmit Kucheria 
5713e8c4d31SAmit Kucheria module_platform_driver(int3400_thermal_driver);
5723e8c4d31SAmit Kucheria 
5733e8c4d31SAmit Kucheria MODULE_DESCRIPTION("INT3400 Thermal driver");
5743e8c4d31SAmit Kucheria MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
5753e8c4d31SAmit Kucheria MODULE_LICENSE("GPL");
576