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