summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--driver/build.mk1
-rw-r--r--driver/temp_sensor/bd99992gw.c260
-rw-r--r--driver/temp_sensor/bd99992gw.h90
-rw-r--r--include/config.h1
-rw-r--r--test/test_config.h3
-rw-r--r--test/thermal.c53
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();
}