diff options
Diffstat (limited to 'firmware/e300/battery')
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 = <c294x_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 = <c3675_pmu_button_ops; + button.onswitch_last_state = io_test_pin(ONSWITCH_DB); + + /* setup the input pins with pull-up for open drain */ + io_input_pin(PWR_IRQ); + io_set_pin(PWR_IRQ); + io_input_pin(WAKEUP); + io_set_pin(WAKEUP); + io_input_pin(ONSWITCH_DB); + io_set_pin(ONSWITCH_DB); + io_input_pin(PWR_RESET); + io_set_pin(PWR_RESET); + + /* clear the old interrupts */ + ltc3675_clear_interrupts(); + + /* setup interrupt masks on chip to be notified of any faults */ + ret = ltc3675_write(LTC3675_REG_IRQB_MASK, 0xff); + if (ret) + goto fail_i2c_write_mask; + + /* program warning @ 3.4V */ + ret = ltc3675_write(LTC3675_REG_UVOT, 0x70); + if (ret) + goto fail_i2c_write_uvot; + + pmu_register_button(&button.pmu_button); + + return 0; + +fail_i2c_write_uvot: +fail_i2c_write_mask: + return ret; +} + +int8_t ltc3675_check_reg_events(pmu_regulator_t *reg) +{ + return 0; +} + +const pmu_regulator_ops_t ltc3675_ops = { + .set_voltage = ltc3675_set_voltage, + .set_regulator = ltc3675_set_regulator, + .check_events = ltc3675_check_reg_events +}; + +/* PD(3) ONSWITCH_DB (PB_STAT), any change */ +irqreturn_t ltc3675_button_change_irq_handler(void) +{ + bool pin_state; + + pin_state = io_test_pin(ONSWITCH_DB); + + /* the pushbutton is active low, therefore backwards logic */ + if (pin_state && !button.onswitch_last_state) { + button.onswitch_release_event = true; + timer1_stop(); + } else if (!pin_state && button.onswitch_last_state) { + button.onswitch_press_event = true; + timer1_start(); + } + button.onswitch_last_state = pin_state; + + return IRQ_HANDLED; +} + +irqreturn_t ltc3675_button_wakeup_irq_handler(void) +{ + button.wakeup_event = true; + + return IRQ_HANDLED; +} + +irqreturn_t ltc3675_button_timer_irq_handler(void) +{ + /* if we got here, the timer overflowed, + * meaning the user pressed the button long enough */ + button.poweroff_event = true; + + return IRQ_HANDLED; +} 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 = <c3675_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 = <c3675_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 = <c3675_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 = <c3675_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 */ |