// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 /* Copyright (c) 2016-2018 Mellanox Technologies. All rights reserved * Copyright (c) 2016 Ivan Vecera */ #include #include #include #include #include #include #include #include "core.h" #include "core_env.h" #define MLXSW_THERMAL_POLL_INT 1000 /* ms */ #define MLXSW_THERMAL_SLOW_POLL_INT 20000 /* ms */ #define MLXSW_THERMAL_ASIC_TEMP_NORM 75000 /* 75C */ #define MLXSW_THERMAL_ASIC_TEMP_HIGH 85000 /* 85C */ #define MLXSW_THERMAL_ASIC_TEMP_HOT 105000 /* 105C */ #define MLXSW_THERMAL_MODULE_TEMP_NORM 55000 /* 55C */ #define MLXSW_THERMAL_MODULE_TEMP_HIGH 65000 /* 65C */ #define MLXSW_THERMAL_MODULE_TEMP_HOT 80000 /* 80C */ #define MLXSW_THERMAL_HYSTERESIS_TEMP 5000 /* 5C */ #define MLXSW_THERMAL_MODULE_TEMP_SHIFT (MLXSW_THERMAL_HYSTERESIS_TEMP * 2) #define MLXSW_THERMAL_MAX_STATE 10 #define MLXSW_THERMAL_MIN_STATE 2 #define MLXSW_THERMAL_MAX_DUTY 255 /* External cooling devices, allowed for binding to mlxsw thermal zones. */ static char * const mlxsw_thermal_external_allowed_cdev[] = { "mlxreg_fan", }; struct mlxsw_cooling_states { int min_state; int max_state; }; static const struct thermal_trip default_thermal_trips[] = { { /* In range - 0-40% PWM */ .type = THERMAL_TRIP_ACTIVE, .temperature = MLXSW_THERMAL_ASIC_TEMP_NORM, .hysteresis = MLXSW_THERMAL_HYSTERESIS_TEMP, }, { /* In range - 40-100% PWM */ .type = THERMAL_TRIP_ACTIVE, .temperature = MLXSW_THERMAL_ASIC_TEMP_HIGH, .hysteresis = MLXSW_THERMAL_HYSTERESIS_TEMP, }, { /* Warning */ .type = THERMAL_TRIP_HOT, .temperature = MLXSW_THERMAL_ASIC_TEMP_HOT, }, }; static const struct thermal_trip default_thermal_module_trips[] = { { /* In range - 0-40% PWM */ .type = THERMAL_TRIP_ACTIVE, .temperature = MLXSW_THERMAL_MODULE_TEMP_NORM, .hysteresis = MLXSW_THERMAL_HYSTERESIS_TEMP, }, { /* In range - 40-100% PWM */ .type = THERMAL_TRIP_ACTIVE, .temperature = MLXSW_THERMAL_MODULE_TEMP_HIGH, .hysteresis = MLXSW_THERMAL_HYSTERESIS_TEMP, }, { /* Warning */ .type = THERMAL_TRIP_HOT, .temperature = MLXSW_THERMAL_MODULE_TEMP_HOT, }, }; static const struct mlxsw_cooling_states default_cooling_states[] = { { .min_state = 0, .max_state = (4 * MLXSW_THERMAL_MAX_STATE) / 10, }, { .min_state = (4 * MLXSW_THERMAL_MAX_STATE) / 10, .max_state = MLXSW_THERMAL_MAX_STATE, }, { .min_state = MLXSW_THERMAL_MAX_STATE, .max_state = MLXSW_THERMAL_MAX_STATE, }, }; #define MLXSW_THERMAL_NUM_TRIPS ARRAY_SIZE(default_thermal_trips) /* Make sure all trips are writable */ #define MLXSW_THERMAL_TRIP_MASK (BIT(MLXSW_THERMAL_NUM_TRIPS) - 1) struct mlxsw_thermal; struct mlxsw_thermal_module { struct mlxsw_thermal *parent; struct thermal_zone_device *tzdev; struct thermal_trip trips[MLXSW_THERMAL_NUM_TRIPS]; struct mlxsw_cooling_states cooling_states[MLXSW_THERMAL_NUM_TRIPS]; int module; /* Module or gearbox number */ u8 slot_index; }; struct mlxsw_thermal_area { struct mlxsw_thermal_module *tz_module_arr; u8 tz_module_num; struct mlxsw_thermal_module *tz_gearbox_arr; u8 tz_gearbox_num; u8 slot_index; bool active; }; struct mlxsw_thermal { struct mlxsw_core *core; const struct mlxsw_bus_info *bus_info; struct thermal_zone_device *tzdev; int polling_delay; struct thermal_cooling_device *cdevs[MLXSW_MFCR_PWMS_MAX]; struct thermal_trip trips[MLXSW_THERMAL_NUM_TRIPS]; struct mlxsw_cooling_states cooling_states[MLXSW_THERMAL_NUM_TRIPS]; struct mlxsw_thermal_area line_cards[]; }; static inline u8 mlxsw_state_to_duty(int state) { return DIV_ROUND_CLOSEST(state * MLXSW_THERMAL_MAX_DUTY, MLXSW_THERMAL_MAX_STATE); } static inline int mlxsw_duty_to_state(u8 duty) { return DIV_ROUND_CLOSEST(duty * MLXSW_THERMAL_MAX_STATE, MLXSW_THERMAL_MAX_DUTY); } static int mlxsw_get_cooling_device_idx(struct mlxsw_thermal *thermal, struct thermal_cooling_device *cdev) { int i; for (i = 0; i < MLXSW_MFCR_PWMS_MAX; i++) if (thermal->cdevs[i] == cdev) return i; /* Allow mlxsw thermal zone binding to an external cooling device */ for (i = 0; i < ARRAY_SIZE(mlxsw_thermal_external_allowed_cdev); i++) { if (!strcmp(cdev->type, mlxsw_thermal_external_allowed_cdev[i])) return 0; } return -ENODEV; } static int mlxsw_thermal_bind(struct thermal_zone_device *tzdev, struct thermal_cooling_device *cdev) { struct mlxsw_thermal *thermal = thermal_zone_device_priv(tzdev); struct device *dev = thermal->bus_info->dev; int i, err; /* If the cooling device is one of ours bind it */ if (mlxsw_get_cooling_device_idx(thermal, cdev) < 0) return 0; for (i = 0; i < MLXSW_THERMAL_NUM_TRIPS; i++) { const struct mlxsw_cooling_states *state = &thermal->cooling_states[i]; err = thermal_zone_bind_cooling_device(tzdev, i, cdev, state->max_state, state->min_state, THERMAL_WEIGHT_DEFAULT); if (err < 0) { dev_err(dev, "Failed to bind cooling device to trip %d\n", i); return err; } } return 0; } static int mlxsw_thermal_unbind(struct thermal_zone_device *tzdev, struct thermal_cooling_device *cdev) { struct mlxsw_thermal *thermal = thermal_zone_device_priv(tzdev); struct device *dev = thermal->bus_info->dev; int i; int err; /* If the cooling device is our one unbind it */ if (mlxsw_get_cooling_device_idx(thermal, cdev) < 0) return 0; for (i = 0; i < MLXSW_THERMAL_NUM_TRIPS; i++) { err = thermal_zone_unbind_cooling_device(tzdev, i, cdev); if (err < 0) { dev_err(dev, "Failed to unbind cooling device\n"); return err; } } return 0; } static int mlxsw_thermal_get_temp(struct thermal_zone_device *tzdev, int *p_temp) { struct mlxsw_thermal *thermal = thermal_zone_device_priv(tzdev); struct device *dev = thermal->bus_info->dev; char mtmp_pl[MLXSW_REG_MTMP_LEN]; int temp; int err; mlxsw_reg_mtmp_pack(mtmp_pl, 0, 0, false, false); err = mlxsw_reg_query(thermal->core, MLXSW_REG(mtmp), mtmp_pl); if (err) { dev_err(dev, "Failed to query temp sensor\n"); return err; } mlxsw_reg_mtmp_unpack(mtmp_pl, &temp, NULL, NULL, NULL, NULL); *p_temp = temp; return 0; } static struct thermal_zone_params mlxsw_thermal_params = { .no_hwmon = true, }; static struct thermal_zone_device_ops mlxsw_thermal_ops = { .bind = mlxsw_thermal_bind, .unbind = mlxsw_thermal_unbind, .get_temp = mlxsw_thermal_get_temp, }; static int mlxsw_thermal_module_bind(struct thermal_zone_device *tzdev, struct thermal_cooling_device *cdev) { struct mlxsw_thermal_module *tz = thermal_zone_device_priv(tzdev); struct mlxsw_thermal *thermal = tz->parent; int i, j, err; /* If the cooling device is one of ours bind it */ if (mlxsw_get_cooling_device_idx(thermal, cdev) < 0) return 0; for (i = 0; i < MLXSW_THERMAL_NUM_TRIPS; i++) { const struct mlxsw_cooling_states *state = &tz->cooling_states[i]; err = thermal_zone_bind_cooling_device(tzdev, i, cdev, state->max_state, state->min_state, THERMAL_WEIGHT_DEFAULT); if (err < 0) goto err_thermal_zone_bind_cooling_device; } return 0; err_thermal_zone_bind_cooling_device: for (j = i - 1; j >= 0; j--) thermal_zone_unbind_cooling_device(tzdev, j, cdev); return err; } static int mlxsw_thermal_module_unbind(struct thermal_zone_device *tzdev, struct thermal_cooling_device *cdev) { struct mlxsw_thermal_module *tz = thermal_zone_device_priv(tzdev); struct mlxsw_thermal *thermal = tz->parent; int i; int err; /* If the cooling device is one of ours unbind it */ if (mlxsw_get_cooling_device_idx(thermal, cdev) < 0) return 0; for (i = 0; i < MLXSW_THERMAL_NUM_TRIPS; i++) { err = thermal_zone_unbind_cooling_device(tzdev, i, cdev); WARN_ON(err); } return err; } static int mlxsw_thermal_module_temp_get(struct thermal_zone_device *tzdev, int *p_temp) { struct mlxsw_thermal_module *tz = thermal_zone_device_priv(tzdev); struct mlxsw_thermal *thermal = tz->parent; char mtmp_pl[MLXSW_REG_MTMP_LEN]; u16 sensor_index; int err; sensor_index = MLXSW_REG_MTMP_MODULE_INDEX_MIN + tz->module; mlxsw_reg_mtmp_pack(mtmp_pl, tz->slot_index, sensor_index, false, false); err = mlxsw_reg_query(thermal->core, MLXSW_REG(mtmp), mtmp_pl); if (err) return err; mlxsw_reg_mtmp_unpack(mtmp_pl, p_temp, NULL, NULL, NULL, NULL); return 0; } static struct thermal_zone_device_ops mlxsw_thermal_module_ops = { .bind = mlxsw_thermal_module_bind, .unbind = mlxsw_thermal_module_unbind, .get_temp = mlxsw_thermal_module_temp_get, }; static int mlxsw_thermal_gearbox_temp_get(struct thermal_zone_device *tzdev, int *p_temp) { struct mlxsw_thermal_module *tz = thermal_zone_device_priv(tzdev); struct mlxsw_thermal *thermal = tz->parent; char mtmp_pl[MLXSW_REG_MTMP_LEN]; u16 index; int temp; int err; index = MLXSW_REG_MTMP_GBOX_INDEX_MIN + tz->module; mlxsw_reg_mtmp_pack(mtmp_pl, tz->slot_index, index, false, false); err = mlxsw_reg_query(thermal->core, MLXSW_REG(mtmp), mtmp_pl); if (err) return err; mlxsw_reg_mtmp_unpack(mtmp_pl, &temp, NULL, NULL, NULL, NULL); *p_temp = temp; return 0; } static struct thermal_zone_device_ops mlxsw_thermal_gearbox_ops = { .bind = mlxsw_thermal_module_bind, .unbind = mlxsw_thermal_module_unbind, .get_temp = mlxsw_thermal_gearbox_temp_get, }; static int mlxsw_thermal_get_max_state(struct thermal_cooling_device *cdev, unsigned long *p_state) { *p_state = MLXSW_THERMAL_MAX_STATE; return 0; } static int mlxsw_thermal_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *p_state) { struct mlxsw_thermal *thermal = cdev->devdata; struct device *dev = thermal->bus_info->dev; char mfsc_pl[MLXSW_REG_MFSC_LEN]; int err, idx; u8 duty; idx = mlxsw_get_cooling_device_idx(thermal, cdev); if (idx < 0) return idx; mlxsw_reg_mfsc_pack(mfsc_pl, idx, 0); err = mlxsw_reg_query(thermal->core, MLXSW_REG(mfsc), mfsc_pl); if (err) { dev_err(dev, "Failed to query PWM duty\n"); return err; } duty = mlxsw_reg_mfsc_pwm_duty_cycle_get(mfsc_pl); *p_state = mlxsw_duty_to_state(duty); return 0; } static int mlxsw_thermal_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) { struct mlxsw_thermal *thermal = cdev->devdata; struct device *dev = thermal->bus_info->dev; char mfsc_pl[MLXSW_REG_MFSC_LEN]; int idx; int err; if (state > MLXSW_THERMAL_MAX_STATE) return -EINVAL; idx = mlxsw_get_cooling_device_idx(thermal, cdev); if (idx < 0) return idx; /* Normalize the state to the valid speed range. */ state = max_t(unsigned long, MLXSW_THERMAL_MIN_STATE, state); mlxsw_reg_mfsc_pack(mfsc_pl, idx, mlxsw_state_to_duty(state)); err = mlxsw_reg_write(thermal->core, MLXSW_REG(mfsc), mfsc_pl); if (err) { dev_err(dev, "Failed to write PWM duty\n"); return err; } return 0; } static const struct thermal_cooling_device_ops mlxsw_cooling_ops = { .get_max_state = mlxsw_thermal_get_max_state, .get_cur_state = mlxsw_thermal_get_cur_state, .set_cur_state = mlxsw_thermal_set_cur_state, }; static int mlxsw_thermal_module_tz_init(struct mlxsw_thermal_module *module_tz) { char tz_name[THERMAL_NAME_LENGTH]; int err; if (module_tz->slot_index) snprintf(tz_name, sizeof(tz_name), "mlxsw-lc%d-module%d", module_tz->slot_index, module_tz->module + 1); else snprintf(tz_name, sizeof(tz_name), "mlxsw-module%d", module_tz->module + 1); module_tz->tzdev = thermal_zone_device_register_with_trips(tz_name, module_tz->trips, MLXSW_THERMAL_NUM_TRIPS, MLXSW_THERMAL_TRIP_MASK, module_tz, &mlxsw_thermal_module_ops, &mlxsw_thermal_params, 0, module_tz->parent->polling_delay); if (IS_ERR(module_tz->tzdev)) { err = PTR_ERR(module_tz->tzdev); return err; } err = thermal_zone_device_enable(module_tz->tzdev); if (err) thermal_zone_device_unregister(module_tz->tzdev); return err; } static void mlxsw_thermal_module_tz_fini(struct thermal_zone_device *tzdev) { thermal_zone_device_unregister(tzdev); } static void mlxsw_thermal_module_init(struct device *dev, struct mlxsw_core *core, struct mlxsw_thermal *thermal, struct mlxsw_thermal_area *area, u8 module) { struct mlxsw_thermal_module *module_tz; module_tz = &area->tz_module_arr[module]; /* Skip if parent is already set (case of port split). */ if (module_tz->parent) return; module_tz->module = module; module_tz->slot_index = area->slot_index; module_tz->parent = thermal; BUILD_BUG_ON(ARRAY_SIZE(default_thermal_module_trips) != MLXSW_THERMAL_NUM_TRIPS); memcpy(module_tz->trips, default_thermal_module_trips, sizeof(thermal->trips)); memcpy(module_tz->cooling_states, default_cooling_states, sizeof(thermal->cooling_states)); } static void mlxsw_thermal_module_fini(struct mlxsw_thermal_module *module_tz) { if (module_tz && module_tz->tzdev) { mlxsw_thermal_module_tz_fini(module_tz->tzdev); module_tz->tzdev = NULL; module_tz->parent = NULL; } } static int mlxsw_thermal_modules_init(struct device *dev, struct mlxsw_core *core, struct mlxsw_thermal *thermal, struct mlxsw_thermal_area *area) { struct mlxsw_thermal_module *module_tz; char mgpir_pl[MLXSW_REG_MGPIR_LEN]; int i, err; mlxsw_reg_mgpir_pack(mgpir_pl, area->slot_index); err = mlxsw_reg_query(core, MLXSW_REG(mgpir), mgpir_pl); if (err) return err; mlxsw_reg_mgpir_unpack(mgpir_pl, NULL, NULL, NULL, &area->tz_module_num, NULL); /* For modular system module counter could be zero. */ if (!area->tz_module_num) return 0; area->tz_module_arr = kcalloc(area->tz_module_num, sizeof(*area->tz_module_arr), GFP_KERNEL); if (!area->tz_module_arr) return -ENOMEM; for (i = 0; i < area->tz_module_num; i++) mlxsw_thermal_module_init(dev, core, thermal, area, i); for (i = 0; i < area->tz_module_num; i++) { module_tz = &area->tz_module_arr[i]; if (!module_tz->parent) continue; err = mlxsw_thermal_module_tz_init(module_tz); if (err) goto err_thermal_module_tz_init; } return 0; err_thermal_module_tz_init: for (i = area->tz_module_num - 1; i >= 0; i--) mlxsw_thermal_module_fini(&area->tz_module_arr[i]); kfree(area->tz_module_arr); return err; } static void mlxsw_thermal_modules_fini(struct mlxsw_thermal *thermal, struct mlxsw_thermal_area *area) { int i; for (i = area->tz_module_num - 1; i >= 0; i--) mlxsw_thermal_module_fini(&area->tz_module_arr[i]); kfree(area->tz_module_arr); } static int mlxsw_thermal_gearbox_tz_init(struct mlxsw_thermal_module *gearbox_tz) { char tz_name[THERMAL_NAME_LENGTH]; int ret; if (gearbox_tz->slot_index) snprintf(tz_name, sizeof(tz_name), "mlxsw-lc%d-gearbox%d", gearbox_tz->slot_index, gearbox_tz->module + 1); else snprintf(tz_name, sizeof(tz_name), "mlxsw-gearbox%d", gearbox_tz->module + 1); gearbox_tz->tzdev = thermal_zone_device_register_with_trips(tz_name, gearbox_tz->trips, MLXSW_THERMAL_NUM_TRIPS, MLXSW_THERMAL_TRIP_MASK, gearbox_tz, &mlxsw_thermal_gearbox_ops, &mlxsw_thermal_params, 0, gearbox_tz->parent->polling_delay); if (IS_ERR(gearbox_tz->tzdev)) return PTR_ERR(gearbox_tz->tzdev); ret = thermal_zone_device_enable(gearbox_tz->tzdev); if (ret) thermal_zone_device_unregister(gearbox_tz->tzdev); return ret; } static void mlxsw_thermal_gearbox_tz_fini(struct mlxsw_thermal_module *gearbox_tz) { thermal_zone_device_unregister(gearbox_tz->tzdev); } static int mlxsw_thermal_gearboxes_init(struct device *dev, struct mlxsw_core *core, struct mlxsw_thermal *thermal, struct mlxsw_thermal_area *area) { enum mlxsw_reg_mgpir_device_type device_type; struct mlxsw_thermal_module *gearbox_tz; char mgpir_pl[MLXSW_REG_MGPIR_LEN]; u8 gbox_num; int i; int err; mlxsw_reg_mgpir_pack(mgpir_pl, area->slot_index); err = mlxsw_reg_query(core, MLXSW_REG(mgpir), mgpir_pl); if (err) return err; mlxsw_reg_mgpir_unpack(mgpir_pl, &gbox_num, &device_type, NULL, NULL, NULL); if (device_type != MLXSW_REG_MGPIR_DEVICE_TYPE_GEARBOX_DIE || !gbox_num) return 0; area->tz_gearbox_num = gbox_num; area->tz_gearbox_arr = kcalloc(area->tz_gearbox_num, sizeof(*area->tz_gearbox_arr), GFP_KERNEL); if (!area->tz_gearbox_arr) return -ENOMEM; for (i = 0; i < area->tz_gearbox_num; i++) { gearbox_tz = &area->tz_gearbox_arr[i]; memcpy(gearbox_tz->trips, default_thermal_trips, sizeof(thermal->trips)); memcpy(gearbox_tz->cooling_states, default_cooling_states, sizeof(thermal->cooling_states)); gearbox_tz->module = i; gearbox_tz->parent = thermal; gearbox_tz->slot_index = area->slot_index; err = mlxsw_thermal_gearbox_tz_init(gearbox_tz); if (err) goto err_thermal_gearbox_tz_init; } return 0; err_thermal_gearbox_tz_init: for (i--; i >= 0; i--) mlxsw_thermal_gearbox_tz_fini(&area->tz_gearbox_arr[i]); kfree(area->tz_gearbox_arr); return err; } static void mlxsw_thermal_gearboxes_fini(struct mlxsw_thermal *thermal, struct mlxsw_thermal_area *area) { int i; for (i = area->tz_gearbox_num - 1; i >= 0; i--) mlxsw_thermal_gearbox_tz_fini(&area->tz_gearbox_arr[i]); kfree(area->tz_gearbox_arr); } static void mlxsw_thermal_got_active(struct mlxsw_core *mlxsw_core, u8 slot_index, void *priv) { struct mlxsw_thermal *thermal = priv; struct mlxsw_thermal_area *linecard; int err; linecard = &thermal->line_cards[slot_index]; if (linecard->active) return; linecard->slot_index = slot_index; err = mlxsw_thermal_modules_init(thermal->bus_info->dev, thermal->core, thermal, linecard); if (err) { dev_err(thermal->bus_info->dev, "Failed to configure thermal objects for line card modules in slot %d\n", slot_index); return; } err = mlxsw_thermal_gearboxes_init(thermal->bus_info->dev, thermal->core, thermal, linecard); if (err) { dev_err(thermal->bus_info->dev, "Failed to configure thermal objects for line card gearboxes in slot %d\n", slot_index); goto err_thermal_linecard_gearboxes_init; } linecard->active = true; return; err_thermal_linecard_gearboxes_init: mlxsw_thermal_modules_fini(thermal, linecard); } static void mlxsw_thermal_got_inactive(struct mlxsw_core *mlxsw_core, u8 slot_index, void *priv) { struct mlxsw_thermal *thermal = priv; struct mlxsw_thermal_area *linecard; linecard = &thermal->line_cards[slot_index]; if (!linecard->active) return; linecard->active = false; mlxsw_thermal_gearboxes_fini(thermal, linecard); mlxsw_thermal_modules_fini(thermal, linecard); } static struct mlxsw_linecards_event_ops mlxsw_thermal_event_ops = { .got_active = mlxsw_thermal_got_active, .got_inactive = mlxsw_thermal_got_inactive, }; int mlxsw_thermal_init(struct mlxsw_core *core, const struct mlxsw_bus_info *bus_info, struct mlxsw_thermal **p_thermal) { char mfcr_pl[MLXSW_REG_MFCR_LEN] = { 0 }; enum mlxsw_reg_mfcr_pwm_frequency freq; struct device *dev = bus_info->dev; char mgpir_pl[MLXSW_REG_MGPIR_LEN]; struct mlxsw_thermal *thermal; u8 pwm_active, num_of_slots; u16 tacho_active; int err, i; mlxsw_reg_mgpir_pack(mgpir_pl, 0); err = mlxsw_reg_query(core, MLXSW_REG(mgpir), mgpir_pl); if (err) return err; mlxsw_reg_mgpir_unpack(mgpir_pl, NULL, NULL, NULL, NULL, &num_of_slots); thermal = kzalloc(struct_size(thermal, line_cards, num_of_slots + 1), GFP_KERNEL); if (!thermal) return -ENOMEM; thermal->core = core; thermal->bus_info = bus_info; memcpy(thermal->trips, default_thermal_trips, sizeof(thermal->trips)); memcpy(thermal->cooling_states, default_cooling_states, sizeof(thermal->cooling_states)); thermal->line_cards[0].slot_index = 0; err = mlxsw_reg_query(thermal->core, MLXSW_REG(mfcr), mfcr_pl); if (err) { dev_err(dev, "Failed to probe PWMs\n"); goto err_reg_query; } mlxsw_reg_mfcr_unpack(mfcr_pl, &freq, &tacho_active, &pwm_active); for (i = 0; i < MLXSW_MFCR_TACHOS_MAX; i++) { if (tacho_active & BIT(i)) { char mfsl_pl[MLXSW_REG_MFSL_LEN]; mlxsw_reg_mfsl_pack(mfsl_pl, i, 0, 0); /* We need to query the register to preserve maximum */ err = mlxsw_reg_query(thermal->core, MLXSW_REG(mfsl), mfsl_pl); if (err) goto err_reg_query; /* set the minimal RPMs to 0 */ mlxsw_reg_mfsl_tach_min_set(mfsl_pl, 0); err = mlxsw_reg_write(thermal->core, MLXSW_REG(mfsl), mfsl_pl); if (err) goto err_reg_write; } } for (i = 0; i < MLXSW_MFCR_PWMS_MAX; i++) { if (pwm_active & BIT(i)) { struct thermal_cooling_device *cdev; cdev = thermal_cooling_device_register("mlxsw_fan", thermal, &mlxsw_cooling_ops); if (IS_ERR(cdev)) { err = PTR_ERR(cdev); dev_err(dev, "Failed to register cooling device\n"); goto err_thermal_cooling_device_register; } thermal->cdevs[i] = cdev; } } thermal->polling_delay = bus_info->low_frequency ? MLXSW_THERMAL_SLOW_POLL_INT : MLXSW_THERMAL_POLL_INT; thermal->tzdev = thermal_zone_device_register_with_trips("mlxsw", thermal->trips, MLXSW_THERMAL_NUM_TRIPS, MLXSW_THERMAL_TRIP_MASK, thermal, &mlxsw_thermal_ops, &mlxsw_thermal_params, 0, thermal->polling_delay); if (IS_ERR(thermal->tzdev)) { err = PTR_ERR(thermal->tzdev); dev_err(dev, "Failed to register thermal zone\n"); goto err_thermal_zone_device_register; } err = mlxsw_thermal_modules_init(dev, core, thermal, &thermal->line_cards[0]); if (err) goto err_thermal_modules_init; err = mlxsw_thermal_gearboxes_init(dev, core, thermal, &thermal->line_cards[0]); if (err) goto err_thermal_gearboxes_init; err = mlxsw_linecards_event_ops_register(core, &mlxsw_thermal_event_ops, thermal); if (err) goto err_linecards_event_ops_register; err = thermal_zone_device_enable(thermal->tzdev); if (err) goto err_thermal_zone_device_enable; thermal->line_cards[0].active = true; *p_thermal = thermal; return 0; err_thermal_zone_device_enable: mlxsw_linecards_event_ops_unregister(thermal->core, &mlxsw_thermal_event_ops, thermal); err_linecards_event_ops_register: mlxsw_thermal_gearboxes_fini(thermal, &thermal->line_cards[0]); err_thermal_gearboxes_init: mlxsw_thermal_modules_fini(thermal, &thermal->line_cards[0]); err_thermal_modules_init: if (thermal->tzdev) { thermal_zone_device_unregister(thermal->tzdev); thermal->tzdev = NULL; } err_thermal_zone_device_register: err_thermal_cooling_device_register: for (i = 0; i < MLXSW_MFCR_PWMS_MAX; i++) if (thermal->cdevs[i]) thermal_cooling_device_unregister(thermal->cdevs[i]); err_reg_write: err_reg_query: kfree(thermal); return err; } void mlxsw_thermal_fini(struct mlxsw_thermal *thermal) { int i; thermal->line_cards[0].active = false; mlxsw_linecards_event_ops_unregister(thermal->core, &mlxsw_thermal_event_ops, thermal); mlxsw_thermal_gearboxes_fini(thermal, &thermal->line_cards[0]); mlxsw_thermal_modules_fini(thermal, &thermal->line_cards[0]); if (thermal->tzdev) { thermal_zone_device_unregister(thermal->tzdev); thermal->tzdev = NULL; } for (i = 0; i < MLXSW_MFCR_PWMS_MAX; i++) { if (thermal->cdevs[i]) { thermal_cooling_device_unregister(thermal->cdevs[i]); thermal->cdevs[i] = NULL; } } kfree(thermal); }