diff options
Diffstat (limited to 'firmware/e300/battery/ltc3675.c')
-rw-r--r-- | firmware/e300/battery/ltc3675.c | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/firmware/e300/battery/ltc3675.c b/firmware/e300/battery/ltc3675.c new file mode 100644 index 000000000..05490a5d6 --- /dev/null +++ b/firmware/e300/battery/ltc3675.c @@ -0,0 +1,382 @@ +/* USRP E310 Firmware Linear Technology LTC3765 driver + * 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 "fpga.h" +#include "i2c_twi.h" +#include "io.h" +#include "interrupt.h" +#include "ltc3675.h" +#include "mcu_settings.h" +#include "utils.h" +#include "timer.h" + +#include <avr/interrupt.h> +#include <util/delay.h> +#include <util/atomic.h> + +static const uint8_t LTC3675_I2C_ADDR = 0x09; + +#define ltc3675_read(reg, val) \ + (i2c_twi_read(LTC3675_I2C_ADDR, reg, val)) + +#define ltc3675_write(reg, val) \ + (i2c_twi_write(LTC3675_I2C_ADDR, reg, val)) + +/* registers */ +static const uint8_t LTC3675_REG_NONE = 0x00; +static const uint8_t LTC3675_REG_BUCK1 = 0x01; +static const uint8_t LTC3675_REG_BUCK2 = 0x02; +static const uint8_t LTC3675_REG_BUCK3 = 0x03; +static const uint8_t LTC3675_REG_BUCK4 = 0x04; +static const uint8_t LTC3675_REG_BOOST = 0x05; +static const uint8_t LTC3675_REG_BUCK_BOOST = 0x06; +static const uint8_t LTC3675_REG_LED_CONFIG = 0x07; +static const uint8_t LTC3675_REG_LED_DAC = 0x08; +static const uint8_t LTC3675_REG_UVOT = 0x09; +static const uint8_t LTC3675_REG_RSTB = 0x0a; +static const uint8_t LTC3675_REG_IRQB_MASK = 0x0b; +static const uint8_t LTC3675_REG_RT_STATUS = 0x0c; +static const uint8_t LTC3675_REG_LAT_STATUS = 0x0d; +static const uint8_t LTC3675_REG_CLEAR_IRQ = 0x0f; + +static const uint8_t LTC3675_UNDER_VOLTAGE_MASK = BIT(7); +static const uint8_t LTC3675_UNDER_VOLTAGE_SHIFT = 7; +static const uint8_t LTC3675_OVER_TEMPERATURE_MASK = BIT(6); +static const uint8_t LTC3675_OVER_TEMPERATURE_SHIFT = 6; +static const uint8_t LTC3675_BUCK_BOOST_PGOOD_MASK = BIT(5); +static const uint8_t LTC3675_BUCK_BOOST_PGOOD_SHIFT = 5; +static const uint8_t LTC3675_BOOST_PGOOD_MASK = BIT(4); +static const uint8_t LTC3675_BOOST_PGOOD_SHIFT = 4; +static const uint8_t LTC3675_BUCK4_PGOOD_MASK = BIT(3); +static const uint8_t LTC3675_BUCK4_PGOOD_SHIFT = 3; +static const uint8_t LTC3675_BUCK3_PGOOD_MASK = BIT(2); +static const uint8_t LTC3675_BUCK3_PGOOD_SHIFT = 2; +static const uint8_t LTC3675_BUCK2_PGOOD_MASK = BIT(1); +static const uint8_t LTC3675_BUCK2_PGOOD_SHIFT = 1; +static const uint8_t LTC3675_BUCK1_PGOOD_MASK = BIT(0); +static const uint8_t LTC3675_BUCK1_PGOOD_SHIFT = 0; + +static const uint8_t LTC3675_ENABLE_REGISTER_BIT = 0x80; + +struct ltc3675_button { + pmu_button_t pmu_button; + + volatile bool onswitch_press_event; + volatile bool onswitch_release_event; + volatile bool onswitch_last_state; + + volatile bool poweroff_event; + + volatile bool wakeup_event; +}; + +static struct ltc3675_button button; + +/** arbitrary wait to give the external supply can settle */ +static const uint8_t LTC3675_REG_ENABLE_DELAY = 10; + +static io_pin_t PWR_IRQ = IO_PD(0); +static io_pin_t WAKEUP = IO_PD(2); +static io_pin_t ONSWITCH_DB = IO_PD(3); +static io_pin_t PWR_RESET = IO_PD(4); + +static void ltc3675_clear_interrupts(void) +{ + ltc3675_write(LTC3675_REG_CLEAR_IRQ, 0x00); + ltc3675_write(LTC3675_REG_NONE, 0x00); +} + +static int8_t ltc3675_set_regulator_helper(uint8_t reg, bool on) +{ + int8_t ret; + uint8_t val; + ret = ltc3675_read(reg, &val); + if (ret) + goto fail_i2c_read; + if (on) + val |= LTC3675_ENABLE_REGISTER_BIT; + else + val &= ~LTC3675_ENABLE_REGISTER_BIT; + ret = ltc3675_write(reg, val); + if (ret) + goto fail_i2c_write; + + if (on) + _delay_ms(LTC3675_REG_ENABLE_DELAY); + + return 0; + +fail_i2c_write: +fail_i2c_read: + return ret; +} + +static inline int8_t ltc3675_get_realtime_status(uint8_t *val) +{ + int8_t ret; + + ret = ltc3675_read(LTC3675_REG_RT_STATUS, val); + if (ret) + return ret; + return 0; +} + +static bool ltc3675_get_power_good(uint8_t mask) +{ + uint8_t val; + int8_t ret; + + ret = ltc3675_get_realtime_status(&val); + if (ret) + return false; + + return !!(mask & val); +} + +static int8_t ltc3675_set_regulator(pmu_regulator_t *reg, bool on) +{ + int8_t ret; + bool status; + ltc3675_pmu_regulator_t *pmu; + + pmu = container_of(reg, ltc3675_pmu_regulator_t, pmu_reg); + + switch (pmu->ltc3675_reg) { + case LTC3675_REG_1: /* master */ + case LTC3675_REG_2: /* slave */ + ret = ltc3675_set_regulator_helper(LTC3675_REG_BUCK1, on); + if (ret) + return ret; + status = ltc3675_get_power_good(LTC3675_BUCK1_PGOOD_MASK); + return (status == on) ? 0 : -1; + case LTC3675_REG_3: /* master */ + case LTC3675_REG_4: /* slave */ + ret = ltc3675_set_regulator_helper(LTC3675_REG_BUCK3, on); + if (ret) + return ret; + status = ltc3675_get_power_good(LTC3675_BUCK3_PGOOD_MASK); + return (status == on) ? 0 : -1; + case LTC3675_REG_5: + ret = ltc3675_set_regulator_helper(LTC3675_REG_BOOST, on); + if (ret) + return ret; + status = ltc3675_get_power_good(LTC3675_BOOST_PGOOD_MASK); + return (status == on) ? 0 : -1; + case LTC3675_REG_6: /* single */ + ret = ltc3675_set_regulator_helper(LTC3675_REG_BUCK_BOOST, on); + if (ret) + return ret; + status = ltc3675_get_power_good(LTC3675_BUCK_BOOST_PGOOD_MASK); + return (status == on) ? 0 : -1; + default: + return -1; + } + + return 0; +} + +static int8_t ltc3675_set_voltage(pmu_regulator_t *reg, uint16_t v) +{ + uint32_t r_fb, r; + uint16_t vmax, r_dac; + uint8_t addr, val; + int8_t ret; + ltc3675_pmu_regulator_t *pmu; + + pmu = container_of(reg, ltc3675_pmu_regulator_t, pmu_reg); + + switch (pmu->ltc3675_reg) { + case LTC3675_REG_1: /* 1A Buck */ + case LTC3675_REG_2: /* 1A Buck */ + vmax = 1500; + addr = LTC3675_REG_BUCK1; + break; + case LTC3675_REG_3: /* 500mA Buck */ + case LTC3675_REG_4: /* 500mA Buck */ + vmax = 1800; + addr = LTC3675_REG_BUCK3; + break; + case LTC3675_REG_5: /* 1A Boost */ + vmax = 5000; + addr = LTC3675_REG_BOOST; + break; + case LTC3675_REG_6: /* 1 A Buck-Boost */ + vmax = 3300; + addr = LTC3675_REG_BUCK_BOOST; + break; + default: + return -1; /* TODO: Should return useful error code */ + } + + if (v > vmax) + return -1; /* TODO: Should return useful error code. */ + + r_fb = ((uint32_t) vmax * 1000) / (uint32_t) 800; /* 800mV full-scale feedback voltage */ + r = ((uint32_t) v * 1000) / r_fb ; + + if (r < 450) + return -1; + + r_dac = (16 * ((uint16_t) r - 450)) / (800 - 450); + + ret = ltc3675_read(addr, &val); + if (ret) + return ret; + + val = (val & 0xf0) | ((uint8_t) r_dac); + ret = ltc3675_write(addr, val); + if (ret) + return ret; + + return 0; +} + +static uint8_t ltc3675_button_check_events(pmu_button_t *pmu_button) +{ + uint8_t flags; + struct ltc3675_button *ltc3675_button; + flags = 0x00; + ltc3675_button = container_of( + pmu_button, struct ltc3675_button, pmu_button); + + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) + { + if (ltc3675_button->onswitch_press_event) { + ltc3675_button->onswitch_press_event = false; + flags |= PMU_BUTTON_EVENT_MASK_PRESS; + } + } + + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) + { + if (ltc3675_button->onswitch_release_event) { + ltc3675_button->onswitch_release_event = false; + flags |= PMU_BUTTON_EVENT_MASK_RELEASE; + } + } + + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) + { + if (ltc3675_button->wakeup_event) { + ltc3675_button->wakeup_event = false; + flags |= PMU_BUTTON_EVENT_MASK_WAKEUP; + } + } + + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) + { + if (ltc3675_button->poweroff_event) { + ltc3675_button->poweroff_event = false; + flags |= PMU_BUTTON_EVENT_MASK_POWERDOWN; + } + } + return flags; +} + +static const pmu_button_ops_t ltc3675_pmu_button_ops = { + .check_events = ltc3675_button_check_events, +}; + + +int8_t ltc3675_init(void) +{ + uint8_t id; + int8_t ret; + + ret = ltc3675_read(LTC3675_REG_LED_CONFIG, &id); + if (ret) + return ret; + + button.pmu_button.ops = <c3675_pmu_button_ops; + button.onswitch_last_state = io_test_pin(ONSWITCH_DB); + + /* setup the input pins with pull-up for open drain */ + io_input_pin(PWR_IRQ); + io_set_pin(PWR_IRQ); + io_input_pin(WAKEUP); + io_set_pin(WAKEUP); + io_input_pin(ONSWITCH_DB); + io_set_pin(ONSWITCH_DB); + io_input_pin(PWR_RESET); + io_set_pin(PWR_RESET); + + /* clear the old interrupts */ + ltc3675_clear_interrupts(); + + /* setup interrupt masks on chip to be notified of any faults */ + ret = ltc3675_write(LTC3675_REG_IRQB_MASK, 0xff); + if (ret) + goto fail_i2c_write_mask; + + /* program warning @ 3.4V */ + ret = ltc3675_write(LTC3675_REG_UVOT, 0x70); + if (ret) + goto fail_i2c_write_uvot; + + pmu_register_button(&button.pmu_button); + + return 0; + +fail_i2c_write_uvot: +fail_i2c_write_mask: + return ret; +} + +int8_t ltc3675_check_reg_events(pmu_regulator_t *reg) +{ + return 0; +} + +const pmu_regulator_ops_t ltc3675_ops = { + .set_voltage = ltc3675_set_voltage, + .set_regulator = ltc3675_set_regulator, + .check_events = ltc3675_check_reg_events +}; + +/* PD(3) ONSWITCH_DB (PB_STAT), any change */ +irqreturn_t ltc3675_button_change_irq_handler(void) +{ + bool pin_state; + + pin_state = io_test_pin(ONSWITCH_DB); + + /* the pushbutton is active low, therefore backwards logic */ + if (pin_state && !button.onswitch_last_state) { + button.onswitch_release_event = true; + timer1_stop(); + } else if (!pin_state && button.onswitch_last_state) { + button.onswitch_press_event = true; + timer1_start(); + } + button.onswitch_last_state = pin_state; + + return IRQ_HANDLED; +} + +irqreturn_t ltc3675_button_wakeup_irq_handler(void) +{ + button.wakeup_event = true; + + return IRQ_HANDLED; +} + +irqreturn_t ltc3675_button_timer_irq_handler(void) +{ + /* if we got here, the timer overflowed, + * meaning the user pressed the button long enough */ + button.poweroff_event = true; + + return IRQ_HANDLED; +} |