aboutsummaryrefslogtreecommitdiffstats
path: root/firmware
diff options
context:
space:
mode:
authorMoritz Fischer <moritz.fischer@ettus.com>2015-07-13 14:03:42 -0700
committerMoritz Fischer <moritz.fischer@ettus.com>2015-07-13 14:03:42 -0700
commitc65e0ea6e387b39e115b6af28e79ba1418f605b2 (patch)
tree85bc5673ca01f22b6b9362b0aba8c80d13ba1eab /firmware
parent69bcfba936e49c2825a6d9be677c3150a5c6b70c (diff)
downloaduhd-c65e0ea6e387b39e115b6af28e79ba1418f605b2.tar.gz
uhd-c65e0ea6e387b39e115b6af28e79ba1418f605b2.tar.bz2
uhd-c65e0ea6e387b39e115b6af28e79ba1418f605b2.zip
e3xx: Added firmware for battery based devices.
Note: This firmware does *not* support Rev B units. Signed-off-by: Moritz Fischer <moritz.fischer@ettus.com>
Diffstat (limited to 'firmware')
-rw-r--r--firmware/e300/battery/E310-Firmware.atsln20
-rw-r--r--firmware/e300/battery/E310-Firmware.cproj227
-rw-r--r--firmware/e300/battery/Makefile77
-rw-r--r--firmware/e300/battery/README.md25
-rw-r--r--firmware/e300/battery/adc.c58
-rw-r--r--firmware/e300/battery/adc.h38
-rw-r--r--firmware/e300/battery/bq2419x.c588
-rw-r--r--firmware/e300/battery/bq2419x.h46
-rw-r--r--firmware/e300/battery/eeprom.c43
-rw-r--r--firmware/e300/battery/eeprom.h55
-rw-r--r--firmware/e300/battery/fpga.c296
-rw-r--r--firmware/e300/battery/fpga.h180
-rw-r--r--firmware/e300/battery/i2c_twi.c187
-rw-r--r--firmware/e300/battery/i2c_twi.h95
-rw-r--r--firmware/e300/battery/interrupt.c154
-rw-r--r--firmware/e300/battery/interrupt.h40
-rw-r--r--firmware/e300/battery/io.c67
-rw-r--r--firmware/e300/battery/io.h97
-rw-r--r--firmware/e300/battery/led.c153
-rw-r--r--firmware/e300/battery/led.h27
-rw-r--r--firmware/e300/battery/ltc294x.c281
-rw-r--r--firmware/e300/battery/ltc294x.h40
-rw-r--r--firmware/e300/battery/ltc3675.c382
-rw-r--r--firmware/e300/battery/ltc3675.h67
-rw-r--r--firmware/e300/battery/main.c65
-rw-r--r--firmware/e300/battery/mcu_settings.h28
-rw-r--r--firmware/e300/battery/pmu.c686
-rw-r--r--firmware/e300/battery/pmu.h177
-rw-r--r--firmware/e300/battery/spi.c86
-rw-r--r--firmware/e300/battery/spi.h74
-rw-r--r--firmware/e300/battery/timer.c82
-rw-r--r--firmware/e300/battery/timer.h53
-rw-r--r--firmware/e300/battery/tps54478.c110
-rw-r--r--firmware/e300/battery/tps54478.h46
-rw-r--r--firmware/e300/battery/utils.h53
35 files changed, 4703 insertions, 0 deletions
diff --git a/firmware/e300/battery/E310-Firmware.atsln b/firmware/e300/battery/E310-Firmware.atsln
new file mode 100644
index 000000000..c4a3a5561
--- /dev/null
+++ b/firmware/e300/battery/E310-Firmware.atsln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Atmel Studio Solution File, Format Version 11.00
+Project("{54F91283-7BC4-4236-8FF9-10F437C3AD48}") = "E310-Firmware", "E310-Firmware.cproj", "{CB962C73-137F-46D4-A5F8-6286DB107987}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|AVR = Debug|AVR
+ Release|AVR = Release|AVR
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {CB962C73-137F-46D4-A5F8-6286DB107987}.Debug|AVR.ActiveCfg = Debug|AVR
+ {CB962C73-137F-46D4-A5F8-6286DB107987}.Debug|AVR.Build.0 = Debug|AVR
+ {CB962C73-137F-46D4-A5F8-6286DB107987}.Release|AVR.ActiveCfg = Release|AVR
+ {CB962C73-137F-46D4-A5F8-6286DB107987}.Release|AVR.Build.0 = Release|AVR
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/firmware/e300/battery/E310-Firmware.cproj b/firmware/e300/battery/E310-Firmware.cproj
new file mode 100644
index 000000000..2d71c0c46
--- /dev/null
+++ b/firmware/e300/battery/E310-Firmware.cproj
@@ -0,0 +1,227 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectVersion>6.2</ProjectVersion>
+ <ToolchainName>com.Atmel.AVRGCC8.C</ToolchainName>
+ <ProjectGuid>{cb962c73-137f-46d4-a5f8-6286db107987}</ProjectGuid>
+ <avrdevice>ATtiny88</avrdevice>
+ <avrdeviceseries>none</avrdeviceseries>
+ <OutputType>Executable</OutputType>
+ <Language>C</Language>
+ <OutputFileName>$(MSBuildProjectName)</OutputFileName>
+ <OutputFileExtension>.elf</OutputFileExtension>
+ <OutputDirectory>$(MSBuildProjectDirectory)\$(Configuration)</OutputDirectory>
+ <AssemblyName>E310-Firmware</AssemblyName>
+ <Name>E310-Firmware</Name>
+ <RootNamespace>E310-Firmware</RootNamespace>
+ <ToolchainFlavour>Native</ToolchainFlavour>
+ <KeepTimersRunning>true</KeepTimersRunning>
+ <OverrideVtor>false</OverrideVtor>
+ <CacheFlash>true</CacheFlash>
+ <ProgFlashFromRam>true</ProgFlashFromRam>
+ <RamSnippetAddress>0x20000000</RamSnippetAddress>
+ <UncachedRange />
+ <preserveEEPROM>true</preserveEEPROM>
+ <OverrideVtorValue>exception_table</OverrideVtorValue>
+ <BootSegment>2</BootSegment>
+ <eraseonlaunchrule>1</eraseonlaunchrule>
+ <AsfFrameworkConfig>
+ <framework-data xmlns="">
+ <options />
+ <configurations />
+ <files />
+ <documentation help="" />
+ <offline-documentation help="" />
+ <dependencies>
+ <content-extension eid="atmel.asf" uuidref="Atmel.ASF" version="3.20.1" />
+ </dependencies>
+ </framework-data>
+ </AsfFrameworkConfig>
+ <avrtool>com.atmel.avrdbg.tool.atmelice</avrtool>
+ <com_atmel_avrdbg_tool_atmelice>
+ <ToolOptions>
+ <InterfaceProperties>
+ </InterfaceProperties>
+ <InterfaceName>debugWIRE</InterfaceName>
+ </ToolOptions>
+ <ToolType>com.atmel.avrdbg.tool.atmelice</ToolType>
+ <ToolNumber>J41800001858</ToolNumber>
+ <ToolName>Atmel-ICE</ToolName>
+ </com_atmel_avrdbg_tool_atmelice>
+ <avrtoolinterface>debugWIRE</avrtoolinterface>
+ <UseGdb>True</UseGdb>
+ <com_atmel_avrdbg_tool_simulator>
+ <ToolOptions xmlns="">
+ <InterfaceProperties>
+ </InterfaceProperties>
+ <InterfaceName>
+ </InterfaceName>
+ </ToolOptions>
+ <ToolType xmlns="">com.atmel.avrdbg.tool.simulator</ToolType>
+ <ToolNumber xmlns="">
+ </ToolNumber>
+ <ToolName xmlns="">Simulator</ToolName>
+ </com_atmel_avrdbg_tool_simulator>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <ToolchainSettings>
+ <AvrGcc>
+ <avrgcc.common.outputfiles.hex>True</avrgcc.common.outputfiles.hex>
+ <avrgcc.common.outputfiles.lss>True</avrgcc.common.outputfiles.lss>
+ <avrgcc.common.outputfiles.eep>True</avrgcc.common.outputfiles.eep>
+ <avrgcc.common.outputfiles.srec>True</avrgcc.common.outputfiles.srec>
+ <avrgcc.common.outputfiles.usersignatures>False</avrgcc.common.outputfiles.usersignatures>
+ <avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>
+ <avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>
+ <avrgcc.compiler.symbols.DefSymbols>
+ <ListValues>
+ <Value>NDEBUG</Value>
+ </ListValues>
+ </avrgcc.compiler.symbols.DefSymbols>
+ <avrgcc.compiler.optimization.level>Optimize for size (-Os)</avrgcc.compiler.optimization.level>
+ <avrgcc.compiler.optimization.PackStructureMembers>True</avrgcc.compiler.optimization.PackStructureMembers>
+ <avrgcc.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcc.compiler.optimization.AllocateBytesNeededForEnum>
+ <avrgcc.compiler.warnings.AllWarnings>True</avrgcc.compiler.warnings.AllWarnings>
+ <avrgcc.linker.libraries.Libraries>
+ <ListValues>
+ <Value>libm</Value>
+ </ListValues>
+ </avrgcc.linker.libraries.Libraries>
+ </AvrGcc>
+ </ToolchainSettings>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <ToolchainSettings>
+ <AvrGcc>
+ <avrgcc.common.outputfiles.hex>True</avrgcc.common.outputfiles.hex>
+ <avrgcc.common.outputfiles.lss>True</avrgcc.common.outputfiles.lss>
+ <avrgcc.common.outputfiles.eep>True</avrgcc.common.outputfiles.eep>
+ <avrgcc.common.outputfiles.srec>True</avrgcc.common.outputfiles.srec>
+ <avrgcc.common.outputfiles.usersignatures>False</avrgcc.common.outputfiles.usersignatures>
+ <avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>
+ <avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>
+ <avrgcc.compiler.symbols.DefSymbols>
+ <ListValues>
+ <Value>DEBUG</Value>
+ </ListValues>
+ </avrgcc.compiler.symbols.DefSymbols>
+ <avrgcc.compiler.preprocessor.UndefSymbols>
+ <ListValues>
+ <Value>DDR3L</Value>
+ </ListValues>
+ </avrgcc.compiler.preprocessor.UndefSymbols>
+ <avrgcc.compiler.optimization.level>Optimize (-O1)</avrgcc.compiler.optimization.level>
+ <avrgcc.compiler.optimization.PackStructureMembers>True</avrgcc.compiler.optimization.PackStructureMembers>
+ <avrgcc.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcc.compiler.optimization.AllocateBytesNeededForEnum>
+ <avrgcc.compiler.optimization.DebugLevel>Maximum (-g3)</avrgcc.compiler.optimization.DebugLevel>
+ <avrgcc.compiler.warnings.AllWarnings>True</avrgcc.compiler.warnings.AllWarnings>
+ <avrgcc.compiler.warnings.ExtraWarnings>True</avrgcc.compiler.warnings.ExtraWarnings>
+ <avrgcc.compiler.warnings.Undefined>True</avrgcc.compiler.warnings.Undefined>
+ <avrgcc.compiler.warnings.Pedantic>True</avrgcc.compiler.warnings.Pedantic>
+ <avrgcc.linker.libraries.Libraries>
+ <ListValues>
+ <Value>libm</Value>
+ </ListValues>
+ </avrgcc.linker.libraries.Libraries>
+ <avrgcc.assembler.debugging.DebugLevel>Default (-Wa,-g)</avrgcc.assembler.debugging.DebugLevel>
+ </AvrGcc>
+ </ToolchainSettings>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="adc.c">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="adc.h">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="bq2419x.c">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="bq2419x.h">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="eeprom.c">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="eeprom.h">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="fpga.c">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="fpga.h">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="i2c_twi.c">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="i2c_twi.h">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="interrupt.c">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="interrupt.h">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="io.c">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="io.h">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="led.c">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="led.h">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="ltc294x.c">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="ltc294x.h">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="ltc3675.c">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="ltc3675.h">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="main.c">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="mcu_settings.h">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="pmu.c">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="pmu.h">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="spi.c">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="spi.h">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="timer.c">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="timer.h">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="tps54478.c">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="tps54478.h">
+ <SubType>compile</SubType>
+ </Compile>
+ <Compile Include="utils.h">
+ <SubType>compile</SubType>
+ </Compile>
+ </ItemGroup>
+ <Import Project="$(AVRSTUDIO_EXE_PATH)\\Vs\\Compiler.targets" />
+</Project> \ No newline at end of file
diff --git a/firmware/e300/battery/Makefile b/firmware/e300/battery/Makefile
new file mode 100644
index 000000000..c2d530e36
--- /dev/null
+++ b/firmware/e300/battery/Makefile
@@ -0,0 +1,77 @@
+# USRP E310 Firmware
+# Copyright (C) 2014-2015 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/>.
+
+##################################################
+# Compiler
+##################################################
+CC = avr-gcc
+OBJCOPY = avr-objcopy
+STRIP = avr-strip
+SIZE = avr-size
+OBJDUMP = avr-objdump
+SREC = srec_cat
+CFLAGS = -Os -std=c11 -Wall -fshort-enums -pedantic -Wl,--gc-sections \
+ -Wstrict-prototypes -Wcast-align -Wshadow
+
+##################################################
+# Files
+##################################################
+HDRS =
+SRCS = main.c adc.c bq2419x.c fpga.c i2c_twi.c interrupt.c io.c ltc294x.c ltc3675.c \
+ pmu.c spi.c timer.c tps54478.c eeprom.c led.c
+TARGET = main
+
+##################################################
+# Device
+##################################################
+MMCU = attiny88
+PROGRAMMER = jtag3isp
+PORT = usb
+AVRDUDE = avrdude -p $(MMCU) -c $(PROGRAMMER) -P $(PORT) -V
+
+##################################################
+# Global Targets
+##################################################
+all: $(TARGET).hex size
+
+clean:
+ $(RM) *.o *.elf *.hex
+
+install: all
+ $(AVRDUDE) -U flash:w:$(TARGET).hex:i
+
+size: $(TARGET).hex
+ @$(SIZE) --format=SysV --mcu=$(MMCU) $(TARGET).elf
+ @$(SIZE) -C --mcu=$(MMCU) $(TARGET).elf
+
+##################################################
+# Dependency Targets
+##################################################
+fuses.hex: $(TARGET).elf
+ $(OBJCOPY) -j .fuse -O ihex $< $@ --change-section-lma .fuse=0
+
+lfuse.hex: fuses.hex
+ $(SREC) $< -Intel -crop 0x00 0x01 -offset 0x00 -O $@ -Intel
+
+hfuse.hex: fuses.hex
+ $(SREC) $< -Intel -crop 0x01 0x02 -offset -0x01 -O $@ -Intel
+
+$(TARGET).hex: $(TARGET).elf
+ $(OBJCOPY) -R .eeprom -R .fuse -O ihex $< $@
+
+$(TARGET).elf: $(SRCS:.c=.o)
+ $(CC) -mmcu=$(MMCU) $^ -o $@
+
+%.o: %.c $(HDRS) Makefile
+ $(CC) -mmcu=$(MMCU) -c $< -o $@ $(CFLAGS)
diff --git a/firmware/e300/battery/README.md b/firmware/e300/battery/README.md
new file mode 100644
index 000000000..fc4d504dd
--- /dev/null
+++ b/firmware/e300/battery/README.md
@@ -0,0 +1,25 @@
+USRP E312 Firmware
+==================
+
+Welcome to the NI Ettus Research USRP E310/E312 Firmware distribution.
+
+# Dependencies
+
+In order to build you'll *avr-gcc* and *avr-libc*.
+
+# Building
+
+The included Makefile specifies all the required flags. To build type:
+```
+$ make
+```
+
+# Flashing
+
+In order to program the device with the firmware type:
+```
+$ make install
+```
+
+Note: The Makefile will have to be modified depending on which programmer you use.
+Known good programmers include 'Atmel AVR Dragon, Atmel JTAGICEIII, Atmel AtmelICE'.
diff --git a/firmware/e300/battery/adc.c b/firmware/e300/battery/adc.c
new file mode 100644
index 000000000..998408066
--- /dev/null
+++ b/firmware/e300/battery/adc.c
@@ -0,0 +1,58 @@
+/* USRP E310 Firmware Atmel AVR ADC 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 <avr/io.h>
+
+#include "adc.h"
+#include "utils.h"
+
+void adc_init(void)
+{
+ /* disable digital input on PC0 (ADC0) */
+ DIDR0 |= 0x1;
+
+ /* set to AVcc reference, left aligned and ADC0 */
+ ADMUX = (1 << REFS0)
+ | (0 << ADLAR)
+ | (0 << MUX0);
+
+ /* prescale clock by 128 */
+ ADCSRA = BIT(ADPS2) | BIT(ADPS1) | BIT(ADPS0);
+}
+
+uint16_t adc_single_shot(void)
+{
+ uint16_t value;
+
+ /* turn on ADC */
+ ADCSRA |= (1 << ADEN);
+
+ /* start conversion */
+ ADCSRA |= (1 << ADSC);
+
+ /* busy wait for conversion */
+ while (ADCSRA & (1 << ADSC)) {
+ };
+
+ /* we need to first read the lower bits,
+ * which will lock the value until higher bits are read */
+ value = (ADCL << 0);
+ value |= (ADCH << 8);
+
+ /* turn adc of again */
+ ADCSRA &= ~(1 << ADEN);
+
+ return value;
+}
diff --git a/firmware/e300/battery/adc.h b/firmware/e300/battery/adc.h
new file mode 100644
index 000000000..7aa3fc4b1
--- /dev/null
+++ b/firmware/e300/battery/adc.h
@@ -0,0 +1,38 @@
+/* USRP E310 Firmware Atmel AVR ADC 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/>.
+ */
+
+/**
+ * \file adc.h
+ * \brief Atmel AVR ADC driver
+ */
+
+#ifndef ADC_H
+#define ADC_H
+
+#include <stdint.h>
+
+/**
+ * \brief Initialize the ADC for conversion on PC0 (ADC0)
+ * with a prescaler of 128, and AVcc reference.
+ */
+void adc_init(void);
+
+/**
+ * \brief Do a single shot conversion on PC0 (ADC0)
+ * \return Value of ADC
+ */
+uint16_t adc_single_shot(void);
+
+#endif /* ADC_H */
diff --git a/firmware/e300/battery/bq2419x.c b/firmware/e300/battery/bq2419x.c
new file mode 100644
index 000000000..11b393622
--- /dev/null
+++ b/firmware/e300/battery/bq2419x.c
@@ -0,0 +1,588 @@
+/* USRP E310 Firmware Texas Instruments BQ2419x 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 "bq2419x.h"
+#include "io.h"
+#include "i2c_twi.h"
+#include "interrupt.h"
+#include "pmu.h"
+#include "mcu_settings.h"
+#include "utils.h"
+
+#include <stdbool.h>
+#include <string.h>
+
+#include <util/atomic.h>
+#include <util/delay.h>
+
+static const uint8_t BQ2419X_I2C_ADDR = 0x6b;
+
+#define bq2419x_read(reg, val) \
+ (i2c_twi_read(BQ2419X_I2C_ADDR, reg, val))
+
+#define bq2419x_write(reg, val) \
+ (i2c_twi_write(BQ2419X_I2C_ADDR, reg, val))
+
+/* register addresses */
+static const uint8_t BQ2419X_REG_INPUT_SRC_CTL = 0x00;
+static const uint8_t BQ2419X_REG_PWR_ON_CONFIG = 0x01;
+static const uint8_t BQ2419X_REG_CHARGE_CURRENT = 0x02;
+static const uint8_t BQ2419X_REG_PRE_TERM_CURRENT = 0x03;
+static const uint8_t BQ2419X_REG_CHARGE_VOLTAGE = 0x04;
+static const uint8_t BQ2419X_REG_TIMER_CONTROL = 0x05;
+static const uint8_t BQ2419X_REG_THERMAL_REG_CTRL = 0x06;
+static const uint8_t BQ2419X_REG_MISC_OPERATION = 0x07;
+static const uint8_t BQ2419X_REG_SYSTEM_STATUS = 0x08;
+static const uint8_t BQ2419X_REG_FAULT = 0x09;
+static const uint8_t BQ2419X_REG_VENDOR_PART_REV = 0x0a;
+
+/* input source control register (REG00) */
+static const uint8_t BQ2419X_EN_HIZ_MASK = BIT(7);
+static const uint8_t BQ2419X_EN_HIZ_SHIFT = 7;
+
+/* power on configuration register (REG01) */
+static const uint8_t BQ2419X_REGISTER_RESET_MASK = BIT(7);
+static const uint8_t BQ2419X_REGISTER_RESET_SHIFT = 7;
+static const uint8_t BQ2419X_I2C_TIMER_RESET = BIT(6);
+static const uint8_t BQ2419X_I2C_TIMER_SHIFT = BIT(6);
+
+static const uint8_t BQ2419X_CHARGE_CFG_MASK = BIT(5) | BIT(4);
+static const uint8_t BQ2419X_CHARGE_CFG_SHIFT = 4;
+static const uint8_t BQ2419X_SYS_MIN_MASK = BIT(3) | BIT(2) | BIT(1);
+static const uint8_t BQ2419X_SYS_MIN_SHIFT = 1;
+
+/* charge current control register (REG02) */
+static const uint8_t BQ2419X_ICHG_MASK = BIT(7) | BIT(6) \
+ | BIT(5) | BIT(4) | BIT(3) | BIT(2);
+static const uint8_t BQ2419X_ICHG_SHIFT = 2;
+/* reserved */
+static const uint8_t BQ2419X_FORCE_20_PCT_MASK = BIT(0);
+static const uint8_t BQ2419X_FORCE_20_PCT_SHIFT = 0;
+
+static const uint8_t BQ2419X_CHARGE_CFG_DISABLED = 0x00;
+static const uint8_t BQ2419X_CHARGE_CFG_CHARGE = 0x01;
+static const uint8_t BQ2419X_CHARGE_CFG_OTG = 0x03;
+
+/* pre charge / termination current control register (REG03) */
+
+/* charge voltage control register (REG04) */
+
+/* charge / termination register (REG05) */
+static const uint8_t BQ2419X_EN_TERM_MASK = BIT(7);
+static const uint8_t BQ2419X_EN_TERM_SHIFT = BIT(7);
+static const uint8_t BQ2419X_TERM_STAT_MASK = BIT(6);
+static const uint8_t BQ2419X_TERM_STAT_SHIFT = 6;
+static const uint8_t BQ2419X_WDT_MASK = BIT(5) | BIT(4);
+static const uint8_t BQ2419X_WDT_SHIFT = 4;
+static const uint8_t BQ2419X_EN_TIMER_MASK = BIT(3);
+static const uint8_t BQ2419X_EN_TIMER_SHIFT = 3;
+
+/* ir compensation / thermal regulation control register (REG06) */
+static const uint8_t BQ2419X_BAT_COMP_MASK = BIT(7) | BIT(6) | BIT(5);
+static const uint8_t BQ2419X_BAT_COMP_SHIFT = 5;
+static const uint8_t BQ2419X_TREG_MASK = BIT(1) | BIT(0);
+static const uint8_t BQ2419X_TREG_SHIFT = 0;
+
+/* misc operation register (REG07) */
+static const uint8_t BQ2419X_DPDM_EN_MASK = BIT(7);
+static const uint8_t BQ2419X_DPDM_EN_SHIFT = 7;
+static const uint8_t BQ2419X_TMR2X_EN_MASK = BIT(6);
+static const uint8_t BQ2419X_TMR2X_EN_SHIFT = 6;
+static const uint8_t BQ2419X_BATFET_DISABLE_MASK = BIT(5);
+static const uint8_t BQ2419X_BATFET_DISABLE_SHIFT = 5;
+
+static const uint8_t BQ2419X_JEITA_VSET_MASK = BIT(4);
+static const uint8_t BQ2419X_JEITA_VSET_SHIFT = BIT(4);
+/* reserved bits */
+/* reserved bits */
+static const uint8_t BQ2419X_INT_MASK_MASK = BIT(1) | BIT(0);
+static const uint8_t BQ2419X_INT_MASK_SHIFT = 0;
+
+/* system status register (REG08) */
+static const uint8_t BQ2419X_VBUS_STAT_MASK = BIT(7) | BIT(6);
+static const uint8_t BQ2419X_VBUS_STAT_SHIFT = 6;
+static const uint8_t BQ2419X_CHG_STAT_MASK = BIT(5) | BIT(4);
+static const uint8_t BQ2419X_CHG_STAT_SHIFT = 4;
+static const uint8_t BQ2419X_DPM_STAT_MASK = BIT(3);
+static const uint8_t BQ2419X_DPM_STAT_SHIFT = 3;
+static const uint8_t BQ2419X_PG_STAT_MASK = BIT(2);
+static const uint8_t BQ2419X_PG_STAT_SHIFT = 2;
+static const uint8_t BQ2419X_THERM_STAT_MASK = BIT(1);
+static const uint8_t BQ2419X_THERM_STAT_SHIFT = 1;
+static const uint8_t BQ2419X_VSYS_STAT_MASK = BIT(0);
+static const uint8_t BQ2419X_VSYS_STAT_SHIFT = 0;
+
+static const uint8_t BQ2419X_CHARGE_STATUS_NOT_CHARGING = 0x00;
+static const uint8_t BQ2419X_CHARGE_STATUS_PRE_CHARGE = 0x01;
+static const uint8_t BQ2419X_CHARGE_STATUS_FAST_CHARGE = 0x02;
+static const uint8_t BQ2419X_CHARGE_STATUS_DONE = 0x03;
+
+/* fault register (REG09) */
+static const uint8_t BQ2419X_WATCHDOG_FAULT_MASK = BIT(7);
+static const uint8_t BQ2419X_WATCHDOG_FAULT_SHIFT = 7;
+static const uint8_t BQ2419X_BOOST_FAULT_MASK = BIT(6);
+static const uint8_t BQ2419X_BOOST_FAULT_SHIFT = 6;
+static const uint8_t BQ2419X_CHG_FAULT_MASK = BIT(5) | BIT(4);
+static const uint8_t BQ2419X_CHG_FAULT_SHIFT = 4;
+static const uint8_t BQ2419X_BAT_FAULT_MASK = BIT(3);
+static const uint8_t BQ2419X_BAT_FAULT_SHIFT = 3;
+static const uint8_t BQ2419X_NTC_FAULT_MASK = BIT(2) | BIT(1) | BIT(0);
+static const uint8_t BQ2419X_NTC_FAULT_SHIFT = 0;
+
+static io_pin_t CHG_IRQ = IO_PB(1);
+
+typedef struct bq2419x_pmu_charger
+{
+ pmu_charger_t pmu_charger;
+ uint8_t fault;
+ uint8_t status;
+ bool first_time;
+
+ bool battery_status_valid;
+ bool battery_health_valid;
+ bool charger_health_valid;
+
+ volatile bool event;
+} bq2419x_pmu_charger_t;
+
+static bq2419x_pmu_charger_t charger;
+
+static volatile bool bq2419x_event = false;
+
+int8_t bq2419x_set_charger(bool on)
+{
+ uint8_t config;
+ int8_t ret;
+
+ ret = bq2419x_read(BQ2419X_REG_PWR_ON_CONFIG, &config);
+ if (ret)
+ return ret;
+
+ config &= ~BQ2419X_CHARGE_CFG_MASK;
+ if (on)
+ config |= 1 << BQ2419X_CHARGE_CFG_SHIFT;
+
+ ret = bq2419x_write(BQ2419X_REG_PWR_ON_CONFIG, config);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int8_t bq2419x_reset(void)
+{
+ uint8_t config;
+ int8_t ret;
+ uint8_t retry = 100;
+
+ ret = bq2419x_read(BQ2419X_REG_PWR_ON_CONFIG, &config);
+ if (ret)
+ return ret;
+
+ config |= BQ2419X_REGISTER_RESET_MASK;
+ ret = bq2419x_write(BQ2419X_REG_PWR_ON_CONFIG, config);
+
+ do {
+ ret = bq2419x_read(BQ2419X_REG_PWR_ON_CONFIG, &config);
+ if (!(config & BQ2419X_REGISTER_RESET_MASK))
+ return 0;
+ _delay_ms(10);
+ } while (retry--);
+
+ return ret;
+}
+
+static void bq2419x_set_host_mode(void)
+{
+ uint8_t timer_ctrl;
+
+ /* to disable watchdog, we need to clear the WDT bits */
+ bq2419x_read(BQ2419X_REG_TIMER_CONTROL, &timer_ctrl);
+ timer_ctrl &= ~BQ2419X_WDT_MASK;
+ bq2419x_write(BQ2419X_REG_TIMER_CONTROL, timer_ctrl);
+}
+
+
+static enum pmu_charge_type bq2419x_charger_get_charge_type(pmu_charger_t *pmu_charger)
+{
+ uint8_t val;
+
+ (void) pmu_charger;
+
+ bq2419x_read(BQ2419X_REG_PWR_ON_CONFIG, &val);
+
+ val &= BQ2419X_CHARGE_CFG_MASK;
+ val >>= BQ2419X_CHARGE_CFG_SHIFT;
+
+ /* if check if charging is disabled */
+ if (!val)
+ return PMU_CHARGE_TYPE_NONE;
+
+ bq2419x_read(BQ2419X_REG_CHARGE_CURRENT, &val);
+
+ val &= BQ2419X_FORCE_20_PCT_MASK;
+ val >>= BQ2419X_FORCE_20_PCT_SHIFT;
+
+ if (val)
+ return PMU_CHARGE_TYPE_TRICKLE;
+ else
+ return PMU_CHARGE_TYPE_FAST;
+}
+
+static enum pmu_health bq2419x_charger_get_health(pmu_charger_t *pmu_charger)
+{
+ uint8_t fault;
+ int8_t ret;
+ struct bq2419x_pmu_charger *bq2419x_charger;
+
+ bq2419x_charger = container_of(
+ pmu_charger, struct bq2419x_pmu_charger, pmu_charger);
+
+ if (bq2419x_charger->charger_health_valid) {
+ fault = bq2419x_charger->fault;
+ bq2419x_charger->charger_health_valid = false;
+ } else {
+ ret = bq2419x_read(BQ2419X_REG_FAULT, &fault);
+ if (ret)
+ return PMU_HEALTH_UNKNOWN;
+ }
+
+ /* if BOOST_FAULT then report overvoltage */
+ if (fault & BQ2419X_BOOST_FAULT_MASK) {
+ return PMU_HEALTH_OVERVOLTAGE;
+ } else {
+ fault &= BQ2419X_CHG_FAULT_MASK;
+ fault >>= BQ2419X_CHG_FAULT_SHIFT;
+ switch (fault) {
+ case 0x0:
+ /* all is well */
+ return PMU_HEALTH_GOOD;
+ case 0x1:
+ /* input fault, could be over- or under-voltage
+ * and we can't tell which, so we report unspec */
+ return PMU_HEALTH_UNSPEC_FAIL;
+ case 0x2:
+ /* thermal shutdown */
+ return PMU_HEALTH_OVERHEAT;
+ case 0x3:
+ /* the charge safety timer expired */
+ return PMU_HEALTH_SAFETY_TIMER_EXPIRE;
+ default:
+ return PMU_HEALTH_UNKNOWN;
+ }
+ }
+}
+
+static enum pmu_status bq2419x_battery_get_status(pmu_charger_t *pmu_charger)
+{
+ uint8_t fault, ss_reg;
+ int8_t ret;
+ struct bq2419x_pmu_charger *bq2419x_charger;
+
+ bq2419x_charger = container_of(
+ pmu_charger, struct bq2419x_pmu_charger, pmu_charger);
+
+ if (bq2419x_charger->battery_status_valid) {
+ fault = bq2419x_charger->fault;
+ ss_reg = bq2419x_charger->status;
+ bq2419x_charger->battery_status_valid = false;
+ } else {
+ ret = bq2419x_read(BQ2419X_REG_FAULT, &fault);
+ if (ret)
+ return ret;
+
+ ret = bq2419x_read(BQ2419X_REG_SYSTEM_STATUS, &ss_reg);
+ if (ret)
+ return ret;
+ }
+
+ fault &= BQ2419X_CHG_FAULT_MASK;
+ fault >>= BQ2419X_CHG_FAULT_SHIFT;
+
+ /* the battery is discharging if either
+ * - we don't have a good power source
+ * - we have a charge fault */
+ if (!(ss_reg & BQ2419X_PG_STAT_MASK) || fault) {
+ return PMU_STATUS_DISCHARGING;
+ } else {
+ ss_reg &= BQ2419X_CHG_STAT_MASK;
+ ss_reg >>= BQ2419X_CHG_STAT_SHIFT;
+
+ switch(ss_reg) {
+ case 0x0: /* not charging */
+ return PMU_STATUS_NOT_CHARGING;
+ case 0x1: /* pre charging */
+ case 0x2: /* fast charging */
+ return PMU_STATUS_CHARGING;
+ case 0x3: /* charge termination done */
+ return PMU_STATUS_FULL;
+ }
+ }
+ return PMU_STATUS_NOT_CHARGING;
+}
+
+static bool bq2419x_battery_get_online(pmu_charger_t *pmu_charger)
+{
+ uint8_t batfet_disable;
+ int8_t ret;
+
+ (void) pmu_charger;
+
+ ret = bq2419x_read(BQ2419X_REG_MISC_OPERATION, &batfet_disable);
+ if (ret)
+ return false;
+
+ batfet_disable &= BQ2419X_BATFET_DISABLE_MASK;
+ batfet_disable >>= BQ2419X_BATFET_DISABLE_SHIFT;
+
+ return !batfet_disable;
+}
+
+static enum pmu_health bq2419x_battery_get_health(pmu_charger_t *pmu_charger)
+{
+ uint8_t fault;
+ int8_t ret;
+ struct bq2419x_pmu_charger *bq2419x_charger;
+
+ bq2419x_charger = container_of(
+ pmu_charger, struct bq2419x_pmu_charger, pmu_charger);
+
+ if (bq2419x_charger->battery_health_valid) {
+ fault = bq2419x_charger->fault;
+ bq2419x_charger->battery_health_valid = false;
+ } else {
+ ret = bq2419x_read(BQ2419X_REG_FAULT, &fault);
+ if (ret)
+ return ret;
+ }
+
+ if (fault & BQ2419X_BAT_FAULT_MASK)
+ return PMU_HEALTH_OVERVOLTAGE;
+ else {
+ fault &= BQ2419X_NTC_FAULT_MASK;
+ fault >>= BQ2419X_NTC_FAULT_SHIFT;
+ switch (fault) {
+ case 0x0:
+ /* all is well */
+ return PMU_HEALTH_GOOD;
+ case 0x1:
+ case 0x3:
+ case 0x5:
+ /* either TS1 cold, TS2 cold, or both cold */
+ return PMU_HEALTH_COLD;
+ case 0x2:
+ case 0x4:
+ case 0x6:
+ /* either TS1 hot, TS2 hot, or both hot */
+ return PMU_HEALTH_OVERHEAT;
+ default:
+ return PMU_HEALTH_UNKNOWN;
+ }
+ }
+}
+
+static bool bq2419x_charger_get_online(pmu_charger_t *pmu_charger)
+{
+ uint8_t val;
+ int8_t ret;
+
+ (void) pmu_charger;
+
+ ret = bq2419x_read(BQ2419X_REG_SYSTEM_STATUS, &val);
+ if (ret)
+ return ret;
+
+ /* check the power good bit */
+ return !!(BQ2419X_PG_STAT_MASK & val);
+}
+
+static inline bool bq2419x_get_irq(void)
+{
+ /* the CHG_IRQ line is low active */
+ return !io_test_pin(CHG_IRQ);
+}
+
+uint8_t bq2419x_pmu_charger_check_events(pmu_charger_t *pmu_charger)
+{
+ uint8_t flags;
+ int8_t ret;
+ uint8_t status;
+ uint8_t fault;
+ uint8_t isc;
+ volatile bool event;
+ bq2419x_pmu_charger_t *bq2419x_charger;
+
+ bq2419x_charger = container_of(
+ pmu_charger, struct bq2419x_pmu_charger, pmu_charger);
+
+ event = false;
+ flags = PMU_CHARGER_EVENT_NONE;
+
+ ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+ if (bq2419x_charger->event) {
+ bq2419x_charger->event = false;
+ event = true;
+ }
+ }
+
+ if (event) {
+
+ ret = bq2419x_read(BQ2419X_REG_SYSTEM_STATUS, &status);
+ if (ret)
+ return ret;
+
+ if (status != bq2419x_charger->status) {
+ if ((bq2419x_charger->status & BQ2419X_PG_STAT_MASK) &&
+ !(status & BQ2419X_PG_STAT_MASK)) {
+ /* we're in host mode and need to turn off HIZ
+ * when PG_STAT goes 1->0, in order to have the
+ * battery supply the juice */
+ ret = bq2419x_read(BQ2419X_REG_INPUT_SRC_CTL,
+ &isc);
+ if (ret)
+ return 0;
+
+ isc &= ~BQ2419X_EN_HIZ_MASK;
+ ret = bq2419x_write(BQ2419X_REG_INPUT_SRC_CTL,
+ isc);
+ if (ret)
+ return 0;
+ }
+
+ if ((bq2419x_charger->status & BQ2419X_CHG_STAT_MASK)
+ != (status & BQ2419X_CHG_STAT_MASK)) {
+ if ((status & BQ2419X_CHG_STAT_MASK) >> BQ2419X_CHG_STAT_SHIFT == 0x3)
+ flags |= PMU_CHARGER_EVENT_CHARGE_DONE;
+ }
+ bq2419x_charger->status = status;
+ flags |= PMU_CHARGER_EVENT_STATUS_CHANGE;
+ }
+
+ ret = bq2419x_read(BQ2419X_REG_FAULT, &fault);
+ if (ret)
+ return ret;
+
+ if (fault != bq2419x_charger->fault) {
+ bq2419x_charger->fault = fault;
+ bq2419x_charger->battery_status_valid = true;
+ bq2419x_charger->battery_health_valid = true;
+ bq2419x_charger->charger_health_valid = true;
+ flags |= PMU_CHARGER_EVENT_FAULT_CHANGE;
+ }
+
+ if (!bq2419x_charger->first_time)
+ bq2419x_charger->first_time = true;
+ }
+ return flags;
+}
+
+irqreturn_t bq2419x_irq_handler(void)
+{
+ /* we check if the device indicates an event
+ * if so we are the source of the IRQ,
+ * so set the flag to deal with it later.
+ * Otherwise we indicate to check the other devices,
+ * by returning IRQ_NONE
+ */
+ if (bq2419x_get_irq()) {
+ charger.event = true;
+ return IRQ_HANDLED;
+ }
+ (void) charger.event;
+ return IRQ_NONE;
+}
+
+static const pmu_charger_ops_t bq2419x_pmu_charger_ops = {
+ .set_charger_voltage = NULL,
+ .set_charger_current = NULL,
+
+ .get_charge_type = bq2419x_charger_get_charge_type,
+ .set_charge_type = NULL,
+ .get_charger_health = bq2419x_charger_get_health,
+ .get_charger_online = bq2419x_charger_get_online,
+
+ .get_battery_health = bq2419x_battery_get_health,
+ .get_battery_status = bq2419x_battery_get_status,
+ .get_battery_online = bq2419x_battery_get_online,
+
+ .check_events = bq2419x_pmu_charger_check_events,
+};
+
+int8_t bq2419x_init(void)
+{
+ uint8_t id, input_src_ctrl, ir_comp;
+ int8_t ret;
+
+ /* initialize the state struct */
+ memset(&charger, 0, sizeof(charger));
+ charger.pmu_charger.ops = &bq2419x_pmu_charger_ops;
+ charger.first_time = true;
+
+ /* check vendor register to verify we're looking at
+ * a TI BQ2419x chip */
+ ret = bq2419x_read(BQ2419X_REG_VENDOR_PART_REV, &id);
+ if (ret)
+ goto fail_i2c_read;
+
+ /* set charge IRQ pin as input */
+ io_input_pin(CHG_IRQ);
+ io_set_pin(CHG_IRQ);
+
+ bq2419x_reset();
+ bq2419x_set_host_mode();
+
+ /* we leave the other registers at default values
+ * BQ2419X_REG_PWR_ON_CONFIG:
+ * - minimum system voltage limit (default) 101 3.5V
+ * BQ2419X_REG_CHARGE_VOLTAGE:
+ * - fast charge current limit (default) 011000 2048mA
+ * BQ2419X_REG_PRE_TERM_CURRENT:
+ * - pre-charge current limit (default) 0001 256mA
+ * - termination current limit (default) 0001 256mA
+ * BQ2419X_REG_CHARGE_VOLTAGE:
+ * - charge voltage limit (default) 101100 4.208V
+ */
+
+ bq2419x_read(BQ2419X_REG_INPUT_SRC_CTL, &input_src_ctrl);
+
+ /* set a 3A limit */
+ input_src_ctrl |= 0x7;
+
+ ret = bq2419x_write(BQ2419X_REG_INPUT_SRC_CTL, input_src_ctrl);
+ if (ret)
+ return ret;
+
+ /* compensate for 20m r_sense */
+ ret = bq2419x_read(BQ2419X_REG_THERMAL_REG_CTRL, &ir_comp);
+ if (ret)
+ return ret;
+ ir_comp &= ~BQ2419X_BAT_COMP_MASK;
+ ir_comp |= (0x02 << BQ2419X_BAT_COMP_SHIFT);
+
+ /* set thermal regulation to 60 C */
+ ir_comp &= ~BQ2419X_TREG_MASK;
+ ir_comp |= (0x00 << BQ2419X_TREG_SHIFT);
+
+ ret = bq2419x_write(BQ2419X_REG_THERMAL_REG_CTRL, ir_comp);
+ if (ret)
+ return ret;
+
+ pmu_register_charger(&charger.pmu_charger);
+
+ return 0;
+
+fail_i2c_read:
+ return 1;
+}
diff --git a/firmware/e300/battery/bq2419x.h b/firmware/e300/battery/bq2419x.h
new file mode 100644
index 000000000..97e691e33
--- /dev/null
+++ b/firmware/e300/battery/bq2419x.h
@@ -0,0 +1,46 @@
+/* USRP E310 Firmware Texas Instruments BQ2419x 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/>.
+ */
+
+/**
+ * \file bq2419x.h
+ * \brief Texas Instruments BQ2419x driver
+ */
+
+#ifndef BQ2419X_H
+#define BQ2419X_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "pmu.h"
+#include "interrupt.h"
+
+typedef enum {
+ BQ2419X_MODEL_24192 = 0x0,
+ BQ2419X_MODEL_24191 = 0x1,
+} bq2491x_model_t;
+
+/**
+ * \brief Initializes the BQ2419X chip
+ */
+int8_t bq2419x_init(void);
+
+/**
+ * \brief The IRQ handler for the CHG_IRQ external pin change interrupt
+ * \return IRQ_HANDLED in case IRQ was handled, IRQ_NONE in case shared interrupt is not for us
+ */
+extern irqreturn_t bq2419x_irq_handler(void);
+
+#endif /* BQ2419X_H */
diff --git a/firmware/e300/battery/eeprom.c b/firmware/e300/battery/eeprom.c
new file mode 100644
index 000000000..f8bc172d2
--- /dev/null
+++ b/firmware/e300/battery/eeprom.c
@@ -0,0 +1,43 @@
+/* USRP E310 Firmware EEPROM 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 "eeprom.h"
+#include <avr/eeprom.h>
+
+/* the avr libc wants it this way ... */
+static uint8_t* EEPROM_AUTOBOOT_OFFSET = (uint8_t *) 0x00;
+static uint16_t *EEPROM_LAST_FULL_OFFSET = (uint16_t *) 0x04;
+
+static const uint8_t EEPROM_AUTOBOOT_MAGIC = 0xa5;
+
+bool eeprom_get_autoboot(void)
+{
+ return EEPROM_AUTOBOOT_MAGIC == eeprom_read_byte(EEPROM_AUTOBOOT_OFFSET);
+}
+
+void eeprom_set_autoboot(bool on)
+{
+ eeprom_update_byte(EEPROM_AUTOBOOT_OFFSET, on ? EEPROM_AUTOBOOT_MAGIC : 0x00);
+}
+
+uint16_t eeprom_get_last_full(void)
+{
+ return eeprom_read_word(EEPROM_LAST_FULL_OFFSET);
+}
+
+void eeprom_set_last_full_charge(uint16_t charge)
+{
+ eeprom_update_word(EEPROM_LAST_FULL_OFFSET, charge);
+}
diff --git a/firmware/e300/battery/eeprom.h b/firmware/e300/battery/eeprom.h
new file mode 100644
index 000000000..79ae505b6
--- /dev/null
+++ b/firmware/e300/battery/eeprom.h
@@ -0,0 +1,55 @@
+/* USRP E310 Firmware EEPROM 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/>.
+ */
+
+/**
+ * \file eeprom.h
+ * \brief AVR EEPROM driver
+ */
+
+#ifndef EEPROM_H
+#define EEPROM_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <avr/io.h>
+
+/**
+ * \brief Get the value for the autoboot flag set in the EEPROM
+ *
+ */
+bool eeprom_get_autoboot(void);
+
+/**
+ * \brief Set the value for the autoboot flag set in the EEPROM
+ *
+ * \param[in] on value to write to EEPROM. Use 'true' to turn on autoboot.
+ *
+ */
+void eeprom_set_autoboot(bool on);
+
+/**
+ * \brief Get last full charge from the EEPROM
+ *
+ */
+uint16_t eeprom_get_last_full(void);
+
+/**
+ * \brief Set last full charge in the EEPROM
+ *
+ * \param[in] charge value to write to EEPROM.
+ */
+void eeprom_set_last_full_charge(uint16_t charge);
+
+#endif /* EEPROM_H */
diff --git a/firmware/e300/battery/fpga.c b/firmware/e300/battery/fpga.c
new file mode 100644
index 000000000..20404e5a1
--- /dev/null
+++ b/firmware/e300/battery/fpga.c
@@ -0,0 +1,296 @@
+/* USRP E310 FPGA 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 "eeprom.h"
+#include "fpga.h"
+#include "spi.h"
+#include "mcu_settings.h"
+#include "utils.h"
+#include <util/delay.h>
+#include <string.h>
+#include <stdbool.h>
+
+typedef struct fpga_tx_mem_map0 {
+ uint16_t battery_voltage;
+ uint8_t battery_status;
+ uint8_t charger_status;
+ uint8_t unused[2];
+ uint8_t version;
+ uint8_t type;
+} fpga_tx_mem_map0_t;
+
+typedef struct fpga_tx_mem_map1 {
+ uint8_t status;
+ uint16_t voltage;
+ uint16_t temp;
+ uint16_t charge;
+ uint8_t type;
+} fpga_tx_mem_map1_t;
+
+typedef struct fpga_tx_mem_map2 {
+ uint8_t unused[4];
+ uint8_t settings;
+ uint16_t charge_last_full;
+ uint8_t type;
+} fpga_tx_mem_map2_t;
+
+typedef struct fpga_rx_mem_map0 {
+ uint8_t unused[2];
+ uint16_t value;
+ uint8_t reg;
+ uint8_t os_status;
+} fpga_rx_mem_map0_t;
+
+typedef struct fpga_rx_mem_map1 {
+ uint8_t unused[3];
+ uint16_t value;
+ uint8_t reg;
+} fpga_rx_mem_map1_t;
+
+typedef struct fpga_rx_mem_map {
+ uint8_t valid;
+ union {
+ fpga_rx_mem_map0_t map0;
+ fpga_rx_mem_map1_t map1;
+ };
+ uint8_t type;
+} fpga_rx_mem_map_t;
+
+static bool shutdown = false;
+static bool write_charge = false;
+static bool write_settings = false;
+
+static volatile fpga_tx_mem_map0_t fpga_tx0;
+static volatile fpga_tx_mem_map1_t fpga_tx1;
+static volatile fpga_tx_mem_map2_t fpga_tx2;
+static volatile fpga_rx_mem_map_t fpga_rx;
+
+/* battery status */
+static const uint8_t BATTERY_TEMP_ALERT_MASK = BIT(7) | BIT(6);
+static const uint8_t BATTERY_TEMP_ALERT_SHIFT = 6;
+static const uint8_t BATTERY_ONLINE_MASK = BIT(5);
+static const uint8_t BATTERY_ONLINE_SHIFT = 5;
+static const uint8_t BATTERY_HEALTH_MASK = BIT(4) | BIT(3) | BIT(2);
+static const uint8_t BATTERY_HEALTH_SHIFT = 2;
+static const uint8_t BATTERY_STATUS_MASK = BIT(1) | BIT(0);
+static const uint8_t BATTERY_STATUS_SHIFT = 0;
+
+/* charger_status */
+static const uint8_t CHARGER_HEALTH_MASK = BIT(5) | BIT(4);
+static const uint8_t CHARGER_HEALTH_SHIFT = 4;
+static const uint8_t CHARGER_ONLINE_MASK = BIT(3);
+static const uint8_t CHARGER_ONLINE_SHIFT = 3;
+/* BIT(2) is unused */
+static const uint8_t CHARGER_CHARGE_TYPE_MASK = BIT(1) | BIT(0);
+static const uint8_t CHARGER_CHARGE_TYPE_SHIFT = 0;
+
+
+void fpga_set_battery_voltage(uint16_t voltage)
+{
+ fpga_tx0.battery_voltage = voltage;
+}
+
+void fpga_set_battery_temp_alert(uint8_t alert)
+{
+ uint8_t status = fpga_tx0.battery_status;
+
+ status &= ~BATTERY_TEMP_ALERT_MASK;
+ status |= alert << BATTERY_TEMP_ALERT_SHIFT;
+
+ fpga_tx0.battery_status = status;
+}
+
+void fpga_set_battery_online(bool online)
+{
+ uint8_t status = fpga_tx0.battery_status;
+
+ status &= ~BATTERY_ONLINE_MASK;
+ status |= (online ? 1 : 0) << BATTERY_ONLINE_SHIFT;
+
+ fpga_tx0.battery_status = status;
+}
+
+void fpga_set_battery_health(uint8_t health)
+{
+ uint8_t status = fpga_tx0.battery_status;
+
+ status &= ~BATTERY_HEALTH_MASK;
+ status |= health << BATTERY_HEALTH_SHIFT;
+
+ fpga_tx0.battery_status = status;
+}
+
+void fpga_set_battery_status(uint8_t st)
+{
+ uint8_t status = fpga_tx0.battery_status;
+
+ status &= ~BATTERY_STATUS_MASK;
+ status |= st << BATTERY_STATUS_SHIFT;
+
+ fpga_tx0.battery_status = status;
+}
+
+void fpga_set_charger_health(uint8_t health)
+{
+ uint8_t status = fpga_tx0.charger_status;
+
+ status &= ~CHARGER_HEALTH_MASK;
+ status |= health << CHARGER_HEALTH_SHIFT;
+
+ fpga_tx0.charger_status = status;
+}
+
+void fpga_set_charger_online(bool online)
+{
+ uint8_t status = fpga_tx0.charger_status;
+
+ status &= ~CHARGER_ONLINE_MASK;
+ status |= (online ? 1 : 0) << CHARGER_ONLINE_SHIFT;
+
+ fpga_tx0.charger_status = status;
+}
+
+void fpga_set_charger_charge_type(uint8_t type)
+{
+ uint8_t status = fpga_tx0.charger_status;
+
+ status &= ~CHARGER_CHARGE_TYPE_MASK;
+ status |= type << CHARGER_CHARGE_TYPE_SHIFT;
+
+ fpga_tx0.charger_status = status;
+}
+
+void fpga_set_gauge_charge(uint16_t charge)
+{
+ fpga_tx1.charge = charge;
+}
+
+uint16_t fpga_get_gauge_charge(void)
+{
+ return fpga_tx1.charge;
+}
+
+bool fpga_get_write_charge(void)
+{
+ bool ret = false;
+
+ if (write_charge) {
+ ret = write_charge;
+ write_charge = false;
+ }
+
+ return ret;
+}
+
+uint8_t fpga_get_settings(void)
+{
+ return fpga_tx2.settings;
+}
+
+bool fpga_get_write_settings(void)
+{
+ bool ret = false;
+
+ if (write_settings) {
+ ret = write_settings;
+ write_settings = false;
+ }
+
+ return ret;
+}
+
+void fpga_set_gauge_charge_last_full(uint16_t charge)
+{
+ fpga_tx2.charge_last_full = charge;
+}
+
+void fpga_set_gauge_temp(uint16_t temp)
+{
+ fpga_tx1.temp = temp;
+}
+
+void fpga_set_gauge_voltage(uint16_t volt)
+{
+ fpga_tx1.voltage = volt;
+}
+
+void fpga_set_gauge_status(uint8_t status)
+{
+ fpga_tx1.status = status;
+}
+
+bool fpga_get_shutdown(void)
+{
+ return shutdown;
+}
+
+void fpga_init(void)
+{
+ memset((void *) &fpga_tx0, 0, sizeof(fpga_tx0));
+ memset((void *) &fpga_tx1, 0, sizeof(fpga_tx1));
+ memset((void *) &fpga_tx2, 0, sizeof(fpga_tx2));
+ fpga_tx0.type = 0;
+ fpga_tx0.version = VERSION_MAJ << 4 | VERSION_MIN;
+
+ fpga_tx1.type = 1;
+ fpga_tx2.type = 2;
+
+ /* get autoboot value from eeprom, keep TX reg on */
+ fpga_tx2.settings = BIT(1) | eeprom_get_autoboot() ? 0x1 : 0x0;
+
+ memset((void *) &fpga_rx, 0, sizeof(fpga_rx));
+
+ shutdown = false;
+}
+
+void fpga_handle_write(uint8_t reg, uint16_t value)
+{
+ if (reg == 0x10) {
+ fpga_tx1.charge = value;
+ write_charge = true;
+ } else if (reg == 0x14) {
+ fpga_tx1.status = value;
+ } else if (reg == 0x1c) {
+ fpga_tx2.settings = (uint8_t) value;
+ write_settings = true;
+ }
+}
+
+void fpga_sync(void)
+{
+ fpga_rx_mem_map_t rx;
+
+ spi_transact_buf((uint8_t *) &fpga_tx0, (uint8_t *) &rx, 8);
+ if (rx.valid) {
+ if (rx.type == 0 && rx.map0.os_status == 0x7a)
+ shutdown = true;
+ else if (rx.type == 1)
+ fpga_handle_write(rx.map1.reg, rx.map1.value);
+ }
+ spi_transact_buf((uint8_t *) &fpga_tx1, (uint8_t *) &rx, 8);
+ if (rx.valid) {
+ if (rx.type == 0 && rx.map0.os_status == 0x7a)
+ shutdown = true;
+ else if (rx.type == 1)
+ fpga_handle_write(rx.map1.reg, rx.map1.value);
+ }
+ spi_transact_buf((uint8_t *) &fpga_tx2, (uint8_t *) &rx, 8);
+ if (rx.valid) {
+ if (rx.type == 0 && rx.map0.os_status == 0x7a)
+ shutdown = true;
+ else if (rx.type == 1)
+ fpga_handle_write(rx.map1.reg, rx.map1.value);
+ }
+}
diff --git a/firmware/e300/battery/fpga.h b/firmware/e300/battery/fpga.h
new file mode 100644
index 000000000..869c130de
--- /dev/null
+++ b/firmware/e300/battery/fpga.h
@@ -0,0 +1,180 @@
+/* USRP E310 Firmware FPGA 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/>.
+ */
+
+/**
+ * \file fpga.h
+ * \brief FPGA driver
+ */
+
+#ifndef FPGA_H
+#define FPGA_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/**
+ * \brief Initialize the fpga internal shadow registers
+ *
+ */
+void fpga_init(void);
+
+/* battery stuff */
+
+/**
+ * \brief Initialize the fpga internal shadow registers
+ *
+ * \param[in] voltage The voltage that will be sent to the FPGA
+ *
+ */
+void fpga_set_battery_voltage(uint16_t voltage);
+
+/**
+ * \brief Trigger a battery temp alert in FPGA
+ *
+ * \param[in] alert The alert flags
+ *
+ */
+void fpga_set_battery_temp_alert(uint8_t alert);
+
+void fpga_set_battery_online(bool online);
+
+bool fpga_get_battery_online(void);
+
+/**
+ * \brief Set the battery health that will be sent to the FPGA with next fpga_sync()
+ *
+ * \param[in] health The battery health
+ *
+ */
+void fpga_set_battery_health(uint8_t health);
+
+/**
+ * \brief Set the battery status that will be sent to the FPGA with next fpga_sync()
+ *
+ * \param[in] status The battery status
+ *
+ */
+void fpga_set_battery_status(uint8_t status);
+
+/* charger stuff */
+/**
+ * \brief Set the charger status that will be sent to the FPGA with next fpga_sync()
+ *
+ * \param[in] status The charger status
+ *
+ */
+void fpga_set_charger_status(uint8_t status);
+
+void fpga_set_charger_online(bool online);
+
+void fpga_set_charger_charge_type(uint8_t type);
+
+/**
+ * \brief Set the charger health that will be sent to the FPGA with next fpga_sync()
+ *
+ * \param[in] health The charger health
+ *
+ */
+void fpga_set_charger_health(uint8_t health);
+
+/**
+ * \brief Set the gauge charge value that is sent to the FPGA with next fpga_sync()
+ *
+ * \param[in] charge The accumulated charge value
+ *
+ */
+void fpga_set_gauge_charge(uint16_t charge);
+
+/**
+ * \brief Get the gauge charge value that is stored in shadow register
+ *
+ * \return The accumulated charge value stored in shadow register
+ *
+ */
+uint16_t fpga_get_gauge_charge(void);
+
+/**
+ * \brief Set the gauge charge last full value that is sent to the FPGA with next fpga_sync()
+ *
+ * \param[in] charge The accumulated charge when last charge cycle terminated
+ *
+ */
+void fpga_set_gauge_charge_last_full(uint16_t charge);
+
+/**
+ * \brief Set the gauge temperature value that is sent to the FPGA with next fpga_sync()
+ *
+ * \param[in] temp The temperature as reported by fuel gauge
+ *
+ */
+void fpga_set_gauge_temp(uint16_t temp);
+
+/**
+ * \brief Set the gauge voltage value that is sent to the FPGA with next fpga_sync()
+ *
+ * \param[in] volt The voltage as reported by fuel gauge
+ *
+ */
+void fpga_set_gauge_voltage(uint16_t volt);
+
+/**
+ * \brief Set the gauge status value that is sent to the FPGA with next fpga_sync()
+ *
+ * \param[in] status The status as reported by fuel gauge
+ *
+ */
+void fpga_set_gauge_status(uint8_t status);
+
+/* misc stuff */
+
+/**
+ * \brief Get the shutdown flag from the shadow registers
+ *
+ * \return true if shutdown was requested by FPGA
+ *
+ */
+bool fpga_get_shutdown(void);
+
+/**
+ * \brief Get the write charge flag from shadow register
+ *
+ * \return true if write to charge count was requested by FPGA
+ *
+ */
+bool fpga_get_write_charge(void);
+
+/**
+ * \brief Get the settings from shadow register
+ *
+ * \return value that was requested to be stored in settings reg
+ *
+ */
+uint8_t fpga_get_settings(void);
+
+/**
+ * \brief Get the settings write flag from shadow register
+ *
+ * \return true if write to settings reg was requested by FPGA
+ *
+ */
+bool fpga_get_write_settings(void);
+
+/**
+ * \brief Synchronize the shadow registers with the FPGA
+ *
+ */
+void fpga_sync(void);
+
+#endif /* FPGA_H */
diff --git a/firmware/e300/battery/i2c_twi.c b/firmware/e300/battery/i2c_twi.c
new file mode 100644
index 000000000..f2c0491d3
--- /dev/null
+++ b/firmware/e300/battery/i2c_twi.c
@@ -0,0 +1,187 @@
+/* USRP E310 Firmware Atmel AVR TWI 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 "i2c_twi.h"
+#include "mcu_settings.h"
+#include "utils.h"
+
+#include <stdbool.h>
+#include <avr/io.h>
+#include <util/twi.h>
+#include <util/delay.h>
+
+static const uint8_t I2C_TIMEOUT = 10;
+
+static inline uint8_t I2C_READ_ADDR(const uint8_t x)
+{
+ return (x << 1) | 0x1;
+}
+
+static inline uint8_t I2C_WRITE_ADDR(const uint8_t x)
+{
+ return (x << 1) & 0xfe;
+}
+
+void i2c_twi_init_calc(uint32_t rate)
+{
+ uint8_t twbr;
+ twbr = ((F_CPU/rate)-16)/2;
+
+ TWBR = twbr;
+
+ PRR &= ~BIT(PRTWI);
+
+ /* www.mikrocontroller.net/articles/AVR_TWI says this might help ... */
+ TWCR &= ~(BIT(TWSTO) | BIT(TWEN));
+ TWCR |= BIT(TWEN);
+}
+
+void i2c_twi_init(i2c_speed_t speed)
+{
+ switch (speed) {
+ case I2C_SPEED_400K:
+ TWBR = 16;
+ break;
+ case I2C_SPEED_100K:
+ TWBR = 32;
+ break;
+ default:
+ TWBR = 32;
+ break;
+ }
+
+ /* reset potential prescalers */
+ TWSR = 0;
+
+ /* www.mikrocontroller.net/articles/AVR_TWI says this might help ... */
+ TWCR &= ~(BIT(TWSTO) | BIT(TWEN));
+ TWCR |= BIT(TWEN);
+}
+
+static void i2c_twi_wait_for_complete(void)
+{
+ uint8_t timeout = 100;
+
+ do {
+ _delay_us(10);
+ timeout--;
+ } while(timeout && !(TWCR & (1<<TWINT)));
+}
+
+static void i2c_twi_start(void)
+{
+ TWCR = BIT(TWINT) | BIT(TWEN) | BIT(TWSTA);
+ i2c_twi_wait_for_complete();
+}
+
+static void i2c_twi_stop(void)
+{
+ TWCR = BIT(TWINT) | BIT(TWEN) | BIT(TWSTO);
+}
+
+
+static uint8_t i2c_twi_recv_byte(bool ack)
+{
+ TWCR = BIT(TWINT) | BIT(TWEN) | (ack ? BIT(TWEA) : 0);
+ i2c_twi_wait_for_complete();
+ return TWDR;
+}
+
+static void i2c_twi_send_byte(uint8_t data)
+{
+ TWDR = data;
+ TWCR = BIT(TWINT) | BIT(TWEN);
+ i2c_twi_wait_for_complete();
+}
+
+int8_t i2c_twi_read(uint8_t addr, uint8_t reg, uint8_t *value)
+{
+ /* start the write transaction to select the register */
+ i2c_twi_start();
+ i2c_twi_send_byte(I2C_WRITE_ADDR(addr));
+ i2c_twi_send_byte(reg);
+
+ /* (re)start for the actual read transaction to read back */
+ i2c_twi_start();
+ i2c_twi_send_byte(I2C_READ_ADDR(addr));
+ *value = i2c_twi_recv_byte(false);
+ i2c_twi_stop();
+
+ return 0;
+}
+
+int8_t i2c_twi_write(uint8_t addr, uint8_t reg, uint8_t value)
+{
+ i2c_twi_start();
+ i2c_twi_send_byte(I2C_WRITE_ADDR(addr));
+ i2c_twi_send_byte(reg);
+ i2c_twi_send_byte(value);
+ i2c_twi_stop();
+
+ return 0;
+}
+
+int8_t i2c_twi_read16(uint8_t addr, uint8_t reg, uint16_t *value)
+{
+ uint8_t msb, lsb;
+
+ /* start the write transaction to select the register */
+ i2c_twi_start();
+ i2c_twi_send_byte(I2C_WRITE_ADDR(addr));
+ i2c_twi_send_byte(reg);
+
+ /* (re)start for the actual read transaction to read back MSB
+ * then LSB, fortunately the datashit describes the opposite w.r.t ACKs*/
+ i2c_twi_start();
+ i2c_twi_send_byte(I2C_READ_ADDR(addr));
+ msb = i2c_twi_recv_byte(true);
+ lsb = i2c_twi_recv_byte(false);
+ i2c_twi_stop();
+
+ *value = (msb << 8) | lsb;
+
+ return 0;
+}
+
+int8_t i2c_twi_write16(uint8_t addr, uint8_t reg, uint16_t value)
+{
+ uint8_t msb, lsb;
+
+ msb = value >> 8;
+ lsb = value & 0xff;
+
+ i2c_twi_start();
+ i2c_twi_send_byte(I2C_WRITE_ADDR(addr));
+ i2c_twi_send_byte(reg);
+ i2c_twi_send_byte(msb);
+ i2c_twi_send_byte(lsb);
+ i2c_twi_stop();
+
+ return 0;
+}
+
+/*
+static const uint8_t I2C_ARA_ADDR = 0x0c;
+uint8_t i2c_twi_smbus_ara(void)
+{
+ volatile uint8_t addr;;
+
+ i2c_twi_start();
+ i2c_twi_send_byte(I2C_READ_ADDR(I2C_ARA_ADDR));
+ addr = i2c_twi_recv_byte(false);
+ i2c_twi_stop();
+ return addr;
+}
+*/
diff --git a/firmware/e300/battery/i2c_twi.h b/firmware/e300/battery/i2c_twi.h
new file mode 100644
index 000000000..dd88a683d
--- /dev/null
+++ b/firmware/e300/battery/i2c_twi.h
@@ -0,0 +1,95 @@
+/* USRP E310 Firmware Atmel AVR TWI 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/>.
+ */
+
+/**
+ * \file i2c_twi.h
+ * \brief Atmel AVR TWI driver
+ */
+
+#ifndef I2C_TWI_H
+#define I2C_TWI_H
+
+#include <stdlib.h>
+#include <stdio.h>
+
+/**
+ * \brief Used for initializing the TWI/I2C module
+ */
+typedef enum i2c_speed_t {
+ I2C_SPEED_100K,
+ I2C_SPEED_400K,
+} i2c_speed_t;
+
+/**
+ * \brief Initialize and calculate the TWBR value based on F_CPU and given rate
+ *
+ * \param[in] rate Target rate
+ */
+void i2c_twi_init_calc(uint32_t rate);
+
+/**
+ * \brief Initializes the AVR TWI/I2C module
+ *
+ * \param[in] speed Can be either 100KHz or 400Khz
+ */
+void i2c_twi_init(i2c_speed_t speed);
+
+/**
+ * \brief Read I2C register from I2C slave
+ * \param[in] addr I2C slave address
+ * \param[in] reg Register address in slave register map
+ * \param[out] value Output value
+ * \return 0 on success, negative error code otherwise
+ */
+int8_t i2c_twi_read(uint8_t addr, uint8_t reg, uint8_t *value);
+
+/**
+ * \brief Read 2 byte I2C register from I2C slave
+ *
+ * This is behaving a bit funny but is required for getting
+ * the 2 byte values from the LTC294x chip.
+ *
+ * \param[in] addr I2C slave address
+ * \param[in] reg Register address in slave register map
+ * \param[out] value Output value
+ * \return 0 on success, negative error code otherwise
+ */
+int8_t i2c_twi_read16(uint8_t addr, uint8_t reg, uint16_t *value);
+
+/**
+ * \brief Write I2C register in I2C slave
+ * \param[in] addr I2C slave address
+ * \param[in] reg Register address in slave register map
+ * \param[in] value Value to be written
+ * \return 0 on success, negative error code otherwise
+ */
+int8_t i2c_twi_write(uint8_t addr, uint8_t reg, uint8_t value);
+
+/**
+ * \brief Write 2 byte I2C register in I2C slave
+ * \param[in] addr I2C slave address
+ * \param[in] reg Register address in slave register map
+ * \param[in] value Value to be written
+ * \return 0 on success, negative error code otherwise
+ */
+int8_t i2c_twi_write16(uint8_t addr, uint8_t reg, uint16_t value);
+
+/**
+ * \brief Handle SMBus alert response
+ * \return 0 on success, negative error code otherwise
+ */
+uint8_t i2c_twi_smbus_ara(void);
+
+#endif /* I2C_TWI_H */
diff --git a/firmware/e300/battery/interrupt.c b/firmware/e300/battery/interrupt.c
new file mode 100644
index 000000000..f0daeddcd
--- /dev/null
+++ b/firmware/e300/battery/interrupt.c
@@ -0,0 +1,154 @@
+/* USRP E310 Firmware Interrupt Management
+ * 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 <stdint.h>
+#include <avr/interrupt.h>
+
+#include "utils.h"
+#include "interrupt.h"
+
+#include "bq2419x.h"
+#include "tps54478.h"
+#include "ltc3675.h"
+#include "ltc294x.h"
+#include "pmu.h"
+#include "led.h"
+
+static const irq_handler_t pcint0_irqs[] = {bq2419x_irq_handler};
+static const irq_handler_t pcint1_irqs[] = {NULL};
+static const irq_handler_t pcint2_irqs[] = {NULL};
+static const irq_handler_t pcint3_irqs[] = {NULL};
+static const irq_handler_t int0_handler = ltc3675_button_wakeup_irq_handler;
+static const irq_handler_t int1_handler = ltc3675_button_change_irq_handler;
+//static const irq_handler_t timer0_comp_a_handler = pmu_led_timer_comp_a_irq_handler;
+//static const irq_handler_t timer0_comp_b_handler = pmu_led_timer_comp_b_irq_handler;
+static const irq_handler_t timer1_handler = ltc3675_button_timer_irq_handler;
+static const irq_handler_t wdt_handler = {led_wdt_handler};
+
+void interrupt_init(void)
+{
+ /* rising edge for WAKEUP and any change for ONSWITCH_DB */
+ EICRA = BIT(ISC01) | BIT(ISC00) | BIT(ISC10);
+
+ /* enable interrupt for WAKE */
+ EIMSK = BIT(INT1) | BIT(INT0);
+
+ /* enable interrupt for CORE_PGOOD and CHG_IRQ */
+ PCMSK0 = /*BIT(PCINT0) | */ BIT(PCINT1);
+
+ /* enable interrupts for PWR_IRQ and AVR_IRQ */
+ PCMSK2 = BIT(PCINT16) | BIT(PCINT21);
+
+ /* enable interrupts for FC_ALn_CC */
+ //PCMSK3 = BIT(PCINT24);
+
+ /* unmask IRQs for PC[23:16] and PC[7:0] */
+ PCICR = BIT(PCIE3) | BIT(PCIE2) | BIT(PCIE0);
+}
+
+ISR(PCINT0_vect)
+{
+ uint8_t i;
+ irqreturn_t ret;
+
+ for (i = 0; i < ARRAY_SIZE(pcint0_irqs); i++) {
+ irq_handler_t handler = pcint0_irqs[i];
+ if (handler != NULL) {
+ ret = handler();
+ if (ret == IRQ_HANDLED)
+ break;
+ }
+ }
+}
+
+ISR(PCINT1_vect)
+{
+ uint8_t i;
+ irqreturn_t ret;
+
+ for (i = 0; i < ARRAY_SIZE(pcint1_irqs); i++) {
+ irq_handler_t handler = pcint1_irqs[i];
+ if (handler != NULL) {
+ ret = handler();
+ if (ret == IRQ_HANDLED)
+ break;
+ }
+ }
+}
+
+ISR(PCINT2_vect)
+{
+ uint8_t i;
+ irqreturn_t ret;
+
+ for (i = 0; i < ARRAY_SIZE(pcint2_irqs); i++) {
+ irq_handler_t handler = pcint2_irqs[i];
+ if (handler != NULL) {
+ ret = handler();
+ if (ret == IRQ_HANDLED)
+ break;
+ }
+ }
+}
+
+ISR(PCINT3_vect)
+{
+ uint8_t i;
+ irqreturn_t ret;
+
+ for (i = 0; i < ARRAY_SIZE(pcint3_irqs); i++) {
+ irq_handler_t handler = pcint3_irqs[i];
+ if (handler != NULL) {
+ ret = handler();
+ if (ret == IRQ_HANDLED)
+ break;
+ }
+ }
+}
+
+ISR(INT0_vect)
+{
+ if (int0_handler)
+ int0_handler();
+}
+
+ISR(INT1_vect)
+{
+ if (int1_handler) {
+ int1_handler();
+ }
+}
+
+/*
+ISR(TIMER0_COMPA_vect)
+{
+ timer0_comp_a_handler();
+}
+
+ISR(TIMER0_COMPB_vect)
+{
+ timer0_comp_b_handler();
+}
+*/
+
+ISR(TIMER1_COMPA_vect)
+{
+ timer1_handler();
+}
+
+ISR(WDT_vect)
+{
+ wdt_handler();
+}
diff --git a/firmware/e300/battery/interrupt.h b/firmware/e300/battery/interrupt.h
new file mode 100644
index 000000000..32cd3f174
--- /dev/null
+++ b/firmware/e300/battery/interrupt.h
@@ -0,0 +1,40 @@
+/* USRP E310 Firmware Interrupt Management
+ * 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/>.
+ */
+
+#ifndef INTERRUPT_H
+#define INTERRUPT_H
+
+/**
+ * \brief Shared IRQ handlers return either of these values
+ *
+ * A shared IRQ handler will either 'claim' the IRQ and return IRQ_HANDLED,
+ * or indicate it is not sure whether it was the IRQ source and return IRQ_NONE.
+ */
+typedef enum {
+ IRQ_NONE,
+ IRQ_HANDLED
+} irqreturn_t;
+
+/**
+ * \brief (Shared) IRQ handlers should use this type signature
+ */
+typedef irqreturn_t (*irq_handler_t)(void);
+
+/**
+ * \brief Initialize the IRQ subsystem
+ */
+void interrupt_init(void);
+
+#endif /* INTERRUPT_H */
diff --git a/firmware/e300/battery/io.c b/firmware/e300/battery/io.c
new file mode 100644
index 000000000..8bb393705
--- /dev/null
+++ b/firmware/e300/battery/io.c
@@ -0,0 +1,67 @@
+/* USRP E310 IO helpers
+* 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 <avr/io.h>
+
+#include "io.h"
+#include "utils.h"
+
+#define _GET_PIN(pin) ((pin) & 0xf)
+#define _GET_MASK(pin) (BIT(_GET_PIN(pin)))
+#define _GET_REG(pin, reg_x) (*reg_x[pin >> 4])
+
+static volatile uint8_t *ddr_x[] = {&DDRA, &DDRB, &DDRC, &DDRD};
+static volatile uint8_t *port_x[] = {&PORTA, &PORTB, &PORTC, &PORTD};
+static volatile uint8_t *pin_x[] = {&PINA, &PINB, &PINC, &PIND};
+
+void io_output_pin(io_pin_t pin)
+{
+ _GET_REG(pin, ddr_x) |= _GET_MASK(pin);
+}
+
+void io_input_pin(io_pin_t pin)
+{
+ _GET_REG(pin, ddr_x) &= ~_GET_MASK(pin);
+}
+
+bool io_is_output(io_pin_t pin)
+{
+ return bit_is_set(_GET_REG(pin, ddr_x), _GET_PIN(pin));
+}
+
+bool io_is_input(io_pin_t pin)
+{
+ return !io_is_output(pin);
+}
+
+void io_set_pin(io_pin_t pin)
+{
+ _GET_REG(pin, port_x) |= _GET_MASK(pin);
+}
+
+void io_clear_pin(io_pin_t pin)
+{
+ _GET_REG(pin, port_x) &= ~_GET_MASK(pin);
+}
+
+bool io_is_pin_set(io_pin_t pin)
+{
+ return bit_is_set(_GET_REG(pin, port_x), _GET_PIN(pin));
+}
+
+bool io_test_pin(io_pin_t pin)
+{
+ return bit_is_set(_GET_REG(pin, pin_x), _GET_PIN(pin));
+}
diff --git a/firmware/e300/battery/io.h b/firmware/e300/battery/io.h
new file mode 100644
index 000000000..81c007268
--- /dev/null
+++ b/firmware/e300/battery/io.h
@@ -0,0 +1,97 @@
+/* USRP E310 IO helpers
+ * 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/>.
+ */
+
+/**
+* \file io.h
+* \brief IO helper functions to manipulate the MCUs pins
+*/
+
+#ifndef IO_H
+#define IO_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#define IO_PX(port, pin) ((uint8_t)(((port - 'A') << 4) + pin))
+#define IO_PA(pin) IO_PX('A', pin)
+#define IO_PB(pin) IO_PX('B', pin)
+#define IO_PC(pin) IO_PX('C', pin)
+#define IO_PD(pin) IO_PX('D', pin)
+
+typedef const uint8_t io_pin_t;
+
+/**
+ * \brief Make pin an output pin
+ *
+ * \param pin The pin to modify
+ */
+void io_output_pin(io_pin_t pin);
+
+/**
+ * \brief Make pin an input pin
+ *
+ * \param pin The pin to modify
+ */
+void io_input_pin(io_pin_t pin);
+
+/**
+ * \brief Check if pin is an output
+ *
+ * \param pin The pin to query
+ * \return Returns true if the pin is configured as output
+ */
+bool io_is_output(io_pin_t pin);
+
+/**
+ * \brief Check if pin is an input
+ *
+ * \param pin The pin to query
+ * \return Returns true if the pin is configured as input
+ */
+bool io_is_input(io_pin_t pin);
+
+/**
+ * \brief If the pin is in input mode, this will enable the pull-up,
+ * if the pin is in output mode, this will set a logic high level
+ *
+ * \param[in] pin The pin to modify
+ */
+void io_set_pin(io_pin_t pin);
+
+/**
+ * \brief If the pin is in input mode this will disable the pull-up
+ * if the pin is in output mode, this will set a logic low level
+ *
+ * \param[in] pin The pin to modify
+ */
+void io_clear_pin(io_pin_t pin);
+
+/**
+ * \brief If the pin is in input mode, this returns true if the pull-up is active,
+ * if the pin is in output mode, this returns true if a logic high is set
+ * \param[in] pin The pin to query
+ * \return True if pin is set, False otherwise
+ */
+bool io_is_pin_set(io_pin_t pin);
+
+
+/**
+ * \brief If the pin is in input mode, this returns the logic input value
+ * \param[in] pin The pin to query
+ * \return Returns true if a logic high is observed on the input pin
+ */
+bool io_test_pin(io_pin_t pin);
+
+#endif /* IO_H */
diff --git a/firmware/e300/battery/led.c b/firmware/e300/battery/led.c
new file mode 100644
index 000000000..0e7e992ba
--- /dev/null
+++ b/firmware/e300/battery/led.c
@@ -0,0 +1,153 @@
+#include "led.h"
+#include "io.h"
+
+/* hardware io */
+static io_pin_t POWER_LED = IO_PC(7);
+static io_pin_t CHARGE = IO_PD(1);
+
+enum led_color {
+ LED_C_RED,
+ LED_C_GREEN,
+ LED_C_OFF
+};
+
+static enum led_color led_color;
+
+static inline void led_set(enum led_color color)
+{
+ switch (color) {
+ case LED_C_RED:
+ io_clear_pin(POWER_LED);
+ io_set_pin(CHARGE);
+ break;
+ case LED_C_GREEN:
+ io_clear_pin(CHARGE);
+ io_set_pin(POWER_LED);
+ break;
+ case LED_C_OFF:
+ default:
+ io_clear_pin(CHARGE);
+ io_clear_pin(POWER_LED);
+ break;
+ }
+ led_color = color;
+}
+
+/* blinken lights */
+static uint8_t blink_cnt;
+static uint8_t orange_cnt;
+
+/* state for sequence */
+static enum led_state state;
+static enum led_state state_next;
+static uint8_t seq_max;
+static uint8_t seq_cnt;
+static const uint8_t T_SEQ = 196;
+static const uint8_t T_ON = 98;
+
+static bool counting;
+
+void led_set_blink_seq(uint8_t n_blinks, enum led_state color)
+{
+ if (color == state)
+ return;
+
+ blink_cnt = 0;
+ seq_cnt = 0;
+ seq_max = 2 * n_blinks + 1;
+
+ state = color;
+}
+
+void led_set_solid(enum led_state color)
+{
+ if (state != LED_BLINK_RED_FAST)
+ state = color;
+ state_next = color;
+}
+
+void led_set_blink(enum led_state color)
+{
+ if (state != LED_BLINK_RED_FAST)
+ state = color;
+ state_next = color;
+}
+
+irqreturn_t led_wdt_handler(void)
+{
+ counting = false;
+ switch (state) {
+ case LED_BLINK_GREEN_SLOW:
+ if (blink_cnt < T_ON)
+ led_set(LED_C_GREEN);
+ else
+ led_set(LED_C_OFF);
+ blink_cnt += 1;
+ break;
+
+ case LED_BLINK_GREEN_FAST:
+ if (blink_cnt < T_ON)
+ led_set(LED_C_GREEN);
+ else
+ led_set(LED_C_OFF);
+ blink_cnt += 4;
+ break;
+
+ case LED_BLINK_RED_FAST:
+ counting = true;
+ if (!seq_cnt) {
+ led_set(LED_C_OFF);
+ } else if (blink_cnt < T_ON)
+ led_set(seq_cnt % 2 ? LED_C_OFF : LED_C_RED);
+ else
+ led_set(LED_C_OFF);
+ blink_cnt += 16;
+ break;
+
+ case LED_BLINK_ORANGE:
+ if (blink_cnt < T_ON)
+ led_set(orange_cnt % 2 ? LED_C_GREEN : LED_C_RED);
+ else
+ led_set(LED_C_OFF);
+
+ orange_cnt++;
+ blink_cnt += 4;
+ break;
+
+ case LED_ORANGE:
+ led_set(orange_cnt % 2 ? LED_C_GREEN : LED_C_RED);
+ orange_cnt++;
+ blink_cnt+=4;
+ break;
+
+ case LED_GREEN:
+ led_set(LED_C_GREEN);
+ break;
+
+ case LED_RED:
+ led_set(LED_C_RED);
+ break;
+
+ case LED_OFF:
+ default:
+ led_set(LED_C_OFF);
+ break;
+ }
+
+ if (blink_cnt >= T_SEQ - 1) {
+ blink_cnt = 0;
+ if (counting) {
+ if (seq_cnt < seq_max) {
+ seq_cnt++;
+ } else {
+ state = state_next;
+ seq_cnt = 0;
+ counting = false;
+ }
+ } else {
+ state = state_next;
+ }
+ }
+
+ return IRQ_HANDLED;
+}
diff --git a/firmware/e300/battery/led.h b/firmware/e300/battery/led.h
new file mode 100644
index 000000000..0952dc3d2
--- /dev/null
+++ b/firmware/e300/battery/led.h
@@ -0,0 +1,27 @@
+#ifndef LED_H
+#define LED_H
+
+#include <stdint.h>
+
+#include "interrupt.h"
+
+enum led_state {
+ LED_BLINK_GREEN_SLOW,
+ LED_BLINK_GREEN_FAST,
+ LED_BLINK_RED_FAST,
+ LED_BLINK_ORANGE,
+ LED_ORANGE,
+ LED_GREEN,
+ LED_RED,
+ LED_OFF
+};
+
+void led_set_blink_seq(uint8_t n_blinks, enum led_state state);
+
+void led_set_blink(enum led_state state);
+
+void led_set_solid(enum led_state state);
+
+extern irqreturn_t led_wdt_handler(void);
+
+#endif /* LED_H */
diff --git a/firmware/e300/battery/ltc294x.c b/firmware/e300/battery/ltc294x.c
new file mode 100644
index 000000000..d62a5e57d
--- /dev/null
+++ b/firmware/e300/battery/ltc294x.c
@@ -0,0 +1,281 @@
+/* USRP E310 Firmware Linear Technology LTC294X 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 <stdbool.h>
+#include <string.h>
+#include <util/atomic.h>
+#include <avr/interrupt.h>
+
+#include "i2c_twi.h"
+#include "io.h"
+#include "ltc294x.h"
+#include "utils.h"
+#include "pmu.h"
+
+static const uint8_t LTC294X_I2C_ADDR = 0x64;
+
+#define ltc294x_read(reg, val) \
+ (i2c_twi_read(LTC294X_I2C_ADDR, reg, val))
+
+#define ltc294x_read16(reg, val) \
+ (i2c_twi_read16(LTC294X_I2C_ADDR, reg, val))
+
+#define ltc294x_write(reg, val) \
+ (i2c_twi_write(LTC294X_I2C_ADDR, reg, val))
+
+#define ltc294x_write16(reg, val) \
+ (i2c_twi_write16(LTC294X_I2C_ADDR, reg, val))
+
+static const uint8_t LTC294X_REG_STATUS = 0x00;
+static const uint8_t LTC294X_REG_CONTROL = 0x01;
+static const uint8_t LTC294X_REG_CHARGE_MSB = 0x2;
+static const uint8_t LTC294X_REG_CHARGE_LSB = 0x3;
+static const uint8_t LTC294X_REG_HIGH_TRESH_MSB = 0x4;
+static const uint8_t LTC294X_REG_HIGH_THRESH_LSB = 0x5;
+static const uint8_t LTC294X_REG_LOW_THRESH_MSB = 0x6;
+static const uint8_t LTC294X_REG_LOW_THRESH_LSB = 0x7;
+static const uint8_t LTC294X_REG_VOLTAGE_MSB = 0x8;
+static const uint8_t LTC294X_REG_VOLTAGE_LSB = 0x9;
+static const uint8_t LTC294X_REG_VOLTAGE_THRESH_HI = 0xa;
+static const uint8_t LTC294X_REG_VOLTAGE_THRESH_LO = 0xb;
+static const uint8_t LTC294X_REG_TEMP_MSB = 0xc;
+static const uint8_t LTC294X_REG_TEMP_LSB = 0xd;
+static const uint8_t LTC294X_REG_TEMP_THRESH_HI = 0xe;
+static const uint8_t LTC294X_REG_TEMP_THRESH_LO = 0xf;
+
+/* status register */
+static const uint8_t LTC294X_CHIP_ID_MASK = BIT(7);
+static const uint8_t LTC294X_CHIP_ID_SHIFT = 7;
+static const uint8_t LTC294X_ACR_OVF_MASK = BIT(5);
+static const uint8_t LTC294X_ACR_OVF_SHIFT = 5;
+static const uint8_t LTC294X_TEMP_ALERT_MASK = BIT(4);
+static const uint8_t LTC294X_TEMP_ALERT_SHIFT = 4;
+static const uint8_t LTC294X_CH_ALERT_HIGH_MASK = BIT(3);
+static const uint8_t LTC294X_CH_ALERT_HIGH_SHIFT = 3;
+static const uint8_t LTC294X_CH_ALERT_LOW_MASK = BIT(2);
+static const uint8_t LTC294X_CH_ALERT_LOW_SHIFT = 2;
+static const uint8_t LTC294X_VOLT_ALERT_MASK = BIT(1);
+static const uint8_t LTC294X_CH_VOLT_ALERT_SHIFT = 1;
+static const uint8_t LTC294X_CH_UVOLT_MASK = BIT(0);
+static const uint8_t LTC294X_CH_UVOLT_SHIFT = 0;
+
+/* control register */
+static const uint8_t LTC294X_ADC_MODE_MASK = BIT(7) | BIT(6);
+static const uint8_t LTC294X_ADC_MODE_SHIFT = 6;
+static const uint8_t LTC294X_PRESCALER_MASK = BIT(5) | BIT(4) | BIT(3);
+static const uint8_t LTC294X_PRESCALER_SHIFT = 3;
+static const uint8_t LTC294X_ALCC_CFG_MASK = BIT(2) | BIT(1);
+static const uint8_t LTC294X_ALCC_CFG_SHIFT = 1;
+static const uint8_t LTC294X_SHUTDOWN_MASK = BIT(0);
+static const uint8_t LTC294X_SHUTDOWN_SHIFT = 0;
+
+/* acrh register */
+
+/* acrl register */
+
+struct ltc294x_gauge {
+ pmu_gauge_t pmu_gauge;
+ volatile uint8_t status;
+ uint16_t temp_thresh_high;
+ uint16_t temp_thresh_low;
+ uint16_t charge_lo_thesh;
+ bool first_time;
+};
+
+static struct ltc294x_gauge gauge;
+
+static io_pin_t FG_ALn_CC = IO_PA(0);
+
+static uint16_t ltc294x_get_charge(void)
+{
+ uint16_t val;
+
+ (void) ltc294x_read16(LTC294X_REG_CHARGE_MSB, &val);
+
+ return val;
+}
+
+static void ltc294x_set_charge(uint16_t val)
+{
+ uint8_t ctrl_val;
+
+ /* datasheet says to shutdown the analog part
+ * when writing the ACR */
+ (void) ltc294x_read(LTC294X_REG_CONTROL, &ctrl_val);
+
+ ctrl_val |= LTC294X_SHUTDOWN_MASK;
+
+ (void) ltc294x_write(LTC294X_REG_CONTROL, ctrl_val);
+
+ /* write the value ... */
+ (void) ltc294x_write16(LTC294X_REG_CHARGE_MSB, val);
+
+ ctrl_val &= ~LTC294X_SHUTDOWN_MASK;
+
+ /* turn it on again ...*/
+ (void) ltc294x_write(LTC294X_REG_CONTROL, ctrl_val);
+}
+
+static uint16_t ltc294x_get_temp(void)
+{
+ uint16_t val;
+
+ (void) ltc294x_read16(LTC294X_REG_TEMP_MSB, &val);
+
+ return val;
+}
+
+static uint16_t ltc294x_get_voltage(void)
+{
+ uint16_t val;
+
+ (void) ltc294x_read16(LTC294X_REG_VOLTAGE_MSB, &val);
+
+ return val;
+}
+
+static int8_t ltc294x_set_charge_thresh(bool high, uint16_t val)
+{
+ int8_t ret;
+
+ ret = ltc294x_write16(
+ high ? LTC294X_REG_HIGH_TRESH_MSB : LTC294X_REG_LOW_THRESH_MSB, val);
+ if (ret)
+ return ret;
+ return 0;
+}
+
+static inline void ltc294x_pmu_set_charge_hi_thesh(uint16_t val)
+{
+ ltc294x_set_charge_thresh(true, val);
+}
+
+static inline void ltc294x_pmu_set_charge_lo_thesh(uint16_t val)
+{
+ ltc294x_set_charge_thresh(false, val);
+}
+
+uint8_t ltc294x_check_events(void)
+{
+ uint8_t status;
+ uint8_t flags = 0;
+ uint16_t value;
+
+ ltc294x_read(LTC294X_REG_STATUS, &status);
+
+
+ if (status && (status != gauge.status)) {
+
+ if (status & LTC294X_CH_ALERT_HIGH_MASK)
+ flags |= PMU_GAUGE_CHARGE_HI;
+ else if (status & LTC294X_CH_ALERT_LOW_MASK)
+ flags |= PMU_GAUGE_CHARGE_LO;
+
+ if (status & LTC294X_VOLT_ALERT_MASK) {
+ /*flags |= PMU_GAUGE_VOLT_HI;
+ flags |= PMU_GAUGE_VOLT_LO; */
+ /* TODO: Figure out which one */
+ }
+
+ if (status & LTC294X_ACR_OVF_MASK) {
+ value = ltc294x_get_charge();
+ if (value <= gauge.charge_lo_thesh)
+ flags |= PMU_GAUGE_CHARGE_LO;
+ }
+
+ if (status & LTC294X_TEMP_ALERT_MASK) {
+ value = ltc294x_get_temp();
+ if (value > gauge.temp_thresh_high)
+ flags |= PMU_GAUGE_TEMP_HI;
+ else if (value <= gauge.temp_thresh_low)
+ flags |= PMU_GAUGE_TEMP_LO;
+ }
+ gauge.status = status;
+ }
+
+ return flags;
+}
+
+static const pmu_gauge_ops_t ltc294x_pmu_gauge_ops = {
+ .check_events = ltc294x_check_events,
+ .get_temperature = ltc294x_get_temp,
+ .get_charge = ltc294x_get_charge,
+ .set_charge = ltc294x_set_charge,
+ .set_low_threshold = ltc294x_pmu_set_charge_lo_thesh,
+ .get_voltage = ltc294x_get_voltage,
+};
+
+int8_t ltc294x_init(ltc294x_model_t model)
+{
+ uint8_t val;
+ int8_t ret;
+
+ /* make input, set pullup */
+ io_input_pin(FG_ALn_CC);
+ io_set_pin(FG_ALn_CC);
+
+ gauge.pmu_gauge.ops = &ltc294x_pmu_gauge_ops;
+
+ ret = ltc294x_read(LTC294X_REG_STATUS, &val);
+ if (ret)
+ goto fail_i2c_read;
+ val &= LTC294X_CHIP_ID_MASK;
+ val >>= LTC294X_CHIP_ID_SHIFT;
+
+ if (val != model)
+ goto fail_id;
+
+ /* set ACR to 0, this allows for calibrating by
+ * completely discharging the battery once */
+ ltc294x_set_charge(0x0010);
+
+ /* set low threshold to 10% assuming full is 0xeae4... */
+ ltc294x_write16(LTC294X_REG_LOW_THRESH_MSB, 0x1794);
+ gauge.charge_lo_thesh = 0x1794;
+
+ /* set high threshold for temperature to 85 C */
+ ltc294x_write16(LTC294X_REG_TEMP_THRESH_HI, 0x98);
+ gauge.temp_thresh_high = 0x9800;
+
+ /* set low threshold for temperature to -2 C */
+ ltc294x_write16(LTC294X_REG_TEMP_THRESH_LO, 0x74);
+ gauge.temp_thresh_low = 0x7400;
+
+ ret = ltc294x_read(LTC294X_REG_CONTROL, &val);
+ if (ret)
+ goto fail_i2c_read;
+
+ /* enable automatic temp conversion */
+ val &= ~LTC294X_ADC_MODE_MASK;
+ val |= 0x3 << LTC294X_ADC_MODE_SHIFT;
+
+ /* set prescaler to 16 */
+ val &= ~LTC294X_PRESCALER_MASK;
+ val |= 0x4 << LTC294X_PRESCALER_SHIFT;
+
+ /* disable IRQ mode */
+ val &= ~LTC294X_ALCC_CFG_MASK;
+ val |= 0x0 << LTC294X_ALCC_CFG_SHIFT;
+
+ ret = ltc294x_write(LTC294X_REG_CONTROL, val);
+
+ pmu_register_gauge(&gauge.pmu_gauge);
+
+ return 0;
+
+fail_id:
+fail_i2c_read:
+ return ret;
+}
diff --git a/firmware/e300/battery/ltc294x.h b/firmware/e300/battery/ltc294x.h
new file mode 100644
index 000000000..be35c7ceb
--- /dev/null
+++ b/firmware/e300/battery/ltc294x.h
@@ -0,0 +1,40 @@
+/* USRP E310 Firmware Linear Technology LTC294X 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/>.
+ */
+
+/**
+ * \file ltc294x.h
+ * \brief Linear Technology LTC294X driver
+ */
+
+#ifndef LTC294X_H
+#define LTC294X_H
+
+#include "pmu.h"
+
+typedef enum {
+ LTC294X_MODEL_2941 = 0x1,
+ LTC294X_MODEL_2942 = 0x0
+} ltc294x_model_t;
+
+/**
+ * \brief Initializes the LTC294X chip
+ * \param model What model we're looking we should probe for
+ * \return 0 on success, negative error code otherwise
+ */
+int8_t ltc294x_init(ltc294x_model_t model);
+
+extern irqreturn_t ltc294x_irq_handler(void);
+
+#endif /* LTC294X_H */
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 = &ltc3675_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;
+}
diff --git a/firmware/e300/battery/ltc3675.h b/firmware/e300/battery/ltc3675.h
new file mode 100644
index 000000000..f06fc4fc2
--- /dev/null
+++ b/firmware/e300/battery/ltc3675.h
@@ -0,0 +1,67 @@
+/* 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/>.
+ */
+
+/**
+ * \file ltc3675.h
+ * \brief Linear Technology LTC3675 driver
+ */
+
+#ifndef LTC3675_H
+#define LTC3675_H
+
+#include "pmu.h"
+
+typedef enum ltc3675_regulator {
+ /** 1A Buck */
+ LTC3675_REG_1,
+ /** 1A Buck */
+ LTC3675_REG_2,
+ /** 500mA Buck */
+ LTC3675_REG_3,
+ /** 500mA Buck */
+ LTC3675_REG_4,
+ /** 1A Boost */
+ LTC3675_REG_5,
+ /** 1A Buck-Boost */
+ LTC3675_REG_6,
+} ltc3675_regulator_t;
+
+typedef struct ltc3675_pmu_regulator {
+ pmu_regulator_t pmu_reg;
+ ltc3675_regulator_t ltc3675_reg;
+} ltc3675_pmu_regulator_t;
+
+extern const pmu_regulator_ops_t ltc3675_ops;
+
+/**
+ * \brief Initializes the LTC3675 chip
+ *
+ * This function will setup the internal pull-up resistors for the open drain
+ * input pins, clear old interrupts, unmask all warnings and set the warning
+ * level to 3.4V
+ */
+int8_t ltc3675_init(void);
+
+/**
+ * \brief Event handler that gets called periodically
+ * \return returns 0 on success, negative error code in case of fault
+ */
+int8_t ltc3675_handle_events(void);
+
+extern irqreturn_t ltc3675_button_wakeup_irq_handler(void);
+extern irqreturn_t ltc3675_button_change_irq_handler(void);
+extern irqreturn_t ltc3675_button_timer_irq_handler(void);
+
+#endif /* LTC3675_H */
diff --git a/firmware/e300/battery/main.c b/firmware/e300/battery/main.c
new file mode 100644
index 000000000..eec9e8293
--- /dev/null
+++ b/firmware/e300/battery/main.c
@@ -0,0 +1,65 @@
+/* USRP E310 Firmware
+ * 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 <avr/io.h>
+#include <errno.h>
+#include <avr/interrupt.h>
+#include <avr/sleep.h>
+
+#include "i2c_twi.h"
+#include "spi.h"
+#include "pmu.h"
+#include "interrupt.h"
+#include "io.h"
+#include "fpga.h"
+#include "eeprom.h"
+#include "utils.h"
+
+/* setup the fuses for compilation with avr-gcc
+ * - use the internal 8 MHz oscillator
+ * - slowly rising power (startup time)
+ * - save the eeprom between flashes, leave SPI enabled for flashing
+ */
+FUSES = {
+ .low = (FUSE_CKSEL0 & FUSE_SUT0),
+ .high = (FUSE_EESAVE & FUSE_SPIEN),
+};
+
+
+int main(void)
+{
+ /* if a reset was caused by watchdog, clear flag, enable wd change bit, disable */
+ if (MCUSR & BIT(WDRF)) {
+ MCUSR &= ~BIT(WDRF);
+ WDTCSR |= BIT(WDCE) | BIT(WDE);
+ WDTCSR = 0x00;
+ }
+
+ i2c_twi_init(I2C_SPEED_100K);
+ spi_init(SPI_TYPE_MASTER, SPI_MSB_FIRST, SPI_MODE_0, SPI_SPEED_2M);
+
+ pmu_init();
+
+ if (eeprom_get_autoboot())
+ pmu_power_on();
+
+ sei();
+
+ while (1) {
+ pmu_handle_events();
+ }
+
+ return 0;
+}
diff --git a/firmware/e300/battery/mcu_settings.h b/firmware/e300/battery/mcu_settings.h
new file mode 100644
index 000000000..175cb8f44
--- /dev/null
+++ b/firmware/e300/battery/mcu_settings.h
@@ -0,0 +1,28 @@
+/* USRP E310 Firmware MCU specific settings
+ * 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/>.
+ */
+
+/**
+ * \file mcu_settings.h
+ * \brief MCU specific settings
+ */
+#ifndef MCU_SETTINGS_H
+#define MCU_SETTINGS_H
+
+#define F_CPU 8000000UL
+
+#define VERSION_MAJ 2
+#define VERSION_MIN 0
+
+#endif /* MCU_SETTINGS_H */
diff --git a/firmware/e300/battery/pmu.c b/firmware/e300/battery/pmu.c
new file mode 100644
index 000000000..bd337783f
--- /dev/null
+++ b/firmware/e300/battery/pmu.c
@@ -0,0 +1,686 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <stdlib.h>
+
+#include <avr/interrupt.h>
+#include <avr/wdt.h>
+#include <util/delay.h>
+#include <util/atomic.h>
+
+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 = &ltc3675_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 = &ltc3675_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 = &ltc3675_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 = &ltc3675_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)));
+ }
+ }
+}
diff --git a/firmware/e300/battery/pmu.h b/firmware/e300/battery/pmu.h
new file mode 100644
index 000000000..5efaab8b1
--- /dev/null
+++ b/firmware/e300/battery/pmu.h
@@ -0,0 +1,177 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file pmu.h
+ * \brief Power Management Unit (PMU) functionality
+ */
+#ifndef PMU_H
+#define PMU_H
+
+#include <stdbool.h>
+#include "interrupt.h"
+#include "utils.h"
+
+/**
+ * \brief Initialize the Power Management Unit
+ * \return 0 on success, negative error code on error
+ */
+int8_t pmu_init(void);
+
+typedef struct pmu_regulator pmu_regulator_t;
+
+typedef struct pmu_regulator_ops {
+ int8_t (*set_voltage)(pmu_regulator_t *, uint16_t);
+ int8_t (*set_regulator)(pmu_regulator_t *, bool);
+ int8_t (*check_events)(pmu_regulator_t *);
+} pmu_regulator_ops_t;
+
+struct pmu_regulator {
+ const pmu_regulator_ops_t *ops;
+ const uint16_t voltage;
+ bool powered;
+ uint8_t error_code;
+};
+
+/**
+ * \brief Event loop that handles all the various events
+ */
+void pmu_handle_events(void);
+
+extern irqreturn_t pmu_fpga_irq_handler(void);
+extern irqreturn_t pmu_led_timer_comp_a_irq_handler(void);
+extern irqreturn_t pmu_led_timer_comp_b_irq_handler(void);
+extern irqreturn_t pmu_wdt_handler(void);
+
+/**
+ * \brief Turn on the regulators and powerup ARM and FPGA
+ */
+void pmu_power_on(void);
+
+enum pmu_health {
+ PMU_HEALTH_GOOD,
+ PMU_HEALTH_UNSPEC_FAIL,
+ PMU_HEALTH_OVERVOLTAGE,
+ PMU_HEALTH_OVERHEAT,
+ PMU_HEALTH_COLD,
+ PMU_HEALTH_SAFETY_TIMER_EXPIRE,
+ PMU_HEALTH_UNKNOWN
+};
+
+enum pmu_charge_type {
+ PMU_CHARGE_TYPE_NONE,
+ PMU_CHARGE_TYPE_TRICKLE,
+ PMU_CHARGE_TYPE_FAST,
+};
+
+enum pmu_status {
+ PMU_STATUS_NOT_CHARGING,
+ PMU_STATUS_CHARGING,
+ PMU_STATUS_FULL,
+ PMU_STATUS_DISCHARGING
+};
+
+typedef struct pmu_charger pmu_charger_t;
+
+enum pmu_charger_event_mask {
+ PMU_CHARGER_EVENT_NONE = 0x00,
+ PMU_CHARGER_EVENT_STATUS_CHANGE = BIT(0),
+ PMU_CHARGER_EVENT_FAULT_CHANGE = BIT(1),
+ PMU_CHARGER_EVENT_CHARGE_DONE = BIT(2)
+};
+
+typedef struct pmu_charger_ops {
+ int8_t (*set_charger_voltage)(pmu_charger_t *, uint16_t);
+ int8_t (*set_charger_current)(pmu_charger_t *, uint16_t);
+
+ uint8_t (*get_temp_alert)(pmu_charger_t *);
+ int8_t (*set_temp_alert)(pmu_charger_t *, uint8_t);
+
+ enum pmu_charge_type (*get_charge_type)(pmu_charger_t *);
+ int8_t (*set_charge_type)(pmu_charger_t *, enum pmu_charge_type);
+ enum pmu_health (*get_charger_health)(pmu_charger_t *);
+ bool (*get_charger_online)(pmu_charger_t *);
+
+ enum pmu_health (*get_battery_health)(pmu_charger_t *);
+ enum pmu_status (*get_battery_status)(pmu_charger_t *);
+ bool (*get_battery_online)(pmu_charger_t *);
+
+ uint8_t (*check_events)(pmu_charger_t *);
+} pmu_charger_ops_t;
+
+struct pmu_charger {
+ const pmu_charger_ops_t *ops;
+};
+
+/**
+ * \brief Set the PMU's charger to the given one
+ * \param[in] pmu_charger Pointer to the implementation's pmu_charger
+ */
+void pmu_register_charger(pmu_charger_t *pmu_charger);
+
+typedef struct pmu_button pmu_button_t;
+
+enum pmu_button_event_mask {
+ PMU_BUTTON_EVENT_MASK_PRESS = 0x01,
+ PMU_BUTTON_EVENT_MASK_RELEASE = 0x02,
+ PMU_BUTTON_EVENT_MASK_POWERDOWN = 0x04,
+ PMU_BUTTON_EVENT_MASK_WAKEUP = 0x08,
+};
+
+typedef struct pmu_button_ops {
+ uint8_t (*check_events)(pmu_button_t *);
+} pmu_button_ops_t;
+
+struct pmu_button {
+ const pmu_button_ops_t *ops;
+};
+
+/**
+ * \brief Set the PMU's button to the given one
+ * \param[in] pmu_button Pointer to the implementation's pmu_button
+ */
+void pmu_register_button(pmu_button_t *pmu_button);
+
+typedef struct pmu_gauge pmu_gauge_t;
+
+enum pmu_gauge_event_mask {
+ PMU_GAUGE_EVENT_NONE = 0x00,
+ PMU_GAUGE_CHARGE_HI = 0x01,
+ PMU_GAUGE_CHARGE_LO = 0x02,
+ PMU_GAUGE_TEMP_HI = 0x04,
+ PMU_GAUGE_TEMP_LO = 0x08,
+ PMU_GAUGE_VOLT_LO = 0x10,
+ PMU_GAUGE_VOLT_HI = 0x20,
+};
+
+typedef struct pmu_gauge_ops {
+ uint8_t (*check_events)(void);
+ uint16_t (*get_charge)(void);
+ void (*set_charge)(uint16_t val);
+ void (*set_low_threshold)(uint16_t val);
+ uint16_t (*get_temperature)(void);
+ uint16_t (*get_voltage)(void);
+} pmu_gauge_ops_t;
+
+struct pmu_gauge {
+ const pmu_gauge_ops_t *ops;
+};
+/**
+ * \brief Set the PMU's power gauge to the given one
+ * \param[in] pmu_gauge Pointer to the implementation's pmu_gauge
+ */
+void pmu_register_gauge(pmu_gauge_t *pmu_gauge);
+
+#endif /* PMU_H */
diff --git a/firmware/e300/battery/spi.c b/firmware/e300/battery/spi.c
new file mode 100644
index 000000000..ae42c6e40
--- /dev/null
+++ b/firmware/e300/battery/spi.c
@@ -0,0 +1,86 @@
+/* USRP E310 Firmware Atmel AVR SPI 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 "mcu_settings.h"
+#include "io.h"
+#include "spi.h"
+#include "utils.h"
+
+#include <avr/io.h>
+#include <stdlib.h>
+#include <util/delay.h>
+
+static io_pin_t AVR_CS = IO_PB(2);
+static io_pin_t AVR_MOSI = IO_PB(3);
+static io_pin_t AVR_MISO = IO_PB(4);
+static io_pin_t AVR_SCK = IO_PB(5);
+
+void spi_init(spi_type_t type, spi_order_t order, spi_mode_t mode, spi_speed_t speed)
+{
+ uint8_t val;
+
+ io_output_pin(AVR_CS);
+ io_output_pin(AVR_MOSI);
+ io_input_pin(AVR_MISO);
+ io_output_pin(AVR_SCK);
+
+ /* slave select is low active */
+ io_set_pin(AVR_CS);
+
+ /* SPCR looks like this: [SPIE | SPE | DORD | MSTR | CPOL | CPHA | SPR1 | SPR0] */
+ val = BIT(SPE);
+ val |= (order == SPI_LSB_FIRST) ? BIT(DORD) : 0;
+ val |= (type == SPI_TYPE_MASTER) ? BIT(MSTR) : 0;
+ val |= mode << CPHA;
+ val |= speed << SPR0;
+
+ SPCR = val;
+}
+
+uint8_t spi_transact(uint8_t data)
+{
+ uint8_t ret;
+
+ io_clear_pin(AVR_CS);
+ SPDR = data;
+ ret = SPDR;
+ io_set_pin(AVR_CS);
+ return ret;
+}
+
+void spi_transact_buf(uint8_t *in, uint8_t *out, uint8_t size)
+{
+ uint8_t i;
+
+ io_clear_pin(AVR_CS);
+
+ for (i = 0; i < size; i++) {
+ SPDR = in[i];
+ spi_wait_till_done();
+ out[i] = SPDR;
+ }
+
+ io_set_pin(AVR_CS);
+}
+
+void spi_wait_till_done(void)
+{
+ uint8_t timeout = 100;
+
+ do {
+ _delay_us(10);
+ timeout--;
+ } while (timeout && !(SPSR & BIT(SPIF)));
+}
diff --git a/firmware/e300/battery/spi.h b/firmware/e300/battery/spi.h
new file mode 100644
index 000000000..9a94dbc7c
--- /dev/null
+++ b/firmware/e300/battery/spi.h
@@ -0,0 +1,74 @@
+/* USRP E310 Firmware Atmel AVR SPI 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/>.
+ */
+
+/**
+ * \file spi.h
+ * \brief Atmel AVR SPI driver
+ */
+
+#ifndef SPI_H
+#define SPI_H
+
+#include <stdlib.h>
+
+typedef enum spi_type {
+ SPI_TYPE_MASTER = 0x00,
+ SPI_TYPE_SLAVE = 0x01
+} spi_type_t;
+
+typedef enum spi_mode {
+ SPI_MODE_0 = 0x00, /** CPOL=0, CPHA=0 */
+ SPI_MODE_1 = 0x01, /** CPOL=0, CPHA=1 */
+ SPI_MODE_2 = 0x02, /** CPOL=1, CPHA=0 */
+ SPI_MODE_3 = 0x03, /** CPOL=1, CPHA=1 */
+} spi_mode_t;
+
+/* note that this assumes a clock speed of 8MHz */
+typedef enum spi_speed {
+ SPI_SPEED_2M = 0x00,
+ SPI_SPEED_500K = 0x01,
+ SPI_SPEED_125K = 0x02,
+ SPI_SPEED_62_5K = 0x03,
+} spi_speed_t;
+
+typedef enum spi_order {
+ SPI_MSB_FIRST = 0x00,
+ SPI_LSB_FIRST = 0x01,
+} spi_order_t;
+
+/**
+ * \brief Initialize the AVR's SPI module
+ * \param[in] master Operate as master
+ * \param[in] order Whether to send the LSB first
+ * \param[in] mode Which clock / phase configuration to use
+ * \param[in] speed Which speed to use
+ */
+void spi_init(spi_type_t type, spi_order_t order, spi_mode_t mode, spi_speed_t speed);
+
+/**
+ * \brief Transact one byte to the slave and receive one byte
+ * \param[in] mode Whether we're using the SPI in MSB or LSB first mode
+ * \param[in] speed Which speed to use, note that these speeds are based on a prescaler,
+ and the values are correct for a FCLK of 8 MHz
+*/
+uint8_t spi_transact(uint8_t data);
+
+void spi_transact_buf(uint8_t *in, uint8_t *out, uint8_t size);
+/**
+ * \brief Wait till transaction is done
+ */
+void spi_wait_till_done(void);
+
+#endif /* SPI_H */
diff --git a/firmware/e300/battery/timer.c b/firmware/e300/battery/timer.c
new file mode 100644
index 000000000..1e87e30bf
--- /dev/null
+++ b/firmware/e300/battery/timer.c
@@ -0,0 +1,82 @@
+/* USRP E310 Firmware Timer 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 <avr/io.h>
+
+#include "timer.h"
+#include "utils.h"
+
+
+static const uint8_t TIMER0_OFF_MASK = 0xf8;
+static const uint8_t TIMER1_OFF_MASK = 0xf8;
+
+void timer0_init(void)
+{
+ /* ctc mode */
+ TCCR0A = BIT(CTC0);
+
+ /* 250 ms with 1024 prescale @ 1 MHz */
+ OCR0A = 244;//244;
+
+ /* ctc mode */
+ TIMSK0 = BIT(OCIE0A);
+}
+
+void timer0_start(void)
+{
+ TCNT0 = 0x00;
+ /* set prescaler to 1024 */
+ TCCR0A |= BIT(CS02) | BIT(CS00);
+}
+
+void timer0_stop(void)
+{
+ /* mask out all the bits to stop */
+ TCCR0A &= TIMER0_OFF_MASK;
+}
+
+void timer1_init(void)
+{
+ /* set counter register to 0 */
+ TCNT1 = 0x0;
+
+ /* ctc mode */
+ TCCR1B = BIT(WGM12);
+
+ /* hold button for roughly 2 seconds
+ * value is calculated as follows:
+ * val = 2 * f_clk / (prescaler * f_irq) - 1
+ */
+ OCR1A = 7811 * 2;
+
+ /* enable CTC on timer 1 */
+ TIMSK1 = BIT(OCIE1A);
+}
+
+void timer1_start(void)
+{
+ /* reset counter register */
+ TCNT1 = 0x0000;
+
+ /* set prescaler to 1024 */
+ TCCR1B |= BIT(CS12) | BIT(CS10);
+}
+
+void timer1_stop(void)
+{
+ /* mask out all the bits to stop */
+ TCCR1B &= TIMER1_OFF_MASK;
+}
+
diff --git a/firmware/e300/battery/timer.h b/firmware/e300/battery/timer.h
new file mode 100644
index 000000000..58dc1842d
--- /dev/null
+++ b/firmware/e300/battery/timer.h
@@ -0,0 +1,53 @@
+/* USRP E310 Firmware Timer 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/>.
+ */
+
+/**
+ * \file timer.h
+ * \brief Timer driver
+ */
+#ifndef TIMER_H
+#define TIMER_H
+
+/**
+ * \brief Initialize Timer 0
+ */
+void timer0_init(void);
+
+/**
+ * \brief Start Timer 0
+ */
+void timer0_start(void);
+
+/**
+ * \brief Stop Timer 0
+ */
+void timer0_stop(void);
+
+/**
+ * \brief Initialize Timer 1
+ */
+void timer1_init(void);
+
+/**
+ * \brief Start Timer 1
+ */
+void timer1_start(void);
+
+/**
+ * \brief Stop Timer 1
+ */
+void timer1_stop(void);
+
+#endif /* TIMER_H */
diff --git a/firmware/e300/battery/tps54478.c b/firmware/e300/battery/tps54478.c
new file mode 100644
index 000000000..3e8ab4f53
--- /dev/null
+++ b/firmware/e300/battery/tps54478.c
@@ -0,0 +1,110 @@
+/* USRP E310 TP54478 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 "mcu_settings.h"
+#include "io.h"
+#include "tps54478.h"
+
+#include <stdlib.h>
+
+#include <util/delay.h>
+#include <util/atomic.h>
+
+static io_pin_t CORE_PWR_EN = IO_PA(3);
+static io_pin_t CORE_PGOOD = IO_PB(0);
+
+/* per spec we should wait 3 ms here,
+ * but 10 is better to give external PSU some time
+ * to settle down*/
+static const uint8_t TPS54478_START_DELAY = 10;
+
+/* we'll use this to check for events in the event handler */
+static volatile bool tps54478_event = false;
+
+bool tps54478_get_power_good(void)
+{
+ return io_test_pin(CORE_PGOOD);
+}
+
+static int8_t tps54478_set_regulator(pmu_regulator_t *pmu_reg, bool on)
+{
+ (void) pmu_reg;
+
+ if (on) {
+ io_input_pin(CORE_PWR_EN);
+ _delay_ms(TPS54478_START_DELAY);
+ } else {
+ io_output_pin(CORE_PWR_EN);
+ /* no delay here as we can't detect this state anyway */
+ return 0;
+ }
+ /* return zero on success ... */
+ return !(on == tps54478_get_power_good());
+}
+
+void tps54478_init(bool enable)
+{
+ /* enable pull-up for open drain */
+ io_input_pin(CORE_PGOOD);
+ io_set_pin(CORE_PGOOD);
+
+ tps54478_set_regulator(NULL, enable);
+
+ io_clear_pin(CORE_PWR_EN);
+}
+
+int8_t tps54478_check_events(pmu_regulator_t *reg)
+{
+ bool power_good;
+ bool event;
+
+ event = false;
+ ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+ if (tps54478_event) {
+ tps54478_event = false;
+ event = true;
+ }
+ }
+
+ if (event) {
+ power_good = tps54478_get_power_good();
+ if (!power_good)
+ return -1;
+ }
+ return 0;
+}
+
+const pmu_regulator_ops_t tps54478_ops = {
+ .set_voltage = NULL,
+ .set_regulator = tps54478_set_regulator,
+ .check_events = tps54478_check_events,
+};
+
+irqreturn_t tps54478_irq_handler(void)
+{
+ bool power_good;
+ power_good = tps54478_get_power_good();
+
+ /* check if the device indicates power is good,
+ * if it is probably we're not the source of the IRQ,
+ * if it is *not* set the event flag to deal with it later */
+ if (power_good) {
+ return IRQ_NONE;
+ } else {
+ tps54478_event = true;
+ return IRQ_HANDLED;
+ }
+ return IRQ_HANDLED;
+}
diff --git a/firmware/e300/battery/tps54478.h b/firmware/e300/battery/tps54478.h
new file mode 100644
index 000000000..c8cad1eff
--- /dev/null
+++ b/firmware/e300/battery/tps54478.h
@@ -0,0 +1,46 @@
+/* USRP E310 Firmware Texas Instruments TPS54478 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/>.
+ */
+
+/**
+ * \file tps54478.h
+ * \brief Texas Instruments TPS54478 driver
+ */
+
+#ifndef TPS54478_H
+#define TPS54478_H
+
+#include <stdbool.h>
+#include "pmu.h"
+#include "interrupt.h"
+
+/**
+ * \brief Initializes the TPS54478 controlling the core power supply
+ * \param[in] enabled Controls whether the core power gets turned on
+ */
+void tps54478_init(bool enabled);
+
+/**
+ * \brief The IRQ handler for the CHG_IRQ external pin change interrupt
+ * \return IRQ_HANDLED in case IRQ was handled, IRQ_NONE in case shared interrupt is not for us
+ */
+extern irqreturn_t tps54478_irq_handler(void);
+
+typedef struct tps54478_pmu_regulator {
+ pmu_regulator_t pmu_reg;
+} tps54478_pmu_regulator_t;
+
+extern const pmu_regulator_ops_t tps54478_ops;
+
+#endif /* TPS54478_H */
diff --git a/firmware/e300/battery/utils.h b/firmware/e300/battery/utils.h
new file mode 100644
index 000000000..65cc2c5d9
--- /dev/null
+++ b/firmware/e300/battery/utils.h
@@ -0,0 +1,53 @@
+/* USRP E310 Firmware Misc utility macros
+ * 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/>.
+ */
+
+/**
+ * \file utils.h
+ * \brief Misc utility macros
+ */
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <stddef.h>
+
+/**
+ * \brief Returns the size of a (static) array
+ *
+ * \param x Array
+ * \warning Do NOT use on pointers
+ */
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+
+
+/**
+ * \brief A ISO compliant version of the linux kernel's container_of macro.
+ * This allows implementing multiple interfaces in a struct.
+ */
+#ifdef __GNUC__
+#define member_type(type, member) __typeof__ (((type *)0)->member)
+#else
+#define member_type(type, member) const void
+#endif
+
+#define container_of(ptr, type, member) ((type *)( \
+ (char *)(member_type(type, member) *){ ptr } - offsetof(type, member)))
+
+/**
+ * \brief Convenience macro to make bitmasks more readable
+ */
+#define BIT(bit) (1 << (bit))
+
+#endif /* UTILS_H */