text: /* Copyright (c) 2012 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.
   */
  
  /* PECI interface for Chrome EC */
  
  #include "chipset.h"
  #include "clock.h"
  #include "common.h"
  #include "console.h"
  #include "gpio.h"
  #include "hooks.h"
  #include "peci.h"
  #include "registers.h"
  #include "temp_sensor.h"
  #include "util.h"
  
  /* Initial PECI baud rate */
  #define PECI_BAUD_RATE 100000
  
  /* Polling interval for PECI, in ms */
  #define PECI_POLL_INTERVAL_MS 250
  
  /*
   * Internal and external path delays, in ns.  The external delay is a
   * best-guess measurement, but we're fairly tolerant of a bad guess because
   * PECI_BAUD_RATE is slow compared to PECI's actual maximum baud rate.
   */
  #define PECI_TD_FET_NS 60
  #define PECI_TD_INT_NS 80
  
  /* Number of controller retries. Should be between 0 and 7. */
  #define PECI_RETRY_COUNT 4
  
  /* Timing negotiation error bypass. 1 = on. 0 = off. */
  #define PECI_ERROR_BYPASS 1
  
  #define TEMP_AVG_LENGTH 4  /* Should be power of 2 */
  static int temp_vals[TEMP_AVG_LENGTH];
  static int temp_idx;
  
  int peci_get_cpu_temp(void)
  {
  	int v = LM4_PECI_M0D0 & 0xffff;
  
  	if (v >= 0x8000 && v <= 0x8fff)
  		return -1;
  
  	return v >> 6;
  }
  
  int peci_temp_sensor_get_val(int idx, int *temp_ptr)
  {
  	int sum = 0;
  	int success_cnt = 0;
  	int i;
  
  	if (!chipset_in_state(CHIPSET_STATE_ON))
  		return EC_ERROR_NOT_POWERED;
  
  	for (i = 0; i < TEMP_AVG_LENGTH; ++i) {
  		if (temp_vals[i] >= 0) {
  			success_cnt++;
  			sum += temp_vals[i];
  		}
  	}
  
  	/*
  	 * Require at least two valid samples. When the AP transitions into S0,
  	 * it is possible, depending on the timing of the PECI sample, to read
  	 * an invalid temperature. This is very rare, but when it does happen
  	 * the temperature returned is CONFIG_PECI_TJMAX. Requiring two valid
  	 * samples here assures us that one bad maximum temperature reading
  	 * when entering S0 won't cause us to trigger an over temperature.
  	 */
  	if (success_cnt < 2)
  		return EC_ERROR_UNKNOWN;
  
  	*temp_ptr = sum / success_cnt;
  	return EC_SUCCESS;
  }
  
  static void peci_temp_sensor_poll(void)
  {
  	temp_vals[temp_idx] = peci_get_cpu_temp();
  	temp_idx = (temp_idx + 1) & (TEMP_AVG_LENGTH - 1);
  }
  DECLARE_HOOK(HOOK_TICK, peci_temp_sensor_poll, HOOK_PRIO_TEMP_SENSOR);
  
  static void peci_freq_changed(void)
  {
  	int freq = clock_get_freq();
  	int baud;
  
  	/* Disable polling while reconfiguring */
  	LM4_PECI_CTL = 0;
  
  	/*
  	 * Calculate baud setting from desired rate, compensating for internal
  	 * and external delays.
  	 */
  	baud = freq / (4 * PECI_BAUD_RATE) - 2;
  	baud -= (freq / 1000000) * (PECI_TD_FET_NS + PECI_TD_INT_NS) / 1000;
  
  	/* Set baud rate and polling rate */
  	LM4_PECI_DIV = (baud << 16) |
  		(PECI_POLL_INTERVAL_MS * (freq / 1000 / 4096));
  
  	/* Set up temperature monitoring to report in degrees K */
  	LM4_PECI_CTL = ((CONFIG_PECI_TJMAX + 273) << 22) | 0x0001 |
  		       (PECI_RETRY_COUNT << 12) |
  		       (PECI_ERROR_BYPASS << 11);
  }
  DECLARE_HOOK(HOOK_FREQ_CHANGE, peci_freq_changed, HOOK_PRIO_DEFAULT);
  
  static void peci_init(void)
  {
  	int i;
  
  	/* Enable the PECI module in run and sleep modes. */
  	clock_enable_peripheral(CGC_OFFSET_PECI, 0x1,
  			CGC_MODE_RUN | CGC_MODE_SLEEP);
  
  	/* Configure GPIOs */
  	gpio_config_module(MODULE_PECI, 1);
  
  	/* Set initial clock frequency */
  	peci_freq_changed();
  
  	/* Initialize temperature reading buffer to a sane value. */
  	for (i = 0; i < TEMP_AVG_LENGTH; ++i)
  		temp_vals[i] = 300; /* 27 C */
  }
  DECLARE_HOOK(HOOK_INIT, peci_init, HOOK_PRIO_DEFAULT);
  
  /*****************************************************************************/
  /* Console commands */
  
  static int command_peci_temp(int argc, char **argv)
  {
  	int t = peci_get_cpu_temp();
  	if (t == -1) {
  		ccprintf("PECI error 0x%04x\n", LM4_PECI_M0D0 & 0xffff);
  		return EC_ERROR_UNKNOWN;
  	}
  	ccprintf("CPU temp = %d K = %d C\n", t, K_TO_C(t));
  	return EC_SUCCESS;
  }
  DECLARE_CONSOLE_COMMAND(pecitemp, command_peci_temp,
  			NULL,
  			"Print CPU temperature",
  			NULL);
  