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 | |
14 | static int int340x_thermal_get_zone_temp(struct thermal_zone_device *zone, |
15 | int *temp) |
16 | { |
17 | struct int34x_thermal_zone *d = thermal_zone_device_priv(tzd: zone); |
18 | unsigned long long tmp; |
19 | acpi_status status; |
20 | |
21 | status = acpi_evaluate_integer(handle: d->adev->handle, pathname: "_TMP" , NULL, data: &tmp); |
22 | if (ACPI_FAILURE(status)) |
23 | return -EIO; |
24 | |
25 | if (d->lpat_table) { |
26 | int conv_temp; |
27 | |
28 | conv_temp = acpi_lpat_raw_to_temp(lpat_table: d->lpat_table, raw: (int)tmp); |
29 | if (conv_temp < 0) |
30 | return conv_temp; |
31 | |
32 | *temp = conv_temp * 10; |
33 | } else { |
34 | /* _TMP returns the temperature in tenths of degrees Kelvin */ |
35 | *temp = deci_kelvin_to_millicelsius(t: tmp); |
36 | } |
37 | |
38 | return 0; |
39 | } |
40 | |
41 | static int int340x_thermal_set_trip_temp(struct thermal_zone_device *zone, |
42 | int trip, int temp) |
43 | { |
44 | struct int34x_thermal_zone *d = thermal_zone_device_priv(tzd: zone); |
45 | char name[] = {'P', 'A', 'T', '0' + trip, '\0'}; |
46 | acpi_status status; |
47 | |
48 | if (trip > 9) |
49 | return -EINVAL; |
50 | |
51 | status = acpi_execute_simple_method(handle: d->adev->handle, method: name, |
52 | arg: millicelsius_to_deci_kelvin(t: temp)); |
53 | if (ACPI_FAILURE(status)) |
54 | return -EIO; |
55 | |
56 | return 0; |
57 | } |
58 | |
59 | static void int340x_thermal_critical(struct thermal_zone_device *zone) |
60 | { |
61 | dev_dbg(&zone->device, "%s: critical temperature reached\n" , zone->type); |
62 | } |
63 | |
64 | static struct thermal_zone_device_ops int340x_thermal_zone_ops = { |
65 | .get_temp = int340x_thermal_get_zone_temp, |
66 | .set_trip_temp = int340x_thermal_set_trip_temp, |
67 | .critical = int340x_thermal_critical, |
68 | }; |
69 | |
70 | static inline void *int_to_trip_priv(int i) |
71 | { |
72 | return (void *)(long)i; |
73 | } |
74 | |
75 | static inline int trip_priv_to_int(const struct thermal_trip *trip) |
76 | { |
77 | return (long)trip->priv; |
78 | } |
79 | |
80 | static int int340x_thermal_read_trips(struct acpi_device *zone_adev, |
81 | struct thermal_trip *zone_trips, |
82 | int trip_cnt) |
83 | { |
84 | int i, ret; |
85 | |
86 | ret = thermal_acpi_critical_trip_temp(adev: zone_adev, |
87 | ret_temp: &zone_trips[trip_cnt].temperature); |
88 | if (!ret) { |
89 | zone_trips[trip_cnt].type = THERMAL_TRIP_CRITICAL; |
90 | trip_cnt++; |
91 | } |
92 | |
93 | ret = thermal_acpi_hot_trip_temp(adev: zone_adev, |
94 | ret_temp: &zone_trips[trip_cnt].temperature); |
95 | if (!ret) { |
96 | zone_trips[trip_cnt].type = THERMAL_TRIP_HOT; |
97 | trip_cnt++; |
98 | } |
99 | |
100 | ret = thermal_acpi_passive_trip_temp(adev: zone_adev, |
101 | ret_temp: &zone_trips[trip_cnt].temperature); |
102 | if (!ret) { |
103 | zone_trips[trip_cnt].type = THERMAL_TRIP_PASSIVE; |
104 | trip_cnt++; |
105 | } |
106 | |
107 | for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) { |
108 | ret = thermal_acpi_active_trip_temp(adev: zone_adev, id: i, |
109 | ret_temp: &zone_trips[trip_cnt].temperature); |
110 | if (ret) |
111 | break; |
112 | |
113 | zone_trips[trip_cnt].type = THERMAL_TRIP_ACTIVE; |
114 | zone_trips[trip_cnt].priv = int_to_trip_priv(i); |
115 | trip_cnt++; |
116 | } |
117 | |
118 | return trip_cnt; |
119 | } |
120 | |
121 | static struct thermal_zone_params int340x_thermal_params = { |
122 | .governor_name = "user_space" , |
123 | .no_hwmon = true, |
124 | }; |
125 | |
126 | struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev, |
127 | int (*get_temp) (struct thermal_zone_device *, int *)) |
128 | { |
129 | struct int34x_thermal_zone *int34x_zone; |
130 | struct thermal_trip *zone_trips; |
131 | unsigned long long trip_cnt = 0; |
132 | unsigned long long hyst; |
133 | int trip_mask = 0; |
134 | acpi_status status; |
135 | int i, ret; |
136 | |
137 | int34x_zone = kzalloc(size: sizeof(*int34x_zone), GFP_KERNEL); |
138 | if (!int34x_zone) |
139 | return ERR_PTR(error: -ENOMEM); |
140 | |
141 | int34x_zone->adev = adev; |
142 | |
143 | int34x_zone->ops = kmemdup(p: &int340x_thermal_zone_ops, |
144 | size: sizeof(int340x_thermal_zone_ops), GFP_KERNEL); |
145 | if (!int34x_zone->ops) { |
146 | ret = -ENOMEM; |
147 | goto err_ops_alloc; |
148 | } |
149 | |
150 | if (get_temp) |
151 | int34x_zone->ops->get_temp = get_temp; |
152 | |
153 | status = acpi_evaluate_integer(handle: adev->handle, pathname: "PATC" , NULL, data: &trip_cnt); |
154 | if (ACPI_SUCCESS(status)) { |
155 | int34x_zone->aux_trip_nr = trip_cnt; |
156 | trip_mask = BIT(trip_cnt) - 1; |
157 | } |
158 | |
159 | zone_trips = kzalloc(size: sizeof(*zone_trips) * (trip_cnt + INT340X_THERMAL_MAX_TRIP_COUNT), |
160 | GFP_KERNEL); |
161 | if (!zone_trips) { |
162 | ret = -ENOMEM; |
163 | goto err_trips_alloc; |
164 | } |
165 | |
166 | for (i = 0; i < trip_cnt; i++) { |
167 | zone_trips[i].type = THERMAL_TRIP_PASSIVE; |
168 | zone_trips[i].temperature = THERMAL_TEMP_INVALID; |
169 | } |
170 | |
171 | trip_cnt = int340x_thermal_read_trips(zone_adev: adev, zone_trips, trip_cnt); |
172 | |
173 | status = acpi_evaluate_integer(handle: adev->handle, pathname: "GTSH" , NULL, data: &hyst); |
174 | if (ACPI_SUCCESS(status)) |
175 | hyst *= 100; |
176 | else |
177 | hyst = 0; |
178 | |
179 | for (i = 0; i < trip_cnt; ++i) |
180 | zone_trips[i].hysteresis = hyst; |
181 | |
182 | int34x_zone->trips = zone_trips; |
183 | |
184 | int34x_zone->lpat_table = acpi_lpat_get_conversion_table(handle: adev->handle); |
185 | |
186 | int34x_zone->zone = thermal_zone_device_register_with_trips( |
187 | acpi_device_bid(adev), |
188 | trips: zone_trips, num_trips: trip_cnt, |
189 | mask: trip_mask, devdata: int34x_zone, |
190 | ops: int34x_zone->ops, |
191 | tzp: &int340x_thermal_params, |
192 | passive_delay: 0, polling_delay: 0); |
193 | if (IS_ERR(ptr: int34x_zone->zone)) { |
194 | ret = PTR_ERR(ptr: int34x_zone->zone); |
195 | goto err_thermal_zone; |
196 | } |
197 | ret = thermal_zone_device_enable(tz: int34x_zone->zone); |
198 | if (ret) |
199 | goto err_enable; |
200 | |
201 | return int34x_zone; |
202 | |
203 | err_enable: |
204 | thermal_zone_device_unregister(tz: int34x_zone->zone); |
205 | err_thermal_zone: |
206 | kfree(objp: int34x_zone->trips); |
207 | acpi_lpat_free_conversion_table(lpat_table: int34x_zone->lpat_table); |
208 | err_trips_alloc: |
209 | kfree(objp: int34x_zone->ops); |
210 | err_ops_alloc: |
211 | kfree(objp: int34x_zone); |
212 | return ERR_PTR(error: ret); |
213 | } |
214 | EXPORT_SYMBOL_GPL(int340x_thermal_zone_add); |
215 | |
216 | void int340x_thermal_zone_remove(struct int34x_thermal_zone *int34x_zone) |
217 | { |
218 | thermal_zone_device_unregister(tz: int34x_zone->zone); |
219 | acpi_lpat_free_conversion_table(lpat_table: int34x_zone->lpat_table); |
220 | kfree(objp: int34x_zone->trips); |
221 | kfree(objp: int34x_zone->ops); |
222 | kfree(objp: int34x_zone); |
223 | } |
224 | EXPORT_SYMBOL_GPL(int340x_thermal_zone_remove); |
225 | |
226 | static int int340x_update_one_trip(struct thermal_trip *trip, void *arg) |
227 | { |
228 | struct acpi_device *zone_adev = arg; |
229 | int temp, err; |
230 | |
231 | switch (trip->type) { |
232 | case THERMAL_TRIP_CRITICAL: |
233 | err = thermal_acpi_critical_trip_temp(adev: zone_adev, ret_temp: &temp); |
234 | break; |
235 | case THERMAL_TRIP_HOT: |
236 | err = thermal_acpi_hot_trip_temp(adev: zone_adev, ret_temp: &temp); |
237 | break; |
238 | case THERMAL_TRIP_PASSIVE: |
239 | err = thermal_acpi_passive_trip_temp(adev: zone_adev, ret_temp: &temp); |
240 | break; |
241 | case THERMAL_TRIP_ACTIVE: |
242 | err = thermal_acpi_active_trip_temp(adev: zone_adev, |
243 | id: trip_priv_to_int(trip), |
244 | ret_temp: &temp); |
245 | break; |
246 | default: |
247 | err = -ENODEV; |
248 | } |
249 | if (err) |
250 | temp = THERMAL_TEMP_INVALID; |
251 | |
252 | trip->temperature = temp; |
253 | return 0; |
254 | } |
255 | |
256 | void int340x_thermal_update_trips(struct int34x_thermal_zone *int34x_zone) |
257 | { |
258 | thermal_zone_for_each_trip(tz: int34x_zone->zone, cb: int340x_update_one_trip, |
259 | data: int34x_zone->adev); |
260 | } |
261 | EXPORT_SYMBOL_GPL(int340x_thermal_update_trips); |
262 | |
263 | MODULE_AUTHOR("Aaron Lu <aaron.lu@intel.com>" ); |
264 | MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>" ); |
265 | MODULE_DESCRIPTION("Intel INT340x common thermal zone handler" ); |
266 | MODULE_LICENSE("GPL v2" ); |
267 | |