diff options
Diffstat (limited to 'firmware/e300/rev_c/ltc3675.c')
-rw-r--r-- | firmware/e300/rev_c/ltc3675.c | 525 |
1 files changed, 525 insertions, 0 deletions
diff --git a/firmware/e300/rev_c/ltc3675.c b/firmware/e300/rev_c/ltc3675.c new file mode 100644 index 000000000..0f85ec7e5 --- /dev/null +++ b/firmware/e300/rev_c/ltc3675.c @@ -0,0 +1,525 @@ +/* + * Copyright 2012 Ettus Research LLC + */ + +/* + ? STOP condition after writing address on read + - Default buck/boost register values are OK +*/ + +#include "config.h" +#include "ltc3675.h" + +//#include <stdio.h> +#include <util/delay.h> +#include <avr/interrupt.h> + +#include "io.h" +#include "i2c.h" +#include "debug.h" +#include "global.h" +#include "error.h" + +#ifndef I2C_REWORK +#include "power.h" +#endif // I2C_REWORK + +const bool _ltc3675_pull_up = +#ifdef I2C_REWORK + true +#else + false +#endif // I2C_REWORK +; + +volatile ltc3675_reg_helper_fn _ltc3675_reg_helper; + +//#define HARDWIRE_ENABLE // Use hardware enable pins instead of I2C on regulators that support it + +#ifdef ATTINY88_DIP + +#ifdef HARDWIRE_ENABLE +static io_pin_t PWR_EN1 = IO_PC(7); // Not routed by card +static io_pin_t PWR_EN2 = IO_PA(0); // Not available on DIP +static io_pin_t PWR_EN3 = IO_PA(1); // Not available on DIP +static io_pin_t PWR_EN4 = IO_PB(6); // Instead of FTDI_BCD +static io_pin_t PWR_EN5 = IO_PB(7); // Instead of FTDI_PWREN2 +#endif // HARDWIRE_ENABLE + +//static io_pin_t PWR_SDA = IO_PC(4); +//static io_pin_t PWR_SCL = IO_PC(5); + +#else + +#ifdef HARDWIRE_ENABLE +static io_pin_t PWR_EN1 = IO_PC(1); +//static io_pin_t PWR_EN2 = IO_PC(2); // Now used by I2C for charge controller +//static io_pin_t PWR_EN3 = IO_PC(3); // Now used by I2C for charge controller +static io_pin_t PWR_EN4 = IO_PA(1); +static io_pin_t PWR_EN5 = IO_PA(2); +#endif // HARDWIRE_ENABLE + +#ifdef I2C_REWORK +static io_pin_t PWR_SDA = IO_PC(2); // Instead of EN5 +static io_pin_t PWR_SCL = IO_PA(2); // Instead of EN2 +#endif // I2C_REWORK + +#endif // ATTINY88_DIP + +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); + +#define LTC3675_BASE_ADDRESS 0x12 +#define LTC3675_WRITE_ADDRESS (LTC3675_BASE_ADDRESS + 0) +#define LTC3675_READ_ADDRESS (LTC3675_BASE_ADDRESS + 1) + +#define LTC3675_RETRY_DELAY 1 // us MAGIC +#define LTC3675_MAX_ACK_RETRIES 10 // * LTC3675_RETRY_DELAY us + +#define LTC3675_SCL_LOW_PERIOD 2 // 1.3 us +#define LTC3675_SCL_HIGH_PERIOD 1 // 0.6 us +#define LTC3675_BUS_FREE_TIME 2 // 1.3 us +#define LTC3675_STOP_TIME 1 // 0.6 us + +#define LTC3675_REGULATOR_ENABLE_DELAY 10 // 50 // ms (some arbitrary value so that the external power supply can settle) + +enum LTC3675Registers +{ + LTC3675_REG_NONE = 0x00, + LTC3675_REG_BUCK1 = 0x01, + LTC3675_REG_BUCK2 = 0x02, + LTC3675_REG_BUCK3 = 0x03, + LTC3675_REG_BUCK4 = 0x04, + LTC3675_REG_BOOST = 0x05, + LTC3675_REG_BUCK_BOOST = 0x06, + LTC3675_REG_LED_CONFIG = 0x07, + LTC3675_REG_LED_DAC = 0x08, + LTC3675_REG_UVOT = 0x09, + LTC3675_REG_RSTB = 0xA0, + LTC3675_REG_IRQB_MASK = 0x0B, + LTC3675_REG_REALTIME_STATUS = 0x0C, + LTC3675_REG_LATCHED_STATUS = 0x0D, + LTC3675_REG_CLEAR_IRQ = 0x0F +}; + +enum LTC3675StatusBits +{ + LTC3675_UnderVoltage = 1 << 7, + LTC3675_OverTemperature = 1 << 6, + LTC3675_BuckBoost_PGood = 1 << 5, + LTC3675_Boost_PGood = 1 << 4, + LTC3675_Buck4_PGood = 1 << 3, + LTC3675_Buck3_PGood = 1 << 2, + LTC3675_Buck2_PGood = 1 << 1, + LTC3675_Buck1_PGood = 1 << 0 +}; + +#define LTC3675_DEFAULT_BUCK_REG_VAL 0x6F +#define LTC3675_DEFAULT_BOOST_REG_VAL 0x0F +#define LTC3675_DEFAULT_BUCK_BOOST_REG_VAL 0x0F + +#define LTC3675_ENABLE_REGISTER_BIT 0x80 + +// Max I2C rate = 400kHz + +static void _ltc3675_clear_irq() +{ + // Two-stage clear + i2c_write_ex(PWR_SDA, PWR_SCL, LTC3675_WRITE_ADDRESS, LTC3675_REG_CLEAR_IRQ, 0x00, _ltc3675_pull_up); + i2c_write_ex(PWR_SDA, PWR_SCL, LTC3675_WRITE_ADDRESS, LTC3675_REG_NONE, 0x00, _ltc3675_pull_up); +} + +volatile uint8_t _ltc3675_last_status = 0x00; + +uint8_t ltc3675_get_last_status(void) +{ + return _ltc3675_last_status; +} + +uint8_t ltc3675_reg_status_to_error(uint8_t val) +{ + if (((val & LTC3675_BuckBoost_PGood) == 0) && ((_ltc3675_reg_helper)(LTC3675_REG_6))) + return BlinkError_3_3V_Peripherals_Power; + + if (((val & LTC3675_Boost_PGood) == 0) && ((_ltc3675_reg_helper)(LTC3675_REG_5))) + return BlinkError_TX_Power; + + //if (((val & LTC3675_Buck4_PGood) == 0) && ((_ltc3675_reg_helper)(LTC3675_REG_4))) + + if (((val & LTC3675_Buck3_PGood) == 0) && ((_ltc3675_reg_helper)(LTC3675_REG_3))) + return BlinkError_1_8V_Peripherals_Power; + + //if (((val & LTC3675_Buck2_PGood) == 0) && ((_ltc3675_reg_helper)(LTC3675_REG_2))) + + if (((val & LTC3675_Buck1_PGood) == 0) && ((_ltc3675_reg_helper)(LTC3675_REG_1))) + return BlinkError_DRAM_Power; + + return BlinkError_None; +} + +bool ltc3675_is_power_good(uint8_t val) +{ + return (ltc3675_reg_status_to_error(val) == BlinkError_None); +} + +uint8_t ltc3675_status_to_error(uint8_t val) +{ + if (val & LTC3675_UnderVoltage) + return BlinkError_LTC3675_UnderVoltage; + + if (val & LTC3675_OverTemperature) + return BlinkError_LTC3675_OverTemperature; + + uint8_t reg_error = ltc3675_reg_status_to_error(val); + if (reg_error != BlinkError_None) + return reg_error; + + return BlinkError_None; +} + +bool _ltc3675_handle_irq(void) +{ + uint8_t val = 0x00; + bool result = false; + + if (i2c_read2_ex(PWR_SDA, PWR_SCL, LTC3675_READ_ADDRESS, LTC3675_REG_LATCHED_STATUS, &val, _ltc3675_pull_up)) + { + debug_log_ex("3675LTCH ", false); + debug_log_hex(val); + } + + if (i2c_read2_ex(PWR_SDA, PWR_SCL, LTC3675_READ_ADDRESS, /*LTC3675_REG_LATCHED_STATUS*/LTC3675_REG_REALTIME_STATUS, &val, _ltc3675_pull_up)) // No point acting on latched because could have been resolved + { + //debug_log_ex("3675LTCH ", false); + debug_log_ex("3675RT ", false); + debug_log_hex(val); + + _ltc3675_last_status = val; + + uint8_t error = ltc3675_status_to_error(val); + + /*if (val & LTC3675_UnderVoltage) + { + pmc_set_blink_error(BlinkError_LTC3675_UnderVoltage); + //_state.low_battery = true; + }*/ + + if (error) + { + pmc_set_blink_error(error); + + /*_i2c_disable_ack_check = true; + uint8_t chk = 0x00; + chk |= (_ltc3675_reg_helper)(LTC3675_REG_6) << 0; + chk |= (_ltc3675_reg_helper)(LTC3675_REG_5) << 1; + chk |= (_ltc3675_reg_helper)(LTC3675_REG_3) << 2; + chk |= (_ltc3675_reg_helper)(LTC3675_REG_1) << 3; + i2c_write_ex(PWR_SDA, PWR_SCL, 0xFE, 0xFF, chk, _ltc3675_pull_up); + _i2c_disable_ack_check = false;*/ + } + + result = true; + } + + _ltc3675_clear_irq(); + + return result; +} + +static bool _ltc3675_get_realtime_status(uint8_t* val) +{ + //cli(); + + if (i2c_read2_ex(PWR_SDA, PWR_SCL, LTC3675_READ_ADDRESS, LTC3675_REG_REALTIME_STATUS, val, _ltc3675_pull_up) == false) + return false; + + debug_log_ex("3675RT ", false); + debug_log_hex(*val); + + //sei(); + + return true; +} + +int8_t ltc3675_check_status(void) +{ + uint8_t val = 0x00; + + pmc_mask_irqs(true); + + bool result = _ltc3675_get_realtime_status(&val); + + pmc_mask_irqs(false); + + if (result == false) + return -1; + + //_ltc3675_last_status = val; + + /*if (val & LTC3675_UnderVoltage) + return BlinkError_LTC3675_UnderVoltage; + + if (val & LTC3675_OverTemperature) + return BlinkError_LTC3675_OverTemperature; + + return BlinkError_None;*/ + + return ltc3675_status_to_error(val); +} + +bool ltc3675_handle_irq(void) +{ + pmc_mask_irqs(true); + + /*uint8_t*/bool result = _ltc3675_handle_irq(); + + pmc_mask_irqs(false); + + return result; +} + +static bool _ltc3675_default_reg_helper(uint8_t address) +{ + uint8_t val = 0x00; + i2c_read2_ex(PWR_SDA, PWR_SCL, LTC3675_READ_ADDRESS, address, &val, _ltc3675_pull_up); + return ((val & LTC3675_ENABLE_REGISTER_BIT) == LTC3675_ENABLE_REGISTER_BIT); +} + +bool ltc3675_init(ltc3675_reg_helper_fn helper) +{ + if (helper) + _ltc3675_reg_helper = helper; + else + _ltc3675_reg_helper = _ltc3675_default_reg_helper; +#ifdef HARDWIRE_ENABLE + io_output_pin(PWR_EN1); + io_output_pin(PWR_EN2); + io_output_pin(PWR_EN3); + io_output_pin(PWR_EN4); + io_output_pin(PWR_EN5); +#endif // HARDWIRE_ENABLE + + /* io_output_pin(PWR_SDA); + io_output_pin(PWR_SCL); + + // Must remain HIGH when idle + io_set_pin(PWR_SDA); + io_set_pin(PWR_SCL); +*/ +#ifdef I2C_REWORK + i2c_init_ex(PWR_SDA, PWR_SCL, _ltc3675_pull_up); +#endif // I2C_REWORK + io_input_pin(PWR_IRQ); +#if !defined(DEBUG) && !defined(ATTINY88_DIP) + io_set_pin(PWR_IRQ); // Enable pull-up for Open Drain +#endif // DEBUG + + io_input_pin(WAKEUP); + io_set_pin(WAKEUP); // Enable pull-up for Open Drain + + io_input_pin(ONSWITCH_DB); + io_set_pin(ONSWITCH_DB); // Enable pull-up for Open Drain + + io_input_pin(PWR_RESET); + io_set_pin(PWR_RESET); // Enable pull-up for Open Drain + + _ltc3675_clear_irq(); // Clear old interrupt - state might have changed (e.g. undervoltage might have been resolved) + + if (i2c_write_ex(PWR_SDA, PWR_SCL, LTC3675_WRITE_ADDRESS, LTC3675_REG_IRQB_MASK, 0xFF, _ltc3675_pull_up) == false) // Any PGOOD fault will pull IRQB low + return false; + + if (i2c_write_ex(PWR_SDA, PWR_SCL, LTC3675_WRITE_ADDRESS, LTC3675_REG_UVOT, 0x70, _ltc3675_pull_up) == false) // 3.4V UV + return false; + + if (ltc3675_has_interrupt()) + _ltc3675_handle_irq(); + + // Non-maskable: + // UV warning threshold (default): 2.7V + // Over temp warning threshold (default): 10 degrees below + + return true; +} + +bool ltc3675_is_waking_up(void) +{ + return io_test_pin(WAKEUP); +} + +static bool _ltc3675_is_pgood(uint8_t reg) +{ + uint8_t val = 0x00; + if (_ltc3675_get_realtime_status(&val) == false) + return false; + return ((reg & val) == reg); +} + +static bool _ltc3675_toggle_reg(uint8_t addr, uint8_t def_reg, bool on) +{ + bool result = true; + + //cli(); + + uint8_t val = 0x00 | def_reg; + if (i2c_read2_ex(PWR_SDA, PWR_SCL, LTC3675_READ_ADDRESS, addr, &val, _ltc3675_pull_up) == false) + return false; + + val &= ~LTC3675_ENABLE_REGISTER_BIT; + + if (i2c_write_ex(PWR_SDA, PWR_SCL, LTC3675_WRITE_ADDRESS, addr, /*def_reg*/val | (on ? LTC3675_ENABLE_REGISTER_BIT : 0x00), _ltc3675_pull_up) == false) + //return true; + result = false; + + if (on) + { + _delay_ms(LTC3675_REGULATOR_ENABLE_DELAY); + } + + //sei(); + + return result; + //return true; +} + +bool ltc3675_enable_reg(ltc3675_regulator_t reg, bool on) +{ + //debug_blink2(reg + 1); + debug_log_ex("3675 ", false); + debug_log_byte_ex(reg, true); + + // Sub-address: index of regulator + // Data: <default reg contents> | <enable> + + bool result = false; + + switch (reg) + { + case LTC3675_REG_1: // Master + case LTC3675_REG_2: // Slave +#ifdef HARDWIRE_ENABLE + io_enable_pin(PWR_EN1, on); + //break; +#else + //debug_blink2(reg + 1); + if (_ltc3675_toggle_reg(LTC3675_REG_BUCK1, LTC3675_DEFAULT_BUCK_REG_VAL, on) == false) { + //debug_blink2(reg + 1); + return false; + } + //debug_blink2(reg + 1); +#endif // HARDWIRE_ENABLE + result = (_ltc3675_is_pgood(LTC3675_Buck1_PGood) == on); + break; + case LTC3675_REG_3: // Master + case LTC3675_REG_4: // Slave +#ifdef HARDWIRE_ENABLE + io_enable_pin(PWR_EN3, on); + //break; +#else + if (_ltc3675_toggle_reg(LTC3675_REG_BUCK3, LTC3675_DEFAULT_BUCK_REG_VAL, on) == false) + return false; +#endif // HARDWIRE_ENABLE + result = (_ltc3675_is_pgood(LTC3675_Buck3_PGood) == on); + break; + case LTC3675_REG_5: // I2C only + if (_ltc3675_toggle_reg(LTC3675_REG_BOOST, LTC3675_DEFAULT_BOOST_REG_VAL, on) == false) // (Boost address, Default reg contents | Enable) + return false; + result = (_ltc3675_is_pgood(LTC3675_Boost_PGood) == on); + break; + case LTC3675_REG_6: // Single +#ifdef HARDWIRE_ENABLE + io_enable_pin(PWR_EN5, on); + //break; +#else + if (_ltc3675_toggle_reg(LTC3675_REG_BUCK_BOOST, LTC3675_DEFAULT_BUCK_BOOST_REG_VAL, on) == false) + return false; +#endif // HARDWIRE_ENABLE + result = (_ltc3675_is_pgood(LTC3675_BuckBoost_PGood) == on); + break; + //default: + // return false; + } + + _debug_log((result ? "+" : "-")); + + return result; +} + +bool ltc3675_set_voltage(ltc3675_regulator_t reg, uint16_t voltage) +{ + // Not necessary due to R-bridges and default DAC registers + + // VRAM will be 1.3579 - a little high? (re-program DAC reference) + // No: minimum FB step will put Vout < 1.35 + + uint16_t max_voltage = 0; + uint8_t reg_subaddr = 0; + + switch (reg) + { + case LTC3675_REG_1: // 1A Buck + case LTC3675_REG_2: // 1A Buck + max_voltage = 1500; + reg_subaddr = LTC3675_REG_BUCK1; + break; + case LTC3675_REG_3: // 500mA Buck + case LTC3675_REG_4: // 500mA Buck + max_voltage = 1800; + reg_subaddr = LTC3675_REG_BUCK3; + break; + case LTC3675_REG_5: // 1A Boost + max_voltage = 5000; + reg_subaddr = LTC3675_REG_BOOST; + break; + case LTC3675_REG_6: // 1A Buck-Boost + max_voltage = 3300; + reg_subaddr = LTC3675_REG_BUCK_BOOST; + break; + } + + if (voltage > max_voltage) + return false; + + //uint32_t rMax = ((uint32_t)voltage * 1000) / (uint32_t)max_voltage; + //uint32_t rFB = ((uint32_t)max_voltage * 1000) / (uint32_t)800; + uint32_t rFB = ((uint32_t)max_voltage * 1000) / (uint32_t)800; // 800mV full-scale feedback voltage + uint32_t r = ((uint32_t)voltage * 1000) / (uint32_t)rFB; + if (r < 450) + return false; + + uint16_t rDAC = (16 * ((uint16_t)r - 450)) / (800 - 450); + + debug_log_ex("Vr ", false); + debug_log_byte_ex(reg, false); + debug_log_ex("=", false); + debug_log_byte_ex((uint8_t)rDAC, false); + + uint8_t val = 0x00; + if (i2c_read2_ex(PWR_SDA, PWR_SCL, LTC3675_READ_ADDRESS, reg_subaddr, &val, _ltc3675_pull_up) == false) + { + debug_log("-"); + return false; + } + + val = (val & 0xF0) | (uint8_t)rDAC; + if (i2c_write_ex(PWR_SDA, PWR_SCL, LTC3675_WRITE_ADDRESS, reg_subaddr, val, _ltc3675_pull_up) == false) + { + debug_log("-"); + return false; + } + + debug_log("+"); + + return true; +} + +bool ltc3675_is_power_button_depressed(void) +{ + return (io_test_pin(ONSWITCH_DB) == false); +} + +bool ltc3675_has_interrupt(void) +{ + return (io_test_pin(PWR_IRQ) == false); +} |