/* 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 "thermistor.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 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 */ static int bd99992gw_get_temp(uint16_t adc) { #ifdef CONFIG_THERMISTOR_NCP15WB return ncp15wb_calculate_temp(adc); #else #error "Unknown thermistor for bd99992gw" return 0; #endif } /* 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; }