diff options
-rw-r--r-- | driver/build.mk | 1 | ||||
-rw-r--r-- | driver/temp_sensor/bd99992gw.c | 260 | ||||
-rw-r--r-- | driver/temp_sensor/bd99992gw.h | 90 | ||||
-rw-r--r-- | include/config.h | 1 | ||||
-rw-r--r-- | test/test_config.h | 3 | ||||
-rw-r--r-- | test/thermal.c | 53 |
6 files changed, 408 insertions, 0 deletions
diff --git a/driver/build.mk b/driver/build.mk index 53245463aa..8c30dd5b5a 100644 --- a/driver/build.mk +++ b/driver/build.mk @@ -52,6 +52,7 @@ driver-$(CONFIG_LED_DRIVER_LP5562)+=led/lp5562.o driver-$(CONFIG_REGULATOR_IR357X)+=regulator_ir357x.o # Temperature sensors +driver-$(CONFIG_TEMP_SENSOR_BD99992GW)+=temp_sensor/bd99992gw.o driver-$(CONFIG_TEMP_SENSOR_G781)+=temp_sensor/g781.o driver-$(CONFIG_TEMP_SENSOR_TMP006)+=temp_sensor/tmp006.o driver-$(CONFIG_TEMP_SENSOR_TMP432)+=temp_sensor/tmp432.o diff --git a/driver/temp_sensor/bd99992gw.c b/driver/temp_sensor/bd99992gw.c new file mode 100644 index 0000000000..b46d8512fe --- /dev/null +++ b/driver/temp_sensor/bd99992gw.c @@ -0,0 +1,260 @@ +/* Copyright 2015 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. + */ + +/* + * BD99992GW PMIC temperature sensor module for Chrome EC. + * Note that ADC / temperature sensor registers are only active while + * the PMIC is in S0. + */ + +#include "bd99992gw.h" +#include "chipset.h" +#include "common.h" +#include "console.h" +#include "gpio.h" +#include "hooks.h" +#include "i2c.h" +#include "temp_sensor.h" +#include "timer.h" +#include "util.h" + +/* Console output macros */ +#define CPUTS(outstr) cputs(CC_THERMAL, outstr) +#define CPRINTS(format, args...) cprints(CC_THERMAL, format, ## args) + +/* List of active channels, ordered by pointer register */ +static enum bd99992gw_adc_channel + active_channels[BD99992GW_ADC_POINTER_REG_COUNT]; + +/* + * Use 27ms as the period between ADC conversions, as we will typically be + * sampling temperature sensors every second, and 27ms is the longest + * supported period. + */ +#define ADC_LOOP_PERIOD BD99992GW_ADC1CNTL1_SLP27MS + +/* + * ADC-to-temp conversion assumes recommended thermistor / resistor + * configuration specified in datasheet (NCP15WB* / 24.9K). + * For 50C through 100C, use linear interpolation from discreet points + * in table below. For temps < 50C, use a simplified linear function. + */ +#define ADC_DISCREET_RANGE_START_TEMP 50 +/* 10 bit ADC result corresponding to START_TEMP */ +#define ADC_DISCREET_RANGE_START_RESULT 407 + +#define ADC_DISCREET_RANGE_LIMIT_TEMP 100 +/* 10 bit ADC result corresponding to LIMIT_TEMP */ +#define ADC_DISCREET_RANGE_LIMIT_RESULT 107 + +/* Table entries in steppings of 5C */ +#define ADC_DISCREET_RANGE_STEP 5 + +/* Discreet range ADC results (9 bit) per temperature, in 5 degree steps */ +static const uint8_t adc_result[] = { + 203, /* 50 C */ + 178, /* 55 C */ + 157, /* 60 C */ + 138, /* 65 C */ + 121, /* 70 C */ + 106, /* 75 C */ + 93, /* 80 C */ + 81, /* 85 C */ + 70, /* 90 C */ + 61, /* 95 C */ + 53, /* 100 C */ +}; + +/* + * From 20C (reasonable lower limit of temperatures we care about accuracy) + * to 50C, the temperature curve is roughly linear, so we don't need to include + * data points in our table. + */ +#define adc_to_temp(result) (ADC_DISCREET_RANGE_START_TEMP - \ + (((result) - ADC_DISCREET_RANGE_START_RESULT) * 3 + 16) / 32) + +static int raw_read8(const int offset, int *data_ptr) +{ + int ret; + ret = i2c_read8(I2C_PORT_THERMAL, BD99992GW_I2C_ADDR, offset, data_ptr); + if (ret != EC_SUCCESS) + CPRINTS("bd99992gw read fail %d\n", ret); + return ret; +} + +static int raw_write8(const int offset, int data) +{ + int ret; + ret = i2c_write8(I2C_PORT_THERMAL, BD99992GW_I2C_ADDR, offset, data); + if (ret != EC_SUCCESS) + CPRINTS("bd99992gw write fail %d\n", ret); + return ret; +} + +static void bd99992gw_init(void) +{ + int i; + int active_channel_count = 0; + uint8_t pointer_reg = BD99992GW_REG_ADC1ADDR0; + + /* Mark active channels from the board temp sensor table */ + for (i = 0; i < TEMP_SENSOR_COUNT; ++i) + if (temp_sensors[i].read == bd99992gw_get_val) + active_channels[active_channel_count++] = + temp_sensors[i].idx; + + /* Make sure we don't have too many active channels. */ + ASSERT(active_channel_count <= ARRAY_SIZE(active_channels)); + + /* Mark the first unused channel so we know where to stop searching */ + if (active_channel_count != ARRAY_SIZE(active_channels)) + active_channels[active_channel_count] = + BD99992GW_ADC_CHANNEL_NONE; + + /* Now write pointer regs with channel to monitor */ + for (i = 0; i < active_channel_count; ++i) + /* Write stop bit on last channel */ + if (raw_write8(pointer_reg + i, active_channels[i] | + ((i == active_channel_count - 1) ? + BD99992GW_ADC1ADDR_STOP : 0))) + return; + + /* Enable ADC interrupts */ + if (raw_write8(BD99992GW_REG_MADC1INT, 0xf & ~BD99992GW_MADC1INT_RND)) + return; + if (raw_write8(BD99992GW_REG_IRQLVL1MSK, BD99992GW_IRQLVL1MSK_MADC)) + return; + + /* Enable ADC sequencing */ + if (raw_write8(BD99992GW_REG_ADC1CNTL2, BD99992GW_ADC1CNTL2_ADCTHERM)) + return; + + /* Start round-robin conversions at 27ms period */ + raw_write8(BD99992GW_REG_ADC1CNTL1, ADC_LOOP_PERIOD | + BD99992GW_ADC1CNTL1_ADEN | BD99992GW_ADC1CNTL1_ADSTRT); +} +/* + * Some regs only work in S0, so we must initialize on AP startup in + * addition to INIT. + */ +DECLARE_HOOK(HOOK_INIT, bd99992gw_init, HOOK_PRIO_DEFAULT); +DECLARE_HOOK(HOOK_CHIPSET_RESUME, bd99992gw_init, HOOK_PRIO_DEFAULT); + +/* Convert ADC result to temperature in celsius */ +test_export_static int bd99992gw_get_temp(uint16_t adc) +{ + int temp; + int head, tail, mid; + uint8_t delta, step; + + /* Is ADC result in linear range? */ + if (adc >= ADC_DISCREET_RANGE_START_RESULT) { + temp = adc_to_temp(adc); + } + /* Hotter than our discreet range limit? */ + else if (adc <= ADC_DISCREET_RANGE_LIMIT_RESULT) { + temp = ADC_DISCREET_RANGE_LIMIT_TEMP; + } + /* We're in the discreet range */ + else { + /* Table uses 9 bit ADC values */ + adc /= 2; + + /* Binary search to find proper table entry */ + head = 0; + tail = ARRAY_SIZE(adc_result) - 1; + while (head != tail) { + mid = (head + tail) / 2; + if (adc_result[mid] >= adc && + adc_result[mid+1] < adc) + break; + if (adc_result[mid] > adc) + head = mid + 1; + else + tail = mid; + } + + /* Now fit between table entries using linear interpolation. */ + if (head != tail) { + delta = adc_result[mid] - adc_result[mid + 1]; + step = ((adc_result[mid] - adc) * + ADC_DISCREET_RANGE_STEP + delta / 2) / delta; + } else { + /* Edge case where adc = max */ + mid = head; + step = 0; + } + + temp = ADC_DISCREET_RANGE_START_TEMP + + ADC_DISCREET_RANGE_STEP * mid + step; + } + + return temp; +} + +/* Get temperature from requested sensor */ +int bd99992gw_get_val(int idx, int *temp_ptr) +{ + uint16_t adc; + int i, read, ret; + enum bd99992gw_adc_channel channel; + + /* ADC unit is only functional in S0 */ + if (!chipset_in_state(CHIPSET_STATE_ON)) + return EC_ERROR_NOT_POWERED; + + /* Find requested channel */ + for (i = 0; i < ARRAY_SIZE(active_channels); ++i) { + channel = active_channels[i]; + if (channel == idx || + channel == BD99992GW_ADC_CHANNEL_NONE) + break; + } + + /* Make sure we found it */ + if (i == ARRAY_SIZE(active_channels) || + active_channels[i] != idx) { + CPRINTS("Bad ADC channel %d\n", idx); + return EC_ERROR_INVAL; + } + + /* Pause conversions */ + ret = raw_write8(0x80, + ADC_LOOP_PERIOD | + BD99992GW_ADC1CNTL1_ADEN | + BD99992GW_ADC1CNTL1_ADSTRT | + BD99992GW_ADC1CNTL1_ADPAUSE); + if (ret) + return ret; + + /* Read 10-bit ADC result */ + ret = raw_read8(BD99992GW_REG_ADC1DATA0L + 2 * i, &read); + if (ret) + return ret; + adc = read; + ret = raw_read8(BD99992GW_REG_ADC1DATA0H + 2 * i, &read); + if (ret) + return ret; + adc |= read << 2; + + /* Convert temperature to C / K */ + *temp_ptr = C_TO_K(bd99992gw_get_temp(adc)); + + /* Clear interrupts */ + ret = raw_write8(BD99992GW_REG_ADC1INT, BD99992GW_ADC1INT_RND); + if (ret) + return ret; + ret = raw_write8(BD99992GW_REG_IRQLVL1, BD99992GW_IRQLVL1_ADC); + if (ret) + return ret; + + /* Resume conversions */ + ret = raw_write8(BD99992GW_REG_ADC1CNTL1, ADC_LOOP_PERIOD | + BD99992GW_ADC1CNTL1_ADEN | BD99992GW_ADC1CNTL1_ADSTRT); + if (ret) + return ret; + + return EC_SUCCESS; +} diff --git a/driver/temp_sensor/bd99992gw.h b/driver/temp_sensor/bd99992gw.h new file mode 100644 index 0000000000..27a9943de1 --- /dev/null +++ b/driver/temp_sensor/bd99992gw.h @@ -0,0 +1,90 @@ +/* Copyright 2015 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. + */ + +/* G781 temperature sensor module for Chrome EC */ + +#ifndef __CROS_EC_TEMP_SENSOR_BD99992GW_H +#define __CROS_EC_TEMP_SENSOR_BD99992GW_H + +#define BD99992GW_I2C_ADDR 0x60 + +/* ADC channels */ +enum bd99992gw_adc_channel { + BD99992GW_ADC_CHANNEL_NONE = -1, + BD99992GW_ADC_CHANNEL_BATTERY = 0, + BD99992GW_ADC_CHANNEL_AC = 1, + BD99992GW_ADC_CHANNEL_SYSTHERM0 = 2, + BD99992GW_ADC_CHANNEL_SYSTHERM1 = 3, + BD99992GW_ADC_CHANNEL_SYSTHERM2 = 4, + BD99992GW_ADC_CHANNEL_SYSTHERM3 = 5, + BD99992GW_ADC_CHANNEL_DIE_TEMP = 6, + BD99992GW_ADC_CHANNEL_VDC = 7, + BD99992GW_ADC_CHANNEL_COUNT = 8, +}; + +/* Registers */ +#define BD99992GW_REG_IRQLVL1 0x02 +#define BD99992GW_IRQLVL1_ADC (1 << 1) /* ADC IRQ asserted */ + +#define BD99992GW_REG_ADC1INT 0x03 +#define BD99992GW_ADC1INT_RND (1 << 0) /* RR cycle completed */ + +#define BD99992GW_REG_MADC1INT 0x0a +#define BD99992GW_MADC1INT_RND (1 << 0) /* RR cycle mask */ + +#define BD99992GW_REG_IRQLVL1MSK 0x13 +#define BD99992GW_IRQLVL1MSK_MADC (1 << 1) /* ADC IRQ mask */ + +#define BD99992GW_REG_ADC1CNTL1 0x80 +#define BD99992GW_ADC1CNTL1_SLP27MS (0x6 << 3) /* 27ms between pass */ +#define BD99992GW_ADC1CNTL1_NOLOOP (0x7 << 3) /* Single loop pass only */ +#define BD99992GW_ADC1CNTL1_ADPAUSE (1 << 2) /* ADC pause */ +#define BD99992GW_ADC1CNTL1_ADSTRT (1 << 1) /* ADC start */ +#define BD99992GW_ADC1CNTL1_ADEN (1 << 0) /* ADC enable */ + +#define BD99992GW_REG_ADC1CNTL2 0x81 +#define BD99992GW_ADC1CNTL2_ADCTHERM (1 << 0) /* Enable ADC sequencing */ + + /* ADC1 Pointer file regs - assign to proper bd99992gw_adc_channel */ +#define BD99992GW_ADC_POINTER_REG_COUNT 8 +#define BD99992GW_REG_ADC1ADDR0 0x82 +#define BD99992GW_REG_ADC1ADDR1 0x83 +#define BD99992GW_REG_ADC1ADDR2 0x84 +#define BD99992GW_REG_ADC1ADDR3 0x85 +#define BD99992GW_REG_ADC1ADDR4 0x86 +#define BD99992GW_REG_ADC1ADDR5 0x87 +#define BD99992GW_REG_ADC1ADDR6 0x88 +#define BD99992GW_REG_ADC1ADDR7 0x89 +#define BD99992GW_ADC1ADDR_STOP (1 << 3) /* Last conversion channel */ + +/* Result registers */ +#define BD99992GW_REG_ADC1DATA0L 0x95 +#define BD99992GW_REG_ADC1DATA0H 0x96 +#define BD99992GW_REG_ADC1DATA1L 0x97 +#define BD99992GW_REG_ADC1DATA1H 0x98 +#define BD99992GW_REG_ADC1DATA2L 0x99 +#define BD99992GW_REG_ADC1DATA2H 0x9a +#define BD99992GW_REG_ADC1DATA3L 0x9b +#define BD99992GW_REG_ADC1DATA3H 0x9c +#define BD99992GW_REG_ADC1DATA4L 0x9d +#define BD99992GW_REG_ADC1DATA4H 0x9e +#define BD99992GW_REG_ADC1DATA5L 0x9f +#define BD99992GW_REG_ADC1DATA5H 0xa0 +#define BD99992GW_REG_ADC1DATA6L 0xa1 +#define BD99992GW_REG_ADC1DATA6H 0xa2 +#define BD99992GW_REG_ADC1DATA7L 0xa3 +#define BD99992GW_REG_ADC1DATA7H 0xa4 + +/** + * Get the latest value from the sensor. + * + * @param idx ADC channel to read. + * @param temp_ptr Destination for temperature in K. + * + * @return EC_SUCCESS if successful, non-zero if error. + */ +int bd99992gw_get_val(int idx, int *temp_ptr); + +#endif /* __CROS_EC_TEMP_SENSOR_BD99992GW_H */ diff --git a/include/config.h b/include/config.h index 2b1152468d..a732d2af5c 100644 --- a/include/config.h +++ b/include/config.h @@ -1451,6 +1451,7 @@ #undef CONFIG_TEMP_SENSOR /* Support particular temperature sensor chips */ +#undef CONFIG_TEMP_SENSOR_BD99992GW /* BD99992GW PMIC, on I2C bus */ #undef CONFIG_TEMP_SENSOR_G781 /* G781 sensor, on I2C bus */ #undef CONFIG_TEMP_SENSOR_TMP006 /* TI TMP006 sensor, on I2C bus */ #undef CONFIG_TEMP_SENSOR_TMP432 /* TI TMP432 sensor, on I2C bus */ diff --git a/test/test_config.h b/test/test_config.h index bc517314b6..e2ad96fbad 100644 --- a/test/test_config.h +++ b/test/test_config.h @@ -78,6 +78,9 @@ int board_discharge_on_ac(int enabled); #define CONFIG_CHIPSET_CAN_THROTTLE #define CONFIG_FANS 1 #define CONFIG_TEMP_SENSOR +#define CONFIG_TEMP_SENSOR_BD99992GW +#define I2C_PORT_THERMAL 1 +int bd99992gw_get_temp(uint16_t adc); #endif #ifdef TEST_FAN diff --git a/test/thermal.c b/test/thermal.c index 10662ffd7d..761dc9358d 100644 --- a/test/thermal.c +++ b/test/thermal.c @@ -480,6 +480,58 @@ static int test_several_limits(void) return EC_SUCCESS; } +/* Tests for bd99992gw temperature sensor ADC-to-temp calculation */ +#define LOW_ADC_TEST_VALUE 887 /* 0 C */ +#define HIGH_ADC_TEST_VALUE 100 /* > 100C */ + +static int test_bd99992_adc_to_temp(void) +{ + int i; + uint8_t temp; + uint8_t new_temp; + + /* ADC value to temperature table, data from datasheet */ + struct { + int adc; + int temp; + } adc_temp_datapoints[] = { + { 615, 30 }, + { 561, 35 }, + { 508, 40 }, + { 407, 50 }, + { 315, 60 }, + { 243, 70 }, + { 186, 80 }, + { 140, 90 }, + { 107, 100 }, + }; + + + /* + * Verify that calculated temp is decreasing for entire ADC range, + * and that a tick down in ADC value results in no more than 1C + * decrease. + */ + i = LOW_ADC_TEST_VALUE; + temp = bd99992gw_get_temp(i); + + while (--i > HIGH_ADC_TEST_VALUE) { + new_temp = bd99992gw_get_temp(i); + TEST_ASSERT(new_temp == temp || + new_temp == temp + 1); + temp = new_temp; + } + + /* Verify several datapoints are within 1C accuracy */ + for (i = 0; i < ARRAY_SIZE(adc_temp_datapoints); ++i) { + temp = bd99992gw_get_temp(adc_temp_datapoints[i].adc); + ASSERT(temp >= adc_temp_datapoints[i].temp - 1 && + temp <= adc_temp_datapoints[i].temp + 1); + } + + return EC_SUCCESS; +} + void run_test(void) { @@ -492,5 +544,6 @@ void run_test(void) RUN_TEST(test_one_limit); RUN_TEST(test_several_limits); + RUN_TEST(test_bd99992_adc_to_temp); test_print_result(); } |