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
1794a3c35eSSrinivas Pandruvada #define INT3400_KEEP_ALIVE 0xA0
183e8c4d31SAmit Kucheria 
193e8c4d31SAmit Kucheria enum int3400_thermal_uuid {
20c7ff2976SSrinivas Pandruvada 	INT3400_THERMAL_ACTIVE = 0,
213e8c4d31SAmit Kucheria 	INT3400_THERMAL_PASSIVE_1,
223e8c4d31SAmit Kucheria 	INT3400_THERMAL_CRITICAL,
2316fc8ecaSMatthew Garrett 	INT3400_THERMAL_ADAPTIVE_PERFORMANCE,
2416fc8ecaSMatthew Garrett 	INT3400_THERMAL_EMERGENCY_CALL_MODE,
2516fc8ecaSMatthew Garrett 	INT3400_THERMAL_PASSIVE_2,
2616fc8ecaSMatthew Garrett 	INT3400_THERMAL_POWER_BOSS,
2716fc8ecaSMatthew Garrett 	INT3400_THERMAL_VIRTUAL_SENSOR,
2816fc8ecaSMatthew Garrett 	INT3400_THERMAL_COOLING_MODE,
2916fc8ecaSMatthew Garrett 	INT3400_THERMAL_HARDWARE_DUTY_CYCLING,
303e8c4d31SAmit Kucheria 	INT3400_THERMAL_MAXIMUM_UUID,
313e8c4d31SAmit Kucheria };
323e8c4d31SAmit Kucheria 
333e8c4d31SAmit Kucheria static char *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = {
343e8c4d31SAmit Kucheria 	"3A95C389-E4B8-4629-A526-C52C88626BAE",
35c7ff2976SSrinivas Pandruvada 	"42A441D6-AE6A-462b-A84B-4A8CE79027D3",
363e8c4d31SAmit Kucheria 	"97C68AE7-15FA-499c-B8C9-5DA81D606E0A",
3716fc8ecaSMatthew Garrett 	"63BE270F-1C11-48FD-A6F7-3AF253FF3E2D",
3816fc8ecaSMatthew Garrett 	"5349962F-71E6-431D-9AE8-0A635B710AEE",
3916fc8ecaSMatthew Garrett 	"9E04115A-AE87-4D1C-9500-0F3E340BFE75",
4016fc8ecaSMatthew Garrett 	"F5A35014-C209-46A4-993A-EB56DE7530A1",
4116fc8ecaSMatthew Garrett 	"6ED722A7-9240-48A5-B479-31EEF723D7CF",
4216fc8ecaSMatthew Garrett 	"16CAF1B7-DD38-40ED-B1C1-1B8A1913D531",
4316fc8ecaSMatthew Garrett 	"BE84BABF-C4D4-403D-B495-3128FD44dAC1",
443e8c4d31SAmit Kucheria };
453e8c4d31SAmit Kucheria 
46006f006fSMatthew Garrett struct odvp_attr;
47006f006fSMatthew Garrett 
483e8c4d31SAmit Kucheria struct int3400_thermal_priv {
493e8c4d31SAmit Kucheria 	struct acpi_device *adev;
50006f006fSMatthew Garrett 	struct platform_device *pdev;
513e8c4d31SAmit Kucheria 	struct thermal_zone_device *thermal;
523e8c4d31SAmit Kucheria 	int art_count;
533e8c4d31SAmit Kucheria 	struct art *arts;
543e8c4d31SAmit Kucheria 	int trt_count;
553e8c4d31SAmit Kucheria 	struct trt *trts;
56668f69a5SSrinivas Pandruvada 	u32 uuid_bitmap;
573e8c4d31SAmit Kucheria 	int rel_misc_dev_res;
583e8c4d31SAmit Kucheria 	int current_uuid_index;
590ba13c76SMatthew Garrett 	char *data_vault;
60006f006fSMatthew Garrett 	int odvp_count;
61006f006fSMatthew Garrett 	int *odvp;
62c7ff2976SSrinivas Pandruvada 	u32 os_uuid_mask;
63006f006fSMatthew Garrett 	struct odvp_attr *odvp_attrs;
64006f006fSMatthew Garrett };
65006f006fSMatthew Garrett 
66006f006fSMatthew Garrett static int evaluate_odvp(struct int3400_thermal_priv *priv);
67006f006fSMatthew Garrett 
68006f006fSMatthew Garrett struct odvp_attr {
69006f006fSMatthew Garrett 	int odvp;
70006f006fSMatthew Garrett 	struct int3400_thermal_priv *priv;
71006f006fSMatthew Garrett 	struct kobj_attribute attr;
720ba13c76SMatthew Garrett };
730ba13c76SMatthew Garrett 
740ba13c76SMatthew Garrett static ssize_t data_vault_read(struct file *file, struct kobject *kobj,
750ba13c76SMatthew Garrett 	     struct bin_attribute *attr, char *buf, loff_t off, size_t count)
760ba13c76SMatthew Garrett {
770ba13c76SMatthew Garrett 	memcpy(buf, attr->private + off, count);
780ba13c76SMatthew Garrett 	return count;
790ba13c76SMatthew Garrett }
800ba13c76SMatthew Garrett 
810ba13c76SMatthew Garrett static BIN_ATTR_RO(data_vault, 0);
820ba13c76SMatthew Garrett 
830ba13c76SMatthew Garrett static struct bin_attribute *data_attributes[] = {
840ba13c76SMatthew Garrett 	&bin_attr_data_vault,
850ba13c76SMatthew Garrett 	NULL,
860ba13c76SMatthew Garrett };
870ba13c76SMatthew Garrett 
8894a3c35eSSrinivas Pandruvada static ssize_t imok_store(struct device *dev, struct device_attribute *attr,
8994a3c35eSSrinivas Pandruvada 			  const char *buf, size_t count)
9094a3c35eSSrinivas Pandruvada {
9194a3c35eSSrinivas Pandruvada 	struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
9294a3c35eSSrinivas Pandruvada 	acpi_status status;
9394a3c35eSSrinivas Pandruvada 	int input, ret;
9494a3c35eSSrinivas Pandruvada 
9594a3c35eSSrinivas Pandruvada 	ret = kstrtouint(buf, 10, &input);
9694a3c35eSSrinivas Pandruvada 	if (ret)
9794a3c35eSSrinivas Pandruvada 		return ret;
9894a3c35eSSrinivas Pandruvada 	status = acpi_execute_simple_method(priv->adev->handle, "IMOK", input);
9994a3c35eSSrinivas Pandruvada 	if (ACPI_FAILURE(status))
10094a3c35eSSrinivas Pandruvada 		return -EIO;
10194a3c35eSSrinivas Pandruvada 
10294a3c35eSSrinivas Pandruvada 	return count;
10394a3c35eSSrinivas Pandruvada }
10494a3c35eSSrinivas Pandruvada 
10594a3c35eSSrinivas Pandruvada static DEVICE_ATTR_WO(imok);
10694a3c35eSSrinivas Pandruvada 
10794a3c35eSSrinivas Pandruvada static struct attribute *imok_attr[] = {
10894a3c35eSSrinivas Pandruvada 	&dev_attr_imok.attr,
10994a3c35eSSrinivas Pandruvada 	NULL
11094a3c35eSSrinivas Pandruvada };
11194a3c35eSSrinivas Pandruvada 
112f1b07a14SSumeet Pawnikar static const struct attribute_group imok_attribute_group = {
113f1b07a14SSumeet Pawnikar 	.attrs = imok_attr,
114f1b07a14SSumeet Pawnikar };
115f1b07a14SSumeet Pawnikar 
1160ba13c76SMatthew Garrett static const struct attribute_group data_attribute_group = {
1170ba13c76SMatthew Garrett 	.bin_attrs = data_attributes,
1183e8c4d31SAmit Kucheria };
1193e8c4d31SAmit Kucheria 
1203e8c4d31SAmit Kucheria static ssize_t available_uuids_show(struct device *dev,
1213e8c4d31SAmit Kucheria 				    struct device_attribute *attr,
1223e8c4d31SAmit Kucheria 				    char *buf)
1233e8c4d31SAmit Kucheria {
1243e8c4d31SAmit Kucheria 	struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
1253e8c4d31SAmit Kucheria 	int i;
1263e8c4d31SAmit Kucheria 	int length = 0;
1273e8c4d31SAmit Kucheria 
1288d485da0SMatthew Garrett 	if (!priv->uuid_bitmap)
1298d485da0SMatthew Garrett 		return sprintf(buf, "UNKNOWN\n");
1308d485da0SMatthew Garrett 
1313e8c4d31SAmit Kucheria 	for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) {
1323e8c4d31SAmit Kucheria 		if (priv->uuid_bitmap & (1 << i))
133f21431f2STakashi Iwai 			length += scnprintf(&buf[length],
1343e8c4d31SAmit Kucheria 					    PAGE_SIZE - length,
1353e8c4d31SAmit Kucheria 					    "%s\n",
1363e8c4d31SAmit Kucheria 					    int3400_thermal_uuids[i]);
1373e8c4d31SAmit Kucheria 	}
1383e8c4d31SAmit Kucheria 
1393e8c4d31SAmit Kucheria 	return length;
1403e8c4d31SAmit Kucheria }
1413e8c4d31SAmit Kucheria 
1423e8c4d31SAmit Kucheria static ssize_t current_uuid_show(struct device *dev,
1433e8c4d31SAmit Kucheria 				 struct device_attribute *devattr, char *buf)
1443e8c4d31SAmit Kucheria {
1453e8c4d31SAmit Kucheria 	struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
146c7ff2976SSrinivas Pandruvada 	int i, length = 0;
1473e8c4d31SAmit Kucheria 
148c7ff2976SSrinivas Pandruvada 	if (priv->current_uuid_index > 0)
1493e8c4d31SAmit Kucheria 		return sprintf(buf, "%s\n",
1503e8c4d31SAmit Kucheria 			       int3400_thermal_uuids[priv->current_uuid_index]);
151c7ff2976SSrinivas Pandruvada 
152c7ff2976SSrinivas Pandruvada 	for (i = 0; i <= INT3400_THERMAL_CRITICAL; i++) {
153c7ff2976SSrinivas Pandruvada 		if (priv->os_uuid_mask & BIT(i))
154c7ff2976SSrinivas Pandruvada 			length += scnprintf(&buf[length],
155c7ff2976SSrinivas Pandruvada 					    PAGE_SIZE - length,
156c7ff2976SSrinivas Pandruvada 					    "%s\n",
157c7ff2976SSrinivas Pandruvada 					    int3400_thermal_uuids[i]);
158c7ff2976SSrinivas Pandruvada 	}
159c7ff2976SSrinivas Pandruvada 
160c7ff2976SSrinivas Pandruvada 	if (length)
161c7ff2976SSrinivas Pandruvada 		return length;
162c7ff2976SSrinivas Pandruvada 
163c7ff2976SSrinivas Pandruvada 	return sprintf(buf, "INVALID\n");
164c7ff2976SSrinivas Pandruvada }
165c7ff2976SSrinivas Pandruvada 
166c7ff2976SSrinivas Pandruvada static int int3400_thermal_run_osc(acpi_handle handle, char *uuid_str, int *enable)
167c7ff2976SSrinivas Pandruvada {
168c7ff2976SSrinivas Pandruvada 	u32 ret, buf[2];
169c7ff2976SSrinivas Pandruvada 	acpi_status status;
170c7ff2976SSrinivas Pandruvada 	int result = 0;
171c7ff2976SSrinivas Pandruvada 	struct acpi_osc_context context = {
172c7ff2976SSrinivas Pandruvada 		.uuid_str = NULL,
173c7ff2976SSrinivas Pandruvada 		.rev = 1,
174c7ff2976SSrinivas Pandruvada 		.cap.length = 8,
175c7ff2976SSrinivas Pandruvada 	};
176c7ff2976SSrinivas Pandruvada 
177c7ff2976SSrinivas Pandruvada 	context.uuid_str = uuid_str;
178c7ff2976SSrinivas Pandruvada 
179c7ff2976SSrinivas Pandruvada 	buf[OSC_QUERY_DWORD] = 0;
180c7ff2976SSrinivas Pandruvada 	buf[OSC_SUPPORT_DWORD] = *enable;
181c7ff2976SSrinivas Pandruvada 
182c7ff2976SSrinivas Pandruvada 	context.cap.pointer = buf;
183c7ff2976SSrinivas Pandruvada 
184c7ff2976SSrinivas Pandruvada 	status = acpi_run_osc(handle, &context);
185c7ff2976SSrinivas Pandruvada 	if (ACPI_SUCCESS(status)) {
186c7ff2976SSrinivas Pandruvada 		ret = *((u32 *)(context.ret.pointer + 4));
187c7ff2976SSrinivas Pandruvada 		if (ret != *enable)
188c7ff2976SSrinivas Pandruvada 			result = -EPERM;
189c7ff2976SSrinivas Pandruvada 
190c7ff2976SSrinivas Pandruvada 		kfree(context.ret.pointer);
191bdff938dSDavidlohr Bueso 	} else
192bdff938dSDavidlohr Bueso 		result = -EPERM;
193c7ff2976SSrinivas Pandruvada 
194c7ff2976SSrinivas Pandruvada 	return result;
1953e8c4d31SAmit Kucheria }
1963e8c4d31SAmit Kucheria 
1973e8c4d31SAmit Kucheria static ssize_t current_uuid_store(struct device *dev,
1983e8c4d31SAmit Kucheria 				  struct device_attribute *attr,
1993e8c4d31SAmit Kucheria 				  const char *buf, size_t count)
2003e8c4d31SAmit Kucheria {
2013e8c4d31SAmit Kucheria 	struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
2023e8c4d31SAmit Kucheria 	int i;
2033e8c4d31SAmit Kucheria 
2043e8c4d31SAmit Kucheria 	for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) {
2058d485da0SMatthew Garrett 		if (!strncmp(buf, int3400_thermal_uuids[i],
2068d485da0SMatthew Garrett 			     sizeof(int3400_thermal_uuids[i]) - 1)) {
2078d485da0SMatthew Garrett 			/*
2088d485da0SMatthew Garrett 			 * If we have a list of supported UUIDs, make sure
2098d485da0SMatthew Garrett 			 * this one is supported.
2108d485da0SMatthew Garrett 			 */
211c7ff2976SSrinivas Pandruvada 			if (priv->uuid_bitmap & BIT(i)) {
2123e8c4d31SAmit Kucheria 				priv->current_uuid_index = i;
2133e8c4d31SAmit Kucheria 				return count;
2143e8c4d31SAmit Kucheria 			}
215c7ff2976SSrinivas Pandruvada 
216c7ff2976SSrinivas Pandruvada 			/*
217c7ff2976SSrinivas Pandruvada 			 * There is support of only 3 policies via the new
218c7ff2976SSrinivas Pandruvada 			 * _OSC to inform OS capability:
219c7ff2976SSrinivas Pandruvada 			 * INT3400_THERMAL_ACTIVE
220c7ff2976SSrinivas Pandruvada 			 * INT3400_THERMAL_PASSIVE_1
221c7ff2976SSrinivas Pandruvada 			 * INT3400_THERMAL_CRITICAL
222c7ff2976SSrinivas Pandruvada 			 */
223c7ff2976SSrinivas Pandruvada 
224c7ff2976SSrinivas Pandruvada 			if (i > INT3400_THERMAL_CRITICAL)
225c7ff2976SSrinivas Pandruvada 				return -EINVAL;
226c7ff2976SSrinivas Pandruvada 
227c7ff2976SSrinivas Pandruvada 			priv->os_uuid_mask |= BIT(i);
228c7ff2976SSrinivas Pandruvada 
229c7ff2976SSrinivas Pandruvada 			break;
230c7ff2976SSrinivas Pandruvada 		}
2313e8c4d31SAmit Kucheria 	}
2323e8c4d31SAmit Kucheria 
233c7ff2976SSrinivas Pandruvada 	if (priv->os_uuid_mask) {
234c7ff2976SSrinivas Pandruvada 		int cap, ret;
235c7ff2976SSrinivas Pandruvada 
236c7ff2976SSrinivas Pandruvada 		/*
237c7ff2976SSrinivas Pandruvada 		 * Capability bits:
238c7ff2976SSrinivas Pandruvada 		 * Bit 0: set to 1 to indicate DPTF is active
239c7ff2976SSrinivas Pandruvada 		 * Bi1 1: set to 1 to active cooling is supported by user space daemon
240c7ff2976SSrinivas Pandruvada 		 * Bit 2: set to 1 to passive cooling is supported by user space daemon
241c7ff2976SSrinivas Pandruvada 		 * Bit 3: set to 1 to critical trip is handled by user space daemon
242c7ff2976SSrinivas Pandruvada 		 */
243c7ff2976SSrinivas Pandruvada 		cap = ((priv->os_uuid_mask << 1) | 0x01);
244c7ff2976SSrinivas Pandruvada 		ret = int3400_thermal_run_osc(priv->adev->handle,
245c7ff2976SSrinivas Pandruvada 					      "b23ba85d-c8b7-3542-88de-8de2ffcfd698",
246c7ff2976SSrinivas Pandruvada 					      &cap);
247c7ff2976SSrinivas Pandruvada 		if (ret)
248c7ff2976SSrinivas Pandruvada 			return ret;
249c7ff2976SSrinivas Pandruvada 	}
250c7ff2976SSrinivas Pandruvada 
251c7ff2976SSrinivas Pandruvada 	return count;
2523e8c4d31SAmit Kucheria }
2533e8c4d31SAmit Kucheria 
2543e8c4d31SAmit Kucheria static DEVICE_ATTR_RW(current_uuid);
2553e8c4d31SAmit Kucheria static DEVICE_ATTR_RO(available_uuids);
2563e8c4d31SAmit Kucheria static struct attribute *uuid_attrs[] = {
2573e8c4d31SAmit Kucheria 	&dev_attr_available_uuids.attr,
2583e8c4d31SAmit Kucheria 	&dev_attr_current_uuid.attr,
2593e8c4d31SAmit Kucheria 	NULL
2603e8c4d31SAmit Kucheria };
2613e8c4d31SAmit Kucheria 
2623e8c4d31SAmit Kucheria static const struct attribute_group uuid_attribute_group = {
2633e8c4d31SAmit Kucheria 	.attrs = uuid_attrs,
2643e8c4d31SAmit Kucheria 	.name = "uuids"
2653e8c4d31SAmit Kucheria };
2663e8c4d31SAmit Kucheria 
2673e8c4d31SAmit Kucheria static int int3400_thermal_get_uuids(struct int3400_thermal_priv *priv)
2683e8c4d31SAmit Kucheria {
2693e8c4d31SAmit Kucheria 	struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL};
2703e8c4d31SAmit Kucheria 	union acpi_object *obja, *objb;
2713e8c4d31SAmit Kucheria 	int i, j;
2723e8c4d31SAmit Kucheria 	int result = 0;
2733e8c4d31SAmit Kucheria 	acpi_status status;
2743e8c4d31SAmit Kucheria 
2753e8c4d31SAmit Kucheria 	status = acpi_evaluate_object(priv->adev->handle, "IDSP", NULL, &buf);
2763e8c4d31SAmit Kucheria 	if (ACPI_FAILURE(status))
2773e8c4d31SAmit Kucheria 		return -ENODEV;
2783e8c4d31SAmit Kucheria 
2793e8c4d31SAmit Kucheria 	obja = (union acpi_object *)buf.pointer;
2803e8c4d31SAmit Kucheria 	if (obja->type != ACPI_TYPE_PACKAGE) {
2813e8c4d31SAmit Kucheria 		result = -EINVAL;
2823e8c4d31SAmit Kucheria 		goto end;
2833e8c4d31SAmit Kucheria 	}
2843e8c4d31SAmit Kucheria 
2853e8c4d31SAmit Kucheria 	for (i = 0; i < obja->package.count; i++) {
2863e8c4d31SAmit Kucheria 		objb = &obja->package.elements[i];
2873e8c4d31SAmit Kucheria 		if (objb->type != ACPI_TYPE_BUFFER) {
2883e8c4d31SAmit Kucheria 			result = -EINVAL;
2893e8c4d31SAmit Kucheria 			goto end;
2903e8c4d31SAmit Kucheria 		}
2913e8c4d31SAmit Kucheria 
2923e8c4d31SAmit Kucheria 		/* UUID must be 16 bytes */
2933e8c4d31SAmit Kucheria 		if (objb->buffer.length != 16) {
2943e8c4d31SAmit Kucheria 			result = -EINVAL;
2953e8c4d31SAmit Kucheria 			goto end;
2963e8c4d31SAmit Kucheria 		}
2973e8c4d31SAmit Kucheria 
2983e8c4d31SAmit Kucheria 		for (j = 0; j < INT3400_THERMAL_MAXIMUM_UUID; j++) {
2993e8c4d31SAmit Kucheria 			guid_t guid;
3003e8c4d31SAmit Kucheria 
3013e8c4d31SAmit Kucheria 			guid_parse(int3400_thermal_uuids[j], &guid);
3023e8c4d31SAmit Kucheria 			if (guid_equal((guid_t *)objb->buffer.pointer, &guid)) {
3033e8c4d31SAmit Kucheria 				priv->uuid_bitmap |= (1 << j);
3043e8c4d31SAmit Kucheria 				break;
3053e8c4d31SAmit Kucheria 			}
3063e8c4d31SAmit Kucheria 		}
3073e8c4d31SAmit Kucheria 	}
3083e8c4d31SAmit Kucheria 
3093e8c4d31SAmit Kucheria end:
3103e8c4d31SAmit Kucheria 	kfree(buf.pointer);
3113e8c4d31SAmit Kucheria 	return result;
3123e8c4d31SAmit Kucheria }
3133e8c4d31SAmit Kucheria 
314006f006fSMatthew Garrett static ssize_t odvp_show(struct kobject *kobj, struct kobj_attribute *attr,
315006f006fSMatthew Garrett 			 char *buf)
316006f006fSMatthew Garrett {
317006f006fSMatthew Garrett 	struct odvp_attr *odvp_attr;
318006f006fSMatthew Garrett 
319006f006fSMatthew Garrett 	odvp_attr = container_of(attr, struct odvp_attr, attr);
320006f006fSMatthew Garrett 
321006f006fSMatthew Garrett 	return sprintf(buf, "%d\n", odvp_attr->priv->odvp[odvp_attr->odvp]);
322006f006fSMatthew Garrett }
323006f006fSMatthew Garrett 
324006f006fSMatthew Garrett static void cleanup_odvp(struct int3400_thermal_priv *priv)
325006f006fSMatthew Garrett {
326006f006fSMatthew Garrett 	int i;
327006f006fSMatthew Garrett 
328006f006fSMatthew Garrett 	if (priv->odvp_attrs) {
329006f006fSMatthew Garrett 		for (i = 0; i < priv->odvp_count; i++) {
330006f006fSMatthew Garrett 			sysfs_remove_file(&priv->pdev->dev.kobj,
331006f006fSMatthew Garrett 					  &priv->odvp_attrs[i].attr.attr);
332006f006fSMatthew Garrett 			kfree(priv->odvp_attrs[i].attr.attr.name);
333006f006fSMatthew Garrett 		}
334006f006fSMatthew Garrett 		kfree(priv->odvp_attrs);
335006f006fSMatthew Garrett 	}
336006f006fSMatthew Garrett 	kfree(priv->odvp);
337006f006fSMatthew Garrett 	priv->odvp_count = 0;
338006f006fSMatthew Garrett }
339006f006fSMatthew Garrett 
340006f006fSMatthew Garrett static int evaluate_odvp(struct int3400_thermal_priv *priv)
341006f006fSMatthew Garrett {
342006f006fSMatthew Garrett 	struct acpi_buffer odvp = { ACPI_ALLOCATE_BUFFER, NULL };
343006f006fSMatthew Garrett 	union acpi_object *obj = NULL;
344006f006fSMatthew Garrett 	acpi_status status;
345006f006fSMatthew Garrett 	int i, ret;
346006f006fSMatthew Garrett 
347006f006fSMatthew Garrett 	status = acpi_evaluate_object(priv->adev->handle, "ODVP", NULL, &odvp);
348006f006fSMatthew Garrett 	if (ACPI_FAILURE(status)) {
349006f006fSMatthew Garrett 		ret = -EINVAL;
350006f006fSMatthew Garrett 		goto out_err;
351006f006fSMatthew Garrett 	}
352006f006fSMatthew Garrett 
353006f006fSMatthew Garrett 	obj = odvp.pointer;
354006f006fSMatthew Garrett 	if (obj->type != ACPI_TYPE_PACKAGE) {
355006f006fSMatthew Garrett 		ret = -EINVAL;
356006f006fSMatthew Garrett 		goto out_err;
357006f006fSMatthew Garrett 	}
358006f006fSMatthew Garrett 
359006f006fSMatthew Garrett 	if (priv->odvp == NULL) {
360006f006fSMatthew Garrett 		priv->odvp_count = obj->package.count;
361006f006fSMatthew Garrett 		priv->odvp = kmalloc_array(priv->odvp_count, sizeof(int),
362006f006fSMatthew Garrett 				     GFP_KERNEL);
363006f006fSMatthew Garrett 		if (!priv->odvp) {
364006f006fSMatthew Garrett 			ret = -ENOMEM;
365006f006fSMatthew Garrett 			goto out_err;
366006f006fSMatthew Garrett 		}
367006f006fSMatthew Garrett 	}
368006f006fSMatthew Garrett 
369006f006fSMatthew Garrett 	if (priv->odvp_attrs == NULL) {
370006f006fSMatthew Garrett 		priv->odvp_attrs = kcalloc(priv->odvp_count,
371006f006fSMatthew Garrett 					   sizeof(struct odvp_attr),
372006f006fSMatthew Garrett 					   GFP_KERNEL);
373006f006fSMatthew Garrett 		if (!priv->odvp_attrs) {
374006f006fSMatthew Garrett 			ret = -ENOMEM;
375006f006fSMatthew Garrett 			goto out_err;
376006f006fSMatthew Garrett 		}
377006f006fSMatthew Garrett 		for (i = 0; i < priv->odvp_count; i++) {
378006f006fSMatthew Garrett 			struct odvp_attr *odvp = &priv->odvp_attrs[i];
379006f006fSMatthew Garrett 
380006f006fSMatthew Garrett 			sysfs_attr_init(&odvp->attr.attr);
381006f006fSMatthew Garrett 			odvp->priv = priv;
382006f006fSMatthew Garrett 			odvp->odvp = i;
383006f006fSMatthew Garrett 			odvp->attr.attr.name = kasprintf(GFP_KERNEL,
384006f006fSMatthew Garrett 							 "odvp%d", i);
385006f006fSMatthew Garrett 
386006f006fSMatthew Garrett 			if (!odvp->attr.attr.name) {
387006f006fSMatthew Garrett 				ret = -ENOMEM;
388006f006fSMatthew Garrett 				goto out_err;
389006f006fSMatthew Garrett 			}
390006f006fSMatthew Garrett 			odvp->attr.attr.mode = 0444;
391006f006fSMatthew Garrett 			odvp->attr.show = odvp_show;
392006f006fSMatthew Garrett 			odvp->attr.store = NULL;
393006f006fSMatthew Garrett 			ret = sysfs_create_file(&priv->pdev->dev.kobj,
394006f006fSMatthew Garrett 						&odvp->attr.attr);
395006f006fSMatthew Garrett 			if (ret)
396006f006fSMatthew Garrett 				goto out_err;
397006f006fSMatthew Garrett 		}
398006f006fSMatthew Garrett 	}
399006f006fSMatthew Garrett 
400006f006fSMatthew Garrett 	for (i = 0; i < obj->package.count; i++) {
401006f006fSMatthew Garrett 		if (obj->package.elements[i].type == ACPI_TYPE_INTEGER)
402006f006fSMatthew Garrett 			priv->odvp[i] = obj->package.elements[i].integer.value;
403006f006fSMatthew Garrett 	}
404006f006fSMatthew Garrett 
405006f006fSMatthew Garrett 	kfree(obj);
406006f006fSMatthew Garrett 	return 0;
407006f006fSMatthew Garrett 
408006f006fSMatthew Garrett out_err:
409006f006fSMatthew Garrett 	cleanup_odvp(priv);
410006f006fSMatthew Garrett 	kfree(obj);
411006f006fSMatthew Garrett 	return ret;
412006f006fSMatthew Garrett }
413006f006fSMatthew Garrett 
4143e8c4d31SAmit Kucheria static void int3400_notify(acpi_handle handle,
4153e8c4d31SAmit Kucheria 			u32 event,
4163e8c4d31SAmit Kucheria 			void *data)
4173e8c4d31SAmit Kucheria {
4183e8c4d31SAmit Kucheria 	struct int3400_thermal_priv *priv = data;
4193e8c4d31SAmit Kucheria 	char *thermal_prop[5];
42039558030SSrinivas Pandruvada 	int therm_event;
4213e8c4d31SAmit Kucheria 
4223e8c4d31SAmit Kucheria 	if (!priv)
4233e8c4d31SAmit Kucheria 		return;
4243e8c4d31SAmit Kucheria 
4253e8c4d31SAmit Kucheria 	switch (event) {
4263e8c4d31SAmit Kucheria 	case INT3400_THERMAL_TABLE_CHANGED:
42739558030SSrinivas Pandruvada 		therm_event = THERMAL_TABLE_CHANGED;
4283e8c4d31SAmit Kucheria 		break;
42994a3c35eSSrinivas Pandruvada 	case INT3400_KEEP_ALIVE:
43094a3c35eSSrinivas Pandruvada 		therm_event = THERMAL_EVENT_KEEP_ALIVE;
43194a3c35eSSrinivas Pandruvada 		break;
432006f006fSMatthew Garrett 	case INT3400_ODVP_CHANGED:
433006f006fSMatthew Garrett 		evaluate_odvp(priv);
43439558030SSrinivas Pandruvada 		therm_event = THERMAL_DEVICE_POWER_CAPABILITY_CHANGED;
435006f006fSMatthew Garrett 		break;
4363e8c4d31SAmit Kucheria 	default:
4373e8c4d31SAmit Kucheria 		/* Ignore unknown notification codes sent to INT3400 device */
43839558030SSrinivas Pandruvada 		return;
4393e8c4d31SAmit Kucheria 	}
44039558030SSrinivas Pandruvada 
44139558030SSrinivas Pandruvada 	thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", priv->thermal->type);
44239558030SSrinivas Pandruvada 	thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d", priv->thermal->temperature);
44339558030SSrinivas Pandruvada 	thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP=");
44439558030SSrinivas Pandruvada 	thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d", therm_event);
44539558030SSrinivas Pandruvada 	thermal_prop[4] = NULL;
44639558030SSrinivas Pandruvada 	kobject_uevent_env(&priv->thermal->device.kobj, KOBJ_CHANGE, thermal_prop);
4473abea10eSChuansheng Liu 	kfree(thermal_prop[0]);
4483abea10eSChuansheng Liu 	kfree(thermal_prop[1]);
4493abea10eSChuansheng Liu 	kfree(thermal_prop[2]);
4503abea10eSChuansheng Liu 	kfree(thermal_prop[3]);
4513e8c4d31SAmit Kucheria }
4523e8c4d31SAmit Kucheria 
4533e8c4d31SAmit Kucheria static int int3400_thermal_get_temp(struct thermal_zone_device *thermal,
4543e8c4d31SAmit Kucheria 			int *temp)
4553e8c4d31SAmit Kucheria {
4563e8c4d31SAmit Kucheria 	*temp = 20 * 1000; /* faked temp sensor with 20C */
4573e8c4d31SAmit Kucheria 	return 0;
4583e8c4d31SAmit Kucheria }
4593e8c4d31SAmit Kucheria 
460f5e50bf4SAndrzej Pietrasiewicz static int int3400_thermal_change_mode(struct thermal_zone_device *thermal,
4613e8c4d31SAmit Kucheria 				       enum thermal_device_mode mode)
4623e8c4d31SAmit Kucheria {
4633e8c4d31SAmit Kucheria 	struct int3400_thermal_priv *priv = thermal->devdata;
4643e8c4d31SAmit Kucheria 	int result = 0;
4653e8c4d31SAmit Kucheria 
4663e8c4d31SAmit Kucheria 	if (!priv)
4673e8c4d31SAmit Kucheria 		return -EINVAL;
4683e8c4d31SAmit Kucheria 
469c7ff2976SSrinivas Pandruvada 	if (mode != thermal->mode) {
470c7ff2976SSrinivas Pandruvada 		int enabled;
471c7ff2976SSrinivas Pandruvada 
472c7ff2976SSrinivas Pandruvada 		if (priv->current_uuid_index < 0 ||
473c7ff2976SSrinivas Pandruvada 		    priv->current_uuid_index >= INT3400_THERMAL_MAXIMUM_UUID)
474c7ff2976SSrinivas Pandruvada 			return -EINVAL;
475c7ff2976SSrinivas Pandruvada 
476c7ff2976SSrinivas Pandruvada 		enabled = (mode == THERMAL_DEVICE_ENABLED);
4773e8c4d31SAmit Kucheria 		result = int3400_thermal_run_osc(priv->adev->handle,
478c7ff2976SSrinivas Pandruvada 						 int3400_thermal_uuids[priv->current_uuid_index],
479c7ff2976SSrinivas Pandruvada 						 &enabled);
480c7ff2976SSrinivas Pandruvada 	}
4817f4957beSAndrzej Pietrasiewicz 
482006f006fSMatthew Garrett 
483006f006fSMatthew Garrett 	evaluate_odvp(priv);
484006f006fSMatthew Garrett 
4853e8c4d31SAmit Kucheria 	return result;
4863e8c4d31SAmit Kucheria }
4873e8c4d31SAmit Kucheria 
4883e8c4d31SAmit Kucheria static struct thermal_zone_device_ops int3400_thermal_ops = {
4893e8c4d31SAmit Kucheria 	.get_temp = int3400_thermal_get_temp,
490f5e50bf4SAndrzej Pietrasiewicz 	.change_mode = int3400_thermal_change_mode,
4913e8c4d31SAmit Kucheria };
4923e8c4d31SAmit Kucheria 
4933e8c4d31SAmit Kucheria static struct thermal_zone_params int3400_thermal_params = {
4943e8c4d31SAmit Kucheria 	.governor_name = "user_space",
4953e8c4d31SAmit Kucheria 	.no_hwmon = true,
4963e8c4d31SAmit Kucheria };
4973e8c4d31SAmit Kucheria 
4980ba13c76SMatthew Garrett static void int3400_setup_gddv(struct int3400_thermal_priv *priv)
4990ba13c76SMatthew Garrett {
5000ba13c76SMatthew Garrett 	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
5010ba13c76SMatthew Garrett 	union acpi_object *obj;
5020ba13c76SMatthew Garrett 	acpi_status status;
5030ba13c76SMatthew Garrett 
5040ba13c76SMatthew Garrett 	status = acpi_evaluate_object(priv->adev->handle, "GDDV", NULL,
5050ba13c76SMatthew Garrett 				      &buffer);
5060ba13c76SMatthew Garrett 	if (ACPI_FAILURE(status) || !buffer.length)
5070ba13c76SMatthew Garrett 		return;
5080ba13c76SMatthew Garrett 
5090ba13c76SMatthew Garrett 	obj = buffer.pointer;
5100ba13c76SMatthew Garrett 	if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 1
511*9e5d3d6bSDavidlohr Bueso 	    || obj->package.elements[0].type != ACPI_TYPE_BUFFER)
512*9e5d3d6bSDavidlohr Bueso 		goto out_free;
5130ba13c76SMatthew Garrett 
5140ba13c76SMatthew Garrett 	priv->data_vault = kmemdup(obj->package.elements[0].buffer.pointer,
5150ba13c76SMatthew Garrett 				   obj->package.elements[0].buffer.length,
5160ba13c76SMatthew Garrett 				   GFP_KERNEL);
517*9e5d3d6bSDavidlohr Bueso 	if (!priv->data_vault)
518*9e5d3d6bSDavidlohr Bueso 		goto out_free;
51938b16d6cSJiasheng Jiang 
5200ba13c76SMatthew Garrett 	bin_attr_data_vault.private = priv->data_vault;
5210ba13c76SMatthew Garrett 	bin_attr_data_vault.size = obj->package.elements[0].buffer.length;
522*9e5d3d6bSDavidlohr Bueso out_free:
5230ba13c76SMatthew Garrett 	kfree(buffer.pointer);
5240ba13c76SMatthew Garrett }
5250ba13c76SMatthew Garrett 
5263e8c4d31SAmit Kucheria static int int3400_thermal_probe(struct platform_device *pdev)
5273e8c4d31SAmit Kucheria {
5283e8c4d31SAmit Kucheria 	struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
5293e8c4d31SAmit Kucheria 	struct int3400_thermal_priv *priv;
5303e8c4d31SAmit Kucheria 	int result;
5313e8c4d31SAmit Kucheria 
5323e8c4d31SAmit Kucheria 	if (!adev)
5333e8c4d31SAmit Kucheria 		return -ENODEV;
5343e8c4d31SAmit Kucheria 
5353e8c4d31SAmit Kucheria 	priv = kzalloc(sizeof(struct int3400_thermal_priv), GFP_KERNEL);
5363e8c4d31SAmit Kucheria 	if (!priv)
5373e8c4d31SAmit Kucheria 		return -ENOMEM;
5383e8c4d31SAmit Kucheria 
539006f006fSMatthew Garrett 	priv->pdev = pdev;
5403e8c4d31SAmit Kucheria 	priv->adev = adev;
5413e8c4d31SAmit Kucheria 
5423e8c4d31SAmit Kucheria 	result = int3400_thermal_get_uuids(priv);
5438d485da0SMatthew Garrett 
5448d485da0SMatthew Garrett 	/* Missing IDSP isn't fatal */
5458d485da0SMatthew Garrett 	if (result && result != -ENODEV)
5463e8c4d31SAmit Kucheria 		goto free_priv;
5473e8c4d31SAmit Kucheria 
5488d485da0SMatthew Garrett 	priv->current_uuid_index = -1;
5498d485da0SMatthew Garrett 
5503e8c4d31SAmit Kucheria 	result = acpi_parse_art(priv->adev->handle, &priv->art_count,
5513e8c4d31SAmit Kucheria 				&priv->arts, true);
5523e8c4d31SAmit Kucheria 	if (result)
5533e8c4d31SAmit Kucheria 		dev_dbg(&pdev->dev, "_ART table parsing error\n");
5543e8c4d31SAmit Kucheria 
5553e8c4d31SAmit Kucheria 	result = acpi_parse_trt(priv->adev->handle, &priv->trt_count,
5563e8c4d31SAmit Kucheria 				&priv->trts, true);
5573e8c4d31SAmit Kucheria 	if (result)
5583e8c4d31SAmit Kucheria 		dev_dbg(&pdev->dev, "_TRT table parsing error\n");
5593e8c4d31SAmit Kucheria 
5603e8c4d31SAmit Kucheria 	platform_set_drvdata(pdev, priv);
5613e8c4d31SAmit Kucheria 
5620ba13c76SMatthew Garrett 	int3400_setup_gddv(priv);
5630ba13c76SMatthew Garrett 
564006f006fSMatthew Garrett 	evaluate_odvp(priv);
565006f006fSMatthew Garrett 
5663e8c4d31SAmit Kucheria 	priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0,
5673e8c4d31SAmit Kucheria 						priv, &int3400_thermal_ops,
5683e8c4d31SAmit Kucheria 						&int3400_thermal_params, 0, 0);
5693e8c4d31SAmit Kucheria 	if (IS_ERR(priv->thermal)) {
5703e8c4d31SAmit Kucheria 		result = PTR_ERR(priv->thermal);
5713e8c4d31SAmit Kucheria 		goto free_art_trt;
5723e8c4d31SAmit Kucheria 	}
5733e8c4d31SAmit Kucheria 
5743e8c4d31SAmit Kucheria 	priv->rel_misc_dev_res = acpi_thermal_rel_misc_device_add(
5753e8c4d31SAmit Kucheria 							priv->adev->handle);
5763e8c4d31SAmit Kucheria 
5773e8c4d31SAmit Kucheria 	result = sysfs_create_group(&pdev->dev.kobj, &uuid_attribute_group);
5783e8c4d31SAmit Kucheria 	if (result)
5793e8c4d31SAmit Kucheria 		goto free_rel_misc;
5803e8c4d31SAmit Kucheria 
581f1b07a14SSumeet Pawnikar 	if (acpi_has_method(priv->adev->handle, "IMOK")) {
582f1b07a14SSumeet Pawnikar 		result = sysfs_create_group(&pdev->dev.kobj, &imok_attribute_group);
583f1b07a14SSumeet Pawnikar 		if (result)
584f1b07a14SSumeet Pawnikar 			goto free_imok;
585f1b07a14SSumeet Pawnikar 	}
586f1b07a14SSumeet Pawnikar 
5870ba13c76SMatthew Garrett 	if (priv->data_vault) {
5880ba13c76SMatthew Garrett 		result = sysfs_create_group(&pdev->dev.kobj,
5890ba13c76SMatthew Garrett 					    &data_attribute_group);
5900ba13c76SMatthew Garrett 		if (result)
5910ba13c76SMatthew Garrett 			goto free_uuid;
5920ba13c76SMatthew Garrett 	}
5930ba13c76SMatthew Garrett 
5943e8c4d31SAmit Kucheria 	result = acpi_install_notify_handler(
5953e8c4d31SAmit Kucheria 			priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify,
5963e8c4d31SAmit Kucheria 			(void *)priv);
5973e8c4d31SAmit Kucheria 	if (result)
5983e8c4d31SAmit Kucheria 		goto free_sysfs;
5993e8c4d31SAmit Kucheria 
6003e8c4d31SAmit Kucheria 	return 0;
6013e8c4d31SAmit Kucheria 
6023e8c4d31SAmit Kucheria free_sysfs:
603006f006fSMatthew Garrett 	cleanup_odvp(priv);
604006f006fSMatthew Garrett 	if (priv->data_vault) {
6050ba13c76SMatthew Garrett 		sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group);
606006f006fSMatthew Garrett 		kfree(priv->data_vault);
607006f006fSMatthew Garrett 	}
6080ba13c76SMatthew Garrett free_uuid:
6093e8c4d31SAmit Kucheria 	sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
610f1b07a14SSumeet Pawnikar free_imok:
611f1b07a14SSumeet Pawnikar 	sysfs_remove_group(&pdev->dev.kobj, &imok_attribute_group);
6123e8c4d31SAmit Kucheria free_rel_misc:
6133e8c4d31SAmit Kucheria 	if (!priv->rel_misc_dev_res)
6143e8c4d31SAmit Kucheria 		acpi_thermal_rel_misc_device_remove(priv->adev->handle);
6153e8c4d31SAmit Kucheria 	thermal_zone_device_unregister(priv->thermal);
6163e8c4d31SAmit Kucheria free_art_trt:
6173e8c4d31SAmit Kucheria 	kfree(priv->trts);
6183e8c4d31SAmit Kucheria 	kfree(priv->arts);
6193e8c4d31SAmit Kucheria free_priv:
6203e8c4d31SAmit Kucheria 	kfree(priv);
6213e8c4d31SAmit Kucheria 	return result;
6223e8c4d31SAmit Kucheria }
6233e8c4d31SAmit Kucheria 
6243e8c4d31SAmit Kucheria static int int3400_thermal_remove(struct platform_device *pdev)
6253e8c4d31SAmit Kucheria {
6263e8c4d31SAmit Kucheria 	struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);
6273e8c4d31SAmit Kucheria 
6283e8c4d31SAmit Kucheria 	acpi_remove_notify_handler(
6293e8c4d31SAmit Kucheria 			priv->adev->handle, ACPI_DEVICE_NOTIFY,
6303e8c4d31SAmit Kucheria 			int3400_notify);
6313e8c4d31SAmit Kucheria 
632006f006fSMatthew Garrett 	cleanup_odvp(priv);
633006f006fSMatthew Garrett 
6343e8c4d31SAmit Kucheria 	if (!priv->rel_misc_dev_res)
6353e8c4d31SAmit Kucheria 		acpi_thermal_rel_misc_device_remove(priv->adev->handle);
6363e8c4d31SAmit Kucheria 
6370ba13c76SMatthew Garrett 	if (priv->data_vault)
6380ba13c76SMatthew Garrett 		sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group);
6393e8c4d31SAmit Kucheria 	sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
640f1b07a14SSumeet Pawnikar 	sysfs_remove_group(&pdev->dev.kobj, &imok_attribute_group);
6413e8c4d31SAmit Kucheria 	thermal_zone_device_unregister(priv->thermal);
6420ba13c76SMatthew Garrett 	kfree(priv->data_vault);
6433e8c4d31SAmit Kucheria 	kfree(priv->trts);
6443e8c4d31SAmit Kucheria 	kfree(priv->arts);
6453e8c4d31SAmit Kucheria 	kfree(priv);
6463e8c4d31SAmit Kucheria 	return 0;
6473e8c4d31SAmit Kucheria }
6483e8c4d31SAmit Kucheria 
6493e8c4d31SAmit Kucheria static const struct acpi_device_id int3400_thermal_match[] = {
6503e8c4d31SAmit Kucheria 	{"INT3400", 0},
65126d8bec1SGayatri Kammela 	{"INTC1040", 0},
65267698880SSrinivas Pandruvada 	{"INTC1041", 0},
653a95be874SSrinivas Pandruvada 	{"INTC10A0", 0},
6543e8c4d31SAmit Kucheria 	{}
6553e8c4d31SAmit Kucheria };
6563e8c4d31SAmit Kucheria 
6573e8c4d31SAmit Kucheria MODULE_DEVICE_TABLE(acpi, int3400_thermal_match);
6583e8c4d31SAmit Kucheria 
6593e8c4d31SAmit Kucheria static struct platform_driver int3400_thermal_driver = {
6603e8c4d31SAmit Kucheria 	.probe = int3400_thermal_probe,
6613e8c4d31SAmit Kucheria 	.remove = int3400_thermal_remove,
6623e8c4d31SAmit Kucheria 	.driver = {
6633e8c4d31SAmit Kucheria 		   .name = "int3400 thermal",
6643e8c4d31SAmit Kucheria 		   .acpi_match_table = ACPI_PTR(int3400_thermal_match),
6653e8c4d31SAmit Kucheria 		   },
6663e8c4d31SAmit Kucheria };
6673e8c4d31SAmit Kucheria 
6683e8c4d31SAmit Kucheria module_platform_driver(int3400_thermal_driver);
6693e8c4d31SAmit Kucheria 
6703e8c4d31SAmit Kucheria MODULE_DESCRIPTION("INT3400 Thermal driver");
6713e8c4d31SAmit Kucheria MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
6723e8c4d31SAmit Kucheria MODULE_LICENSE("GPL");
673