1/*
2 * INT3400 thermal driver
3 *
4 * Copyright (C) 2014, Intel Corporation
5 * Authors: Zhang Rui <rui.zhang@intel.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 *
11 */
12
13#include <linux/module.h>
14#include <linux/platform_device.h>
15#include <linux/acpi.h>
16#include <linux/thermal.h>
17#include "acpi_thermal_rel.h"
18
19enum int3400_thermal_uuid {
20	INT3400_THERMAL_PASSIVE_1,
21	INT3400_THERMAL_ACTIVE,
22	INT3400_THERMAL_CRITICAL,
23	INT3400_THERMAL_MAXIMUM_UUID,
24};
25
26static u8 *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = {
27	"42A441D6-AE6A-462b-A84B-4A8CE79027D3",
28	"3A95C389-E4B8-4629-A526-C52C88626BAE",
29	"97C68AE7-15FA-499c-B8C9-5DA81D606E0A",
30};
31
32struct int3400_thermal_priv {
33	struct acpi_device *adev;
34	struct thermal_zone_device *thermal;
35	int mode;
36	int art_count;
37	struct art *arts;
38	int trt_count;
39	struct trt *trts;
40	u8 uuid_bitmap;
41	int rel_misc_dev_res;
42	int current_uuid_index;
43};
44
45static ssize_t available_uuids_show(struct device *dev,
46				    struct device_attribute *attr,
47				    char *buf)
48{
49	struct platform_device *pdev = to_platform_device(dev);
50	struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);
51	int i;
52	int length = 0;
53
54	for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) {
55		if (priv->uuid_bitmap & (1 << i))
56			if (PAGE_SIZE - length > 0)
57				length += snprintf(&buf[length],
58						   PAGE_SIZE - length,
59						   "%s\n",
60						   int3400_thermal_uuids[i]);
61	}
62
63	return length;
64}
65
66static ssize_t current_uuid_show(struct device *dev,
67				 struct device_attribute *devattr, char *buf)
68{
69	struct platform_device *pdev = to_platform_device(dev);
70	struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);
71
72	if (priv->uuid_bitmap & (1 << priv->current_uuid_index))
73		return sprintf(buf, "%s\n",
74			       int3400_thermal_uuids[priv->current_uuid_index]);
75	else
76		return sprintf(buf, "INVALID\n");
77}
78
79static ssize_t current_uuid_store(struct device *dev,
80				  struct device_attribute *attr,
81				  const char *buf, size_t count)
82{
83	struct platform_device *pdev = to_platform_device(dev);
84	struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);
85	int i;
86
87	for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) {
88		if ((priv->uuid_bitmap & (1 << i)) &&
89		    !(strncmp(buf, int3400_thermal_uuids[i],
90			      sizeof(int3400_thermal_uuids[i]) - 1))) {
91			priv->current_uuid_index = i;
92			return count;
93		}
94	}
95
96	return -EINVAL;
97}
98
99static DEVICE_ATTR(current_uuid, 0644, current_uuid_show, current_uuid_store);
100static DEVICE_ATTR_RO(available_uuids);
101static struct attribute *uuid_attrs[] = {
102	&dev_attr_available_uuids.attr,
103	&dev_attr_current_uuid.attr,
104	NULL
105};
106
107static struct attribute_group uuid_attribute_group = {
108	.attrs = uuid_attrs,
109	.name = "uuids"
110};
111
112static int int3400_thermal_get_uuids(struct int3400_thermal_priv *priv)
113{
114	struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL};
115	union acpi_object *obja, *objb;
116	int i, j;
117	int result = 0;
118	acpi_status status;
119
120	status = acpi_evaluate_object(priv->adev->handle, "IDSP", NULL, &buf);
121	if (ACPI_FAILURE(status))
122		return -ENODEV;
123
124	obja = (union acpi_object *)buf.pointer;
125	if (obja->type != ACPI_TYPE_PACKAGE) {
126		result = -EINVAL;
127		goto end;
128	}
129
130	for (i = 0; i < obja->package.count; i++) {
131		objb = &obja->package.elements[i];
132		if (objb->type != ACPI_TYPE_BUFFER) {
133			result = -EINVAL;
134			goto end;
135		}
136
137		/* UUID must be 16 bytes */
138		if (objb->buffer.length != 16) {
139			result = -EINVAL;
140			goto end;
141		}
142
143		for (j = 0; j < INT3400_THERMAL_MAXIMUM_UUID; j++) {
144			u8 uuid[16];
145
146			acpi_str_to_uuid(int3400_thermal_uuids[j], uuid);
147			if (!strncmp(uuid, objb->buffer.pointer, 16)) {
148				priv->uuid_bitmap |= (1 << j);
149				break;
150			}
151		}
152	}
153
154end:
155	kfree(buf.pointer);
156	return result;
157}
158
159static int int3400_thermal_run_osc(acpi_handle handle,
160				enum int3400_thermal_uuid uuid, bool enable)
161{
162	u32 ret, buf[2];
163	acpi_status status;
164	int result = 0;
165	struct acpi_osc_context context = {
166		.uuid_str = int3400_thermal_uuids[uuid],
167		.rev = 1,
168		.cap.length = 8,
169	};
170
171	buf[OSC_QUERY_DWORD] = 0;
172	buf[OSC_SUPPORT_DWORD] = enable;
173
174	context.cap.pointer = buf;
175
176	status = acpi_run_osc(handle, &context);
177	if (ACPI_SUCCESS(status)) {
178		ret = *((u32 *)(context.ret.pointer + 4));
179		if (ret != enable)
180			result = -EPERM;
181	} else
182		result = -EPERM;
183
184	kfree(context.ret.pointer);
185	return result;
186}
187
188static int int3400_thermal_get_temp(struct thermal_zone_device *thermal,
189			unsigned long *temp)
190{
191	*temp = 20 * 1000; /* faked temp sensor with 20C */
192	return 0;
193}
194
195static int int3400_thermal_get_mode(struct thermal_zone_device *thermal,
196				enum thermal_device_mode *mode)
197{
198	struct int3400_thermal_priv *priv = thermal->devdata;
199
200	if (!priv)
201		return -EINVAL;
202
203	*mode = priv->mode;
204
205	return 0;
206}
207
208static int int3400_thermal_set_mode(struct thermal_zone_device *thermal,
209				enum thermal_device_mode mode)
210{
211	struct int3400_thermal_priv *priv = thermal->devdata;
212	bool enable;
213	int result = 0;
214
215	if (!priv)
216		return -EINVAL;
217
218	if (mode == THERMAL_DEVICE_ENABLED)
219		enable = true;
220	else if (mode == THERMAL_DEVICE_DISABLED)
221		enable = false;
222	else
223		return -EINVAL;
224
225	if (enable != priv->mode) {
226		priv->mode = enable;
227		result = int3400_thermal_run_osc(priv->adev->handle,
228						 priv->current_uuid_index,
229						 enable);
230	}
231	return result;
232}
233
234static struct thermal_zone_device_ops int3400_thermal_ops = {
235	.get_temp = int3400_thermal_get_temp,
236};
237
238static struct thermal_zone_params int3400_thermal_params = {
239	.governor_name = "user_space",
240	.no_hwmon = true,
241};
242
243static int int3400_thermal_probe(struct platform_device *pdev)
244{
245	struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
246	struct int3400_thermal_priv *priv;
247	int result;
248
249	if (!adev)
250		return -ENODEV;
251
252	priv = kzalloc(sizeof(struct int3400_thermal_priv), GFP_KERNEL);
253	if (!priv)
254		return -ENOMEM;
255
256	priv->adev = adev;
257
258	result = int3400_thermal_get_uuids(priv);
259	if (result)
260		goto free_priv;
261
262	result = acpi_parse_art(priv->adev->handle, &priv->art_count,
263				&priv->arts, true);
264	if (result)
265		dev_dbg(&pdev->dev, "_ART table parsing error\n");
266
267	result = acpi_parse_trt(priv->adev->handle, &priv->trt_count,
268				&priv->trts, true);
269	if (result)
270		dev_dbg(&pdev->dev, "_TRT table parsing error\n");
271
272	platform_set_drvdata(pdev, priv);
273
274	if (priv->uuid_bitmap & 1 << INT3400_THERMAL_PASSIVE_1) {
275		int3400_thermal_ops.get_mode = int3400_thermal_get_mode;
276		int3400_thermal_ops.set_mode = int3400_thermal_set_mode;
277	}
278	priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0,
279						priv, &int3400_thermal_ops,
280						&int3400_thermal_params, 0, 0);
281	if (IS_ERR(priv->thermal)) {
282		result = PTR_ERR(priv->thermal);
283		goto free_art_trt;
284	}
285
286	priv->rel_misc_dev_res = acpi_thermal_rel_misc_device_add(
287							priv->adev->handle);
288
289	result = sysfs_create_group(&pdev->dev.kobj, &uuid_attribute_group);
290	if (result)
291		goto free_zone;
292
293	return 0;
294
295free_zone:
296	thermal_zone_device_unregister(priv->thermal);
297free_art_trt:
298	kfree(priv->trts);
299	kfree(priv->arts);
300free_priv:
301	kfree(priv);
302	return result;
303}
304
305static int int3400_thermal_remove(struct platform_device *pdev)
306{
307	struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);
308
309	if (!priv->rel_misc_dev_res)
310		acpi_thermal_rel_misc_device_remove(priv->adev->handle);
311
312	sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
313	thermal_zone_device_unregister(priv->thermal);
314	kfree(priv->trts);
315	kfree(priv->arts);
316	kfree(priv);
317	return 0;
318}
319
320static const struct acpi_device_id int3400_thermal_match[] = {
321	{"INT3400", 0},
322	{}
323};
324
325MODULE_DEVICE_TABLE(acpi, int3400_thermal_match);
326
327static struct platform_driver int3400_thermal_driver = {
328	.probe = int3400_thermal_probe,
329	.remove = int3400_thermal_remove,
330	.driver = {
331		   .name = "int3400 thermal",
332		   .acpi_match_table = ACPI_PTR(int3400_thermal_match),
333		   },
334};
335
336module_platform_driver(int3400_thermal_driver);
337
338MODULE_DESCRIPTION("INT3400 Thermal driver");
339MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
340MODULE_LICENSE("GPL");
341