| // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 |
| /* Copyright (c) 2016-2018 Mellanox Technologies. All rights reserved |
| * Copyright (c) 2016 Ivan Vecera <cera@cera.cz> |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/types.h> |
| #include <linux/device.h> |
| #include <linux/sysfs.h> |
| #include <linux/thermal.h> |
| #include <linux/err.h> |
| #include <linux/sfp.h> |
| |
| #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_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", |
| }; |
| |
| enum mlxsw_thermal_trips { |
| MLXSW_THERMAL_TEMP_TRIP_NORM, |
| MLXSW_THERMAL_TEMP_TRIP_HIGH, |
| MLXSW_THERMAL_TEMP_TRIP_HOT, |
| }; |
| |
| 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 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 void |
| mlxsw_thermal_module_trips_reset(struct mlxsw_thermal_module *tz) |
| { |
| tz->trips[MLXSW_THERMAL_TEMP_TRIP_NORM].temperature = 0; |
| tz->trips[MLXSW_THERMAL_TEMP_TRIP_HIGH].temperature = 0; |
| tz->trips[MLXSW_THERMAL_TEMP_TRIP_HOT].temperature = 0; |
| } |
| |
| static int |
| mlxsw_thermal_module_trips_update(struct device *dev, struct mlxsw_core *core, |
| struct mlxsw_thermal_module *tz, |
| int crit_temp, int emerg_temp) |
| { |
| int err; |
| |
| /* Do not try to query temperature thresholds directly from the module's |
| * EEPROM if we got valid thresholds from MTMP. |
| */ |
| if (!emerg_temp || !crit_temp) { |
| err = mlxsw_env_module_temp_thresholds_get(core, tz->slot_index, |
| tz->module, |
| SFP_TEMP_HIGH_WARN, |
| &crit_temp); |
| if (err) |
| return err; |
| |
| err = mlxsw_env_module_temp_thresholds_get(core, tz->slot_index, |
| tz->module, |
| SFP_TEMP_HIGH_ALARM, |
| &emerg_temp); |
| if (err) |
| return err; |
| } |
| |
| if (crit_temp > emerg_temp) { |
| dev_warn(dev, "%s : Critical threshold %d is above emergency threshold %d\n", |
| tz->tzdev->type, crit_temp, emerg_temp); |
| return 0; |
| } |
| |
| /* According to the system thermal requirements, the thermal zones are |
| * defined with three trip points. The critical and emergency |
| * temperature thresholds, provided by QSFP module are set as "active" |
| * and "hot" trip points, "normal" trip point is derived from "active" |
| * by subtracting double hysteresis value. |
| */ |
| if (crit_temp >= MLXSW_THERMAL_MODULE_TEMP_SHIFT) |
| tz->trips[MLXSW_THERMAL_TEMP_TRIP_NORM].temperature = crit_temp - |
| MLXSW_THERMAL_MODULE_TEMP_SHIFT; |
| else |
| tz->trips[MLXSW_THERMAL_TEMP_TRIP_NORM].temperature = crit_temp; |
| tz->trips[MLXSW_THERMAL_TEMP_TRIP_HIGH].temperature = crit_temp; |
| tz->trips[MLXSW_THERMAL_TEMP_TRIP_HOT].temperature = emerg_temp; |
| |
| return 0; |
| } |
| |
| static int mlxsw_thermal_bind(struct thermal_zone_device *tzdev, |
| struct thermal_cooling_device *cdev) |
| { |
| struct mlxsw_thermal *thermal = tzdev->devdata; |
| 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 = tzdev->devdata; |
| 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 = tzdev->devdata; |
| 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 = tzdev->devdata; |
| 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 = tzdev->devdata; |
| 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 void |
| mlxsw_thermal_module_temp_and_thresholds_get(struct mlxsw_core *core, |
| u8 slot_index, u16 sensor_index, |
| int *p_temp, int *p_crit_temp, |
| int *p_emerg_temp) |
| { |
| char mtmp_pl[MLXSW_REG_MTMP_LEN]; |
| int err; |
| |
| /* Read module temperature and thresholds. */ |
| mlxsw_reg_mtmp_pack(mtmp_pl, slot_index, sensor_index, |
| false, false); |
| err = mlxsw_reg_query(core, MLXSW_REG(mtmp), mtmp_pl); |
| if (err) { |
| /* Set temperature and thresholds to zero to avoid passing |
| * uninitialized data back to the caller. |
| */ |
| *p_temp = 0; |
| *p_crit_temp = 0; |
| *p_emerg_temp = 0; |
| |
| return; |
| } |
| mlxsw_reg_mtmp_unpack(mtmp_pl, p_temp, NULL, p_crit_temp, p_emerg_temp, |
| NULL); |
| } |
| |
| static int mlxsw_thermal_module_temp_get(struct thermal_zone_device *tzdev, |
| int *p_temp) |
| { |
| struct mlxsw_thermal_module *tz = tzdev->devdata; |
| struct mlxsw_thermal *thermal = tz->parent; |
| int temp, crit_temp, emerg_temp; |
| struct device *dev; |
| u16 sensor_index; |
| |
| dev = thermal->bus_info->dev; |
| sensor_index = MLXSW_REG_MTMP_MODULE_INDEX_MIN + tz->module; |
| |
| /* Read module temperature and thresholds. */ |
| mlxsw_thermal_module_temp_and_thresholds_get(thermal->core, |
| tz->slot_index, |
| sensor_index, &temp, |
| &crit_temp, &emerg_temp); |
| *p_temp = temp; |
| |
| if (!temp) |
| return 0; |
| |
| /* Update trip points. */ |
| mlxsw_thermal_module_trips_update(dev, thermal->core, tz, |
| crit_temp, emerg_temp); |
| |
| 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 = tzdev->devdata; |
| 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 int |
| 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; |
| int dummy_temp, crit_temp, emerg_temp; |
| u16 sensor_index; |
| |
| sensor_index = MLXSW_REG_MTMP_MODULE_INDEX_MIN + module; |
| module_tz = &area->tz_module_arr[module]; |
| /* Skip if parent is already set (case of port split). */ |
| if (module_tz->parent) |
| return 0; |
| module_tz->module = module; |
| module_tz->slot_index = area->slot_index; |
| module_tz->parent = thermal; |
| memcpy(module_tz->trips, default_thermal_trips, |
| sizeof(thermal->trips)); |
| memcpy(module_tz->cooling_states, default_cooling_states, |
| sizeof(thermal->cooling_states)); |
| /* Initialize all trip point. */ |
| mlxsw_thermal_module_trips_reset(module_tz); |
| /* Read module temperature and thresholds. */ |
| mlxsw_thermal_module_temp_and_thresholds_get(core, area->slot_index, |
| sensor_index, &dummy_temp, |
| &crit_temp, &emerg_temp); |
| /* Update trip point according to the module data. */ |
| return mlxsw_thermal_module_trips_update(dev, core, module_tz, |
| crit_temp, emerg_temp); |
| } |
| |
| 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++) { |
| err = mlxsw_thermal_module_init(dev, core, thermal, area, i); |
| if (err) |
| goto err_thermal_module_init; |
| } |
| |
| 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: |
| err_thermal_module_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); |
| } |