mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2025-12-29 18:11:05 +00:00
Problems with existing thermal control loop:
* Not multi-board friendly. thermal.c only supports Link and needs
refactoring. Temp thresholds and fan speeds are hard-coded.
* Only the PECI temp is used to determine the fan speed. Other temp sensors
are ignored.
* Has confusing data structures. Values in the CPU temp thresholds array mix
ACPI thresholds with fan step values.
With this change, the thermal task monitors all temp sensors in order to
perform two completely independent functions:
Function one: Determine if the host needs to be throttled by or informed of
any thermal events.
For thermal events, each temp sensor will have three threshold levels.
TEMP_HOST_WARN
* When any sensor goes above this level, host_throttle_cpu(1) will be called
to ask the CPU to slow itself down.
* When all sensors drop below this level, host_throttle_cpu(0) will be called.
* Exactly AT this level, nothing happens (this provides hysteresis).
TEMP_HOST_HIGH
* When any sensor goes above this level, chipset_throttle_cpu(1) will be
called to slow the CPU down whether it wants to or not.
* When all sensors drop below this level, chipset_throttle_cpu(0) will be
called.
* Exactly AT this level, nothing happens (this provides hysteresis).
TEMP_HOST_SHUTDOWN
* When any sensor is above this level, chipset_force_shutdown() will be
called to halt the CPU.
* Nothing turns the CPU back on again - the user just has to wait for things
to cool off. Pressing the power button too soon will just trigger shutdown
again as soon as the EC can read the host temp.
Function two: Determine the amount of fan cooling needed
For fan cooling, each temp sensor will have two levels.
TEMP_FAN_OFF
* At or below this temperature, no active cooling is needed.
TEMP_FAN_MAX
* At or above this temperature, active cooling should be running at maximum.
The highest level of all temp sensors will be used to request the amount of
active cooling needed. The function pwm_fan_percent_to_rpm() is invoked to
convert the amount of cooling to the target fan RPM.
The default pwm_fan_percent_to_rpm() function converts smoothly between the
configured CONFIG_PWM_FAN_RPM_MIN and CONFIG_PWM_FAN_RPM_MAX for percentages
between 1 and 100. 0% means "off".
The default function probably provide the smoothest and quietest behavior,
but individual boards can provide their own pwm_fan_percent_to_rpm() to
implement whatever curves, hysteresis, feedback, or other hackery they wish.
BUG=chrome-os-partner:20805
BRANCH=none
TEST=manual
Compile-time test with
make BOARD=falco runtests
On the EC console, the existing fan commands should work correctly:
faninfo - display the fan state
fanduty NUM - force the fan PWM to the specified percentage (0-100)
fanset RPM - force the fan to the specified RPM
fanset NUM% - force the fan to the specified percentage (0-100) between
its configured minimum and maximum speeds from board.h
(CONFIG_PWM_FAN_RPM_MIN and CONFIG_PWM_FAN_RPM_MAX)
fanauto - let the EC control the fan automatically
You can test the default pwm_fan_percent_to_rpm() with
fanset 1%
faninfo
The fan should be turning at CONFIG_PWM_FAN_RPM_MIN. Let the EC control it
automatically again with
fanauto
Also on the EC console, the thermal settings can be examined or changed:
> temps
PECI : 327 K = 54 C
ECInternal : 320 K = 47 C
G781Internal : 319 K = 46 C
G781External : 318 K = 45 C
>
> thermalget
sensor warn high shutdown fan_off fan_max name
0 373 387 383 333 363 PECI
1 0 0 0 0 0 ECInternal
2 0 0 0 0 0 G781Internal
3 0 0 0 0 0 G781External
>
> help thermalset
Usage: thermalset sensor warn [high [shutdown [fan_off [fan_max]]]]
set thermal parameters (-1 to skip)
>
> thermalset 2 -1 -1 999
sensor warn high shutdown fan_off fan_max name
0 373 387 383 333 363 PECI
1 0 0 0 0 0 ECInternal
2 0 0 999 0 0 G781Internal
3 0 0 0 0 0 G781External
>
From the host, ectool can be used to get and set these parameters with
nearly identical commands:
ectool thermalget
ectool thermalset 2 -1 -1 999
Change-Id: Idb27977278f766826045fb7d41929953ec6b1cca
Signed-off-by: Bill Richardson <wfrichar@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/66688
Reviewed-by: Randall Spangler <rspangler@chromium.org>
442 lines
11 KiB
C
442 lines
11 KiB
C
/* 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.
|
|
*/
|
|
|
|
/* TMP006 temperature sensor module for Chrome EC */
|
|
|
|
#include "common.h"
|
|
#include "console.h"
|
|
#include "gpio.h"
|
|
#include "hooks.h"
|
|
#include "host_command.h"
|
|
#include "i2c.h"
|
|
#include "math.h"
|
|
#include "task.h"
|
|
#include "temp_sensor.h"
|
|
#include "tmp006.h"
|
|
#include "util.h"
|
|
|
|
/* Console output macros */
|
|
#define CPUTS(outstr) cputs(CC_THERMAL, outstr)
|
|
#define CPRINTF(format, args...) cprintf(CC_THERMAL, format, ## args)
|
|
|
|
/* Constants for calculating target object temperatures */
|
|
static const float A1 = 1.75e-3f;
|
|
static const float A2 = -1.678e-5f;
|
|
static const float B0 = -2.94e-5f;
|
|
static const float B1 = -5.7e-7f;
|
|
static const float B2 = 4.63e-9f;
|
|
static const float C2 = 13.4f;
|
|
|
|
/* Defined in board_temp_sensor.c. */
|
|
extern const struct tmp006_t tmp006_sensors[TMP006_COUNT];
|
|
|
|
/* Flags for tdata->fail */
|
|
#define FAIL_INIT (1 << 0) /* Just initialized */
|
|
#define FAIL_POWER (1 << 1) /* Sensor not powered */
|
|
#define FAIL_I2C (1 << 2) /* I2C communication error */
|
|
#define FAIL_NOT_READY (1 << 3) /* Data not ready */
|
|
|
|
struct tmp006_data_t {
|
|
int v; /* Object voltage */
|
|
int t[4]; /* Circular buffer of last four die temperatures */
|
|
int tidx; /* Index of the current value in t[] */
|
|
int fail; /* Fail flags; non-zero if last read failed */
|
|
float s0; /* Sensitivity factor */
|
|
float b0, b1, b2; /* Coefficients for self-heating correction */
|
|
};
|
|
|
|
static struct tmp006_data_t tmp006_data[TMP006_COUNT];
|
|
|
|
/**
|
|
* Check if sensor has power
|
|
*
|
|
* @param idx Sensor index
|
|
*
|
|
* @return non-zero if sensor has power.
|
|
*/
|
|
static int tmp006_has_power(int idx)
|
|
{
|
|
/* All TMP006 sensors are powered by VS. */
|
|
return gpio_get_level(GPIO_PGOOD_1_8VS);
|
|
}
|
|
|
|
static int tmp006_read_die_temp(const struct tmp006_data_t *tdata,
|
|
int *temp_ptr)
|
|
{
|
|
if (tdata->fail)
|
|
return EC_ERROR_UNKNOWN;
|
|
|
|
/* Return previous die temperature */
|
|
*temp_ptr = tdata->t[(tdata->tidx - 1) & 0x3] / 100;
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Calculate the remote object temperature.
|
|
*
|
|
* @param Tdie_i Die temperature in 1/100 K.
|
|
* @param Vobj_i Voltage read from register 0. In nV.
|
|
* @param tdata TMP006 data for this sensor.
|
|
*
|
|
* @return Object temperature in 1/100 K.
|
|
*/
|
|
static int tmp006_calculate_object_temp(int Tdie_i, int Vobj_i,
|
|
const struct tmp006_data_t *tdata)
|
|
{
|
|
float Tdie, Vobj;
|
|
float Tx, S, Vos, Vx, fv, Tobj, T4;
|
|
int Tobj_i;
|
|
|
|
Tdie = (float)Tdie_i * 1e-2f;
|
|
Vobj = (float)Vobj_i * 1e-9f;
|
|
|
|
/* Calculate according to TMP006 users guide. */
|
|
Tx = Tdie - 298.15f;
|
|
/* S is the sensitivity */
|
|
S = tdata->s0 * (1.0f + A1 * Tx + A2 * Tx * Tx);
|
|
/* Vos is the offset voltage */
|
|
Vos = tdata->b0 + tdata->b1 * Tx + tdata->b2 * Tx * Tx;
|
|
Vx = Vobj - Vos;
|
|
/* fv is Seebeck coefficient f(Vobj) */
|
|
fv = Vx + C2 * Vx * Vx;
|
|
|
|
T4 = Tdie * Tdie * Tdie * Tdie + fv / S;
|
|
Tobj = sqrtf(sqrtf(T4));
|
|
Tobj_i = (int32_t)(Tobj * 100.0f);
|
|
|
|
return Tobj_i;
|
|
}
|
|
|
|
/**
|
|
* Apply TMP006 temporal correction.
|
|
*
|
|
* @param T1-T4 Four die temperature readings separated by 1s in 1/100K.
|
|
* @param Vobj Voltage read from register 0, in nV.
|
|
*
|
|
* @return Corrected object voltage in 1/100K.
|
|
*/
|
|
static int tmp006_correct_object_voltage(int T1, int T2, int T3, int T4,
|
|
int Vobj)
|
|
{
|
|
int Tslope = 3 * T1 + T2 - T3 - 3 * T4;
|
|
return Vobj + 296 * Tslope;
|
|
}
|
|
|
|
static int tmp006_read_object_temp(const struct tmp006_data_t *tdata,
|
|
int *temp_ptr)
|
|
{
|
|
int pidx = (tdata->tidx - 1) & 0x3;
|
|
int t = tdata->t[pidx];
|
|
int v = tdata->v;
|
|
|
|
if (tdata->fail)
|
|
return EC_ERROR_UNKNOWN;
|
|
|
|
if (!tdata->s0)
|
|
return EC_ERROR_NOT_CALIBRATED;
|
|
|
|
v = tmp006_correct_object_voltage(
|
|
t,
|
|
tdata->t[(pidx + 3) & 3],
|
|
tdata->t[(pidx + 2) & 3],
|
|
tdata->t[(pidx + 1) & 3],
|
|
v);
|
|
|
|
*temp_ptr = tmp006_calculate_object_temp(t, v, tdata) / 100;
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int tmp006_poll_sensor(int sensor_id)
|
|
{
|
|
struct tmp006_data_t *tdata = tmp006_data + sensor_id;
|
|
int traw, t;
|
|
int vraw, v;
|
|
int rv;
|
|
int addr = tmp006_sensors[sensor_id].addr;
|
|
int idx;
|
|
|
|
if (!tmp006_has_power(sensor_id)) {
|
|
tdata->fail |= FAIL_POWER;
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
|
|
/*
|
|
* If sensor has just initialized and/or has lost power, wait for
|
|
* data ready; otherwise, we read garbage data.
|
|
*/
|
|
if (tdata->fail && (FAIL_POWER | FAIL_INIT)) {
|
|
rv = i2c_read16(TMP006_PORT(addr), TMP006_REG(addr), 0x02, &v);
|
|
if (rv) {
|
|
tdata->fail |= FAIL_I2C;
|
|
return EC_ERROR_UNKNOWN;
|
|
} else if (!(v & 0x80)) {
|
|
tdata->fail |= FAIL_NOT_READY;
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
rv = i2c_read16(TMP006_PORT(addr), TMP006_REG(addr), 0x01, &traw);
|
|
if (rv) {
|
|
tdata->fail |= FAIL_I2C;
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
|
|
/* Convert temperature from raw to 1/100 K */
|
|
t = ((int)(int16_t)traw * 100) / 128 + 27300;
|
|
|
|
rv = i2c_read16(TMP006_PORT(addr), TMP006_REG(addr), 0x00, &vraw);
|
|
if (rv) {
|
|
tdata->fail |= FAIL_I2C;
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
|
|
/* Convert voltage from raw to nV */
|
|
v = ((int)(int16_t)vraw * 15625) / 100;
|
|
|
|
/*
|
|
* If last read failed, set the entire temperature history to the
|
|
* current temperature. This keeps us from making inaccurate temporal
|
|
* corrections based on stale data.
|
|
*/
|
|
if (tdata->fail) {
|
|
for (idx = 0; idx < 4; idx++)
|
|
tdata->t[idx] = t;
|
|
} else {
|
|
idx = tdata->tidx;
|
|
tdata->t[idx] = t;
|
|
tdata->tidx = (idx + 1) & 3;
|
|
}
|
|
|
|
tdata->v = v;
|
|
tdata->fail = 0;
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int tmp006_get_val(int idx, int *temp_ptr)
|
|
{
|
|
/*
|
|
* Note: idx is a thermal sensor index, where the top N-1 bits are the
|
|
* TMP006 index and the bottom bit is (0=die, 1=remote).
|
|
*/
|
|
int tidx = idx >> 1;
|
|
const struct tmp006_data_t *tdata = tmp006_data + tidx;
|
|
|
|
if (tdata->fail & FAIL_POWER) {
|
|
/*
|
|
* Sensor isn't powered, or hasn't successfully provided data
|
|
* since being powered. Keep reporting not-powered until
|
|
* we get good data (which will clear FAIL_POWER) or there is
|
|
* an I2C error.
|
|
*/
|
|
return (tdata->fail & FAIL_I2C) ? EC_ERROR_UNKNOWN :
|
|
EC_ERROR_NOT_POWERED;
|
|
}
|
|
|
|
/* Check the low bit to determine which temperature to read. */
|
|
if ((idx & 0x1) == 0)
|
|
return tmp006_read_die_temp(tdata, temp_ptr);
|
|
else
|
|
return tmp006_read_object_temp(tdata, temp_ptr);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Hooks */
|
|
|
|
static void tmp006_poll(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < TMP006_COUNT; ++i)
|
|
tmp006_poll_sensor(i);
|
|
}
|
|
DECLARE_HOOK(HOOK_SECOND, tmp006_poll, HOOK_PRIO_TEMP_SENSOR);
|
|
|
|
static void tmp006_init(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < TMP006_COUNT; ++i) {
|
|
struct tmp006_data_t *tdata = tmp006_data + i;
|
|
|
|
/* Report error until we actually read the sensor */
|
|
tdata->fail = FAIL_INIT;
|
|
|
|
/* Use defaults for Bn params */
|
|
tdata->b0 = B0;
|
|
tdata->b1 = B1;
|
|
tdata->b2 = B2;
|
|
}
|
|
}
|
|
DECLARE_HOOK(HOOK_INIT, tmp006_init, HOOK_PRIO_DEFAULT);
|
|
|
|
/*****************************************************************************/
|
|
/* Host commands */
|
|
|
|
int tmp006_get_calibration(struct host_cmd_handler_args *args)
|
|
{
|
|
const struct ec_params_tmp006_get_calibration *p = args->params;
|
|
struct ec_response_tmp006_get_calibration *r = args->response;
|
|
const struct tmp006_data_t *tdata;
|
|
|
|
if (p->index >= TMP006_COUNT)
|
|
return EC_RES_INVALID_PARAM;
|
|
|
|
tdata = tmp006_data + p->index;
|
|
|
|
r->s0 = tdata->s0;
|
|
r->b0 = tdata->b0;
|
|
r->b1 = tdata->b1;
|
|
r->b2 = tdata->b2;
|
|
|
|
args->response_size = sizeof(*r);
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_TMP006_GET_CALIBRATION,
|
|
tmp006_get_calibration,
|
|
EC_VER_MASK(0));
|
|
|
|
int tmp006_set_calibration(struct host_cmd_handler_args *args)
|
|
{
|
|
const struct ec_params_tmp006_set_calibration *p = args->params;
|
|
struct tmp006_data_t *tdata;
|
|
|
|
if (p->index >= TMP006_COUNT)
|
|
return EC_RES_INVALID_PARAM;
|
|
|
|
tdata = tmp006_data + p->index;
|
|
|
|
tdata->s0 = p->s0;
|
|
tdata->b0 = p->b0;
|
|
tdata->b1 = p->b1;
|
|
tdata->b2 = p->b2;
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_TMP006_SET_CALIBRATION,
|
|
tmp006_set_calibration,
|
|
EC_VER_MASK(0));
|
|
|
|
/*****************************************************************************/
|
|
/* Console commands */
|
|
|
|
/**
|
|
* Print temperature info for a sensor; used by console command.
|
|
*/
|
|
static int tmp006_print(int idx)
|
|
{
|
|
int vraw, v;
|
|
int traw, t;
|
|
int rv;
|
|
int d;
|
|
int addr = tmp006_sensors[idx].addr;
|
|
|
|
|
|
ccprintf("Debug data from %s:\n", tmp006_sensors[idx].name);
|
|
|
|
if (!tmp006_has_power(idx)) {
|
|
ccputs("Sensor powered off.\n");
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
|
|
rv = i2c_read16(TMP006_PORT(addr), TMP006_REG(addr), 0xfe, &d);
|
|
if (rv)
|
|
return rv;
|
|
ccprintf(" Manufacturer ID: 0x%04x\n", d);
|
|
|
|
rv = i2c_read16(TMP006_PORT(addr), TMP006_REG(addr), 0xff, &d);
|
|
ccprintf(" Device ID: 0x%04x\n", d);
|
|
|
|
rv = i2c_read16(TMP006_PORT(addr), TMP006_REG(addr), 0x02, &d);
|
|
ccprintf(" Config: 0x%04x\n", d);
|
|
|
|
rv = i2c_read16(TMP006_PORT(addr), TMP006_REG(addr), 0x00, &vraw);
|
|
v = ((int)(int16_t)vraw * 15625) / 100;
|
|
ccprintf(" Voltage: 0x%04x = %d nV\n", vraw, v);
|
|
|
|
rv = i2c_read16(TMP006_PORT(addr), TMP006_REG(addr), 0x01, &traw);
|
|
t = ((int)(int16_t)traw * 100) / 128;
|
|
ccprintf(" Temperature: 0x%04x = %d.%02d C\n",
|
|
traw, t / 100, t > 0 ? t % 100 : 100 - (t % 100));
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int command_sensor_info(int argc, char **argv)
|
|
{
|
|
int i;
|
|
int rv, rv1;
|
|
|
|
rv1 = EC_SUCCESS;
|
|
for (i = 0; i < TMP006_COUNT; i++) {
|
|
rv = tmp006_print(i);
|
|
if (rv != EC_SUCCESS)
|
|
rv1 = rv;
|
|
cflush();
|
|
}
|
|
|
|
return rv1;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(tmp006, command_sensor_info,
|
|
NULL,
|
|
"Print TMP006 sensors",
|
|
NULL);
|
|
|
|
static int command_t6cal(int argc, char **argv)
|
|
{
|
|
struct tmp006_data_t *tdata;
|
|
char *e;
|
|
int v;
|
|
int i;
|
|
|
|
if (argc < 2) {
|
|
ccprintf("# Name S0 b0"
|
|
" b1 b2\n");
|
|
for (i = 0; i < TMP006_COUNT; i++) {
|
|
tdata = tmp006_data + i;
|
|
ccprintf("%d %-11s"
|
|
"%7de-17 %7de-8 %7de-10 %7de-12\n",
|
|
i, tmp006_sensors[i].name,
|
|
(int)(tdata->s0 * 1e17f),
|
|
(int)(tdata->b0 * 1e8f),
|
|
(int)(tdata->b1 * 1e10f),
|
|
(int)(tdata->b2 * 1e12f));
|
|
}
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
if (argc != 4)
|
|
return EC_ERROR_PARAM_COUNT;
|
|
|
|
i = strtoi(argv[1], &e, 0);
|
|
if (*e || i < 0 || i >= TMP006_COUNT)
|
|
return EC_ERROR_PARAM1;
|
|
tdata = tmp006_data + i;
|
|
|
|
v = strtoi(argv[3], &e, 0);
|
|
if (*e)
|
|
return EC_ERROR_PARAM3;
|
|
|
|
if (!strcasecmp(argv[2], "s0"))
|
|
tdata->s0 = (float)v * 1e-17f;
|
|
else if (!strcasecmp(argv[2], "b0"))
|
|
tdata->b0 = (float)v * 1e-8f;
|
|
else if (!strcasecmp(argv[2], "b1"))
|
|
tdata->b1 = (float)v * 1e-10f;
|
|
else if (!strcasecmp(argv[2], "b2"))
|
|
tdata->b2 = (float)v * 1e-12f;
|
|
else
|
|
return EC_ERROR_PARAM2;
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(t6cal, command_t6cal,
|
|
"[<index> <coeff_name> <radix>]",
|
|
"Set/print TMP006 calibration",
|
|
NULL);
|