1 | // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 |
2 | /* Copyright (c) 2016-2018 Mellanox Technologies. All rights reserved |
3 | * Copyright (c) 2016 Ivan Vecera <cera@cera.cz> |
4 | */ |
5 | |
6 | #include <linux/kernel.h> |
7 | #include <linux/types.h> |
8 | #include <linux/device.h> |
9 | #include <linux/sysfs.h> |
10 | #include <linux/thermal.h> |
11 | #include <linux/err.h> |
12 | #include <linux/sfp.h> |
13 | |
14 | #include "core.h" |
15 | #include "core_env.h" |
16 | |
17 | #define MLXSW_THERMAL_POLL_INT 1000 /* ms */ |
18 | #define MLXSW_THERMAL_SLOW_POLL_INT 20000 /* ms */ |
19 | #define MLXSW_THERMAL_ASIC_TEMP_NORM 75000 /* 75C */ |
20 | #define MLXSW_THERMAL_ASIC_TEMP_HIGH 85000 /* 85C */ |
21 | #define MLXSW_THERMAL_ASIC_TEMP_HOT 105000 /* 105C */ |
22 | #define MLXSW_THERMAL_MODULE_TEMP_NORM 55000 /* 55C */ |
23 | #define MLXSW_THERMAL_MODULE_TEMP_HIGH 65000 /* 65C */ |
24 | #define MLXSW_THERMAL_MODULE_TEMP_HOT 80000 /* 80C */ |
25 | #define MLXSW_THERMAL_HYSTERESIS_TEMP 5000 /* 5C */ |
26 | #define MLXSW_THERMAL_MODULE_TEMP_SHIFT (MLXSW_THERMAL_HYSTERESIS_TEMP * 2) |
27 | #define MLXSW_THERMAL_MAX_STATE 10 |
28 | #define MLXSW_THERMAL_MIN_STATE 2 |
29 | #define MLXSW_THERMAL_MAX_DUTY 255 |
30 | |
31 | /* External cooling devices, allowed for binding to mlxsw thermal zones. */ |
32 | static char * const mlxsw_thermal_external_allowed_cdev[] = { |
33 | "mlxreg_fan" , |
34 | "emc2305" , |
35 | }; |
36 | |
37 | struct mlxsw_cooling_states { |
38 | int min_state; |
39 | int max_state; |
40 | }; |
41 | |
42 | static const struct thermal_trip default_thermal_trips[] = { |
43 | { /* In range - 0-40% PWM */ |
44 | .type = THERMAL_TRIP_ACTIVE, |
45 | .temperature = MLXSW_THERMAL_ASIC_TEMP_NORM, |
46 | .hysteresis = MLXSW_THERMAL_HYSTERESIS_TEMP, |
47 | }, |
48 | { |
49 | /* In range - 40-100% PWM */ |
50 | .type = THERMAL_TRIP_ACTIVE, |
51 | .temperature = MLXSW_THERMAL_ASIC_TEMP_HIGH, |
52 | .hysteresis = MLXSW_THERMAL_HYSTERESIS_TEMP, |
53 | }, |
54 | { /* Warning */ |
55 | .type = THERMAL_TRIP_HOT, |
56 | .temperature = MLXSW_THERMAL_ASIC_TEMP_HOT, |
57 | }, |
58 | }; |
59 | |
60 | static const struct thermal_trip default_thermal_module_trips[] = { |
61 | { /* In range - 0-40% PWM */ |
62 | .type = THERMAL_TRIP_ACTIVE, |
63 | .temperature = MLXSW_THERMAL_MODULE_TEMP_NORM, |
64 | .hysteresis = MLXSW_THERMAL_HYSTERESIS_TEMP, |
65 | }, |
66 | { |
67 | /* In range - 40-100% PWM */ |
68 | .type = THERMAL_TRIP_ACTIVE, |
69 | .temperature = MLXSW_THERMAL_MODULE_TEMP_HIGH, |
70 | .hysteresis = MLXSW_THERMAL_HYSTERESIS_TEMP, |
71 | }, |
72 | { /* Warning */ |
73 | .type = THERMAL_TRIP_HOT, |
74 | .temperature = MLXSW_THERMAL_MODULE_TEMP_HOT, |
75 | }, |
76 | }; |
77 | |
78 | static const struct mlxsw_cooling_states default_cooling_states[] = { |
79 | { |
80 | .min_state = 0, |
81 | .max_state = (4 * MLXSW_THERMAL_MAX_STATE) / 10, |
82 | }, |
83 | { |
84 | .min_state = (4 * MLXSW_THERMAL_MAX_STATE) / 10, |
85 | .max_state = MLXSW_THERMAL_MAX_STATE, |
86 | }, |
87 | { |
88 | .min_state = MLXSW_THERMAL_MAX_STATE, |
89 | .max_state = MLXSW_THERMAL_MAX_STATE, |
90 | }, |
91 | }; |
92 | |
93 | #define MLXSW_THERMAL_NUM_TRIPS ARRAY_SIZE(default_thermal_trips) |
94 | |
95 | /* Make sure all trips are writable */ |
96 | #define MLXSW_THERMAL_TRIP_MASK (BIT(MLXSW_THERMAL_NUM_TRIPS) - 1) |
97 | |
98 | struct mlxsw_thermal; |
99 | |
100 | struct mlxsw_thermal_module { |
101 | struct mlxsw_thermal *parent; |
102 | struct thermal_zone_device *tzdev; |
103 | struct thermal_trip trips[MLXSW_THERMAL_NUM_TRIPS]; |
104 | struct mlxsw_cooling_states cooling_states[MLXSW_THERMAL_NUM_TRIPS]; |
105 | int module; /* Module or gearbox number */ |
106 | u8 slot_index; |
107 | }; |
108 | |
109 | struct mlxsw_thermal_area { |
110 | struct mlxsw_thermal_module *tz_module_arr; |
111 | u8 tz_module_num; |
112 | struct mlxsw_thermal_module *tz_gearbox_arr; |
113 | u8 tz_gearbox_num; |
114 | u8 slot_index; |
115 | bool active; |
116 | }; |
117 | |
118 | struct mlxsw_thermal { |
119 | struct mlxsw_core *core; |
120 | const struct mlxsw_bus_info *bus_info; |
121 | struct thermal_zone_device *tzdev; |
122 | int polling_delay; |
123 | struct thermal_cooling_device *cdevs[MLXSW_MFCR_PWMS_MAX]; |
124 | struct thermal_trip trips[MLXSW_THERMAL_NUM_TRIPS]; |
125 | struct mlxsw_cooling_states cooling_states[MLXSW_THERMAL_NUM_TRIPS]; |
126 | struct mlxsw_thermal_area line_cards[]; |
127 | }; |
128 | |
129 | static inline u8 mlxsw_state_to_duty(int state) |
130 | { |
131 | return DIV_ROUND_CLOSEST(state * MLXSW_THERMAL_MAX_DUTY, |
132 | MLXSW_THERMAL_MAX_STATE); |
133 | } |
134 | |
135 | static inline int mlxsw_duty_to_state(u8 duty) |
136 | { |
137 | return DIV_ROUND_CLOSEST(duty * MLXSW_THERMAL_MAX_STATE, |
138 | MLXSW_THERMAL_MAX_DUTY); |
139 | } |
140 | |
141 | static int mlxsw_get_cooling_device_idx(struct mlxsw_thermal *thermal, |
142 | struct thermal_cooling_device *cdev) |
143 | { |
144 | int i; |
145 | |
146 | for (i = 0; i < MLXSW_MFCR_PWMS_MAX; i++) |
147 | if (thermal->cdevs[i] == cdev) |
148 | return i; |
149 | |
150 | /* Allow mlxsw thermal zone binding to an external cooling device */ |
151 | for (i = 0; i < ARRAY_SIZE(mlxsw_thermal_external_allowed_cdev); i++) { |
152 | if (!strcmp(cdev->type, mlxsw_thermal_external_allowed_cdev[i])) |
153 | return 0; |
154 | } |
155 | |
156 | return -ENODEV; |
157 | } |
158 | |
159 | static int mlxsw_thermal_bind(struct thermal_zone_device *tzdev, |
160 | struct thermal_cooling_device *cdev) |
161 | { |
162 | struct mlxsw_thermal *thermal = thermal_zone_device_priv(tzd: tzdev); |
163 | struct device *dev = thermal->bus_info->dev; |
164 | int i, err; |
165 | |
166 | /* If the cooling device is one of ours bind it */ |
167 | if (mlxsw_get_cooling_device_idx(thermal, cdev) < 0) |
168 | return 0; |
169 | |
170 | for (i = 0; i < MLXSW_THERMAL_NUM_TRIPS; i++) { |
171 | const struct mlxsw_cooling_states *state = &thermal->cooling_states[i]; |
172 | |
173 | err = thermal_zone_bind_cooling_device(tzdev, i, cdev, |
174 | state->max_state, |
175 | state->min_state, |
176 | THERMAL_WEIGHT_DEFAULT); |
177 | if (err < 0) { |
178 | dev_err(dev, "Failed to bind cooling device to trip %d\n" , i); |
179 | return err; |
180 | } |
181 | } |
182 | return 0; |
183 | } |
184 | |
185 | static int mlxsw_thermal_unbind(struct thermal_zone_device *tzdev, |
186 | struct thermal_cooling_device *cdev) |
187 | { |
188 | struct mlxsw_thermal *thermal = thermal_zone_device_priv(tzd: tzdev); |
189 | struct device *dev = thermal->bus_info->dev; |
190 | int i; |
191 | int err; |
192 | |
193 | /* If the cooling device is our one unbind it */ |
194 | if (mlxsw_get_cooling_device_idx(thermal, cdev) < 0) |
195 | return 0; |
196 | |
197 | for (i = 0; i < MLXSW_THERMAL_NUM_TRIPS; i++) { |
198 | err = thermal_zone_unbind_cooling_device(tzdev, i, cdev); |
199 | if (err < 0) { |
200 | dev_err(dev, "Failed to unbind cooling device\n" ); |
201 | return err; |
202 | } |
203 | } |
204 | return 0; |
205 | } |
206 | |
207 | static int mlxsw_thermal_get_temp(struct thermal_zone_device *tzdev, |
208 | int *p_temp) |
209 | { |
210 | struct mlxsw_thermal *thermal = thermal_zone_device_priv(tzd: tzdev); |
211 | struct device *dev = thermal->bus_info->dev; |
212 | char mtmp_pl[MLXSW_REG_MTMP_LEN]; |
213 | int temp; |
214 | int err; |
215 | |
216 | mlxsw_reg_mtmp_pack(payload: mtmp_pl, slot_index: 0, sensor_index: 0, max_temp_enable: false, max_temp_reset: false); |
217 | |
218 | err = mlxsw_reg_query(mlxsw_core: thermal->core, MLXSW_REG(mtmp), payload: mtmp_pl); |
219 | if (err) { |
220 | dev_err(dev, "Failed to query temp sensor\n" ); |
221 | return err; |
222 | } |
223 | mlxsw_reg_mtmp_unpack(payload: mtmp_pl, p_temp: &temp, NULL, NULL, NULL, NULL); |
224 | |
225 | *p_temp = temp; |
226 | return 0; |
227 | } |
228 | |
229 | static struct thermal_zone_params mlxsw_thermal_params = { |
230 | .no_hwmon = true, |
231 | }; |
232 | |
233 | static struct thermal_zone_device_ops mlxsw_thermal_ops = { |
234 | .bind = mlxsw_thermal_bind, |
235 | .unbind = mlxsw_thermal_unbind, |
236 | .get_temp = mlxsw_thermal_get_temp, |
237 | }; |
238 | |
239 | static int mlxsw_thermal_module_bind(struct thermal_zone_device *tzdev, |
240 | struct thermal_cooling_device *cdev) |
241 | { |
242 | struct mlxsw_thermal_module *tz = thermal_zone_device_priv(tzd: tzdev); |
243 | struct mlxsw_thermal *thermal = tz->parent; |
244 | int i, j, err; |
245 | |
246 | /* If the cooling device is one of ours bind it */ |
247 | if (mlxsw_get_cooling_device_idx(thermal, cdev) < 0) |
248 | return 0; |
249 | |
250 | for (i = 0; i < MLXSW_THERMAL_NUM_TRIPS; i++) { |
251 | const struct mlxsw_cooling_states *state = &tz->cooling_states[i]; |
252 | |
253 | err = thermal_zone_bind_cooling_device(tzdev, i, cdev, |
254 | state->max_state, |
255 | state->min_state, |
256 | THERMAL_WEIGHT_DEFAULT); |
257 | if (err < 0) |
258 | goto err_thermal_zone_bind_cooling_device; |
259 | } |
260 | return 0; |
261 | |
262 | err_thermal_zone_bind_cooling_device: |
263 | for (j = i - 1; j >= 0; j--) |
264 | thermal_zone_unbind_cooling_device(tzdev, j, cdev); |
265 | return err; |
266 | } |
267 | |
268 | static int mlxsw_thermal_module_unbind(struct thermal_zone_device *tzdev, |
269 | struct thermal_cooling_device *cdev) |
270 | { |
271 | struct mlxsw_thermal_module *tz = thermal_zone_device_priv(tzd: tzdev); |
272 | struct mlxsw_thermal *thermal = tz->parent; |
273 | int i; |
274 | int err; |
275 | |
276 | /* If the cooling device is one of ours unbind it */ |
277 | if (mlxsw_get_cooling_device_idx(thermal, cdev) < 0) |
278 | return 0; |
279 | |
280 | for (i = 0; i < MLXSW_THERMAL_NUM_TRIPS; i++) { |
281 | err = thermal_zone_unbind_cooling_device(tzdev, i, cdev); |
282 | WARN_ON(err); |
283 | } |
284 | return err; |
285 | } |
286 | |
287 | static int mlxsw_thermal_module_temp_get(struct thermal_zone_device *tzdev, |
288 | int *p_temp) |
289 | { |
290 | struct mlxsw_thermal_module *tz = thermal_zone_device_priv(tzd: tzdev); |
291 | struct mlxsw_thermal *thermal = tz->parent; |
292 | char mtmp_pl[MLXSW_REG_MTMP_LEN]; |
293 | u16 sensor_index; |
294 | int err; |
295 | |
296 | sensor_index = MLXSW_REG_MTMP_MODULE_INDEX_MIN + tz->module; |
297 | mlxsw_reg_mtmp_pack(payload: mtmp_pl, slot_index: tz->slot_index, sensor_index, |
298 | max_temp_enable: false, max_temp_reset: false); |
299 | err = mlxsw_reg_query(mlxsw_core: thermal->core, MLXSW_REG(mtmp), payload: mtmp_pl); |
300 | if (err) |
301 | return err; |
302 | mlxsw_reg_mtmp_unpack(payload: mtmp_pl, p_temp, NULL, NULL, NULL, NULL); |
303 | return 0; |
304 | } |
305 | |
306 | static struct thermal_zone_device_ops mlxsw_thermal_module_ops = { |
307 | .bind = mlxsw_thermal_module_bind, |
308 | .unbind = mlxsw_thermal_module_unbind, |
309 | .get_temp = mlxsw_thermal_module_temp_get, |
310 | }; |
311 | |
312 | static int mlxsw_thermal_gearbox_temp_get(struct thermal_zone_device *tzdev, |
313 | int *p_temp) |
314 | { |
315 | struct mlxsw_thermal_module *tz = thermal_zone_device_priv(tzd: tzdev); |
316 | struct mlxsw_thermal *thermal = tz->parent; |
317 | char mtmp_pl[MLXSW_REG_MTMP_LEN]; |
318 | u16 index; |
319 | int temp; |
320 | int err; |
321 | |
322 | index = MLXSW_REG_MTMP_GBOX_INDEX_MIN + tz->module; |
323 | mlxsw_reg_mtmp_pack(payload: mtmp_pl, slot_index: tz->slot_index, sensor_index: index, max_temp_enable: false, max_temp_reset: false); |
324 | |
325 | err = mlxsw_reg_query(mlxsw_core: thermal->core, MLXSW_REG(mtmp), payload: mtmp_pl); |
326 | if (err) |
327 | return err; |
328 | |
329 | mlxsw_reg_mtmp_unpack(payload: mtmp_pl, p_temp: &temp, NULL, NULL, NULL, NULL); |
330 | |
331 | *p_temp = temp; |
332 | return 0; |
333 | } |
334 | |
335 | static struct thermal_zone_device_ops mlxsw_thermal_gearbox_ops = { |
336 | .bind = mlxsw_thermal_module_bind, |
337 | .unbind = mlxsw_thermal_module_unbind, |
338 | .get_temp = mlxsw_thermal_gearbox_temp_get, |
339 | }; |
340 | |
341 | static int mlxsw_thermal_get_max_state(struct thermal_cooling_device *cdev, |
342 | unsigned long *p_state) |
343 | { |
344 | *p_state = MLXSW_THERMAL_MAX_STATE; |
345 | return 0; |
346 | } |
347 | |
348 | static int mlxsw_thermal_get_cur_state(struct thermal_cooling_device *cdev, |
349 | unsigned long *p_state) |
350 | |
351 | { |
352 | struct mlxsw_thermal *thermal = cdev->devdata; |
353 | struct device *dev = thermal->bus_info->dev; |
354 | char mfsc_pl[MLXSW_REG_MFSC_LEN]; |
355 | int err, idx; |
356 | u8 duty; |
357 | |
358 | idx = mlxsw_get_cooling_device_idx(thermal, cdev); |
359 | if (idx < 0) |
360 | return idx; |
361 | |
362 | mlxsw_reg_mfsc_pack(payload: mfsc_pl, pwm: idx, pwm_duty_cycle: 0); |
363 | err = mlxsw_reg_query(mlxsw_core: thermal->core, MLXSW_REG(mfsc), payload: mfsc_pl); |
364 | if (err) { |
365 | dev_err(dev, "Failed to query PWM duty\n" ); |
366 | return err; |
367 | } |
368 | |
369 | duty = mlxsw_reg_mfsc_pwm_duty_cycle_get(buf: mfsc_pl); |
370 | *p_state = mlxsw_duty_to_state(duty); |
371 | return 0; |
372 | } |
373 | |
374 | static int mlxsw_thermal_set_cur_state(struct thermal_cooling_device *cdev, |
375 | unsigned long state) |
376 | |
377 | { |
378 | struct mlxsw_thermal *thermal = cdev->devdata; |
379 | struct device *dev = thermal->bus_info->dev; |
380 | char mfsc_pl[MLXSW_REG_MFSC_LEN]; |
381 | int idx; |
382 | int err; |
383 | |
384 | if (state > MLXSW_THERMAL_MAX_STATE) |
385 | return -EINVAL; |
386 | |
387 | idx = mlxsw_get_cooling_device_idx(thermal, cdev); |
388 | if (idx < 0) |
389 | return idx; |
390 | |
391 | /* Normalize the state to the valid speed range. */ |
392 | state = max_t(unsigned long, MLXSW_THERMAL_MIN_STATE, state); |
393 | mlxsw_reg_mfsc_pack(payload: mfsc_pl, pwm: idx, pwm_duty_cycle: mlxsw_state_to_duty(state)); |
394 | err = mlxsw_reg_write(mlxsw_core: thermal->core, MLXSW_REG(mfsc), payload: mfsc_pl); |
395 | if (err) { |
396 | dev_err(dev, "Failed to write PWM duty\n" ); |
397 | return err; |
398 | } |
399 | return 0; |
400 | } |
401 | |
402 | static const struct thermal_cooling_device_ops mlxsw_cooling_ops = { |
403 | .get_max_state = mlxsw_thermal_get_max_state, |
404 | .get_cur_state = mlxsw_thermal_get_cur_state, |
405 | .set_cur_state = mlxsw_thermal_set_cur_state, |
406 | }; |
407 | |
408 | static int |
409 | mlxsw_thermal_module_tz_init(struct mlxsw_thermal_module *module_tz) |
410 | { |
411 | char tz_name[THERMAL_NAME_LENGTH]; |
412 | int err; |
413 | |
414 | if (module_tz->slot_index) |
415 | snprintf(buf: tz_name, size: sizeof(tz_name), fmt: "mlxsw-lc%d-module%d" , |
416 | module_tz->slot_index, module_tz->module + 1); |
417 | else |
418 | snprintf(buf: tz_name, size: sizeof(tz_name), fmt: "mlxsw-module%d" , |
419 | module_tz->module + 1); |
420 | module_tz->tzdev = thermal_zone_device_register_with_trips(type: tz_name, |
421 | trips: module_tz->trips, |
422 | MLXSW_THERMAL_NUM_TRIPS, |
423 | MLXSW_THERMAL_TRIP_MASK, |
424 | devdata: module_tz, |
425 | ops: &mlxsw_thermal_module_ops, |
426 | tzp: &mlxsw_thermal_params, |
427 | passive_delay: 0, |
428 | polling_delay: module_tz->parent->polling_delay); |
429 | if (IS_ERR(ptr: module_tz->tzdev)) { |
430 | err = PTR_ERR(ptr: module_tz->tzdev); |
431 | return err; |
432 | } |
433 | |
434 | err = thermal_zone_device_enable(tz: module_tz->tzdev); |
435 | if (err) |
436 | thermal_zone_device_unregister(tz: module_tz->tzdev); |
437 | |
438 | return err; |
439 | } |
440 | |
441 | static void mlxsw_thermal_module_tz_fini(struct thermal_zone_device *tzdev) |
442 | { |
443 | thermal_zone_device_unregister(tz: tzdev); |
444 | } |
445 | |
446 | static void |
447 | mlxsw_thermal_module_init(struct device *dev, struct mlxsw_core *core, |
448 | struct mlxsw_thermal *thermal, |
449 | struct mlxsw_thermal_area *area, u8 module) |
450 | { |
451 | struct mlxsw_thermal_module *module_tz; |
452 | |
453 | module_tz = &area->tz_module_arr[module]; |
454 | /* Skip if parent is already set (case of port split). */ |
455 | if (module_tz->parent) |
456 | return; |
457 | module_tz->module = module; |
458 | module_tz->slot_index = area->slot_index; |
459 | module_tz->parent = thermal; |
460 | BUILD_BUG_ON(ARRAY_SIZE(default_thermal_module_trips) != |
461 | MLXSW_THERMAL_NUM_TRIPS); |
462 | memcpy(module_tz->trips, default_thermal_module_trips, |
463 | sizeof(thermal->trips)); |
464 | memcpy(module_tz->cooling_states, default_cooling_states, |
465 | sizeof(thermal->cooling_states)); |
466 | } |
467 | |
468 | static void mlxsw_thermal_module_fini(struct mlxsw_thermal_module *module_tz) |
469 | { |
470 | if (module_tz && module_tz->tzdev) { |
471 | mlxsw_thermal_module_tz_fini(tzdev: module_tz->tzdev); |
472 | module_tz->tzdev = NULL; |
473 | module_tz->parent = NULL; |
474 | } |
475 | } |
476 | |
477 | static int |
478 | mlxsw_thermal_modules_init(struct device *dev, struct mlxsw_core *core, |
479 | struct mlxsw_thermal *thermal, |
480 | struct mlxsw_thermal_area *area) |
481 | { |
482 | struct mlxsw_thermal_module *module_tz; |
483 | char mgpir_pl[MLXSW_REG_MGPIR_LEN]; |
484 | int i, err; |
485 | |
486 | mlxsw_reg_mgpir_pack(payload: mgpir_pl, slot_index: area->slot_index); |
487 | err = mlxsw_reg_query(mlxsw_core: core, MLXSW_REG(mgpir), payload: mgpir_pl); |
488 | if (err) |
489 | return err; |
490 | |
491 | mlxsw_reg_mgpir_unpack(payload: mgpir_pl, NULL, NULL, NULL, |
492 | num_of_modules: &area->tz_module_num, NULL); |
493 | |
494 | /* For modular system module counter could be zero. */ |
495 | if (!area->tz_module_num) |
496 | return 0; |
497 | |
498 | area->tz_module_arr = kcalloc(n: area->tz_module_num, |
499 | size: sizeof(*area->tz_module_arr), |
500 | GFP_KERNEL); |
501 | if (!area->tz_module_arr) |
502 | return -ENOMEM; |
503 | |
504 | for (i = 0; i < area->tz_module_num; i++) |
505 | mlxsw_thermal_module_init(dev, core, thermal, area, module: i); |
506 | |
507 | for (i = 0; i < area->tz_module_num; i++) { |
508 | module_tz = &area->tz_module_arr[i]; |
509 | if (!module_tz->parent) |
510 | continue; |
511 | err = mlxsw_thermal_module_tz_init(module_tz); |
512 | if (err) |
513 | goto err_thermal_module_tz_init; |
514 | } |
515 | |
516 | return 0; |
517 | |
518 | err_thermal_module_tz_init: |
519 | for (i = area->tz_module_num - 1; i >= 0; i--) |
520 | mlxsw_thermal_module_fini(module_tz: &area->tz_module_arr[i]); |
521 | kfree(objp: area->tz_module_arr); |
522 | return err; |
523 | } |
524 | |
525 | static void |
526 | mlxsw_thermal_modules_fini(struct mlxsw_thermal *thermal, |
527 | struct mlxsw_thermal_area *area) |
528 | { |
529 | int i; |
530 | |
531 | for (i = area->tz_module_num - 1; i >= 0; i--) |
532 | mlxsw_thermal_module_fini(module_tz: &area->tz_module_arr[i]); |
533 | kfree(objp: area->tz_module_arr); |
534 | } |
535 | |
536 | static int |
537 | mlxsw_thermal_gearbox_tz_init(struct mlxsw_thermal_module *gearbox_tz) |
538 | { |
539 | char tz_name[40]; |
540 | int ret; |
541 | |
542 | if (gearbox_tz->slot_index) |
543 | snprintf(buf: tz_name, size: sizeof(tz_name), fmt: "mlxsw-lc%d-gearbox%d" , |
544 | gearbox_tz->slot_index, gearbox_tz->module + 1); |
545 | else |
546 | snprintf(buf: tz_name, size: sizeof(tz_name), fmt: "mlxsw-gearbox%d" , |
547 | gearbox_tz->module + 1); |
548 | gearbox_tz->tzdev = thermal_zone_device_register_with_trips(type: tz_name, |
549 | trips: gearbox_tz->trips, |
550 | MLXSW_THERMAL_NUM_TRIPS, |
551 | MLXSW_THERMAL_TRIP_MASK, |
552 | devdata: gearbox_tz, |
553 | ops: &mlxsw_thermal_gearbox_ops, |
554 | tzp: &mlxsw_thermal_params, passive_delay: 0, |
555 | polling_delay: gearbox_tz->parent->polling_delay); |
556 | if (IS_ERR(ptr: gearbox_tz->tzdev)) |
557 | return PTR_ERR(ptr: gearbox_tz->tzdev); |
558 | |
559 | ret = thermal_zone_device_enable(tz: gearbox_tz->tzdev); |
560 | if (ret) |
561 | thermal_zone_device_unregister(tz: gearbox_tz->tzdev); |
562 | |
563 | return ret; |
564 | } |
565 | |
566 | static void |
567 | mlxsw_thermal_gearbox_tz_fini(struct mlxsw_thermal_module *gearbox_tz) |
568 | { |
569 | thermal_zone_device_unregister(tz: gearbox_tz->tzdev); |
570 | } |
571 | |
572 | static int |
573 | mlxsw_thermal_gearboxes_init(struct device *dev, struct mlxsw_core *core, |
574 | struct mlxsw_thermal *thermal, |
575 | struct mlxsw_thermal_area *area) |
576 | { |
577 | enum mlxsw_reg_mgpir_device_type device_type; |
578 | struct mlxsw_thermal_module *gearbox_tz; |
579 | char mgpir_pl[MLXSW_REG_MGPIR_LEN]; |
580 | u8 gbox_num; |
581 | int i; |
582 | int err; |
583 | |
584 | mlxsw_reg_mgpir_pack(payload: mgpir_pl, slot_index: area->slot_index); |
585 | err = mlxsw_reg_query(mlxsw_core: core, MLXSW_REG(mgpir), payload: mgpir_pl); |
586 | if (err) |
587 | return err; |
588 | |
589 | mlxsw_reg_mgpir_unpack(payload: mgpir_pl, num_of_devices: &gbox_num, device_type: &device_type, NULL, |
590 | NULL, NULL); |
591 | if (device_type != MLXSW_REG_MGPIR_DEVICE_TYPE_GEARBOX_DIE || |
592 | !gbox_num) |
593 | return 0; |
594 | |
595 | area->tz_gearbox_num = gbox_num; |
596 | area->tz_gearbox_arr = kcalloc(n: area->tz_gearbox_num, |
597 | size: sizeof(*area->tz_gearbox_arr), |
598 | GFP_KERNEL); |
599 | if (!area->tz_gearbox_arr) |
600 | return -ENOMEM; |
601 | |
602 | for (i = 0; i < area->tz_gearbox_num; i++) { |
603 | gearbox_tz = &area->tz_gearbox_arr[i]; |
604 | memcpy(gearbox_tz->trips, default_thermal_trips, |
605 | sizeof(thermal->trips)); |
606 | memcpy(gearbox_tz->cooling_states, default_cooling_states, |
607 | sizeof(thermal->cooling_states)); |
608 | gearbox_tz->module = i; |
609 | gearbox_tz->parent = thermal; |
610 | gearbox_tz->slot_index = area->slot_index; |
611 | err = mlxsw_thermal_gearbox_tz_init(gearbox_tz); |
612 | if (err) |
613 | goto err_thermal_gearbox_tz_init; |
614 | } |
615 | |
616 | return 0; |
617 | |
618 | err_thermal_gearbox_tz_init: |
619 | for (i--; i >= 0; i--) |
620 | mlxsw_thermal_gearbox_tz_fini(gearbox_tz: &area->tz_gearbox_arr[i]); |
621 | kfree(objp: area->tz_gearbox_arr); |
622 | return err; |
623 | } |
624 | |
625 | static void |
626 | mlxsw_thermal_gearboxes_fini(struct mlxsw_thermal *thermal, |
627 | struct mlxsw_thermal_area *area) |
628 | { |
629 | int i; |
630 | |
631 | for (i = area->tz_gearbox_num - 1; i >= 0; i--) |
632 | mlxsw_thermal_gearbox_tz_fini(gearbox_tz: &area->tz_gearbox_arr[i]); |
633 | kfree(objp: area->tz_gearbox_arr); |
634 | } |
635 | |
636 | static void |
637 | mlxsw_thermal_got_active(struct mlxsw_core *mlxsw_core, u8 slot_index, |
638 | void *priv) |
639 | { |
640 | struct mlxsw_thermal *thermal = priv; |
641 | struct mlxsw_thermal_area *linecard; |
642 | int err; |
643 | |
644 | linecard = &thermal->line_cards[slot_index]; |
645 | |
646 | if (linecard->active) |
647 | return; |
648 | |
649 | linecard->slot_index = slot_index; |
650 | err = mlxsw_thermal_modules_init(dev: thermal->bus_info->dev, core: thermal->core, |
651 | thermal, area: linecard); |
652 | if (err) { |
653 | dev_err(thermal->bus_info->dev, "Failed to configure thermal objects for line card modules in slot %d\n" , |
654 | slot_index); |
655 | return; |
656 | } |
657 | |
658 | err = mlxsw_thermal_gearboxes_init(dev: thermal->bus_info->dev, |
659 | core: thermal->core, thermal, area: linecard); |
660 | if (err) { |
661 | dev_err(thermal->bus_info->dev, "Failed to configure thermal objects for line card gearboxes in slot %d\n" , |
662 | slot_index); |
663 | goto err_thermal_linecard_gearboxes_init; |
664 | } |
665 | |
666 | linecard->active = true; |
667 | |
668 | return; |
669 | |
670 | err_thermal_linecard_gearboxes_init: |
671 | mlxsw_thermal_modules_fini(thermal, area: linecard); |
672 | } |
673 | |
674 | static void |
675 | mlxsw_thermal_got_inactive(struct mlxsw_core *mlxsw_core, u8 slot_index, |
676 | void *priv) |
677 | { |
678 | struct mlxsw_thermal *thermal = priv; |
679 | struct mlxsw_thermal_area *linecard; |
680 | |
681 | linecard = &thermal->line_cards[slot_index]; |
682 | if (!linecard->active) |
683 | return; |
684 | linecard->active = false; |
685 | mlxsw_thermal_gearboxes_fini(thermal, area: linecard); |
686 | mlxsw_thermal_modules_fini(thermal, area: linecard); |
687 | } |
688 | |
689 | static struct mlxsw_linecards_event_ops mlxsw_thermal_event_ops = { |
690 | .got_active = mlxsw_thermal_got_active, |
691 | .got_inactive = mlxsw_thermal_got_inactive, |
692 | }; |
693 | |
694 | int mlxsw_thermal_init(struct mlxsw_core *core, |
695 | const struct mlxsw_bus_info *bus_info, |
696 | struct mlxsw_thermal **p_thermal) |
697 | { |
698 | char mfcr_pl[MLXSW_REG_MFCR_LEN] = { 0 }; |
699 | enum mlxsw_reg_mfcr_pwm_frequency freq; |
700 | struct device *dev = bus_info->dev; |
701 | char mgpir_pl[MLXSW_REG_MGPIR_LEN]; |
702 | struct mlxsw_thermal *thermal; |
703 | u8 pwm_active, num_of_slots; |
704 | u16 tacho_active; |
705 | int err, i; |
706 | |
707 | mlxsw_reg_mgpir_pack(payload: mgpir_pl, slot_index: 0); |
708 | err = mlxsw_reg_query(mlxsw_core: core, MLXSW_REG(mgpir), payload: mgpir_pl); |
709 | if (err) |
710 | return err; |
711 | |
712 | mlxsw_reg_mgpir_unpack(payload: mgpir_pl, NULL, NULL, NULL, NULL, |
713 | num_of_slots: &num_of_slots); |
714 | |
715 | thermal = kzalloc(struct_size(thermal, line_cards, num_of_slots + 1), |
716 | GFP_KERNEL); |
717 | if (!thermal) |
718 | return -ENOMEM; |
719 | |
720 | thermal->core = core; |
721 | thermal->bus_info = bus_info; |
722 | memcpy(thermal->trips, default_thermal_trips, sizeof(thermal->trips)); |
723 | memcpy(thermal->cooling_states, default_cooling_states, sizeof(thermal->cooling_states)); |
724 | thermal->line_cards[0].slot_index = 0; |
725 | |
726 | err = mlxsw_reg_query(mlxsw_core: thermal->core, MLXSW_REG(mfcr), payload: mfcr_pl); |
727 | if (err) { |
728 | dev_err(dev, "Failed to probe PWMs\n" ); |
729 | goto err_reg_query; |
730 | } |
731 | mlxsw_reg_mfcr_unpack(payload: mfcr_pl, p_pwm_frequency: &freq, p_tacho_active: &tacho_active, p_pwm_active: &pwm_active); |
732 | |
733 | for (i = 0; i < MLXSW_MFCR_TACHOS_MAX; i++) { |
734 | if (tacho_active & BIT(i)) { |
735 | char mfsl_pl[MLXSW_REG_MFSL_LEN]; |
736 | |
737 | mlxsw_reg_mfsl_pack(payload: mfsl_pl, tacho: i, tach_min: 0, tach_max: 0); |
738 | |
739 | /* We need to query the register to preserve maximum */ |
740 | err = mlxsw_reg_query(mlxsw_core: thermal->core, MLXSW_REG(mfsl), |
741 | payload: mfsl_pl); |
742 | if (err) |
743 | goto err_reg_query; |
744 | |
745 | /* set the minimal RPMs to 0 */ |
746 | mlxsw_reg_mfsl_tach_min_set(buf: mfsl_pl, val: 0); |
747 | err = mlxsw_reg_write(mlxsw_core: thermal->core, MLXSW_REG(mfsl), |
748 | payload: mfsl_pl); |
749 | if (err) |
750 | goto err_reg_write; |
751 | } |
752 | } |
753 | for (i = 0; i < MLXSW_MFCR_PWMS_MAX; i++) { |
754 | if (pwm_active & BIT(i)) { |
755 | struct thermal_cooling_device *cdev; |
756 | |
757 | cdev = thermal_cooling_device_register("mlxsw_fan" , |
758 | thermal, |
759 | &mlxsw_cooling_ops); |
760 | if (IS_ERR(ptr: cdev)) { |
761 | err = PTR_ERR(ptr: cdev); |
762 | dev_err(dev, "Failed to register cooling device\n" ); |
763 | goto err_thermal_cooling_device_register; |
764 | } |
765 | thermal->cdevs[i] = cdev; |
766 | } |
767 | } |
768 | |
769 | thermal->polling_delay = bus_info->low_frequency ? |
770 | MLXSW_THERMAL_SLOW_POLL_INT : |
771 | MLXSW_THERMAL_POLL_INT; |
772 | |
773 | thermal->tzdev = thermal_zone_device_register_with_trips(type: "mlxsw" , |
774 | trips: thermal->trips, |
775 | MLXSW_THERMAL_NUM_TRIPS, |
776 | MLXSW_THERMAL_TRIP_MASK, |
777 | devdata: thermal, |
778 | ops: &mlxsw_thermal_ops, |
779 | tzp: &mlxsw_thermal_params, passive_delay: 0, |
780 | polling_delay: thermal->polling_delay); |
781 | if (IS_ERR(ptr: thermal->tzdev)) { |
782 | err = PTR_ERR(ptr: thermal->tzdev); |
783 | dev_err(dev, "Failed to register thermal zone\n" ); |
784 | goto err_thermal_zone_device_register; |
785 | } |
786 | |
787 | err = mlxsw_thermal_modules_init(dev, core, thermal, |
788 | area: &thermal->line_cards[0]); |
789 | if (err) |
790 | goto err_thermal_modules_init; |
791 | |
792 | err = mlxsw_thermal_gearboxes_init(dev, core, thermal, |
793 | area: &thermal->line_cards[0]); |
794 | if (err) |
795 | goto err_thermal_gearboxes_init; |
796 | |
797 | err = mlxsw_linecards_event_ops_register(mlxsw_core: core, |
798 | ops: &mlxsw_thermal_event_ops, |
799 | priv: thermal); |
800 | if (err) |
801 | goto err_linecards_event_ops_register; |
802 | |
803 | err = thermal_zone_device_enable(tz: thermal->tzdev); |
804 | if (err) |
805 | goto err_thermal_zone_device_enable; |
806 | |
807 | thermal->line_cards[0].active = true; |
808 | *p_thermal = thermal; |
809 | return 0; |
810 | |
811 | err_thermal_zone_device_enable: |
812 | mlxsw_linecards_event_ops_unregister(mlxsw_core: thermal->core, |
813 | ops: &mlxsw_thermal_event_ops, |
814 | priv: thermal); |
815 | err_linecards_event_ops_register: |
816 | mlxsw_thermal_gearboxes_fini(thermal, area: &thermal->line_cards[0]); |
817 | err_thermal_gearboxes_init: |
818 | mlxsw_thermal_modules_fini(thermal, area: &thermal->line_cards[0]); |
819 | err_thermal_modules_init: |
820 | if (thermal->tzdev) { |
821 | thermal_zone_device_unregister(tz: thermal->tzdev); |
822 | thermal->tzdev = NULL; |
823 | } |
824 | err_thermal_zone_device_register: |
825 | err_thermal_cooling_device_register: |
826 | for (i = 0; i < MLXSW_MFCR_PWMS_MAX; i++) |
827 | if (thermal->cdevs[i]) |
828 | thermal_cooling_device_unregister(thermal->cdevs[i]); |
829 | err_reg_write: |
830 | err_reg_query: |
831 | kfree(objp: thermal); |
832 | return err; |
833 | } |
834 | |
835 | void mlxsw_thermal_fini(struct mlxsw_thermal *thermal) |
836 | { |
837 | int i; |
838 | |
839 | thermal->line_cards[0].active = false; |
840 | mlxsw_linecards_event_ops_unregister(mlxsw_core: thermal->core, |
841 | ops: &mlxsw_thermal_event_ops, |
842 | priv: thermal); |
843 | mlxsw_thermal_gearboxes_fini(thermal, area: &thermal->line_cards[0]); |
844 | mlxsw_thermal_modules_fini(thermal, area: &thermal->line_cards[0]); |
845 | if (thermal->tzdev) { |
846 | thermal_zone_device_unregister(tz: thermal->tzdev); |
847 | thermal->tzdev = NULL; |
848 | } |
849 | |
850 | for (i = 0; i < MLXSW_MFCR_PWMS_MAX; i++) { |
851 | if (thermal->cdevs[i]) { |
852 | thermal_cooling_device_unregister(thermal->cdevs[i]); |
853 | thermal->cdevs[i] = NULL; |
854 | } |
855 | } |
856 | |
857 | kfree(objp: thermal); |
858 | } |
859 | |