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