aboutsummaryrefslogtreecommitdiffstats
path: root/firmware/e300/battery/pmu.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/e300/battery/pmu.c')
-rw-r--r--firmware/e300/battery/pmu.c686
1 files changed, 686 insertions, 0 deletions
diff --git a/firmware/e300/battery/pmu.c b/firmware/e300/battery/pmu.c
new file mode 100644
index 000000000..bd337783f
--- /dev/null
+++ b/firmware/e300/battery/pmu.c
@@ -0,0 +1,686 @@
+/* USRP E310 Firmware PMU
+ * Copyright (C) 2014 Ettus Research
+ * This file is part of the USRP E310 Firmware
+ * The USRP E310 Firmware is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ * The USRP E310 Firmware is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with the USRP E310 Firmware. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "adc.h"
+#include "bq2419x.h"
+#include "eeprom.h"
+#include "fpga.h"
+#include "mcu_settings.h"
+#include "io.h"
+#include "led.h"
+#include "ltc3675.h"
+#include "ltc294x.h"
+#include "tps54478.h"
+#include "timer.h"
+#include "utils.h"
+
+#include <stdlib.h>
+
+#include <avr/interrupt.h>
+#include <avr/wdt.h>
+#include <util/delay.h>
+#include <util/atomic.h>
+
+void pmu_power_on(void);
+void pmu_power_down(void);
+
+/* if we sense less than 2000 mV we assume battery is not there */
+static const uint16_t PMU_BAT_MIN_VOLTAGE = 2000;
+
+/* wait 10 ms, such random, so magic, wow */
+static const uint8_t PMU_FPGA_RESET_DELAY = 10;
+
+/* more magic wait constants */
+static const uint8_t PMU_USB_CLK_WAIT = 200;
+static const uint8_t PMU_FTDI_WAIT = 100;
+
+static io_pin_t VBAT = IO_PC(0);
+static io_pin_t POWER_LED = IO_PC(7);
+static io_pin_t CHARGE = IO_PD(1);
+static io_pin_t USB_RESETn = IO_PA(2);
+static io_pin_t FTDI_RESETn = IO_PB(6);
+static io_pin_t FTDI_CBUS3 = IO_PB(7);
+static io_pin_t USB_CLK_EN = IO_PA(1);
+static io_pin_t AVR_RESET = IO_PC(6);
+static io_pin_t AVR_IRQ = IO_PD(5);
+static io_pin_t PS_POR = IO_PD(6);
+static io_pin_t PS_SRST = IO_PD(7);
+static io_pin_t OVERTEMP = IO_PC(2);
+
+static uint16_t last_full_charge;
+static uint16_t charge_on_last_unplug;
+static bool battery_present_last;
+
+static const uint8_t PMU_BLINK_ERROR_DELAY_MS = 250;
+static const uint8_t PMU_BLINK_ERROR_TICKS_PER_BLINK = 10;
+
+typedef enum pmu_state {
+ OFF,
+ BOOT,
+ SHUTDOWN,
+ ON
+} pmu_state_t;
+
+static pmu_state_t state;
+static volatile bool pmu_fpga_event;
+
+typedef enum pmu_error {
+ PMU_ERROR_NONE = 0x00,
+ PMU_ERROR_LOW_VOLTAGE = 0x01,
+ PMU_ERROR_REG_LOW_VOLTAGE = 0x02,
+ PMU_ERROR_FPGA_POWER = 0x03,
+ PMU_ERROR_DRAM_POWER = 0x04,
+ PMU_ERROR_1_8V = 0x05,
+ PMU_ERROR_3_3V = 0x06,
+ PMU_ERROR_TX_POWER = 0x07,
+ PMU_ERROR_CHARGER_TEMP = 0x08,
+ PMU_ERROR_CHARGER_ERROR = 0x09,
+ PMU_ERROR_BATTERY_LOW = 0x0a,
+ PMU_ERROR_GAUGE_TEMP = 0x0b,
+ PMU_ERROR_GLOBAL_TEMP = 0x0c,
+} pmu_error_t;
+
+static volatile pmu_error_t pmu_error;
+
+/* this cannot be static const because
+ * 'All the expressions in an initializer for an object
+ * that has static storage duration shall be constant expressions
+ * or string literals.' [section 6.7.8/4, C standard */
+#ifdef DDR3L
+#define DRAM_VOLTAGE 1350
+#else
+#define DRAM_VOLTAGE 0
+#endif /* DDR3L */
+
+static ltc3675_pmu_regulator_t PS_VDRAM = {
+ .pmu_reg = {
+ .ops = &ltc3675_ops,
+ .powered = false,
+ .voltage = DRAM_VOLTAGE /* DRAM_VOLTAGE */,
+ .error_code = PMU_ERROR_DRAM_POWER,
+ },
+ .ltc3675_reg = LTC3675_REG_1,
+};
+
+static ltc3675_pmu_regulator_t PS_PERIPHERALS_1_8 = {
+ .pmu_reg = {
+ .ops = &ltc3675_ops,
+ .powered = false,
+ .voltage = 0 /*1800 hardware default? */,
+ .error_code = PMU_ERROR_1_8V,
+ },
+ .ltc3675_reg = LTC3675_REG_3,
+};
+
+static ltc3675_pmu_regulator_t PS_PERIPHERALS_3_3 = {
+ .pmu_reg = {
+ .ops = &ltc3675_ops,
+ .powered = false,
+ .voltage = 0 /*3300 hardware default */,
+ .error_code = PMU_ERROR_3_3V,
+ },
+ .ltc3675_reg = LTC3675_REG_6,
+};
+
+static ltc3675_pmu_regulator_t PS_TX = {
+ .pmu_reg = {
+ .ops = &ltc3675_ops,
+ .powered = false,
+ .voltage = 0 /*5000 hardware default? */,
+ .error_code = PMU_ERROR_TX_POWER,
+ },
+ .ltc3675_reg = LTC3675_REG_5,
+};
+
+static tps54478_pmu_regulator_t PS_FPGA = {
+ .pmu_reg = {
+ .ops = &tps54478_ops,
+ .powered = false,
+ .voltage = 1000,
+ .error_code = PMU_ERROR_FPGA_POWER,
+ },
+};
+
+static pmu_regulator_t *boot_order[] = {
+ &PS_FPGA.pmu_reg,
+ &PS_VDRAM.pmu_reg,
+ &PS_PERIPHERALS_1_8.pmu_reg,
+ &PS_TX.pmu_reg,
+ &PS_PERIPHERALS_3_3.pmu_reg,
+};
+
+static pmu_button_t *button;
+void pmu_register_button(pmu_button_t *pmu_button)
+{
+ button = pmu_button;
+}
+
+static pmu_charger_t *charger;
+void pmu_register_charger(pmu_charger_t *pmu_charger)
+{
+ charger = pmu_charger;
+}
+
+static pmu_gauge_t *gauge;
+void pmu_register_gauge(pmu_gauge_t *pmu_gauge)
+{
+ gauge = pmu_gauge;
+}
+
+/**
+ * \brief Reads the battery voltage from ADC0
+ *
+ * Vout = (375k / (274k + 357k)) * Vbat
+ * Vbat = (Vout * (274k + 357k)) / 357k
+ *
+ * ADC = (Vin * 1024) / Vref
+ * Vin = (ADC * Vref) / 1024
+ * Vref = 3.3V
+ * Vbat(mV) = 100 * (((ADC * 3.3) / 1024) * (274k + 357k)) / 357k
+ * Vbat(mV) ~= ADC * 5.7
+ */
+static uint16_t pmu_battery_voltage(void)
+{
+ uint16_t tmp;
+
+ tmp = adc_single_shot();
+ tmp *= 5.6961f;
+ return (uint16_t) tmp;
+}
+
+static inline bool pmu_battery_present(void)
+{
+ return (pmu_battery_voltage() > PMU_BAT_MIN_VOLTAGE);
+}
+
+static void pmu_reset_fpga(bool delay)
+{
+ io_clear_pin(PS_POR);
+ io_clear_pin(PS_SRST);
+
+ if (delay)
+ _delay_ms(PMU_FPGA_RESET_DELAY);
+
+ io_set_pin(PS_POR);
+ io_set_pin(PS_SRST);
+}
+
+int8_t pmu_init(void)
+{
+ int8_t ret;
+ bool battery_present;
+
+ state = OFF;
+
+ /* make the LED outputs */
+ io_output_pin(CHARGE);
+ io_output_pin(POWER_LED);
+
+ /* initialize the ADC, so we can sense the battery */
+ adc_init();
+
+ /* initialize TPS54478 for core power */
+ tps54478_init(true);
+
+ /* wiggle USB and FTDI pins */
+ io_input_pin(USB_RESETn);
+ io_output_pin(FTDI_RESETn);
+ io_output_pin(USB_CLK_EN);
+ io_input_pin(FTDI_CBUS3);
+
+ /* make OVERTEMP input pin */
+ io_input_pin(OVERTEMP);
+
+ /* initialize the charger */
+ ret = bq2419x_init();
+ if (ret)
+ goto fail_bq2419x;
+
+ /* wait a sec */
+ _delay_ms(1000);
+
+ /* wdt setup */
+ cli();
+ WDTCSR |= BIT(WDCE) | BIT(WDE);
+ WDTCSR = BIT(WDIE);
+ sei();
+
+ /* see if we got a battery */
+ battery_present = pmu_battery_present();
+ battery_present_last = battery_present;
+
+ if (battery_present) {
+ last_full_charge = eeprom_get_last_full();
+ ret = ltc294x_init(LTC294X_MODEL_2942);
+ }
+ if (ret)
+ return ret;
+
+ ret = ltc3675_init();
+ if (ret)
+ goto fail_ltc3675;
+
+
+ /* need to hold them low until power is stable */
+ io_output_pin(PS_POR);
+ io_output_pin(PS_SRST);
+ io_clear_pin(PS_POR);
+ io_clear_pin(PS_SRST);
+
+ /* TODO: Not sure if needed */
+ io_input_pin(AVR_RESET);
+
+ /* TODO: This will probably need to change */
+ io_input_pin(AVR_IRQ);
+ io_set_pin(AVR_IRQ); // enable pull-up ?
+
+ /* configure and enable interrupts */
+ interrupt_init();
+
+ /* initialize the timers */
+ timer0_init();
+ timer1_init();
+
+ state = OFF;
+
+ return 0;
+
+fail_ltc3675:
+fail_bq2419x:
+ return -1;
+}
+
+#define is_off (OFF == state)
+#define is_on (ON == state)
+#define is_booting (BOOT == state)
+
+static inline int8_t pmu_set_regulator(pmu_regulator_t *reg, bool on)
+{
+ return reg->ops->set_regulator(reg, on);
+}
+
+void pmu_power_on(void)
+{
+ uint8_t i;
+ int8_t ret;
+ pmu_regulator_t *reg;
+
+ /* if somehow this gets called twice, bail early on */
+ if (is_booting)
+ return;
+ else if (is_on)
+ return;
+ else
+ state = BOOT;
+
+ /* reset the fpga */
+ pmu_reset_fpga(true);
+ fpga_init();
+
+ for (i = 0; i < ARRAY_SIZE(boot_order); i++) {
+ reg = boot_order[i];
+ /* if regulator set a on/off function, call it */
+ if (reg->ops->set_regulator) {
+ ret = pmu_set_regulator(reg, true);
+ if (ret) {
+ pmu_error = reg->error_code;
+ goto fail_regulators;
+ }
+ }
+
+ /* if regulator set a set_voltage function, call it */
+ if (reg->ops->set_voltage && reg->voltage) {
+ ret = reg->ops->set_voltage(reg, reg->voltage);
+ if (ret) {
+ pmu_error = reg->error_code;
+ goto fail_regulators;
+ }
+ }
+
+ /* if we got here, this means all is well */
+ reg->powered = true;
+ }
+
+ /* enable the usb clock */
+ io_set_pin(USB_CLK_EN);
+ _delay_ms(PMU_USB_CLK_WAIT);
+ io_set_pin(FTDI_RESETn);
+ _delay_ms(PMU_FTDI_WAIT);
+
+ /* power for the fpga should be up now, let it run */
+ pmu_reset_fpga(false);
+
+ state = ON;
+
+ return;
+
+fail_regulators:
+ /* TODO: Turn of stuff again in reverse order */
+ return;
+}
+
+static inline enum pmu_status pmu_battery_get_status(pmu_charger_t *pmu_charger)
+{
+ return pmu_charger->ops->get_battery_status
+ ? pmu_charger->ops->get_battery_status(pmu_charger) : 0;
+}
+
+void pmu_power_down(void)
+{
+ int8_t i;
+ int8_t ret;
+ pmu_regulator_t *reg;
+
+ state = SHUTDOWN;
+
+ /* keep zynq in reset,
+ * TODO: do we need to also clear PS_POR? */
+ io_clear_pin(PS_SRST);
+
+ /* turn off usb clock */
+ io_clear_pin(USB_CLK_EN);
+
+ for (i = ARRAY_SIZE(boot_order) - 1; i >= 0; i--) {
+ reg = boot_order[i];
+ if (reg->ops->set_regulator) {
+ ret = pmu_set_regulator(reg, false);
+ if (ret)
+ goto fail_regulators;
+ }
+
+ /* if we got here, this means regulator is off */
+ reg->powered = false;
+ }
+
+ state = OFF;
+
+ _delay_ms(1000);
+
+ return;
+
+fail_regulators:
+ /* for now set solid red */
+ pmu_error = reg->error_code;
+}
+
+static inline int8_t pmu_charger_check_events(pmu_charger_t *ch)
+{
+ return ch->ops->check_events
+ ? ch->ops->check_events(ch) : 0;
+}
+
+static inline int8_t pmu_regulator_check_events(pmu_regulator_t *reg)
+{
+ return reg->ops->check_events
+ ? reg->ops->check_events(reg) : 0;
+}
+
+static inline uint8_t pmu_button_check_events(pmu_button_t *pmu_button)
+{
+ return pmu_button->ops->check_events
+ ? pmu_button->ops->check_events(pmu_button) : 0;
+}
+
+static inline uint8_t pmu_charger_get_charge_type(pmu_charger_t *pmu_charger)
+{
+ return pmu_charger->ops->get_charge_type
+ ? pmu_charger->ops->get_charge_type(pmu_charger) : 0;
+}
+
+static inline uint8_t pmu_charger_get_health(pmu_charger_t *pmu_charger)
+{
+ return pmu_charger->ops->get_charger_health
+ ? pmu_charger->ops->get_charger_health(pmu_charger) : 0;
+}
+
+static inline uint8_t pmu_battery_get_health(pmu_charger_t *pmu_charger)
+{
+ return charger->ops->get_battery_health(pmu_charger);
+}
+
+static inline uint8_t pmu_battery_get_temp_alert(pmu_charger_t *pmu_charger)
+{
+ return pmu_charger->ops->get_temp_alert
+ ? pmu_charger->ops->get_temp_alert(pmu_charger) : 0;
+}
+
+static inline bool pmu_charger_get_online(pmu_charger_t *pmu_charger)
+{
+ return pmu_charger->ops->get_charger_online
+ ? pmu_charger->ops->get_charger_online(pmu_charger) : 0;
+}
+
+static inline bool pmu_battery_get_online(pmu_charger_t *pmu_charger)
+{
+ return pmu_charger->ops->get_battery_online
+ ? pmu_charger->ops->get_battery_online(pmu_charger) : 0;
+}
+
+static inline uint8_t pmu_gauge_check_events(void)
+{
+ return gauge->ops->check_events
+ ? gauge->ops->check_events() : 0;
+}
+
+static inline uint16_t pmu_gauge_get_temperature(void)
+{
+ return gauge->ops->get_temperature
+ ? gauge->ops->get_temperature() : 0;
+}
+
+static inline uint16_t pmu_gauge_get_charge(void)
+{
+ return gauge->ops->get_charge();
+}
+
+static inline void pmu_gauge_set_charge(uint16_t val)
+{
+ gauge->ops->set_charge(val);
+}
+
+static inline uint16_t pmu_gauge_get_voltage(void)
+{
+ return gauge->ops->get_voltage();
+}
+
+static inline void pmu_gauge_set_low_threshold(uint16_t val)
+{
+ if (gauge->ops->set_low_threshold)
+ gauge->ops->set_low_threshold(val);
+}
+
+static inline bool pmu_is_charging(void)
+{
+ if (charger)
+ return PMU_STATUS_CHARGING == pmu_battery_get_status(charger);
+
+ return false;
+}
+
+static inline bool pmu_is_full(void)
+{
+ if (charger)
+ return PMU_STATUS_FULL == pmu_battery_get_status(charger);
+
+ return false;
+}
+
+void pmu_handle_events(void)
+{
+ uint8_t flags;
+ uint16_t val;
+ bool battery_present = pmu_battery_present();
+ bool is_charging = false;
+ bool is_full = false;
+ bool overtemp = io_test_pin(OVERTEMP);
+
+ /* check if someone plugged the battery late,
+ * if so init gauge */
+ if (battery_present && !battery_present_last) {
+ ltc294x_init(LTC294X_MODEL_2942);
+ pmu_gauge_set_charge(charge_on_last_unplug);
+ } else if (!battery_present && battery_present_last) {
+ gauge = NULL;
+ charge_on_last_unplug = pmu_gauge_get_charge();
+ }
+ battery_present_last = battery_present;
+
+ if (overtemp) {
+ fpga_set_gauge_status(BIT(6));
+ pmu_error = PMU_ERROR_GLOBAL_TEMP;
+ }
+
+ if (battery_present) {
+ is_charging = pmu_is_charging();
+ is_full = pmu_is_full();
+ }
+
+ /* resolve errors if we can */
+ if (pmu_error != PMU_ERROR_NONE) {
+ switch (pmu_error) {
+ case PMU_ERROR_BATTERY_LOW:
+ if (is_off || is_charging)
+ pmu_error = PMU_ERROR_NONE;
+ break;
+ case PMU_ERROR_CHARGER_TEMP:
+ if (!is_charging)
+ pmu_error = PMU_ERROR_NONE;
+ break;
+ case PMU_ERROR_GLOBAL_TEMP:
+ if (!overtemp)
+ pmu_error = PMU_ERROR_NONE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ (void) pmu_regulator_check_events(&PS_FPGA.pmu_reg);
+
+ (void) pmu_regulator_check_events(&PS_VDRAM.pmu_reg);
+
+ flags = pmu_button_check_events(button);
+ if (is_off && (flags & PMU_BUTTON_EVENT_MASK_WAKEUP))
+ pmu_power_on();
+
+ else if (is_on && (flags & PMU_BUTTON_EVENT_MASK_POWERDOWN))
+ pmu_power_down();
+
+ /* if no battery present, no point ... */
+ if (battery_present) {
+ flags = pmu_charger_check_events(charger);
+
+ if (flags != PMU_CHARGER_EVENT_NONE) {
+ if ((flags & PMU_CHARGER_EVENT_FAULT_CHANGE)
+ || (flags & PMU_CHARGER_EVENT_STATUS_CHANGE)) {
+ uint8_t health = pmu_battery_get_health(charger);
+ switch (health) {
+ case PMU_HEALTH_OVERHEAT:
+ pmu_power_down();
+ pmu_error = PMU_ERROR_CHARGER_TEMP;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if ((flags & PMU_CHARGER_EVENT_CHARGE_DONE)) {
+ last_full_charge = pmu_gauge_get_charge();
+ pmu_gauge_set_low_threshold(last_full_charge / 10);
+ eeprom_set_last_full_charge(last_full_charge);
+ }
+ }
+
+ flags = pmu_gauge_check_events();
+ if (flags != PMU_GAUGE_EVENT_NONE) {
+ if (flags & PMU_GAUGE_CHARGE_LO) {
+ if (!is_charging) {
+ fpga_set_gauge_status(BIT(7));
+ pmu_error = PMU_ERROR_BATTERY_LOW;
+ }
+ }
+
+ if (flags & PMU_GAUGE_TEMP_HI) {
+ fpga_set_gauge_status(BIT(6));
+ pmu_error = PMU_ERROR_GAUGE_TEMP;
+ }
+
+ if (flags & PMU_GAUGE_TEMP_LO) {
+ fpga_set_gauge_status(BIT(6));
+ pmu_error = PMU_ERROR_GAUGE_TEMP;
+ }
+ }
+ }
+
+ /* blink error codes ... */
+ switch (pmu_error) {
+ case PMU_ERROR_NONE:
+ if (is_off) {
+ if (is_charging)
+ led_set_blink(LED_BLINK_GREEN_SLOW);
+ else
+ led_set_solid(LED_OFF);
+ } else if (is_on) {
+ if (is_charging)
+ led_set_blink(LED_BLINK_GREEN_FAST);
+ else if (is_full || !battery_present)
+ led_set_solid(LED_GREEN);
+ else if (battery_present)
+ led_set_solid(LED_ORANGE);
+ else
+ led_set_solid(LED_GREEN);
+ }
+ break;
+ case PMU_ERROR_BATTERY_LOW:
+ if (!is_charging && is_on)
+ led_set_blink(LED_BLINK_ORANGE);
+ break;
+ default:
+ led_set_blink_seq(pmu_error, LED_BLINK_RED_FAST);
+ break;
+ };
+
+ fpga_set_charger_health(pmu_charger_get_health(charger));
+ fpga_set_charger_online(pmu_charger_get_online(charger));
+ if (battery_present) {
+ fpga_set_charger_charge_type(pmu_charger_get_charge_type(charger));
+ fpga_set_battery_voltage(pmu_battery_voltage());
+ fpga_set_battery_temp_alert(pmu_battery_get_temp_alert(charger));
+ fpga_set_battery_status(pmu_battery_get_status(charger));
+ fpga_set_battery_health(pmu_battery_get_health(charger));
+ fpga_set_battery_online(pmu_battery_get_online(charger));
+ fpga_set_gauge_charge(pmu_gauge_get_charge());
+ fpga_set_gauge_charge_last_full(last_full_charge);
+ fpga_set_gauge_temp(pmu_gauge_get_temperature());
+ fpga_set_gauge_voltage(pmu_gauge_get_voltage());
+ }
+ if (state != OFF) {
+ fpga_sync();
+ if (fpga_get_write_charge()) {
+ val = fpga_get_gauge_charge();
+ pmu_gauge_set_charge(val);
+ if (pmu_error == PMU_ERROR_BATTERY_LOW)
+ pmu_error = PMU_ERROR_NONE;
+ }
+
+ if (fpga_get_shutdown())
+ pmu_power_down();
+
+ if (fpga_get_write_settings()) {
+ eeprom_set_autoboot(fpga_get_settings() & BIT(0));
+ pmu_set_regulator(&PS_TX.pmu_reg, !!(fpga_get_settings() & BIT(1)));
+ }
+ }
+}