text: /* Copyright 2020 The Chromium OS Authors. All rights reserved.
   * Use of this source code is governed by a BSD-style license that can be
   * found in the LICENSE file.
   */
  
  #include "chipset.h"
  #include "common.h"
  #include "console.h"
  #include "extpower.h"
  #include "fan.h"
  #include "hooks.h"
  #include "host_command.h"
  #include "lid_switch.h"
  #include "motion_lid.h"
  #include "tablet_mode.h"
  #include "temp_sensor.h"
  #include "thermal.h"
  #include "util.h"
  
  /* Console output macros */
  #define CPUTS(outstr) cputs(CC_THERMAL, outstr)
  #define CPRINTS(format, args...) cprints(CC_THERMAL, format, ## args)
  
  struct fan_step {
  	/*
  	 * Sensor 1~4 trigger point, set -1 if we're not using this
  	 * sensor to determine fan speed.
  	 */
  	int8_t on[TEMP_SENSOR_COUNT];
  
  	/*
  	 * Sensor 1~4 trigger point, set -1 if we're not using this
  	 * sensor to determine fan speed.
  	 */
  	int8_t off[TEMP_SENSOR_COUNT];
  
  	/* Fan 1~2 rpm */
  	uint16_t rpm[FAN_CH_COUNT];
  };
  
  static const struct fan_step *fan_step_table;
  
  static const struct fan_step fan1_table_clamshell[] = {
  	{
  		/* level 0 */
  		.on = {-1, -1, -1, -1},
  		.off = {-1, -1, -1, -1},
  		.rpm = {0},
  	},
  	{
  		/* level 1 */
  		.on = {-1, -1, 40, -1},
  		.off = {-1, -1, 31, -1},
  		.rpm = {1900},
  	},
  	{
  		/* level 2 */
  		.on = {-1, -1, 45, -1},
  		.off = {-1, -1, 43, -1},
  		.rpm = {2900},
  	},
  	{
  		/* level 3 */
  		.on = {-1, -1, 48, -1},
  		.off = {-1, -1, 46, -1},
  		.rpm = {3200},
  	},
  	{
  		/* level 4 */
  		.on = {-1, -1, 51, -1},
  		.off = {-1, -1, 49, -1},
  		.rpm = {3550},
  	},
  	{
  		/* level 5 */
  		.on = {-1, -1, 54, -1},
  		.off = {-1, -1, 52, -1},
  		.rpm = {3950},
  	},
  	{
  		/* level 6 */
  		.on = {-1, -1, 57, -1},
  		.off = {-1, -1, 55, -1},
  		.rpm = {4250},
  	},
  	{
  		/* level 7 */
  		.on = {-1, -1, 60, -1},
  		.off = {-1, -1, 58, -1},
  		.rpm = {4650},
  	},
  };
  
  static const struct fan_step fan1_table_tablet[] = {
  	{
  		/* level 0 */
  		.on = {-1, -1, -1, -1},
  		.off = {-1, -1, -1, -1},
  		.rpm = {0},
  	},
  	{
  		/* level 1 */
  		.on = {-1, -1, 41, -1},
  		.off = {-1, -1, 31, -1},
  		.rpm = {2100},
  	},
  	{
  		/* level 2 */
  		.on = {-1, -1, 50, -1},
  		.off = {-1, -1, 48, -1},
  		.rpm = {2600},
  	},
  	{
  		/* level 3 */
  		.on = {-1, -1, 54, -1},
  		.off = {-1, -1, 52, -1},
  		.rpm = {2800},
  	},
  	{
  		/* level 4 */
  		.on = {-1, -1, 57, -1},
  		.off = {-1, -1, 55, -1},
  		.rpm = {3300},
  	},
  	{
  		/* level 5 */
  		.on = {-1, -1, 60, -1},
  		.off = {-1, -1, 58, -1},
  		.rpm = {3800},
  	},
  	{
  		/* level 6 */
  		.on = {-1, -1, 72, -1},
  		.off = {-1, -1, 69, -1},
  		.rpm = {4000},
  	},
  	{
  		/* level 7 */
  		.on = {-1, -1, 74, -1},
  		.off = {-1, -1, 73, -1},
  		.rpm = {4300},
  	},
  };
  
  static const struct fan_step fan1_table_stand[] = {
  	{
  		/* level 0 */
  		.on = {-1, -1, -1, -1},
  		.off = {-1, -1, -1, -1},
  		.rpm = {0},
  	},
  	{
  		/* level 1 */
  		.on = {-1, -1, 34, -1},
  		.off = {-1, -1, 31, -1},
  		.rpm = {1850},
  	},
  	{
  		/* level 2 */
  		.on = {-1, -1, 42, -1},
  		.off = {-1, -1, 39, -1},
  		.rpm = {2550},
  	},
  	{
  		/* level 3 */
  		.on = {-1, -1, 49, -1},
  		.off = {-1, -1, 48, -1},
  		.rpm = {2900},
  	},
  	{
  		/* level 4 */
  		.on = {-1, -1, 51, -1},
  		.off = {-1, -1, 50, -1},
  		.rpm = {3350},
  	},
  	{
  		/* level 5 */
  		.on = {-1, -1, 53, -1},
  		.off = {-1, -1, 52, -1},
  		.rpm = {3700},
  	},
  	{
  		/* level 6 */
  		.on = {-1, -1, 55, -1},
  		.off = {-1, -1, 54, -1},
  		.rpm = {3900},
  	},
  	{
  		/* level 7 */
  		.on = {-1, -1, 57, -1},
  		.off = {-1, -1, 56, -1},
  		.rpm = {4250},
  	},
  };
  
  static const struct fan_step fan0_table_clamshell[] = {
  	{
  		/* level 0 */
  		.on = {-1, -1, -1, -1},
  		.off = {-1, -1, -1, -1},
  		.rpm = {0},
  	},
  	{
  		/* level 1 */
  		.on = {-1, -1, 41, -1},
  		.off = {-1, -1, 31, -1},
  		.rpm = {2350},
  	},
  	{
  		/* level 2 */
  		.on = {-1, -1, 44, -1},
  		.off = {-1, -1, 42, -1},
  		.rpm = {3300},
  	},
  	{
  		/* level 3 */
  		.on = {-1, -1, 47, -1},
  		.off = {-1, -1, 45, -1},
  		.rpm = {3600},
  	},
  	{
  		/* level 4 */
  		.on = {-1, -1, 50, -1},
  		.off = {-1, -1, 48, -1},
  		.rpm = {4050},
  	},
  	{
  		/* level 5 */
  		.on = {-1, -1, 53, -1},
  		.off = {-1, -1, 51, -1},
  		.rpm = {4450},
  	},
  	{
  		/* level 6 */
  		.on = {-1, -1, 56, -1},
  		.off = {-1, -1, 54, -1},
  		.rpm = {4750},
  	},
  	{
  		/* level 7 */
  		.on = {-1, -1, 59, -1},
  		.off = {-1, -1, 57, -1},
  		.rpm = {5150},
  	},
  };
  
  static const struct fan_step fan0_table_tablet[] = {
  	{
  		/* level 0 */
  		.on = {-1, -1, -1, -1},
  		.off = {-1, -1, -1, -1},
  		.rpm = {0},
  	},
  	{
  		/* level 1 */
  		.on = {-1, -1, 41, -1},
  		.off = {-1, -1, 31, -1},
  		.rpm = {2250},
  	},
  	{
  		/* level 2 */
  		.on = {-1, -1, 50, -1},
  		.off = {-1, -1, 48, -1},
  		.rpm = {2850},
  	},
  	{
  		/* level 3 */
  		.on = {-1, -1, 54, -1},
  		.off = {-1, -1, 51, -1},
  		.rpm = {3100},
  	},
  	{
  		/* level 4 */
  		.on = {-1, -1, 57, -1},
  		.off = {-1, -1, 55, -1},
  		.rpm = {3500},
  	},
  	{
  		/* level 5 */
  		.on = {-1, -1, 60, -1},
  		.off = {-1, -1, 58, -1},
  		.rpm = {3900},
  	},
  	{
  		/* level 6 */
  		.on = {-1, -1, 72, -1},
  		.off = {-1, -1, 69, -1},
  		.rpm = {4150},
  	},
  	{
  		/* level 7 */
  		.on = {-1, -1, 74, -1},
  		.off = {-1, -1, 73, -1},
  		.rpm = {4400},
  	},
  };
  
  static const struct fan_step fan0_table_stand[] = {
  	{
  		/* level 0 */
  		.on = {-1, -1, -1, -1},
  		.off = {-1, -1, -1, -1},
  		.rpm = {0},
  	},
  	{
  		/* level 1 */
  		.on = {-1, -1, 34, -1},
  		.off = {-1, -1, 31, -1},
  		.rpm = {2250},
  	},
  	{
  		/* level 2 */
  		.on = {-1, -1, 42, -1},
  		.off = {-1, -1, 39, -1},
  		.rpm = {2800},
  	},
  	{
  		/* level 3 */
  		.on = {-1, -1, 49, -1},
  		.off = {-1, -1, 48, -1},
  		.rpm = {3150},
  	},
  	{
  		/* level 4 */
  		.on = {-1, -1, 51, -1},
  		.off = {-1, -1, 50, -1},
  		.rpm = {3550},
  	},
  	{
  		/* level 5 */
  		.on = {-1, -1, 53, -1},
  		.off = {-1, -1, 52, -1},
  		.rpm = {3900},
  	},
  	{
  		/* level 6 */
  		.on = {-1, -1, 55, -1},
  		.off = {-1, -1, 54, -1},
  		.rpm = {4150},
  	},
  	{
  		/* level 7 */
  		.on = {-1, -1, 57, -1},
  		.off = {-1, -1, 56, -1},
  		.rpm = {4400},
  	},
  };
  
  #define NUM_FAN_LEVELS ARRAY_SIZE(fan1_table_clamshell)
  
  #define lid_angle_tablet	340
  static int throttle_on;
  
  BUILD_ASSERT(ARRAY_SIZE(fan1_table_clamshell) ==
  	ARRAY_SIZE(fan1_table_tablet));
  
  #define average_time 60
  int fan_table_to_rpm(int fan, int *temp)
  {
  	static int current_level;
  	static int avg_tmp[TEMP_SENSOR_COUNT];
  	static int avg_calc_tmp[TEMP_SENSOR_COUNT][average_time];
  	static int prev_tmp[TEMP_SENSOR_COUNT];
  	static int new_rpm;
  	int i, j, avg_sum = 0;
  	int lid_angle = motion_lid_get_angle();
  	static int fan_up_count, fan_down_count;
  	static int temp_count;
  
  	/*
  	 * Select different fan curve table
  	 * by mode: clamshell, tent/stand, tablet and fan id
  	 */
  	if (tablet_get_mode()) {
  		if (gpio_get_level(GPIO_FAN_ID))
  			fan_step_table = fan1_table_stand;
  		else
  			fan_step_table = fan0_table_stand;
  
  		if (lid_angle >= lid_angle_tablet) {
  			if (gpio_get_level(GPIO_FAN_ID))
  				fan_step_table = fan1_table_tablet;
  			else
  				fan_step_table = fan0_table_tablet;
  		}
  	} else {
  		if (gpio_get_level(GPIO_FAN_ID))
  			fan_step_table = fan1_table_clamshell;
  		else
  			fan_step_table = fan0_table_clamshell;
  	}
  
  	/*
  	 * Average temp 60 sec timing average
  	 */
  	if (temp_count < average_time) {
  		avg_calc_tmp[TEMP_SENSOR_CPU][temp_count] =
  			temp[TEMP_SENSOR_CPU];
  		temp_count++;
  	} else
  		temp_count = 0;
  
  	for (j = 0; j < average_time; j++)
  		avg_sum = avg_sum + avg_calc_tmp[TEMP_SENSOR_CPU][j];
  
  	avg_tmp[TEMP_SENSOR_CPU] = avg_sum/average_time;
  
  	/*
  	 * Compare the current and previous temperature, we have
  	 * the three paths :
  	 *  1. decreasing path. (check the release point)
  	 *  2. increasing path. (check the trigger point)
  	 *  3. invariant path. (return the current RPM)
  	 */
  	if (avg_tmp[TEMP_SENSOR_CPU] < prev_tmp[TEMP_SENSOR_CPU]) {
  		for (i = current_level; i >= 0; i--) {
  			if (avg_tmp[TEMP_SENSOR_CPU] <
  				fan_step_table[i].off[TEMP_SENSOR_CPU]) {
  			/*
  			 * fan step down debounce
  			 */
  				if (fan_down_count < 10) {
  					fan_down_count++;
  					fan_up_count = 0;
  
  					return new_rpm;
  				}
  				fan_down_count = 0;
  				fan_up_count = 0;
  
  				current_level = i - 1;
  			} else
  				break;
  		}
  	} else if (avg_tmp[TEMP_SENSOR_CPU] > prev_tmp[TEMP_SENSOR_CPU]) {
  		for (i = current_level+1; i < NUM_FAN_LEVELS; i++) {
  			if ((avg_tmp[TEMP_SENSOR_CPU] >
  				 fan_step_table[i].on[TEMP_SENSOR_CPU])) {
  			/*
  			 * fan step up debounce
  			 */
  				if (fan_up_count < 10) {
  					fan_up_count++;
  					fan_down_count = 0;
  
  					return new_rpm;
  				}
  				fan_down_count = 0;
  				fan_up_count = 0;
  
  				current_level = i;
  			} else
  				break;
  		}
  	} else {
  		fan_down_count = 0;
  		fan_up_count = 0;
  	}
  
  	if (current_level < 1)
  		current_level = 1;
  
  	if (current_level >= 7)
  		current_level = 7;
  
  	for (i = 0; i < TEMP_SENSOR_COUNT; ++i)
  		prev_tmp[i] = avg_tmp[i];
  
  	ASSERT(current_level < NUM_FAN_LEVELS);
  
  	switch (fan) {
  	case FAN_CH_0:
  		new_rpm = fan_step_table[current_level].rpm[FAN_CH_0];
  		break;
  	default:
  		break;
  	}
  
  	return new_rpm;
  }
  
  void board_override_fan_control(int fan, int *tmp)
  {
  	if (chipset_in_state(CHIPSET_STATE_ON | CHIPSET_STATE_ANY_SUSPEND)) {
  		int new_rpm = fan_table_to_rpm(fan, tmp);
  
  		if (new_rpm != fan_get_rpm_target(FAN_CH(fan))) {
  			cprints(CC_THERMAL, "Setting fan RPM to %d", new_rpm);
  			board_print_temps();
  			fan_set_rpm_mode(FAN_CH(fan), 1);
  			fan_set_rpm_target(FAN_CH(fan), new_rpm);
  		}
  	}
  }
  
  void thermal_protect(void)
  {
  	if ((!lid_is_open()) && (!extpower_is_present())) {
  		int rv1, rv2;
  		int thermal_sensor1, thermal_sensor2;
  
  		rv1 = temp_sensor_read(TEMP_SENSOR_5V_REGULATOR,
  				       &thermal_sensor1);
  		rv2 = temp_sensor_read(TEMP_SENSOR_CPU,
  				       &thermal_sensor2);
  
  		if (rv2 == EC_SUCCESS) {
  			if (thermal_sensor2 > C_TO_K(70)) {
  				chipset_throttle_cpu(1);
  				throttle_on = 1;
  			} else if (thermal_sensor2 < C_TO_K(60) &&
  				   throttle_on) {
  				chipset_throttle_cpu(0);
  				throttle_on = 0;
  			}
  		}
  		if (rv1 == EC_SUCCESS &&
  		    thermal_sensor1 > C_TO_K(51))
  			chipset_force_shutdown(CHIPSET_SHUTDOWN_THERMAL);
  	}
  }
  DECLARE_HOOK(HOOK_SECOND, thermal_protect, HOOK_PRIO_DEFAULT);
  