/* 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 "board.h" #include "config.h" #include "console.h" #include "fpu.h" #include "gpio.h" #include "i2c.h" #include "math.h" #include "task.h" #include "temp_sensor.h" #include "tmp006.h" #include "util.h" /* Defined in board_temp_sensor.c. */ extern const struct tmp006_t tmp006_sensors[TMP006_COUNT]; struct tmp006_data_t { /* Object voltage */ int v; /* The last four die temperature value. Used as a circular buffer. */ int t[4]; /* The index of the current value in the dir temperature array. */ int tidx; /* Fail bit: 1 if last read fail. 0 if ok. */ int fail; }; static struct tmp006_data_t tmp006_data[TMP006_COUNT]; static int tmp006_read_die_temp(int idx) { int pidx = (tmp006_data[idx].tidx - 1) & 0x3; if (tmp006_data[idx].fail == 1) return -1; return tmp006_data[idx].t[pidx] / 100; } /* Calculate the remote object temperature. * Parameters: * Tdie: Die temperature in 1/100 K. * Vobj: Voltage read from register 0. In nV. * S0: Sensitivity factor in 1e-17. * Return: * Object temperature in 1/100 K. */ static int tmp006_calculate_object_temp(int Tdie_i, int Vobj_i, int S0_i) { #ifdef CONFIG_FPU float Tdie, Vobj, S0; float Tx, S, Vos, Vx, fv, Tobj, T4; int Tobj_i; enable_fpu(); Tdie = (float)Tdie_i * 1e-2f; Vobj = (float)Vobj_i * 1e-9f; S0 = (float)S0_i * 1e-17f; /* Calculate according to TMP006 users guide. */ Tx = Tdie - 298.15f; /* S is the sensitivity */ S = S0 * (1.0f + 1.75e-3f * Tx - 1.678e-5f * Tx * Tx); /* Vos is the offset voltage */ Vos = -2.94e-5f - 5.7e-7f * Tx + 4.63e-9f * Tx * Tx; Vx = Vobj - Vos; /* fv is Seebeck coefficient f(Vobj) */ fv = Vx + 13.4f * Vx * Vx; T4 = Tdie * Tdie * Tdie * Tdie + fv / S; Tobj = sqrtf(sqrtf(T4)); Tobj_i = (int32_t)(Tobj * 100.0f); disable_fpu(Tobj_i); return Tobj_i; #else /* This is the fixed-point version of object temperature calculation. * Should be accurate but it is hard to prevent and debug * overflow/underflow problem. Only use this version if there is no * FPU support. * Division is delayed when possible to preserve precision, but should * not cause overflow. * Assuming Tdie is between 200K and 400K, and S0 between 3e-14 and * 9e-14, the maximum value during the calculation should be less than * (1 << 30), which fits in int32_t. */ int32_t Tx, S19, Vos, Vx, fv9, ub, lb; Tx = Tdie - 29815; /* S19 is the sensitivity multipled by 1e19 */ S19 = S0 * (100000 + 175 * Tx / 100 - 1678 * Tx / 100 * Tx / 100000) / 1000; /* Vos is the offset voltage in nV */ Vos = -29400 - 570 * Tx / 100 + 463 * Tx / 100 * Tx / 10000; Vx = Vobj - Vos; /* fv9 is Seebeck coefficient f(Vobj) multipled by 1e9 */ fv9 = Vx + 134 * Vx / 100000 * Vx / 100000; /* The last step in the calculation involves square root, so we use * binary search. * Assuming the object temperature is between 200K and 400K, the search * should take at most 14 iterations. */ ub = 40000; lb = 20000; while (lb != ub) { int32_t t, rhs, lhs; t = (ub + lb) / 2; lhs = t / 100 * t / 10000 * t / 10000 * (S19/100) / 1000 * t; rhs = Tdie / 100 * Tdie / 10000 * Tdie / 10000 * (S19/100) / 1000 * Tdie + fv9 * 1000; if (lhs > rhs) ub = t; else lb = t + 1; } return ub; #endif /* CONFIG_FPU */ } /* Temporal Correction * Parameters: * T1-T4: Four die temperature readings separated by 1s in 1/100K. * v: 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(int idx) { int pidx = (tmp006_data[idx].tidx - 1) & 0x3; int t = tmp006_data[idx].t[pidx]; int v = tmp006_data[idx].v; if (tmp006_data[idx].fail) return -1; v = tmp006_correct_object_voltage( t, tmp006_data[idx].t[(pidx + 3) & 3], tmp006_data[idx].t[(pidx + 2) & 3], tmp006_data[idx].t[(pidx + 1) & 3], v); /* TODO: Calibrate the sensitivity factor. */ return tmp006_calculate_object_temp(t, v, tmp006_sensors[idx].sens) / 100; } static int tmp006_poll_sensor(int sensor_id) { int traw, t; int vraw, v; int rv; int addr = tmp006_sensors[sensor_id].addr; int idx; /* TODO: For now, all TMP006 sensors are powered by VS. Modify this * if we have different design. */ if (gpio_get_level(GPIO_PGOOD_1_8VS) == 0) { tmp006_data[sensor_id].fail = 1; return EC_ERROR_UNKNOWN; } rv = i2c_read16(TMP006_PORT(addr), TMP006_REG(addr), 0x01, &traw); if (rv) { tmp006_data[sensor_id].fail = 1; return EC_ERROR_UNKNOWN; } t = ((int)(int16_t)traw * 100) / 128 + 27300; rv = i2c_read16(TMP006_PORT(addr), TMP006_REG(addr), 0x00, &vraw); if (rv) { tmp006_data[sensor_id].fail = 1; return EC_ERROR_UNKNOWN; } v = ((int)(int16_t)vraw * 15625) / 100; idx = tmp006_data[sensor_id].tidx; tmp006_data[sensor_id].t[idx] = t; tmp006_data[sensor_id].v = v; tmp006_data[sensor_id].tidx = (idx + 1) & 3; tmp006_data[sensor_id].fail = 0; return EC_SUCCESS; } /* 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); /* TODO: For now, all TMP006 sensors are powered by VS. Modify this * if we have different design. */ if (gpio_get_level(GPIO_PGOOD_1_8VS) == 0) { 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; } int tmp006_get_val(int idx) { /* Check the low bit to determine which temperature to read. */ if ((idx & 0x1) == 0) return tmp006_read_die_temp(idx >> 1); else return tmp006_read_object_temp(idx >> 1); } int tmp006_poll(void) { int i; int rv; int rv1 = EC_SUCCESS; for (i = 0; i < TMP006_COUNT; ++i) { rv = tmp006_poll_sensor(i); if (rv != EC_SUCCESS) rv1 = rv; } return rv1; } /*****************************************************************************/ /* Console commands */ 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);