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