/* 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 . */ #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 #include #include #include #include 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 = <c3675_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 = <c3675_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 = <c3675_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 = <c3675_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))); } } }