aboutsummaryrefslogtreecommitdiffstats
path: root/firmware/e300/rev_c/ltc3675.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/e300/rev_c/ltc3675.c')
-rw-r--r--firmware/e300/rev_c/ltc3675.c525
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);
+}