text: // 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_ASIC_TEMP_CRIT	110000	/* 110C */
  #define MLXSW_THERMAL_HYSTERESIS_TEMP	5000	/* 5C */
  #define MLXSW_THERMAL_MODULE_TEMP_SHIFT	(MLXSW_THERMAL_HYSTERESIS_TEMP * 2)
  #define MLXSW_THERMAL_ZONE_MAX_NAME	16
  #define MLXSW_THERMAL_TEMP_SCORE_MAX	GENMASK(31, 0)
  #define MLXSW_THERMAL_MAX_STATE	10
  #define MLXSW_THERMAL_MAX_DUTY	255
  /* Minimum and maximum fan allowed speed in percent: from 20% to 100%. Values
   * MLXSW_THERMAL_MAX_STATE + x, where x is between 2 and 10 are used for
   * setting fan speed dynamic minimum. For example, if value is set to 14 (40%)
   * cooling levels vector will be set to 4, 4, 4, 4, 4, 5, 6, 7, 8, 9, 10 to
   * introduce PWM speed in percent: 40, 40, 40, 40, 40, 50, 60. 70, 80, 90, 100.
   */
  #define MLXSW_THERMAL_SPEED_MIN		(MLXSW_THERMAL_MAX_STATE + 2)
  #define MLXSW_THERMAL_SPEED_MAX		(MLXSW_THERMAL_MAX_STATE * 2)
  #define MLXSW_THERMAL_SPEED_MIN_LEVEL	2		/* 20% */
  
  /* 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,
  	MLXSW_THERMAL_TEMP_TRIP_CRIT,
  };
  
  struct mlxsw_thermal_trip {
  	int	type;
  	int	temp;
  	int	hyst;
  	int	min_state;
  	int	max_state;
  };
  
  static const struct mlxsw_thermal_trip default_thermal_trips[] = {
  	{	/* In range - 0-40% PWM */
  		.type		= THERMAL_TRIP_ACTIVE,
  		.temp		= MLXSW_THERMAL_ASIC_TEMP_NORM,
  		.hyst		= MLXSW_THERMAL_HYSTERESIS_TEMP,
  		.min_state	= 0,
  		.max_state	= (4 * MLXSW_THERMAL_MAX_STATE) / 10,
  	},
  	{
  		/* In range - 40-100% PWM */
  		.type		= THERMAL_TRIP_ACTIVE,
  		.temp		= MLXSW_THERMAL_ASIC_TEMP_HIGH,
  		.hyst		= MLXSW_THERMAL_HYSTERESIS_TEMP,
  		.min_state	= (4 * MLXSW_THERMAL_MAX_STATE) / 10,
  		.max_state	= MLXSW_THERMAL_MAX_STATE,
  	},
  	{	/* Warning */
  		.type		= THERMAL_TRIP_HOT,
  		.temp		= MLXSW_THERMAL_ASIC_TEMP_HOT,
  		.hyst		= MLXSW_THERMAL_HYSTERESIS_TEMP,
  		.min_state	= MLXSW_THERMAL_MAX_STATE,
  		.max_state	= MLXSW_THERMAL_MAX_STATE,
  	},
  	{	/* Critical - soft poweroff */
  		.type		= THERMAL_TRIP_CRITICAL,
  		.temp		= MLXSW_THERMAL_ASIC_TEMP_CRIT,
  		.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 mlxsw_thermal_trip trips[MLXSW_THERMAL_NUM_TRIPS];
  	enum thermal_device_mode mode;
  	int module; /* Module or gearbox number */
  };
  
  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];
  	u8 cooling_levels[MLXSW_THERMAL_MAX_STATE + 1];
  	struct mlxsw_thermal_trip trips[MLXSW_THERMAL_NUM_TRIPS];
  	enum thermal_device_mode mode;
  	struct mlxsw_thermal_module *tz_module_arr;
  	struct mlxsw_thermal_module *tz_gearbox_arr;
  	u8 tz_gearbox_num;
  	unsigned int tz_highest_score;
  	struct thermal_zone_device *tz_highest_dev;
  };
  
  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 (strnstr(cdev->type, mlxsw_thermal_external_allowed_cdev[i],
  			    sizeof(cdev->type)))
  			return 0;
  	}
  
  	return -ENODEV;
  }
  
  static void
  mlxsw_thermal_module_trips_reset(struct mlxsw_thermal_module *tz)
  {
  	tz->trips[MLXSW_THERMAL_TEMP_TRIP_NORM].temp = 0;
  	tz->trips[MLXSW_THERMAL_TEMP_TRIP_HIGH].temp = 0;
  	tz->trips[MLXSW_THERMAL_TEMP_TRIP_HOT].temp = 0;
  	tz->trips[MLXSW_THERMAL_TEMP_TRIP_CRIT].temp = 0;
  }
  
  static int
  mlxsw_thermal_module_trips_update(struct device *dev, struct mlxsw_core *core,
  				  struct mlxsw_thermal_module *tz)
  {
  	int crit_temp, emerg_temp;
  	int err;
  
  	err = mlxsw_env_module_temp_thresholds_get(core, tz->module,
  						   SFP_TEMP_HIGH_WARN,
  						   &crit_temp);
  	if (err)
  		return err;
  
  	err = mlxsw_env_module_temp_thresholds_get(core, tz->module,
  						   SFP_TEMP_HIGH_ALARM,
  						   &emerg_temp);
  	if (err)
  		return err;
  
  	/* According to the system thermal requirements, the thermal zones are
  	 * defined with four trip points. The critical and emergency
  	 * temperature thresholds, provided by QSFP module are set as "active"
  	 * and "hot" trip points, "normal" and "critical" trip points are
  	 * derived from "active" and "hot" by subtracting or adding double
  	 * hysteresis value.
  	 */
  	if (crit_temp >= MLXSW_THERMAL_MODULE_TEMP_SHIFT)
  		tz->trips[MLXSW_THERMAL_TEMP_TRIP_NORM].temp = crit_temp -
  					MLXSW_THERMAL_MODULE_TEMP_SHIFT;
  	else
  		tz->trips[MLXSW_THERMAL_TEMP_TRIP_NORM].temp = crit_temp;
  	tz->trips[MLXSW_THERMAL_TEMP_TRIP_HIGH].temp = crit_temp;
  	tz->trips[MLXSW_THERMAL_TEMP_TRIP_HOT].temp = emerg_temp;
  	if (emerg_temp > crit_temp)
  		tz->trips[MLXSW_THERMAL_TEMP_TRIP_CRIT].temp = emerg_temp +
  					MLXSW_THERMAL_MODULE_TEMP_SHIFT;
  	else
  		tz->trips[MLXSW_THERMAL_TEMP_TRIP_CRIT].temp = emerg_temp;
  
  	return 0;
  }
  
  static void mlxsw_thermal_tz_score_update(struct mlxsw_thermal *thermal,
  					  struct thermal_zone_device *tzdev,
  					  struct mlxsw_thermal_trip *trips,
  					  int temp)
  {
  	struct mlxsw_thermal_trip *trip = trips;
  	unsigned int score, delta, i, shift = 1;
  
  	/* Calculate thermal zone score, if temperature is above the critical
  	 * threshold score is set to MLXSW_THERMAL_TEMP_SCORE_MAX.
  	 */
  	score = MLXSW_THERMAL_TEMP_SCORE_MAX;
  	for (i = MLXSW_THERMAL_TEMP_TRIP_NORM; i < MLXSW_THERMAL_NUM_TRIPS;
  	     i++, trip++) {
  		if (temp < trip->temp) {
  			delta = DIV_ROUND_CLOSEST(temp, trip->temp - temp);
  			score = delta * shift;
  			break;
  		}
  		shift *= 256;
  	}
  
  	if (score > thermal->tz_highest_score) {
  		thermal->tz_highest_score = score;
  		thermal->tz_highest_dev = tzdev;
  	}
  }
  
  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_thermal_trip *trip = &thermal->trips[i];
  
  		err = thermal_zone_bind_cooling_device(tzdev, i, cdev,
  						       trip->max_state,
  						       trip->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_mode(struct thermal_zone_device *tzdev,
  				  enum thermal_device_mode *mode)
  {
  	struct mlxsw_thermal *thermal = tzdev->devdata;
  
  	*mode = thermal->mode;
  
  	return 0;
  }
  
  static int mlxsw_thermal_set_mode(struct thermal_zone_device *tzdev,
  				  enum thermal_device_mode mode)
  {
  	struct mlxsw_thermal *thermal = tzdev->devdata;
  
  	mutex_lock(&tzdev->lock);
  
  	if (mode == THERMAL_DEVICE_ENABLED)
  		tzdev->polling_delay = thermal->polling_delay;
  	else
  		tzdev->polling_delay = 0;
  
  	mutex_unlock(&tzdev->lock);
  
  	thermal->mode = mode;
  	thermal_zone_device_update(tzdev, THERMAL_EVENT_UNSPECIFIED);
  
  	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, 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);
  	if (temp > 0)
  		mlxsw_thermal_tz_score_update(thermal, tzdev, thermal->trips,
  					      temp);
  
  	*p_temp = temp;
  	return 0;
  }
  
  static int mlxsw_thermal_get_trip_type(struct thermal_zone_device *tzdev,
  				       int trip,
  				       enum thermal_trip_type *p_type)
  {
  	struct mlxsw_thermal *thermal = tzdev->devdata;
  
  	if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS)
  		return -EINVAL;
  
  	*p_type = thermal->trips[trip].type;
  	return 0;
  }
  
  static int mlxsw_thermal_get_trip_temp(struct thermal_zone_device *tzdev,
  				       int trip, int *p_temp)
  {
  	struct mlxsw_thermal *thermal = tzdev->devdata;
  
  	if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS)
  		return -EINVAL;
  
  	*p_temp = thermal->trips[trip].temp;
  	return 0;
  }
  
  static int mlxsw_thermal_set_trip_temp(struct thermal_zone_device *tzdev,
  				       int trip, int temp)
  {
  	struct mlxsw_thermal *thermal = tzdev->devdata;
  
  	if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS ||
  	    temp > MLXSW_THERMAL_ASIC_TEMP_CRIT)
  		return -EINVAL;
  
  	thermal->trips[trip].temp = temp;
  	return 0;
  }
  
  static int mlxsw_thermal_get_trip_hyst(struct thermal_zone_device *tzdev,
  				       int trip, int *p_hyst)
  {
  	struct mlxsw_thermal *thermal = tzdev->devdata;
  
  	*p_hyst = thermal->trips[trip].hyst;
  	return 0;
  }
  
  static int mlxsw_thermal_set_trip_hyst(struct thermal_zone_device *tzdev,
  				       int trip, int hyst)
  {
  	struct mlxsw_thermal *thermal = tzdev->devdata;
  
  	thermal->trips[trip].hyst = hyst;
  	return 0;
  }
  
  static int mlxsw_thermal_trend_get(struct thermal_zone_device *tzdev,
  				   int trip, enum thermal_trend *trend)
  {
  	struct mlxsw_thermal_module *tz = tzdev->devdata;
  	struct mlxsw_thermal *thermal = tz->parent;
  
  	if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS)
  		return -EINVAL;
  
  	if (tzdev == thermal->tz_highest_dev)
  		return 1;
  
  	*trend = THERMAL_TREND_STABLE;
  	return 0;
  }
  
  static struct thermal_zone_device_ops mlxsw_thermal_ops = {
  	.bind = mlxsw_thermal_bind,
  	.unbind = mlxsw_thermal_unbind,
  	.get_mode = mlxsw_thermal_get_mode,
  	.set_mode = mlxsw_thermal_set_mode,
  	.get_temp = mlxsw_thermal_get_temp,
  	.get_trip_type	= mlxsw_thermal_get_trip_type,
  	.get_trip_temp	= mlxsw_thermal_get_trip_temp,
  	.set_trip_temp	= mlxsw_thermal_set_trip_temp,
  	.get_trip_hyst	= mlxsw_thermal_get_trip_hyst,
  	.set_trip_hyst	= mlxsw_thermal_set_trip_hyst,
  	.get_trend	= mlxsw_thermal_trend_get,
  };
  
  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_thermal_trip *trip = &tz->trips[i];
  
  		err = thermal_zone_bind_cooling_device(tzdev, i, cdev,
  						       trip->max_state,
  						       trip->min_state,
  						       THERMAL_WEIGHT_DEFAULT);
  		if (err < 0)
  			goto err_bind_cooling_device;
  	}
  	return 0;
  
  err_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 int mlxsw_thermal_module_mode_get(struct thermal_zone_device *tzdev,
  					 enum thermal_device_mode *mode)
  {
  	struct mlxsw_thermal_module *tz = tzdev->devdata;
  
  	*mode = tz->mode;
  
  	return 0;
  }
  
  static int mlxsw_thermal_module_mode_set(struct thermal_zone_device *tzdev,
  					 enum thermal_device_mode mode)
  {
  	struct mlxsw_thermal_module *tz = tzdev->devdata;
  	struct mlxsw_thermal *thermal = tz->parent;
  
  	mutex_lock(&tzdev->lock);
  
  	if (mode == THERMAL_DEVICE_ENABLED)
  		tzdev->polling_delay = thermal->polling_delay;
  	else
  		tzdev->polling_delay = 0;
  
  	mutex_unlock(&tzdev->lock);
  
  	tz->mode = mode;
  	thermal_zone_device_update(tzdev, THERMAL_EVENT_UNSPECIFIED);
  
  	return 0;
  }
  
  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;
  	struct device *dev = thermal->bus_info->dev;
  	char mtmp_pl[MLXSW_REG_MTMP_LEN];
  	int temp;
  	int err;
  
  	/* Read module temperature. */
  	mlxsw_reg_mtmp_pack(mtmp_pl, MLXSW_REG_MTMP_MODULE_INDEX_MIN +
  			    tz->module, false, false);
  	err = mlxsw_reg_query(thermal->core, MLXSW_REG(mtmp), mtmp_pl);
  	if (err) {
  		/* Do not return error - in case of broken module's sensor
  		 * it will cause error message flooding.
  		 */
  		temp = 0;
  		*p_temp = (int) temp;
  		return 0;
  	}
  	mlxsw_reg_mtmp_unpack(mtmp_pl, &temp, NULL, NULL);
  	*p_temp = temp;
  
  	if (!temp)
  		return 0;
  
  	/* Update trip points. */
  	err = mlxsw_thermal_module_trips_update(dev, thermal->core, tz);
  	if (!err && temp > 0)
  		mlxsw_thermal_tz_score_update(thermal, tzdev, tz->trips, temp);
  
  	return 0;
  }
  
  static int
  mlxsw_thermal_module_trip_type_get(struct thermal_zone_device *tzdev, int trip,
  				   enum thermal_trip_type *p_type)
  {
  	struct mlxsw_thermal_module *tz = tzdev->devdata;
  
  	if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS)
  		return -EINVAL;
  
  	*p_type = tz->trips[trip].type;
  	return 0;
  }
  
  static int
  mlxsw_thermal_module_trip_temp_get(struct thermal_zone_device *tzdev,
  				   int trip, int *p_temp)
  {
  	struct mlxsw_thermal_module *tz = tzdev->devdata;
  
  	if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS)
  		return -EINVAL;
  
  	*p_temp = tz->trips[trip].temp;
  	return 0;
  }
  
  static int
  mlxsw_thermal_module_trip_temp_set(struct thermal_zone_device *tzdev,
  				   int trip, int temp)
  {
  	struct mlxsw_thermal_module *tz = tzdev->devdata;
  
  	if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS ||
  	    temp > tz->trips[MLXSW_THERMAL_TEMP_TRIP_CRIT].temp)
  		return -EINVAL;
  
  	tz->trips[trip].temp = temp;
  	return 0;
  }
  
  static int
  mlxsw_thermal_module_trip_hyst_get(struct thermal_zone_device *tzdev, int trip,
  				   int *p_hyst)
  {
  	struct mlxsw_thermal_module *tz = tzdev->devdata;
  
  	*p_hyst = tz->trips[trip].hyst;
  	return 0;
  }
  
  static int
  mlxsw_thermal_module_trip_hyst_set(struct thermal_zone_device *tzdev, int trip,
  				   int hyst)
  {
  	struct mlxsw_thermal_module *tz = tzdev->devdata;
  
  	tz->trips[trip].hyst = hyst;
  	return 0;
  }
  
  static struct thermal_zone_device_ops mlxsw_thermal_module_ops = {
  	.bind		= mlxsw_thermal_module_bind,
  	.unbind		= mlxsw_thermal_module_unbind,
  	.get_mode	= mlxsw_thermal_module_mode_get,
  	.set_mode	= mlxsw_thermal_module_mode_set,
  	.get_temp	= mlxsw_thermal_module_temp_get,
  	.get_trip_type	= mlxsw_thermal_module_trip_type_get,
  	.get_trip_temp	= mlxsw_thermal_module_trip_temp_get,
  	.set_trip_temp	= mlxsw_thermal_module_trip_temp_set,
  	.get_trip_hyst	= mlxsw_thermal_module_trip_hyst_get,
  	.set_trip_hyst	= mlxsw_thermal_module_trip_hyst_set,
  	.get_trend	= mlxsw_thermal_trend_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, 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);
  	if (temp > 0)
  		mlxsw_thermal_tz_score_update(thermal, tzdev, tz->trips, temp);
  
  	*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_mode	= mlxsw_thermal_module_mode_get,
  	.set_mode	= mlxsw_thermal_module_mode_set,
  	.get_temp	= mlxsw_thermal_gearbox_temp_get,
  	.get_trip_type	= mlxsw_thermal_module_trip_type_get,
  	.get_trip_temp	= mlxsw_thermal_module_trip_temp_get,
  	.set_trip_temp	= mlxsw_thermal_module_trip_temp_set,
  	.get_trip_hyst	= mlxsw_thermal_module_trip_hyst_get,
  	.set_trip_hyst	= mlxsw_thermal_module_trip_hyst_set,
  	.get_trend	= mlxsw_thermal_trend_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];
  	unsigned long cur_state, i;
  	int idx;
  	u8 duty;
  	int err;
  
  	idx = mlxsw_get_cooling_device_idx(thermal, cdev);
  	if (idx < 0)
  		return idx;
  
  	/* Verify if this request is for changing allowed fan dynamical
  	 * minimum. If it is - update cooling levels accordingly and update
  	 * state, if current state is below the newly requested minimum state.
  	 * For example, if current state is 5, and minimal state is to be
  	 * changed from 4 to 6, thermal->cooling_levels[0 to 5] will be changed
  	 * all from 4 to 6. And state 5 (thermal->cooling_levels[4]) should be
  	 * overwritten.
  	 */
  	if (state >= MLXSW_THERMAL_SPEED_MIN &&
  	    state <= MLXSW_THERMAL_SPEED_MAX) {
  		state -= MLXSW_THERMAL_MAX_STATE;
  		for (i = 0; i <= MLXSW_THERMAL_MAX_STATE; i++)
  			thermal->cooling_levels[i] = max(state, i);
  
  		mlxsw_reg_mfsc_pack(mfsc_pl, idx, 0);
  		err = mlxsw_reg_query(thermal->core, MLXSW_REG(mfsc), mfsc_pl);
  		if (err)
  			return err;
  
  		duty = mlxsw_reg_mfsc_pwm_duty_cycle_get(mfsc_pl);
  		cur_state = mlxsw_duty_to_state(duty);
  
  		/* If current fan state is lower than requested dynamical
  		 * minimum, increase fan speed up to dynamical minimum.
  		 */
  		if (state < cur_state)
  			return 0;
  
  		state = cur_state;
  	}
  
  	if (state > MLXSW_THERMAL_MAX_STATE)
  		return -EINVAL;
  
  	/* Normalize the state to the valid speed range. */
  	state = thermal->cooling_levels[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[MLXSW_THERMAL_ZONE_MAX_NAME];
  	int err;
  
  	snprintf(tz_name, sizeof(tz_name), "mlxsw-module%d",
  		 module_tz->module + 1);
  	module_tz->tzdev = thermal_zone_device_register(tz_name,
  							MLXSW_THERMAL_NUM_TRIPS,
  							MLXSW_THERMAL_TRIP_MASK,
  							module_tz,
  							&mlxsw_thermal_module_ops,
  							NULL, 0, 0);
  	if (IS_ERR(module_tz->tzdev)) {
  		err = PTR_ERR(module_tz->tzdev);
  		return err;
  	}
  
  	module_tz->mode = THERMAL_DEVICE_ENABLED;
  	return 0;
  }
  
  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, u8 local_port)
  {
  	struct mlxsw_thermal_module *module_tz;
  	char pmlp_pl[MLXSW_REG_PMLP_LEN];
  	u8 width, module;
  	int err;
  
  	mlxsw_reg_pmlp_pack(pmlp_pl, local_port);
  	err = mlxsw_reg_query(core, MLXSW_REG(pmlp), pmlp_pl);
  	if (err)
  		return err;
  
  	width = mlxsw_reg_pmlp_width_get(pmlp_pl);
  	if (!width)
  		return 0;
  
  	module = mlxsw_reg_pmlp_module_get(pmlp_pl, 0);
  	module_tz = &thermal->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->parent = thermal;
  	memcpy(module_tz->trips, default_thermal_trips,
  	       sizeof(thermal->trips));
  	/* Initialize all trip point. */
  	mlxsw_thermal_module_trips_reset(module_tz);
  	/* Update trip point according to the module data. */
  	return mlxsw_thermal_module_trips_update(dev, core, module_tz);
  }
  
  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)
  {
  	unsigned int module_count = mlxsw_core_max_ports(core);
  	struct mlxsw_thermal_module *module_tz;
  	int i, err;
  
  	if (!mlxsw_core_res_query_enabled(core))
  		return 0;
  
  	thermal->tz_module_arr = kcalloc(module_count,
  					 sizeof(*thermal->tz_module_arr),
  					 GFP_KERNEL);
  	if (!thermal->tz_module_arr)
  		return -ENOMEM;
  
  	for (i = 1; i < module_count; i++) {
  		err = mlxsw_thermal_module_init(dev, core, thermal, i);
  		if (err)
  			goto err_unreg_tz_module_arr;
  	}
  
  	for (i = 0; i < module_count - 1; i++) {
  		module_tz = &thermal->tz_module_arr[i];
  		if (!module_tz->parent)
  			continue;
  		err = mlxsw_thermal_module_tz_init(module_tz);
  		if (err)
  			goto err_unreg_tz_module_arr;
  	}
  
  	return 0;
  
  err_unreg_tz_module_arr:
  	for (i = module_count - 1; i >= 0; i--)
  		mlxsw_thermal_module_fini(&thermal->tz_module_arr[i]);
  	kfree(thermal->tz_module_arr);
  	return err;
  }
  
  static void
  mlxsw_thermal_modules_fini(struct mlxsw_thermal *thermal)
  {
  	unsigned int module_count = mlxsw_core_max_ports(thermal->core);
  	int i;
  
  	if (!mlxsw_core_res_query_enabled(thermal->core))
  		return;
  
  	for (i = module_count - 1; i >= 0; i--)
  		mlxsw_thermal_module_fini(&thermal->tz_module_arr[i]);
  	kfree(thermal->tz_module_arr);
  }
  
  static int
  mlxsw_thermal_gearbox_tz_init(struct mlxsw_thermal_module *gearbox_tz)
  {
  	char tz_name[MLXSW_THERMAL_ZONE_MAX_NAME];
  
  	snprintf(tz_name, sizeof(tz_name), "mlxsw-gearbox%d",
  		 gearbox_tz->module + 1);
  	gearbox_tz->tzdev = thermal_zone_device_register(tz_name,
  						MLXSW_THERMAL_NUM_TRIPS,
  						MLXSW_THERMAL_TRIP_MASK,
  						gearbox_tz,
  						&mlxsw_thermal_gearbox_ops,
  						NULL, 0, 0);
  	if (IS_ERR(gearbox_tz->tzdev))
  		return PTR_ERR(gearbox_tz->tzdev);
  
  	gearbox_tz->mode = THERMAL_DEVICE_ENABLED;
  	return 0;
  }
  
  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_module *gearbox_tz;
  	char mgpir_pl[MLXSW_REG_MGPIR_LEN];
  	int i;
  	int err;
  
  	if (!mlxsw_core_res_query_enabled(core))
  		return 0;
  
  	mlxsw_reg_mgpir_pack(mgpir_pl);
  	err = mlxsw_reg_query(core, MLXSW_REG(mgpir), mgpir_pl);
  	if (err)
  		return err;
  
  	mlxsw_reg_mgpir_unpack(mgpir_pl, &thermal->tz_gearbox_num, NULL, NULL);
  	if (!thermal->tz_gearbox_num)
  		return 0;
  
  	thermal->tz_gearbox_arr = kcalloc(thermal->tz_gearbox_num,
  					  sizeof(*thermal->tz_gearbox_arr),
  					  GFP_KERNEL);
  	if (!thermal->tz_gearbox_arr)
  		return -ENOMEM;
  
  	for (i = 0; i < thermal->tz_gearbox_num; i++) {
  		gearbox_tz = &thermal->tz_gearbox_arr[i];
  		memcpy(gearbox_tz->trips, default_thermal_trips,
  		       sizeof(thermal->trips));
  		gearbox_tz->module = i;
  		gearbox_tz->parent = thermal;
  		err = mlxsw_thermal_gearbox_tz_init(gearbox_tz);
  		if (err)
  			goto err_unreg_tz_gearbox;
  	}
  
  	return 0;
  
  err_unreg_tz_gearbox:
  	for (i--; i >= 0; i--)
  		mlxsw_thermal_gearbox_tz_fini(&thermal->tz_gearbox_arr[i]);
  	kfree(thermal->tz_gearbox_arr);
  	return err;
  }
  
  static void
  mlxsw_thermal_gearboxes_fini(struct mlxsw_thermal *thermal)
  {
  	int i;
  
  	if (!mlxsw_core_res_query_enabled(thermal->core))
  		return;
  
  	for (i = thermal->tz_gearbox_num - 1; i >= 0; i--)
  		mlxsw_thermal_gearbox_tz_fini(&thermal->tz_gearbox_arr[i]);
  	kfree(thermal->tz_gearbox_arr);
  }
  
  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;
  	struct mlxsw_thermal *thermal;
  	u16 tacho_active;
  	u8 pwm_active;
  	int err, i;
  
  	thermal = devm_kzalloc(dev, sizeof(*thermal),
  			       GFP_KERNEL);
  	if (!thermal)
  		return -ENOMEM;
  
  	thermal->core = core;
  	thermal->bus_info = bus_info;
  	memcpy(thermal->trips, default_thermal_trips, sizeof(thermal->trips));
  
  	err = mlxsw_reg_query(thermal->core, MLXSW_REG(mfcr), mfcr_pl);
  	if (err) {
  		dev_err(dev, "Failed to probe PWMs\n");
  		goto err_free_thermal;
  	}
  	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_free_thermal;
  
  			/* 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_free_thermal;
  		}
  	}
  	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_unreg_cdevs;
  			}
  			thermal->cdevs[i] = cdev;
  		}
  	}
  
  	/* Initialize cooling levels per PWM state. */
  	for (i = 0; i < MLXSW_THERMAL_MAX_STATE; i++)
  		thermal->cooling_levels[i] = max(MLXSW_THERMAL_SPEED_MIN_LEVEL,
  						 i);
  
  	thermal->polling_delay = bus_info->low_frequency ?
  				 MLXSW_THERMAL_SLOW_POLL_INT :
  				 MLXSW_THERMAL_POLL_INT;
  
  	thermal->tzdev = thermal_zone_device_register("mlxsw",
  						      MLXSW_THERMAL_NUM_TRIPS,
  						      MLXSW_THERMAL_TRIP_MASK,
  						      thermal,
  						      &mlxsw_thermal_ops,
  						      NULL, 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_unreg_cdevs;
  	}
  
  	err = mlxsw_thermal_modules_init(dev, core, thermal);
  	if (err)
  		goto err_unreg_tzdev;
  
  	err = mlxsw_thermal_gearboxes_init(dev, core, thermal);
  	if (err)
  		goto err_unreg_modules_tzdev;
  
  	thermal->mode = THERMAL_DEVICE_ENABLED;
  	*p_thermal = thermal;
  	return 0;
  
  err_unreg_modules_tzdev:
  	mlxsw_thermal_modules_fini(thermal);
  err_unreg_tzdev:
  	if (thermal->tzdev) {
  		thermal_zone_device_unregister(thermal->tzdev);
  		thermal->tzdev = NULL;
  	}
  err_unreg_cdevs:
  	for (i = 0; i < MLXSW_MFCR_PWMS_MAX; i++)
  		if (thermal->cdevs[i])
  			thermal_cooling_device_unregister(thermal->cdevs[i]);
  err_free_thermal:
  	devm_kfree(dev, thermal);
  	return err;
  }
  
  void mlxsw_thermal_fini(struct mlxsw_thermal *thermal)
  {
  	int i;
  
  	mlxsw_thermal_gearboxes_fini(thermal);
  	mlxsw_thermal_modules_fini(thermal);
  	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;
  		}
  	}
  
  	devm_kfree(thermal->bus_info->dev, thermal);
  }
  