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