GNU Linux-libre 4.19.295-gnu1
[releases.git] / drivers / thermal / int340x_thermal / int340x_thermal_zone.c
1 /*
2  * int340x_thermal_zone.c
3  * Copyright (c) 2015, 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/acpi.h>
19 #include <linux/thermal.h>
20 #include "int340x_thermal_zone.h"
21
22 static int int340x_thermal_get_zone_temp(struct thermal_zone_device *zone,
23                                          int *temp)
24 {
25         struct int34x_thermal_zone *d = zone->devdata;
26         unsigned long long tmp;
27         acpi_status status;
28
29         if (d->override_ops && d->override_ops->get_temp)
30                 return d->override_ops->get_temp(zone, temp);
31
32         status = acpi_evaluate_integer(d->adev->handle, "_TMP", NULL, &tmp);
33         if (ACPI_FAILURE(status))
34                 return -EIO;
35
36         if (d->lpat_table) {
37                 int conv_temp;
38
39                 conv_temp = acpi_lpat_raw_to_temp(d->lpat_table, (int)tmp);
40                 if (conv_temp < 0)
41                         return conv_temp;
42
43                 *temp = (unsigned long)conv_temp * 10;
44         } else
45                 /* _TMP returns the temperature in tenths of degrees Kelvin */
46                 *temp = DECI_KELVIN_TO_MILLICELSIUS(tmp);
47
48         return 0;
49 }
50
51 static int int340x_thermal_get_trip_temp(struct thermal_zone_device *zone,
52                                          int trip, int *temp)
53 {
54         struct int34x_thermal_zone *d = zone->devdata;
55         int i, ret = 0;
56
57         if (d->override_ops && d->override_ops->get_trip_temp)
58                 return d->override_ops->get_trip_temp(zone, trip, temp);
59
60         mutex_lock(&d->trip_mutex);
61
62         if (trip < d->aux_trip_nr)
63                 *temp = d->aux_trips[trip];
64         else if (trip == d->crt_trip_id)
65                 *temp = d->crt_temp;
66         else if (trip == d->psv_trip_id)
67                 *temp = d->psv_temp;
68         else if (trip == d->hot_trip_id)
69                 *temp = d->hot_temp;
70         else {
71                 for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) {
72                         if (d->act_trips[i].valid &&
73                             d->act_trips[i].id == trip) {
74                                 *temp = d->act_trips[i].temp;
75                                 break;
76                         }
77                 }
78                 if (i == INT340X_THERMAL_MAX_ACT_TRIP_COUNT)
79                         ret = -EINVAL;
80         }
81
82         mutex_unlock(&d->trip_mutex);
83
84         return ret;
85 }
86
87 static int int340x_thermal_get_trip_type(struct thermal_zone_device *zone,
88                                          int trip,
89                                          enum thermal_trip_type *type)
90 {
91         struct int34x_thermal_zone *d = zone->devdata;
92         int i, ret = 0;
93
94         if (d->override_ops && d->override_ops->get_trip_type)
95                 return d->override_ops->get_trip_type(zone, trip, type);
96
97         mutex_lock(&d->trip_mutex);
98
99         if (trip < d->aux_trip_nr)
100                 *type = THERMAL_TRIP_PASSIVE;
101         else if (trip == d->crt_trip_id)
102                 *type = THERMAL_TRIP_CRITICAL;
103         else if (trip == d->hot_trip_id)
104                 *type = THERMAL_TRIP_HOT;
105         else if (trip == d->psv_trip_id)
106                 *type = THERMAL_TRIP_PASSIVE;
107         else {
108                 for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) {
109                         if (d->act_trips[i].valid &&
110                             d->act_trips[i].id == trip) {
111                                 *type = THERMAL_TRIP_ACTIVE;
112                                 break;
113                         }
114                 }
115                 if (i == INT340X_THERMAL_MAX_ACT_TRIP_COUNT)
116                         ret = -EINVAL;
117         }
118
119         mutex_unlock(&d->trip_mutex);
120
121         return ret;
122 }
123
124 static int int340x_thermal_set_trip_temp(struct thermal_zone_device *zone,
125                                       int trip, int temp)
126 {
127         struct int34x_thermal_zone *d = zone->devdata;
128         acpi_status status;
129         char name[10];
130
131         if (d->override_ops && d->override_ops->set_trip_temp)
132                 return d->override_ops->set_trip_temp(zone, trip, temp);
133
134         snprintf(name, sizeof(name), "PAT%d", trip);
135         status = acpi_execute_simple_method(d->adev->handle, name,
136                         MILLICELSIUS_TO_DECI_KELVIN(temp));
137         if (ACPI_FAILURE(status))
138                 return -EIO;
139
140         d->aux_trips[trip] = temp;
141
142         return 0;
143 }
144
145
146 static int int340x_thermal_get_trip_hyst(struct thermal_zone_device *zone,
147                 int trip, int *temp)
148 {
149         struct int34x_thermal_zone *d = zone->devdata;
150         acpi_status status;
151         unsigned long long hyst;
152
153         if (d->override_ops && d->override_ops->get_trip_hyst)
154                 return d->override_ops->get_trip_hyst(zone, trip, temp);
155
156         status = acpi_evaluate_integer(d->adev->handle, "GTSH", NULL, &hyst);
157         if (ACPI_FAILURE(status))
158                 *temp = 0;
159         else
160                 *temp = hyst * 100;
161
162         return 0;
163 }
164
165 static struct thermal_zone_device_ops int340x_thermal_zone_ops = {
166         .get_temp       = int340x_thermal_get_zone_temp,
167         .get_trip_temp  = int340x_thermal_get_trip_temp,
168         .get_trip_type  = int340x_thermal_get_trip_type,
169         .set_trip_temp  = int340x_thermal_set_trip_temp,
170         .get_trip_hyst =  int340x_thermal_get_trip_hyst,
171 };
172
173 static int int340x_thermal_get_trip_config(acpi_handle handle, char *name,
174                                       int *temp)
175 {
176         unsigned long long r;
177         acpi_status status;
178
179         status = acpi_evaluate_integer(handle, name, NULL, &r);
180         if (ACPI_FAILURE(status))
181                 return -EIO;
182
183         *temp = DECI_KELVIN_TO_MILLICELSIUS(r);
184
185         return 0;
186 }
187
188 int int340x_thermal_read_trips(struct int34x_thermal_zone *int34x_zone)
189 {
190         int trip_cnt = int34x_zone->aux_trip_nr;
191         int i;
192
193         mutex_lock(&int34x_zone->trip_mutex);
194
195         int34x_zone->crt_trip_id = -1;
196         if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_CRT",
197                                              &int34x_zone->crt_temp))
198                 int34x_zone->crt_trip_id = trip_cnt++;
199
200         int34x_zone->hot_trip_id = -1;
201         if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_HOT",
202                                              &int34x_zone->hot_temp))
203                 int34x_zone->hot_trip_id = trip_cnt++;
204
205         int34x_zone->psv_trip_id = -1;
206         if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_PSV",
207                                              &int34x_zone->psv_temp))
208                 int34x_zone->psv_trip_id = trip_cnt++;
209
210         for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) {
211                 char name[5] = { '_', 'A', 'C', '0' + i, '\0' };
212
213                 if (int340x_thermal_get_trip_config(int34x_zone->adev->handle,
214                                         name,
215                                         &int34x_zone->act_trips[i].temp))
216                         break;
217
218                 int34x_zone->act_trips[i].id = trip_cnt++;
219                 int34x_zone->act_trips[i].valid = true;
220         }
221
222         mutex_unlock(&int34x_zone->trip_mutex);
223
224         return trip_cnt;
225 }
226 EXPORT_SYMBOL_GPL(int340x_thermal_read_trips);
227
228 static struct thermal_zone_params int340x_thermal_params = {
229         .governor_name = "user_space",
230         .no_hwmon = true,
231 };
232
233 struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev,
234                                 struct thermal_zone_device_ops *override_ops)
235 {
236         struct int34x_thermal_zone *int34x_thermal_zone;
237         acpi_status status;
238         unsigned long long trip_cnt;
239         int trip_mask = 0;
240         int ret;
241
242         int34x_thermal_zone = kzalloc(sizeof(*int34x_thermal_zone),
243                                       GFP_KERNEL);
244         if (!int34x_thermal_zone)
245                 return ERR_PTR(-ENOMEM);
246
247         mutex_init(&int34x_thermal_zone->trip_mutex);
248
249         int34x_thermal_zone->adev = adev;
250         int34x_thermal_zone->override_ops = override_ops;
251
252         status = acpi_evaluate_integer(adev->handle, "PATC", NULL, &trip_cnt);
253         if (ACPI_FAILURE(status))
254                 trip_cnt = 0;
255         else {
256                 int34x_thermal_zone->aux_trips =
257                         kcalloc(trip_cnt,
258                                 sizeof(*int34x_thermal_zone->aux_trips),
259                                 GFP_KERNEL);
260                 if (!int34x_thermal_zone->aux_trips) {
261                         ret = -ENOMEM;
262                         goto err_trip_alloc;
263                 }
264                 trip_mask = BIT(trip_cnt) - 1;
265                 int34x_thermal_zone->aux_trip_nr = trip_cnt;
266         }
267
268         trip_cnt = int340x_thermal_read_trips(int34x_thermal_zone);
269
270         int34x_thermal_zone->lpat_table = acpi_lpat_get_conversion_table(
271                                                                 adev->handle);
272
273         int34x_thermal_zone->zone = thermal_zone_device_register(
274                                                 acpi_device_bid(adev),
275                                                 trip_cnt,
276                                                 trip_mask, int34x_thermal_zone,
277                                                 &int340x_thermal_zone_ops,
278                                                 &int340x_thermal_params,
279                                                 0, 0);
280         if (IS_ERR(int34x_thermal_zone->zone)) {
281                 ret = PTR_ERR(int34x_thermal_zone->zone);
282                 goto err_thermal_zone;
283         }
284
285         return int34x_thermal_zone;
286
287 err_thermal_zone:
288         acpi_lpat_free_conversion_table(int34x_thermal_zone->lpat_table);
289         kfree(int34x_thermal_zone->aux_trips);
290 err_trip_alloc:
291         mutex_destroy(&int34x_thermal_zone->trip_mutex);
292         kfree(int34x_thermal_zone);
293         return ERR_PTR(ret);
294 }
295 EXPORT_SYMBOL_GPL(int340x_thermal_zone_add);
296
297 void int340x_thermal_zone_remove(struct int34x_thermal_zone
298                                  *int34x_thermal_zone)
299 {
300         thermal_zone_device_unregister(int34x_thermal_zone->zone);
301         acpi_lpat_free_conversion_table(int34x_thermal_zone->lpat_table);
302         kfree(int34x_thermal_zone->aux_trips);
303         mutex_destroy(&int34x_thermal_zone->trip_mutex);
304         kfree(int34x_thermal_zone);
305 }
306 EXPORT_SYMBOL_GPL(int340x_thermal_zone_remove);
307
308 MODULE_AUTHOR("Aaron Lu <aaron.lu@intel.com>");
309 MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
310 MODULE_DESCRIPTION("Intel INT340x common thermal zone handler");
311 MODULE_LICENSE("GPL v2");