1/*
2 * ACPI INT3403 thermal driver
3 * Copyright (c) 2013, Intel Corporation.
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms and conditions of the GNU General Public License,
7 * version 2, as published by the Free Software Foundation.
8 *
9 * This program is distributed in the hope it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
12 * more details.
13 */
14
15#include <linux/kernel.h>
16#include <linux/module.h>
17#include <linux/init.h>
18#include <linux/types.h>
19#include <linux/acpi.h>
20#include <linux/thermal.h>
21#include <linux/platform_device.h>
22#include "int340x_thermal_zone.h"
23
24#define INT3403_TYPE_SENSOR		0x03
25#define INT3403_TYPE_CHARGER		0x0B
26#define INT3403_TYPE_BATTERY		0x0C
27#define INT3403_PERF_CHANGED_EVENT	0x80
28#define INT3403_THERMAL_EVENT		0x90
29
30/* Preserved structure for future expandbility */
31struct int3403_sensor {
32	struct int34x_thermal_zone *int340x_zone;
33};
34
35struct int3403_performance_state {
36	u64 performance;
37	u64 power;
38	u64 latency;
39	u64 linear;
40	u64 control;
41	u64 raw_performace;
42	char *raw_unit;
43	int reserved;
44};
45
46struct int3403_cdev {
47	struct thermal_cooling_device *cdev;
48	unsigned long max_state;
49};
50
51struct int3403_priv {
52	struct platform_device *pdev;
53	struct acpi_device *adev;
54	unsigned long long type;
55	void *priv;
56};
57
58static void int3403_notify(acpi_handle handle,
59		u32 event, void *data)
60{
61	struct int3403_priv *priv = data;
62	struct int3403_sensor *obj;
63
64	if (!priv)
65		return;
66
67	obj = priv->priv;
68	if (priv->type != INT3403_TYPE_SENSOR || !obj)
69		return;
70
71	switch (event) {
72	case INT3403_PERF_CHANGED_EVENT:
73		break;
74	case INT3403_THERMAL_EVENT:
75		int340x_thermal_zone_device_update(obj->int340x_zone);
76		break;
77	default:
78		dev_err(&priv->pdev->dev, "Unsupported event [0x%x]\n", event);
79		break;
80	}
81}
82
83static int int3403_sensor_add(struct int3403_priv *priv)
84{
85	int result = 0;
86	struct int3403_sensor *obj;
87
88	obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL);
89	if (!obj)
90		return -ENOMEM;
91
92	priv->priv = obj;
93
94	obj->int340x_zone = int340x_thermal_zone_add(priv->adev, NULL);
95	if (IS_ERR(obj->int340x_zone))
96		return PTR_ERR(obj->int340x_zone);
97
98	result = acpi_install_notify_handler(priv->adev->handle,
99			ACPI_DEVICE_NOTIFY, int3403_notify,
100			(void *)priv);
101	if (result)
102		goto err_free_obj;
103
104	return 0;
105
106 err_free_obj:
107	int340x_thermal_zone_remove(obj->int340x_zone);
108	return result;
109}
110
111static int int3403_sensor_remove(struct int3403_priv *priv)
112{
113	struct int3403_sensor *obj = priv->priv;
114
115	acpi_remove_notify_handler(priv->adev->handle,
116				   ACPI_DEVICE_NOTIFY, int3403_notify);
117	int340x_thermal_zone_remove(obj->int340x_zone);
118
119	return 0;
120}
121
122/* INT3403 Cooling devices */
123static int int3403_get_max_state(struct thermal_cooling_device *cdev,
124				 unsigned long *state)
125{
126	struct int3403_priv *priv = cdev->devdata;
127	struct int3403_cdev *obj = priv->priv;
128
129	*state = obj->max_state;
130	return 0;
131}
132
133static int int3403_get_cur_state(struct thermal_cooling_device *cdev,
134				 unsigned long *state)
135{
136	struct int3403_priv *priv = cdev->devdata;
137	unsigned long long level;
138	acpi_status status;
139
140	status = acpi_evaluate_integer(priv->adev->handle, "PPPC", NULL, &level);
141	if (ACPI_SUCCESS(status)) {
142		*state = level;
143		return 0;
144	} else
145		return -EINVAL;
146}
147
148static int
149int3403_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
150{
151	struct int3403_priv *priv = cdev->devdata;
152	acpi_status status;
153
154	status = acpi_execute_simple_method(priv->adev->handle, "SPPC", state);
155	if (ACPI_SUCCESS(status))
156		return 0;
157	else
158		return -EINVAL;
159}
160
161static const struct thermal_cooling_device_ops int3403_cooling_ops = {
162	.get_max_state = int3403_get_max_state,
163	.get_cur_state = int3403_get_cur_state,
164	.set_cur_state = int3403_set_cur_state,
165};
166
167static int int3403_cdev_add(struct int3403_priv *priv)
168{
169	int result = 0;
170	acpi_status status;
171	struct int3403_cdev *obj;
172	struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
173	union acpi_object *p;
174
175	obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL);
176	if (!obj)
177		return -ENOMEM;
178
179	status = acpi_evaluate_object(priv->adev->handle, "PPSS", NULL, &buf);
180	if (ACPI_FAILURE(status))
181		return -ENODEV;
182
183	p = buf.pointer;
184	if (!p || (p->type != ACPI_TYPE_PACKAGE)) {
185		printk(KERN_WARNING "Invalid PPSS data\n");
186		kfree(buf.pointer);
187		return -EFAULT;
188	}
189
190	obj->max_state = p->package.count - 1;
191	obj->cdev =
192		thermal_cooling_device_register(acpi_device_bid(priv->adev),
193				priv, &int3403_cooling_ops);
194	if (IS_ERR(obj->cdev))
195		result = PTR_ERR(obj->cdev);
196
197	priv->priv = obj;
198
199	kfree(buf.pointer);
200	/* TODO: add ACPI notification support */
201
202	return result;
203}
204
205static int int3403_cdev_remove(struct int3403_priv *priv)
206{
207	struct int3403_cdev *obj = priv->priv;
208
209	thermal_cooling_device_unregister(obj->cdev);
210	return 0;
211}
212
213static int int3403_add(struct platform_device *pdev)
214{
215	struct int3403_priv *priv;
216	int result = 0;
217	acpi_status status;
218
219	priv = devm_kzalloc(&pdev->dev, sizeof(struct int3403_priv),
220			    GFP_KERNEL);
221	if (!priv)
222		return -ENOMEM;
223
224	priv->pdev = pdev;
225	priv->adev = ACPI_COMPANION(&(pdev->dev));
226	if (!priv->adev) {
227		result = -EINVAL;
228		goto err;
229	}
230
231	status = acpi_evaluate_integer(priv->adev->handle, "PTYP",
232				       NULL, &priv->type);
233	if (ACPI_FAILURE(status)) {
234		result = -EINVAL;
235		goto err;
236	}
237
238	platform_set_drvdata(pdev, priv);
239	switch (priv->type) {
240	case INT3403_TYPE_SENSOR:
241		result = int3403_sensor_add(priv);
242		break;
243	case INT3403_TYPE_CHARGER:
244	case INT3403_TYPE_BATTERY:
245		result = int3403_cdev_add(priv);
246		break;
247	default:
248		result = -EINVAL;
249	}
250
251	if (result)
252		goto err;
253	return result;
254
255err:
256	return result;
257}
258
259static int int3403_remove(struct platform_device *pdev)
260{
261	struct int3403_priv *priv = platform_get_drvdata(pdev);
262
263	switch (priv->type) {
264	case INT3403_TYPE_SENSOR:
265		int3403_sensor_remove(priv);
266		break;
267	case INT3403_TYPE_CHARGER:
268	case INT3403_TYPE_BATTERY:
269		int3403_cdev_remove(priv);
270		break;
271	default:
272		break;
273	}
274
275	return 0;
276}
277
278static const struct acpi_device_id int3403_device_ids[] = {
279	{"INT3403", 0},
280	{"", 0},
281};
282MODULE_DEVICE_TABLE(acpi, int3403_device_ids);
283
284static struct platform_driver int3403_driver = {
285	.probe = int3403_add,
286	.remove = int3403_remove,
287	.driver = {
288		.name = "int3403 thermal",
289		.acpi_match_table = int3403_device_ids,
290	},
291};
292
293module_platform_driver(int3403_driver);
294
295MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
296MODULE_LICENSE("GPL v2");
297MODULE_DESCRIPTION("ACPI INT3403 thermal driver");
298