diff options
84 files changed, 15959 insertions, 9 deletions
diff --git a/firmware/e300/rev_b/Makefile b/firmware/e300/rev_b/Makefile new file mode 100644 index 000000000..0f13dc60b --- /dev/null +++ b/firmware/e300/rev_b/Makefile @@ -0,0 +1,76 @@ +# +# Copyright 2009 Ettus Research LLC +# + +################################################## +# Compiler +################################################## +CC = avr-gcc +OBJCOPY = avr-objcopy +STRIP = avr-strip +OBJDUMP = avr-objdump +SREC = srec_cat +CFLAGS = -Os -std=gnu99 -Wall -fshort-enums -pedantic-errors -Wl,--gc-sections \ +	-Wstrict-prototypes -Wmissing-prototypes -Wcast-align -Wshadow \ +	-DENABLE_SERIAL -DCHARGER_TI -DLED_POLARITY -DDEBUG_VOID +# -DENABLE_SERIAL	: Output serial debug +# -DCHARGER_TI		: Use TI charger (rev B) instead of LTC (rev A) +# -DLED_POLARITY	: Dual-polarity LED on rev B +# -DDDR3L		: Lower DDR voltage (rev B R-divider changed, so disable this to get back into nominal range) +# -DDEBUG_VOID		: Use (void) debug function replacements instead of inline NOOP (use if .text overflows) +# -DI2C_REWORK		: Rev A only +# -DDEBUG		: Enable debug routines (LED blinks, etc) +# -DDEBUG_SAFETY	: Extra debug prints +# -DATTINY88_DIP	: ATTINY88 DIP testing on STK600 +# -DHARDWIRE_ENABLE : LTC3675 dedicated enable lines - don't use + +#-Werror +#-D IO_DEBUG + +################################################## +# Files +################################################## +HDRS = +SRCS =  main.c io.c power.c ltc3675.c i2c.c debug.c bq24190.c +TARGET = main + +################################################## +# Device +################################################## +MMCU = attiny88 +#PROGRAMMER = avrisp2 +PROGRAMMER = stk600 +PORT = usb +AVRDUDE = avrdude -p $(MMCU) -c $(PROGRAMMER) -P $(PORT) + +################################################## +# Global Targets +################################################## +all: $(TARGET).hex + +clean: +	$(RM) *.o *.elf *.hex + +install: all +	$(AVRDUDE) -U flash:w:$(TARGET).hex:i + +################################################## +# 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/rev_b/PMC.atsln b/firmware/e300/rev_b/PMC.atsln new file mode 100644 index 000000000..e9f4ffc79 --- /dev/null +++ b/firmware/e300/rev_b/PMC.atsln @@ -0,0 +1,29 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Atmel Studio Solution File, Format Version 11.00 +Project("{54F91283-7BC4-4236-8FF9-10F437C3AD48}") = "PMC", "PMC.cproj", "{A379D421-4236-44AF-A711-52B7BDA29919}" +EndProject +Global +	GlobalSection(SolutionConfigurationPlatforms) = preSolution +		Debug|AVR = Debug|AVR +		Release (DDR3L)|AVR = Release (DDR3L)|AVR +		Release (DDR3L, Ti)|AVR = Release (DDR3L, Ti)|AVR +		Release (Dev)|AVR = Release (Dev)|AVR +		Release|AVR = Release|AVR +	EndGlobalSection +	GlobalSection(ProjectConfigurationPlatforms) = postSolution +		{A379D421-4236-44AF-A711-52B7BDA29919}.Debug|AVR.ActiveCfg = Debug|AVR +		{A379D421-4236-44AF-A711-52B7BDA29919}.Debug|AVR.Build.0 = Debug|AVR +		{A379D421-4236-44AF-A711-52B7BDA29919}.Release (DDR3L)|AVR.ActiveCfg = Release (DDR3L)|AVR +		{A379D421-4236-44AF-A711-52B7BDA29919}.Release (DDR3L)|AVR.Build.0 = Release (DDR3L)|AVR +		{A379D421-4236-44AF-A711-52B7BDA29919}.Release (DDR3L, Ti)|AVR.ActiveCfg = Release (DDR3L, Ti)|AVR +		{A379D421-4236-44AF-A711-52B7BDA29919}.Release (DDR3L, Ti)|AVR.Build.0 = Release (DDR3L, Ti)|AVR +		{A379D421-4236-44AF-A711-52B7BDA29919}.Release (Dev)|AVR.ActiveCfg = Release (Dev)|AVR +		{A379D421-4236-44AF-A711-52B7BDA29919}.Release (Dev)|AVR.Build.0 = Release (Dev)|AVR +		{A379D421-4236-44AF-A711-52B7BDA29919}.Release|AVR.ActiveCfg = Release|AVR +		{A379D421-4236-44AF-A711-52B7BDA29919}.Release|AVR.Build.0 = Release|AVR +	EndGlobalSection +	GlobalSection(SolutionProperties) = preSolution +		HideSolutionNode = FALSE +	EndGlobalSection +EndGlobal diff --git a/firmware/e300/rev_b/PMC.cproj b/firmware/e300/rev_b/PMC.cproj new file mode 100644 index 000000000..e3784d10b --- /dev/null +++ b/firmware/e300/rev_b/PMC.cproj @@ -0,0 +1,288 @@ +<?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.0</ProjectVersion> +    <ToolchainName>com.Atmel.AVRGCC8</ToolchainName> +    <ProjectGuid>{a379d421-4236-44af-a711-52b7bda29919}</ProjectGuid> +    <avrdevice>ATtiny88</avrdevice> +    <avrdeviceseries>none</avrdeviceseries> +    <OutputType>Executable</OutputType> +    <Language>C</Language> +    <OutputFileName>$(MSBuildProjectName)</OutputFileName> +    <OutputFileExtension>.elf</OutputFileExtension> +    <OutputDirectory>$(MSBuildProjectDirectory)\$(Configuration)</OutputDirectory> +    <AssemblyName>PMC</AssemblyName> +    <Name>PMC</Name> +    <RootNamespace>PMC</RootNamespace> +    <ToolchainFlavour>Native</ToolchainFlavour> +    <KeepTimersRunning>true</KeepTimersRunning> +    <OverrideVtor>false</OverrideVtor> +    <OverrideVtorValue /> +    <eraseonlaunchrule>0</eraseonlaunchrule> +    <AsfVersion>3.1.3</AsfVersion> +    <avrtool>com.atmel.avrdbg.tool.stk600</avrtool> +    <avrtoolinterface>ISP</avrtoolinterface> +    <com_atmel_avrdbg_tool_stk600> +      <ToolType>com.atmel.avrdbg.tool.stk600</ToolType> +      <ToolName>STK600</ToolName> +      <ToolNumber>004A8D68669B</ToolNumber> +      <KeepTimersRunning>true</KeepTimersRunning> +      <OverrideVtor>false</OverrideVtor> +      <OverrideVtorValue> +      </OverrideVtorValue> +      <Channel> +        <host>127.0.0.1</host> +        <port>49172</port> +        <ssl>False</ssl> +      </Channel> +      <ToolOptions> +        <InterfaceName>ISP</InterfaceName> +        <InterfaceProperties> +          <JtagDbgClock>249000</JtagDbgClock> +          <JtagProgClock>1000000</JtagProgClock> +          <IspClock>25000</IspClock> +          <JtagInChain>false</JtagInChain> +          <JtagEnableExtResetOnStartSession>false</JtagEnableExtResetOnStartSession> +          <JtagDevicesBefore>0</JtagDevicesBefore> +          <JtagDevicesAfter>0</JtagDevicesAfter> +          <JtagInstrBitsBefore>0</JtagInstrBitsBefore> +          <JtagInstrBitsAfter>0</JtagInstrBitsAfter> +        </InterfaceProperties> +      </ToolOptions> +    </com_atmel_avrdbg_tool_stk600> +    <com_atmel_avrdbg_tool_ispmk2> +      <ToolType>com.atmel.avrdbg.tool.ispmk2</ToolType> +      <ToolName>AVRISP mkII</ToolName> +      <ToolNumber>000200136505</ToolNumber> +      <KeepTimersRunning>true</KeepTimersRunning> +      <OverrideVtor>false</OverrideVtor> +      <OverrideVtorValue> +      </OverrideVtorValue> +      <Channel> +        <host>127.0.0.1</host> +        <port>49228</port> +        <ssl>False</ssl> +      </Channel> +      <ToolOptions> +        <InterfaceName>ISP</InterfaceName> +        <InterfaceProperties> +          <JtagDbgClock>249000</JtagDbgClock> +          <JtagProgClock>1000000</JtagProgClock> +          <IspClock>8000</IspClock> +          <JtagInChain>false</JtagInChain> +          <JtagEnableExtResetOnStartSession>false</JtagEnableExtResetOnStartSession> +          <JtagDevicesBefore>0</JtagDevicesBefore> +          <JtagDevicesAfter>0</JtagDevicesAfter> +          <JtagInstrBitsBefore>0</JtagInstrBitsBefore> +          <JtagInstrBitsAfter>0</JtagInstrBitsAfter> +        </InterfaceProperties> +      </ToolOptions> +    </com_atmel_avrdbg_tool_ispmk2> +  </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.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned> +        <avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned> +        <avrgcc.compiler.symbols.DefSymbols> +          <ListValues> +            <Value>I2C_REWORK</Value> +            <Value>ENABLE_SERIAL</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.compiler.warnings.WarningsAsErrors>True</avrgcc.compiler.warnings.WarningsAsErrors> +        <avrgcc.linker.libraries.Libraries> +          <ListValues> +            <Value>m</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.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned> +        <avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned> +        <avrgcc.compiler.symbols.DefSymbols> +          <ListValues> +            <Value>ATTINY88_DIP</Value> +            <Value>DEBUG</Value> +            <Value>I2C_REWORK</Value> +          </ListValues> +        </avrgcc.compiler.symbols.DefSymbols> +        <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>Default (-g2)</avrgcc.compiler.optimization.DebugLevel> +        <avrgcc.compiler.warnings.AllWarnings>True</avrgcc.compiler.warnings.AllWarnings> +        <avrgcc.compiler.warnings.WarningsAsErrors>True</avrgcc.compiler.warnings.WarningsAsErrors> +        <avrgcc.linker.libraries.Libraries> +          <ListValues> +            <Value>m</Value> +          </ListValues> +        </avrgcc.linker.libraries.Libraries> +        <avrgcc.assembler.debugging.DebugLevel>Default (-Wa,-g)</avrgcc.assembler.debugging.DebugLevel> +      </AvrGcc> +    </ToolchainSettings> +  </PropertyGroup> +  <PropertyGroup Condition=" '$(Configuration)' == 'Release (DDR3L)' "> +    <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.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned> +        <avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned> +        <avrgcc.compiler.symbols.DefSymbols> +          <ListValues> +            <Value>I2C_REWORK</Value> +            <Value>ENABLE_SERIAL</Value> +            <Value>DDR3L</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.compiler.warnings.WarningsAsErrors>True</avrgcc.compiler.warnings.WarningsAsErrors> +        <avrgcc.linker.libraries.Libraries> +          <ListValues> +            <Value>m</Value> +          </ListValues> +        </avrgcc.linker.libraries.Libraries> +      </AvrGcc> +    </ToolchainSettings> +    <OutputPath>bin\Release (DDR3)\</OutputPath> +  </PropertyGroup> +  <PropertyGroup Condition=" '$(Configuration)' == 'Release (Dev)' "> +    <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.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned> +        <avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned> +        <avrgcc.compiler.symbols.DefSymbols> +          <ListValues> +            <Value>I2C_REWORK</Value> +            <Value>ENABLE_SERIAL</Value> +            <Value>ATTINY88_DIP</Value> +          </ListValues> +        </avrgcc.compiler.symbols.DefSymbols> +        <avrgcc.compiler.optimization.level>Optimize for size (-Os)</avrgcc.compiler.optimization.level> +        <avrgcc.compiler.optimization.PrepareFunctionsForGarbageCollection>True</avrgcc.compiler.optimization.PrepareFunctionsForGarbageCollection> +        <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.compiler.warnings.WarningsAsErrors>True</avrgcc.compiler.warnings.WarningsAsErrors> +        <avrgcc.linker.libraries.Libraries> +          <ListValues> +            <Value>m</Value> +          </ListValues> +        </avrgcc.linker.libraries.Libraries> +        <avrgcc.linker.optimization.GarbageCollectUnusedSections>True</avrgcc.linker.optimization.GarbageCollectUnusedSections> +      </AvrGcc> +    </ToolchainSettings> +    <OutputPath>bin\Release (Dev)\</OutputPath> +  </PropertyGroup> +  <PropertyGroup Condition=" '$(Configuration)' == 'Release (DDR3L, Ti)' "> +    <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.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned> +  <avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned> +  <avrgcc.compiler.symbols.DefSymbols> +    <ListValues> +      <Value>I2C_REWORK_disabled</Value> +      <Value>DDR3L_disabled_revB_R_FB_network_changed</Value> +      <Value>CHARGER_TI</Value> +      <Value>ENABLE_SERIAL_disabled</Value> +      <Value>LED_POLARITY</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.compiler.warnings.WarningsAsErrors>True</avrgcc.compiler.warnings.WarningsAsErrors> +  <avrgcc.linker.libraries.Libraries> +    <ListValues> +      <Value>m</Value> +    </ListValues> +  </avrgcc.linker.libraries.Libraries> +</AvrGcc> +    </ToolchainSettings> +    <OutputPath>bin\Release (DDR3L, Ti)\</OutputPath> +  </PropertyGroup> +  <ItemGroup> +    <Compile Include="bq24190.c"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="bq24190.h"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="config.h"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="debug.c"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="debug.h"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="error.h"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="global.h"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="i2c.c"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="i2c.h"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="io.c"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="io.h"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="ltc3675.c"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="ltc3675.h"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="ltc4155.c"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="ltc4155.h"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="main.c"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="power.c"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="power.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/rev_b/bq24190.c b/firmware/e300/rev_b/bq24190.c new file mode 100644 index 000000000..029beacf6 --- /dev/null +++ b/firmware/e300/rev_b/bq24190.c @@ -0,0 +1,335 @@ +/* + * bq24190.c + * + * Created: 11/12/2012 4:58:12 PM + *  Author: Balint Seeber + */ + +#ifdef CHARGER_TI + +#include "config.h" +#include "bq24190.h" + +#include <util/delay.h> + +#include "io.h" +#include "i2c.h" +#include "debug.h" +#include "global.h" +#include "error.h" + +#ifndef I2C_REWORK +#include "power.h" +#endif // I2C_REWORK + +static io_pin_t USBPM_IRQ	= IO_PB(1); + +#ifdef ATTINY88_DIP + +static io_pin_t CHRG_SDA     = IO_PC(2); +static io_pin_t CHRG_SCL     = IO_PC(3); + +#else + +#ifdef I2C_REWORK + +static io_pin_t CHRG_SDA     = IO_PC(4); +static io_pin_t CHRG_SCL     = IO_PC(5); + +#else + +#define CHRG_SDA	PWR_SDA +#define CHRG_SCL	PWR_SCL + +#endif // I2C_REWORK + +#endif // ATTINY88_DIP + +const bool _bq24190_pull_up = false; + +#define BQ24190_BASE_ADDRESS    (0x6B << 1) +#define BQ24190_WRITE_ADDRESS   (BQ24190_BASE_ADDRESS + 0) +#define BQ24190_READ_ADDRESS    (BQ24190_BASE_ADDRESS + 1) + +enum BQ24190Registers +{ +	BQ24190_REG_INPUT_SOURCE_CTL= 0, +	BQ24190_REG_PWR_ON_CONFIG	= 1, +	BQ24190_REG_CHARGE_CURRENT	= 2, +	BQ24190_REG_PRE_TERM_CURRENT= 3, +	BQ24190_REG_CHARGE_VOLTAGE	= 4, +	BQ24190_REG_TIMER_CONTROL	= 5, +	BQ24190_REG_SYSTEM_STATUS	= 8, +	BQ24190_REG_FAULT			= 9 +}; +/* +enum BQ24190TimerControl +{ +	 +}; +*/ +enum BQ24190Shifts +{ +	BQ24190_SHIFTS_CHARGER_CONFIG	= 4, +	BQ24190_SHIFTS_I2C_WATCHDOG		= 4, +	BQ24190_SHIFTS_CHARGER_STATUS	= 4, +	BQ24190_SHIFTS_CHARGER_FAULT	= 4, +}; + +enum BQ24190VBusStatus +{ +	BQ24190_VBUS_UNKNOWN, +	BQ24190_VBUS_USB, +	BQ24190_VBUS_ADAPTER, +	BQ24190_VBUS_OTG +}; + +enum BQ24190ChargerStatus +{ +	BQ24190_CHRG_STAT_NOT_CHARGING, +	BQ24190_CHRG_STAT_PRE_CHARGE, +	BQ24190_CHRG_STAT_FAST_CHARGING, +	BQ24190_CHRG_STAT_CHARGE_TERMINATION_DONE, +	BQ24190_CHRG_STAT_MASK = BQ24190_CHRG_STAT_CHARGE_TERMINATION_DONE +}; + +enum BQ24190SystemStatus +{ +	BQ24190_STATUS_DPM					= 0x08, +	BQ24190_STATUS_POWER_GOOD			= 0x04, +	BQ24190_STATUS_THERMAL_REGULATION	= 0x02, +	BQ24190_STATUS_VSYSMIN_REGULATION	= 0x01 +}; + +enum BQ24190Faults +{ +	BQ24190_FAULT_WATCHDOG_EXPIRED	= 0x80, +	BQ24190_FAULT_VBUS_OVERLOADED	= 0x40, +	BQ24190_FAULT_BATOVP			= 0x08 +}; + +enum BQ24190ChargerFaults +{ +	BQ24190_CHRGFAULT_NORMAL, +	BQ24190_CHRGFAULT_INPUT, +	BQ24190_CHRGFAULT_THERMAL, +	BQ24190_CHRGFAULT_SAFETY_TIMER +}; + +enum BQ24190NTCFaults +{ +	BQ24190_NTCFAULT_NORMAL, +	BQ24190_NTCFAULT_TS1_COLD, +	BQ24190_NTCFAULT_TS1_HOT, +	BQ24190_NTCFAULT_TS2_COLD, +	BQ24190_NTCFAULT_TS2_HOT, +	BQ24190_NTCFAULT_BOTH_COLD, +	BQ24190_NTCFAULT_BOTH_HOT +}; + +bool bq24190_toggle_charger(bool on) +{ +	uint8_t config = 0; +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, BQ24190_READ_ADDRESS, BQ24190_REG_PWR_ON_CONFIG, &config, _bq24190_pull_up) == false) +		return false; +	 +	debug_log_ex("BQPC ", false); +	debug_log_hex(config); +	 +	config &= ~(0x3 << BQ24190_SHIFTS_CHARGER_CONFIG); +	if (on) +		config |= (0x01 << BQ24190_SHIFTS_CHARGER_CONFIG);	// Enable charger +	 +	if (i2c_write_ex(CHRG_SDA, CHRG_SCL, BQ24190_WRITE_ADDRESS, BQ24190_REG_PWR_ON_CONFIG, config, _bq24190_pull_up) == false) +		return false; +	 +	//////// +/* +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, BQ24190_READ_ADDRESS, BQ24190_REG_PWR_ON_CONFIG, &config, _bq24190_pull_up) == false) +		return false; +	 +	debug_log_ex("BQPC ", false); +	debug_log_hex(config); +*/ +	//////// +	 +	return true; +} + +bool bq24190_init(bool disable_charger) +{ +#ifdef I2C_REWORK +	i2c_init_ex(CHRG_SDA, CHRG_SCL, _bq24190_pull_up); +#endif // I2C_REWORK +	io_input_pin(USBPM_IRQ); +#if !defined(DEBUG) && !defined(ATTINY88_DIP) +	//io_set_pin(USBPM_IRQ);	// [Enable pull-up for Open Drain] AVR pull-up not enough +#endif // DEBUG +	if (disable_charger) +	{ +		if (bq24190_toggle_charger(false) == false) +			return false; +	} +	 +	/////////////////////////////////// +	 +	uint8_t timer_control = 0; +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, BQ24190_READ_ADDRESS, BQ24190_REG_TIMER_CONTROL, &timer_control, _bq24190_pull_up) == false) +		return false; +	 +	debug_log_ex("BQTC ", false); +	debug_log_hex(timer_control); +	 +	timer_control &= ~(0x3 << BQ24190_SHIFTS_I2C_WATCHDOG); +	timer_control |= (0x00 << BQ24190_SHIFTS_I2C_WATCHDOG);	// Disable I2C watch dog +	 +	if (i2c_write_ex(CHRG_SDA, CHRG_SCL, BQ24190_WRITE_ADDRESS, BQ24190_REG_TIMER_CONTROL, timer_control, _bq24190_pull_up) == false) +		return false; +	 +	/////////////////////////////////// +	 +	//BQ24190_REG_PWR_ON_CONFIG +	// Minimum System Voltage Limit: (default) 101 3.5V +	 +	//BQ24190_REG_CHARGE_CURRENT +	// Fast Charge Current Limit: (default) 011000 2048mA +	 +	//BQ24190_REG_PRE_TERM_CURRENT +	// Pre-charge current limit: (default) 0001 256mA +	// Termination current limit: (default) 0001 256mA +	 +	//BQ24190_REG_CHARGE_VOLTAGE +	// Charge voltage limit: (default) 101100 4.208V +	 +	/////////////////////////////////// +	 +	uint8_t input_src_ctl = 0; +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, BQ24190_READ_ADDRESS, BQ24190_REG_INPUT_SOURCE_CTL, &input_src_ctl, _bq24190_pull_up) == false) +		return false; +	 +	debug_log_ex("BQIS ", false); +	debug_log_hex(input_src_ctl); +	 +	// Input voltage limit: (default) 0110 4.36V +	 +	//input_src_ctl &= ~(0x07); +	input_src_ctl |= (0x07);	// Set 3A limit +	 +	if (i2c_write_ex(CHRG_SDA, CHRG_SCL, BQ24190_WRITE_ADDRESS, BQ24190_REG_INPUT_SOURCE_CTL, input_src_ctl, _bq24190_pull_up) == false) +		return false; +	 +	return true; +} + +bool bq24190_has_interrupt(void) +{ +	//bool state = io_test_pin(USBPM_IRQ); +	//debug_log_ex("BQIRQ", false); +	//debug_log_byte(state); +	//return (state != 1); +	return (io_test_pin(USBPM_IRQ) == false); +} + +static uint8_t _bq24190_last_status, _bq24190_last_fault; + +bool _bq24190_handle_irq(void) +{ +	uint8_t val = 0x00; +	bool result = false; +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, BQ24190_READ_ADDRESS, BQ24190_REG_SYSTEM_STATUS, &val, _bq24190_pull_up) == false) +		goto _bq24190_handle_fail; +	 +	debug_log_ex("BQST ", false); +	debug_log_hex(val); +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, BQ24190_READ_ADDRESS, BQ24190_REG_SYSTEM_STATUS, &val, _bq24190_pull_up) == false) +		goto _bq24190_handle_fail; +	 +	_bq24190_last_status = val; + +	debug_log_ex("BQST ", false); +	debug_log_hex(val); +	 +	/*if (val & LTC4155_WALLSNS_GOOD) +	{ +		uint8_t wall_state = 0; +		if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_WALL, &wall_state, _ltc4155_pull_up) == false) +			goto _bq24190_handle_fail; +		 +		wall_state &= ~0x1E; +		wall_state |= 0x0E; +		 +		if (i2c_write_ex(CHRG_SDA, CHRG_SCL, LTC4155_WRITE_ADDRESS, LTC4155_REG_WALL, wall_state, _ltc4155_pull_up) == false) +			goto _bq24190_handle_fail; +		 +		debug_log("I+"); +	}*/ +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, BQ24190_READ_ADDRESS, BQ24190_REG_FAULT, &val, _bq24190_pull_up) == false) +		goto _bq24190_handle_fail; +	 +	debug_log_ex("BQF  ", false); +	debug_log_hex(val); +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, BQ24190_READ_ADDRESS, BQ24190_REG_FAULT, &val, _bq24190_pull_up) == false) +		goto _bq24190_handle_fail; +	 +	_bq24190_last_fault = val; +	 +	debug_log_ex("BQF  ", false); +	debug_log_hex(val); +	 +	val = (_bq24190_last_status >> BQ24190_SHIFTS_CHARGER_STATUS) & BQ24190_CHRG_STAT_MASK; +	 +	if (_state.blink_error == BlinkError_None) +	{ +		switch (val) +		{ +			case BQ24190_CHRG_STAT_PRE_CHARGE: +			case BQ24190_CHRG_STAT_FAST_CHARGING: +			//case BQ24190_CHRG_STAT_CHARGE_TERMINATION_DONE: +			{ +				if ((_state.battery_not_present == false)/* && +					(_ltc4155_last_good & (LTC4155_WALLSNS_GOOD | LTC4155_USBSNS_GOOD))*/) +				{ +					//charge_set_led(true); +					charge_notify(true); +					break; +				} +			} +			//case BQ24190_CHRG_STAT_NOT_CHARGING: +			default: +				//charge_set_led(false); +				charge_notify(false); +		} +	} +	 +//	bq24190_dump(); +	 +	result = true; +_bq24190_handle_fail: +	return result; +} + +bool bq24190_handle_irq(void)	// IRQ is pulsed (not held) +{ +	pmc_mask_irqs(true); +	 +	//_delay_ms(250);	// [Wait for registers to update] +	 +	bool result = _bq24190_handle_irq(); +	 +	pmc_mask_irqs(false); +	 +	return result; +} + +//void bq24190_dump(void) +/* +bool bq24190_set_charge_current_limit(uint8_t deciamps) +{ +	return true; +} +*/ +#endif // CHARGER_TI diff --git a/firmware/e300/rev_b/bq24190.h b/firmware/e300/rev_b/bq24190.h new file mode 100644 index 000000000..dc20ca796 --- /dev/null +++ b/firmware/e300/rev_b/bq24190.h @@ -0,0 +1,26 @@ +/* + * bq24190.h + * + * Created: 11/12/2012 4:58:23 PM + *  Author: Balint Seeber + */  + + +#ifndef BQ24190_H_ +#define BQ24190_H_ + +#include <stdbool.h> +#include <stdint.h> + +#ifdef CHARGER_TI + +bool bq24190_init(bool disable_charger); +bool bq24190_has_interrupt(void); +bool bq24190_handle_irq(void); +//void bq24190_dump(void); +//bool bq24190_set_charge_current_limit(uint8_t deciamps); +bool bq24190_toggle_charger(bool on); + +#endif // !CHARGER_TI + +#endif /* BQ24190_H_ */ diff --git a/firmware/e300/rev_b/config.h b/firmware/e300/rev_b/config.h new file mode 100644 index 000000000..2547f65f0 --- /dev/null +++ b/firmware/e300/rev_b/config.h @@ -0,0 +1,2 @@ +#define __DELAY_BACKWARD_COMPATIBLE__   // Avoid compile-time arg error to '__builtin_avr_delay_cycles' +#define F_CPU   1000000UL // 1 MHz (8MHz / 8) diff --git a/firmware/e300/rev_b/debug.c b/firmware/e300/rev_b/debug.c new file mode 100644 index 000000000..ff7d09445 --- /dev/null +++ b/firmware/e300/rev_b/debug.c @@ -0,0 +1,297 @@ +/* + * debug.c + */ + +#include "config.h" +#include "debug.h" + +#include <util/delay.h> +#include <avr/io.h> +#include <avr/interrupt.h> + +#include "io.h" +#include "power.h" +#include "global.h" + +#define DEBUG_BLINK_DELAY	250	// ms + +#ifdef ATTINY88_DIP + +#define SERIAL_DEBUG_INDEX			6 +#define SERIAL_DEBUG_PORT			PORTD +static io_pin_t SERIAL_DEBUG      = IO_PD(SERIAL_DEBUG_INDEX); + +#else +/* +#ifdef I2C_REWORK +//static io_pin_t SERIAL_DEBUG      = IO_PC(1);	// EN1 +#else +//static io_pin_t SERIAL_DEBUG      = EN4; +#endif // I2C_REWORK +*/ +// No good: PWR_EN4 trace still connected to LTC3675 +//#define SERIAL_DEBUG_INDEX			1 +//#define SERIAL_DEBUG_PORT			PORTA +//static io_pin_t SERIAL_DEBUG      = IO_PA(SERIAL_DEBUG_INDEX); + +// AVR_MISO +#define SERIAL_DEBUG_INDEX			4 +#define SERIAL_DEBUG_PORT			PORTB +static io_pin_t SERIAL_DEBUG      = IO_PB(SERIAL_DEBUG_INDEX); + +#endif // ATTINY88_DIP +/* +#ifdef DEBUG + +#else + +#endif // DEBUG +*/ +#ifdef DEBUG + +#ifdef ATTINY88_DIP +static io_pin_t DEBUG_1 = IO_PB(6); +static io_pin_t DEBUG_2	= IO_PB(7); +#endif // ATTINY88_DIP + +void debug_init() +{ +	io_output_pin(DEBUG_1); +	io_output_pin(DEBUG_2); +	 +	io_enable_pin(DEBUG_1, true); +	io_enable_pin(DEBUG_2, true); +#ifdef ENABLE_SERIAL	 +	io_set_pin(SERIAL_DEBUG); +	io_output_pin(SERIAL_DEBUG); +#endif // ENABLE_SERIAL +} + +#else + +void debug_init() +{ +#ifdef ENABLE_SERIAL +	io_set_pin(SERIAL_DEBUG); +	io_output_pin(SERIAL_DEBUG); +#endif // ENABLE_SERIAL +} + +#endif // DEBUG + +#if defined(DEBUG) && !defined(DEBUG_VOID) + +void debug_set(io_pin_t pin, bool enable) +{ +	io_enable_pin(pin, !enable); +} + +void debug_blink(uint8_t count) +{ +	io_enable_pin(DEBUG_1, false); +	io_enable_pin(DEBUG_2, true); +	_delay_ms(DEBUG_BLINK_DELAY * 2); + +	for (; count > 0; count--) { +		io_enable_pin(DEBUG_2, false); +		_delay_ms(DEBUG_BLINK_DELAY); +		io_enable_pin(DEBUG_2, true); +		_delay_ms(DEBUG_BLINK_DELAY); +	} + +	io_enable_pin(DEBUG_1, true); +	io_enable_pin(DEBUG_2, true); +	_delay_ms(DEBUG_BLINK_DELAY * 2); +} + +void debug_blink_rev(uint8_t count) +{ +	io_enable_pin(DEBUG_2, false); +	io_enable_pin(DEBUG_1, true); +	_delay_ms(DEBUG_BLINK_DELAY * 2); + +	for (; count > 0; count--) { +		io_enable_pin(DEBUG_1, false); +		_delay_ms(DEBUG_BLINK_DELAY); +		io_enable_pin(DEBUG_1, true); +		_delay_ms(DEBUG_BLINK_DELAY); +	} + +	io_enable_pin(DEBUG_2, true); +	io_enable_pin(DEBUG_1, true); +	_delay_ms(DEBUG_BLINK_DELAY * 2); +} + +void debug_blink2(uint8_t count) +{ +	io_enable_pin(DEBUG_1, true); +	io_enable_pin(DEBUG_2, true); +	_delay_ms(DEBUG_BLINK_DELAY * 2); + +	bool b = false; +	for (; count > 0; count--) { +		io_enable_pin(DEBUG_1, b); +		io_enable_pin(DEBUG_2, b); +		_delay_ms(DEBUG_BLINK_DELAY); +		b = !b; +	} + +	io_enable_pin(DEBUG_1, true); +	io_enable_pin(DEBUG_2, true); +	_delay_ms(DEBUG_BLINK_DELAY * 2); +} + +void debug_wait(void) +{ +	io_enable_pin(DEBUG_1, true); +	io_enable_pin(DEBUG_2, true); +	 +	bool b = false; +	while (true) +	{ +		io_enable_pin(DEBUG_1, b); +		io_enable_pin(DEBUG_2, !b); +		 +		_delay_ms(DEBUG_BLINK_DELAY); +		 +		b = !b; +	} +	 +	io_enable_pin(DEBUG_1, true); +	io_enable_pin(DEBUG_2, true); +} + +#else + +#ifndef DEBUG_VOID + +void debug_blink_rev(uint8_t count) +{ +	charge_set_led(true); +	_delay_ms(DEBUG_BLINK_DELAY * 4); + +	for (; count > 0; count--) { +		charge_set_led(false); +		_delay_ms(DEBUG_BLINK_DELAY); +		charge_set_led(true); +		_delay_ms(DEBUG_BLINK_DELAY * 2); +	} + +	_delay_ms(DEBUG_BLINK_DELAY * 2); +	charge_set_led(false); +	_delay_ms(DEBUG_BLINK_DELAY * 4); +} +#endif // DEBUG_VOID + +#endif // DEBUG + +#ifdef ENABLE_SERIAL + +static void _serial_tx(uint8_t* buffer) +{ +	//uint8_t time_fix = 0; +	// 3333/2 - 10 +	// 650 +	//	[-2 for DEV] -20 works (perhaps different USB-Serial converter) +	//	[-20 for PRD] Which board? +	//	+20 Board #5 (-0: 3.592, -10: 3.280) +	const uint16_t delay = 650+20; +	uint16_t countdown; +	 +	for (uint8_t j = 0; j < 10; ++j) +	{ +		if (buffer[j]) +		SERIAL_DEBUG_PORT |= _BV(SERIAL_DEBUG_INDEX); +		else +		SERIAL_DEBUG_PORT &= ~_BV(SERIAL_DEBUG_INDEX); +		 +		countdown = delay; +		while (--countdown) +		__asm("nop"); +	} +} + +static void _serial_tx_char(char c) +{ +	uint8_t buffer[10]; +	uint8_t i = 0; +	 +	buffer[i++] = 0;	// START +	for (int idx = 0; idx < 8; ++idx) +	buffer[i++] = (((uint8_t)(c) & ((uint8_t)1<<((idx)))) ? 0x01 : 0x00);	// Endianness: 7- +	buffer[i++] = 1;	// STOP +	 +	_serial_tx(buffer); +} + +void debug_log_ex_P(const char* message, bool new_line) +{ +	char c = pgm_read_byte(message); +	if (c == '\0') +		return; +	 +	pmc_mask_irqs(true); + +	do +	{ +		_serial_tx_char(c); +		c = pgm_read_byte(++message); +	} while (c != '\0'); +	 +	if (new_line) +		_serial_tx_char('\n'); + +	io_set_pin(SERIAL_DEBUG); +	 +	pmc_mask_irqs(false); +} + +void _debug_log_ex(const char* message, bool new_line) +{ +	if (message[0] == '\0') +	return; +	 +	pmc_mask_irqs(true); + +	do +	{ +		_serial_tx_char(*message); +	} while (*(++message) != '\0'); +	 +	if (new_line) +	_serial_tx_char('\n'); + +	io_set_pin(SERIAL_DEBUG); +	 +	pmc_mask_irqs(false); +} + +void debug_log_byte_ex(uint8_t n, bool new_line) +{ +	char ch[4]; +	ch[0] = '0' + (n / 100); +	ch[1] = '0' + ((n % 100) / 10); +	ch[2] = '0' + (n % 10); +	ch[3] = '\0'; +	_debug_log_ex(ch, new_line); +} + +void debug_log_hex_ex(uint8_t n, bool new_line) +{ +	char ch[4]; +	ch[0] = 'x'; +	uint8_t _n = n >> 4; +	if (_n < 10) +		ch[1] = '0' + _n; +	else +		ch[1] = 'A' + (_n - 10); +	n &= 0x0F; +	if (n < 10) +		ch[2] = '0' + n; +	else +		ch[2] = 'A' + (n - 10); +	ch[3] = '\0'; +	_debug_log_ex(ch, new_line); +} + +#endif // ENABLE_SERIAL diff --git a/firmware/e300/rev_b/debug.h b/firmware/e300/rev_b/debug.h new file mode 100644 index 000000000..5e6435972 --- /dev/null +++ b/firmware/e300/rev_b/debug.h @@ -0,0 +1,90 @@ +/* + * debug.h + */  + +#ifndef DEBUG_H_ +#define DEBUG_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <avr/pgmspace.h> + +#include "io.h" + +#ifdef DEBUG +#define DEBUG_INLINE +#define DEBUG_NOOP	; +#define LED_ON		false +#define LED_OFF		true +#else +#define DEBUG_INLINE inline +#define DEBUG_NOOP	{} +#define LED_ON		true +#define LED_OFF		false +#endif // DEBUG + +//#define DEBUG_VOID +#define DEBUG_SAFETY + +#ifdef DEBUG_VOID + +//#define debug_init	(void) +#define debug_set	(void) +#define debug_blink	(void) +#define debug_blink_rev	(void) +#define debug_blink2 (void) +#define debug_wait	(void) + +#else + +//DEBUG_INLINE void debug_init(void) DEBUG_NOOP +DEBUG_INLINE void debug_set(io_pin_t pin, bool enable) DEBUG_NOOP +DEBUG_INLINE void debug_blink(uint8_t count) DEBUG_NOOP +//DEBUG_INLINE void debug_blink_rev(uint8_t count) DEBUG_NOOP +void debug_blink_rev(uint8_t count); +DEBUG_INLINE void debug_blink2(uint8_t count) DEBUG_NOOP +DEBUG_INLINE void debug_wait(void) DEBUG_NOOP + +#endif // DEBUG_VOID + +#if defined(DEBUG) && !defined(ENABLE_SERIAL) +#define ENABLE_SERIAL +#endif // DEBUG && !ENABLE_SERIAL + +/*DEBUG_INLINE */void debug_init(void)/* DEBUG_NOOP*/; + +#ifdef ENABLE_SERIAL + +void debug_log_ex_P(const char* message, bool new_line); +void debug_log_hex_ex(uint8_t n, bool new_line); +void debug_log_byte_ex(uint8_t n, bool new_line); +void _debug_log_ex(const char* message, bool new_line); + +// Prototypes to silence avr-gcc +inline void debug_log_P(const char* message); +inline void debug_log_hex(uint8_t n); +inline void debug_log_byte(uint8_t n); +inline void _debug_log(const char* message); + +inline void debug_log_P(const char* message) { debug_log_ex_P(message, true); } +inline void debug_log_hex(uint8_t n) { debug_log_hex_ex(n, true); } +inline void debug_log_byte(uint8_t n) { debug_log_byte_ex(n, true); } +inline void _debug_log(const char* message) { _debug_log_ex(message, true); } + +#else + +inline void debug_log_ex_P		(const char* message, bool new_line) {}; +inline void debug_log_hex_ex	(uint8_t n, bool new_line) {}; +inline void debug_log_byte_ex	(uint8_t n, bool new_line) {}; +inline void _debug_log_ex		(const char* message, bool new_line) {}; + +#define debug_log_P			(void) +#define debug_log_hex		(void) +#define debug_log_byte		(void) +#define _debug_log			(void) +#endif // ENABLE_SERIAL + +#define debug_log(x)		debug_log_P(PSTR(x)) +#define debug_log_ex(x,nl)	debug_log_ex_P(PSTR(x), nl) + +#endif /* DEBUG_H_ */ diff --git a/firmware/e300/rev_b/error.h b/firmware/e300/rev_b/error.h new file mode 100644 index 000000000..82a8f0aca --- /dev/null +++ b/firmware/e300/rev_b/error.h @@ -0,0 +1,31 @@ +/* + * error.h + * + * Created: 4/09/2012 6:25:53 PM + *  Author: Balint Seeber + */  + + +#ifndef ERROR_H_ +#define ERROR_H_ + +enum ErrorBlinkCount	// Lower number = higher priority +{ +	BlinkError_None, +	// Low power/battery +	BlinkError_LowVoltage, +	BlinkError_LTC3675_UnderVoltage = BlinkError_LowVoltage, +	BlinkError_LTC4155_UnderVoltage = BlinkError_LowVoltage,	// FIXME: This does not work when checking status +	// Should match power boot steps +	BlinkError_FPGA_Power, +	BlinkError_DRAM_Power, +	BlinkError_1_8V_Peripherals_Power, +	BlinkError_3_3V_Peripherals_Power, +	BlinkError_TX_Power, +	// LTC3675 +	BlinkError_LTC3675_OverTemperature, +	// LTC4155 +	BlinkError_LTC4155_BadCell +}; + +#endif /* ERROR_H_ */ diff --git a/firmware/e300/rev_b/global.h b/firmware/e300/rev_b/global.h new file mode 100644 index 000000000..50fab581d --- /dev/null +++ b/firmware/e300/rev_b/global.h @@ -0,0 +1,49 @@ +/* + * global.h + * + * Created: 31/08/2012 8:47:14 PM + *  Author: Balint Seeber + */  + +#ifndef GLOBAL_H_ +#define GLOBAL_H_ + +#include <stdbool.h> +#include <stdint.h> +#include <avr/pgmspace.h> + +typedef struct State +{ +	bool interrupts_enabled; +	uint8_t interrupt_depth; +	//bool timers_running; +	uint8_t active_timers; +	bool powered; +	bool battery_not_present; +	bool battery_charging; +	bool wake_up; +	bool power_off; +	bool core_power_bad; +	bool ltc3675_irq; +#ifdef CHARGER_TI +	bool bq24190_irq; +#else +	bool ltc4155_irq; +#endif // CHARGER_TI +	//bool low_battery; +	uint8_t blink_error; +	uint8_t blinker_state; +	uint8_t blink_loops; +	uint8_t blink_last_loop; +	bool blink_stop; +} STATE; + +//extern volatile bool _timers_running; +extern volatile STATE _state; + +void pmc_set_blink_error(uint8_t count); +uint8_t pmc_get_blink_error(void); + +bool pmc_mask_irqs(bool mask); + +#endif /* GLOBAL_H_ */ diff --git a/firmware/e300/rev_b/i2c.c b/firmware/e300/rev_b/i2c.c new file mode 100644 index 000000000..70a28e61a --- /dev/null +++ b/firmware/e300/rev_b/i2c.c @@ -0,0 +1,518 @@ +#include "config.h" +#include "i2c.h" + +#include <util/delay.h> + +#include "io.h" +#include "debug.h" + +/* +	- Reset bus on failure (lack of ACK, etc) +	- Clock stretching +	- In pull-up mode, much code was commented out to ever avoid driving the bus (for a fleeting moment) as this was visible on the scope as short peaks (instead the line will briefly go Hi Z). +*/ + +volatile bool _i2c_disable_ack_check = false; + +// FIXME: Follow magic numbers should be in a struct that is passed into each function + +#define I2C_DEFAULT_RETRY_DELAY     1   // us MAGIC +#define I2C_DEFAULT_MAX_ACK_RETRIES 10  // * I2C_DEFAULT_RETRY_DELAY us + +#define I2C_DEFAULT_BUS_WAIT		10	// us MAGIC +#define I2C_DEFAULT_MAX_BUS_RETRIES	10 + +#define I2C_DEFAULT_SCL_LOW_PERIOD  2   // 1.3 us +#define I2C_DEFAULT_SCL_HIGH_PERIOD 1   // 0.6 us +#define I2C_DEFAULT_BUS_FREE_TIME   2   // 1.3 us +#define I2C_DEFAULT_STOP_TIME       1   // 0.6 us + +#define I2C_DELAY	_delay_us	// _delay_ms + +static bool _i2c_start_ex(io_pin_t sda, io_pin_t scl, bool pull_up) +{ +	// Assumes: SDA/SCL are both inputs + +	uint8_t retries = I2C_DEFAULT_MAX_BUS_RETRIES; +	while ((io_test_pin(sda) == false) || (io_test_pin(scl) == false)) +	{ +		I2C_DELAY(I2C_DEFAULT_BUS_WAIT); +		if (retries-- == 0) +		{ +debug_log("I2C:S1"); +			return false; +		}			 +	} +	 +	// START condition +//	if (pull_up == false) +		io_clear_pin(sda);	// Set LOW before switching to output +	io_output_pin(sda); +//	if (pull_up) +//		io_clear_pin(sda); +	I2C_DELAY(I2C_DEFAULT_SCL_LOW_PERIOD);  // Thd, sta +	 +	retries = I2C_DEFAULT_MAX_BUS_RETRIES; +	while (io_test_pin(scl) == false)	// SCL should remain high +	{ +		I2C_DELAY(I2C_DEFAULT_BUS_WAIT); +		if (retries-- == 0) +		{ +			io_input_pin(sda); +debug_log_ex("I2C:S2", false); +debug_log_hex(scl); +			return false; +		}			 +	} + +//	if (pull_up == false) +		io_clear_pin(scl); +	io_output_pin(scl); +//	if (pull_up) +//		io_clear_pin(scl); +	I2C_DELAY(I2C_DEFAULT_SCL_LOW_PERIOD / 2);   // MAGIC + +	return true; +} + +static bool _i2c_stop_ex(io_pin_t sda, io_pin_t scl, bool pull_up) +{ +	// Assumes: +	//	SCL is output & LOW +	//	SDA is input (Hi-Z, or pull-up enabled) +	 +	// Assuming pull-up already enabled +	//if (pull_up) +	//	io_set_pin(sda); +	 +	bool result = true; +	 +	// SDA should be HIGH after ACK has been clocked away +//	bool skip_drive = false; +	uint8_t retries = 0; +	while (io_test_pin(sda) == false) +	{ +		if (retries == I2C_DEFAULT_MAX_ACK_RETRIES) +		{ +			debug_log_ex("I2C:STP ", false); +			debug_log_hex(sda); +			debug_blink_rev(4); +			 +//			skip_drive = true; +			result = false; +			break;	// SDA is being held low?! +		} + +		++retries; +		I2C_DELAY(I2C_DEFAULT_RETRY_DELAY); +	} +	 +	// STOP condition +//	if ((pull_up == false) || (skip_drive)) +		io_clear_pin(sda);	// Don't tri-state if internal pull-up is used +//	//else +//	// Pin will now be driven, but having checked SDA is HIGH above means slave's SDA should be Open Collector (i.e. it won't blow up) +	io_output_pin(sda);	// Drive LOW +//	if (pull_up) +//		io_clear_pin(sda); + +	/////////////////////////////////// +	 +//	if (pull_up) +//		io_set_pin(scl);	// Don't tri-state if internal pull-up is used. Line will be driven, but assuming this is the only master on the clock line (i.e. no one else will pull it low). +	io_input_pin(scl); +	if (pull_up) +		io_set_pin(scl); +	I2C_DELAY(I2C_DEFAULT_STOP_TIME); +	 +	/////////////////////////////////// + +//	if ((pull_up) && (skip_drive == false)) +//		io_set_pin(sda);	// Don't tri-state if internal pull-up is used +	io_input_pin(sda); +//	if ((pull_up) && (skip_drive)) +		io_set_pin(sda); +	I2C_DELAY(I2C_DEFAULT_BUS_FREE_TIME); +	 +	return result; +} +/* +static void _i2c_stop(io_pin_t sda, io_pin_t scl) +{ +	_i2c_stop_ex(sda, scl, false); +} +*//* +static void _i2c_abort_safe_ex(io_pin_t pin, bool pull_up) +{ +	if (io_is_output(pin)) +	{ +		if (io_is_pin_set(pin))	// This is bad - hope no slave is pulling down the line +		{ +			io_input_pin(pin);	// Pull-up already enabled +			 +			if (pull_up == false) +				io_clear_pin(pin);	// Doing this after changing direction ensures the line is not brought down +		} +		else	// Currently pulling line down +		{ +			io_input_pin(pin);	// Hi-Z +			 +			if (pull_up)	// There will be a moment where the line will float (better than driving the line though...) +			{ +				io_set_pin(pin); +			} +		} +	} +	else	// Already an input +	{ +		if (pull_up) +		{ +			io_set_pin(pin);	// Enable pull-ups +		} +		else +		{ +			io_clear_pin(pin);	// Disable pull-ups +		} +	} +	 +	// Normally: pin will be Hi-Z input +	// With internal pull-up: pin will be input with pull-up enabled +} +*/ +static void _i2c_abort_safe(io_pin_t pin, bool pull_up) +{ +	if (pull_up == false) +		io_clear_pin(pin);	// Should never be output/HIGH, could be input/<was outputting HIGH> so disable pull-ups +	 +	io_input_pin(pin); +	 +	if (pull_up) +		io_set_pin(pin);	// Enable pull-up +} + +static void _i2c_abort_ex(io_pin_t sda, io_pin_t scl, bool pull_up) +{ +/*	if (pull_up == false) +	{ +		io_clear_pin(sda); +		io_clear_pin(scl); +	} +	 +	io_input_pin(scl); +	io_input_pin(sda); +	 +	if (pull_up) +	{ +		io_set_pin(sda); +		io_set_pin(scl); +	} +*/ +	_i2c_abort_safe(scl, pull_up); +	_i2c_abort_safe(sda, pull_up); + +	//_i2c_abort_safe_ex(scl, pull_up); +	//_i2c_abort_safe_ex(sda, pull_up); +} +/* +static void _i2c_abort(io_pin_t sda, io_pin_t scl) +{ +	_i2c_abort_ex(sda, scl, false); +} +*/ +static bool _i2c_write_byte_ex(io_pin_t sda, io_pin_t scl, uint8_t value, bool pull_up) +{ +    // Assumes: +    //  SDA output is LOW +    //  SCL output is LOW + +    for (uint8_t i = 0; i < 8; ++i) +    { +		bool b = ((value & (0x01 << (7 - i))) != 0x00);	// MSB first +		 +		if (b) +		{ +			if (pull_up) +			{ +//				io_set_pin(sda);	// This is bad (will drive line for a moment), but more stable than letting line float +				io_input_pin(sda); +				io_set_pin(sda); +			}				 +			else +				io_input_pin(sda);	// Release HIGH +			 +			if (io_test_pin(sda) == false) +			{ +				debug_log("I2C:WR "); +				debug_log_hex(sda); +				debug_blink_rev(1); +				return false; +			}			 +		}			 +		else +		{ +			if (pull_up) +			{ +//				if (io_is_output(sda)) +					io_clear_pin(sda); +//				else +//				{ +					io_output_pin(sda);	// [This is bad (will drive line for a moment), but more stable than letting line float] +//					io_clear_pin(sda); +//				} +			} +			else +			{ +				io_enable_pin(sda, false); +				io_output_pin(sda);	// Drive LOW +			}				 +		} +		 +		/////////////////////////////// + +        io_input_pin(scl);	// Release HIGH +		if (pull_up) +			io_set_pin(scl); +        I2C_DELAY(I2C_DEFAULT_SCL_HIGH_PERIOD); +#ifdef I2C_ALLOW_CLOCK_STRETCH +		uint8_t retries = I2C_DEFAULT_MAX_BUS_RETRIES; +		while (io_test_pin(scl) == false)	// Clock stretch requested? +		{ +			I2C_DELAY(I2C_DEFAULT_BUS_WAIT); +			if (--retries == 0) +			{ +				io_input_pin(sda);	// Release HIGH +				if (pull_up) +					io_set_pin(sda); +				 +				debug_log_ex("I2C:STRTCH ", false); +				debug_log_hex(scl); +				debug_blink_rev(2); +				return false; +			} +		} +#endif // I2C_ALLOW_CLOCK_STRETCH +		if (pull_up) +			io_clear_pin(scl); +        io_output_pin(scl);	// Drive LOW +        I2C_DELAY(I2C_DEFAULT_SCL_LOW_PERIOD); +    } + +    io_input_pin(sda);	// Release HIGH +	if (pull_up) +		io_set_pin(sda);	// Assuming letting line float won't confuse slave when pulling line LOW for ACK +    I2C_DELAY(I2C_DEFAULT_SCL_HIGH_PERIOD); + +    uint8_t retries = 0; +    while ((_i2c_disable_ack_check == false) && (io_test_pin(sda))) +    { +        if (retries == I2C_DEFAULT_MAX_ACK_RETRIES) +		{ +			debug_log_ex("I2C:ACK ", false); +			debug_log_hex_ex(sda, false); +			debug_log_hex(value); +			debug_blink_rev(3); +            return false;	// Will abort and not release bus - done by caller +		} + +        ++retries; +        I2C_DELAY(I2C_DEFAULT_RETRY_DELAY); +    } + +    // Clock away acknowledge +//	if (pull_up) +//		io_set_pin(scl); +    io_input_pin(scl);	// Release HIGH +	if (pull_up) +		io_set_pin(scl); +    I2C_DELAY(I2C_DEFAULT_SCL_HIGH_PERIOD); + +	if (pull_up) +		io_clear_pin(scl); +    io_output_pin(scl);	// Drive LOW +//	if (pull_up) +//		io_clear_pin(scl); +    I2C_DELAY(I2C_DEFAULT_SCL_LOW_PERIOD); + +    return true; +} + +static bool _i2c_read_byte_ex(io_pin_t sda, io_pin_t scl, uint8_t* value, bool pull_up) +{ +    // Assumes: +    //  SDA output is LOW +    //  SCL output is LOW +	 +	io_input_pin(sda); +	if (pull_up) +		io_set_pin(sda);	// OK to leave line floating for a moment (better not to drive as slave will be pulling it to ground) + +    (*value) = 0x00; + +    for (uint8_t i = 0; i < 8; ++i) +    { +//		if (pull_up) +//			io_set_pin(scl);	// [Not ideal with pull-up] +        io_input_pin(scl);	// Release HIGH +		if (pull_up) +			io_set_pin(scl); +        I2C_DELAY(I2C_DEFAULT_SCL_HIGH_PERIOD); +#ifdef I2C_ALLOW_CLOCK_STRETCH +		uint8_t retries = I2C_DEFAULT_MAX_BUS_RETRIES; +		while (io_test_pin(scl) == false)	// Clock stretch requested? +		{ +			I2C_DELAY(I2C_DEFAULT_BUS_WAIT); +			if (--retries == 0) +			{ +				debug_log_ex("I2C:R "); +				debug_log_hex(scl); +				debug_blink_rev(5); +				return false; +			}				 +		} +#endif // I2C_ALLOW_CLOCK_STRETCH +        (*value) |= ((io_test_pin(sda) ? 0x1 : 0x0) << (7 - i));   // MSB first + +		if (pull_up) +			io_clear_pin(scl); +        io_output_pin(scl);	// Drive LOW (not ideal with pull-up) +//		if (pull_up) +//			io_clear_pin(scl); +        I2C_DELAY(I2C_DEFAULT_SCL_LOW_PERIOD); +    } + +    // Not necessary to ACK since it's only this one byte + +    return true; +} + +bool i2c_read2_ex(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t* value, bool pull_up) +{ +	if (_i2c_start_ex(sda, scl, pull_up) == false) +		return false; + +	if (_i2c_write_byte_ex(sda, scl, addr & ~0x01, pull_up) == false) +	{ +#ifdef I2C_EXTRA_DEBUGGING +		//debug_log_ex("R21:", false); +		debug_log("R21"); +		//debug_log_hex(addr); +#endif // I2C_EXTRA_DEBUGGING +		goto i2c_read2_fail; +	} + +	if (_i2c_write_byte_ex(sda, scl, subaddr, pull_up) == false) +	{ +#ifdef I2C_EXTRA_DEBUGGING +		//debug_log_ex("R22:", false); +		debug_log("R22"); +		//debug_log_hex(subaddr); +#endif // I2C_EXTRA_DEBUGGING +		goto i2c_read2_fail; +	} +	 +	io_input_pin(scl); +	if (pull_up) +		io_set_pin(scl); +	I2C_DELAY(I2C_DEFAULT_BUS_WAIT); +	 +	if (_i2c_start_ex(sda, scl, pull_up) == false) +	{ +		return false; +	} +	 +	if (_i2c_write_byte_ex(sda, scl, addr | 0x01, pull_up) == false) +	{ +#ifdef I2C_EXTRA_DEBUGGING +		//debug_log_ex("R23:", false); +		debug_log("R23"); +		//debug_log_hex(addr); +#endif // I2C_EXTRA_DEBUGGING +		goto i2c_read2_fail; +	} + +	if (_i2c_read_byte_ex(sda, scl, value, pull_up) == false) +	{ +#ifdef I2C_EXTRA_DEBUGGING +		//debug_log_ex("R24:", false); +		debug_log("R24"); +		//debug_log_hex(*value); +#endif // I2C_EXTRA_DEBUGGING +		goto i2c_read2_fail; +	} +	 +	if (_i2c_stop_ex(sda, scl, pull_up) == false) +	{ +#ifdef I2C_EXTRA_DEBUGGING +		debug_log("R25"); +#endif // I2C_EXTRA_DEBUGGING +	} + +	return true; +i2c_read2_fail: +	_i2c_abort_ex(sda, scl, pull_up); +	return false; +} + +bool i2c_write_ex(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t value, bool pull_up) +{ +	if (_i2c_start_ex(sda, scl, pull_up) == false) +		return false; + +    if (_i2c_write_byte_ex(sda, scl, addr, pull_up) == false) +        goto i2c_write_fail; + +    if (_i2c_write_byte_ex(sda, scl, subaddr, pull_up) == false) +        goto i2c_write_fail; + +    if (_i2c_write_byte_ex(sda, scl, value, pull_up) == false) +        goto i2c_write_fail; + +    _i2c_stop_ex(sda, scl, pull_up); + +    return true; +i2c_write_fail: +	_i2c_abort_ex(sda, scl, pull_up); +	return false; +} + +bool i2c_write(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t value) +{ +	return i2c_write_ex(sda, scl, addr, subaddr, value, false); +} + +bool i2c_read_ex(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t* value, bool pull_up) +{ +    if (_i2c_start_ex(sda, scl, pull_up) == false) +		return false; + +    if (_i2c_write_byte_ex(sda, scl, addr, pull_up) == false) +        goto i2c_read_fail; + +    if (_i2c_write_byte_ex(sda, scl, subaddr, pull_up) == false) +        goto i2c_read_fail; + +    if (_i2c_read_byte_ex(sda, scl, value, pull_up) == false) +        goto i2c_read_fail; + +    _i2c_stop_ex(sda, scl, pull_up); + +    return true; +i2c_read_fail: +	_i2c_abort_ex(sda, scl, pull_up); +	return false; +} + +bool i2c_read(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t* value) +{ +	return i2c_read_ex(sda, scl, addr, subaddr, value, false); +} + +void i2c_init_ex(io_pin_t sda, io_pin_t scl, bool pull_up) +{ +	_i2c_abort_ex(sda, scl, pull_up); +} + +void i2c_init(io_pin_t sda, io_pin_t scl) +{ +	i2c_init_ex(sda, scl, false); +} diff --git a/firmware/e300/rev_b/i2c.h b/firmware/e300/rev_b/i2c.h new file mode 100644 index 000000000..5898e7e43 --- /dev/null +++ b/firmware/e300/rev_b/i2c.h @@ -0,0 +1,17 @@ +#ifndef I2C_H +#define I2C_H + +#include "io.h" + +void i2c_init(io_pin_t sda, io_pin_t scl); +bool i2c_read(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t* value); +bool i2c_write(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t value); + +void i2c_init_ex(io_pin_t sda, io_pin_t scl, bool pull_up); +bool i2c_read_ex(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t* value, bool pull_up); +bool i2c_read2_ex(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t* value, bool pull_up); +bool i2c_write_ex(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t value, bool pull_up); + +extern volatile bool _i2c_disable_ack_check; + +#endif // I2C_H diff --git a/firmware/e300/rev_b/io.c b/firmware/e300/rev_b/io.c new file mode 100644 index 000000000..0256181c6 --- /dev/null +++ b/firmware/e300/rev_b/io.c @@ -0,0 +1,75 @@ +/* + * Copyright 2009-2012 Ettus Research LLC + */ + +#include "io.h" +#include <avr/io.h> + +#define _GET_PIN(pin)           ((pin) & 0xf) +#define _GET_MASK(pin)          (_BV(_GET_PIN(pin))) +#define _GET_REG(pin, reg_x)    (*reg_x[pin >> 4]) + +#ifndef IO_DEBUG +static volatile uint8_t *ddr_x[] = {&DDRA, &DDRB, &DDRC, &DDRD};		// 0: input, 1: output +static volatile uint8_t *port_x[] = {&PORTA, &PORTB, &PORTC, &PORTD};	// Port contents (will appear at output if direction is set to output. If input, '1' enables pull-ups, '0' set tri-state) +static volatile uint8_t *pin_x[] = {&PINA, &PINB, &PINC, &PIND};		// Port contents (input) If output, will return value on PORT +#endif + +void io_output_pin(io_pin_t pin){ +#ifndef IO_DEBUG +	_GET_REG(pin, ddr_x) |= _GET_MASK(pin); +#endif +} + +void io_input_pin(io_pin_t pin){ +#ifndef IO_DEBUG +	_GET_REG(pin, ddr_x) &= ~_GET_MASK(pin); +#endif +} + +bool io_is_output(io_pin_t pin){ +#ifndef IO_DEBUG +	return bit_is_set(_GET_REG(pin, ddr_x), _GET_PIN(pin)); +#else +	return 0; +#endif +} + +bool io_is_input(io_pin_t pin){ +	return !io_is_output(pin); +} + +void io_set_pin(io_pin_t pin){	// In input mode, will enable pull-ups +#ifndef IO_DEBUG +	_GET_REG(pin, port_x) |= _GET_MASK(pin); +#endif +} + +void io_clear_pin(io_pin_t pin){	// In input mode, will disable pull-ups +#ifndef IO_DEBUG +	_GET_REG(pin, port_x) &= ~_GET_MASK(pin); +#endif +} + +bool io_is_pin_set(io_pin_t pin){ +#ifndef IO_DEBUG +	return bit_is_set(_GET_REG(pin, port_x), _GET_PIN(pin)); +#else +	return 0; +#endif +} + +void io_enable_pin(io_pin_t pin, bool enable){ +    if (enable) +        io_set_pin(pin); +    else +        io_clear_pin(pin); +} + +bool io_test_pin(io_pin_t pin){ +#ifndef IO_DEBUG +	return bit_is_set(_GET_REG(pin, pin_x), _GET_PIN(pin)); +#else +	return 0; +#endif +} diff --git a/firmware/e300/rev_b/io.h b/firmware/e300/rev_b/io.h new file mode 100644 index 000000000..7eea8f0a3 --- /dev/null +++ b/firmware/e300/rev_b/io.h @@ -0,0 +1,31 @@ +/* + * Copyright 2009 Ettus Research LLC + */ + +#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; + +void io_output_pin(io_pin_t pin); +void io_input_pin(io_pin_t pin); +bool io_is_output(io_pin_t pin); +bool io_is_input(io_pin_t pin); + +void io_set_pin(io_pin_t pin); +void io_clear_pin(io_pin_t pin); +void io_enable_pin(io_pin_t pin, bool enable); +bool io_is_pin_set(io_pin_t pin); + +bool io_test_pin(io_pin_t pin); + +#endif /* IO_H */ diff --git a/firmware/e300/rev_b/ltc3675.c b/firmware/e300/rev_b/ltc3675.c new file mode 100644 index 000000000..0f85ec7e5 --- /dev/null +++ b/firmware/e300/rev_b/ltc3675.c @@ -0,0 +1,525 @@ +/* + * Copyright 2012 Ettus Research LLC + */ + +/* +    ? STOP condition after writing address on read +    - Default buck/boost register values are OK +*/ + +#include "config.h" +#include "ltc3675.h" + +//#include <stdio.h> +#include <util/delay.h> +#include <avr/interrupt.h> + +#include "io.h" +#include "i2c.h" +#include "debug.h" +#include "global.h" +#include "error.h" + +#ifndef I2C_REWORK +#include "power.h" +#endif // I2C_REWORK + +const bool _ltc3675_pull_up = +#ifdef I2C_REWORK +	true +#else +	false +#endif // I2C_REWORK +; + +volatile ltc3675_reg_helper_fn _ltc3675_reg_helper; + +//#define HARDWIRE_ENABLE	// Use hardware enable pins instead of I2C on regulators that support it + +#ifdef ATTINY88_DIP + +#ifdef HARDWIRE_ENABLE +static io_pin_t PWR_EN1     = IO_PC(7);	// Not routed by card +static io_pin_t PWR_EN2     = IO_PA(0);	// Not available on DIP +static io_pin_t PWR_EN3     = IO_PA(1);	// Not available on DIP +static io_pin_t PWR_EN4     = IO_PB(6);	// Instead of FTDI_BCD +static io_pin_t PWR_EN5     = IO_PB(7);	// Instead of FTDI_PWREN2 +#endif // HARDWIRE_ENABLE + +//static io_pin_t PWR_SDA     = IO_PC(4); +//static io_pin_t PWR_SCL     = IO_PC(5); + +#else + +#ifdef HARDWIRE_ENABLE +static io_pin_t PWR_EN1     = IO_PC(1); +//static io_pin_t PWR_EN2     = IO_PC(2);	// Now used by I2C for charge controller +//static io_pin_t PWR_EN3     = IO_PC(3);	// Now used by I2C for charge controller +static io_pin_t PWR_EN4     = IO_PA(1); +static io_pin_t PWR_EN5     = IO_PA(2); +#endif // HARDWIRE_ENABLE + +#ifdef I2C_REWORK +static io_pin_t PWR_SDA     = IO_PC(2);		// Instead of EN5 +static io_pin_t PWR_SCL     = IO_PA(2);		// Instead of EN2 +#endif // I2C_REWORK + +#endif // ATTINY88_DIP + +static io_pin_t PWR_IRQ     = IO_PD(0); +static io_pin_t WAKEUP      = IO_PD(2); +static io_pin_t ONSWITCH_DB = IO_PD(3); +static io_pin_t PWR_RESET   = IO_PD(4); + +#define LTC3675_BASE_ADDRESS    0x12 +#define LTC3675_WRITE_ADDRESS   (LTC3675_BASE_ADDRESS + 0) +#define LTC3675_READ_ADDRESS    (LTC3675_BASE_ADDRESS + 1) + +#define LTC3675_RETRY_DELAY     1   // us MAGIC +#define LTC3675_MAX_ACK_RETRIES 10  // * LTC3675_RETRY_DELAY us + +#define LTC3675_SCL_LOW_PERIOD  2   // 1.3 us +#define LTC3675_SCL_HIGH_PERIOD 1   // 0.6 us +#define LTC3675_BUS_FREE_TIME   2   // 1.3 us +#define LTC3675_STOP_TIME       1   // 0.6 us + +#define LTC3675_REGULATOR_ENABLE_DELAY	10	// 50	// ms (some arbitrary value so that the external power supply can settle) + +enum LTC3675Registers +{ +	LTC3675_REG_NONE			= 0x00, +	LTC3675_REG_BUCK1			= 0x01, +	LTC3675_REG_BUCK2			= 0x02, +	LTC3675_REG_BUCK3			= 0x03, +	LTC3675_REG_BUCK4			= 0x04, +	LTC3675_REG_BOOST			= 0x05, +	LTC3675_REG_BUCK_BOOST		= 0x06, +	LTC3675_REG_LED_CONFIG		= 0x07, +	LTC3675_REG_LED_DAC			= 0x08, +	LTC3675_REG_UVOT			= 0x09, +	LTC3675_REG_RSTB			= 0xA0, +	LTC3675_REG_IRQB_MASK		= 0x0B, +	LTC3675_REG_REALTIME_STATUS	= 0x0C, +	LTC3675_REG_LATCHED_STATUS	= 0x0D, +	LTC3675_REG_CLEAR_IRQ		= 0x0F +}; + +enum LTC3675StatusBits +{ +	LTC3675_UnderVoltage	= 1 << 7, +	LTC3675_OverTemperature	= 1 << 6, +	LTC3675_BuckBoost_PGood	= 1 << 5, +	LTC3675_Boost_PGood		= 1 << 4, +	LTC3675_Buck4_PGood		= 1 << 3, +	LTC3675_Buck3_PGood		= 1 << 2, +	LTC3675_Buck2_PGood		= 1 << 1, +	LTC3675_Buck1_PGood		= 1 << 0 +}; + +#define LTC3675_DEFAULT_BUCK_REG_VAL		0x6F +#define LTC3675_DEFAULT_BOOST_REG_VAL		0x0F +#define LTC3675_DEFAULT_BUCK_BOOST_REG_VAL	0x0F + +#define LTC3675_ENABLE_REGISTER_BIT			0x80 + +// Max I2C rate = 400kHz + +static void _ltc3675_clear_irq() +{ +	// Two-stage clear +	i2c_write_ex(PWR_SDA, PWR_SCL, LTC3675_WRITE_ADDRESS, LTC3675_REG_CLEAR_IRQ, 0x00, _ltc3675_pull_up); +	i2c_write_ex(PWR_SDA, PWR_SCL, LTC3675_WRITE_ADDRESS, LTC3675_REG_NONE, 0x00, _ltc3675_pull_up); +} + +volatile uint8_t _ltc3675_last_status = 0x00; + +uint8_t ltc3675_get_last_status(void) +{ +	return _ltc3675_last_status; +} + +uint8_t ltc3675_reg_status_to_error(uint8_t val) +{ +	if (((val & LTC3675_BuckBoost_PGood) == 0) && ((_ltc3675_reg_helper)(LTC3675_REG_6))) +		return BlinkError_3_3V_Peripherals_Power; +	 +	if (((val & LTC3675_Boost_PGood) == 0) && ((_ltc3675_reg_helper)(LTC3675_REG_5))) +		return BlinkError_TX_Power; +	 +	//if (((val & LTC3675_Buck4_PGood) == 0) && ((_ltc3675_reg_helper)(LTC3675_REG_4))) +	 +	if (((val & LTC3675_Buck3_PGood) == 0) && ((_ltc3675_reg_helper)(LTC3675_REG_3))) +		return BlinkError_1_8V_Peripherals_Power; +	 +	//if (((val & LTC3675_Buck2_PGood) == 0) && ((_ltc3675_reg_helper)(LTC3675_REG_2))) +	 +	if (((val & LTC3675_Buck1_PGood) == 0) && ((_ltc3675_reg_helper)(LTC3675_REG_1))) +		return BlinkError_DRAM_Power; +	 +	return BlinkError_None; +} + +bool ltc3675_is_power_good(uint8_t val) +{ +	return (ltc3675_reg_status_to_error(val) == BlinkError_None); +} + +uint8_t ltc3675_status_to_error(uint8_t val) +{ +	if (val & LTC3675_UnderVoltage) +		return BlinkError_LTC3675_UnderVoltage; +	 +	if (val & LTC3675_OverTemperature) +		return BlinkError_LTC3675_OverTemperature; +	 +	uint8_t reg_error = ltc3675_reg_status_to_error(val); +	if (reg_error != BlinkError_None) +		return reg_error; +	 +	return BlinkError_None; +} + +bool _ltc3675_handle_irq(void) +{ +	uint8_t val = 0x00; +	bool result = false; +	 +	if (i2c_read2_ex(PWR_SDA, PWR_SCL, LTC3675_READ_ADDRESS, LTC3675_REG_LATCHED_STATUS, &val, _ltc3675_pull_up)) +	{ +		debug_log_ex("3675LTCH ", false); +		debug_log_hex(val); +	} +	 +	if (i2c_read2_ex(PWR_SDA, PWR_SCL, LTC3675_READ_ADDRESS, /*LTC3675_REG_LATCHED_STATUS*/LTC3675_REG_REALTIME_STATUS, &val, _ltc3675_pull_up))	// No point acting on latched because could have been resolved +	{ +		//debug_log_ex("3675LTCH ", false); +		debug_log_ex("3675RT ", false); +		debug_log_hex(val); +		 +		_ltc3675_last_status = val; +		 +		uint8_t error = ltc3675_status_to_error(val); +		 +		/*if (val & LTC3675_UnderVoltage) +		{ +			pmc_set_blink_error(BlinkError_LTC3675_UnderVoltage); +			//_state.low_battery = true; +		}*/ +		 +		if (error) +		{ +			pmc_set_blink_error(error); +			 +			/*_i2c_disable_ack_check = true; +			uint8_t chk = 0x00; +			chk |= (_ltc3675_reg_helper)(LTC3675_REG_6) << 0; +			chk |= (_ltc3675_reg_helper)(LTC3675_REG_5) << 1; +			chk |= (_ltc3675_reg_helper)(LTC3675_REG_3) << 2; +			chk |= (_ltc3675_reg_helper)(LTC3675_REG_1) << 3; +			i2c_write_ex(PWR_SDA, PWR_SCL, 0xFE, 0xFF, chk, _ltc3675_pull_up); +			_i2c_disable_ack_check = false;*/ +		}			 +		 +		result = true; +	} +	 +	_ltc3675_clear_irq(); +	 +	return result; +} + +static bool _ltc3675_get_realtime_status(uint8_t* val) +{ +	//cli(); +	 +	if (i2c_read2_ex(PWR_SDA, PWR_SCL, LTC3675_READ_ADDRESS, LTC3675_REG_REALTIME_STATUS, val, _ltc3675_pull_up) == false) +		return false; +	 +	debug_log_ex("3675RT ", false); +	debug_log_hex(*val); +	 +	//sei(); +	 +	return true; +} + +int8_t ltc3675_check_status(void) +{ +	uint8_t val = 0x00; +	 +	pmc_mask_irqs(true); +	 +	bool result = _ltc3675_get_realtime_status(&val); +	 +	pmc_mask_irqs(false); +	 +	if (result == false) +		return -1; +	 +	//_ltc3675_last_status = val; +	 +	/*if (val & LTC3675_UnderVoltage) +		return BlinkError_LTC3675_UnderVoltage; +	 +	if (val & LTC3675_OverTemperature) +		return BlinkError_LTC3675_OverTemperature; +	 +	return BlinkError_None;*/ +	 +	return ltc3675_status_to_error(val); +} + +bool ltc3675_handle_irq(void) +{ +	pmc_mask_irqs(true); +	 +	/*uint8_t*/bool result = _ltc3675_handle_irq(); +	 +	pmc_mask_irqs(false); +	 +	return result; +} + +static bool _ltc3675_default_reg_helper(uint8_t address) +{ +	uint8_t val = 0x00; +	i2c_read2_ex(PWR_SDA, PWR_SCL, LTC3675_READ_ADDRESS, address, &val, _ltc3675_pull_up); +	return ((val & LTC3675_ENABLE_REGISTER_BIT) == LTC3675_ENABLE_REGISTER_BIT); +} + +bool ltc3675_init(ltc3675_reg_helper_fn helper) +{ +	if (helper) +		_ltc3675_reg_helper = helper; +	else +		_ltc3675_reg_helper = _ltc3675_default_reg_helper; +#ifdef HARDWIRE_ENABLE +    io_output_pin(PWR_EN1); +    io_output_pin(PWR_EN2); +    io_output_pin(PWR_EN3); +    io_output_pin(PWR_EN4); +    io_output_pin(PWR_EN5); +#endif // HARDWIRE_ENABLE + + /*	io_output_pin(PWR_SDA); +    io_output_pin(PWR_SCL); + +    // Must remain HIGH when idle +    io_set_pin(PWR_SDA); +    io_set_pin(PWR_SCL); +*/ +#ifdef I2C_REWORK +	i2c_init_ex(PWR_SDA, PWR_SCL, _ltc3675_pull_up); +#endif // I2C_REWORK +    io_input_pin(PWR_IRQ); +#if !defined(DEBUG) && !defined(ATTINY88_DIP) +	io_set_pin(PWR_IRQ);	// Enable pull-up for Open Drain +#endif // DEBUG +	 +    io_input_pin(WAKEUP); +	io_set_pin(WAKEUP);	// Enable pull-up for Open Drain +	 +    io_input_pin(ONSWITCH_DB); +	io_set_pin(ONSWITCH_DB);	// Enable pull-up for Open Drain +	 +    io_input_pin(PWR_RESET); +	io_set_pin(PWR_RESET);	// Enable pull-up for Open Drain +	 +	_ltc3675_clear_irq();	// Clear old interrupt - state might have changed (e.g. undervoltage might have been resolved) + +    if (i2c_write_ex(PWR_SDA, PWR_SCL, LTC3675_WRITE_ADDRESS, LTC3675_REG_IRQB_MASK, 0xFF, _ltc3675_pull_up) == false)	// Any PGOOD fault will pull IRQB low +		return false; +	 +	if (i2c_write_ex(PWR_SDA, PWR_SCL, LTC3675_WRITE_ADDRESS, LTC3675_REG_UVOT, 0x70, _ltc3675_pull_up) == false)	// 3.4V UV +		return false; +	 +	if (ltc3675_has_interrupt()) +		_ltc3675_handle_irq(); +	 +	// Non-maskable: +	//	UV warning threshold (default): 2.7V +	//	Over temp warning threshold (default): 10 degrees below + +    return true; +} + +bool ltc3675_is_waking_up(void) +{ +	return io_test_pin(WAKEUP); +} + +static bool _ltc3675_is_pgood(uint8_t reg) +{ +	uint8_t val = 0x00; +	if (_ltc3675_get_realtime_status(&val) == false) +		return false; +	return ((reg & val) == reg); +} + +static bool _ltc3675_toggle_reg(uint8_t addr, uint8_t def_reg, bool on) +{ +	bool result = true; +	 +	//cli(); +	 +	uint8_t val = 0x00 | def_reg; +	if (i2c_read2_ex(PWR_SDA, PWR_SCL, LTC3675_READ_ADDRESS, addr, &val, _ltc3675_pull_up) == false) +		return false; +	 +	val &= ~LTC3675_ENABLE_REGISTER_BIT; +	 +	if (i2c_write_ex(PWR_SDA, PWR_SCL, LTC3675_WRITE_ADDRESS, addr, /*def_reg*/val | (on ? LTC3675_ENABLE_REGISTER_BIT : 0x00), _ltc3675_pull_up) == false) +		//return true; +		result = false; +	 +	if (on) +	{ +		_delay_ms(LTC3675_REGULATOR_ENABLE_DELAY); +	} +	 +	//sei(); + +	return result; +	//return true; +} + +bool ltc3675_enable_reg(ltc3675_regulator_t reg, bool on) +{ +	//debug_blink2(reg + 1); +	debug_log_ex("3675 ", false); +	debug_log_byte_ex(reg, true); +	 +	// Sub-address: index of regulator +	// Data: <default reg contents> | <enable> +	 +	bool result = false; +	 +    switch (reg) +    { +        case LTC3675_REG_1: // Master +        case LTC3675_REG_2: // Slave +#ifdef HARDWIRE_ENABLE +            io_enable_pin(PWR_EN1, on); +			//break; +#else +			//debug_blink2(reg + 1); +			if (_ltc3675_toggle_reg(LTC3675_REG_BUCK1, LTC3675_DEFAULT_BUCK_REG_VAL, on) == false) { +				//debug_blink2(reg + 1); +				return false; +			} +			//debug_blink2(reg + 1); +#endif // HARDWIRE_ENABLE +			result = (_ltc3675_is_pgood(LTC3675_Buck1_PGood) == on); +			break; +        case LTC3675_REG_3: // Master +        case LTC3675_REG_4: // Slave +#ifdef HARDWIRE_ENABLE +            io_enable_pin(PWR_EN3, on); +            //break; +#else +			if (_ltc3675_toggle_reg(LTC3675_REG_BUCK3, LTC3675_DEFAULT_BUCK_REG_VAL, on) == false) +				return false; +#endif // HARDWIRE_ENABLE +			result = (_ltc3675_is_pgood(LTC3675_Buck3_PGood) == on); +			break; +        case LTC3675_REG_5: // I2C only +            if (_ltc3675_toggle_reg(LTC3675_REG_BOOST, LTC3675_DEFAULT_BOOST_REG_VAL, on) == false)    // (Boost address, Default reg contents | Enable) +				return false; +			result = (_ltc3675_is_pgood(LTC3675_Boost_PGood) == on); +			break; +        case LTC3675_REG_6: // Single +#ifdef HARDWIRE_ENABLE +            io_enable_pin(PWR_EN5, on); +            //break; +#else +			if (_ltc3675_toggle_reg(LTC3675_REG_BUCK_BOOST, LTC3675_DEFAULT_BUCK_BOOST_REG_VAL, on) == false) +				return false; +#endif // HARDWIRE_ENABLE +			result = (_ltc3675_is_pgood(LTC3675_BuckBoost_PGood) == on); +			break; +        //default: +		//	return false; +    } +	 +	_debug_log((result ? "+" : "-")); + +    return result; +} + +bool ltc3675_set_voltage(ltc3675_regulator_t reg, uint16_t voltage) +{ +    // Not necessary due to R-bridges and default DAC registers + +    // VRAM will be 1.3579 - a little high? (re-program DAC reference) +    //  No: minimum FB step will put Vout < 1.35 +	 +	uint16_t max_voltage = 0; +	uint8_t reg_subaddr = 0; +	 +	switch (reg) +	{ +		case LTC3675_REG_1:	// 1A Buck +		case LTC3675_REG_2:	// 1A Buck +			max_voltage = 1500; +			reg_subaddr = LTC3675_REG_BUCK1; +			break; +		case LTC3675_REG_3:	// 500mA Buck +		case LTC3675_REG_4:	// 500mA Buck +			max_voltage = 1800; +			reg_subaddr = LTC3675_REG_BUCK3; +			break; +		case LTC3675_REG_5:	// 1A Boost +			max_voltage = 5000; +			reg_subaddr = LTC3675_REG_BOOST; +			break; +		case LTC3675_REG_6:	// 1A Buck-Boost +			max_voltage = 3300; +			reg_subaddr = LTC3675_REG_BUCK_BOOST; +			break; +	} +	 +	if (voltage > max_voltage) +		return false; +	 +	//uint32_t rMax = ((uint32_t)voltage * 1000) / (uint32_t)max_voltage; +	//uint32_t rFB = ((uint32_t)max_voltage * 1000) / (uint32_t)800; +	uint32_t rFB = ((uint32_t)max_voltage * 1000) / (uint32_t)800;	// 800mV full-scale feedback voltage +	uint32_t r = ((uint32_t)voltage * 1000) / (uint32_t)rFB; +	if (r < 450) +		return false; +	 +	uint16_t rDAC = (16 * ((uint16_t)r - 450)) / (800 - 450); +	 +	debug_log_ex("Vr ", false); +	debug_log_byte_ex(reg, false); +	debug_log_ex("=", false); +	debug_log_byte_ex((uint8_t)rDAC, false); +	 +	uint8_t val = 0x00; +	if (i2c_read2_ex(PWR_SDA, PWR_SCL, LTC3675_READ_ADDRESS, reg_subaddr, &val, _ltc3675_pull_up) == false) +	{ +		debug_log("-"); +		return false; +	}		 +	 +	val = (val & 0xF0) | (uint8_t)rDAC; +	if (i2c_write_ex(PWR_SDA, PWR_SCL, LTC3675_WRITE_ADDRESS, reg_subaddr, val, _ltc3675_pull_up) == false) +	{ +		debug_log("-"); +		return false; +	} +	 +	debug_log("+"); + +	return true; +} + +bool ltc3675_is_power_button_depressed(void) +{ +	return (io_test_pin(ONSWITCH_DB) == false); +} + +bool ltc3675_has_interrupt(void) +{ +	return (io_test_pin(PWR_IRQ) == false); +} diff --git a/firmware/e300/rev_b/ltc3675.h b/firmware/e300/rev_b/ltc3675.h new file mode 100644 index 000000000..d7c4baa59 --- /dev/null +++ b/firmware/e300/rev_b/ltc3675.h @@ -0,0 +1,37 @@ +/* + * Copyright 2012 Ettus Research LLC + */ + +#ifndef LTC3675_H +#define LTC3675_H + +//#include "types.h" +#include <stdbool.h> +#include <stdint.h> + +typedef bool (*ltc3675_reg_helper_fn)(uint8_t address); + +bool ltc3675_init(ltc3675_reg_helper_fn helper); + +typedef enum ltc3675_regulators { +    LTC3675_REG_1,  // 1A Buck +    LTC3675_REG_2,  // 1A Buck +    LTC3675_REG_3,  // 500mA Buck +    LTC3675_REG_4,  // 500mA Buck +    LTC3675_REG_5,  // 1A Boost +    LTC3675_REG_6   // 1A Buck-Boost +    // LED Boost +} ltc3675_regulator_t; + +bool ltc3675_enable_reg(ltc3675_regulator_t reg, bool on); +bool ltc3675_set_voltage(ltc3675_regulator_t reg, uint16_t voltage); +bool ltc3675_is_power_button_depressed(void); +bool ltc3675_has_interrupt(void); +bool ltc3675_handle_irq(void); +int8_t ltc3675_check_status(void); +uint8_t ltc3675_get_last_status(void); +uint8_t ltc3675_status_to_error(uint8_t val); +bool ltc3675_is_power_good(uint8_t val); +bool ltc3675_is_waking_up(void); + +#endif /* LTC3675_H */ diff --git a/firmware/e300/rev_b/ltc4155.c b/firmware/e300/rev_b/ltc4155.c new file mode 100644 index 000000000..5f404e651 --- /dev/null +++ b/firmware/e300/rev_b/ltc4155.c @@ -0,0 +1,402 @@ +/* + * ltc4155.c + */  + +#ifndef CHARGER_TI + +#include "config.h" +#include "ltc4155.h" + +#include <util/delay.h> + +#include "io.h" +#include "i2c.h" +#include "power.h" +#include "debug.h" +#include "global.h" +#include "error.h" + +static io_pin_t USBPM_IRQ	= IO_PB(1); + +#ifdef ATTINY88_DIP + +static io_pin_t CHRG_SDA     = IO_PC(2); +static io_pin_t CHRG_SCL     = IO_PC(3); + +#else + +#ifdef I2C_REWORK + +static io_pin_t CHRG_SDA     = IO_PC(4); +static io_pin_t CHRG_SCL     = IO_PC(5); + +#else + +#define CHRG_SDA	PWR_SDA +#define CHRG_SCL	PWR_SCL + +#endif // I2C_REWORK + +#endif // ATTINY88_DIP + +const bool _ltc4155_pull_up = false; + +#define LTC4155_BASE_ADDRESS    0x12 +#define LTC4155_WRITE_ADDRESS   (LTC4155_BASE_ADDRESS + 0) +#define LTC4155_READ_ADDRESS    (LTC4155_BASE_ADDRESS + 1) +/* +#define LTC4155_RETRY_DELAY     1   // us MAGIC +#define LTC4155_MAX_ACK_RETRIES 10  // * LTC4155_RETRY_DELAY us + +#define LTC4155_SCL_LOW_PERIOD  2   // 1.3 us +#define LTC4155_SCL_HIGH_PERIOD 1   // 0.6 us +#define LTC4155_BUS_FREE_TIME   2   // 1.3 us +#define LTC4155_STOP_TIME       1   // 0.6 us +*/ +enum LTC4155Registers +{ +	LTC4155_REG_USB			= 0x00,	// W/R +	LTC4155_REG_WALL		= 0x01,	// W/R +	LTC4155_REG_CHARGE		= 0x02,	// W/R +	LTC4155_REG_STATUS		= 0x03,	// R +	LTC4155_REG_GOOD		= 0x04,	// R +	LTC4155_REG_THERMISTOR	= 0x05,	// R +	LTC4155_REG_ENABLE		= 0x06,	// W/R +	LTC4155_REG_ARM_AND_SHIP= 0x07	// W +}; + +enum LTC4155InterruptMasks	// LTC4155_REG_ENABLE +{ +	LTC4155_ENABLE_USB_OTG	= 1 << 1, +	 +	LTC4155_INT_UVCL	= 1 << 2, +	LTC4155_INT_ILIMIT	= 1 << 3, +	LTC4155_INT_USB_OTG	= 1 << 4, +	LTC4155_INT_EXT_PWR	= 1 << 5, +	LTC4155_INT_FAULT	= 1 << 6, +	LTC4155_INT_CHARGER	= 1 << 7 +}; + +enum LTC4155Options	// LTC4155_REG_USB +{ +	LTC4155_USB_OTG_LOCKOUT				= 1 << 5, +	LTC4155_ENABLE_BATTERY_CONDITIONER	= 1 << 6, +	LTC4155_DISABLE_INPUT_UVCL			= 1 << 7 +}; + +enum LTC4155Shifts +{ +	LTC4155_SHIFTS_CHARGE_CURRENT_LIMIT	= 4, +	LTC4155_SHIFTS_CHARGE_FLOAT_VOLTAGE	= 2, +	LTC4155_SHIFTS_WALL_PRIORITY		= 7, +	LTC4155_SHIFTS_WALL_SAFETY_TIMER	= 5 +}; + +enum LTC4155Statuses	// LTC4155_REG_STATUS +{ +	LTC4155_LOW_BATTERY		= 1 << 0, +	LTC4155_BOOST_ENABLE	= 1 << 3, +	LTC4155_ID_PIN_DETECT	= 1 << 4, +}; + +enum LTC4155Goods	// LTC4155_REG_GOOD +{ +	LTC4155_BAD_CELL_FAULT		= 1 << 0, +	LTC4155_OTG_FAULT			= 1 << 1, +	LTC4155_OVP_ACTIVE			= 1 << 2, +	LTC4155_INPUT_UVCL_ACTIVE	= 1 << 3, +	LTC4155_INPUT_CURRENT_LIMIT_ACTIVE = 1 << 4, +	LTC4155_WALLSNS_GOOD		= 1 << 5, +	LTC4155_USBSNS_GOOD			= 1 << 6, +	LTC4155_EXTERNAL_POWER_GOOD	= 1 << 7 +}; + +enum LTC4155BatteryChargerStatues +{ +	LTC4155_CHARGER_OFF, +	LTC4155_CHARGER_LOW_BATTERY_VOLTAGE, +	LTC4155_CHARGER_CONSTANT_CURRENT, +	LTC4155_CHARGER_CONSTANT_VOLTAGE_VPROG_GT_VCX, +	LTC4155_CHARGER_CONSTANT_VOLTAGE_VPROG_LT_VCX, +	LTC4155_CHARGER_NTC_TOO_WARM, +	LTC4155_CHARGER_NTC_TOO_COLD, +	LTC4155_CHARGER_NTC_HOT +}; + +enum LTC4155ThermistorStatuses +{ +	LTC4155_NTC_NORMAL, +	LTC4155_NTC_TOO_COLD, +	LTC4155_NTC_TOO_WARM, +	LTC4155_NTC_FAULT +}; + +static const uint8_t _ltc4155_interrupt_mask = +//	LTC4155_ENABLE_USB_OTG |// Enable +5V on USB connector	// Is this causing the chip to power off the output?! +	LTC4155_INT_UVCL | +	LTC4155_INT_ILIMIT | +	LTC4155_INT_USB_OTG | +	LTC4155_INT_EXT_PWR |	// Turn up current limit +	LTC4155_INT_FAULT |		// Blink error +	LTC4155_INT_CHARGER;	// Illuminate charge LED + +static bool _ltc4155_clear_irq(void) +{ +	return i2c_write_ex(CHRG_SDA, CHRG_SCL, LTC4155_WRITE_ADDRESS, LTC4155_REG_ENABLE, _ltc4155_interrupt_mask, _ltc4155_pull_up); +} + +bool ltc4155_clear_irq(void) +{ +	pmc_mask_irqs(true); +	 +	bool result = _ltc4155_clear_irq(); +	 +	pmc_mask_irqs(false); +	 +	return result; +} + +static uint8_t _ltc4155_last_good, _ltc4155_last_status; + +bool _ltc4155_handle_irq(void) +{ +	_ltc4155_clear_irq();	// Clear frozen registers to get the real-time ones +	 +	_delay_ms(50);	// Wait for registers to clear/update +	 +	////////////////// +	 +	uint8_t val = 0x00; +	bool result = false; +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_GOOD, &val, _ltc4155_pull_up) == false) +		goto _ltc4155_handle_fail; +	 +	//if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_GOOD, &val, _ltc4155_pull_up) == false) +	//	goto _ltc4155_handle_fail; +	 +	debug_log_ex("4155GO ", false); +	debug_log_hex(val); +	 +	if (val & LTC4155_WALLSNS_GOOD) +	{ +		uint8_t wall_state = 0; +		if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_WALL, &wall_state, _ltc4155_pull_up) == false) +			goto _ltc4155_handle_fail; +		 +		wall_state &= ~0x1E; +		wall_state |= 0x0E; +		 +		if (i2c_write_ex(CHRG_SDA, CHRG_SCL, LTC4155_WRITE_ADDRESS, LTC4155_REG_WALL, wall_state, _ltc4155_pull_up) == false) +			goto _ltc4155_handle_fail; +		 +		debug_log("I+"); +	} +	 +	_ltc4155_last_good = val; +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_STATUS, &val, _ltc4155_pull_up) == false) +		goto _ltc4155_handle_fail; +	 +	//if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_STATUS, &val, _ltc4155_pull_up) == false) +	//	goto _ltc4155_handle_fail; +	 +	debug_log_ex("4155ST ", false); +	debug_log_hex(val); +	 +	_ltc4155_last_status = val; +	 +	val >>= 5; +	 +	if (_state.blink_error == BlinkError_None) +	{ +		switch (val) +		{ +			case LTC4155_CHARGER_CONSTANT_CURRENT: +			case LTC4155_CHARGER_CONSTANT_VOLTAGE_VPROG_GT_VCX: +			case LTC4155_CHARGER_LOW_BATTERY_VOLTAGE:	// If this persists for more than 1/2hr, BAD_CELL_FAULT is enabled and FAULT interrupt is generated +			{ +				if ((_state.battery_not_present == false) && +					(_ltc4155_last_good & (LTC4155_WALLSNS_GOOD | LTC4155_USBSNS_GOOD))) +				{ +					//charge_set_led(true); +					charge_notify(true); +					break; +				}						 +			} +			case LTC4155_CHARGER_CONSTANT_VOLTAGE_VPROG_LT_VCX:	// Small amount of current still charging the battery but below Vc/x threshold +			//case LTC4155_CHARGER_NTC_TOO_WARM: +			//case LTC4155_CHARGER_NTC_TOO_COLD: +			//case LTC4155_CHARGER_NTC_HOT: +			//	break; +			//case LTC4155_CHARGER_OFF: +			default: +				//charge_set_led(false); +				charge_notify(false); +		} +	} +	 +//	ltc4155_dump(); +	 +	result = true; +_ltc4155_handle_fail: +	_ltc4155_clear_irq();	// Even though it happens first above, this is necessary otherwise future IRQs won't be detected +	 +	return result; +} + +#define LTC4155_CHARGE_CURRENT_LIMIT	/*0xF*/0x7	// [100%] 50% + +bool ltc4155_set_charge_current_limit(uint8_t percentage) +{ +	uint8_t val = 0; +	uint8_t limit = 0; +	 +	if (percentage > 100) +		return false; +	else if (percentage == 100) +		percentage = 0xF; +	else if (percentage > 12)	// 0..88 -> 0..8800 +	{ +		uint16_t l = (((uint16_t)percentage - 12) * 100) / 586; +		limit = (uint8_t)l; +	} +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_WRITE_ADDRESS, LTC4155_REG_CHARGE, &val, _ltc4155_pull_up) == false) +		return false; +	 +	val &= ((0x1 << LTC4155_SHIFTS_CHARGE_CURRENT_LIMIT) - 1); +	//val |= (LTC4155_CHARGE_CURRENT_LIMIT << LTC4155_SHIFTS_CHARGE_CURRENT_LIMIT); +	val |= (limit << LTC4155_SHIFTS_CHARGE_CURRENT_LIMIT); +	 +	if (i2c_write_ex(CHRG_SDA, CHRG_SCL, LTC4155_WRITE_ADDRESS, LTC4155_REG_CHARGE, val, _ltc4155_pull_up) == false) +		return false; +	 +//ltc4155_dump(); +	 +	return true; +} + +bool ltc4155_init(bool disable_charger) +{ +	io_input_pin(USBPM_IRQ); +#if !defined(DEBUG) && !defined(ATTINY88_DIP) +	io_set_pin(USBPM_IRQ);	// Enable pull-up for Open Drain +#endif // DEBUG +#ifdef I2C_REWORK +	i2c_init_ex(CHRG_SDA, CHRG_SCL, _ltc4155_pull_up); +#endif // I2C_REWORK +	if (/*_ltc4155_clear_irq()*/_ltc4155_handle_irq() == false)	// Will set interrupt masks	// FIXME: Why does this cause instability?! +		return false; + +	const uint8_t charge_state = +		(disable_charger ? 0x0 : LTC4155_CHARGE_CURRENT_LIMIT) << LTC4155_SHIFTS_CHARGE_CURRENT_LIMIT |	// Battery charger I limit = 100% +		0x3 << LTC4155_SHIFTS_CHARGE_FLOAT_VOLTAGE |	// FIXME: Vbatt float = 4.05V - 4.2V for LiPo (default 0x00) +		0x0;	// Full capacity charge threshold = 10% +	if (i2c_write_ex(CHRG_SDA, CHRG_SCL, LTC4155_WRITE_ADDRESS, LTC4155_REG_CHARGE, charge_state, _ltc4155_pull_up) == false) +		return false; + +	const uint8_t wall_state = +		0x0 << LTC4155_SHIFTS_WALL_PRIORITY | +		0x0 << LTC4155_SHIFTS_WALL_SAFETY_TIMER |	// Charge safety timer = 4hr	// FIXME: 8hr or Vc/x +		0xE;	// 3 amps, 0x1F - CLPROG1 +	if (i2c_write_ex(CHRG_SDA, CHRG_SCL, LTC4155_WRITE_ADDRESS, LTC4155_REG_WALL, wall_state, _ltc4155_pull_up) == false) +		return false; + +	// FIXME: +	// Disable ID pin detection & autonomous startup +	// Enable OTG +	//i2c_write_ex(CHRG_SDA, CHRG_SCL, LTC4155_WRITE_ADDRESS, LTC4155_REG_USB, LTC4155_USB_OTG_LOCKOUT, _ltc4155_pull_up);	// Disable autonomous startup +	//i2c_write_ex(CHRG_SDA, CHRG_SCL, LTC4155_WRITE_ADDRESS, LTC4155_REG_ENABLE, LTC4155_ENABLE_USB_OTG, _ltc4155_pull_up);	// Enable OTG +	 +	if (_ltc4155_handle_irq() == false)	// One more time (IRQ LED stays lit in dev setup) +		return false; +	 +	return true; +} + +bool ltc4155_has_interrupt(void) +{ +	//bool state = io_test_pin(USBPM_IRQ); +	//debug_log_ex("4155IRQ", false); +	//debug_log_byte(state); +	//return (state != 1); +	return (io_test_pin(USBPM_IRQ) == false); +} + +bool ltc4155_handle_irq(void) +{ +	pmc_mask_irqs(true); +	 +	bool result = _ltc4155_handle_irq(); +	 +	pmc_mask_irqs(false); +	 +	return result; +} + +bool ltc4155_arm_ship_and_store(void) +{ +	return true; +} + +bool ltc4155_get_thermistor(uint8_t* val, bool* warning) +{ +	bool result = false; +	uint8_t _val = 0; +	 +	pmc_mask_irqs(true); +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_THERMISTOR, &_val, _ltc4155_pull_up) == false) +		goto ltc4155_get_thermistor_fail; +	 +	if (val) +		(*val) = _val >> 1; +	 +	if (warning) +		(*warning) = ((_val & 0x01) != 0x00); +	 +	result = true; +ltc4155_get_thermistor_fail: +	pmc_mask_irqs(false); +	return result; +} + +void ltc4155_dump(void) +{ +	pmc_mask_irqs(true); +	 +	uint8_t val = 0x00; +	bool warning = false; +	 +	if (ltc4155_get_thermistor(&val, &warning) == false) +		goto ltc4155_dump_fail; +	 +	debug_log_ex("\tTHRM", false); +	if (warning) +		debug_log_ex("!", false); +	debug_log_byte(val); +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_WALL, &val, _ltc4155_pull_up) == false) +		goto ltc4155_dump_fail; +	 +	debug_log_ex("\tWALL", false); +	debug_log_hex(val); +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_GOOD, &val, _ltc4155_pull_up) == false) +		goto ltc4155_dump_fail; +	 +	debug_log_ex("\t4155GO ", false); +	debug_log_hex(val); +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_STATUS, &val, _ltc4155_pull_up) == false) +		goto ltc4155_dump_fail; +	 +	debug_log_ex("\t4155ST ", false); +	debug_log_hex(val); +	 +ltc4155_dump_fail: +	pmc_mask_irqs(false); +} + +#endif // !CHARGER_TI diff --git a/firmware/e300/rev_b/ltc4155.h b/firmware/e300/rev_b/ltc4155.h new file mode 100644 index 000000000..7e8e3751d --- /dev/null +++ b/firmware/e300/rev_b/ltc4155.h @@ -0,0 +1,25 @@ +/* + * ltc4155.h + * + * Created: 17/08/2012 8:09:43 PM + *  Author: Balint Seeber + */  + + +#ifndef LTC4155_H_ +#define LTC4155_H_ + +#include <stdbool.h> +#include <stdint.h> + +#ifndef CHARGER_TI + +bool ltc4155_init(bool disable_charger); +bool ltc4155_has_interrupt(void); +bool ltc4155_handle_irq(void); +void ltc4155_dump(void); +bool ltc4155_set_charge_current_limit(uint8_t percentage); + +#endif // !CHARGER_TI + +#endif /* LTC4155_H_ */ diff --git a/firmware/e300/rev_b/main.c b/firmware/e300/rev_b/main.c new file mode 100644 index 000000000..1e065ffef --- /dev/null +++ b/firmware/e300/rev_b/main.c @@ -0,0 +1,385 @@ +/* + * Copyright 2009 Ettus Research LLC + */ + +#include "config.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <avr/io.h> +#include <util/delay.h> +#include <avr/sleep.h> +#include <avr/interrupt.h> + +#include "global.h" +#include "power.h" +#include "debug.h" +#include "error.h" +#include "ltc3675.h" +#ifdef CHARGER_TI +#include "bq24190.h" +#else +#include "ltc4155.h" +#endif // CHARGER_TI + +#define AUTO_POWER_ON + +#define INITIAL_DELAY	250	// ms + +FUSES = {	// FIXME: & FUSE_CKSEL1 for low power 128 kHz clock +	.low = (FUSE_CKSEL0 & FUSE_SUT0 & FUSE_CKDIV8),	// Internal 8MHz Oscillator, Slowly rising power (start-up time), Divide Clock by 8 +	.high = (FUSE_EESAVE & FUSE_SPIEN),	// Save EEPROM between flashes	// FIXME: Leave SPIEN for programming enabled? +};	// Not using watchdog as it runs during sleep and consumes power + +volatile STATE _state; + +/* +    - Main/shared variables must be volatile +	- Port pins are tri-stated on reset +	* AVR_IRQ PD(5) +	- Enable pull-ups on all O.D. outputs from regulator chip +	* PS_POR/SRST should be driven HIGH by ATTiny? +	- AVR_RESET -> RESET pin - don't configure fuse (this would disable this functionality and prohibit serial programming) +	* Ship-and-store mode for charge controller? +	* cli before I2C calls +	* PS_TX +	- en5-clk, en2-data +	* Instruction following SEI is executed before interrupts +	* LTC3675 real-time status doesn't contain UV/OT +	* LTC3675 PGOOD -> power down (no point in checking blink state) +	* On WALL, use TX, on battery use OTG switcher +	* PRR - Power Reduction Register (p40) +	- 100% -> 50% battery charge limit +	* Check latched status for UV/OT in 3675 +	* If blink error reset, get latest charge status from 4155 +	* Fix UV status check from 3675/4155 as they currently share the same error  +	* Use charger termination at 8hr or Vc/x +	* Check PGood on all regs after power on before setting powered=true +	* Re-init 4155 on soft-power on +	- Re-set 3A limit in 4155 after external power connection +	- Removing power when running on battery, 4155GO 0xA0 - but WALL has been removed +	- Why is charger reporting Constant Current when power is removed +	* ltc3675_is_power_button_depressed: check if any reg is on, otherwise value will be invalid +	* When e.g. 3.3V doesn't come up, blink code is correctly 4 but there's a very short blink before re-starting the sequence +	- Vprog<Vc/x +*/ + +bool pmc_mask_irqs(bool mask) +{ +	if (_state.interrupts_enabled == false) +		return false; +	 +	if (mask) +	{ +		if (_state.interrupt_depth == 0) +			cli(); +		++_state.interrupt_depth; +	}		 +	else +	{ +		if (_state.interrupt_depth == 0) +			return false; +		 +		--_state.interrupt_depth; +		if (_state.interrupt_depth == 0) +			sei(); +	} +	 +	return true; +} + +int main(void) +{ +	_delay_ms(INITIAL_DELAY); +	 +	/////////////////////////////////////////////////////////////////////////// +	 +	memset((void*)&_state, 0x00, sizeof(STATE)); +	 +	debug_init(); +	debug_blink(1); +	 +	//debug_log("#");	// Will not boot if this is 21 chars long?! +	debug_log("Hello world"); +	 +    set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // SLEEP_MODE_PWR_SAVE combination is documented as Reserved +	 +//ltc4155_dump(); + +    // FIXME: Init as SPI slave (FPGA is master) +	 +	// 8-bit timer for blinking errors on charge LED +	TCCR0A = _BV(CTC0);		// CTC mode +	OCR0A = 244;			// 250ms with 1024 prescale +	TIMSK0 = _BV(OCIE0A);	// Enable CTC on Timer 0 + +	bool init_result = power_init(); +	debug_log_ex("Init", false); +	_debug_log(init_result ? "+" : "-"); +	debug_blink(2); +	//debug_blink_rev(6); +	 +	/////////////////////////////////// +#ifdef AUTO_POWER_ON +	power_on(); // Turn on immediately. Need to de-press power button to turn off. +	debug_log("Power"); +	debug_blink(3); +	//debug_blink_rev(10); + +	//debug_wait(); +	 +//ltc4155_dump(); +#endif // AUTO_POWER_ON +	_state.interrupts_enabled = true; +	sei();	// Enable interrupts + +	asm("nop"); + +	_state.wake_up = false;	// This will fire the first time the regs are turned on +	 +	bool one_more = false; +	 +	while (true) +	{ +		one_more = false; +#ifdef CHARGER_TI +		if (_state.bq24190_irq) +		{ +			bq24190_handle_irq(); +			 +			_state.bq24190_irq = false; +		} +#else +		if ((_state.ltc4155_irq)/* || ltc4155_has_interrupt()*/)	// [Don't know why PCINT ISR misses LTC4155 IRQ on power up, so double-check state of line] +		{ +			ltc4155_handle_irq(); +//ltc4155_dump(); +			_state.ltc4155_irq = false; +		} +#endif // !CHARGER_TI +		if (_state.core_power_bad)	// FIXME: Check whether it's supposed to be on +		{ +			if (power_is_subsys_on(PS_FPGA)) +			{ +				_delay_ms(1);	// Seeing weird 120us drop in PGOOD during boot from flash (no apparent drop in 1.0V though) +				 +				if (tps54478_is_power_good() == false) +				{ +					debug_log("ML:FPGA!"); +			 +					//power_off(); +					_state.power_off = true; +			 +					/*while (_state.wake_up == false) +					{ +						blink_error_sequence(1); +					}*/ +					pmc_set_blink_error(BlinkError_FPGA_Power);	// [If a blink error was set in power_off, this will supercede it] +				} +			}			 +			 +			_state.core_power_bad = false; +		} +		 +		if ((_state.ltc3675_irq)/* || ltc3675_has_interrupt()*/)	// This is fired on initial power up +		{ +			debug_log("ML:3675+"); +			 +			ltc3675_handle_irq(); +			 +			if (ltc3675_is_power_good(ltc3675_get_last_status()) == false) +			{ +				debug_log("ML:3675!"); +				 +				//power_off(); +				_state.power_off = true; +			} +			 +			_state.ltc3675_irq = false; +		} +		 +		if (_state.power_off) +		{ +			debug_log("ML:Off.."); +			 +			power_off(); +			 +			_state.power_off = false; +			_state.wake_up = false; +		} +		else if (_state.wake_up) +		{ +			_delay_ms(1);	// Tapping 3.1 ohm load ing 4155 in dev setup causes transient on this line and causes power on sequence to begin again +			 +			//if (_state.powered == false)	// Don't check in case button is held long enough to force LTC3675 shutdown (will not change 'powered' value) +			if (ltc3675_is_waking_up()) +			{ +				debug_log("ML:On.."); +				 +				power_on(); +			} +			 +			_state.wake_up = false; +		} +		 +		// Check to see if the error state has resolved itself at the end of each sequence of the current blink error +		 +		if ((_state.blink_error != BlinkError_None) && (_state.blink_last_loop != _state.blink_loops)) +		{ +			// [Check IRQs periodically] +			 +			bool ltc3675_use_last_status = false; +			/*if (ltc3675_has_interrupt()) +			{ +				//debug_set(IO_PB(6), ((_state.blink_loops % 2) == 0)); +				ltc3675_use_last_status = true; +				ltc3675_handle_irq(); +			}*/ +			 +			/////////////////////////// +			 +			switch (_state.blink_error) +			{ +				case BlinkError_LTC3675_UnderVoltage: +				case BlinkError_LTC3675_OverTemperature: +				case BlinkError_DRAM_Power: +				case BlinkError_3_3V_Peripherals_Power: +				case BlinkError_1_8V_Peripherals_Power: +				case BlinkError_TX_Power: +					if (((ltc3675_use_last_status) && (ltc3675_status_to_error(ltc3675_get_last_status()) != BlinkError_None)) ||  +						((ltc3675_use_last_status == false) && (ltc3675_check_status() != BlinkError_None))) +						break; +					debug_log("BE:3675-"); +					goto cancel_blink_error; +				case BlinkError_FPGA_Power: +					if (tps54478_is_power_good() == false) +						break; +					debug_log("BE:FPGA-"); +					goto cancel_blink_error; +				default: +cancel_blink_error:				 +					//debug_set(IO_PB(7), true); +					pmc_set_blink_error(BlinkError_None); +			} +			 +			//////////////////////////////////// +			 +			// More periodic checks +			// Need to do this has some interrupts are on PCINT, and while GIE is disabled, might change & change back +			//	E.g. LTC3675 IRQ due to UV, reset IRQ, re-asserts UV +#ifndef CHARGER_TI +			if (ltc4155_has_interrupt()) +			{ +				debug_log("BE:4155"); +				 +				_state.ltc4155_irq = true; +				one_more = true; +			} +#endif // !CHARGER_TI +			if (ltc3675_has_interrupt()) +			{ +				debug_log("BE:3675"); +				 +				_state.ltc3675_irq = true; +				one_more = true; +			} +			 +			if (power_is_subsys_on(PS_FPGA)) +			{ +				if (tps54478_is_power_good() == false) +				{ +					debug_log("BE:FPGA!"); +				 +					_state.core_power_bad = true; +					one_more = true; +				} +			} +			 +			//////////////////////////////////// +			 +			_state.blink_last_loop = _state.blink_loops; +		} +		 +		//if (_state.timers_running == false) +		if ((_state.active_timers == 0) && (one_more == false)) +		{ +			debug_log("^"); +			sleep_mode(); +			debug_log("$"); +		}			 +	} + +	return 0; +} + +uint8_t pmc_get_blink_error(void) +{ +	return _state.blink_error; +} + +void pmc_set_blink_error(uint8_t count) +{ +	if ((_state.blink_error != BlinkError_None) && (count /*> _state.blink_error*/!= BlinkError_None))	// [Prioritise] Always keep first sequence running +		return; +	else if (_state.blink_error == count)	// Don't restart if the same +		return; +	 +	if (count == BlinkError_None) +	{ +		debug_log("BLNK-"); +		_state.blink_stop = true; +		return; +	} +	 +	//char msg[25]; +	//sprintf(msg, "Blink code = %i\n", count); +	//debug_log(msg); +	debug_log_ex("BLNK ", false); +	debug_log_byte(count); +	 +	_state.blink_error = count; +	_state.blink_loops = 0; +	_state.blink_last_loop = 0; +	_state.blinker_state = 0; +	_state.blink_stop = false; + +	charge_set_led(false); +	 +	TCNT0 = 0; +	if ((TCCR0A & 0x07) == 0x00)	// Might already be active with existing error +		_state.active_timers++; +	TCCR0A |= 0x05;	// Start with 1024 prescale +} + +ISR(TIMER0_COMPA_vect)	// Blink the sequence, and leave one slot at the beginning and end where the LED is off so one can get a sense of how many blinks occurred +{ +	pmc_mask_irqs(true); +	 +	if (_state.blinker_state < (2 * _state.blink_error + 1)) +		charge_set_led((_state.blinker_state % 2) == 1); +	 +	_state.blinker_state++; +	 +	if (_state.blinker_state == (2 * _state.blink_error + 1 + 1)) +	{ +		_state.blinker_state = 0; +		 +		if (_state.blink_stop) +		{ +			if ((TCCR0A & 0x07) != 0x00) +				_state.active_timers--; +			TCCR0A &= ~0x07; +			 +			_state.blink_error = BlinkError_None; +			 +			debug_log("BLNK."); +		} +		else +		{ +			_state.blink_loops++; +		} +	} +	 +	pmc_mask_irqs(false); +} diff --git a/firmware/e300/rev_b/power.c b/firmware/e300/rev_b/power.c new file mode 100644 index 000000000..79864f817 --- /dev/null +++ b/firmware/e300/rev_b/power.c @@ -0,0 +1,909 @@ +/* +	* Test battery voltage code +	* Charger error blinking uses busy wait - will drain battery if encounters error while unattended? Surely rest of H/W will pull more current. +*/ +#include "config.h" +#include "power.h" + +#include <string.h> +#include <util/delay.h> +#include <avr/io.h> +#include <avr/interrupt.h> +#include <avr/sleep.h> + +#include "io.h" +#include "i2c.h" +#include "ltc3675.h" +#include "ltc4155.h" +#include "bq24190.h" +#include "debug.h" +#include "global.h" +#include "error.h" + +#define BLINK_ERROR_DELAY		250  // ms + +#define POWER_DEFAULT_DELAY     50  // ms +#define POWER_DEFAULT_RETRIES   10 + +#define BATT_MIN_VOLTAGE		2000	// mV + +#define ARRAY_SIZE(a)			(sizeof(a)/sizeof(a[0])) +#define ZERO_MEMORY(s)			memset(&s, 0x00, sizeof(s)) + +#ifndef I2C_REWORK +io_pin_t PWR_SDA     = IO_PC(4); +io_pin_t PWR_SCL     = IO_PC(5); + +io_pin_t USBHUB_RESET= IO_PA(2); +#endif // I2C_REWORK + +//volatile bool powered = false; + +#ifdef DDR3L +#define DRAM_VOLTAGE	1350 +#else +#define DRAM_VOLTAGE	0	// Hardware default +#endif // DDR3 + +struct reg_config { +    int16_t voltage;    // mV +	uint8_t device; +    uint8_t address;    // Device specific +    bool powered; +} default_reg_config[] = {        // Index maps to 'power_subsystem_t', 0 volts means leave at hardware default +	{ 0000, REG_UNKNOWN, 0/*, true*/ },				// PS_UNKNOWN +	{ 1000, REG_TPS54478, 0/*, true*/ },			// PS_FPGA +	{ DRAM_VOLTAGE, REG_LTC3675, LTC3675_REG_1 },	// PS_VDRAM +	{ /*1800*/0, REG_LTC3675, LTC3675_REG_3 },		// PS_PERIPHERALS_1_8 +	{ /*3300*/0, REG_LTC3675, LTC3675_REG_6 },		// PS_PERIPHERALS_3_3 +	{ /*5000*/0, REG_LTC3675, LTC3675_REG_5 }		// PS_TX +}; +/* +int8_t power_get_regulator_index(uint8_t device, uint8_t address) +{ +	for (int8_t i = 0; i < ARRAY_SIZE(default_reg_config); ++i) +	{ +		struct reg_config* reg = default_reg_config + i; +		if ((reg->device == device) && (reg->address == address)) +			return i; +	} +	 +	return -1; +} +*/ +bool power_is_subsys_on(power_subsystem_t index) +{ +	if ((index <= PS_UNKNOWN) || (index >= PS_MAX)) +		return false; +	 +	return default_reg_config[index].powered; +} + +static bool ltc3675_reg_helper(uint8_t address) +{ +	for (int8_t i = 0; i < ARRAY_SIZE(default_reg_config); ++i) +	{ +		struct reg_config* reg = default_reg_config + i; +		if ((reg->device == REG_LTC3675) && (reg->address == address)) +			return reg->powered; +	} +#ifdef DEBUG_SAFETY +	debug_log_ex("!3675HLP ", false); +	debug_log_hex(address); +#endif // DEBUG_SAFETY +	return false; +	//return power_is_subsys_on(power_get_regulator_index(REG_LTC3675, address) - 1); +} + +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); + +#ifndef ATTINY88_DIP +static io_pin_t FTDI_BCD    = IO_PB(6); +static io_pin_t FTDI_PWREN2 = IO_PB(7); +#endif // ATTINY88_DIP + +static io_pin_t AVR_RESET   = IO_PC(6); +static io_pin_t AVR_IRQ     = IO_PD(5); + +/////////////////////////////////////////////////////////////////////////////// + +#define TPS54478_START_DELAY	10	// 50 (safety)	// 3 (per spec)	// ms (some arbitrary value so that the external power supply can settle) + +#ifdef ATTINY88_DIP +static io_pin_t CORE_PWR_EN = IO_PC(1);	// IO_PC(7) not routed by card, using PWER_EN1 instead +#else +static io_pin_t CORE_PWR_EN = IO_PA(3); +#endif // ATTINY88_DIP +static io_pin_t CORE_PGOOD = IO_PB(0); + +void tps54478_init(bool enable) +{ +	tps54478_set_power(enable); +	io_clear_pin(CORE_PWR_EN); +	 +    io_input_pin(CORE_PGOOD); +#if !defined(DEBUG) && !defined(ATTINY88_DIP)	// Don't enable pull-up when connected to a pulled-up switch +	io_set_pin(CORE_PGOOD);	// Enable pull-up for Open Drain +#endif // DEBUG +//#ifdef DEBUG +//	io_enable_pin(CORE_PWR_EN, false); +//#endif // DEBUG +//_delay_ms(2500); +} + +void tps54478_set_power(bool on) +{ +	debug_log_ex("54478", false); +	 +	// Assumes: Hi-Z input/LOW output +	 +	if (on) +	{ +		io_input_pin(CORE_PWR_EN); +		_delay_ms(TPS54478_START_DELAY); +		 +		debug_log("+"); +	}		 +	else +	{ +		io_output_pin(CORE_PWR_EN); +		// Don't delay here as we can't detect its state anyway +		 +		debug_log("-"); +	}		 +	 +	//io_enable_pin(CORE_PWR_EN, on); +} + +bool tps54478_is_power_good(void) +{ +    return io_test_pin(CORE_PGOOD);	// This doesn't necessarily mean it's good - the chip might be malfunctioning (or switched off) +} + +/////////////////////////////////////////////////////////////////////////////// + +static io_pin_t CHARGE      = IO_PD(1); + +#if !defined(ATTINY88_DIP) && defined(LED_POLARITY) +static io_pin_t POWER_LED	= IO_PC(7); + +void power_set_led_ex(bool on, bool swap) +{ +	if (swap) +	{ +		if ((on == false) && (/*io_is_pin_set(CHARGE)*/_state.battery_charging))	// If charging and turning off, don't change charge light +		{ +			charge_set_led(true);	// Force it again just in case +			return; +		} +	}		 +	 +	io_clear_pin(CHARGE); +	io_enable_pin(POWER_LED, on); +} + +void power_set_led(bool on) +{ +	power_set_led_ex(on, true); +} +#endif // !ATTINY88_DIP && LED_POLARITY + +void charge_set_led_ex(bool on, bool swap) +{ +#ifdef ATTINY88_DIP +	// +#else + +#ifdef LED_POLARITY +	io_clear_pin(POWER_LED); +#endif // LED_POLARITY + +#endif // ATTINY88_DIP + +#ifdef ATTINY88_DIP +	io_enable_pin(CHARGE, !on); +#else +    io_enable_pin(CHARGE, on); + +#ifdef LED_POLARITY +	if (swap) +	{ +		if ((on == false) && (_state.powered))	// If no longer charging, turn power light back on +			power_set_led(true); +	}			 +#endif // LED_POLARITY + +#endif // ATTINY88_DIP +} + +void charge_set_led(bool on) +{ +	charge_set_led_ex(on, true); +} + +void charge_notify(bool charging) +{ +	_state.battery_charging = charging; +	 +	charge_set_led(charging); +} + +/////////////////////////////////////////////////////////////////////////////// + +void usbhub_reset(void) +{ +#ifndef I2C_REWORK +	io_clear_pin(USBHUB_RESET); +	 +	_delay_us(1 * 10);	// Minimum active low pulse is 1us +	 +	io_set_pin(USBHUB_RESET); +#endif // I2C_REWORK +} + +/////////////////////////////////////////////////////////////////////////////// + +void power_signal_interrupt(void) +{ +    io_set_pin(AVR_IRQ);	// FIXME: Active low? +} + +/////////////////////////////////////////////////////////////////////////////// + +#if !defined(DEBUG) && !(defined(ENABLE_SERIAL) && defined(ATTINY88_DIP)) +static io_pin_t PS_POR      = IO_PD(6); +#define PS_POR_AVAILABLE +#endif // DEBUG +static io_pin_t PS_SRST     = IO_PD(7); + +#define FPGA_RESET_DELAY    10  // ms   // MAGIC + +void fpga_reset(bool delay) +{ +#ifdef PS_POR_AVAILABLE +    io_clear_pin(PS_POR); +#endif // PS_POR_AVAILABLE +    io_clear_pin(PS_SRST); + +    if (delay) +        _delay_ms(FPGA_RESET_DELAY); +#ifdef PS_POR_AVAILABLE +    io_enable_pin(PS_POR, true); +#endif // PS_POR_AVAILABLE +    io_enable_pin(PS_SRST, true); +} + +/////////////////////////////////////////////////////////////////////////////// + +static io_pin_t VBAT        = IO_PC(0); + +void battery_init(void) +{ +    //io_input_pin(VBAT); +    DIDR0 |= 0x1;           // Digital input disable PC0 (ADC0) + +    ADMUX = (1 << REFS0)    // AVcc reference +          | (0 << ADLAR)    // Left-aligned result +          | (0 << MUX0);    // ADC0 + +    ADCSRA = (0x7 << ADPS0);// Prescale clock by 128 +} + +uint16_t battery_get_voltage(void) +{ +    // Vout = (357k / (274k + 357k)) * Vbat +    // Vbat = (Vout * (274k + 357k)) / 357k + +    // ADC = (Vin * 1024) / Vref +    // Vin = (ADC * Vref) / 1024 +    // Vref = 3.3 + +    // Vbat(mV) = 1000 * (((ADC * 3.3) / 1024) * (274k + 357k)) / 357k +    // Vbat(mV) ~= ADC * 5.70 + +    ADCSRA |= (1 << ADEN);        // FIXME: Turn on ADC (or leave on all the time?) + +    ADCSRA |= (1 << ADSC);  // Start conversion + +    while (ADCSRA & (1 << ADSC));   // Wait for End of Conversion + +    /*uint16_t*/uint32_t voltage = (ADCH << 8) | (ADCL << 0); +#ifdef ATTINY88_DIP +	voltage = (voltage * 32227) / 10000;	// ~3.22265625 +#else +    voltage = (voltage * 56961) / 10000;	// ~5.69606748 +#endif // ATTINY88_DIP +    ADCSRA &= ~(1 << ADEN);         // FIXME: Turn off ADC (or leave on all the time?) + +    return (uint16_t)voltage; +} + +/////////////////////////////////////////////////////////////////////////////// + +void blink_error_sequence(uint8_t len) +{ +    charge_set_led(false); +    _delay_ms(BLINK_ERROR_DELAY * 2); + +    for (; len > 0; len--) { +        charge_set_led(true); +        _delay_ms(BLINK_ERROR_DELAY); +        charge_set_led(false); +        _delay_ms(BLINK_ERROR_DELAY); +    } + +    //for (len = 2; len > 0; len--)   // Could have *2 on delay, but so as never to overflow 8-bit argument +    //    _delay_ms(BLINK_ERROR_DELAY); +} + +typedef struct power_params { +    power_subsystem_t subsys; +    bool enable; +    uint8_t retry; +    //uint16_t opaque; +} power_params_t; + +static bool _power_up_fpga(power_params_t* params) +{ +    if (params->subsys != PS_FPGA) +        return false; + +    if (params->enable == false) +    { +        //if (tps54478_is_power_good() == false)  // Already off +		//	return true; + +        if (params->retry == 0) +		{ +			io_clear_pin(PS_SRST);	// FIXME: Hold it low to stop +#ifdef PS_POR_AVAILABLE +			io_clear_pin(PS_POR);	// Prepare it for shutdown, and then the potential next power cycle +#endif // PS_POR_AVAILABLE			 +            tps54478_set_power(false); +		} + +        //return (tps54478_is_power_good() == false); +		return true; +    } + +    //bool fpga_power_good = tps54478_is_power_good();  // TODO: Can it ever already be good? + +    if (params->retry == 0) +        tps54478_set_power(true); + +    return tps54478_is_power_good(); +} + +static bool _power_up_reg(power_params_t* params) +{ +    if ((params->subsys > PS_TX) || (params->subsys < PS_VDRAM)) +        return false; + +    struct reg_config* cfg = default_reg_config + params->subsys; + +    if (params->enable == false) +        return ltc3675_enable_reg(cfg->address, false); + +	if (cfg->voltage > 0) +	{ +		if (ltc3675_set_voltage(cfg->address, cfg->voltage) == false) +			return false; +	}	 + +    return ltc3675_enable_reg(cfg->address, true); +} + +static bool _power_enable_subsys(power_params_t* params) +{ +    switch (params->subsys) +    { +        case PS_FPGA: +            return _power_up_fpga(params); +        //case PS_: +        //    break; +        default: +            return _power_up_reg(params); +    } + +    return false;   // Should never get here +} + +bool power_enable(power_subsystem_t subsys, bool on) +{ +    power_params_t params; +    ZERO_MEMORY(params); +    params.subsys = subsys; +    params.enable = on; + +    return _power_enable_subsys(¶ms); +} + +typedef bool (*boot_function_t)(power_params_t*); + +struct boot_step { +    power_subsystem_t subsys; +	//boot_function_t fn; +	//uint8_t delay; +	//uint8_t retries; +    //uint16_t opaque; +	//bool powered; +} boot_steps[] = {  // MAGIC: Retries/delays +	{ PS_FPGA,				/*NULL, POWER_DEFAULT_DELAY, POWER_DEFAULT_RETRIES*/ },	// 7..8						// 3..4 +	{ PS_VDRAM,				/*NULL, POWER_DEFAULT_DELAY, POWER_DEFAULT_RETRIES*/ },	// 9..10					// 5..6 +	{ PS_PERIPHERALS_1_8,	/*NULL, POWER_DEFAULT_DELAY, POWER_DEFAULT_RETRIES*/ },	// 11..12					// 7..8 +	{ PS_PERIPHERALS_3_3,	/*NULL, POWER_DEFAULT_DELAY, POWER_DEFAULT_RETRIES*/ },	// 13..14					// 9..10 +	{ PS_TX,				/*NULL, POWER_DEFAULT_DELAY, POWER_DEFAULT_RETRIES*/ }  // CHECK: Leaving TX off +}; +/* +bool power_is_subsys_on(int8_t index) +{ +	if ((index < 0) || (index >= ARRAY_SIZE(boot_steps))) +		return false; +	 +	struct boot_step* step = boot_steps + index; +	 +	return step->powered; +} +*/ +bool power_init(void) +{ +	io_output_pin(CHARGE); +#ifdef LED_POLARITY +    io_output_pin(POWER_LED); +#endif // LED_POLARITY + +	charge_set_led(true); +	 +	battery_init(); +	 +    tps54478_init(true);	// Will keep EN float (keep power on) +#ifndef I2C_REWORK +	i2c_init(PWR_SDA, PWR_SCL); +	 +	io_output_pin(USBHUB_RESET); +#endif // I2C_REWORK +#ifdef CHARGER_TI +	if (bq24190_init(true) == false) +		return false; +#else +	if (ltc4155_init(/*_state.battery_not_present*/true/*false*/) == false) +		return false; +#endif // CHARGER_TI +#ifdef CHARGER_TI +	_delay_ms(1000);	// Still at 1.4V on dev board +#else +	_delay_ms(25);	// Wait for charge current to stop (Vbatt to fall to 0V) +#endif // CHARGER_TI +	uint16_t batt_voltage = battery_get_voltage(); +	debug_log_ex("Vb ", false); +	debug_log_byte((uint8_t)(batt_voltage / 100)); +	//debug_log_hex_ex(batt_voltage >> 8, false); +	//debug_log_hex(batt_voltage & 0xFF); +	if (batt_voltage < BATT_MIN_VOLTAGE) +	{ +		_state.battery_not_present = true; +		 +		//debug_log("NoBatt"); +	} +	else +	{ +#ifdef CHARGER_TI +		bq24190_toggle_charger(true); +#else +		ltc4155_set_charge_current_limit(50); +#endif // CHARGER_TI +	} + +    if (ltc3675_init(ltc3675_reg_helper) == false) +		return false; +#ifdef PS_POR_AVAILABLE +	io_output_pin(PS_POR); +#endif // PS_POR_AVAILABLE +    io_output_pin(PS_SRST); +    // Hold low until power is stable +#ifdef PS_POR_AVAILABLE +    io_clear_pin(PS_POR); +#endif // PS_POR_AVAILABLE +    io_clear_pin(PS_SRST); +/* +    AVR_CS +    AVR_MOSI +    AVR_MISO +    AVR_SCK + +    FTDI_BCD +    FTDI_PWREN2 +*/ +	io_input_pin(AVR_RESET);	// Has external pull-up (won't do anything because this is configured at the hardware RESET pin) +	 +	//io_output_pin(AVR_IRQ);		// Output here, input to FPGA +    io_input_pin(AVR_IRQ); +	//io_set_pin(AVR_IRQ);	// FIXME: Active low? +	 +	/////////////// +	 +	EICRA = _BV(ISC01) | _BV(ISC00) | _BV(ISC10)/* | _BV(ISC11)*/;	// Rising edge for INT0 (WAKEUP). [Falling for INT1.] Any logical change INT1 (ONSWITCH_DB) +	//EIMSK = _BV(INT0);	// [Turn on WAKEUP interrupt] Don't do this, as unit will turn on anyway +	EIMSK = _BV(INT1) | _BV(INT0);	// Turn on ONSWITCH_DB and WAKEUP +	 +	PCMSK0 = _BV(PCINT1) | _BV(PCINT0);	// USBPM_IRQ | CORE_PGOOD +	PCMSK2 = _BV(PCINT16)/* | _BV(PCINT20)*/;	// PWR_IRQ/* | PWR_RESET*/ +	PCICR = _BV(PCIE2) | _BV(PCIE0); + +	/////////////// +/* +	TCNT0; +	OCR0A = 0x; +	TCCR0A = _BV(CTC0); +	TIFR0; +	TIMSK0; +	TCCR0A |= 0x05;	// Switch on with 1024 prescaler +*/ +	TCCR1B = _BV(WGM12);	// CTC mode +	OCR1A = 15624 * 2;		// Hold button for 2 seconds to switch off +	TIMSK1 = _BV(OCIE1A);	// Enable CTC on Timer 1 +	 +	charge_set_led(false); +	 +	return true; +} + +bool power_on(void) +{ +	pmc_mask_irqs(true); +	 +    //charge_set_led(false); +	 +	bool last_power_led_state = /*true*/false; +	 +	//if ((ARRAY_SIZE(boot_steps) % 2) == 0)	// Should end with 'true' +	//	last_power_led_state = false; +	 +	power_set_led(last_power_led_state); +	 +	uint8_t step_count, retry; +	for (step_count = 0; step_count < ARRAY_SIZE(boot_steps); step_count++) +	{ +		last_power_led_state = !last_power_led_state; +		power_set_led(last_power_led_state); +		 +//		debug_blink(step_count); +//		debug_blink(3 + (step_count * 2) + 0); +//		debug_blink_rev(7 + (step_count * 2) + 0); +		 +	    struct boot_step* step = boot_steps + step_count; +	    if (/*(step->fn == NULL) && */(step->subsys == PS_UNKNOWN)) +            continue; +		 +		debug_log_ex("PWR ", false); +		debug_log_byte_ex(step->subsys, true); + +        power_params_t params; + +	    for (retry = 0; retry < /*step->retries*/POWER_DEFAULT_RETRIES; retry++) +		{ +	        ZERO_MEMORY(params); +            params.subsys = step->subsys; +            params.enable = true; +	        params.retry = retry; + +	        if ((/*(step->fn != NULL) && (step->fn(¶ms))) || +				((step->fn == NULL) && */(_power_enable_subsys(¶ms)))) +			{ +				//step->powered = true; +				default_reg_config[step->subsys].powered = true; +				 +				debug_log("+"); +//				debug_blink(3 + (step_count * 2) + 1); +//				debug_blink_rev(7 + (step_count * 2) + 1); + +//ltc4155_dump(); + +                break; +	        } +			 +			debug_log("?"); + +            if ((retry < /*step->retries*/POWER_DEFAULT_RETRIES)/* && (step->delay > 0)*/) +                _delay_ms(/*step->delay*/POWER_DEFAULT_DELAY); +	    } +		 +//		debug_blink(step_count); + +	    if (retry == /*step->retries*/POWER_DEFAULT_RETRIES) +	        break; +    } + +    if (step_count != ARRAY_SIZE(boot_steps)) +	{ +		debug_log("x"); +		 +		//sei();	// For button press detection + +        /*while (_state.powered == false) { +            blink_error_sequence(step_count + BlinkError_FPGA_Power); +        }*/ +		pmc_set_blink_error(step_count + BlinkError_FPGA_Power); + +		pmc_mask_irqs(false); + +        return false; +    } +	 +	/////////////////////////////////// +	 +	//bool was_hub_in_reset = (io_is_pin_set(USBHUB_RESET) == false); +	 +	usbhub_reset(); +	 +	/*if (was_hub_in_reset) +	{ +		_delay_ms(10); +		usbhub_reset(); +	}*/ +	 +	fpga_reset(false);  // Power has been brought up, so let FPGA run +	 +	/////////////////////////////////// +	 +//	ltc4155_dump(); +	 +	// Turn off WAKEUP interrupt, enable ONSWITCH_DB +	//EIMSK = _BV(INT1); +	 +	_state.powered = true; +//debug_blink_rev(1); +//_delay_ms(1000);	// Wait for FPGA PGOOD to stabilise +	pmc_mask_irqs(false); +	 +	power_set_led(true); +	 +	if (_state.battery_charging) +	{ +		_delay_ms(500*2); +		charge_set_led(true); +	}		 +	 +    return true; +} + +uint8_t power_off(void) +{ +	pmc_mask_irqs(true); +	 +	io_clear_pin(PS_SRST);	// FIXME: Hold it low to stop FPGA running +	 +	bool last_power_led_state = /*false*/true; +	 +	//if ((ARRAY_SIZE(boot_steps) % 2) == 0)	// Should end with 'false' +	//	last_power_led_state = true; +	 +	//power_set_led(last_power_led_state); +	 +	/////////////////////////////////// +	 +	int8_t step_count, retry; +	for (step_count = ARRAY_SIZE(boot_steps) - 1; step_count >= 0; step_count--) +	{ +		last_power_led_state = !last_power_led_state; +		power_set_led(last_power_led_state); +		 +//		debug_blink(step_count); +		 +	    struct boot_step* step = boot_steps + step_count; +	    if (/*(step->fn == NULL) && */(step->subsys == PS_UNKNOWN)) +            continue; + +        power_params_t params; + +	    for (retry = 0; retry < /*step->retries*/POWER_DEFAULT_RETRIES; retry++) +		{ +	        ZERO_MEMORY(params); +            params.subsys = step->subsys; +            params.enable = false; +	        params.retry = retry; +			 +			if ((/*(step->fn != NULL) && (step->fn(¶ms))) || +				((step->fn == NULL) && */(_power_enable_subsys(¶ms)))) +			{ +				//step->powered = false; +				default_reg_config[step->subsys].powered = false; +				break; +			} +			 +            if ((retry < /*step->retries*/POWER_DEFAULT_RETRIES)/* && (step->delay > 0)*/) +                _delay_ms(/*step->delay*/POWER_DEFAULT_DELAY); +	    } +		 +//		debug_blink(step_count); + +	    if (retry == /*step->retries*/POWER_DEFAULT_RETRIES) +	        break; +    } + +    if (step_count != -1) +	{ +		/*pmc_mask_irqs(false); +		 +        while (_state.powered) { +            blink_error_sequence(step_count + BlinkError_FPGA_Power); +        }*/ +		if (pmc_get_blink_error() == BlinkError_None)	// Only set blink error if no existing error +			pmc_set_blink_error(step_count + BlinkError_FPGA_Power); +		 +		pmc_mask_irqs(false); + +        return (step_count + 1); +    } +	 +	/////////////////////////////////// +	 +	// Turn off WAKEUP interrupt, enable ONSWITCH_DB +	//EIMSK = _BV(INT1); + +	_state.powered = false; +	 +	pmc_mask_irqs(false); +	 +	power_set_led_ex(false, false); +	_delay_ms(500*2); +	 +	power_set_led(false);	// Will turn on charger LED if battery is charging +	 +	return 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef DEBUG + +#ifdef ATTINY88_DIP +static io_pin_t DEBUG_1 = IO_PB(6); +static io_pin_t DEBUG_2	= IO_PB(7); +#endif // ATTINY88_DIP + +#endif // DEBUG + +ISR(INT0_vect)	// PD(2) WAKEUP: Rising edge +{ +	//cli(); +	pmc_mask_irqs(true); +	 +	//power_on(); +	debug_log("\nINT0\n"); +	_state.wake_up = true; +	 +	//sei(); +	pmc_mask_irqs(false); +} + +ISR(INT1_vect)	// PD(3) ONSWITCH_DB (PB_STAT): Any change +{ +	//cli(); +	pmc_mask_irqs(true); +	 +	if (ltc3675_is_power_button_depressed()) +	{ +		debug_log("PWRBTN+"); +		 +		TCNT1 = 0; +		if ((TCCR1B & 0x07) == 0x00) +		{ +			_state.active_timers++; +			debug_log("TIMER1+"); +		} +		TCCR1B |= /*0x5*/0x3;	// [1024] 64 prescaler +		//_state.timers_running = true; +		 +		//debug_set(DEBUG_1, true); +		//debug_set(DEBUG_2, false); +	} +	else +	{ +		debug_log("PWRBTN-"); +		 +		//if (TIMSK1 & _BV(OCIE1A))	// If letting go of button and still running, stop timer +		{ +			//TIMSK1 &= ~_BV(OCIE1A); +			if ((TCCR1B & 0x07) != 0x00) +			{ +				_state.active_timers--; +				debug_log("TIMER1-"); +			} +			TCCR1B &= ~0x7;	// Disable timer +			//_state.timers_running = false; +			 +			//debug_set(DEBUG_1, false); +		} +	} +	 +	//sei(); +	pmc_mask_irqs(false); +} + +ISR(TIMER1_COMPA_vect) +{ +	//cli(); +	pmc_mask_irqs(true); +	 +	debug_log("TIMER1"); + +	//TIMSK1 &= ~_BV(OCIE1A);	// Turn off timer +	TCCR1B &= ~0x7;	// Disable timer +	//_state.timers_running = false; +	_state.active_timers--; +	 +	if (_state.powered) +	{ +		debug_log("PWROFF"); +		 +		_state.power_off = true; +	}		 +	 +	//debug_set(DEBUG_2, true); +	 +	//power_off(); +	 +	//sei(); +	pmc_mask_irqs(false); +	 +	//sleep_mode(); +} + +ISR(PCINT0_vect) +{ +	//cli(); +	pmc_mask_irqs(true); +	 +	//debug_log("PCINT0"); +	 +	// CORE_PGOOD +	//	Assert low: power problem -> shutdown +	// USBPM_IRQ +	//	Charge status change? -> update LED +	//	Power problem:	battery -> blink charge LED +	//					major -> shutdown +	 +	if (/*(_state.powered) && */(/*io_test_pin(CORE_PGOOD)*/tps54478_is_power_good() == false)) +	{ +		_state.core_power_bad = true; +	} +#ifdef CHARGER_TI +	if (bq24190_has_interrupt()) +	{ +		_state.bq24190_irq = true; +	} +#else +	if (ltc4155_has_interrupt()) +	{ +		_state.ltc4155_irq = true; +	} +#endif // CHARGER_TI +	//sei(); +	pmc_mask_irqs(false); +} + +ISR(PCINT2_vect) +{ +	//cli(); +	pmc_mask_irqs(true); +	 +	//debug_log("PCINT2"); +	 +	// PWR_IRQ +	//	Regulator problem: shutdown +	// PWR_RESET +	//	Ignored +	 +	if (ltc3675_has_interrupt()) +	{ +		//debug_set(IO_PB(6), true); +		_state.ltc3675_irq = true; +	} +	 +	//sei(); +	pmc_mask_irqs(false); +} diff --git a/firmware/e300/rev_b/power.h b/firmware/e300/rev_b/power.h new file mode 100644 index 000000000..453633414 --- /dev/null +++ b/firmware/e300/rev_b/power.h @@ -0,0 +1,58 @@ +#ifndef POWER_H +#define POWER_H + +#include <stdbool.h> +#include <stdint.h> + +void tps54478_init(bool enable); +void tps54478_set_power(bool on);   // Zynq core power (1.0V for FPGA) +bool tps54478_is_power_good(void); + +void charge_set_led(bool on);		// Here for error blink codes +void charge_notify(bool charging); + +void power_signal_interrupt(void); + +void fpga_reset(bool delay); + +typedef enum power_subsystems { +    PS_UNKNOWN, +    PS_FPGA, +    PS_VDRAM, +    PS_PERIPHERALS_1_8, +    PS_PERIPHERALS_3_3, +    PS_TX, +	PS_MAX +} power_subsystem_t; + +enum Regulators +{ +	REG_UNKNOWN, +	REG_TPS54478, +	REG_LTC3675 +}; + +bool power_enable(power_subsystem_t subsys, bool on); + +void battery_init(void); +uint16_t battery_get_voltage(void);  // mV + +bool power_init(void); +bool power_on(void); +uint8_t power_off(void); + +//bool power_is_subsys_on(int8_t index); +bool power_is_subsys_on(power_subsystem_t index); +//int8_t power_get_regulator_index(uint8_t device, uint8_t address); +//bool ltc3675_reg_helper(uint8_t address); + +void usbhub_reset(void); + +#ifndef I2C_REWORK +#include "io.h" + +extern io_pin_t PWR_SDA; +extern io_pin_t PWR_SCL; +#endif // I2C_REWORK + +#endif // POWER_H diff --git a/firmware/e300/rev_c/Makefile b/firmware/e300/rev_c/Makefile new file mode 100644 index 000000000..0f13dc60b --- /dev/null +++ b/firmware/e300/rev_c/Makefile @@ -0,0 +1,76 @@ +# +# Copyright 2009 Ettus Research LLC +# + +################################################## +# Compiler +################################################## +CC = avr-gcc +OBJCOPY = avr-objcopy +STRIP = avr-strip +OBJDUMP = avr-objdump +SREC = srec_cat +CFLAGS = -Os -std=gnu99 -Wall -fshort-enums -pedantic-errors -Wl,--gc-sections \ +	-Wstrict-prototypes -Wmissing-prototypes -Wcast-align -Wshadow \ +	-DENABLE_SERIAL -DCHARGER_TI -DLED_POLARITY -DDEBUG_VOID +# -DENABLE_SERIAL	: Output serial debug +# -DCHARGER_TI		: Use TI charger (rev B) instead of LTC (rev A) +# -DLED_POLARITY	: Dual-polarity LED on rev B +# -DDDR3L		: Lower DDR voltage (rev B R-divider changed, so disable this to get back into nominal range) +# -DDEBUG_VOID		: Use (void) debug function replacements instead of inline NOOP (use if .text overflows) +# -DI2C_REWORK		: Rev A only +# -DDEBUG		: Enable debug routines (LED blinks, etc) +# -DDEBUG_SAFETY	: Extra debug prints +# -DATTINY88_DIP	: ATTINY88 DIP testing on STK600 +# -DHARDWIRE_ENABLE : LTC3675 dedicated enable lines - don't use + +#-Werror +#-D IO_DEBUG + +################################################## +# Files +################################################## +HDRS = +SRCS =  main.c io.c power.c ltc3675.c i2c.c debug.c bq24190.c +TARGET = main + +################################################## +# Device +################################################## +MMCU = attiny88 +#PROGRAMMER = avrisp2 +PROGRAMMER = stk600 +PORT = usb +AVRDUDE = avrdude -p $(MMCU) -c $(PROGRAMMER) -P $(PORT) + +################################################## +# Global Targets +################################################## +all: $(TARGET).hex + +clean: +	$(RM) *.o *.elf *.hex + +install: all +	$(AVRDUDE) -U flash:w:$(TARGET).hex:i + +################################################## +# 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/rev_c/PMC.atsln b/firmware/e300/rev_c/PMC.atsln new file mode 100644 index 000000000..e9f4ffc79 --- /dev/null +++ b/firmware/e300/rev_c/PMC.atsln @@ -0,0 +1,29 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Atmel Studio Solution File, Format Version 11.00 +Project("{54F91283-7BC4-4236-8FF9-10F437C3AD48}") = "PMC", "PMC.cproj", "{A379D421-4236-44AF-A711-52B7BDA29919}" +EndProject +Global +	GlobalSection(SolutionConfigurationPlatforms) = preSolution +		Debug|AVR = Debug|AVR +		Release (DDR3L)|AVR = Release (DDR3L)|AVR +		Release (DDR3L, Ti)|AVR = Release (DDR3L, Ti)|AVR +		Release (Dev)|AVR = Release (Dev)|AVR +		Release|AVR = Release|AVR +	EndGlobalSection +	GlobalSection(ProjectConfigurationPlatforms) = postSolution +		{A379D421-4236-44AF-A711-52B7BDA29919}.Debug|AVR.ActiveCfg = Debug|AVR +		{A379D421-4236-44AF-A711-52B7BDA29919}.Debug|AVR.Build.0 = Debug|AVR +		{A379D421-4236-44AF-A711-52B7BDA29919}.Release (DDR3L)|AVR.ActiveCfg = Release (DDR3L)|AVR +		{A379D421-4236-44AF-A711-52B7BDA29919}.Release (DDR3L)|AVR.Build.0 = Release (DDR3L)|AVR +		{A379D421-4236-44AF-A711-52B7BDA29919}.Release (DDR3L, Ti)|AVR.ActiveCfg = Release (DDR3L, Ti)|AVR +		{A379D421-4236-44AF-A711-52B7BDA29919}.Release (DDR3L, Ti)|AVR.Build.0 = Release (DDR3L, Ti)|AVR +		{A379D421-4236-44AF-A711-52B7BDA29919}.Release (Dev)|AVR.ActiveCfg = Release (Dev)|AVR +		{A379D421-4236-44AF-A711-52B7BDA29919}.Release (Dev)|AVR.Build.0 = Release (Dev)|AVR +		{A379D421-4236-44AF-A711-52B7BDA29919}.Release|AVR.ActiveCfg = Release|AVR +		{A379D421-4236-44AF-A711-52B7BDA29919}.Release|AVR.Build.0 = Release|AVR +	EndGlobalSection +	GlobalSection(SolutionProperties) = preSolution +		HideSolutionNode = FALSE +	EndGlobalSection +EndGlobal diff --git a/firmware/e300/rev_c/PMC.cproj b/firmware/e300/rev_c/PMC.cproj new file mode 100644 index 000000000..e3784d10b --- /dev/null +++ b/firmware/e300/rev_c/PMC.cproj @@ -0,0 +1,288 @@ +<?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.0</ProjectVersion> +    <ToolchainName>com.Atmel.AVRGCC8</ToolchainName> +    <ProjectGuid>{a379d421-4236-44af-a711-52b7bda29919}</ProjectGuid> +    <avrdevice>ATtiny88</avrdevice> +    <avrdeviceseries>none</avrdeviceseries> +    <OutputType>Executable</OutputType> +    <Language>C</Language> +    <OutputFileName>$(MSBuildProjectName)</OutputFileName> +    <OutputFileExtension>.elf</OutputFileExtension> +    <OutputDirectory>$(MSBuildProjectDirectory)\$(Configuration)</OutputDirectory> +    <AssemblyName>PMC</AssemblyName> +    <Name>PMC</Name> +    <RootNamespace>PMC</RootNamespace> +    <ToolchainFlavour>Native</ToolchainFlavour> +    <KeepTimersRunning>true</KeepTimersRunning> +    <OverrideVtor>false</OverrideVtor> +    <OverrideVtorValue /> +    <eraseonlaunchrule>0</eraseonlaunchrule> +    <AsfVersion>3.1.3</AsfVersion> +    <avrtool>com.atmel.avrdbg.tool.stk600</avrtool> +    <avrtoolinterface>ISP</avrtoolinterface> +    <com_atmel_avrdbg_tool_stk600> +      <ToolType>com.atmel.avrdbg.tool.stk600</ToolType> +      <ToolName>STK600</ToolName> +      <ToolNumber>004A8D68669B</ToolNumber> +      <KeepTimersRunning>true</KeepTimersRunning> +      <OverrideVtor>false</OverrideVtor> +      <OverrideVtorValue> +      </OverrideVtorValue> +      <Channel> +        <host>127.0.0.1</host> +        <port>49172</port> +        <ssl>False</ssl> +      </Channel> +      <ToolOptions> +        <InterfaceName>ISP</InterfaceName> +        <InterfaceProperties> +          <JtagDbgClock>249000</JtagDbgClock> +          <JtagProgClock>1000000</JtagProgClock> +          <IspClock>25000</IspClock> +          <JtagInChain>false</JtagInChain> +          <JtagEnableExtResetOnStartSession>false</JtagEnableExtResetOnStartSession> +          <JtagDevicesBefore>0</JtagDevicesBefore> +          <JtagDevicesAfter>0</JtagDevicesAfter> +          <JtagInstrBitsBefore>0</JtagInstrBitsBefore> +          <JtagInstrBitsAfter>0</JtagInstrBitsAfter> +        </InterfaceProperties> +      </ToolOptions> +    </com_atmel_avrdbg_tool_stk600> +    <com_atmel_avrdbg_tool_ispmk2> +      <ToolType>com.atmel.avrdbg.tool.ispmk2</ToolType> +      <ToolName>AVRISP mkII</ToolName> +      <ToolNumber>000200136505</ToolNumber> +      <KeepTimersRunning>true</KeepTimersRunning> +      <OverrideVtor>false</OverrideVtor> +      <OverrideVtorValue> +      </OverrideVtorValue> +      <Channel> +        <host>127.0.0.1</host> +        <port>49228</port> +        <ssl>False</ssl> +      </Channel> +      <ToolOptions> +        <InterfaceName>ISP</InterfaceName> +        <InterfaceProperties> +          <JtagDbgClock>249000</JtagDbgClock> +          <JtagProgClock>1000000</JtagProgClock> +          <IspClock>8000</IspClock> +          <JtagInChain>false</JtagInChain> +          <JtagEnableExtResetOnStartSession>false</JtagEnableExtResetOnStartSession> +          <JtagDevicesBefore>0</JtagDevicesBefore> +          <JtagDevicesAfter>0</JtagDevicesAfter> +          <JtagInstrBitsBefore>0</JtagInstrBitsBefore> +          <JtagInstrBitsAfter>0</JtagInstrBitsAfter> +        </InterfaceProperties> +      </ToolOptions> +    </com_atmel_avrdbg_tool_ispmk2> +  </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.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned> +        <avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned> +        <avrgcc.compiler.symbols.DefSymbols> +          <ListValues> +            <Value>I2C_REWORK</Value> +            <Value>ENABLE_SERIAL</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.compiler.warnings.WarningsAsErrors>True</avrgcc.compiler.warnings.WarningsAsErrors> +        <avrgcc.linker.libraries.Libraries> +          <ListValues> +            <Value>m</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.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned> +        <avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned> +        <avrgcc.compiler.symbols.DefSymbols> +          <ListValues> +            <Value>ATTINY88_DIP</Value> +            <Value>DEBUG</Value> +            <Value>I2C_REWORK</Value> +          </ListValues> +        </avrgcc.compiler.symbols.DefSymbols> +        <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>Default (-g2)</avrgcc.compiler.optimization.DebugLevel> +        <avrgcc.compiler.warnings.AllWarnings>True</avrgcc.compiler.warnings.AllWarnings> +        <avrgcc.compiler.warnings.WarningsAsErrors>True</avrgcc.compiler.warnings.WarningsAsErrors> +        <avrgcc.linker.libraries.Libraries> +          <ListValues> +            <Value>m</Value> +          </ListValues> +        </avrgcc.linker.libraries.Libraries> +        <avrgcc.assembler.debugging.DebugLevel>Default (-Wa,-g)</avrgcc.assembler.debugging.DebugLevel> +      </AvrGcc> +    </ToolchainSettings> +  </PropertyGroup> +  <PropertyGroup Condition=" '$(Configuration)' == 'Release (DDR3L)' "> +    <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.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned> +        <avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned> +        <avrgcc.compiler.symbols.DefSymbols> +          <ListValues> +            <Value>I2C_REWORK</Value> +            <Value>ENABLE_SERIAL</Value> +            <Value>DDR3L</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.compiler.warnings.WarningsAsErrors>True</avrgcc.compiler.warnings.WarningsAsErrors> +        <avrgcc.linker.libraries.Libraries> +          <ListValues> +            <Value>m</Value> +          </ListValues> +        </avrgcc.linker.libraries.Libraries> +      </AvrGcc> +    </ToolchainSettings> +    <OutputPath>bin\Release (DDR3)\</OutputPath> +  </PropertyGroup> +  <PropertyGroup Condition=" '$(Configuration)' == 'Release (Dev)' "> +    <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.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned> +        <avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned> +        <avrgcc.compiler.symbols.DefSymbols> +          <ListValues> +            <Value>I2C_REWORK</Value> +            <Value>ENABLE_SERIAL</Value> +            <Value>ATTINY88_DIP</Value> +          </ListValues> +        </avrgcc.compiler.symbols.DefSymbols> +        <avrgcc.compiler.optimization.level>Optimize for size (-Os)</avrgcc.compiler.optimization.level> +        <avrgcc.compiler.optimization.PrepareFunctionsForGarbageCollection>True</avrgcc.compiler.optimization.PrepareFunctionsForGarbageCollection> +        <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.compiler.warnings.WarningsAsErrors>True</avrgcc.compiler.warnings.WarningsAsErrors> +        <avrgcc.linker.libraries.Libraries> +          <ListValues> +            <Value>m</Value> +          </ListValues> +        </avrgcc.linker.libraries.Libraries> +        <avrgcc.linker.optimization.GarbageCollectUnusedSections>True</avrgcc.linker.optimization.GarbageCollectUnusedSections> +      </AvrGcc> +    </ToolchainSettings> +    <OutputPath>bin\Release (Dev)\</OutputPath> +  </PropertyGroup> +  <PropertyGroup Condition=" '$(Configuration)' == 'Release (DDR3L, Ti)' "> +    <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.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned> +  <avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned> +  <avrgcc.compiler.symbols.DefSymbols> +    <ListValues> +      <Value>I2C_REWORK_disabled</Value> +      <Value>DDR3L_disabled_revB_R_FB_network_changed</Value> +      <Value>CHARGER_TI</Value> +      <Value>ENABLE_SERIAL_disabled</Value> +      <Value>LED_POLARITY</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.compiler.warnings.WarningsAsErrors>True</avrgcc.compiler.warnings.WarningsAsErrors> +  <avrgcc.linker.libraries.Libraries> +    <ListValues> +      <Value>m</Value> +    </ListValues> +  </avrgcc.linker.libraries.Libraries> +</AvrGcc> +    </ToolchainSettings> +    <OutputPath>bin\Release (DDR3L, Ti)\</OutputPath> +  </PropertyGroup> +  <ItemGroup> +    <Compile Include="bq24190.c"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="bq24190.h"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="config.h"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="debug.c"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="debug.h"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="error.h"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="global.h"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="i2c.c"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="i2c.h"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="io.c"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="io.h"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="ltc3675.c"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="ltc3675.h"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="ltc4155.c"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="ltc4155.h"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="main.c"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="power.c"> +      <SubType>compile</SubType> +    </Compile> +    <Compile Include="power.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/rev_c/bq24190.c b/firmware/e300/rev_c/bq24190.c new file mode 100644 index 000000000..029beacf6 --- /dev/null +++ b/firmware/e300/rev_c/bq24190.c @@ -0,0 +1,335 @@ +/* + * bq24190.c + * + * Created: 11/12/2012 4:58:12 PM + *  Author: Balint Seeber + */ + +#ifdef CHARGER_TI + +#include "config.h" +#include "bq24190.h" + +#include <util/delay.h> + +#include "io.h" +#include "i2c.h" +#include "debug.h" +#include "global.h" +#include "error.h" + +#ifndef I2C_REWORK +#include "power.h" +#endif // I2C_REWORK + +static io_pin_t USBPM_IRQ	= IO_PB(1); + +#ifdef ATTINY88_DIP + +static io_pin_t CHRG_SDA     = IO_PC(2); +static io_pin_t CHRG_SCL     = IO_PC(3); + +#else + +#ifdef I2C_REWORK + +static io_pin_t CHRG_SDA     = IO_PC(4); +static io_pin_t CHRG_SCL     = IO_PC(5); + +#else + +#define CHRG_SDA	PWR_SDA +#define CHRG_SCL	PWR_SCL + +#endif // I2C_REWORK + +#endif // ATTINY88_DIP + +const bool _bq24190_pull_up = false; + +#define BQ24190_BASE_ADDRESS    (0x6B << 1) +#define BQ24190_WRITE_ADDRESS   (BQ24190_BASE_ADDRESS + 0) +#define BQ24190_READ_ADDRESS    (BQ24190_BASE_ADDRESS + 1) + +enum BQ24190Registers +{ +	BQ24190_REG_INPUT_SOURCE_CTL= 0, +	BQ24190_REG_PWR_ON_CONFIG	= 1, +	BQ24190_REG_CHARGE_CURRENT	= 2, +	BQ24190_REG_PRE_TERM_CURRENT= 3, +	BQ24190_REG_CHARGE_VOLTAGE	= 4, +	BQ24190_REG_TIMER_CONTROL	= 5, +	BQ24190_REG_SYSTEM_STATUS	= 8, +	BQ24190_REG_FAULT			= 9 +}; +/* +enum BQ24190TimerControl +{ +	 +}; +*/ +enum BQ24190Shifts +{ +	BQ24190_SHIFTS_CHARGER_CONFIG	= 4, +	BQ24190_SHIFTS_I2C_WATCHDOG		= 4, +	BQ24190_SHIFTS_CHARGER_STATUS	= 4, +	BQ24190_SHIFTS_CHARGER_FAULT	= 4, +}; + +enum BQ24190VBusStatus +{ +	BQ24190_VBUS_UNKNOWN, +	BQ24190_VBUS_USB, +	BQ24190_VBUS_ADAPTER, +	BQ24190_VBUS_OTG +}; + +enum BQ24190ChargerStatus +{ +	BQ24190_CHRG_STAT_NOT_CHARGING, +	BQ24190_CHRG_STAT_PRE_CHARGE, +	BQ24190_CHRG_STAT_FAST_CHARGING, +	BQ24190_CHRG_STAT_CHARGE_TERMINATION_DONE, +	BQ24190_CHRG_STAT_MASK = BQ24190_CHRG_STAT_CHARGE_TERMINATION_DONE +}; + +enum BQ24190SystemStatus +{ +	BQ24190_STATUS_DPM					= 0x08, +	BQ24190_STATUS_POWER_GOOD			= 0x04, +	BQ24190_STATUS_THERMAL_REGULATION	= 0x02, +	BQ24190_STATUS_VSYSMIN_REGULATION	= 0x01 +}; + +enum BQ24190Faults +{ +	BQ24190_FAULT_WATCHDOG_EXPIRED	= 0x80, +	BQ24190_FAULT_VBUS_OVERLOADED	= 0x40, +	BQ24190_FAULT_BATOVP			= 0x08 +}; + +enum BQ24190ChargerFaults +{ +	BQ24190_CHRGFAULT_NORMAL, +	BQ24190_CHRGFAULT_INPUT, +	BQ24190_CHRGFAULT_THERMAL, +	BQ24190_CHRGFAULT_SAFETY_TIMER +}; + +enum BQ24190NTCFaults +{ +	BQ24190_NTCFAULT_NORMAL, +	BQ24190_NTCFAULT_TS1_COLD, +	BQ24190_NTCFAULT_TS1_HOT, +	BQ24190_NTCFAULT_TS2_COLD, +	BQ24190_NTCFAULT_TS2_HOT, +	BQ24190_NTCFAULT_BOTH_COLD, +	BQ24190_NTCFAULT_BOTH_HOT +}; + +bool bq24190_toggle_charger(bool on) +{ +	uint8_t config = 0; +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, BQ24190_READ_ADDRESS, BQ24190_REG_PWR_ON_CONFIG, &config, _bq24190_pull_up) == false) +		return false; +	 +	debug_log_ex("BQPC ", false); +	debug_log_hex(config); +	 +	config &= ~(0x3 << BQ24190_SHIFTS_CHARGER_CONFIG); +	if (on) +		config |= (0x01 << BQ24190_SHIFTS_CHARGER_CONFIG);	// Enable charger +	 +	if (i2c_write_ex(CHRG_SDA, CHRG_SCL, BQ24190_WRITE_ADDRESS, BQ24190_REG_PWR_ON_CONFIG, config, _bq24190_pull_up) == false) +		return false; +	 +	//////// +/* +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, BQ24190_READ_ADDRESS, BQ24190_REG_PWR_ON_CONFIG, &config, _bq24190_pull_up) == false) +		return false; +	 +	debug_log_ex("BQPC ", false); +	debug_log_hex(config); +*/ +	//////// +	 +	return true; +} + +bool bq24190_init(bool disable_charger) +{ +#ifdef I2C_REWORK +	i2c_init_ex(CHRG_SDA, CHRG_SCL, _bq24190_pull_up); +#endif // I2C_REWORK +	io_input_pin(USBPM_IRQ); +#if !defined(DEBUG) && !defined(ATTINY88_DIP) +	//io_set_pin(USBPM_IRQ);	// [Enable pull-up for Open Drain] AVR pull-up not enough +#endif // DEBUG +	if (disable_charger) +	{ +		if (bq24190_toggle_charger(false) == false) +			return false; +	} +	 +	/////////////////////////////////// +	 +	uint8_t timer_control = 0; +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, BQ24190_READ_ADDRESS, BQ24190_REG_TIMER_CONTROL, &timer_control, _bq24190_pull_up) == false) +		return false; +	 +	debug_log_ex("BQTC ", false); +	debug_log_hex(timer_control); +	 +	timer_control &= ~(0x3 << BQ24190_SHIFTS_I2C_WATCHDOG); +	timer_control |= (0x00 << BQ24190_SHIFTS_I2C_WATCHDOG);	// Disable I2C watch dog +	 +	if (i2c_write_ex(CHRG_SDA, CHRG_SCL, BQ24190_WRITE_ADDRESS, BQ24190_REG_TIMER_CONTROL, timer_control, _bq24190_pull_up) == false) +		return false; +	 +	/////////////////////////////////// +	 +	//BQ24190_REG_PWR_ON_CONFIG +	// Minimum System Voltage Limit: (default) 101 3.5V +	 +	//BQ24190_REG_CHARGE_CURRENT +	// Fast Charge Current Limit: (default) 011000 2048mA +	 +	//BQ24190_REG_PRE_TERM_CURRENT +	// Pre-charge current limit: (default) 0001 256mA +	// Termination current limit: (default) 0001 256mA +	 +	//BQ24190_REG_CHARGE_VOLTAGE +	// Charge voltage limit: (default) 101100 4.208V +	 +	/////////////////////////////////// +	 +	uint8_t input_src_ctl = 0; +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, BQ24190_READ_ADDRESS, BQ24190_REG_INPUT_SOURCE_CTL, &input_src_ctl, _bq24190_pull_up) == false) +		return false; +	 +	debug_log_ex("BQIS ", false); +	debug_log_hex(input_src_ctl); +	 +	// Input voltage limit: (default) 0110 4.36V +	 +	//input_src_ctl &= ~(0x07); +	input_src_ctl |= (0x07);	// Set 3A limit +	 +	if (i2c_write_ex(CHRG_SDA, CHRG_SCL, BQ24190_WRITE_ADDRESS, BQ24190_REG_INPUT_SOURCE_CTL, input_src_ctl, _bq24190_pull_up) == false) +		return false; +	 +	return true; +} + +bool bq24190_has_interrupt(void) +{ +	//bool state = io_test_pin(USBPM_IRQ); +	//debug_log_ex("BQIRQ", false); +	//debug_log_byte(state); +	//return (state != 1); +	return (io_test_pin(USBPM_IRQ) == false); +} + +static uint8_t _bq24190_last_status, _bq24190_last_fault; + +bool _bq24190_handle_irq(void) +{ +	uint8_t val = 0x00; +	bool result = false; +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, BQ24190_READ_ADDRESS, BQ24190_REG_SYSTEM_STATUS, &val, _bq24190_pull_up) == false) +		goto _bq24190_handle_fail; +	 +	debug_log_ex("BQST ", false); +	debug_log_hex(val); +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, BQ24190_READ_ADDRESS, BQ24190_REG_SYSTEM_STATUS, &val, _bq24190_pull_up) == false) +		goto _bq24190_handle_fail; +	 +	_bq24190_last_status = val; + +	debug_log_ex("BQST ", false); +	debug_log_hex(val); +	 +	/*if (val & LTC4155_WALLSNS_GOOD) +	{ +		uint8_t wall_state = 0; +		if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_WALL, &wall_state, _ltc4155_pull_up) == false) +			goto _bq24190_handle_fail; +		 +		wall_state &= ~0x1E; +		wall_state |= 0x0E; +		 +		if (i2c_write_ex(CHRG_SDA, CHRG_SCL, LTC4155_WRITE_ADDRESS, LTC4155_REG_WALL, wall_state, _ltc4155_pull_up) == false) +			goto _bq24190_handle_fail; +		 +		debug_log("I+"); +	}*/ +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, BQ24190_READ_ADDRESS, BQ24190_REG_FAULT, &val, _bq24190_pull_up) == false) +		goto _bq24190_handle_fail; +	 +	debug_log_ex("BQF  ", false); +	debug_log_hex(val); +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, BQ24190_READ_ADDRESS, BQ24190_REG_FAULT, &val, _bq24190_pull_up) == false) +		goto _bq24190_handle_fail; +	 +	_bq24190_last_fault = val; +	 +	debug_log_ex("BQF  ", false); +	debug_log_hex(val); +	 +	val = (_bq24190_last_status >> BQ24190_SHIFTS_CHARGER_STATUS) & BQ24190_CHRG_STAT_MASK; +	 +	if (_state.blink_error == BlinkError_None) +	{ +		switch (val) +		{ +			case BQ24190_CHRG_STAT_PRE_CHARGE: +			case BQ24190_CHRG_STAT_FAST_CHARGING: +			//case BQ24190_CHRG_STAT_CHARGE_TERMINATION_DONE: +			{ +				if ((_state.battery_not_present == false)/* && +					(_ltc4155_last_good & (LTC4155_WALLSNS_GOOD | LTC4155_USBSNS_GOOD))*/) +				{ +					//charge_set_led(true); +					charge_notify(true); +					break; +				} +			} +			//case BQ24190_CHRG_STAT_NOT_CHARGING: +			default: +				//charge_set_led(false); +				charge_notify(false); +		} +	} +	 +//	bq24190_dump(); +	 +	result = true; +_bq24190_handle_fail: +	return result; +} + +bool bq24190_handle_irq(void)	// IRQ is pulsed (not held) +{ +	pmc_mask_irqs(true); +	 +	//_delay_ms(250);	// [Wait for registers to update] +	 +	bool result = _bq24190_handle_irq(); +	 +	pmc_mask_irqs(false); +	 +	return result; +} + +//void bq24190_dump(void) +/* +bool bq24190_set_charge_current_limit(uint8_t deciamps) +{ +	return true; +} +*/ +#endif // CHARGER_TI diff --git a/firmware/e300/rev_c/bq24190.h b/firmware/e300/rev_c/bq24190.h new file mode 100644 index 000000000..dc20ca796 --- /dev/null +++ b/firmware/e300/rev_c/bq24190.h @@ -0,0 +1,26 @@ +/* + * bq24190.h + * + * Created: 11/12/2012 4:58:23 PM + *  Author: Balint Seeber + */  + + +#ifndef BQ24190_H_ +#define BQ24190_H_ + +#include <stdbool.h> +#include <stdint.h> + +#ifdef CHARGER_TI + +bool bq24190_init(bool disable_charger); +bool bq24190_has_interrupt(void); +bool bq24190_handle_irq(void); +//void bq24190_dump(void); +//bool bq24190_set_charge_current_limit(uint8_t deciamps); +bool bq24190_toggle_charger(bool on); + +#endif // !CHARGER_TI + +#endif /* BQ24190_H_ */ diff --git a/firmware/e300/rev_c/config.h b/firmware/e300/rev_c/config.h new file mode 100644 index 000000000..2547f65f0 --- /dev/null +++ b/firmware/e300/rev_c/config.h @@ -0,0 +1,2 @@ +#define __DELAY_BACKWARD_COMPATIBLE__   // Avoid compile-time arg error to '__builtin_avr_delay_cycles' +#define F_CPU   1000000UL // 1 MHz (8MHz / 8) diff --git a/firmware/e300/rev_c/debug.c b/firmware/e300/rev_c/debug.c new file mode 100644 index 000000000..ff7d09445 --- /dev/null +++ b/firmware/e300/rev_c/debug.c @@ -0,0 +1,297 @@ +/* + * debug.c + */ + +#include "config.h" +#include "debug.h" + +#include <util/delay.h> +#include <avr/io.h> +#include <avr/interrupt.h> + +#include "io.h" +#include "power.h" +#include "global.h" + +#define DEBUG_BLINK_DELAY	250	// ms + +#ifdef ATTINY88_DIP + +#define SERIAL_DEBUG_INDEX			6 +#define SERIAL_DEBUG_PORT			PORTD +static io_pin_t SERIAL_DEBUG      = IO_PD(SERIAL_DEBUG_INDEX); + +#else +/* +#ifdef I2C_REWORK +//static io_pin_t SERIAL_DEBUG      = IO_PC(1);	// EN1 +#else +//static io_pin_t SERIAL_DEBUG      = EN4; +#endif // I2C_REWORK +*/ +// No good: PWR_EN4 trace still connected to LTC3675 +//#define SERIAL_DEBUG_INDEX			1 +//#define SERIAL_DEBUG_PORT			PORTA +//static io_pin_t SERIAL_DEBUG      = IO_PA(SERIAL_DEBUG_INDEX); + +// AVR_MISO +#define SERIAL_DEBUG_INDEX			4 +#define SERIAL_DEBUG_PORT			PORTB +static io_pin_t SERIAL_DEBUG      = IO_PB(SERIAL_DEBUG_INDEX); + +#endif // ATTINY88_DIP +/* +#ifdef DEBUG + +#else + +#endif // DEBUG +*/ +#ifdef DEBUG + +#ifdef ATTINY88_DIP +static io_pin_t DEBUG_1 = IO_PB(6); +static io_pin_t DEBUG_2	= IO_PB(7); +#endif // ATTINY88_DIP + +void debug_init() +{ +	io_output_pin(DEBUG_1); +	io_output_pin(DEBUG_2); +	 +	io_enable_pin(DEBUG_1, true); +	io_enable_pin(DEBUG_2, true); +#ifdef ENABLE_SERIAL	 +	io_set_pin(SERIAL_DEBUG); +	io_output_pin(SERIAL_DEBUG); +#endif // ENABLE_SERIAL +} + +#else + +void debug_init() +{ +#ifdef ENABLE_SERIAL +	io_set_pin(SERIAL_DEBUG); +	io_output_pin(SERIAL_DEBUG); +#endif // ENABLE_SERIAL +} + +#endif // DEBUG + +#if defined(DEBUG) && !defined(DEBUG_VOID) + +void debug_set(io_pin_t pin, bool enable) +{ +	io_enable_pin(pin, !enable); +} + +void debug_blink(uint8_t count) +{ +	io_enable_pin(DEBUG_1, false); +	io_enable_pin(DEBUG_2, true); +	_delay_ms(DEBUG_BLINK_DELAY * 2); + +	for (; count > 0; count--) { +		io_enable_pin(DEBUG_2, false); +		_delay_ms(DEBUG_BLINK_DELAY); +		io_enable_pin(DEBUG_2, true); +		_delay_ms(DEBUG_BLINK_DELAY); +	} + +	io_enable_pin(DEBUG_1, true); +	io_enable_pin(DEBUG_2, true); +	_delay_ms(DEBUG_BLINK_DELAY * 2); +} + +void debug_blink_rev(uint8_t count) +{ +	io_enable_pin(DEBUG_2, false); +	io_enable_pin(DEBUG_1, true); +	_delay_ms(DEBUG_BLINK_DELAY * 2); + +	for (; count > 0; count--) { +		io_enable_pin(DEBUG_1, false); +		_delay_ms(DEBUG_BLINK_DELAY); +		io_enable_pin(DEBUG_1, true); +		_delay_ms(DEBUG_BLINK_DELAY); +	} + +	io_enable_pin(DEBUG_2, true); +	io_enable_pin(DEBUG_1, true); +	_delay_ms(DEBUG_BLINK_DELAY * 2); +} + +void debug_blink2(uint8_t count) +{ +	io_enable_pin(DEBUG_1, true); +	io_enable_pin(DEBUG_2, true); +	_delay_ms(DEBUG_BLINK_DELAY * 2); + +	bool b = false; +	for (; count > 0; count--) { +		io_enable_pin(DEBUG_1, b); +		io_enable_pin(DEBUG_2, b); +		_delay_ms(DEBUG_BLINK_DELAY); +		b = !b; +	} + +	io_enable_pin(DEBUG_1, true); +	io_enable_pin(DEBUG_2, true); +	_delay_ms(DEBUG_BLINK_DELAY * 2); +} + +void debug_wait(void) +{ +	io_enable_pin(DEBUG_1, true); +	io_enable_pin(DEBUG_2, true); +	 +	bool b = false; +	while (true) +	{ +		io_enable_pin(DEBUG_1, b); +		io_enable_pin(DEBUG_2, !b); +		 +		_delay_ms(DEBUG_BLINK_DELAY); +		 +		b = !b; +	} +	 +	io_enable_pin(DEBUG_1, true); +	io_enable_pin(DEBUG_2, true); +} + +#else + +#ifndef DEBUG_VOID + +void debug_blink_rev(uint8_t count) +{ +	charge_set_led(true); +	_delay_ms(DEBUG_BLINK_DELAY * 4); + +	for (; count > 0; count--) { +		charge_set_led(false); +		_delay_ms(DEBUG_BLINK_DELAY); +		charge_set_led(true); +		_delay_ms(DEBUG_BLINK_DELAY * 2); +	} + +	_delay_ms(DEBUG_BLINK_DELAY * 2); +	charge_set_led(false); +	_delay_ms(DEBUG_BLINK_DELAY * 4); +} +#endif // DEBUG_VOID + +#endif // DEBUG + +#ifdef ENABLE_SERIAL + +static void _serial_tx(uint8_t* buffer) +{ +	//uint8_t time_fix = 0; +	// 3333/2 - 10 +	// 650 +	//	[-2 for DEV] -20 works (perhaps different USB-Serial converter) +	//	[-20 for PRD] Which board? +	//	+20 Board #5 (-0: 3.592, -10: 3.280) +	const uint16_t delay = 650+20; +	uint16_t countdown; +	 +	for (uint8_t j = 0; j < 10; ++j) +	{ +		if (buffer[j]) +		SERIAL_DEBUG_PORT |= _BV(SERIAL_DEBUG_INDEX); +		else +		SERIAL_DEBUG_PORT &= ~_BV(SERIAL_DEBUG_INDEX); +		 +		countdown = delay; +		while (--countdown) +		__asm("nop"); +	} +} + +static void _serial_tx_char(char c) +{ +	uint8_t buffer[10]; +	uint8_t i = 0; +	 +	buffer[i++] = 0;	// START +	for (int idx = 0; idx < 8; ++idx) +	buffer[i++] = (((uint8_t)(c) & ((uint8_t)1<<((idx)))) ? 0x01 : 0x00);	// Endianness: 7- +	buffer[i++] = 1;	// STOP +	 +	_serial_tx(buffer); +} + +void debug_log_ex_P(const char* message, bool new_line) +{ +	char c = pgm_read_byte(message); +	if (c == '\0') +		return; +	 +	pmc_mask_irqs(true); + +	do +	{ +		_serial_tx_char(c); +		c = pgm_read_byte(++message); +	} while (c != '\0'); +	 +	if (new_line) +		_serial_tx_char('\n'); + +	io_set_pin(SERIAL_DEBUG); +	 +	pmc_mask_irqs(false); +} + +void _debug_log_ex(const char* message, bool new_line) +{ +	if (message[0] == '\0') +	return; +	 +	pmc_mask_irqs(true); + +	do +	{ +		_serial_tx_char(*message); +	} while (*(++message) != '\0'); +	 +	if (new_line) +	_serial_tx_char('\n'); + +	io_set_pin(SERIAL_DEBUG); +	 +	pmc_mask_irqs(false); +} + +void debug_log_byte_ex(uint8_t n, bool new_line) +{ +	char ch[4]; +	ch[0] = '0' + (n / 100); +	ch[1] = '0' + ((n % 100) / 10); +	ch[2] = '0' + (n % 10); +	ch[3] = '\0'; +	_debug_log_ex(ch, new_line); +} + +void debug_log_hex_ex(uint8_t n, bool new_line) +{ +	char ch[4]; +	ch[0] = 'x'; +	uint8_t _n = n >> 4; +	if (_n < 10) +		ch[1] = '0' + _n; +	else +		ch[1] = 'A' + (_n - 10); +	n &= 0x0F; +	if (n < 10) +		ch[2] = '0' + n; +	else +		ch[2] = 'A' + (n - 10); +	ch[3] = '\0'; +	_debug_log_ex(ch, new_line); +} + +#endif // ENABLE_SERIAL diff --git a/firmware/e300/rev_c/debug.h b/firmware/e300/rev_c/debug.h new file mode 100644 index 000000000..5e6435972 --- /dev/null +++ b/firmware/e300/rev_c/debug.h @@ -0,0 +1,90 @@ +/* + * debug.h + */  + +#ifndef DEBUG_H_ +#define DEBUG_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <avr/pgmspace.h> + +#include "io.h" + +#ifdef DEBUG +#define DEBUG_INLINE +#define DEBUG_NOOP	; +#define LED_ON		false +#define LED_OFF		true +#else +#define DEBUG_INLINE inline +#define DEBUG_NOOP	{} +#define LED_ON		true +#define LED_OFF		false +#endif // DEBUG + +//#define DEBUG_VOID +#define DEBUG_SAFETY + +#ifdef DEBUG_VOID + +//#define debug_init	(void) +#define debug_set	(void) +#define debug_blink	(void) +#define debug_blink_rev	(void) +#define debug_blink2 (void) +#define debug_wait	(void) + +#else + +//DEBUG_INLINE void debug_init(void) DEBUG_NOOP +DEBUG_INLINE void debug_set(io_pin_t pin, bool enable) DEBUG_NOOP +DEBUG_INLINE void debug_blink(uint8_t count) DEBUG_NOOP +//DEBUG_INLINE void debug_blink_rev(uint8_t count) DEBUG_NOOP +void debug_blink_rev(uint8_t count); +DEBUG_INLINE void debug_blink2(uint8_t count) DEBUG_NOOP +DEBUG_INLINE void debug_wait(void) DEBUG_NOOP + +#endif // DEBUG_VOID + +#if defined(DEBUG) && !defined(ENABLE_SERIAL) +#define ENABLE_SERIAL +#endif // DEBUG && !ENABLE_SERIAL + +/*DEBUG_INLINE */void debug_init(void)/* DEBUG_NOOP*/; + +#ifdef ENABLE_SERIAL + +void debug_log_ex_P(const char* message, bool new_line); +void debug_log_hex_ex(uint8_t n, bool new_line); +void debug_log_byte_ex(uint8_t n, bool new_line); +void _debug_log_ex(const char* message, bool new_line); + +// Prototypes to silence avr-gcc +inline void debug_log_P(const char* message); +inline void debug_log_hex(uint8_t n); +inline void debug_log_byte(uint8_t n); +inline void _debug_log(const char* message); + +inline void debug_log_P(const char* message) { debug_log_ex_P(message, true); } +inline void debug_log_hex(uint8_t n) { debug_log_hex_ex(n, true); } +inline void debug_log_byte(uint8_t n) { debug_log_byte_ex(n, true); } +inline void _debug_log(const char* message) { _debug_log_ex(message, true); } + +#else + +inline void debug_log_ex_P		(const char* message, bool new_line) {}; +inline void debug_log_hex_ex	(uint8_t n, bool new_line) {}; +inline void debug_log_byte_ex	(uint8_t n, bool new_line) {}; +inline void _debug_log_ex		(const char* message, bool new_line) {}; + +#define debug_log_P			(void) +#define debug_log_hex		(void) +#define debug_log_byte		(void) +#define _debug_log			(void) +#endif // ENABLE_SERIAL + +#define debug_log(x)		debug_log_P(PSTR(x)) +#define debug_log_ex(x,nl)	debug_log_ex_P(PSTR(x), nl) + +#endif /* DEBUG_H_ */ diff --git a/firmware/e300/rev_c/error.h b/firmware/e300/rev_c/error.h new file mode 100644 index 000000000..82a8f0aca --- /dev/null +++ b/firmware/e300/rev_c/error.h @@ -0,0 +1,31 @@ +/* + * error.h + * + * Created: 4/09/2012 6:25:53 PM + *  Author: Balint Seeber + */  + + +#ifndef ERROR_H_ +#define ERROR_H_ + +enum ErrorBlinkCount	// Lower number = higher priority +{ +	BlinkError_None, +	// Low power/battery +	BlinkError_LowVoltage, +	BlinkError_LTC3675_UnderVoltage = BlinkError_LowVoltage, +	BlinkError_LTC4155_UnderVoltage = BlinkError_LowVoltage,	// FIXME: This does not work when checking status +	// Should match power boot steps +	BlinkError_FPGA_Power, +	BlinkError_DRAM_Power, +	BlinkError_1_8V_Peripherals_Power, +	BlinkError_3_3V_Peripherals_Power, +	BlinkError_TX_Power, +	// LTC3675 +	BlinkError_LTC3675_OverTemperature, +	// LTC4155 +	BlinkError_LTC4155_BadCell +}; + +#endif /* ERROR_H_ */ diff --git a/firmware/e300/rev_c/global.h b/firmware/e300/rev_c/global.h new file mode 100644 index 000000000..50fab581d --- /dev/null +++ b/firmware/e300/rev_c/global.h @@ -0,0 +1,49 @@ +/* + * global.h + * + * Created: 31/08/2012 8:47:14 PM + *  Author: Balint Seeber + */  + +#ifndef GLOBAL_H_ +#define GLOBAL_H_ + +#include <stdbool.h> +#include <stdint.h> +#include <avr/pgmspace.h> + +typedef struct State +{ +	bool interrupts_enabled; +	uint8_t interrupt_depth; +	//bool timers_running; +	uint8_t active_timers; +	bool powered; +	bool battery_not_present; +	bool battery_charging; +	bool wake_up; +	bool power_off; +	bool core_power_bad; +	bool ltc3675_irq; +#ifdef CHARGER_TI +	bool bq24190_irq; +#else +	bool ltc4155_irq; +#endif // CHARGER_TI +	//bool low_battery; +	uint8_t blink_error; +	uint8_t blinker_state; +	uint8_t blink_loops; +	uint8_t blink_last_loop; +	bool blink_stop; +} STATE; + +//extern volatile bool _timers_running; +extern volatile STATE _state; + +void pmc_set_blink_error(uint8_t count); +uint8_t pmc_get_blink_error(void); + +bool pmc_mask_irqs(bool mask); + +#endif /* GLOBAL_H_ */ diff --git a/firmware/e300/rev_c/i2c.c b/firmware/e300/rev_c/i2c.c new file mode 100644 index 000000000..70a28e61a --- /dev/null +++ b/firmware/e300/rev_c/i2c.c @@ -0,0 +1,518 @@ +#include "config.h" +#include "i2c.h" + +#include <util/delay.h> + +#include "io.h" +#include "debug.h" + +/* +	- Reset bus on failure (lack of ACK, etc) +	- Clock stretching +	- In pull-up mode, much code was commented out to ever avoid driving the bus (for a fleeting moment) as this was visible on the scope as short peaks (instead the line will briefly go Hi Z). +*/ + +volatile bool _i2c_disable_ack_check = false; + +// FIXME: Follow magic numbers should be in a struct that is passed into each function + +#define I2C_DEFAULT_RETRY_DELAY     1   // us MAGIC +#define I2C_DEFAULT_MAX_ACK_RETRIES 10  // * I2C_DEFAULT_RETRY_DELAY us + +#define I2C_DEFAULT_BUS_WAIT		10	// us MAGIC +#define I2C_DEFAULT_MAX_BUS_RETRIES	10 + +#define I2C_DEFAULT_SCL_LOW_PERIOD  2   // 1.3 us +#define I2C_DEFAULT_SCL_HIGH_PERIOD 1   // 0.6 us +#define I2C_DEFAULT_BUS_FREE_TIME   2   // 1.3 us +#define I2C_DEFAULT_STOP_TIME       1   // 0.6 us + +#define I2C_DELAY	_delay_us	// _delay_ms + +static bool _i2c_start_ex(io_pin_t sda, io_pin_t scl, bool pull_up) +{ +	// Assumes: SDA/SCL are both inputs + +	uint8_t retries = I2C_DEFAULT_MAX_BUS_RETRIES; +	while ((io_test_pin(sda) == false) || (io_test_pin(scl) == false)) +	{ +		I2C_DELAY(I2C_DEFAULT_BUS_WAIT); +		if (retries-- == 0) +		{ +debug_log("I2C:S1"); +			return false; +		}			 +	} +	 +	// START condition +//	if (pull_up == false) +		io_clear_pin(sda);	// Set LOW before switching to output +	io_output_pin(sda); +//	if (pull_up) +//		io_clear_pin(sda); +	I2C_DELAY(I2C_DEFAULT_SCL_LOW_PERIOD);  // Thd, sta +	 +	retries = I2C_DEFAULT_MAX_BUS_RETRIES; +	while (io_test_pin(scl) == false)	// SCL should remain high +	{ +		I2C_DELAY(I2C_DEFAULT_BUS_WAIT); +		if (retries-- == 0) +		{ +			io_input_pin(sda); +debug_log_ex("I2C:S2", false); +debug_log_hex(scl); +			return false; +		}			 +	} + +//	if (pull_up == false) +		io_clear_pin(scl); +	io_output_pin(scl); +//	if (pull_up) +//		io_clear_pin(scl); +	I2C_DELAY(I2C_DEFAULT_SCL_LOW_PERIOD / 2);   // MAGIC + +	return true; +} + +static bool _i2c_stop_ex(io_pin_t sda, io_pin_t scl, bool pull_up) +{ +	// Assumes: +	//	SCL is output & LOW +	//	SDA is input (Hi-Z, or pull-up enabled) +	 +	// Assuming pull-up already enabled +	//if (pull_up) +	//	io_set_pin(sda); +	 +	bool result = true; +	 +	// SDA should be HIGH after ACK has been clocked away +//	bool skip_drive = false; +	uint8_t retries = 0; +	while (io_test_pin(sda) == false) +	{ +		if (retries == I2C_DEFAULT_MAX_ACK_RETRIES) +		{ +			debug_log_ex("I2C:STP ", false); +			debug_log_hex(sda); +			debug_blink_rev(4); +			 +//			skip_drive = true; +			result = false; +			break;	// SDA is being held low?! +		} + +		++retries; +		I2C_DELAY(I2C_DEFAULT_RETRY_DELAY); +	} +	 +	// STOP condition +//	if ((pull_up == false) || (skip_drive)) +		io_clear_pin(sda);	// Don't tri-state if internal pull-up is used +//	//else +//	// Pin will now be driven, but having checked SDA is HIGH above means slave's SDA should be Open Collector (i.e. it won't blow up) +	io_output_pin(sda);	// Drive LOW +//	if (pull_up) +//		io_clear_pin(sda); + +	/////////////////////////////////// +	 +//	if (pull_up) +//		io_set_pin(scl);	// Don't tri-state if internal pull-up is used. Line will be driven, but assuming this is the only master on the clock line (i.e. no one else will pull it low). +	io_input_pin(scl); +	if (pull_up) +		io_set_pin(scl); +	I2C_DELAY(I2C_DEFAULT_STOP_TIME); +	 +	/////////////////////////////////// + +//	if ((pull_up) && (skip_drive == false)) +//		io_set_pin(sda);	// Don't tri-state if internal pull-up is used +	io_input_pin(sda); +//	if ((pull_up) && (skip_drive)) +		io_set_pin(sda); +	I2C_DELAY(I2C_DEFAULT_BUS_FREE_TIME); +	 +	return result; +} +/* +static void _i2c_stop(io_pin_t sda, io_pin_t scl) +{ +	_i2c_stop_ex(sda, scl, false); +} +*//* +static void _i2c_abort_safe_ex(io_pin_t pin, bool pull_up) +{ +	if (io_is_output(pin)) +	{ +		if (io_is_pin_set(pin))	// This is bad - hope no slave is pulling down the line +		{ +			io_input_pin(pin);	// Pull-up already enabled +			 +			if (pull_up == false) +				io_clear_pin(pin);	// Doing this after changing direction ensures the line is not brought down +		} +		else	// Currently pulling line down +		{ +			io_input_pin(pin);	// Hi-Z +			 +			if (pull_up)	// There will be a moment where the line will float (better than driving the line though...) +			{ +				io_set_pin(pin); +			} +		} +	} +	else	// Already an input +	{ +		if (pull_up) +		{ +			io_set_pin(pin);	// Enable pull-ups +		} +		else +		{ +			io_clear_pin(pin);	// Disable pull-ups +		} +	} +	 +	// Normally: pin will be Hi-Z input +	// With internal pull-up: pin will be input with pull-up enabled +} +*/ +static void _i2c_abort_safe(io_pin_t pin, bool pull_up) +{ +	if (pull_up == false) +		io_clear_pin(pin);	// Should never be output/HIGH, could be input/<was outputting HIGH> so disable pull-ups +	 +	io_input_pin(pin); +	 +	if (pull_up) +		io_set_pin(pin);	// Enable pull-up +} + +static void _i2c_abort_ex(io_pin_t sda, io_pin_t scl, bool pull_up) +{ +/*	if (pull_up == false) +	{ +		io_clear_pin(sda); +		io_clear_pin(scl); +	} +	 +	io_input_pin(scl); +	io_input_pin(sda); +	 +	if (pull_up) +	{ +		io_set_pin(sda); +		io_set_pin(scl); +	} +*/ +	_i2c_abort_safe(scl, pull_up); +	_i2c_abort_safe(sda, pull_up); + +	//_i2c_abort_safe_ex(scl, pull_up); +	//_i2c_abort_safe_ex(sda, pull_up); +} +/* +static void _i2c_abort(io_pin_t sda, io_pin_t scl) +{ +	_i2c_abort_ex(sda, scl, false); +} +*/ +static bool _i2c_write_byte_ex(io_pin_t sda, io_pin_t scl, uint8_t value, bool pull_up) +{ +    // Assumes: +    //  SDA output is LOW +    //  SCL output is LOW + +    for (uint8_t i = 0; i < 8; ++i) +    { +		bool b = ((value & (0x01 << (7 - i))) != 0x00);	// MSB first +		 +		if (b) +		{ +			if (pull_up) +			{ +//				io_set_pin(sda);	// This is bad (will drive line for a moment), but more stable than letting line float +				io_input_pin(sda); +				io_set_pin(sda); +			}				 +			else +				io_input_pin(sda);	// Release HIGH +			 +			if (io_test_pin(sda) == false) +			{ +				debug_log("I2C:WR "); +				debug_log_hex(sda); +				debug_blink_rev(1); +				return false; +			}			 +		}			 +		else +		{ +			if (pull_up) +			{ +//				if (io_is_output(sda)) +					io_clear_pin(sda); +//				else +//				{ +					io_output_pin(sda);	// [This is bad (will drive line for a moment), but more stable than letting line float] +//					io_clear_pin(sda); +//				} +			} +			else +			{ +				io_enable_pin(sda, false); +				io_output_pin(sda);	// Drive LOW +			}				 +		} +		 +		/////////////////////////////// + +        io_input_pin(scl);	// Release HIGH +		if (pull_up) +			io_set_pin(scl); +        I2C_DELAY(I2C_DEFAULT_SCL_HIGH_PERIOD); +#ifdef I2C_ALLOW_CLOCK_STRETCH +		uint8_t retries = I2C_DEFAULT_MAX_BUS_RETRIES; +		while (io_test_pin(scl) == false)	// Clock stretch requested? +		{ +			I2C_DELAY(I2C_DEFAULT_BUS_WAIT); +			if (--retries == 0) +			{ +				io_input_pin(sda);	// Release HIGH +				if (pull_up) +					io_set_pin(sda); +				 +				debug_log_ex("I2C:STRTCH ", false); +				debug_log_hex(scl); +				debug_blink_rev(2); +				return false; +			} +		} +#endif // I2C_ALLOW_CLOCK_STRETCH +		if (pull_up) +			io_clear_pin(scl); +        io_output_pin(scl);	// Drive LOW +        I2C_DELAY(I2C_DEFAULT_SCL_LOW_PERIOD); +    } + +    io_input_pin(sda);	// Release HIGH +	if (pull_up) +		io_set_pin(sda);	// Assuming letting line float won't confuse slave when pulling line LOW for ACK +    I2C_DELAY(I2C_DEFAULT_SCL_HIGH_PERIOD); + +    uint8_t retries = 0; +    while ((_i2c_disable_ack_check == false) && (io_test_pin(sda))) +    { +        if (retries == I2C_DEFAULT_MAX_ACK_RETRIES) +		{ +			debug_log_ex("I2C:ACK ", false); +			debug_log_hex_ex(sda, false); +			debug_log_hex(value); +			debug_blink_rev(3); +            return false;	// Will abort and not release bus - done by caller +		} + +        ++retries; +        I2C_DELAY(I2C_DEFAULT_RETRY_DELAY); +    } + +    // Clock away acknowledge +//	if (pull_up) +//		io_set_pin(scl); +    io_input_pin(scl);	// Release HIGH +	if (pull_up) +		io_set_pin(scl); +    I2C_DELAY(I2C_DEFAULT_SCL_HIGH_PERIOD); + +	if (pull_up) +		io_clear_pin(scl); +    io_output_pin(scl);	// Drive LOW +//	if (pull_up) +//		io_clear_pin(scl); +    I2C_DELAY(I2C_DEFAULT_SCL_LOW_PERIOD); + +    return true; +} + +static bool _i2c_read_byte_ex(io_pin_t sda, io_pin_t scl, uint8_t* value, bool pull_up) +{ +    // Assumes: +    //  SDA output is LOW +    //  SCL output is LOW +	 +	io_input_pin(sda); +	if (pull_up) +		io_set_pin(sda);	// OK to leave line floating for a moment (better not to drive as slave will be pulling it to ground) + +    (*value) = 0x00; + +    for (uint8_t i = 0; i < 8; ++i) +    { +//		if (pull_up) +//			io_set_pin(scl);	// [Not ideal with pull-up] +        io_input_pin(scl);	// Release HIGH +		if (pull_up) +			io_set_pin(scl); +        I2C_DELAY(I2C_DEFAULT_SCL_HIGH_PERIOD); +#ifdef I2C_ALLOW_CLOCK_STRETCH +		uint8_t retries = I2C_DEFAULT_MAX_BUS_RETRIES; +		while (io_test_pin(scl) == false)	// Clock stretch requested? +		{ +			I2C_DELAY(I2C_DEFAULT_BUS_WAIT); +			if (--retries == 0) +			{ +				debug_log_ex("I2C:R "); +				debug_log_hex(scl); +				debug_blink_rev(5); +				return false; +			}				 +		} +#endif // I2C_ALLOW_CLOCK_STRETCH +        (*value) |= ((io_test_pin(sda) ? 0x1 : 0x0) << (7 - i));   // MSB first + +		if (pull_up) +			io_clear_pin(scl); +        io_output_pin(scl);	// Drive LOW (not ideal with pull-up) +//		if (pull_up) +//			io_clear_pin(scl); +        I2C_DELAY(I2C_DEFAULT_SCL_LOW_PERIOD); +    } + +    // Not necessary to ACK since it's only this one byte + +    return true; +} + +bool i2c_read2_ex(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t* value, bool pull_up) +{ +	if (_i2c_start_ex(sda, scl, pull_up) == false) +		return false; + +	if (_i2c_write_byte_ex(sda, scl, addr & ~0x01, pull_up) == false) +	{ +#ifdef I2C_EXTRA_DEBUGGING +		//debug_log_ex("R21:", false); +		debug_log("R21"); +		//debug_log_hex(addr); +#endif // I2C_EXTRA_DEBUGGING +		goto i2c_read2_fail; +	} + +	if (_i2c_write_byte_ex(sda, scl, subaddr, pull_up) == false) +	{ +#ifdef I2C_EXTRA_DEBUGGING +		//debug_log_ex("R22:", false); +		debug_log("R22"); +		//debug_log_hex(subaddr); +#endif // I2C_EXTRA_DEBUGGING +		goto i2c_read2_fail; +	} +	 +	io_input_pin(scl); +	if (pull_up) +		io_set_pin(scl); +	I2C_DELAY(I2C_DEFAULT_BUS_WAIT); +	 +	if (_i2c_start_ex(sda, scl, pull_up) == false) +	{ +		return false; +	} +	 +	if (_i2c_write_byte_ex(sda, scl, addr | 0x01, pull_up) == false) +	{ +#ifdef I2C_EXTRA_DEBUGGING +		//debug_log_ex("R23:", false); +		debug_log("R23"); +		//debug_log_hex(addr); +#endif // I2C_EXTRA_DEBUGGING +		goto i2c_read2_fail; +	} + +	if (_i2c_read_byte_ex(sda, scl, value, pull_up) == false) +	{ +#ifdef I2C_EXTRA_DEBUGGING +		//debug_log_ex("R24:", false); +		debug_log("R24"); +		//debug_log_hex(*value); +#endif // I2C_EXTRA_DEBUGGING +		goto i2c_read2_fail; +	} +	 +	if (_i2c_stop_ex(sda, scl, pull_up) == false) +	{ +#ifdef I2C_EXTRA_DEBUGGING +		debug_log("R25"); +#endif // I2C_EXTRA_DEBUGGING +	} + +	return true; +i2c_read2_fail: +	_i2c_abort_ex(sda, scl, pull_up); +	return false; +} + +bool i2c_write_ex(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t value, bool pull_up) +{ +	if (_i2c_start_ex(sda, scl, pull_up) == false) +		return false; + +    if (_i2c_write_byte_ex(sda, scl, addr, pull_up) == false) +        goto i2c_write_fail; + +    if (_i2c_write_byte_ex(sda, scl, subaddr, pull_up) == false) +        goto i2c_write_fail; + +    if (_i2c_write_byte_ex(sda, scl, value, pull_up) == false) +        goto i2c_write_fail; + +    _i2c_stop_ex(sda, scl, pull_up); + +    return true; +i2c_write_fail: +	_i2c_abort_ex(sda, scl, pull_up); +	return false; +} + +bool i2c_write(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t value) +{ +	return i2c_write_ex(sda, scl, addr, subaddr, value, false); +} + +bool i2c_read_ex(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t* value, bool pull_up) +{ +    if (_i2c_start_ex(sda, scl, pull_up) == false) +		return false; + +    if (_i2c_write_byte_ex(sda, scl, addr, pull_up) == false) +        goto i2c_read_fail; + +    if (_i2c_write_byte_ex(sda, scl, subaddr, pull_up) == false) +        goto i2c_read_fail; + +    if (_i2c_read_byte_ex(sda, scl, value, pull_up) == false) +        goto i2c_read_fail; + +    _i2c_stop_ex(sda, scl, pull_up); + +    return true; +i2c_read_fail: +	_i2c_abort_ex(sda, scl, pull_up); +	return false; +} + +bool i2c_read(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t* value) +{ +	return i2c_read_ex(sda, scl, addr, subaddr, value, false); +} + +void i2c_init_ex(io_pin_t sda, io_pin_t scl, bool pull_up) +{ +	_i2c_abort_ex(sda, scl, pull_up); +} + +void i2c_init(io_pin_t sda, io_pin_t scl) +{ +	i2c_init_ex(sda, scl, false); +} diff --git a/firmware/e300/rev_c/i2c.h b/firmware/e300/rev_c/i2c.h new file mode 100644 index 000000000..5898e7e43 --- /dev/null +++ b/firmware/e300/rev_c/i2c.h @@ -0,0 +1,17 @@ +#ifndef I2C_H +#define I2C_H + +#include "io.h" + +void i2c_init(io_pin_t sda, io_pin_t scl); +bool i2c_read(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t* value); +bool i2c_write(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t value); + +void i2c_init_ex(io_pin_t sda, io_pin_t scl, bool pull_up); +bool i2c_read_ex(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t* value, bool pull_up); +bool i2c_read2_ex(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t* value, bool pull_up); +bool i2c_write_ex(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t value, bool pull_up); + +extern volatile bool _i2c_disable_ack_check; + +#endif // I2C_H diff --git a/firmware/e300/rev_c/io.c b/firmware/e300/rev_c/io.c new file mode 100644 index 000000000..0256181c6 --- /dev/null +++ b/firmware/e300/rev_c/io.c @@ -0,0 +1,75 @@ +/* + * Copyright 2009-2012 Ettus Research LLC + */ + +#include "io.h" +#include <avr/io.h> + +#define _GET_PIN(pin)           ((pin) & 0xf) +#define _GET_MASK(pin)          (_BV(_GET_PIN(pin))) +#define _GET_REG(pin, reg_x)    (*reg_x[pin >> 4]) + +#ifndef IO_DEBUG +static volatile uint8_t *ddr_x[] = {&DDRA, &DDRB, &DDRC, &DDRD};		// 0: input, 1: output +static volatile uint8_t *port_x[] = {&PORTA, &PORTB, &PORTC, &PORTD};	// Port contents (will appear at output if direction is set to output. If input, '1' enables pull-ups, '0' set tri-state) +static volatile uint8_t *pin_x[] = {&PINA, &PINB, &PINC, &PIND};		// Port contents (input) If output, will return value on PORT +#endif + +void io_output_pin(io_pin_t pin){ +#ifndef IO_DEBUG +	_GET_REG(pin, ddr_x) |= _GET_MASK(pin); +#endif +} + +void io_input_pin(io_pin_t pin){ +#ifndef IO_DEBUG +	_GET_REG(pin, ddr_x) &= ~_GET_MASK(pin); +#endif +} + +bool io_is_output(io_pin_t pin){ +#ifndef IO_DEBUG +	return bit_is_set(_GET_REG(pin, ddr_x), _GET_PIN(pin)); +#else +	return 0; +#endif +} + +bool io_is_input(io_pin_t pin){ +	return !io_is_output(pin); +} + +void io_set_pin(io_pin_t pin){	// In input mode, will enable pull-ups +#ifndef IO_DEBUG +	_GET_REG(pin, port_x) |= _GET_MASK(pin); +#endif +} + +void io_clear_pin(io_pin_t pin){	// In input mode, will disable pull-ups +#ifndef IO_DEBUG +	_GET_REG(pin, port_x) &= ~_GET_MASK(pin); +#endif +} + +bool io_is_pin_set(io_pin_t pin){ +#ifndef IO_DEBUG +	return bit_is_set(_GET_REG(pin, port_x), _GET_PIN(pin)); +#else +	return 0; +#endif +} + +void io_enable_pin(io_pin_t pin, bool enable){ +    if (enable) +        io_set_pin(pin); +    else +        io_clear_pin(pin); +} + +bool io_test_pin(io_pin_t pin){ +#ifndef IO_DEBUG +	return bit_is_set(_GET_REG(pin, pin_x), _GET_PIN(pin)); +#else +	return 0; +#endif +} diff --git a/firmware/e300/rev_c/io.h b/firmware/e300/rev_c/io.h new file mode 100644 index 000000000..7eea8f0a3 --- /dev/null +++ b/firmware/e300/rev_c/io.h @@ -0,0 +1,31 @@ +/* + * Copyright 2009 Ettus Research LLC + */ + +#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; + +void io_output_pin(io_pin_t pin); +void io_input_pin(io_pin_t pin); +bool io_is_output(io_pin_t pin); +bool io_is_input(io_pin_t pin); + +void io_set_pin(io_pin_t pin); +void io_clear_pin(io_pin_t pin); +void io_enable_pin(io_pin_t pin, bool enable); +bool io_is_pin_set(io_pin_t pin); + +bool io_test_pin(io_pin_t pin); + +#endif /* IO_H */ diff --git a/firmware/e300/rev_c/ltc3675.c b/firmware/e300/rev_c/ltc3675.c new file mode 100644 index 000000000..0f85ec7e5 --- /dev/null +++ b/firmware/e300/rev_c/ltc3675.c @@ -0,0 +1,525 @@ +/* + * Copyright 2012 Ettus Research LLC + */ + +/* +    ? STOP condition after writing address on read +    - Default buck/boost register values are OK +*/ + +#include "config.h" +#include "ltc3675.h" + +//#include <stdio.h> +#include <util/delay.h> +#include <avr/interrupt.h> + +#include "io.h" +#include "i2c.h" +#include "debug.h" +#include "global.h" +#include "error.h" + +#ifndef I2C_REWORK +#include "power.h" +#endif // I2C_REWORK + +const bool _ltc3675_pull_up = +#ifdef I2C_REWORK +	true +#else +	false +#endif // I2C_REWORK +; + +volatile ltc3675_reg_helper_fn _ltc3675_reg_helper; + +//#define HARDWIRE_ENABLE	// Use hardware enable pins instead of I2C on regulators that support it + +#ifdef ATTINY88_DIP + +#ifdef HARDWIRE_ENABLE +static io_pin_t PWR_EN1     = IO_PC(7);	// Not routed by card +static io_pin_t PWR_EN2     = IO_PA(0);	// Not available on DIP +static io_pin_t PWR_EN3     = IO_PA(1);	// Not available on DIP +static io_pin_t PWR_EN4     = IO_PB(6);	// Instead of FTDI_BCD +static io_pin_t PWR_EN5     = IO_PB(7);	// Instead of FTDI_PWREN2 +#endif // HARDWIRE_ENABLE + +//static io_pin_t PWR_SDA     = IO_PC(4); +//static io_pin_t PWR_SCL     = IO_PC(5); + +#else + +#ifdef HARDWIRE_ENABLE +static io_pin_t PWR_EN1     = IO_PC(1); +//static io_pin_t PWR_EN2     = IO_PC(2);	// Now used by I2C for charge controller +//static io_pin_t PWR_EN3     = IO_PC(3);	// Now used by I2C for charge controller +static io_pin_t PWR_EN4     = IO_PA(1); +static io_pin_t PWR_EN5     = IO_PA(2); +#endif // HARDWIRE_ENABLE + +#ifdef I2C_REWORK +static io_pin_t PWR_SDA     = IO_PC(2);		// Instead of EN5 +static io_pin_t PWR_SCL     = IO_PA(2);		// Instead of EN2 +#endif // I2C_REWORK + +#endif // ATTINY88_DIP + +static io_pin_t PWR_IRQ     = IO_PD(0); +static io_pin_t WAKEUP      = IO_PD(2); +static io_pin_t ONSWITCH_DB = IO_PD(3); +static io_pin_t PWR_RESET   = IO_PD(4); + +#define LTC3675_BASE_ADDRESS    0x12 +#define LTC3675_WRITE_ADDRESS   (LTC3675_BASE_ADDRESS + 0) +#define LTC3675_READ_ADDRESS    (LTC3675_BASE_ADDRESS + 1) + +#define LTC3675_RETRY_DELAY     1   // us MAGIC +#define LTC3675_MAX_ACK_RETRIES 10  // * LTC3675_RETRY_DELAY us + +#define LTC3675_SCL_LOW_PERIOD  2   // 1.3 us +#define LTC3675_SCL_HIGH_PERIOD 1   // 0.6 us +#define LTC3675_BUS_FREE_TIME   2   // 1.3 us +#define LTC3675_STOP_TIME       1   // 0.6 us + +#define LTC3675_REGULATOR_ENABLE_DELAY	10	// 50	// ms (some arbitrary value so that the external power supply can settle) + +enum LTC3675Registers +{ +	LTC3675_REG_NONE			= 0x00, +	LTC3675_REG_BUCK1			= 0x01, +	LTC3675_REG_BUCK2			= 0x02, +	LTC3675_REG_BUCK3			= 0x03, +	LTC3675_REG_BUCK4			= 0x04, +	LTC3675_REG_BOOST			= 0x05, +	LTC3675_REG_BUCK_BOOST		= 0x06, +	LTC3675_REG_LED_CONFIG		= 0x07, +	LTC3675_REG_LED_DAC			= 0x08, +	LTC3675_REG_UVOT			= 0x09, +	LTC3675_REG_RSTB			= 0xA0, +	LTC3675_REG_IRQB_MASK		= 0x0B, +	LTC3675_REG_REALTIME_STATUS	= 0x0C, +	LTC3675_REG_LATCHED_STATUS	= 0x0D, +	LTC3675_REG_CLEAR_IRQ		= 0x0F +}; + +enum LTC3675StatusBits +{ +	LTC3675_UnderVoltage	= 1 << 7, +	LTC3675_OverTemperature	= 1 << 6, +	LTC3675_BuckBoost_PGood	= 1 << 5, +	LTC3675_Boost_PGood		= 1 << 4, +	LTC3675_Buck4_PGood		= 1 << 3, +	LTC3675_Buck3_PGood		= 1 << 2, +	LTC3675_Buck2_PGood		= 1 << 1, +	LTC3675_Buck1_PGood		= 1 << 0 +}; + +#define LTC3675_DEFAULT_BUCK_REG_VAL		0x6F +#define LTC3675_DEFAULT_BOOST_REG_VAL		0x0F +#define LTC3675_DEFAULT_BUCK_BOOST_REG_VAL	0x0F + +#define LTC3675_ENABLE_REGISTER_BIT			0x80 + +// Max I2C rate = 400kHz + +static void _ltc3675_clear_irq() +{ +	// Two-stage clear +	i2c_write_ex(PWR_SDA, PWR_SCL, LTC3675_WRITE_ADDRESS, LTC3675_REG_CLEAR_IRQ, 0x00, _ltc3675_pull_up); +	i2c_write_ex(PWR_SDA, PWR_SCL, LTC3675_WRITE_ADDRESS, LTC3675_REG_NONE, 0x00, _ltc3675_pull_up); +} + +volatile uint8_t _ltc3675_last_status = 0x00; + +uint8_t ltc3675_get_last_status(void) +{ +	return _ltc3675_last_status; +} + +uint8_t ltc3675_reg_status_to_error(uint8_t val) +{ +	if (((val & LTC3675_BuckBoost_PGood) == 0) && ((_ltc3675_reg_helper)(LTC3675_REG_6))) +		return BlinkError_3_3V_Peripherals_Power; +	 +	if (((val & LTC3675_Boost_PGood) == 0) && ((_ltc3675_reg_helper)(LTC3675_REG_5))) +		return BlinkError_TX_Power; +	 +	//if (((val & LTC3675_Buck4_PGood) == 0) && ((_ltc3675_reg_helper)(LTC3675_REG_4))) +	 +	if (((val & LTC3675_Buck3_PGood) == 0) && ((_ltc3675_reg_helper)(LTC3675_REG_3))) +		return BlinkError_1_8V_Peripherals_Power; +	 +	//if (((val & LTC3675_Buck2_PGood) == 0) && ((_ltc3675_reg_helper)(LTC3675_REG_2))) +	 +	if (((val & LTC3675_Buck1_PGood) == 0) && ((_ltc3675_reg_helper)(LTC3675_REG_1))) +		return BlinkError_DRAM_Power; +	 +	return BlinkError_None; +} + +bool ltc3675_is_power_good(uint8_t val) +{ +	return (ltc3675_reg_status_to_error(val) == BlinkError_None); +} + +uint8_t ltc3675_status_to_error(uint8_t val) +{ +	if (val & LTC3675_UnderVoltage) +		return BlinkError_LTC3675_UnderVoltage; +	 +	if (val & LTC3675_OverTemperature) +		return BlinkError_LTC3675_OverTemperature; +	 +	uint8_t reg_error = ltc3675_reg_status_to_error(val); +	if (reg_error != BlinkError_None) +		return reg_error; +	 +	return BlinkError_None; +} + +bool _ltc3675_handle_irq(void) +{ +	uint8_t val = 0x00; +	bool result = false; +	 +	if (i2c_read2_ex(PWR_SDA, PWR_SCL, LTC3675_READ_ADDRESS, LTC3675_REG_LATCHED_STATUS, &val, _ltc3675_pull_up)) +	{ +		debug_log_ex("3675LTCH ", false); +		debug_log_hex(val); +	} +	 +	if (i2c_read2_ex(PWR_SDA, PWR_SCL, LTC3675_READ_ADDRESS, /*LTC3675_REG_LATCHED_STATUS*/LTC3675_REG_REALTIME_STATUS, &val, _ltc3675_pull_up))	// No point acting on latched because could have been resolved +	{ +		//debug_log_ex("3675LTCH ", false); +		debug_log_ex("3675RT ", false); +		debug_log_hex(val); +		 +		_ltc3675_last_status = val; +		 +		uint8_t error = ltc3675_status_to_error(val); +		 +		/*if (val & LTC3675_UnderVoltage) +		{ +			pmc_set_blink_error(BlinkError_LTC3675_UnderVoltage); +			//_state.low_battery = true; +		}*/ +		 +		if (error) +		{ +			pmc_set_blink_error(error); +			 +			/*_i2c_disable_ack_check = true; +			uint8_t chk = 0x00; +			chk |= (_ltc3675_reg_helper)(LTC3675_REG_6) << 0; +			chk |= (_ltc3675_reg_helper)(LTC3675_REG_5) << 1; +			chk |= (_ltc3675_reg_helper)(LTC3675_REG_3) << 2; +			chk |= (_ltc3675_reg_helper)(LTC3675_REG_1) << 3; +			i2c_write_ex(PWR_SDA, PWR_SCL, 0xFE, 0xFF, chk, _ltc3675_pull_up); +			_i2c_disable_ack_check = false;*/ +		}			 +		 +		result = true; +	} +	 +	_ltc3675_clear_irq(); +	 +	return result; +} + +static bool _ltc3675_get_realtime_status(uint8_t* val) +{ +	//cli(); +	 +	if (i2c_read2_ex(PWR_SDA, PWR_SCL, LTC3675_READ_ADDRESS, LTC3675_REG_REALTIME_STATUS, val, _ltc3675_pull_up) == false) +		return false; +	 +	debug_log_ex("3675RT ", false); +	debug_log_hex(*val); +	 +	//sei(); +	 +	return true; +} + +int8_t ltc3675_check_status(void) +{ +	uint8_t val = 0x00; +	 +	pmc_mask_irqs(true); +	 +	bool result = _ltc3675_get_realtime_status(&val); +	 +	pmc_mask_irqs(false); +	 +	if (result == false) +		return -1; +	 +	//_ltc3675_last_status = val; +	 +	/*if (val & LTC3675_UnderVoltage) +		return BlinkError_LTC3675_UnderVoltage; +	 +	if (val & LTC3675_OverTemperature) +		return BlinkError_LTC3675_OverTemperature; +	 +	return BlinkError_None;*/ +	 +	return ltc3675_status_to_error(val); +} + +bool ltc3675_handle_irq(void) +{ +	pmc_mask_irqs(true); +	 +	/*uint8_t*/bool result = _ltc3675_handle_irq(); +	 +	pmc_mask_irqs(false); +	 +	return result; +} + +static bool _ltc3675_default_reg_helper(uint8_t address) +{ +	uint8_t val = 0x00; +	i2c_read2_ex(PWR_SDA, PWR_SCL, LTC3675_READ_ADDRESS, address, &val, _ltc3675_pull_up); +	return ((val & LTC3675_ENABLE_REGISTER_BIT) == LTC3675_ENABLE_REGISTER_BIT); +} + +bool ltc3675_init(ltc3675_reg_helper_fn helper) +{ +	if (helper) +		_ltc3675_reg_helper = helper; +	else +		_ltc3675_reg_helper = _ltc3675_default_reg_helper; +#ifdef HARDWIRE_ENABLE +    io_output_pin(PWR_EN1); +    io_output_pin(PWR_EN2); +    io_output_pin(PWR_EN3); +    io_output_pin(PWR_EN4); +    io_output_pin(PWR_EN5); +#endif // HARDWIRE_ENABLE + + /*	io_output_pin(PWR_SDA); +    io_output_pin(PWR_SCL); + +    // Must remain HIGH when idle +    io_set_pin(PWR_SDA); +    io_set_pin(PWR_SCL); +*/ +#ifdef I2C_REWORK +	i2c_init_ex(PWR_SDA, PWR_SCL, _ltc3675_pull_up); +#endif // I2C_REWORK +    io_input_pin(PWR_IRQ); +#if !defined(DEBUG) && !defined(ATTINY88_DIP) +	io_set_pin(PWR_IRQ);	// Enable pull-up for Open Drain +#endif // DEBUG +	 +    io_input_pin(WAKEUP); +	io_set_pin(WAKEUP);	// Enable pull-up for Open Drain +	 +    io_input_pin(ONSWITCH_DB); +	io_set_pin(ONSWITCH_DB);	// Enable pull-up for Open Drain +	 +    io_input_pin(PWR_RESET); +	io_set_pin(PWR_RESET);	// Enable pull-up for Open Drain +	 +	_ltc3675_clear_irq();	// Clear old interrupt - state might have changed (e.g. undervoltage might have been resolved) + +    if (i2c_write_ex(PWR_SDA, PWR_SCL, LTC3675_WRITE_ADDRESS, LTC3675_REG_IRQB_MASK, 0xFF, _ltc3675_pull_up) == false)	// Any PGOOD fault will pull IRQB low +		return false; +	 +	if (i2c_write_ex(PWR_SDA, PWR_SCL, LTC3675_WRITE_ADDRESS, LTC3675_REG_UVOT, 0x70, _ltc3675_pull_up) == false)	// 3.4V UV +		return false; +	 +	if (ltc3675_has_interrupt()) +		_ltc3675_handle_irq(); +	 +	// Non-maskable: +	//	UV warning threshold (default): 2.7V +	//	Over temp warning threshold (default): 10 degrees below + +    return true; +} + +bool ltc3675_is_waking_up(void) +{ +	return io_test_pin(WAKEUP); +} + +static bool _ltc3675_is_pgood(uint8_t reg) +{ +	uint8_t val = 0x00; +	if (_ltc3675_get_realtime_status(&val) == false) +		return false; +	return ((reg & val) == reg); +} + +static bool _ltc3675_toggle_reg(uint8_t addr, uint8_t def_reg, bool on) +{ +	bool result = true; +	 +	//cli(); +	 +	uint8_t val = 0x00 | def_reg; +	if (i2c_read2_ex(PWR_SDA, PWR_SCL, LTC3675_READ_ADDRESS, addr, &val, _ltc3675_pull_up) == false) +		return false; +	 +	val &= ~LTC3675_ENABLE_REGISTER_BIT; +	 +	if (i2c_write_ex(PWR_SDA, PWR_SCL, LTC3675_WRITE_ADDRESS, addr, /*def_reg*/val | (on ? LTC3675_ENABLE_REGISTER_BIT : 0x00), _ltc3675_pull_up) == false) +		//return true; +		result = false; +	 +	if (on) +	{ +		_delay_ms(LTC3675_REGULATOR_ENABLE_DELAY); +	} +	 +	//sei(); + +	return result; +	//return true; +} + +bool ltc3675_enable_reg(ltc3675_regulator_t reg, bool on) +{ +	//debug_blink2(reg + 1); +	debug_log_ex("3675 ", false); +	debug_log_byte_ex(reg, true); +	 +	// Sub-address: index of regulator +	// Data: <default reg contents> | <enable> +	 +	bool result = false; +	 +    switch (reg) +    { +        case LTC3675_REG_1: // Master +        case LTC3675_REG_2: // Slave +#ifdef HARDWIRE_ENABLE +            io_enable_pin(PWR_EN1, on); +			//break; +#else +			//debug_blink2(reg + 1); +			if (_ltc3675_toggle_reg(LTC3675_REG_BUCK1, LTC3675_DEFAULT_BUCK_REG_VAL, on) == false) { +				//debug_blink2(reg + 1); +				return false; +			} +			//debug_blink2(reg + 1); +#endif // HARDWIRE_ENABLE +			result = (_ltc3675_is_pgood(LTC3675_Buck1_PGood) == on); +			break; +        case LTC3675_REG_3: // Master +        case LTC3675_REG_4: // Slave +#ifdef HARDWIRE_ENABLE +            io_enable_pin(PWR_EN3, on); +            //break; +#else +			if (_ltc3675_toggle_reg(LTC3675_REG_BUCK3, LTC3675_DEFAULT_BUCK_REG_VAL, on) == false) +				return false; +#endif // HARDWIRE_ENABLE +			result = (_ltc3675_is_pgood(LTC3675_Buck3_PGood) == on); +			break; +        case LTC3675_REG_5: // I2C only +            if (_ltc3675_toggle_reg(LTC3675_REG_BOOST, LTC3675_DEFAULT_BOOST_REG_VAL, on) == false)    // (Boost address, Default reg contents | Enable) +				return false; +			result = (_ltc3675_is_pgood(LTC3675_Boost_PGood) == on); +			break; +        case LTC3675_REG_6: // Single +#ifdef HARDWIRE_ENABLE +            io_enable_pin(PWR_EN5, on); +            //break; +#else +			if (_ltc3675_toggle_reg(LTC3675_REG_BUCK_BOOST, LTC3675_DEFAULT_BUCK_BOOST_REG_VAL, on) == false) +				return false; +#endif // HARDWIRE_ENABLE +			result = (_ltc3675_is_pgood(LTC3675_BuckBoost_PGood) == on); +			break; +        //default: +		//	return false; +    } +	 +	_debug_log((result ? "+" : "-")); + +    return result; +} + +bool ltc3675_set_voltage(ltc3675_regulator_t reg, uint16_t voltage) +{ +    // Not necessary due to R-bridges and default DAC registers + +    // VRAM will be 1.3579 - a little high? (re-program DAC reference) +    //  No: minimum FB step will put Vout < 1.35 +	 +	uint16_t max_voltage = 0; +	uint8_t reg_subaddr = 0; +	 +	switch (reg) +	{ +		case LTC3675_REG_1:	// 1A Buck +		case LTC3675_REG_2:	// 1A Buck +			max_voltage = 1500; +			reg_subaddr = LTC3675_REG_BUCK1; +			break; +		case LTC3675_REG_3:	// 500mA Buck +		case LTC3675_REG_4:	// 500mA Buck +			max_voltage = 1800; +			reg_subaddr = LTC3675_REG_BUCK3; +			break; +		case LTC3675_REG_5:	// 1A Boost +			max_voltage = 5000; +			reg_subaddr = LTC3675_REG_BOOST; +			break; +		case LTC3675_REG_6:	// 1A Buck-Boost +			max_voltage = 3300; +			reg_subaddr = LTC3675_REG_BUCK_BOOST; +			break; +	} +	 +	if (voltage > max_voltage) +		return false; +	 +	//uint32_t rMax = ((uint32_t)voltage * 1000) / (uint32_t)max_voltage; +	//uint32_t rFB = ((uint32_t)max_voltage * 1000) / (uint32_t)800; +	uint32_t rFB = ((uint32_t)max_voltage * 1000) / (uint32_t)800;	// 800mV full-scale feedback voltage +	uint32_t r = ((uint32_t)voltage * 1000) / (uint32_t)rFB; +	if (r < 450) +		return false; +	 +	uint16_t rDAC = (16 * ((uint16_t)r - 450)) / (800 - 450); +	 +	debug_log_ex("Vr ", false); +	debug_log_byte_ex(reg, false); +	debug_log_ex("=", false); +	debug_log_byte_ex((uint8_t)rDAC, false); +	 +	uint8_t val = 0x00; +	if (i2c_read2_ex(PWR_SDA, PWR_SCL, LTC3675_READ_ADDRESS, reg_subaddr, &val, _ltc3675_pull_up) == false) +	{ +		debug_log("-"); +		return false; +	}		 +	 +	val = (val & 0xF0) | (uint8_t)rDAC; +	if (i2c_write_ex(PWR_SDA, PWR_SCL, LTC3675_WRITE_ADDRESS, reg_subaddr, val, _ltc3675_pull_up) == false) +	{ +		debug_log("-"); +		return false; +	} +	 +	debug_log("+"); + +	return true; +} + +bool ltc3675_is_power_button_depressed(void) +{ +	return (io_test_pin(ONSWITCH_DB) == false); +} + +bool ltc3675_has_interrupt(void) +{ +	return (io_test_pin(PWR_IRQ) == false); +} diff --git a/firmware/e300/rev_c/ltc3675.h b/firmware/e300/rev_c/ltc3675.h new file mode 100644 index 000000000..d7c4baa59 --- /dev/null +++ b/firmware/e300/rev_c/ltc3675.h @@ -0,0 +1,37 @@ +/* + * Copyright 2012 Ettus Research LLC + */ + +#ifndef LTC3675_H +#define LTC3675_H + +//#include "types.h" +#include <stdbool.h> +#include <stdint.h> + +typedef bool (*ltc3675_reg_helper_fn)(uint8_t address); + +bool ltc3675_init(ltc3675_reg_helper_fn helper); + +typedef enum ltc3675_regulators { +    LTC3675_REG_1,  // 1A Buck +    LTC3675_REG_2,  // 1A Buck +    LTC3675_REG_3,  // 500mA Buck +    LTC3675_REG_4,  // 500mA Buck +    LTC3675_REG_5,  // 1A Boost +    LTC3675_REG_6   // 1A Buck-Boost +    // LED Boost +} ltc3675_regulator_t; + +bool ltc3675_enable_reg(ltc3675_regulator_t reg, bool on); +bool ltc3675_set_voltage(ltc3675_regulator_t reg, uint16_t voltage); +bool ltc3675_is_power_button_depressed(void); +bool ltc3675_has_interrupt(void); +bool ltc3675_handle_irq(void); +int8_t ltc3675_check_status(void); +uint8_t ltc3675_get_last_status(void); +uint8_t ltc3675_status_to_error(uint8_t val); +bool ltc3675_is_power_good(uint8_t val); +bool ltc3675_is_waking_up(void); + +#endif /* LTC3675_H */ diff --git a/firmware/e300/rev_c/ltc4155.c b/firmware/e300/rev_c/ltc4155.c new file mode 100644 index 000000000..5f404e651 --- /dev/null +++ b/firmware/e300/rev_c/ltc4155.c @@ -0,0 +1,402 @@ +/* + * ltc4155.c + */  + +#ifndef CHARGER_TI + +#include "config.h" +#include "ltc4155.h" + +#include <util/delay.h> + +#include "io.h" +#include "i2c.h" +#include "power.h" +#include "debug.h" +#include "global.h" +#include "error.h" + +static io_pin_t USBPM_IRQ	= IO_PB(1); + +#ifdef ATTINY88_DIP + +static io_pin_t CHRG_SDA     = IO_PC(2); +static io_pin_t CHRG_SCL     = IO_PC(3); + +#else + +#ifdef I2C_REWORK + +static io_pin_t CHRG_SDA     = IO_PC(4); +static io_pin_t CHRG_SCL     = IO_PC(5); + +#else + +#define CHRG_SDA	PWR_SDA +#define CHRG_SCL	PWR_SCL + +#endif // I2C_REWORK + +#endif // ATTINY88_DIP + +const bool _ltc4155_pull_up = false; + +#define LTC4155_BASE_ADDRESS    0x12 +#define LTC4155_WRITE_ADDRESS   (LTC4155_BASE_ADDRESS + 0) +#define LTC4155_READ_ADDRESS    (LTC4155_BASE_ADDRESS + 1) +/* +#define LTC4155_RETRY_DELAY     1   // us MAGIC +#define LTC4155_MAX_ACK_RETRIES 10  // * LTC4155_RETRY_DELAY us + +#define LTC4155_SCL_LOW_PERIOD  2   // 1.3 us +#define LTC4155_SCL_HIGH_PERIOD 1   // 0.6 us +#define LTC4155_BUS_FREE_TIME   2   // 1.3 us +#define LTC4155_STOP_TIME       1   // 0.6 us +*/ +enum LTC4155Registers +{ +	LTC4155_REG_USB			= 0x00,	// W/R +	LTC4155_REG_WALL		= 0x01,	// W/R +	LTC4155_REG_CHARGE		= 0x02,	// W/R +	LTC4155_REG_STATUS		= 0x03,	// R +	LTC4155_REG_GOOD		= 0x04,	// R +	LTC4155_REG_THERMISTOR	= 0x05,	// R +	LTC4155_REG_ENABLE		= 0x06,	// W/R +	LTC4155_REG_ARM_AND_SHIP= 0x07	// W +}; + +enum LTC4155InterruptMasks	// LTC4155_REG_ENABLE +{ +	LTC4155_ENABLE_USB_OTG	= 1 << 1, +	 +	LTC4155_INT_UVCL	= 1 << 2, +	LTC4155_INT_ILIMIT	= 1 << 3, +	LTC4155_INT_USB_OTG	= 1 << 4, +	LTC4155_INT_EXT_PWR	= 1 << 5, +	LTC4155_INT_FAULT	= 1 << 6, +	LTC4155_INT_CHARGER	= 1 << 7 +}; + +enum LTC4155Options	// LTC4155_REG_USB +{ +	LTC4155_USB_OTG_LOCKOUT				= 1 << 5, +	LTC4155_ENABLE_BATTERY_CONDITIONER	= 1 << 6, +	LTC4155_DISABLE_INPUT_UVCL			= 1 << 7 +}; + +enum LTC4155Shifts +{ +	LTC4155_SHIFTS_CHARGE_CURRENT_LIMIT	= 4, +	LTC4155_SHIFTS_CHARGE_FLOAT_VOLTAGE	= 2, +	LTC4155_SHIFTS_WALL_PRIORITY		= 7, +	LTC4155_SHIFTS_WALL_SAFETY_TIMER	= 5 +}; + +enum LTC4155Statuses	// LTC4155_REG_STATUS +{ +	LTC4155_LOW_BATTERY		= 1 << 0, +	LTC4155_BOOST_ENABLE	= 1 << 3, +	LTC4155_ID_PIN_DETECT	= 1 << 4, +}; + +enum LTC4155Goods	// LTC4155_REG_GOOD +{ +	LTC4155_BAD_CELL_FAULT		= 1 << 0, +	LTC4155_OTG_FAULT			= 1 << 1, +	LTC4155_OVP_ACTIVE			= 1 << 2, +	LTC4155_INPUT_UVCL_ACTIVE	= 1 << 3, +	LTC4155_INPUT_CURRENT_LIMIT_ACTIVE = 1 << 4, +	LTC4155_WALLSNS_GOOD		= 1 << 5, +	LTC4155_USBSNS_GOOD			= 1 << 6, +	LTC4155_EXTERNAL_POWER_GOOD	= 1 << 7 +}; + +enum LTC4155BatteryChargerStatues +{ +	LTC4155_CHARGER_OFF, +	LTC4155_CHARGER_LOW_BATTERY_VOLTAGE, +	LTC4155_CHARGER_CONSTANT_CURRENT, +	LTC4155_CHARGER_CONSTANT_VOLTAGE_VPROG_GT_VCX, +	LTC4155_CHARGER_CONSTANT_VOLTAGE_VPROG_LT_VCX, +	LTC4155_CHARGER_NTC_TOO_WARM, +	LTC4155_CHARGER_NTC_TOO_COLD, +	LTC4155_CHARGER_NTC_HOT +}; + +enum LTC4155ThermistorStatuses +{ +	LTC4155_NTC_NORMAL, +	LTC4155_NTC_TOO_COLD, +	LTC4155_NTC_TOO_WARM, +	LTC4155_NTC_FAULT +}; + +static const uint8_t _ltc4155_interrupt_mask = +//	LTC4155_ENABLE_USB_OTG |// Enable +5V on USB connector	// Is this causing the chip to power off the output?! +	LTC4155_INT_UVCL | +	LTC4155_INT_ILIMIT | +	LTC4155_INT_USB_OTG | +	LTC4155_INT_EXT_PWR |	// Turn up current limit +	LTC4155_INT_FAULT |		// Blink error +	LTC4155_INT_CHARGER;	// Illuminate charge LED + +static bool _ltc4155_clear_irq(void) +{ +	return i2c_write_ex(CHRG_SDA, CHRG_SCL, LTC4155_WRITE_ADDRESS, LTC4155_REG_ENABLE, _ltc4155_interrupt_mask, _ltc4155_pull_up); +} + +bool ltc4155_clear_irq(void) +{ +	pmc_mask_irqs(true); +	 +	bool result = _ltc4155_clear_irq(); +	 +	pmc_mask_irqs(false); +	 +	return result; +} + +static uint8_t _ltc4155_last_good, _ltc4155_last_status; + +bool _ltc4155_handle_irq(void) +{ +	_ltc4155_clear_irq();	// Clear frozen registers to get the real-time ones +	 +	_delay_ms(50);	// Wait for registers to clear/update +	 +	////////////////// +	 +	uint8_t val = 0x00; +	bool result = false; +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_GOOD, &val, _ltc4155_pull_up) == false) +		goto _ltc4155_handle_fail; +	 +	//if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_GOOD, &val, _ltc4155_pull_up) == false) +	//	goto _ltc4155_handle_fail; +	 +	debug_log_ex("4155GO ", false); +	debug_log_hex(val); +	 +	if (val & LTC4155_WALLSNS_GOOD) +	{ +		uint8_t wall_state = 0; +		if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_WALL, &wall_state, _ltc4155_pull_up) == false) +			goto _ltc4155_handle_fail; +		 +		wall_state &= ~0x1E; +		wall_state |= 0x0E; +		 +		if (i2c_write_ex(CHRG_SDA, CHRG_SCL, LTC4155_WRITE_ADDRESS, LTC4155_REG_WALL, wall_state, _ltc4155_pull_up) == false) +			goto _ltc4155_handle_fail; +		 +		debug_log("I+"); +	} +	 +	_ltc4155_last_good = val; +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_STATUS, &val, _ltc4155_pull_up) == false) +		goto _ltc4155_handle_fail; +	 +	//if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_STATUS, &val, _ltc4155_pull_up) == false) +	//	goto _ltc4155_handle_fail; +	 +	debug_log_ex("4155ST ", false); +	debug_log_hex(val); +	 +	_ltc4155_last_status = val; +	 +	val >>= 5; +	 +	if (_state.blink_error == BlinkError_None) +	{ +		switch (val) +		{ +			case LTC4155_CHARGER_CONSTANT_CURRENT: +			case LTC4155_CHARGER_CONSTANT_VOLTAGE_VPROG_GT_VCX: +			case LTC4155_CHARGER_LOW_BATTERY_VOLTAGE:	// If this persists for more than 1/2hr, BAD_CELL_FAULT is enabled and FAULT interrupt is generated +			{ +				if ((_state.battery_not_present == false) && +					(_ltc4155_last_good & (LTC4155_WALLSNS_GOOD | LTC4155_USBSNS_GOOD))) +				{ +					//charge_set_led(true); +					charge_notify(true); +					break; +				}						 +			} +			case LTC4155_CHARGER_CONSTANT_VOLTAGE_VPROG_LT_VCX:	// Small amount of current still charging the battery but below Vc/x threshold +			//case LTC4155_CHARGER_NTC_TOO_WARM: +			//case LTC4155_CHARGER_NTC_TOO_COLD: +			//case LTC4155_CHARGER_NTC_HOT: +			//	break; +			//case LTC4155_CHARGER_OFF: +			default: +				//charge_set_led(false); +				charge_notify(false); +		} +	} +	 +//	ltc4155_dump(); +	 +	result = true; +_ltc4155_handle_fail: +	_ltc4155_clear_irq();	// Even though it happens first above, this is necessary otherwise future IRQs won't be detected +	 +	return result; +} + +#define LTC4155_CHARGE_CURRENT_LIMIT	/*0xF*/0x7	// [100%] 50% + +bool ltc4155_set_charge_current_limit(uint8_t percentage) +{ +	uint8_t val = 0; +	uint8_t limit = 0; +	 +	if (percentage > 100) +		return false; +	else if (percentage == 100) +		percentage = 0xF; +	else if (percentage > 12)	// 0..88 -> 0..8800 +	{ +		uint16_t l = (((uint16_t)percentage - 12) * 100) / 586; +		limit = (uint8_t)l; +	} +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_WRITE_ADDRESS, LTC4155_REG_CHARGE, &val, _ltc4155_pull_up) == false) +		return false; +	 +	val &= ((0x1 << LTC4155_SHIFTS_CHARGE_CURRENT_LIMIT) - 1); +	//val |= (LTC4155_CHARGE_CURRENT_LIMIT << LTC4155_SHIFTS_CHARGE_CURRENT_LIMIT); +	val |= (limit << LTC4155_SHIFTS_CHARGE_CURRENT_LIMIT); +	 +	if (i2c_write_ex(CHRG_SDA, CHRG_SCL, LTC4155_WRITE_ADDRESS, LTC4155_REG_CHARGE, val, _ltc4155_pull_up) == false) +		return false; +	 +//ltc4155_dump(); +	 +	return true; +} + +bool ltc4155_init(bool disable_charger) +{ +	io_input_pin(USBPM_IRQ); +#if !defined(DEBUG) && !defined(ATTINY88_DIP) +	io_set_pin(USBPM_IRQ);	// Enable pull-up for Open Drain +#endif // DEBUG +#ifdef I2C_REWORK +	i2c_init_ex(CHRG_SDA, CHRG_SCL, _ltc4155_pull_up); +#endif // I2C_REWORK +	if (/*_ltc4155_clear_irq()*/_ltc4155_handle_irq() == false)	// Will set interrupt masks	// FIXME: Why does this cause instability?! +		return false; + +	const uint8_t charge_state = +		(disable_charger ? 0x0 : LTC4155_CHARGE_CURRENT_LIMIT) << LTC4155_SHIFTS_CHARGE_CURRENT_LIMIT |	// Battery charger I limit = 100% +		0x3 << LTC4155_SHIFTS_CHARGE_FLOAT_VOLTAGE |	// FIXME: Vbatt float = 4.05V - 4.2V for LiPo (default 0x00) +		0x0;	// Full capacity charge threshold = 10% +	if (i2c_write_ex(CHRG_SDA, CHRG_SCL, LTC4155_WRITE_ADDRESS, LTC4155_REG_CHARGE, charge_state, _ltc4155_pull_up) == false) +		return false; + +	const uint8_t wall_state = +		0x0 << LTC4155_SHIFTS_WALL_PRIORITY | +		0x0 << LTC4155_SHIFTS_WALL_SAFETY_TIMER |	// Charge safety timer = 4hr	// FIXME: 8hr or Vc/x +		0xE;	// 3 amps, 0x1F - CLPROG1 +	if (i2c_write_ex(CHRG_SDA, CHRG_SCL, LTC4155_WRITE_ADDRESS, LTC4155_REG_WALL, wall_state, _ltc4155_pull_up) == false) +		return false; + +	// FIXME: +	// Disable ID pin detection & autonomous startup +	// Enable OTG +	//i2c_write_ex(CHRG_SDA, CHRG_SCL, LTC4155_WRITE_ADDRESS, LTC4155_REG_USB, LTC4155_USB_OTG_LOCKOUT, _ltc4155_pull_up);	// Disable autonomous startup +	//i2c_write_ex(CHRG_SDA, CHRG_SCL, LTC4155_WRITE_ADDRESS, LTC4155_REG_ENABLE, LTC4155_ENABLE_USB_OTG, _ltc4155_pull_up);	// Enable OTG +	 +	if (_ltc4155_handle_irq() == false)	// One more time (IRQ LED stays lit in dev setup) +		return false; +	 +	return true; +} + +bool ltc4155_has_interrupt(void) +{ +	//bool state = io_test_pin(USBPM_IRQ); +	//debug_log_ex("4155IRQ", false); +	//debug_log_byte(state); +	//return (state != 1); +	return (io_test_pin(USBPM_IRQ) == false); +} + +bool ltc4155_handle_irq(void) +{ +	pmc_mask_irqs(true); +	 +	bool result = _ltc4155_handle_irq(); +	 +	pmc_mask_irqs(false); +	 +	return result; +} + +bool ltc4155_arm_ship_and_store(void) +{ +	return true; +} + +bool ltc4155_get_thermistor(uint8_t* val, bool* warning) +{ +	bool result = false; +	uint8_t _val = 0; +	 +	pmc_mask_irqs(true); +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_THERMISTOR, &_val, _ltc4155_pull_up) == false) +		goto ltc4155_get_thermistor_fail; +	 +	if (val) +		(*val) = _val >> 1; +	 +	if (warning) +		(*warning) = ((_val & 0x01) != 0x00); +	 +	result = true; +ltc4155_get_thermistor_fail: +	pmc_mask_irqs(false); +	return result; +} + +void ltc4155_dump(void) +{ +	pmc_mask_irqs(true); +	 +	uint8_t val = 0x00; +	bool warning = false; +	 +	if (ltc4155_get_thermistor(&val, &warning) == false) +		goto ltc4155_dump_fail; +	 +	debug_log_ex("\tTHRM", false); +	if (warning) +		debug_log_ex("!", false); +	debug_log_byte(val); +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_WALL, &val, _ltc4155_pull_up) == false) +		goto ltc4155_dump_fail; +	 +	debug_log_ex("\tWALL", false); +	debug_log_hex(val); +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_GOOD, &val, _ltc4155_pull_up) == false) +		goto ltc4155_dump_fail; +	 +	debug_log_ex("\t4155GO ", false); +	debug_log_hex(val); +	 +	if (i2c_read2_ex(CHRG_SDA, CHRG_SCL, LTC4155_READ_ADDRESS, LTC4155_REG_STATUS, &val, _ltc4155_pull_up) == false) +		goto ltc4155_dump_fail; +	 +	debug_log_ex("\t4155ST ", false); +	debug_log_hex(val); +	 +ltc4155_dump_fail: +	pmc_mask_irqs(false); +} + +#endif // !CHARGER_TI diff --git a/firmware/e300/rev_c/ltc4155.h b/firmware/e300/rev_c/ltc4155.h new file mode 100644 index 000000000..7e8e3751d --- /dev/null +++ b/firmware/e300/rev_c/ltc4155.h @@ -0,0 +1,25 @@ +/* + * ltc4155.h + * + * Created: 17/08/2012 8:09:43 PM + *  Author: Balint Seeber + */  + + +#ifndef LTC4155_H_ +#define LTC4155_H_ + +#include <stdbool.h> +#include <stdint.h> + +#ifndef CHARGER_TI + +bool ltc4155_init(bool disable_charger); +bool ltc4155_has_interrupt(void); +bool ltc4155_handle_irq(void); +void ltc4155_dump(void); +bool ltc4155_set_charge_current_limit(uint8_t percentage); + +#endif // !CHARGER_TI + +#endif /* LTC4155_H_ */ diff --git a/firmware/e300/rev_c/main.c b/firmware/e300/rev_c/main.c new file mode 100644 index 000000000..1e065ffef --- /dev/null +++ b/firmware/e300/rev_c/main.c @@ -0,0 +1,385 @@ +/* + * Copyright 2009 Ettus Research LLC + */ + +#include "config.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <avr/io.h> +#include <util/delay.h> +#include <avr/sleep.h> +#include <avr/interrupt.h> + +#include "global.h" +#include "power.h" +#include "debug.h" +#include "error.h" +#include "ltc3675.h" +#ifdef CHARGER_TI +#include "bq24190.h" +#else +#include "ltc4155.h" +#endif // CHARGER_TI + +#define AUTO_POWER_ON + +#define INITIAL_DELAY	250	// ms + +FUSES = {	// FIXME: & FUSE_CKSEL1 for low power 128 kHz clock +	.low = (FUSE_CKSEL0 & FUSE_SUT0 & FUSE_CKDIV8),	// Internal 8MHz Oscillator, Slowly rising power (start-up time), Divide Clock by 8 +	.high = (FUSE_EESAVE & FUSE_SPIEN),	// Save EEPROM between flashes	// FIXME: Leave SPIEN for programming enabled? +};	// Not using watchdog as it runs during sleep and consumes power + +volatile STATE _state; + +/* +    - Main/shared variables must be volatile +	- Port pins are tri-stated on reset +	* AVR_IRQ PD(5) +	- Enable pull-ups on all O.D. outputs from regulator chip +	* PS_POR/SRST should be driven HIGH by ATTiny? +	- AVR_RESET -> RESET pin - don't configure fuse (this would disable this functionality and prohibit serial programming) +	* Ship-and-store mode for charge controller? +	* cli before I2C calls +	* PS_TX +	- en5-clk, en2-data +	* Instruction following SEI is executed before interrupts +	* LTC3675 real-time status doesn't contain UV/OT +	* LTC3675 PGOOD -> power down (no point in checking blink state) +	* On WALL, use TX, on battery use OTG switcher +	* PRR - Power Reduction Register (p40) +	- 100% -> 50% battery charge limit +	* Check latched status for UV/OT in 3675 +	* If blink error reset, get latest charge status from 4155 +	* Fix UV status check from 3675/4155 as they currently share the same error  +	* Use charger termination at 8hr or Vc/x +	* Check PGood on all regs after power on before setting powered=true +	* Re-init 4155 on soft-power on +	- Re-set 3A limit in 4155 after external power connection +	- Removing power when running on battery, 4155GO 0xA0 - but WALL has been removed +	- Why is charger reporting Constant Current when power is removed +	* ltc3675_is_power_button_depressed: check if any reg is on, otherwise value will be invalid +	* When e.g. 3.3V doesn't come up, blink code is correctly 4 but there's a very short blink before re-starting the sequence +	- Vprog<Vc/x +*/ + +bool pmc_mask_irqs(bool mask) +{ +	if (_state.interrupts_enabled == false) +		return false; +	 +	if (mask) +	{ +		if (_state.interrupt_depth == 0) +			cli(); +		++_state.interrupt_depth; +	}		 +	else +	{ +		if (_state.interrupt_depth == 0) +			return false; +		 +		--_state.interrupt_depth; +		if (_state.interrupt_depth == 0) +			sei(); +	} +	 +	return true; +} + +int main(void) +{ +	_delay_ms(INITIAL_DELAY); +	 +	/////////////////////////////////////////////////////////////////////////// +	 +	memset((void*)&_state, 0x00, sizeof(STATE)); +	 +	debug_init(); +	debug_blink(1); +	 +	//debug_log("#");	// Will not boot if this is 21 chars long?! +	debug_log("Hello world"); +	 +    set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // SLEEP_MODE_PWR_SAVE combination is documented as Reserved +	 +//ltc4155_dump(); + +    // FIXME: Init as SPI slave (FPGA is master) +	 +	// 8-bit timer for blinking errors on charge LED +	TCCR0A = _BV(CTC0);		// CTC mode +	OCR0A = 244;			// 250ms with 1024 prescale +	TIMSK0 = _BV(OCIE0A);	// Enable CTC on Timer 0 + +	bool init_result = power_init(); +	debug_log_ex("Init", false); +	_debug_log(init_result ? "+" : "-"); +	debug_blink(2); +	//debug_blink_rev(6); +	 +	/////////////////////////////////// +#ifdef AUTO_POWER_ON +	power_on(); // Turn on immediately. Need to de-press power button to turn off. +	debug_log("Power"); +	debug_blink(3); +	//debug_blink_rev(10); + +	//debug_wait(); +	 +//ltc4155_dump(); +#endif // AUTO_POWER_ON +	_state.interrupts_enabled = true; +	sei();	// Enable interrupts + +	asm("nop"); + +	_state.wake_up = false;	// This will fire the first time the regs are turned on +	 +	bool one_more = false; +	 +	while (true) +	{ +		one_more = false; +#ifdef CHARGER_TI +		if (_state.bq24190_irq) +		{ +			bq24190_handle_irq(); +			 +			_state.bq24190_irq = false; +		} +#else +		if ((_state.ltc4155_irq)/* || ltc4155_has_interrupt()*/)	// [Don't know why PCINT ISR misses LTC4155 IRQ on power up, so double-check state of line] +		{ +			ltc4155_handle_irq(); +//ltc4155_dump(); +			_state.ltc4155_irq = false; +		} +#endif // !CHARGER_TI +		if (_state.core_power_bad)	// FIXME: Check whether it's supposed to be on +		{ +			if (power_is_subsys_on(PS_FPGA)) +			{ +				_delay_ms(1);	// Seeing weird 120us drop in PGOOD during boot from flash (no apparent drop in 1.0V though) +				 +				if (tps54478_is_power_good() == false) +				{ +					debug_log("ML:FPGA!"); +			 +					//power_off(); +					_state.power_off = true; +			 +					/*while (_state.wake_up == false) +					{ +						blink_error_sequence(1); +					}*/ +					pmc_set_blink_error(BlinkError_FPGA_Power);	// [If a blink error was set in power_off, this will supercede it] +				} +			}			 +			 +			_state.core_power_bad = false; +		} +		 +		if ((_state.ltc3675_irq)/* || ltc3675_has_interrupt()*/)	// This is fired on initial power up +		{ +			debug_log("ML:3675+"); +			 +			ltc3675_handle_irq(); +			 +			if (ltc3675_is_power_good(ltc3675_get_last_status()) == false) +			{ +				debug_log("ML:3675!"); +				 +				//power_off(); +				_state.power_off = true; +			} +			 +			_state.ltc3675_irq = false; +		} +		 +		if (_state.power_off) +		{ +			debug_log("ML:Off.."); +			 +			power_off(); +			 +			_state.power_off = false; +			_state.wake_up = false; +		} +		else if (_state.wake_up) +		{ +			_delay_ms(1);	// Tapping 3.1 ohm load ing 4155 in dev setup causes transient on this line and causes power on sequence to begin again +			 +			//if (_state.powered == false)	// Don't check in case button is held long enough to force LTC3675 shutdown (will not change 'powered' value) +			if (ltc3675_is_waking_up()) +			{ +				debug_log("ML:On.."); +				 +				power_on(); +			} +			 +			_state.wake_up = false; +		} +		 +		// Check to see if the error state has resolved itself at the end of each sequence of the current blink error +		 +		if ((_state.blink_error != BlinkError_None) && (_state.blink_last_loop != _state.blink_loops)) +		{ +			// [Check IRQs periodically] +			 +			bool ltc3675_use_last_status = false; +			/*if (ltc3675_has_interrupt()) +			{ +				//debug_set(IO_PB(6), ((_state.blink_loops % 2) == 0)); +				ltc3675_use_last_status = true; +				ltc3675_handle_irq(); +			}*/ +			 +			/////////////////////////// +			 +			switch (_state.blink_error) +			{ +				case BlinkError_LTC3675_UnderVoltage: +				case BlinkError_LTC3675_OverTemperature: +				case BlinkError_DRAM_Power: +				case BlinkError_3_3V_Peripherals_Power: +				case BlinkError_1_8V_Peripherals_Power: +				case BlinkError_TX_Power: +					if (((ltc3675_use_last_status) && (ltc3675_status_to_error(ltc3675_get_last_status()) != BlinkError_None)) ||  +						((ltc3675_use_last_status == false) && (ltc3675_check_status() != BlinkError_None))) +						break; +					debug_log("BE:3675-"); +					goto cancel_blink_error; +				case BlinkError_FPGA_Power: +					if (tps54478_is_power_good() == false) +						break; +					debug_log("BE:FPGA-"); +					goto cancel_blink_error; +				default: +cancel_blink_error:				 +					//debug_set(IO_PB(7), true); +					pmc_set_blink_error(BlinkError_None); +			} +			 +			//////////////////////////////////// +			 +			// More periodic checks +			// Need to do this has some interrupts are on PCINT, and while GIE is disabled, might change & change back +			//	E.g. LTC3675 IRQ due to UV, reset IRQ, re-asserts UV +#ifndef CHARGER_TI +			if (ltc4155_has_interrupt()) +			{ +				debug_log("BE:4155"); +				 +				_state.ltc4155_irq = true; +				one_more = true; +			} +#endif // !CHARGER_TI +			if (ltc3675_has_interrupt()) +			{ +				debug_log("BE:3675"); +				 +				_state.ltc3675_irq = true; +				one_more = true; +			} +			 +			if (power_is_subsys_on(PS_FPGA)) +			{ +				if (tps54478_is_power_good() == false) +				{ +					debug_log("BE:FPGA!"); +				 +					_state.core_power_bad = true; +					one_more = true; +				} +			} +			 +			//////////////////////////////////// +			 +			_state.blink_last_loop = _state.blink_loops; +		} +		 +		//if (_state.timers_running == false) +		if ((_state.active_timers == 0) && (one_more == false)) +		{ +			debug_log("^"); +			sleep_mode(); +			debug_log("$"); +		}			 +	} + +	return 0; +} + +uint8_t pmc_get_blink_error(void) +{ +	return _state.blink_error; +} + +void pmc_set_blink_error(uint8_t count) +{ +	if ((_state.blink_error != BlinkError_None) && (count /*> _state.blink_error*/!= BlinkError_None))	// [Prioritise] Always keep first sequence running +		return; +	else if (_state.blink_error == count)	// Don't restart if the same +		return; +	 +	if (count == BlinkError_None) +	{ +		debug_log("BLNK-"); +		_state.blink_stop = true; +		return; +	} +	 +	//char msg[25]; +	//sprintf(msg, "Blink code = %i\n", count); +	//debug_log(msg); +	debug_log_ex("BLNK ", false); +	debug_log_byte(count); +	 +	_state.blink_error = count; +	_state.blink_loops = 0; +	_state.blink_last_loop = 0; +	_state.blinker_state = 0; +	_state.blink_stop = false; + +	charge_set_led(false); +	 +	TCNT0 = 0; +	if ((TCCR0A & 0x07) == 0x00)	// Might already be active with existing error +		_state.active_timers++; +	TCCR0A |= 0x05;	// Start with 1024 prescale +} + +ISR(TIMER0_COMPA_vect)	// Blink the sequence, and leave one slot at the beginning and end where the LED is off so one can get a sense of how many blinks occurred +{ +	pmc_mask_irqs(true); +	 +	if (_state.blinker_state < (2 * _state.blink_error + 1)) +		charge_set_led((_state.blinker_state % 2) == 1); +	 +	_state.blinker_state++; +	 +	if (_state.blinker_state == (2 * _state.blink_error + 1 + 1)) +	{ +		_state.blinker_state = 0; +		 +		if (_state.blink_stop) +		{ +			if ((TCCR0A & 0x07) != 0x00) +				_state.active_timers--; +			TCCR0A &= ~0x07; +			 +			_state.blink_error = BlinkError_None; +			 +			debug_log("BLNK."); +		} +		else +		{ +			_state.blink_loops++; +		} +	} +	 +	pmc_mask_irqs(false); +} diff --git a/firmware/e300/rev_c/power.c b/firmware/e300/rev_c/power.c new file mode 100644 index 000000000..36bc81456 --- /dev/null +++ b/firmware/e300/rev_c/power.c @@ -0,0 +1,900 @@ +/* +	* Test battery voltage code +	* Charger error blinking uses busy wait - will drain battery if encounters error while unattended? Surely rest of H/W will pull more current. +*/ +#include "config.h" +#include "power.h" + +#include <string.h> +#include <util/delay.h> +#include <avr/io.h> +#include <avr/interrupt.h> +#include <avr/sleep.h> + +#include "io.h" +#include "i2c.h" +#include "ltc3675.h" +#include "ltc4155.h" +#include "bq24190.h" +#include "debug.h" +#include "global.h" +#include "error.h" + +#define BLINK_ERROR_DELAY		250  // ms + +#define POWER_DEFAULT_DELAY     50  // ms +#define POWER_DEFAULT_RETRIES   10 + +#define BATT_MIN_VOLTAGE		2000	// mV + +#define ARRAY_SIZE(a)			(sizeof(a)/sizeof(a[0])) +#define ZERO_MEMORY(s)			memset(&s, 0x00, sizeof(s)) + +#ifndef I2C_REWORK +io_pin_t PWR_SDA     = IO_PC(4); +io_pin_t PWR_SCL     = IO_PC(5); + +io_pin_t USB_RESETn= IO_PA(2); +#endif // I2C_REWORK + +//volatile bool powered = false; + +#ifdef DDR3L +#define DRAM_VOLTAGE	1350 // TODO: Misleading, actual DRAM voltage is 1.5V. This sets the regular voltage. +#else +#define DRAM_VOLTAGE	0	// Hardware default +#endif // DDR3 + +struct reg_config { +    int16_t voltage;    // mV +	uint8_t device; +    uint8_t address;    // Device specific +    bool powered; +} default_reg_config[] = {        // Index maps to 'power_subsystem_t', 0 volts means leave at hardware default +	{ 0000, REG_UNKNOWN, 0/*, true*/ },				// PS_UNKNOWN +	{ 1000, REG_TPS54478, 0/*, true*/ },			// PS_FPGA +	{ DRAM_VOLTAGE, REG_LTC3675, LTC3675_REG_1 },	// PS_VDRAM +	{ /*1800*/0, REG_LTC3675, LTC3675_REG_3 },		// PS_PERIPHERALS_1_8 +	{ /*3300*/0, REG_LTC3675, LTC3675_REG_6 },		// PS_PERIPHERALS_3_3 +	{ /*5000*/0, REG_LTC3675, LTC3675_REG_5 }		// PS_TX +}; +/* +int8_t power_get_regulator_index(uint8_t device, uint8_t address) +{ +	for (int8_t i = 0; i < ARRAY_SIZE(default_reg_config); ++i) +	{ +		struct reg_config* reg = default_reg_config + i; +		if ((reg->device == device) && (reg->address == address)) +			return i; +	} +	 +	return -1; +} +*/ +bool power_is_subsys_on(power_subsystem_t index) +{ +	if ((index <= PS_UNKNOWN) || (index >= PS_MAX)) +		return false; +	 +	return default_reg_config[index].powered; +} + +static bool ltc3675_reg_helper(uint8_t address) +{ +	for (int8_t i = 0; i < ARRAY_SIZE(default_reg_config); ++i) +	{ +		struct reg_config* reg = default_reg_config + i; +		if ((reg->device == REG_LTC3675) && (reg->address == address)) +			return reg->powered; +	} +#ifdef DEBUG_SAFETY +	debug_log_ex("!3675HLP ", false); +	debug_log_hex(address); +#endif // DEBUG_SAFETY +	return false; +	//return power_is_subsys_on(power_get_regulator_index(REG_LTC3675, address) - 1); +} + +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); + +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); + +/////////////////////////////////////////////////////////////////////////////// + +#define TPS54478_START_DELAY	10	// 50 (safety)	// 3 (per spec)	// ms (some arbitrary value so that the external power supply can settle) + +#ifdef ATTINY88_DIP +static io_pin_t CORE_PWR_EN = IO_PC(1);	// IO_PC(7) not routed by card, using PWER_EN1 instead +#else +static io_pin_t CORE_PWR_EN = IO_PA(3); +#endif // ATTINY88_DIP +static io_pin_t CORE_PGOOD = IO_PB(0); + +void tps54478_init(bool enable) +{ +	tps54478_set_power(enable); +	io_clear_pin(CORE_PWR_EN); +	 +    io_input_pin(CORE_PGOOD); +#if !defined(DEBUG) && !defined(ATTINY88_DIP)	// Don't enable pull-up when connected to a pulled-up switch +	io_set_pin(CORE_PGOOD);	// Enable pull-up for Open Drain +#endif // DEBUG +//#ifdef DEBUG +//	io_enable_pin(CORE_PWR_EN, false); +//#endif // DEBUG +//_delay_ms(2500); +} + +void tps54478_set_power(bool on) +{ +	debug_log_ex("54478", false); +	 +	// Assumes: Hi-Z input/LOW output +	 +	if (on) +	{ +		io_input_pin(CORE_PWR_EN); +		_delay_ms(TPS54478_START_DELAY); +		 +		debug_log("+"); +	}		 +	else +	{ +		io_output_pin(CORE_PWR_EN); +		// Don't delay here as we can't detect its state anyway +		 +		debug_log("-"); +	}		 +	 +	//io_enable_pin(CORE_PWR_EN, on); +} + +bool tps54478_is_power_good(void) +{ +    return io_test_pin(CORE_PGOOD);	// This doesn't necessarily mean it's good - the chip might be malfunctioning (or switched off) +} + +/////////////////////////////////////////////////////////////////////////////// + +static io_pin_t CHARGE      = IO_PD(1); + +#if !defined(ATTINY88_DIP) && defined(LED_POLARITY) +static io_pin_t POWER_LED	= IO_PC(7); + +void power_set_led_ex(bool on, bool swap) +{ +	if (swap) +	{ +		if ((on == false) && (/*io_is_pin_set(CHARGE)*/_state.battery_charging))	// If charging and turning off, don't change charge light +		{ +			charge_set_led(true);	// Force it again just in case +			return; +		} +	}		 +	 +	io_clear_pin(CHARGE); +	io_enable_pin(POWER_LED, on); +} + +void power_set_led(bool on) +{ +	power_set_led_ex(on, true); +} +#endif // !ATTINY88_DIP && LED_POLARITY + +void charge_set_led_ex(bool on, bool swap) +{ +#ifdef ATTINY88_DIP +	// +#else + +#ifdef LED_POLARITY +	io_clear_pin(POWER_LED); +#endif // LED_POLARITY + +#endif // ATTINY88_DIP + +#ifdef ATTINY88_DIP +	io_enable_pin(CHARGE, !on); +#else +    io_enable_pin(CHARGE, on); + +#ifdef LED_POLARITY +	if (swap) +	{ +		if ((on == false) && (_state.powered))	// If no longer charging, turn power light back on +			power_set_led(true); +	}			 +#endif // LED_POLARITY + +#endif // ATTINY88_DIP +} + +void charge_set_led(bool on) +{ +	charge_set_led_ex(on, true); +} + +void charge_notify(bool charging) +{ +	_state.battery_charging = charging; +	 +	charge_set_led(charging); +} + +/////////////////////////////////////////////////////////////////////////////// + +void power_signal_interrupt(void) +{ +    io_set_pin(AVR_IRQ);	// FIXME: Active low? +} + +/////////////////////////////////////////////////////////////////////////////// + +#if !defined(DEBUG) && !(defined(ENABLE_SERIAL) && defined(ATTINY88_DIP)) +static io_pin_t PS_POR      = IO_PD(6); +#define PS_POR_AVAILABLE +#endif // DEBUG +static io_pin_t PS_SRST     = IO_PD(7); + +#define FPGA_RESET_DELAY    10  // ms   // MAGIC + +void fpga_reset(bool delay) +{ +#ifdef PS_POR_AVAILABLE +    io_clear_pin(PS_POR); +#endif // PS_POR_AVAILABLE +    io_clear_pin(PS_SRST); + +    if (delay) +        _delay_ms(FPGA_RESET_DELAY); +#ifdef PS_POR_AVAILABLE +    io_enable_pin(PS_POR, true); +#endif // PS_POR_AVAILABLE +    io_enable_pin(PS_SRST, true); +} + +/////////////////////////////////////////////////////////////////////////////// + +static io_pin_t VBAT        = IO_PC(0); + +void battery_init(void) +{ +    //io_input_pin(VBAT); +    DIDR0 |= 0x1;           // Digital input disable PC0 (ADC0) + +    ADMUX = (1 << REFS0)    // AVcc reference +          | (0 << ADLAR)    // Left-aligned result +          | (0 << MUX0);    // ADC0 + +    ADCSRA = (0x7 << ADPS0);// Prescale clock by 128 +} + +uint16_t battery_get_voltage(void) +{ +    // Vout = (357k / (274k + 357k)) * Vbat +    // Vbat = (Vout * (274k + 357k)) / 357k + +    // ADC = (Vin * 1024) / Vref +    // Vin = (ADC * Vref) / 1024 +    // Vref = 3.3 + +    // Vbat(mV) = 1000 * (((ADC * 3.3) / 1024) * (274k + 357k)) / 357k +    // Vbat(mV) ~= ADC * 5.70 + +    ADCSRA |= (1 << ADEN);        // FIXME: Turn on ADC (or leave on all the time?) + +    ADCSRA |= (1 << ADSC);  // Start conversion + +    while (ADCSRA & (1 << ADSC));   // Wait for End of Conversion + +    /*uint16_t*/uint32_t voltage = (ADCH << 8) | (ADCL << 0); +#ifdef ATTINY88_DIP +	voltage = (voltage * 32227) / 10000;	// ~3.22265625 +#else +    voltage = (voltage * 56961) / 10000;	// ~5.69606748 +#endif // ATTINY88_DIP +    ADCSRA &= ~(1 << ADEN);         // FIXME: Turn off ADC (or leave on all the time?) + +    return (uint16_t)voltage; +} + +/////////////////////////////////////////////////////////////////////////////// + +void blink_error_sequence(uint8_t len) +{ +    charge_set_led(false); +    _delay_ms(BLINK_ERROR_DELAY * 2); + +    for (; len > 0; len--) { +        charge_set_led(true); +        _delay_ms(BLINK_ERROR_DELAY); +        charge_set_led(false); +        _delay_ms(BLINK_ERROR_DELAY); +    } + +    //for (len = 2; len > 0; len--)   // Could have *2 on delay, but so as never to overflow 8-bit argument +    //    _delay_ms(BLINK_ERROR_DELAY); +} + +typedef struct power_params { +    power_subsystem_t subsys; +    bool enable; +    uint8_t retry; +    //uint16_t opaque; +} power_params_t; + +static bool _power_up_fpga(power_params_t* params) +{ +    if (params->subsys != PS_FPGA) +        return false; + +    if (params->enable == false) +    { +        //if (tps54478_is_power_good() == false)  // Already off +		//	return true; + +        if (params->retry == 0) +		{ +			io_clear_pin(PS_SRST);	// FIXME: Hold it low to stop +#ifdef PS_POR_AVAILABLE +			io_clear_pin(PS_POR);	// Prepare it for shutdown, and then the potential next power cycle +#endif // PS_POR_AVAILABLE			 +            tps54478_set_power(false); +		} + +        //return (tps54478_is_power_good() == false); +		return true; +    } + +    //bool fpga_power_good = tps54478_is_power_good();  // TODO: Can it ever already be good? + +    if (params->retry == 0) +        tps54478_set_power(true); + +    return tps54478_is_power_good(); +} + +static bool _power_up_reg(power_params_t* params) +{ +    if ((params->subsys > PS_TX) || (params->subsys < PS_VDRAM)) +        return false; + +    struct reg_config* cfg = default_reg_config + params->subsys; + +    if (params->enable == false) +        return ltc3675_enable_reg(cfg->address, false); + +	if (cfg->voltage > 0) +	{ +		if (ltc3675_set_voltage(cfg->address, cfg->voltage) == false) +			return false; +	}	 + +    return ltc3675_enable_reg(cfg->address, true); +} + +static bool _power_enable_subsys(power_params_t* params) +{ +    switch (params->subsys) +    { +        case PS_FPGA: +            return _power_up_fpga(params); +        //case PS_: +        //    break; +        default: +            return _power_up_reg(params); +    } + +    return false;   // Should never get here +} + +bool power_enable(power_subsystem_t subsys, bool on) +{ +    power_params_t params; +    ZERO_MEMORY(params); +    params.subsys = subsys; +    params.enable = on; + +    return _power_enable_subsys(¶ms); +} + +typedef bool (*boot_function_t)(power_params_t*); + +struct boot_step { +    power_subsystem_t subsys; +	//boot_function_t fn; +	//uint8_t delay; +	//uint8_t retries; +    //uint16_t opaque; +	//bool powered; +} boot_steps[] = {  // MAGIC: Retries/delays +	{ PS_FPGA,				/*NULL, POWER_DEFAULT_DELAY, POWER_DEFAULT_RETRIES*/ },	// 7..8						// 3..4 +	{ PS_VDRAM,				/*NULL, POWER_DEFAULT_DELAY, POWER_DEFAULT_RETRIES*/ },	// 9..10					// 5..6 +	{ PS_PERIPHERALS_1_8,	/*NULL, POWER_DEFAULT_DELAY, POWER_DEFAULT_RETRIES*/ },	// 11..12					// 7..8 +	{ PS_PERIPHERALS_3_3,	/*NULL, POWER_DEFAULT_DELAY, POWER_DEFAULT_RETRIES*/ },	// 13..14					// 9..10 +	{ PS_TX,				/*NULL, POWER_DEFAULT_DELAY, POWER_DEFAULT_RETRIES*/ }  // CHECK: Leaving TX off +}; +/* +bool power_is_subsys_on(int8_t index) +{ +	if ((index < 0) || (index >= ARRAY_SIZE(boot_steps))) +		return false; +	 +	struct boot_step* step = boot_steps + index; +	 +	return step->powered; +} +*/ +bool power_init(void) +{ +	io_output_pin(CHARGE); +#ifdef LED_POLARITY +    io_output_pin(POWER_LED); +#endif // LED_POLARITY + +	charge_set_led(true); +	 +	battery_init(); +	 +    tps54478_init(true);	// Will keep EN float (keep power on) +#ifndef I2C_REWORK +	i2c_init(PWR_SDA, PWR_SCL); +	 + +	 +#endif // I2C_REWORK +	io_input_pin(USB_RESETn);   +	io_output_pin(FTDI_RESETn);  +	io_output_pin(USB_CLK_EN); +	io_input_pin(FTDI_CBUS3); +	 +#ifdef CHARGER_TI +	if (bq24190_init(true) == false) +		return false; +#else +	if (ltc4155_init(/*_state.battery_not_present*/true/*false*/) == false) +		return false; +#endif // CHARGER_TI +#ifdef CHARGER_TI +	_delay_ms(1000);	// Still at 1.4V on dev board +#else +	_delay_ms(25);	// Wait for charge current to stop (Vbatt to fall to 0V) +#endif // CHARGER_TI +	uint16_t batt_voltage = battery_get_voltage(); +	debug_log_ex("Vb ", false); +	debug_log_byte((uint8_t)(batt_voltage / 100)); +	//debug_log_hex_ex(batt_voltage >> 8, false); +	//debug_log_hex(batt_voltage & 0xFF); +	if (batt_voltage < BATT_MIN_VOLTAGE) +	{ +		_state.battery_not_present = true; +		 +		//debug_log("NoBatt"); +	} +	else +	{ +#ifdef CHARGER_TI +		bq24190_toggle_charger(true); +#else +		ltc4155_set_charge_current_limit(50); +#endif // CHARGER_TI +	} + +    if (ltc3675_init(ltc3675_reg_helper) == false) +		return false; +#ifdef PS_POR_AVAILABLE +	io_output_pin(PS_POR); +#endif // PS_POR_AVAILABLE +    io_output_pin(PS_SRST); +    // Hold low until power is stable +#ifdef PS_POR_AVAILABLE +    io_clear_pin(PS_POR); +#endif // PS_POR_AVAILABLE +    io_clear_pin(PS_SRST); +/* +    AVR_CS +    AVR_MOSI +    AVR_MISO +    AVR_SCK + +    FTDI_BCD +    FTDI_PWREN2 +*/ +	io_input_pin(AVR_RESET);	// Has external pull-up (won't do anything because this is configured at the hardware RESET pin) +	 +	//io_output_pin(AVR_IRQ);		// Output here, input to FPGA +    io_input_pin(AVR_IRQ); +	//io_set_pin(AVR_IRQ);	// FIXME: Active low? +	 +	/////////////// +	 +	EICRA = _BV(ISC01) | _BV(ISC00) | _BV(ISC10)/* | _BV(ISC11)*/;	// Rising edge for INT0 (WAKEUP). [Falling for INT1.] Any logical change INT1 (ONSWITCH_DB) +	//EIMSK = _BV(INT0);	// [Turn on WAKEUP interrupt] Don't do this, as unit will turn on anyway +	EIMSK = _BV(INT1) | _BV(INT0);	// Turn on ONSWITCH_DB and WAKEUP +	 +	PCMSK0 = _BV(PCINT1) | _BV(PCINT0);	// USBPM_IRQ | CORE_PGOOD +	PCMSK2 = _BV(PCINT16)/* | _BV(PCINT20)*/;	// PWR_IRQ/* | PWR_RESET*/ +	PCICR = _BV(PCIE2) | _BV(PCIE0); + +	/////////////// +/* +	TCNT0; +	OCR0A = 0x; +	TCCR0A = _BV(CTC0); +	TIFR0; +	TIMSK0; +	TCCR0A |= 0x05;	// Switch on with 1024 prescaler +*/ +	TCCR1B = _BV(WGM12);	// CTC mode +	OCR1A = 15624 * 2;		// Hold button for 2 seconds to switch off +	TIMSK1 = _BV(OCIE1A);	// Enable CTC on Timer 1 +	 +	charge_set_led(false); +	 +	return true; +} + +bool power_on(void) +{ +	pmc_mask_irqs(true); +	 +    //charge_set_led(false); +	 +	bool last_power_led_state = /*true*/false; +	 +	//if ((ARRAY_SIZE(boot_steps) % 2) == 0)	// Should end with 'true' +	//	last_power_led_state = false; +	 +	power_set_led(last_power_led_state); +	 +	fpga_reset(true); +	 +	uint8_t step_count, retry; +	for (step_count = 0; step_count < ARRAY_SIZE(boot_steps); step_count++) +	{ +		last_power_led_state = !last_power_led_state; +		power_set_led(last_power_led_state); +		 +//		debug_blink(step_count); +//		debug_blink(3 + (step_count * 2) + 0); +//		debug_blink_rev(7 + (step_count * 2) + 0); +		 +	    struct boot_step* step = boot_steps + step_count; +	    if (/*(step->fn == NULL) && */(step->subsys == PS_UNKNOWN)) +            continue; +		 +		debug_log_ex("PWR ", false); +		debug_log_byte_ex(step->subsys, true); + +        power_params_t params; + +	    for (retry = 0; retry < /*step->retries*/POWER_DEFAULT_RETRIES; retry++) +		{ +	        ZERO_MEMORY(params); +            params.subsys = step->subsys; +            params.enable = true; +	        params.retry = retry; + +	        if ((/*(step->fn != NULL) && (step->fn(¶ms))) || +				((step->fn == NULL) && */(_power_enable_subsys(¶ms)))) +			{ +				//step->powered = true; +				default_reg_config[step->subsys].powered = true; +				 +				debug_log("+"); +//				debug_blink(3 + (step_count * 2) + 1); +//				debug_blink_rev(7 + (step_count * 2) + 1); + +//ltc4155_dump(); + +                break; +	        } +			 +			debug_log("?"); + +            if ((retry < /*step->retries*/POWER_DEFAULT_RETRIES)/* && (step->delay > 0)*/) +                _delay_ms(/*step->delay*/POWER_DEFAULT_DELAY); +	    } +		 +//		debug_blink(step_count); + +	    if (retry == /*step->retries*/POWER_DEFAULT_RETRIES) +	        break; +    } + +    if (step_count != ARRAY_SIZE(boot_steps)) +	{ +		debug_log("x"); +		 +		//sei();	// For button press detection + +        /*while (_state.powered == false) { +            blink_error_sequence(step_count + BlinkError_FPGA_Power); +        }*/ +		pmc_set_blink_error(step_count + BlinkError_FPGA_Power); + +		pmc_mask_irqs(false); + +        return false; +    } +	 +	/////////////////////////////////// +	 +	io_set_pin(USB_CLK_EN); +	_delay_ms(200); +	io_set_pin(FTDI_RESETn); +	fpga_reset(false);  // Power has been brought up, so let FPGA run +	_delay_ms(100); +	 +	/////////////////////////////////// +	 +//	ltc4155_dump(); +	 +	// Turn off WAKEUP interrupt, enable ONSWITCH_DB +	//EIMSK = _BV(INT1); +	 +	_state.powered = true; +//debug_blink_rev(1); +//_delay_ms(1000);	// Wait for FPGA PGOOD to stabilise +	pmc_mask_irqs(false); +	 +	power_set_led(true); +	 +	if (_state.battery_charging) +	{ +		_delay_ms(500*2); +		charge_set_led(true); +	}		 +	 +    return true; +} + +uint8_t power_off(void) +{ +	pmc_mask_irqs(true); +	 +	io_clear_pin(PS_SRST);	// FIXME: Hold it low to stop FPGA running +	 +	fpga_reset(true); +	io_clear_pin(USB_CLK_EN); +	 +	bool last_power_led_state = /*false*/true; +	 +	//if ((ARRAY_SIZE(boot_steps) % 2) == 0)	// Should end with 'false' +	//	last_power_led_state = true; +	 +	//power_set_led(last_power_led_state); +	 +	/////////////////////////////////// +	 +	int8_t step_count, retry; +	for (step_count = ARRAY_SIZE(boot_steps) - 1; step_count >= 0; step_count--) +	{ +		last_power_led_state = !last_power_led_state; +		power_set_led(last_power_led_state); +		 +//		debug_blink(step_count); +		 +	    struct boot_step* step = boot_steps + step_count; +	    if (/*(step->fn == NULL) && */(step->subsys == PS_UNKNOWN)) +            continue; + +        power_params_t params; + +	    for (retry = 0; retry < /*step->retries*/POWER_DEFAULT_RETRIES; retry++) +		{ +	        ZERO_MEMORY(params); +            params.subsys = step->subsys; +            params.enable = false; +	        params.retry = retry; +			 +			if ((/*(step->fn != NULL) && (step->fn(¶ms))) || +				((step->fn == NULL) && */(_power_enable_subsys(¶ms)))) +			{ +				//step->powered = false; +				default_reg_config[step->subsys].powered = false; +				break; +			} +			 +            if ((retry < /*step->retries*/POWER_DEFAULT_RETRIES)/* && (step->delay > 0)*/) +                _delay_ms(/*step->delay*/POWER_DEFAULT_DELAY); +	    } +		 +//		debug_blink(step_count); + +	    if (retry == /*step->retries*/POWER_DEFAULT_RETRIES) +	        break; +    } + +    if (step_count != -1) +	{ +		/*pmc_mask_irqs(false); +		 +        while (_state.powered) { +            blink_error_sequence(step_count + BlinkError_FPGA_Power); +        }*/ +		if (pmc_get_blink_error() == BlinkError_None)	// Only set blink error if no existing error +			pmc_set_blink_error(step_count + BlinkError_FPGA_Power); +		 +		pmc_mask_irqs(false); + +        return (step_count + 1); +    } +	 +	/////////////////////////////////// +	 +	// Turn off WAKEUP interrupt, enable ONSWITCH_DB +	//EIMSK = _BV(INT1); + +	_state.powered = false; +	 +	pmc_mask_irqs(false); +	 +	power_set_led_ex(false, false); +	_delay_ms(500*2); +	 +	power_set_led(false);	// Will turn on charger LED if battery is charging +	 +	return 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef DEBUG + +#ifdef ATTINY88_DIP +static io_pin_t DEBUG_1 = IO_PB(6); +static io_pin_t DEBUG_2	= IO_PB(7); +#endif // ATTINY88_DIP + +#endif // DEBUG + +ISR(INT0_vect)	// PD(2) WAKEUP: Rising edge +{ +	//cli(); +	pmc_mask_irqs(true); +	 +	//power_on(); +	debug_log("\nINT0\n"); +	_state.wake_up = true; +	 +	//sei(); +	pmc_mask_irqs(false); +} + +ISR(INT1_vect)	// PD(3) ONSWITCH_DB (PB_STAT): Any change +{ +	//cli(); +	pmc_mask_irqs(true); +	 +	if (ltc3675_is_power_button_depressed()) +	{ +		debug_log("PWRBTN+"); +		 +		TCNT1 = 0; +		if ((TCCR1B & 0x07) == 0x00) +		{ +			_state.active_timers++; +			debug_log("TIMER1+"); +		} +		TCCR1B |= /*0x5*/0x3;	// [1024] 64 prescaler +		//_state.timers_running = true; +		 +		//debug_set(DEBUG_1, true); +		//debug_set(DEBUG_2, false); +	} +	else +	{ +		debug_log("PWRBTN-"); +		 +		//if (TIMSK1 & _BV(OCIE1A))	// If letting go of button and still running, stop timer +		{ +			//TIMSK1 &= ~_BV(OCIE1A); +			if ((TCCR1B & 0x07) != 0x00) +			{ +				_state.active_timers--; +				debug_log("TIMER1-"); +			} +			TCCR1B &= ~0x7;	// Disable timer +			//_state.timers_running = false; +			 +			//debug_set(DEBUG_1, false); +		} +	} +	 +	//sei(); +	pmc_mask_irqs(false); +} + +ISR(TIMER1_COMPA_vect) +{ +	//cli(); +	pmc_mask_irqs(true); +	 +	debug_log("TIMER1"); + +	//TIMSK1 &= ~_BV(OCIE1A);	// Turn off timer +	TCCR1B &= ~0x7;	// Disable timer +	//_state.timers_running = false; +	_state.active_timers--; +	 +	if (_state.powered) +	{ +		debug_log("PWROFF"); +		 +		_state.power_off = true; +	}		 +	 +	//debug_set(DEBUG_2, true); +	 +	//power_off(); +	 +	//sei(); +	pmc_mask_irqs(false); +	 +	//sleep_mode(); +} + +ISR(PCINT0_vect) +{ +	//cli(); +	pmc_mask_irqs(true); +	 +	//debug_log("PCINT0"); +	 +	// CORE_PGOOD +	//	Assert low: power problem -> shutdown +	// USBPM_IRQ +	//	Charge status change? -> update LED +	//	Power problem:	battery -> blink charge LED +	//					major -> shutdown +	 +	if (/*(_state.powered) && */(/*io_test_pin(CORE_PGOOD)*/tps54478_is_power_good() == false)) +	{ +		_state.core_power_bad = true; +	} +#ifdef CHARGER_TI +	if (bq24190_has_interrupt()) +	{ +		_state.bq24190_irq = true; +	} +#else +	if (ltc4155_has_interrupt()) +	{ +		_state.ltc4155_irq = true; +	} +#endif // CHARGER_TI +	//sei(); +	pmc_mask_irqs(false); +} + +ISR(PCINT2_vect) +{ +	//cli(); +	pmc_mask_irqs(true); +	 +	//debug_log("PCINT2"); +	 +	// PWR_IRQ +	//	Regulator problem: shutdown +	// PWR_RESET +	//	Ignored +	 +	if (ltc3675_has_interrupt()) +	{ +		//debug_set(IO_PB(6), true); +		_state.ltc3675_irq = true; +	} +	 +	//sei(); +	pmc_mask_irqs(false); +} diff --git a/firmware/e300/rev_c/power.h b/firmware/e300/rev_c/power.h new file mode 100644 index 000000000..453633414 --- /dev/null +++ b/firmware/e300/rev_c/power.h @@ -0,0 +1,58 @@ +#ifndef POWER_H +#define POWER_H + +#include <stdbool.h> +#include <stdint.h> + +void tps54478_init(bool enable); +void tps54478_set_power(bool on);   // Zynq core power (1.0V for FPGA) +bool tps54478_is_power_good(void); + +void charge_set_led(bool on);		// Here for error blink codes +void charge_notify(bool charging); + +void power_signal_interrupt(void); + +void fpga_reset(bool delay); + +typedef enum power_subsystems { +    PS_UNKNOWN, +    PS_FPGA, +    PS_VDRAM, +    PS_PERIPHERALS_1_8, +    PS_PERIPHERALS_3_3, +    PS_TX, +	PS_MAX +} power_subsystem_t; + +enum Regulators +{ +	REG_UNKNOWN, +	REG_TPS54478, +	REG_LTC3675 +}; + +bool power_enable(power_subsystem_t subsys, bool on); + +void battery_init(void); +uint16_t battery_get_voltage(void);  // mV + +bool power_init(void); +bool power_on(void); +uint8_t power_off(void); + +//bool power_is_subsys_on(int8_t index); +bool power_is_subsys_on(power_subsystem_t index); +//int8_t power_get_regulator_index(uint8_t device, uint8_t address); +//bool ltc3675_reg_helper(uint8_t address); + +void usbhub_reset(void); + +#ifndef I2C_REWORK +#include "io.h" + +extern io_pin_t PWR_SDA; +extern io_pin_t PWR_SCL; +#endif // I2C_REWORK + +#endif // POWER_H diff --git a/host/docs/mainpage.dox b/host/docs/mainpage.dox index 597938c35..17f01cfc0 100644 --- a/host/docs/mainpage.dox +++ b/host/docs/mainpage.dox @@ -39,6 +39,7 @@ devices and how to use the API to connect to them through your own software.  ## USRP E-Series Devices  \li \subpage page_usrp_e1x0 +\li \subpage page_usrp_e3x0  \li \subpage page_gpsdo  ## USRP X-Series Devices diff --git a/host/docs/usrp_e3x0.dox b/host/docs/usrp_e3x0.dox new file mode 100644 index 000000000..bfa9ebf11 --- /dev/null +++ b/host/docs/usrp_e3x0.dox @@ -0,0 +1,302 @@ +/*! \page page_usrp_e3x0 USRP-E3x0 Series Device Manual + +\tableofcontents + +\section e3x0_feature_list Comparative features list + +- Hardware Capabilities: +	  Integrated RF frontend (70 MHz - 6 GHz) +	- External PPS reference input +	- External 10 MHz reference input +	- Configurable clock rate +	- Internal GPIO connector with UHD API control +	- 2 USB 2.0 Host ports +	- Internal GPSDO +	- Soundcard mono input / stereo output +	- USB UART +	- Zynq-7020 FPGA +- FPGA Capabilities: +	- 2 RX DDC chains in FPGA +	- 2 TX DUC chain in FPGA +	- Timed commands in FPGA +	- Timed sampling in FPGA +	- 16-bit and sample modes (sc16) +	- Up to 10 MHz of RF bandwidth with 16-bit samples + +\section e3x0_getting_started Getting started + +This will run you through the first steps relevant to get your USRP E300/310 +up and running. + +\subsection e3x0_first_boot First boot + +After unpacking and assembling your USRP E300/E310 insert the micro sd card into the micro sd card slot. + +There are two different methods to connect to the device + +- using the onboard serial to usb connector +- using the gigabit ethernet connector and a ssh client on your host computer + +For the first boot, booting with the serial cable connected to the device +is recommended, as it allows to review and modify the network configuration, +and allows to enter the bootloader in case of issues during the boot. + + +\subsubsection e3x0_first_boot_serial Serial connection + +To use the serial connection together with a Linux or OSX machine (most other UNIX variants come with a version of screen, too) +a terminal emulator such as screen can be used: + +    $ sudo screen /dev/ttyUSB0 115200 + +The exact device node /dev/ttyUSB0 depends on your operating system's driver and other USB devices that might be already connected. +It can be usually found by perusing the output of dmesg or journalctl, after connecting the USRP E300/E310 device to your host computer. + +An example of a dmesg output for the serial to usb converter: + +    924.102764] usb 1-1: FTDI USB Serial Device converter now attached to ttyUSB0 + + +On Microsoft Windows the serial connection can be established using a tool such as Putty by selecting a baudrate of 115200 and the corresponding serial port for the serial to usb converter. + +In both cases you should see boot messages fly by and finally end up with a login prompt similar to the following: + +TODO!! + +Note: The username is 'root' and the default password is empty. + +You should be presented with a shell similar to the following + +    root@ettus-e300:~# + + +\subsubsection e3x0_first_boot_ssh SSH connection + +The USRP E300/E310 device relies on the DHCP protocol to automatically obtain an IP address. +In case your network setup does not include a DHCP server, refer to the section \ref e3x0_first_boot_serial or configure a DHCP server to hand out IP addresses on your network. + +After the device obtained an IP address you can log in from a Linux or OSX machine by typing: + +    $ ssh root@192.168.10.42 + +where the IP address depends on your local network setup. + +On Microsoft Windows again the connection can be established using a tool such as Putty, by selecting a username of root without password. + +You should be presented with a shell similar to the following + +    root@ettus-e300:~# + +\section e3x0_hw Hardware Setup + +\section e3x0_load_fpga_imgs Load FPGA Images onto the Device + +The USRP-X Series device ships with a bitstream pre-programmed in the flash, +which is automatically loaded onto the FPGA during device power-up. However, +a new FPGA image can be configured over the PCI Express interface or the +on-board USB-JTAG programmer. This process can be seen as a "one-time load", in +that if you power-cycle the device, it will not retain the FPGA image. + +Please note that this process is *different* than replacing the FPGA image +stored in the flash, which will then be automatically loaded the next time the +device is reset. + +\subsection e3x0_load_fpga_imgs_jtag Use JTAG to load FPGA images + +The USRP-E Series device features an on-board JTAG connector that can be accessed on the PCB +of the device. The iMPACT tool in the <a href="http://www.xilinx.com/support/download/index.htm">Xilinx Programming Tools (ISE, iMPACT)</a> package can be used to load an image over the JTAG interface. This can be useful for unbricking devices. + +If you have iMPACT installed, you can use the `impact_jtag_programmer.sh` tool to install images. Make sure your e3x0 is powered on and connected to your computer using the internal JTAG connector. Then run the tool: + +    <path_to_uhd_tools>/impact_jtag_programmer.sh --fpga-path=<fpga_image_path> + +\subsection e3x0_setup_change_ip Change the USRP's IP address + +You may need to change the USRP's IP address for several reasons: +- to satisfy your particular network configuration +- to use multiple USRP-E Series devices with the same host computer +- to set a known IP address into USRP (in case you forgot) + +\section e3x0_addressing Addressing the Device + +\subsection e3x0_addressing_singledev Single device configuration + +In a single-device configuration, +the USRP device must have a unique IPv4 address on the host computer. +The USRP can be identified through its IPv4 address or resolvable hostname. +See the application notes on \ref page_identification. +Use this addressing scheme with the uhd::usrp::multi_usrp interface (not a typo!). + +Example device address string representation for a USRP-E Series device with IPv4 address 192.168.10.2: + +    addr=192.168.10.2 + +\subsection e3x0_addressing_multidevcfg Multiple device configuration + +In a multi-device configuration, +each USRP device must have a unique IPv4 address on the host computer. +The device address parameter keys must be suffixed with the device index. +Each parameter key should be of the format \<key\>\<index\>. +Use this addressing scheme with the uhd::usrp::multi_usrp interface. + +- The order in which devices are indexed corresponds to the indexing of the transmit and receive channels. +- The key indexing provides the same granularity of device identification as in the single device case. + +Example device address string representation for 2 USRPs with IPv4 addresses **192.168.10.2** and **192.168.20.2**: + +    addr0=192.168.10.2, addr1=192.168.20.2 + + +\section e3x0_comm_problems Communication Problems + +When setting up a development machine for the first time, +you may have various difficulties communicating with the USRP device. +The following tips are designed to help narrow down and diagnose the problem. + +\subsection e3x0_comm_problems_runtimeerr RuntimeError: no control response + +This is a common error that occurs when you have set the subnet of your network +interface to a different subnet than the network interface of the USRP device.  For +example, if your network interface is set to **192.168.20.1**, and the USRP device is **192.168.10.2** +(note the difference in the third numbers of the IP addresses), you +will likely see a 'no control response' error message. + +Fixing this is simple - just set the your host PC's IP address to the same +subnet as that of your USRP device. Instructions for setting your IP address are in the +previous section of this documentation. + +\subsection e3x0_comm_problems_firewall Firewall issues + +When the IP address is not specified, +the device discovery broadcasts UDP packets from each Ethernet interface. +Many firewalls will block the replies to these broadcast packets. +If disabling your system's firewall +or specifying the IP address yields a discovered device, +then your firewall may be blocking replies to UDP broadcast packets. +If this is the case, we recommend that you disable the firewall +or create a rule to allow all incoming packets with UDP source port **49152**. + +\subsection e3x0_comm_problems_ping Ping the device +The USRP device will reply to ICMP echo requests ("ping"). +A successful ping response means that the device has booted properly +and that it is using the expected IP address. + +    ping 192.168.10.2 + +\subsection e3x0_comm_problems_monitor Monitor the host network traffic +Use Wireshark to monitor packets sent to and received from the device. + +\subsection e3x0_comm_problems_leds Observe Ethernet port LEDs +When there is network traffic arriving at the Ethernet port, LEDs will light up. +You can use this to make sure the network connection is correctly set up, e.g. +by pinging the USRP and making sure the LEDs start to blink. + +\section e3x0_hw Hardware Notes + +\subsection e3x0_hw_fpanel Front Panel + +\image html e3x0_fp_overlay.png "e3x0" + +- **RF A Group** +  + **TX/RX LED**: Indicates that data is streaming on the TX/RX channel on daughterboard A +  + **RX2 LED**: Indicates that data is streaming on the RX2 channel on daughterboard A +- **REF**: Indicates that the external Reference Clock is locked +- **PPS**: Indicates a valid PPS signal by pulsing once per second +- **AUX I/O**: Front panel GPIO connector. +- **GPS**: Indicates that GPS reference is locked +- **LINK**: Indicates that the host computer is communicating with the device (Activity) + +- **RF B Group** +  + **TX/RX LED**: Indicates that data is streaming on the TX/RX channel on daughterboard B +  + **RX2 LED**: Indicates that data is streaming on the RX2 channel on daughterboard B +- **PWR**: Power switch + +\subsection e3x0_hw_rear_panel Rear Panel + +\image html e3x0_rp_overlay.png "e3x0 Rear Panel" + +- **PWR**: Connector for the USRP-X Series power supply +- **1G/10G ETH**: SFP+ ports for Ethernet interfaces +- **REF OUT**: Output port for the exported reference clock +- **REF IN**: Reference clock input +- **PCIe x4**: Connector for Cabled PCI Express link +- **PPS/TRIG OUT**: Output port for the PPS signal +- **PPS/TRIG IN**: Input port for the PPS signal  +- **GPS**: Connection for the GPS antenna + +\subsection e3x0_hw_e3x0_hw_ref10M Ref Clock - 10 MHz + +Using an external 10 MHz reference clock, a square wave will offer the best phase +noise performance, but a sinusoid is acceptable.  The power level of the reference clock cannot exceed +15 dBm. + +\subsection e3x0_hw_pps PPS - Pulse Per Second +Using a PPS signal for timestamp synchronization requires a square wave signal with the following a 5Vpp amplitude. + +To test the PPS input, you can use the following tool from the UHD examples: + +- `<args>` are device address arguments (optional if only one USRP device is on your machine) + +    cd <install-path>/lib/uhd/examples +    ./test_pps_input --args=\<args\> + +\subsection e3x0_hw_gpsdo Internal GPSDO + +Please see \ref page_gpsdo_e3x0 for information on configuring and using the internal GPSDO. + +\subsection e3x0_hw_gpio Internal GPIO + +### Connector + +\image html e3x0_gpio_conn.png "e3x0 GPIO Connector" + +### Pin Mapping + +- Pin 1:  +3.3V +- Pin 2:  Data[0] +- Pin 3:  Data[1] +- Pin 4:  Data[2] +- Pin 5:  Data[3] +- Pin 6:  Data[4] +- Pin 7:  Data[5] +- Pin 8:  Data[6] +- Pin 9:  Data[7] +- Pin 10: Data[8] +TODO: + + +Please see the \ref page_gpio_api for information on configuring and using the GPIO bus. + +\subsection e3x0_hw_chipscope Debugging custom FPGA designs with Xilinx Chipscope + +Xilinx chipscope allows for debugging custom FPGA designs similar to a logic analyzer. +USRP-E series devices can be used with Xilinx chipscope using the internal JTAG connector. + +Further information on how to use Chipscope can be found in the Xilinx Chipscope Pro Software and Cores User Guide (UG029). + +\section e3x0_misc Miscellaneous + +\subsection e3x0_misc_multirx Multiple RX channels + +There are two complete DDC and DUC DSP chains in the FPGA. In the single channel case, +only one chain is ever used. To receive from both channels, the user must set the **RX** or **TX** +subdevice specification. + +In the following example, a TVRX2 is installed. +Channel 0 is sourced from subdevice **RX1**, +and channel 1 is sourced from subdevice **RX2** (**RX1** and **RX2** are antenna connectors on the TVRX2 daughterboard). + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} +usrp->set_rx_subdev_spec("A:RX1 A:RX2"); +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +\subsection e3x0_misc_sensors Available Sensors + +The following sensors are available for the USRP-E Series motherboards; +they can be queried through the API. + +- **fe_locked** - rx / tx frontend pll locked +- **temp** - processor temperature value +- Other sensors are added when the GPSDO is enabled + +*/ +// vim:ft=doxygen: diff --git a/host/lib/convert/CMakeLists.txt b/host/lib/convert/CMakeLists.txt index 363555f45..bec88b520 100644 --- a/host/lib/convert/CMakeLists.txt +++ b/host/lib/convert/CMakeLists.txt @@ -94,8 +94,11 @@ IF(CMAKE_COMPILER_IS_GNUCXX)  ENDIF(CMAKE_COMPILER_IS_GNUCXX)  IF(HAVE_ARM_NEON_H) +    ENABLE_LANGUAGE(ASM) +      LIBUHD_APPEND_SOURCES(          ${CMAKE_CURRENT_SOURCE_DIR}/convert_with_neon.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/convert_neon.S      )  ENDIF() diff --git a/host/lib/convert/convert_neon.S b/host/lib/convert/convert_neon.S new file mode 100644 index 000000000..8cbe82bde --- /dev/null +++ b/host/lib/convert/convert_neon.S @@ -0,0 +1,37 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +	.arch armv7-a +	.fpu neon +	.syntax unified +	.text +	.align 2 +	.global neon_item32_sc16_swap_16n +	.type neon_item32_sc16_swap_16n, %function +neon_item32_sc16_swap_16n: +.loop_swap: +	vld2.16      {q0, q1}, [r0]! +	vld2.16      {q2, q3}, [r0]! +	vswp               q0, q1 +	vswp               q2, q3 +	vst2.16      {q0, q1}, [r1]! +	vst2.16      {q2, q3}, [r1]! +	subs               r2, #1 +	bne          .loop_swap +	bx                 lr +	.size neon_item32_sc16_swap_16n, .-neon_item32_sc16_swap_16n +	.section .note.GNU-stack,"",%progbits diff --git a/host/lib/convert/convert_with_neon.cpp b/host/lib/convert/convert_with_neon.cpp index e994d97a6..f1c7773ec 100644 --- a/host/lib/convert/convert_with_neon.cpp +++ b/host/lib/convert/convert_with_neon.cpp @@ -1,5 +1,5 @@  // -// Copyright 2011-2012 Ettus Research LLC +// Copyright 2011-2014 Ettus Research LLC  //  // This program is free software: you can redistribute it and/or modify  // it under the terms of the GNU General Public License as published by @@ -19,6 +19,12 @@  #include <uhd/utils/byteswap.hpp>  #include <arm_neon.h> +extern "C" { +void neon_item32_sc16_swap_16n(void *, void *, int iter); +} + +static const int SIMD_WIDTH = 16; +  using namespace uhd::convert;  DECLARE_CONVERTER(fc32, 1, sc16_item32_le, 1, PRIORITY_SIMD){ @@ -58,3 +64,31 @@ DECLARE_CONVERTER(sc16_item32_le, 1, fc32, 1, PRIORITY_SIMD){      item32_sc16_to_xx<uhd::htowx>(input+i, output+i, nsamps-i, scale_factor);  } + +DECLARE_CONVERTER(sc16, 1, sc16_item32_le, 1, PRIORITY_SIMD){ +    const sc16_t *input = reinterpret_cast<const sc16_t *>(inputs[0]); +    item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + +    size_t i = nsamps / SIMD_WIDTH; + +    if (i) +        neon_item32_sc16_swap_16n((void *) input, (void *) output, i); + +    i *= SIMD_WIDTH; + +    xx_to_item32_sc16<uhd::htowx>(input+i, output+i, nsamps-i, scale_factor); +} + +DECLARE_CONVERTER(sc16_item32_le, 1, sc16, 1, PRIORITY_SIMD){ +    const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); +    sc16_t *output = reinterpret_cast<sc16_t *>(outputs[0]); + +    size_t i = nsamps / SIMD_WIDTH; + +    if (i) +        neon_item32_sc16_swap_16n((void *) input, (void *) output, i); + +    i *= SIMD_WIDTH; + +    item32_sc16_to_xx<uhd::wtohx>(input+i, output+i, nsamps-i, scale_factor); +} diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt index c8c2e6a8d..f6788b5ef 100644 --- a/host/lib/usrp/CMakeLists.txt +++ b/host/lib/usrp/CMakeLists.txt @@ -37,5 +37,6 @@ INCLUDE_SUBDIRECTORY(usrp1)  INCLUDE_SUBDIRECTORY(usrp2)  INCLUDE_SUBDIRECTORY(b100)  INCLUDE_SUBDIRECTORY(e100) +INCLUDE_SUBDIRECTORY(e300)  INCLUDE_SUBDIRECTORY(x300)  INCLUDE_SUBDIRECTORY(b200) diff --git a/host/lib/usrp/dboard/CMakeLists.txt b/host/lib/usrp/dboard/CMakeLists.txt index 9e8653608..3c5bb4fa8 100644 --- a/host/lib/usrp/dboard/CMakeLists.txt +++ b/host/lib/usrp/dboard/CMakeLists.txt @@ -37,5 +37,6 @@ LIBUHD_APPEND_SOURCES(      ${CMAKE_CURRENT_SOURCE_DIR}/db_tvrx.cpp      ${CMAKE_CURRENT_SOURCE_DIR}/db_dbsrx2.cpp      ${CMAKE_CURRENT_SOURCE_DIR}/db_tvrx2.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/db_e3x0.cpp  ) diff --git a/host/lib/usrp/dboard/db_e3x0.cpp b/host/lib/usrp/dboard/db_e3x0.cpp new file mode 100644 index 000000000..523927d49 --- /dev/null +++ b/host/lib/usrp/dboard/db_e3x0.cpp @@ -0,0 +1,64 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/static.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> + +namespace uhd { namespace usrp { + +/*********************************************************************** + * The E310 dboard + *   AD9361 Interface, thus two subdevs + **********************************************************************/ +class e310_dboard : public xcvr_dboard_base{ +public: +    e310_dboard(ctor_args_t args) : xcvr_dboard_base(args) {} + +    ~e310_dboard(void) {} +}; + +/*********************************************************************** + * The E310 dboard + *   AD9364 Interface, thus one subdev + **********************************************************************/ +class e300_dboard : public xcvr_dboard_base{ +public: +    e300_dboard(ctor_args_t args) : xcvr_dboard_base(args) {} + +    ~e300_dboard(void) {} +}; + +/*********************************************************************** + * Register the E310 dboards + **********************************************************************/ +static dboard_base::sptr make_e310_dboard(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new e310_dboard(args)); +} + +static dboard_base::sptr make_e300_dboard(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new e300_dboard(args)); +} + +}} // namespace + +using namespace uhd::usrp; + +UHD_STATIC_BLOCK(reg_e3x0_dboards){ +    dboard_manager::register_dboard(0x0110, &make_e310_dboard, "E310 MIMO XCVR"); +    dboard_manager::register_dboard(0x0100, &make_e300_dboard, "E300 SISO XCVR"); +} diff --git a/host/lib/usrp/e300/CMakeLists.txt b/host/lib/usrp/e300/CMakeLists.txt new file mode 100644 index 000000000..9ee9b5521 --- /dev/null +++ b/host/lib/usrp/e300/CMakeLists.txt @@ -0,0 +1,55 @@ +# +# Copyright 2013-2014 Ettus Research LLC +# +# This program 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 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the USRP-E300 support +######################################################################## +find_package(UDev) + +LIBUHD_REGISTER_COMPONENT("E300" ENABLE_E300 OFF "ENABLE_LIBUHD" OFF) + +IF(ENABLE_E300) +    LIST(APPEND E300_SOURCES +        ${CMAKE_CURRENT_SOURCE_DIR}/e300_impl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/e300_io_impl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/e300_fifo_config.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/e300_sysfs_hooks.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/e300_network.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/e300_global_regs.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/e300_spi.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/e300_sensor_manager.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/e300_i2c.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/e300_eeprom_manager.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/e300_common.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/e300_async_serial.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/e300_ublox_control_impl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/e300_remote_codec_ctrl.cpp +    ) +    LIBUHD_APPEND_SOURCES(${E300_SOURCES}) +    IF(UDEV_FOUND) +        INCLUDE_DIRECTORIES(${UDEV_INCLUDE_DIR}) +        LIBUHD_APPEND_LIBS(${UDEV_LIBS}) +        SET_SOURCE_FILES_PROPERTIES( +            ${E300_SOURCES} +            PROPERTIES COMPILE_DEFINITIONS "E300_NATIVE=1" +        ) +    ENDIF(UDEV_FOUND) +ENDIF(ENABLE_E300) diff --git a/host/lib/usrp/e300/e300_async_serial.cpp b/host/lib/usrp/e300/e300_async_serial.cpp new file mode 100644 index 000000000..cdf18f7f7 --- /dev/null +++ b/host/lib/usrp/e300/e300_async_serial.cpp @@ -0,0 +1,245 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "e300_async_serial.hpp" + +namespace uhd { namespace usrp { namespace gps { + +async_serial::async_serial() +    : _io(), +      _port(_io), +      _background_thread(), +      _open(false), +      _error(false) +{ +} + +async_serial::async_serial( +        const std::string &node, +        const size_t baud_rate, +        boost::asio::serial_port_base::parity opt_parity, +        boost::asio::serial_port_base::character_size opt_csize, +        boost::asio::serial_port_base::flow_control opt_flow, +        boost::asio::serial_port_base::stop_bits opt_stop) +    : _io(), +      _port(_io), +      _background_thread(), +      _open(false), +      _error(false) +{ +    open(node, baud_rate, opt_parity, opt_csize, opt_flow, opt_stop); +} + +void async_serial::open( +        const std::string &node, +        const size_t baud_rate, +        boost::asio::serial_port_base::parity opt_parity, +        boost::asio::serial_port_base::character_size opt_csize, +        boost::asio::serial_port_base::flow_control opt_flow, +        boost::asio::serial_port_base::stop_bits opt_stop) +{ +    if(is_open()) +        close(); + +    _set_error_status(true); +    _port.open(node); +    _port.set_option( +        boost::asio::serial_port_base::baud_rate(baud_rate)); +    _port.set_option(opt_parity); +    _port.set_option(opt_csize); +    _port.set_option(opt_flow); +    _port.set_option(opt_stop); + +    _io.post(boost::bind(&async_serial::_do_read, this)); + +    boost::thread t(boost::bind(&boost::asio::io_service::run, &_io)); +    _background_thread.swap(t); +    _set_error_status(false); +    _open=true; +} + +bool async_serial::is_open() const +{ +    return _open; +} + +bool async_serial::error_status() const +{ +    boost::lock_guard<boost::mutex> l(_error_mutex); +    return _error; +} + +void async_serial::close() +{ +    if(!is_open()) +        return; + +    _open=false; +    _io.post(boost::bind(&async_serial::_do_close, this)); +    _background_thread.join(); +    _io.reset(); +    if(error_status()) +        throw(boost::system::system_error(boost::system::error_code(), +                "Error while closing the device")); +} + +void async_serial::write(const char *data, size_t size) +{ +    { +        boost::lock_guard<boost::mutex> l(_write_queue_mutex); +        _write_queue.insert(_write_queue.end(), data, data+size); +    } +    _io.post(boost::bind(&async_serial::_do_write, this)); +} + +void async_serial::write(const std::vector<char> &data) +{ +    { +        boost::lock_guard<boost::mutex> l(_write_queue_mutex); +        _write_queue.insert( +            _write_queue.end(), +            data.begin(), +            data.end()); +    } +    _io.post(boost::bind(&async_serial::_do_write, this)); +} + +void async_serial::write_string(const std::string &s) +{ +    { +        boost::lock_guard<boost::mutex> l(_write_queue_mutex); +        _write_queue.insert( +            _write_queue.end(), +            s.begin(), +            s.end()); +    } +    _io.post(boost::bind(&async_serial::_do_write, this)); +} + +async_serial::~async_serial() +{ +    if(is_open()) { +        try { +            close(); +        } catch(...) { +            //Don't throw from a destructor +        } +    } +} + +void async_serial::_do_read() +{ +    _port.async_read_some(boost::asio::buffer( +        _read_buffer,READ_BUFFER_SIZE), +        boost::bind(&async_serial::_read_end, +        this, +        boost::asio::placeholders::error, +        boost::asio::placeholders::bytes_transferred)); +} + +void async_serial::_read_end( +    const boost::system::error_code& error, +    size_t bytes_transferred) +{ +    if(error) { +        if(is_open()) { +            _do_close(); +            _set_error_status(true); +        } +    } else { +        if(_callback) +            _callback( +                _read_buffer, +                bytes_transferred); +        _do_read(); +    } +} + +void async_serial::_do_write() +{ +    // if a write operation is already in progress, do nothing +    if(_write_buffer == 0) { +        boost::lock_guard<boost::mutex> l(_write_queue_mutex); +        _write_buffer_size=_write_queue.size(); +        _write_buffer.reset(new char[_write_queue.size()]); +        std::copy(_write_queue.begin(),_write_queue.end(), +                _write_buffer.get()); +        _write_queue.clear(); +        async_write( +            _port, boost::asio::buffer(_write_buffer.get(), +               _write_buffer_size), +            boost::bind( +                &async_serial::_write_end, +                this, +                boost::asio::placeholders::error)); +    } +} + +void async_serial::_write_end(const boost::system::error_code& error) +{ +    if(!error) { +        boost::lock_guard<boost::mutex> l(_write_queue_mutex); +        if(_write_queue.empty()) { +            _write_buffer.reset(); +            _write_buffer_size=0; +            return; +        } +        _write_buffer_size = _write_queue.size(); +        _write_buffer.reset(new char[_write_queue.size()]); +        std::copy(_write_queue.begin(),_write_queue.end(), +             _write_buffer.get()); +        _write_queue.clear(); +        async_write( +            _port, +            boost::asio::buffer(_write_buffer.get(), +            _write_buffer_size), +            boost::bind( +               &async_serial::_write_end, +               this, +               boost::asio::placeholders::error)); +    } else { +        _set_error_status(true); +        _do_close(); +    } +} + +void async_serial::_do_close() +{ +    boost::system::error_code ec; +    _port.cancel(ec); +    if(ec) +        _set_error_status(true); +    _port.close(ec); +    if(ec) +        _set_error_status(true); +} + +void async_serial::_set_error_status(const bool e) +{ +    boost::lock_guard<boost::mutex> l(_error_mutex); +    _error = e; +} + + +void async_serial::set_read_callback( +    const boost::function<void (const char*, size_t)> &callback) +{ +    _callback = callback; +} + + +}}} // namespace diff --git a/host/lib/usrp/e300/e300_async_serial.hpp b/host/lib/usrp/e300/e300_async_serial.hpp new file mode 100644 index 000000000..fafc7de3d --- /dev/null +++ b/host/lib/usrp/e300/e300_async_serial.hpp @@ -0,0 +1,113 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_ASYNC_SERIAL_HPP +#define INCLUDED_ASYNC_SERIAL_HPP + +#include <boost/asio.hpp> +#include <boost/bind.hpp> +#include <boost/thread.hpp> +#include <boost/utility.hpp> +#include <boost/function.hpp> +#include <boost/shared_array.hpp> + +namespace uhd { namespace usrp { namespace gps { + +class async_serial : private boost::noncopyable +{ +public: +    async_serial(); +    ~async_serial(); + +    async_serial(const std::string &node, const size_t baud_rate, +        boost::asio::serial_port_base::parity opt_parity= +            boost::asio::serial_port_base::parity( +                boost::asio::serial_port_base::parity::none), +        boost::asio::serial_port_base::character_size opt_csize= +            boost::asio::serial_port_base::character_size(8), +        boost::asio::serial_port_base::flow_control opt_flow= +            boost::asio::serial_port_base::flow_control( +                boost::asio::serial_port_base::flow_control::none), +        boost::asio::serial_port_base::stop_bits opt_stop= +            boost::asio::serial_port_base::stop_bits( +                boost::asio::serial_port_base::stop_bits::one)); + +   void open(const std::string& node, const size_t baud_rate, +        boost::asio::serial_port_base::parity opt_parity= +            boost::asio::serial_port_base::parity( +                boost::asio::serial_port_base::parity::none), +        boost::asio::serial_port_base::character_size opt_csize= +            boost::asio::serial_port_base::character_size(8), +        boost::asio::serial_port_base::flow_control opt_flow= +            boost::asio::serial_port_base::flow_control( +                boost::asio::serial_port_base::flow_control::none), +        boost::asio::serial_port_base::stop_bits opt_stop= +            boost::asio::serial_port_base::stop_bits( +                boost::asio::serial_port_base::stop_bits::one)); + +   bool is_open(void) const; + +   bool error_status(void) const; + +   void close(void); + +   void write(const char *data, const size_t size); +   void write(const std::vector<char> &data); + +   void write_string(const std::string &s); + +   static const size_t READ_BUFFER_SIZE=512; + +   void set_read_callback( +       const boost::function<void (const char*, size_t)>& callback); + +   void clear_callback(); + +private: // methods +   void _do_read(); + +   void _read_end( +       const boost::system::error_code &error, +       size_t bytes_transferred); + +   void _do_write(); + +   void _write_end(const boost::system::error_code &error); + +   void _do_close(); + +   void _set_error_status(const bool e); +private: // members +    boost::asio::io_service         _io; +    boost::asio::serial_port        _port; +    boost::thread                   _background_thread; +    bool                            _open; +    bool                            _error; +    mutable boost::mutex            _error_mutex; + +    std::vector<char>              _write_queue; +    boost::shared_array<char>      _write_buffer; +    size_t                         _write_buffer_size; +    boost::mutex                   _write_queue_mutex; +    char                           _read_buffer[READ_BUFFER_SIZE]; + +    boost::function<void (const char*, size_t)> _callback; +}; + +}}} // namespace + +#endif //INCLUDED_ASYNC_SERIAL_HPP diff --git a/host/lib/usrp/e300/e300_common.cpp b/host/lib/usrp/e300/e300_common.cpp new file mode 100644 index 000000000..97e906be7 --- /dev/null +++ b/host/lib/usrp/e300/e300_common.cpp @@ -0,0 +1,59 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// +#include <uhd/utils/msg.hpp> + +#include "e300_fifo_config.hpp" +#include "e300_fifo_config.hpp" + +#include "e300_common.hpp" + +#include <boost/filesystem.hpp> +#include <fstream> + +namespace uhd { namespace usrp { namespace e300 { + +namespace common { + +void load_fpga_image(const std::string &path) +{ +    if (not boost::filesystem::exists("/dev/xdevcfg")) +        ::system("mknod /dev/xdevcfg c 259 0"); + +    UHD_MSG(status) << "Loading FPGA image: " << path << "..." << std::flush; + +    std::ifstream fpga_file(path.c_str(), std::ios_base::binary); +    UHD_ASSERT_THROW(fpga_file.good()); + +    std::FILE *wfile; +    wfile = std::fopen("/dev/xdevcfg", "wb"); +    UHD_ASSERT_THROW(!(wfile == NULL)); + +    char buff[16384]; // devcfg driver can't handle huge writes +    do { +        fpga_file.read(buff, sizeof(buff)); +        std::fwrite(buff, 1, fpga_file.gcount(), wfile); +    } while (fpga_file); + +    fpga_file.close(); +    std::fclose(wfile); + +    UHD_MSG(status) << " done" << std::endl; +} + +} + +}}} diff --git a/host/lib/usrp/e300/e300_common.hpp b/host/lib/usrp/e300/e300_common.hpp new file mode 100644 index 000000000..d9a0afd9e --- /dev/null +++ b/host/lib/usrp/e300/e300_common.hpp @@ -0,0 +1,31 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_E300_COMMON_HPP +#define INCLUDED_E300_COMMON_HPP + +namespace uhd { namespace usrp { namespace e300 { + +namespace common { + +void load_fpga_image(const std::string &path); + +}; + +}}} + +#endif // INCLUDED_E300_COMMON_HPP diff --git a/host/lib/usrp/e300/e300_defaults.hpp b/host/lib/usrp/e300/e300_defaults.hpp new file mode 100644 index 000000000..9c0e5df1d --- /dev/null +++ b/host/lib/usrp/e300/e300_defaults.hpp @@ -0,0 +1,82 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_E300_DEFAULTS_HPP +#define INCLUDED_E300_DEFAULTS_HPP + +#include "ad9361_client.h" + +namespace uhd { namespace usrp { namespace e300 { + +static const double DEFAULT_TICK_RATE       = 32e6; +static const double MAX_TICK_RATE           = 50e6; +static const double MIN_TICK_RATE           = 1e6; + +static const double DEFAULT_TX_SAMP_RATE    = 1.0e6; +static const double DEFAULT_RX_SAMP_RATE    = 1.0e6; +static const double DEFAULT_DDC_FREQ        = 0.0; +static const double DEFAULT_DUC_FREQ        = 0.0; + +static const double DEFAULT_FE_GAIN         = 0.0; +static const double DEFAULT_FE_FREQ         = 1.0e9; +static const double DEFAULT_FE_BW           = 56e6; + +static const std::string DEFAULT_TIME_SRC   = "none"; +static const std::string DEFAULT_CLOCK_SRC  = "internal"; + +static const size_t DEFAULT_RX_DATA_FRAME_SIZE = 4096; +static const size_t DEFAULT_RX_DATA_NUM_FRAMES = 32; + +static const size_t DEFAULT_TX_DATA_FRAME_SIZE = 2048; +static const size_t DEFAULT_TX_DATA_NUM_FRAMES = 32; + +static const size_t DEFAULT_CTRL_FRAME_SIZE    = 64; +static const size_t DEFAULT_CTRL_NUM_FRAMES    = 32; + +static const size_t MAX_NET_RX_DATA_FRAME_SIZE = 1200; +static const size_t MAX_NET_TX_DATA_FRAME_SIZE = 1200; + +class e300_ad9361_client_t : public ad9361_params { +public: +    ~e300_ad9361_client_t() {} +    double get_band_edge(frequency_band_t band) { +        switch (band) { +        case AD9361_RX_BAND0:   return 1.2e9; +        case AD9361_RX_BAND1:   return 2.6e9; +        case AD9361_TX_BAND0:   return 2940.0e6; +        default:                return 0; +        } +    } +    clocking_mode_t get_clocking_mode() { +        return AD9361_XTAL_N_CLK_PATH; +    } +    digital_interface_mode_t get_digital_interface_mode() { +        return AD9361_DDR_FDD_LVCMOS; +    } +    digital_interface_delays_t get_digital_interface_timing() { +        digital_interface_delays_t delays; +        delays.rx_clk_delay = 0; +        delays.rx_data_delay = 0xF; +        delays.tx_clk_delay = 0; +        delays.tx_data_delay = 0xF; +        return delays; +    } +}; + +}}} // namespace + +#endif // INCLUDED_E300_DEFAULTS_HPP diff --git a/host/lib/usrp/e300/e300_eeprom_manager.cpp b/host/lib/usrp/e300/e300_eeprom_manager.cpp new file mode 100644 index 000000000..af1235b6b --- /dev/null +++ b/host/lib/usrp/e300/e300_eeprom_manager.cpp @@ -0,0 +1,236 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "e300_eeprom_manager.hpp" +#include <uhd/types/mac_addr.hpp> +#include <uhd/utils/byteswap.hpp> + +namespace uhd { namespace usrp { namespace e300 { + +static const std::string _bytes_to_string(const uint8_t* bytes, size_t max_len) +{ +    std::string out; +    for (size_t i = 0; i < max_len; i++) { +        if (bytes[i] < 32 or bytes[i] > 127) return out; +        out += bytes[i]; +    } +    return out; +} + +static void _string_to_bytes(const std::string &string, size_t max_len, uint8_t* buffer) +{ +    byte_vector_t bytes; +    const size_t len = std::min(string.size(), max_len); +    for (size_t i = 0; i < len; i++){ +        buffer[i] = string[i]; +    } +    if (len < max_len - 1) +        buffer[len] = '\0'; +} + +e300_eeprom_manager::e300_eeprom_manager(i2c::sptr i2c) : _i2c(i2c) +{ +    read_mb_eeprom(); +    read_db_eeprom(); +} + +e300_eeprom_manager::~e300_eeprom_manager(void) +{ +} + +const mboard_eeprom_t& e300_eeprom_manager::read_mb_eeprom(void) +{ +    boost::mutex::scoped_lock(_mutex); + +    std::vector<boost::uint8_t> bytes; +    bytes.resize(sizeof(mb_eeprom_map_t)); +    mb_eeprom_map_t *map_ptr = reinterpret_cast<mb_eeprom_map_t*>(&bytes[0]); +    memset(map_ptr, 0xff, sizeof(mb_eeprom_map_t)); + +    // get the old contents +    for(size_t i = 0; i < sizeof(mb_eeprom_map_t); i++) +        bytes[i] = _i2c->get_i2c_reg8(MB_ADDR, i); + +    mb_eeprom_map_t &map = *map_ptr; + +    _mb_eeprom["product"] = boost::lexical_cast<std::string>( +        uhd::ntohx<boost::uint16_t>(map.hw_product)); +    _mb_eeprom["revision"] = boost::lexical_cast<std::string>( +        uhd::ntohx<boost::uint16_t>(map.hw_revision)); +    _mb_eeprom["serial"] = _bytes_to_string( +        map.serial, MB_SERIAL_LEN); + +    byte_vector_t mac_addr(map.mac_addr, map.mac_addr + 6); +    _mb_eeprom["mac-addr"] = mac_addr_t::from_bytes(mac_addr).to_string(); + +    _mb_eeprom["name"] = _bytes_to_string( +        map.user_name, MB_NAME_LEN); + +    return _mb_eeprom; +} + +const dboard_eeprom_t& e300_eeprom_manager::read_db_eeprom(void) +{ +    boost::mutex::scoped_lock(_mutex); + +    std::vector<boost::uint8_t> bytes; +    bytes.resize(sizeof(db_eeprom_map_t)); +    db_eeprom_map_t *map_ptr = reinterpret_cast<db_eeprom_map_t*>(&bytes[0]); +    memset(map_ptr, 0xff, sizeof(db_eeprom_map_t)); + +    // get the old contents +    for(size_t i = 0; i < sizeof(db_eeprom_map_t); i++) +        bytes[i] = _i2c->get_i2c_reg16(DB_ADDR, i); + +    db_eeprom_map_t &map = *map_ptr; + +    _db_eeprom.id = uhd::usrp::dboard_id_t::from_uint16( +        uhd::ntohx<boost::uint16_t>(map.hw_product)); + +    _db_eeprom.revision = boost::lexical_cast<std::string>( +        uhd::ntohx<boost::uint16_t>(map.hw_revision)); +    _db_eeprom.serial = _bytes_to_string( +        map.serial, DB_SERIAL_LEN); + +    return _db_eeprom; +} + +void e300_eeprom_manager::write_db_eeprom(const dboard_eeprom_t& eeprom) +{ +    boost::mutex::scoped_lock(_mutex); +    _db_eeprom = eeprom; +    std::vector<boost::uint8_t> bytes; +    bytes.resize(sizeof(db_eeprom_map_t)); + + +    db_eeprom_map_t *map_ptr = reinterpret_cast<db_eeprom_map_t*>(&bytes[0]); +    memset(map_ptr, 0xff, sizeof(db_eeprom_map_t)); + +    // get the old contents +    for(size_t i = 0; i < sizeof(db_eeprom_map_t); i++) +        bytes[i] = _i2c->get_i2c_reg16(DB_ADDR, i); + +    db_eeprom_map_t &map = *map_ptr; + +    if (_db_eeprom.id != dboard_id_t::none()) { +        map.hw_product = uhd::htonx<boost::uint16_t>( +            _db_eeprom.id.to_uint16()); +    } + +    if (not _db_eeprom.revision.empty()) { +        map.hw_revision = uhd::htonx<boost::uint16_t>( +            boost::lexical_cast<boost::uint16_t>(_db_eeprom.revision)); +    } + +    if (not _db_eeprom.serial.empty()) { +        _string_to_bytes(_db_eeprom.serial, DB_SERIAL_LEN, map.serial); +    } +    for(size_t i = 0; i < sizeof(mb_eeprom_map_t); i++) +        _i2c->set_i2c_reg16(DB_ADDR, i, bytes[i]); +} + +void e300_eeprom_manager::write_mb_eeprom(const mboard_eeprom_t& eeprom) +{ +    boost::mutex::scoped_lock(_mutex); +    _mb_eeprom = eeprom; +    std::vector<boost::uint8_t> bytes; +    bytes.resize(sizeof(mb_eeprom_map_t)); + + +    mb_eeprom_map_t *map_ptr = reinterpret_cast<mb_eeprom_map_t*>(&bytes[0]); +    memset(map_ptr, 0xff, sizeof(mb_eeprom_map_t)); + +    // get the old contents +    for(size_t i = 0; i < sizeof(mb_eeprom_map_t); i++) +        bytes[i] = _i2c->get_i2c_reg8(MB_ADDR, i); + +    mb_eeprom_map_t &map = *map_ptr; + +    if (_mb_eeprom.has_key("product")) { +        map.hw_product = uhd::htonx<boost::uint16_t>( +            boost::lexical_cast<boost::uint16_t>(_mb_eeprom["product"])); +    } +    if (_mb_eeprom.has_key("revision")) { +        map.hw_revision = uhd::htonx<boost::uint16_t>( +            boost::lexical_cast<boost::uint16_t>(_mb_eeprom["revision"])); +    } +    if (_mb_eeprom.has_key("serial")) { +        _string_to_bytes(_mb_eeprom["serial"], MB_SERIAL_LEN, map.serial); +    } +    if (_mb_eeprom.has_key("mac-addr")) { +        byte_vector_t mac_addr = mac_addr_t::from_string(_mb_eeprom["mac-addr"]).to_bytes(); +        std::copy(mac_addr.begin(), mac_addr.end(), map.mac_addr); +    } + +    //store the name +    if (_mb_eeprom.has_key("name")) { +        _string_to_bytes(_mb_eeprom["name"], MB_NAME_LEN, map.user_name); +    } + +    for(size_t i = 0; i < sizeof(mb_eeprom_map_t); i++) +        _i2c->set_i2c_reg8(MB_ADDR, i, bytes[i]); + +} + +e300_eeprom_manager::mboard_t e300_eeprom_manager::get_mb_type(void) const +{ +    boost::mutex::scoped_lock(_mutex); +    boost::uint16_t pid = boost::lexical_cast<boost::uint16_t>( +        _mb_eeprom["product"]); +    return get_mb_type(pid); +} + +e300_eeprom_manager::mboard_t e300_eeprom_manager::get_mb_type( +    boost::uint16_t pid) +{ +    switch (pid) { +    case E300_MB_PID: +        return USRP_E300_MB; + +    case E310_MB_PID: +        return USRP_E310_MB; + +    default: +        return UNKNOWN; +    }; +} + + +std::string e300_eeprom_manager::get_mb_type_string(void) const +{ +    boost::mutex::scoped_lock(_mutex); +    boost::uint16_t product = boost::lexical_cast<boost::uint16_t>( +        _mb_eeprom["product"]); +    switch (product) { +    case E300_MB_PID: +        return "E300"; + +    case E310_MB_PID: +        return "E310"; + +    default: +        return "UNKNOWN"; +    }; +} + +i2c::sptr e300_eeprom_manager::get_i2c_sptr(void) +{ +    return _i2c; +} + + +}}} // namespace diff --git a/host/lib/usrp/e300/e300_eeprom_manager.hpp b/host/lib/usrp/e300/e300_eeprom_manager.hpp new file mode 100644 index 000000000..e77f25ed5 --- /dev/null +++ b/host/lib/usrp/e300/e300_eeprom_manager.hpp @@ -0,0 +1,125 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_E300_EEPROM_MANAGER_HPP +#define INCLUDED_E300_EEPROM_MANAGER_HPP + +#include <boost/thread/mutex.hpp> +#include <boost/shared_ptr.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> + +#include "e300_i2c.hpp" + +namespace uhd { namespace usrp { namespace e300 { + +static const boost::uint16_t E300_MB_PID = 0x77d1; +static const boost::uint16_t E310_MB_PID = 0x77d2; + +static const boost::uint16_t E300_DB_PID = 0x0100; +static const boost::uint16_t E310_DB_PID = 0x0110; + +class e300_eeprom_manager : boost::noncopyable +{ +public: +    typedef boost::shared_ptr<e300_eeprom_manager> sptr; +    e300_eeprom_manager(i2c::sptr i2c); +    ~e300_eeprom_manager(); + +    // mboard +    const mboard_eeprom_t& read_mb_eeprom(); +    void write_mb_eeprom(const mboard_eeprom_t& eeprom); + +    UHD_INLINE const mboard_eeprom_t& get_mb_eeprom() +    { +        return _mb_eeprom; +    } + +    // dboard +    const dboard_eeprom_t& read_db_eeprom(); +    void write_db_eeprom(const dboard_eeprom_t& eeprom); + +    UHD_INLINE const dboard_eeprom_t& get_db_eeprom() +    { +        return _db_eeprom; +    } + + +    i2c::sptr get_i2c_sptr(void); + +    enum mboard_t {USRP_E300_MB, USRP_E310_MB, UNKNOWN}; + +    mboard_t get_mb_type(void) const; +    static mboard_t get_mb_type(boost::uint16_t pid); +    std::string get_mb_type_string(void) const; + +private: // types +    const static size_t MB_SERIAL_LEN = 6; +    const static size_t MB_NAME_LEN   = 32; +    const static size_t MB_ADDR       = 0x51; + +    const static size_t DB_SERIAL_LEN = 6; +    const static size_t DB_ADDR       = 0x50; + +    struct mb_eeprom_map_t +    { +        // Data format version +        boost::uint16_t data_version_major; +        boost::uint16_t data_version_minor; + +        // NIC mac address +        boost::uint8_t mac_addr[6]; + +        // HW identification info +        boost::uint16_t hw_product; +        boost::uint16_t hw_revision; + +        // serial +        boost::uint8_t serial[MB_SERIAL_LEN]; +        boost::uint8_t pad[20 - MB_SERIAL_LEN]; + +        //User specific +        boost::uint8_t user_name[MB_NAME_LEN]; +    }; + +    struct db_eeprom_map_t +    { +        // Data format version +        boost::uint16_t data_version_major; +        boost::uint16_t data_version_minor; + +        // HW identification info +        boost::uint16_t hw_product; +        boost::uint16_t hw_revision; + +        // serial +        boost::uint8_t serial[MB_SERIAL_LEN]; +        boost::uint8_t pad[20 - MB_SERIAL_LEN]; +    }; + +private: // members +    mboard_eeprom_t                         _mb_eeprom; +    dboard_eeprom_t                         _db_eeprom; +    i2c::sptr                               _i2c; + +    boost::mutex    _mutex; +}; + +}}} //namespace + +#endif // INCLUDED_E300_EEPROM_MANAGER_HPP diff --git a/host/lib/usrp/e300/e300_fifo_config.cpp b/host/lib/usrp/e300/e300_fifo_config.cpp new file mode 100644 index 000000000..ac4ace7f2 --- /dev/null +++ b/host/lib/usrp/e300/e300_fifo_config.cpp @@ -0,0 +1,429 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifdef E300_NATIVE + +#include <boost/cstdint.hpp> +#include <uhd/config.hpp> + +// constants coded into the fpga parameters +static const size_t ZF_CONFIG_BASE    = 0x40000000; +static const size_t ZF_PAGE_WIDTH     = 10; +static const size_t H2S_STREAMS_WIDTH = 4; +static const size_t H2S_CMDFIFO_DEPTH = 5; +static const size_t S2H_STREAMS_WIDTH = 4; +static const size_t S2H_CMDFIFO_DEPTH = 5; + +// calculate more useful constants for this module +static const size_t ZF_PAGE_SIZE(1 << ZF_PAGE_WIDTH); +static const size_t H2S_NUM_STREAMS(1 << H2S_STREAMS_WIDTH); +static const size_t H2S_NUM_CMDS(1 << H2S_CMDFIFO_DEPTH); +static const size_t S2H_NUM_STREAMS(1 << S2H_STREAMS_WIDTH); +static const size_t S2H_NUM_CMDS(1 << S2H_CMDFIFO_DEPTH); + +//offsetsinto the arbiter memory map +static const size_t ARBITER_WR_CLEAR      = 0; +static const size_t ARBITER_RD_SIG        = 0; +static const size_t ARBITER_WR_ADDR       = 4; +static const size_t ARBITER_WR_SIZE       = 8; +static const size_t ARBITER_WR_STS_RDY    = 12; +static const size_t ARBITER_WR_STS        = 16; +static const size_t ARBITER_RB_STATUS     = 16; +static const size_t ARBITER_RB_STATUS_OCC = 20; +static const size_t ARBITER_RB_ADDR_SPACE = 24; +static const size_t ARBITER_RB_SIZE_SPACE = 28; + +// registers for the wb32_iface +static const size_t SR_CORE_READBACK = 0; + + +static UHD_INLINE size_t S2H_BASE(const size_t base) +{ +    return base + ZF_PAGE_SIZE * 0; +} + +static UHD_INLINE size_t H2S_BASE(const size_t base) +{ +    return base + ZF_PAGE_SIZE * 1; +} + +static UHD_INLINE size_t REG_BASE(const size_t base) +{ +    return base + ZF_PAGE_SIZE * 2; +} + +static UHD_INLINE size_t DST_BASE(const size_t base) +{ +    return base + ZF_PAGE_SIZE * 3; +} + +static UHD_INLINE size_t ZF_STREAM_OFF(const size_t which) +{ +    return which * 32; +} + +#include "e300_fifo_config.hpp" +#include <sys/mman.h> //mmap +#include <fcntl.h> //open, close +#include <poll.h> //poll +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> //sleep +#include <uhd/types/time_spec.hpp> //timeout +#include <uhd/utils/log.hpp> +#include <uhd/utils/atomic.hpp> + +//locking stuff for shared irq +#include <boost/thread/mutex.hpp> +#include <boost/thread/condition_variable.hpp> + +struct e300_fifo_poll_waiter +{ +    e300_fifo_poll_waiter(const int fd): +        fd(fd) +    { +        //NOP +    } + +    void wait(const double timeout) +    { +        if (_poll_claimed.cas(1, 0)) +        { +            boost::mutex::scoped_lock l(mutex); +            cond.wait(l); +        } +        else +        { +            struct pollfd fds[1]; +            fds[0].fd = fd; +            fds[0].events = POLLIN; +            ::poll(fds, 1, long(timeout*1000)); +            if (fds[0].revents & POLLIN) +                ::read(fd, NULL, 0); + +            _poll_claimed.write(0); +            cond.notify_all(); +        } +    } + +    uhd::atomic_uint32_t _poll_claimed; +    boost::condition_variable cond; +    boost::mutex mutex; +    int fd; +}; + +static const size_t DEFAULT_FRAME_SIZE = 2048; +static const size_t DEFAULT_NUM_FRAMES = 32; + +using namespace uhd; +using namespace uhd::transport; + +struct __mem_addrz_t +{ +    size_t which, phys, data, ctrl; +}; + +/*********************************************************************** + * peek n' poke mmapped space + **********************************************************************/ +UHD_INLINE void zf_poke32(const boost::uint32_t addr, const boost::uint32_t data) +{ +    volatile boost::uint32_t *p = reinterpret_cast<boost::uint32_t *>(addr); +    *p = data; +} + +UHD_INLINE boost::uint32_t zf_peek32(const boost::uint32_t addr) +{ +    volatile const boost::uint32_t *p = reinterpret_cast<const boost::uint32_t *>(addr); +    return *p; +} + +/*********************************************************************** + * managed buffer + **********************************************************************/ +struct e300_fifo_mb : managed_buffer +{ +    e300_fifo_mb(const __mem_addrz_t &addrs, const size_t len): +        ctrl_base(addrs.ctrl), phys_mem(addrs.phys), mem((void *)addrs.data), len(len){} + +    void release(void) +    { +        UHD_ASSERT_THROW(zf_peek32(ctrl_base+ARBITER_RB_ADDR_SPACE) > 0); +        UHD_ASSERT_THROW(zf_peek32(ctrl_base+ARBITER_RB_SIZE_SPACE) > 0); +        zf_poke32(ctrl_base + ARBITER_WR_ADDR, phys_mem); +        zf_poke32(ctrl_base + ARBITER_WR_SIZE, this->size()); +    } + +    template <typename T> +    UHD_INLINE typename T::sptr get_new(void) +    { +        return make(reinterpret_cast<T *>(this), mem, len); +    } + +    const size_t ctrl_base; +    const size_t phys_mem; +    void *const mem; +    const size_t len; +}; + +/*********************************************************************** + * transport + **********************************************************************/ +class e300_transport : public zero_copy_if +{ +public: +    e300_transport( +        boost::shared_ptr<void> allocator, +        const __mem_addrz_t &addrs, +        const size_t num_frames, +        const size_t frame_size, +        e300_fifo_poll_waiter *waiter, +        const bool auto_release +    ): +        _allocator(allocator), +        _addrs(addrs), +        _num_frames(num_frames), +        _frame_size(frame_size), +        _index(0), +        _waiter(waiter) +    { +        //UHD_MSG(status) << boost::format("phys 0x%x") % addrs.phys << std::endl; +        //UHD_MSG(status) << boost::format("data 0x%x") % addrs.data << std::endl; +        //UHD_MSG(status) << boost::format("ctrl 0x%x") % addrs.ctrl << std::endl; + +        const boost::uint32_t sig = zf_peek32(_addrs.ctrl + ARBITER_RD_SIG); +        UHD_ASSERT_THROW((sig >> 16) == 0xACE0); + +        zf_poke32(_addrs.ctrl + ARBITER_WR_CLEAR, 1); +        for (size_t i = 0; i < num_frames; i++) +        { +            //create a managed buffer at the given offset +            __mem_addrz_t mb_addrs = addrs; +            mb_addrs.phys += (i*frame_size); +            mb_addrs.data += (i*frame_size); +            boost::shared_ptr<e300_fifo_mb> mb(new e300_fifo_mb(mb_addrs, frame_size)); + +            //setup the buffers so they are "positioned for use" +            const size_t sts_good = (1 << 7) | (_addrs.which & 0xf); +            if (auto_release) mb->get_new<managed_recv_buffer>(); //release for read +            else zf_poke32(_addrs.ctrl + ARBITER_WR_STS, sts_good); //poke an ok into the sts fifo + +            _buffs.push_back(mb); +        } +    } + +    ~e300_transport(void) +    { +        //NOP +    } + +    template <typename T> +    UHD_INLINE typename T::sptr get_buff(const double timeout) +    { +        const time_spec_t exit_time = time_spec_t::get_system_time() + time_spec_t(timeout); +        do +        { +            if (zf_peek32(_addrs.ctrl + ARBITER_RB_STATUS_OCC)) +            { +                const boost::uint32_t sts = zf_peek32(_addrs.ctrl + ARBITER_RB_STATUS); +                UHD_ASSERT_THROW((sts >> 7) & 0x1); //assert OK +                UHD_ASSERT_THROW((sts & 0xf) == _addrs.which); //expected tag +                zf_poke32(_addrs.ctrl + ARBITER_WR_STS_RDY, 1); //pop from sts fifo +                if (_index == _num_frames) +                    _index = 0; +                return _buffs[_index++]->get_new<T>(); +            } +            _waiter->wait(timeout); +            //boost::this_thread::sleep(boost::posix_time::milliseconds(1)); +        } +        while (time_spec_t::get_system_time() < exit_time); + +        return typename T::sptr(); +    } + +    managed_recv_buffer::sptr get_recv_buff(const double timeout) +    { +        return this->get_buff<managed_recv_buffer>(timeout); +    } + +    size_t get_num_recv_frames(void) const +    { +        return _num_frames; +    } + +    size_t get_recv_frame_size(void) const +    { +        return _frame_size; +    } + +    managed_send_buffer::sptr get_send_buff(const double timeout) +    { +        return this->get_buff<managed_send_buffer>(timeout); +    } + +    size_t get_num_send_frames(void) const +    { +        return _num_frames; +    } + +    size_t get_send_frame_size(void) const +    { +        return _frame_size; +    } + +private: +    boost::shared_ptr<void> _allocator; +    const __mem_addrz_t _addrs; +    const size_t _num_frames; +    const size_t _frame_size; +    size_t _index; +    e300_fifo_poll_waiter *_waiter; +    std::vector<boost::shared_ptr<e300_fifo_mb> > _buffs; +}; + +/*********************************************************************** + * memory mapping + **********************************************************************/ +class e300_fifo_interface_impl : public virtual e300_fifo_interface +{ +public: +    e300_fifo_interface_impl(const e300_fifo_config_t &config): +        _config(config), +        _bytes_in_use(0), +        _recv_entries_in_use(std::vector<size_t>(S2H_NUM_STREAMS, 0)), +        _send_entries_in_use(std::vector<size_t>(H2S_NUM_STREAMS, 0)) +    { +        //open the file descriptor to our kernel module +        const std::string dev = "/dev/axi_fpga"; +        _fd = ::open(dev.c_str(), O_RDWR|O_SYNC); +        if (_fd < 0) +        { +            throw uhd::runtime_error("e300: failed to open " + dev); +        } + +        //mmap the control and data regions into virtual space +        //UHD_VAR(_config.ctrl_length); +        //UHD_VAR(_config.buff_length); +        //UHD_VAR(_config.phys_addr); +        _buff = ::mmap(NULL, _config.ctrl_length + _config.buff_length, PROT_READ|PROT_WRITE, MAP_SHARED, _fd, 0); +        if (_buff == MAP_FAILED) +        { +            ::close(_fd); +            throw uhd::runtime_error("e300: failed to mmap " + dev); +        } + +        //segment the memory according to zynq fifo arbiter +        _ctrl_space = size_t(_buff); +        _data_space = size_t(_buff) + _config.ctrl_length; + +        //zero out the data region +        std::memset((void *)_data_space, 0, _config.buff_length); + +        //create a poll _waiter for the transports +        _waiter = new e300_fifo_poll_waiter(_fd); +    } + +    virtual ~e300_fifo_interface_impl(void) +    { +        delete _waiter; +        UHD_LOG << "cleanup: munmap" << std::endl; +        ::munmap(_buff, _config.ctrl_length + _config.buff_length); +        ::close(_fd); +    } + +    uhd::transport::zero_copy_if::sptr make_recv_xport( +        const size_t which_stream, +        const uhd::transport::zero_copy_xport_params ¶ms) +    { +        return this->_make_xport(which_stream, params, true); +    } + +    uhd::transport::zero_copy_if::sptr make_send_xport( +        const size_t which_stream, +        const uhd::transport::zero_copy_xport_params ¶ms) +    { +        return this->_make_xport(which_stream, params, false); +    } + +    size_t get_global_regs_base() const +    { +        return REG_BASE(_ctrl_space); +    } + +private: +    uhd::transport::zero_copy_if::sptr _make_xport( +        const size_t which_stream, +        const uhd::transport::zero_copy_xport_params ¶ms, +        const bool is_recv) +    { +        boost::mutex::scoped_lock lock(_setup_mutex); + +        const size_t frame_size = is_recv ? params.recv_frame_size : params.send_frame_size; +        const size_t num_frames = is_recv ? params.num_recv_frames : params.num_send_frames; +        size_t &entries_in_use = (is_recv)? _recv_entries_in_use.at(which_stream) +                                          : _send_entries_in_use.at(which_stream); + +        __mem_addrz_t addrs; +        addrs.which = which_stream; +        addrs.phys = _config.phys_addr + _bytes_in_use; +        addrs.data = _data_space + _bytes_in_use; +        addrs.ctrl = ((is_recv)? S2H_BASE(_ctrl_space) : H2S_BASE(_ctrl_space)) + ZF_STREAM_OFF(which_stream); + +        uhd::transport::zero_copy_if::sptr xport; +        if (is_recv) xport.reset(new e300_transport(shared_from_this(), addrs, num_frames, frame_size, _waiter, is_recv)); +        else         xport.reset(new e300_transport(shared_from_this(), addrs, num_frames, frame_size, _waiter, is_recv)); + +        _bytes_in_use += num_frames*frame_size; +        entries_in_use += num_frames; + +        UHD_ASSERT_THROW(_recv_entries_in_use.at(which_stream) <= S2H_NUM_CMDS); +        UHD_ASSERT_THROW(_send_entries_in_use.at(which_stream) <= H2S_NUM_CMDS); +        UHD_ASSERT_THROW(_bytes_in_use <= _config.buff_length); + + +        return xport; +    } + +    e300_fifo_config_t     _config; +    e300_fifo_poll_waiter *_waiter; +    size_t                 _bytes_in_use; +    int                    _fd; +    void                  *_buff; +    size_t                 _ctrl_space; +    size_t                 _data_space; +    std::vector<size_t>    _recv_entries_in_use; +    std::vector<size_t>    _send_entries_in_use; +    boost::mutex           _setup_mutex; +}; + +e300_fifo_interface::sptr e300_fifo_interface::make(const e300_fifo_config_t &config) +{ +    return e300_fifo_interface::sptr(new e300_fifo_interface_impl(config)); +} + +#else //E300_NATIVE + +#include "e300_fifo_config.hpp" +#include <uhd/exception.hpp> + +e300_fifo_interface::sptr e300_fifo_interface::make(const e300_fifo_config_t &) +{ +    throw uhd::assertion_error("e300_fifo_interface::make() !E300_NATIVE"); +} + +#endif //E300_NATIVE diff --git a/host/lib/usrp/e300/e300_fifo_config.hpp b/host/lib/usrp/e300/e300_fifo_config.hpp new file mode 100644 index 000000000..967f451ca --- /dev/null +++ b/host/lib/usrp/e300/e300_fifo_config.hpp @@ -0,0 +1,52 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_E300_FIFO_CONFIG_HPP +#define INCLUDED_E300_FIFO_CONFIG_HPP + +#include <uhd/types/device_addr.hpp> +#include <uhd/types/wb_iface.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <boost/enable_shared_from_this.hpp> + +struct e300_fifo_config_t +{ +    size_t ctrl_length; +    size_t buff_length; +    size_t phys_addr; +}; + +e300_fifo_config_t e300_read_sysfs(void); +std::string e300_get_sysfs_attr(const std::string &node, const std::string &attr); + +struct e300_fifo_interface : boost::enable_shared_from_this<e300_fifo_interface> +{ +    typedef boost::shared_ptr<e300_fifo_interface> sptr; +    static sptr make(const e300_fifo_config_t &config); + +    virtual uhd::transport::zero_copy_if::sptr make_recv_xport( +        const size_t which_stream, +        const uhd::transport::zero_copy_xport_params ¶ms) = 0; + +    virtual uhd::transport::zero_copy_if::sptr make_send_xport( +        const size_t which_stream, +        const uhd::transport::zero_copy_xport_params &parms) = 0; + +    virtual size_t get_global_regs_base(void) const = 0; +}; + +#endif /* INCLUDED_E300_FIFO_CONFIG_HPP */ diff --git a/host/lib/usrp/e300/e300_fpga_defs.hpp b/host/lib/usrp/e300/e300_fpga_defs.hpp new file mode 100644 index 000000000..d58fd63a9 --- /dev/null +++ b/host/lib/usrp/e300/e300_fpga_defs.hpp @@ -0,0 +1,29 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_E300_FPGA_DEFS_HPP +#define INCLUDED_E300_FPGA_DEFS_HPP +namespace uhd { namespace usrp { namespace e300 { namespace fpga { + +static const size_t NUM_RADIOS = 2; + +static const boost::uint32_t COMPAT_MAJOR = 4; +static const boost::uint32_t COMPAT_MINOR = 0; + +}}}} // namespace + +#endif // INCLUDED_E300_FPGA_DEFS_HPP diff --git a/host/lib/usrp/e300/e300_global_regs.cpp b/host/lib/usrp/e300/e300_global_regs.cpp new file mode 100644 index 000000000..3ba895826 --- /dev/null +++ b/host/lib/usrp/e300/e300_global_regs.cpp @@ -0,0 +1,131 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "e300_global_regs.hpp" + +#include <boost/cstdint.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/byteswap.hpp> +#include <cstring> +#include <iostream> + +namespace uhd { namespace usrp { namespace e300 { + +class global_regs_local_impl : public global_regs +{ +public: +    global_regs_local_impl(const size_t ctrl_base) : _ctrl_base(ctrl_base) +    { +    } + +    virtual ~global_regs_local_impl(void) +    { +    } + +    boost::uint32_t peek32(const uhd::wb_iface::wb_addr_type addr) +    { +        // setup readback register +        _poke32(_ctrl_base + global_regs::SR_CORE_READBACK, addr); +        return _peek32(_ctrl_base); +    } + +    void poke32(const uhd::wb_iface::wb_addr_type addr, const boost::uint32_t data) +    { +        _poke32(_ctrl_base + static_cast<size_t>(addr), data); +    } + + +private: +    const size_t _ctrl_base; + +    UHD_INLINE void _poke32(const boost::uint32_t addr, const boost::uint32_t data) +    { +        volatile boost::uint32_t *p = reinterpret_cast<boost::uint32_t *>(addr); +        *p = data; +    } + +    UHD_INLINE boost::uint32_t _peek32(const boost::uint32_t addr) +    { +        volatile const boost::uint32_t *p = reinterpret_cast<const boost::uint32_t *>(addr); +        return *p; +    } +}; + +global_regs::sptr global_regs::make(const size_t ctrl_base) +{ +    return sptr(new global_regs_local_impl(ctrl_base)); +} + +class global_regs_zc_impl : public global_regs +{ +public: +    global_regs_zc_impl(uhd::transport::zero_copy_if::sptr xport) : _xport(xport) +    { +    } + +    virtual ~global_regs_zc_impl(void) +    { +    } + +    boost::uint32_t peek32(const uhd::wb_iface::wb_addr_type addr) +    { +        global_regs_transaction_t transaction; +        transaction.is_poke = uhd::htonx<boost::uint32_t>(0); +        transaction.addr    = uhd::htonx<boost::uint32_t>( +            static_cast<boost::uint32_t>(addr)); +        { +            uhd::transport::managed_send_buffer::sptr buff = _xport->get_send_buff(10.0); +            if (not buff or buff->size() < sizeof(transaction)) +                throw std::runtime_error("global_regs_zc_impl send timeout"); +            std::memcpy(buff->cast<void *>(), &transaction, sizeof(transaction)); +            buff->commit(sizeof(transaction)); +        } +        { +            uhd::transport::managed_recv_buffer::sptr buff = _xport->get_recv_buff(10.0); +            if (not buff or buff->size() < sizeof(transaction)) +                throw std::runtime_error("global_regs_zc_impl recv timeout"); +            std::memcpy(&transaction, buff->cast<const void *>(), sizeof(transaction)); +        } +        return uhd::ntohx<boost::uint32_t>(transaction.data); +    } + +    void poke32(const uhd::wb_iface::wb_addr_type addr, const boost::uint32_t data) +    { +        global_regs_transaction_t transaction; +        transaction.is_poke = uhd::htonx<boost::uint32_t>(1); +        transaction.addr    = uhd::htonx<boost::uint32_t>( +            static_cast<boost::uint32_t>(addr)); +        transaction.data    = uhd::htonx<boost::uint32_t>(data); +        { +            uhd::transport::managed_send_buffer::sptr buff = _xport->get_send_buff(10.0); +            if (not buff or buff->size() < sizeof(transaction)) +                throw uhd::runtime_error("global_regs_zc_impl send timeout"); +            std::memcpy(buff->cast<void *>(), &transaction, sizeof(transaction)); +            buff->commit(sizeof(transaction)); +        } +    } + +private: +    uhd::transport::zero_copy_if::sptr _xport; +}; + +global_regs::sptr global_regs::make(uhd::transport::zero_copy_if::sptr xport) +{ +    return sptr(new global_regs_zc_impl(xport)); +} + +}}}; diff --git a/host/lib/usrp/e300/e300_global_regs.hpp b/host/lib/usrp/e300/e300_global_regs.hpp new file mode 100644 index 000000000..12693da79 --- /dev/null +++ b/host/lib/usrp/e300/e300_global_regs.hpp @@ -0,0 +1,78 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_E300_GLOBAL_REGS_HPP +#define INCLUDED_E300_GLOBAL_REGS_HPP + +#include <uhd/types/wb_iface.hpp> +#include <uhd/transport/zero_copy.hpp> + +namespace uhd { namespace usrp { namespace e300 { + +struct global_regs_transaction_t { +    boost::uint32_t is_poke; +    boost::uint32_t addr; +    boost::uint32_t data; +    boost::uint32_t pad; +}; + +class global_regs : boost::noncopyable, public virtual uhd::wb_iface +{ +public: +    typedef boost::shared_ptr<global_regs> sptr; + +    static sptr make(const size_t ctrl_base); +    static sptr make(uhd::transport::zero_copy_if::sptr xport); + +    static const size_t SR_CORE_READBACK = 0; +    static const size_t SR_CORE_MISC     = 4; +    static const size_t SR_CORE_TEST     = 28; +    static const size_t SR_CORE_XB_LOCAL = 32; + +    // leave some room for registers, +    // xbar starts with an offset of one +    // 1K page. A part of which is used for +    // DST_LOOKUP for DST_LOOKUP + +    static const size_t SR_CORE_DST       = 1024; +    static const size_t SR_CORE_XBAR      = 2048; + +    static const size_t RB32_CORE_MISC    = 1; +    static const size_t RB32_CORE_COMPAT  = 2; +    static const size_t RB32_CORE_GITHASH = 3; +    static const size_t RB32_CORE_PLL     = 4; +    static const size_t RB32_CORE_TEST    = 24; + +    // PPS selection +    static const size_t PPS_GPS = 0; +    static const size_t PPS_INT = 2; +    static const size_t PPS_EXT = 3; +}; + +UHD_INLINE boost::uint32_t XB_ADDR(const boost::uint32_t addr) +{ +    return global_regs::SR_CORE_XBAR + (addr << 2); +} + +UHD_INLINE boost::uint32_t DST_ADDR(const boost::uint32_t addr) +{ +    return global_regs::SR_CORE_DST + (addr << 2); +} + +}}}; + +#endif /* INCLUDED_E300_GLOBAL_REGS_HPP */ diff --git a/host/lib/usrp/e300/e300_i2c.cpp b/host/lib/usrp/e300/e300_i2c.cpp new file mode 100644 index 000000000..d8f535a98 --- /dev/null +++ b/host/lib/usrp/e300/e300_i2c.cpp @@ -0,0 +1,409 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/exception.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/transport/udp_simple.hpp> + + +#include "e300_i2c.hpp" +#include <cstring> + +namespace uhd { namespace usrp { namespace e300 { + +class zc_impl : public i2c +{ +public: +    zc_impl(uhd::transport::zero_copy_if::sptr xport) : _xport(xport) +    { +    } + +    virtual ~zc_impl(void) +    { +    } + +    void set_i2c_reg8( +        const boost::uint8_t addr, +        const boost::uint8_t reg, +        const boost::uint8_t value) +    { +        i2c_transaction_t transaction; +        transaction.type = WRITE | ONEBYTE; +        transaction.addr = addr; +        transaction.reg  = uhd::htonx<boost::uint16_t>(reg); +        transaction.data = value; +        { +            uhd::transport::managed_send_buffer::sptr buff = _xport->get_send_buff(10.0); +            if (not buff or buff->size() < sizeof(transaction)) +                throw uhd::runtime_error("i2c_zc_impl send timeout"); +            std::memcpy(buff->cast<void *>(), &transaction, sizeof(transaction)); +            buff->commit(sizeof(transaction)); +        } +    } + +    boost::uint8_t get_i2c_reg8( +        const boost::uint8_t addr, +        const boost::uint8_t reg) +    { +        i2c_transaction_t transaction; +        transaction.type = READ | ONEBYTE; +        transaction.addr = addr; +        transaction.reg = uhd::htonx<boost::uint16_t>(reg); +        { +            uhd::transport::managed_send_buffer::sptr buff = _xport->get_send_buff(10.0); +            if (not buff or buff->size() < sizeof(transaction)) +                throw std::runtime_error("i2c_zc_impl send timeout"); +            std::memcpy(buff->cast<void *>(), &transaction, sizeof(transaction)); +            buff->commit(sizeof(transaction)); +        } +        { +            uhd::transport::managed_recv_buffer::sptr buff = _xport->get_recv_buff(10.0); +            if (not buff or buff->size() < sizeof(transaction)) +                throw std::runtime_error("i2c_zc_impl recv timeout"); +            std::memcpy(&transaction, buff->cast<const void *>(), sizeof(transaction)); +        } +        return transaction.data; +    } + +    void set_i2c_reg16( +        const boost::uint8_t addr, +        const boost::uint16_t reg, +        const boost::uint8_t value) +    { +        i2c_transaction_t transaction; +        transaction.type = WRITE | TWOBYTE; +        transaction.addr = addr; +        transaction.reg  = uhd::htonx<boost::uint16_t>(reg); +        transaction.data = value; +        { +            uhd::transport::managed_send_buffer::sptr buff = _xport->get_send_buff(10.0); +            if (not buff or buff->size() < sizeof(transaction)) +                throw uhd::runtime_error("i2c_zc_impl send timeout"); +            std::memcpy(buff->cast<void *>(), &transaction, sizeof(transaction)); +            buff->commit(sizeof(transaction)); +        } +    } + +    boost::uint8_t get_i2c_reg16( +        const boost::uint8_t addr, +        const boost::uint16_t reg) +    { +        i2c_transaction_t transaction; +        transaction.type = READ | TWOBYTE; +        transaction.addr = addr; +        transaction.reg  = uhd::htonx<boost::uint16_t>(reg); +        { +            uhd::transport::managed_send_buffer::sptr buff = _xport->get_send_buff(10.0); +            if (not buff or buff->size() < sizeof(transaction)) +                throw std::runtime_error("i2c_zc_impl send timeout"); +            std::memcpy(buff->cast<void *>(), &transaction, sizeof(transaction)); +            buff->commit(sizeof(transaction)); +        } +        { +            uhd::transport::managed_recv_buffer::sptr buff = _xport->get_recv_buff(10.0); +            if (not buff or buff->size() < sizeof(transaction)) +                throw std::runtime_error("i2c_zc_impl recv timeout"); +            std::memcpy(&transaction, buff->cast<const void *>(), sizeof(transaction)); +        } +        return transaction.data; +    } + + +private: +    uhd::transport::zero_copy_if::sptr _xport; +}; + +i2c::sptr i2c::make_zc(uhd::transport::zero_copy_if::sptr xport) +{ +    return sptr(new zc_impl(xport)); +} + +class simple_udp_impl : public i2c +{ +public: +    simple_udp_impl(const std::string &ip_addr, const std::string &port) +    { +        _xport = uhd::transport::udp_simple::make_connected(ip_addr, port); +    } + +    virtual ~simple_udp_impl(void) +    { +    } + +    void set_i2c_reg8( +        const boost::uint8_t addr, +        const boost::uint8_t reg, +        const boost::uint8_t value) +    { +        i2c_transaction_t transaction; +        transaction.type = i2c::WRITE | ONEBYTE; +        transaction.addr = addr; +        transaction.reg  = uhd::htonx<boost::uint16_t>(reg); +        transaction.data = value; + +        _xport->send( +            boost::asio::buffer( +                &transaction, +                sizeof(transaction))); +    } + +    boost::uint8_t get_i2c_reg8( +        const boost::uint8_t addr, +        const boost::uint8_t reg) +    { +        i2c_transaction_t transaction; +        transaction.type = i2c::READ | ONEBYTE; +        transaction.addr = addr; +        transaction.reg  = uhd::htonx<boost::uint16_t>(reg); +        transaction.data = 0; + +        _xport->send( +            boost::asio::buffer( +                &transaction, +                sizeof(transaction))); + +        boost::uint8_t buff[sizeof(i2c_transaction_t)] = {}; +        const size_t nbytes = _xport->recv( +            boost::asio::buffer(buff), 0.100); +        if (not (nbytes == sizeof(transaction))) +            throw std::runtime_error("i2c_simple_udp_impl recv timeout"); +        i2c_transaction_t *reply = reinterpret_cast<i2c_transaction_t*>(buff); +        return reply->data; +    } + +    void set_i2c_reg16( +        const boost::uint8_t addr, +        const boost::uint16_t reg, +        const boost::uint8_t value) +    { +        i2c_transaction_t transaction; +        transaction.type = i2c::WRITE | TWOBYTE; +        transaction.addr = addr; +        transaction.reg = uhd::htonx<boost::uint16_t>(reg); +        transaction.data = value; + +        _xport->send( +            boost::asio::buffer( +                &transaction, +                sizeof(transaction))); +    } + +    boost::uint8_t get_i2c_reg16( +        const boost::uint8_t addr, +        const boost::uint16_t reg) +    { +        i2c_transaction_t transaction; +        transaction.type = i2c::READ | TWOBYTE; +        transaction.addr = addr; +        transaction.reg = uhd::htonx<boost::uint16_t>(reg); +        transaction.data = 0; + +        _xport->send( +            boost::asio::buffer( +                &transaction, +                sizeof(transaction))); + +        boost::uint8_t buff[sizeof(i2c_transaction_t)] = {}; +        const size_t nbytes = _xport->recv( +            boost::asio::buffer(buff), 0.100); +        if (not (nbytes == sizeof(transaction))) +            throw std::runtime_error("i2c_simple_udp_impl recv timeout"); +        i2c_transaction_t *reply = reinterpret_cast<i2c_transaction_t*>(buff); +        return reply->data; +    } + +private: +    uhd::transport::udp_simple::sptr _xport; +}; + +i2c::sptr i2c::make_simple_udp( +    const std::string &ip_addr, +    const std::string &port) +{ +    return sptr(new simple_udp_impl(ip_addr,port)); +} + +}}} // namespace + +#ifdef E300_NATIVE + +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/ioctl.h> + +#include <boost/thread.hpp> +#include <boost/cstdint.hpp> + +namespace uhd { namespace usrp { namespace e300 { + +class i2cdev_impl : public i2c +{ +public: +    i2cdev_impl(const std::string &device) +    { +        _fd = ::open(device.c_str(), O_RDWR); +        if (_fd < 0) +            throw uhd::system_error("open failed."); +    } + +    virtual ~i2cdev_impl(void) +    { +        close(_fd); +    } + +    void set_i2c_reg8( +        const boost::uint8_t addr, +        const boost::uint8_t reg, +        const boost::uint8_t value) +    { +        boost::uint8_t outbuf[2]; +        i2c_rdwr_ioctl_data packets; +        i2c_msg messages[1]; + +        messages[0].addr = addr; +        messages[0].flags = 0; +        messages[0].len = sizeof(outbuf); +        messages[0].buf = outbuf; + +        outbuf[0] = reg; +        outbuf[1] = value; + +        packets.msgs = messages; +        packets.nmsgs = 1; + +        if(::ioctl(_fd, I2C_RDWR, &packets) < 0) { +            throw std::runtime_error("ioctl failed"); +        } +        // this is ugly +        boost::this_thread::sleep(boost::posix_time::milliseconds(5)); +    } + +    boost::uint8_t get_i2c_reg8( +        const boost::uint8_t addr, +        const boost::uint8_t reg) +    { +        i2c_rdwr_ioctl_data packets; +        i2c_msg messages[2]; + +        boost::uint8_t outbuf = reg; +        messages[0].addr = addr; +        messages[0].flags = 0; +        messages[0].len = sizeof(outbuf); +        messages[0].buf = &outbuf; + +        boost::uint8_t inbuf; +        messages[1].addr = addr; +        messages[1].flags = I2C_M_RD; +        messages[1].len = sizeof(inbuf); +        messages[1].buf = &inbuf; + +        packets.msgs = messages; +        packets.nmsgs = 2; + +        if(::ioctl(_fd, I2C_RDWR, &packets) < 0) { +            throw std::runtime_error("ioctl failed."); +        } + +        return inbuf; +    } + +    // the daughterboard uses 16 bit addresses +    void set_i2c_reg16( +        const boost::uint8_t addr, +        const boost::uint16_t reg, +        const boost::uint8_t value) +    { +        boost::uint8_t outbuf[3]; +        i2c_rdwr_ioctl_data packets; +        i2c_msg messages[1]; + +        messages[0].addr = addr; +        messages[0].flags = 0; +        messages[0].len = sizeof(outbuf); +        messages[0].buf = outbuf; + +        outbuf[0] = (reg >> 8) & 0xff; +        outbuf[1] = reg & 0xff; +        outbuf[2] = value; + +        packets.msgs = messages; +        packets.nmsgs = 1; + +        if(::ioctl(_fd, I2C_RDWR, &packets) < 0) { +            throw std::runtime_error("ioctl failed"); +        } +        // this is ugly +        boost::this_thread::sleep(boost::posix_time::milliseconds(5)); +    } + + +    // the daughterboard eeprom uses 16 bit addresses +    boost::uint8_t get_i2c_reg16( +        const boost::uint8_t addr, +        const boost::uint16_t reg) +    { +        i2c_rdwr_ioctl_data packets; +        i2c_msg messages[2]; + +        // always little endian +        boost::uint8_t outbuf[2]; +        outbuf[0] = (reg >> 8) & 0xff; +        outbuf[1] = reg & 0xff; + +        messages[0].addr = addr; +        messages[0].flags = 0; +        messages[0].len = sizeof(outbuf); +        messages[0].buf = outbuf; + +        boost::uint8_t inbuf; +        messages[1].addr = addr; +        messages[1].flags = I2C_M_RD; +        messages[1].len = sizeof(inbuf); +        messages[1].buf = &inbuf; + +        packets.msgs = messages; +        packets.nmsgs = 2; + +        if(::ioctl(_fd, I2C_RDWR, &packets) < 0) { +            throw std::runtime_error("ioctl failed."); +        } + +        return inbuf; +    } + +private: +    int _fd; +}; + +}}} // namespace + +using namespace uhd::usrp::e300; + +i2c::sptr i2c::make_i2cdev(const std::string &device) +{ +    return sptr(new i2cdev_impl(device)); +} +#else +using namespace uhd::usrp::e300; + +i2c::sptr i2c::make_i2cdev(const std::string &) +{ +    throw uhd::assertion_error("i2c::make() !E300_NATIVE"); +} +#endif // E300_NATIVE diff --git a/host/lib/usrp/e300/e300_i2c.hpp b/host/lib/usrp/e300/e300_i2c.hpp new file mode 100644 index 000000000..6cca7ab70 --- /dev/null +++ b/host/lib/usrp/e300/e300_i2c.hpp @@ -0,0 +1,77 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_E300_I2C_HPP +#define INCLUDED_E300_I2C_HPP + +#include <boost/noncopyable.hpp> +#include <boost/cstdint.hpp> +#include <boost/shared_ptr.hpp> + +#include <uhd/transport/zero_copy.hpp> + +namespace uhd { namespace usrp { namespace e300 { + +struct i2c_transaction_t { +    boost::uint16_t reg; +    boost::uint8_t  addr; +    boost::uint8_t  data; +    boost::uint8_t  type; +}; + +class i2c : public boost::noncopyable +{ +public: +    typedef boost::shared_ptr<i2c> sptr; + +    static sptr make_i2cdev(const std::string &device); +    static sptr make_zc(uhd::transport::zero_copy_if::sptr xport); +    static sptr make_simple_udp( +        const std::string &ip_addr, +        const std::string &port); + +    virtual boost::uint8_t get_i2c_reg8( +        const boost::uint8_t addr, +        const boost::uint8_t reg) = 0; + +    virtual boost::uint8_t get_i2c_reg16( +        const boost::uint8_t addr, +        const boost::uint16_t reg) = 0; + +    virtual void set_i2c_reg8( +        const boost::uint8_t addr, +        const boost::uint8_t reg, +        const boost::uint8_t value) = 0; + +    virtual void set_i2c_reg16( +        const boost::uint8_t addr, +        const boost::uint16_t reg, +        const boost::uint8_t value) = 0; + + +    static const boost::uint8_t DB_EEPROM_ADDR = 0x50; +    static const boost::uint8_t MB_EEPROM_ADDR = 0x51; + +    static const boost::uint8_t WRITE          = 0x1; +    static const boost::uint8_t READ           = 0x0; +    static const boost::uint8_t TWOBYTE        = 0x4; +    static const boost::uint8_t ONEBYTE        = 0x2; +}; + +}}}; + +#endif // INCLUDED_E300_I2C_HPP diff --git a/host/lib/usrp/e300/e300_impl.cpp b/host/lib/usrp/e300/e300_impl.cpp new file mode 100644 index 000000000..0f4cb5852 --- /dev/null +++ b/host/lib/usrp/e300/e300_impl.cpp @@ -0,0 +1,1347 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "e300_impl.hpp" +#include "e300_defaults.hpp" +#include "e300_fpga_defs.hpp" +#include "e300_spi.hpp" +#include "e300_regs.hpp" +#include "e300_eeprom_manager.hpp" +#include "e300_sensor_manager.hpp" +#include "e300_common.hpp" +#include "e300_remote_codec_ctrl.hpp" + +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/images.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/types/sensors.hpp> +#include <boost/make_shared.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/format.hpp> +#include <boost/filesystem.hpp> +#include <boost/functional/hash.hpp> +#include <boost/bind.hpp> +#include <boost/make_shared.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/thread/thread.hpp> //sleep +#include <boost/asio.hpp> +#include <fstream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; +namespace fs = boost::filesystem; +namespace asio = boost::asio; + +//! mapping of frontend to radio perif index +static const size_t FE0 = 1; +static const size_t FE1 = 0; + +namespace uhd { namespace usrp { namespace e300 { + +/*********************************************************************** + * Discovery + **********************************************************************/ + +static std::vector<std::string> discover_ip_addrs( +    const std::string& addr_hint, const std::string& port) +{ +    std::vector<std::string> addrs; + +    // Create a UDP transport to communicate: +    // Some devices will cause a throw when opened for a broadcast address. +    // We print and recover so the caller can loop through all bcast addrs. +    uhd::transport::udp_simple::sptr udp_bcast_xport; +    try { +        udp_bcast_xport = uhd::transport::udp_simple::make_broadcast(addr_hint, port); +    } catch(const std::exception &e) { +        UHD_MSG(error) << boost::format("Cannot open UDP transport on %s for discovery\n%s") +        % addr_hint % e.what() << std::endl; +        return addrs; +    } catch(...) { +        UHD_MSG(error) << "E300 Network discovery unknown error" << std::endl; +        return addrs; +    } + +    // TODO: Do not abuse the I2C transport here ... +    // we send a read request to i2c address 0x51, +    // to read register 0 +    i2c_transaction_t req; +    req.type = i2c::READ | i2c::ONEBYTE; +    req.addr = 0x51; // mboard's eeprom address, we don't really care +    req.reg = 4; + +    // send dummy request +    try { +    udp_bcast_xport->send(boost::asio::buffer(&req, sizeof(req))); +    } catch (const std::exception &ex) { +        UHD_MSG(error) << "E300 Network discovery error " << ex.what() << std::endl; +        return addrs; +    } catch(...) { +        UHD_MSG(error) << "E300 Network discovery unknown error" << std::endl; +        return addrs; +    } + +    // loop for replies until timeout +    while (true) { +        boost::uint8_t buff[sizeof(i2c_transaction_t)] = {}; +        const size_t nbytes = udp_bcast_xport->recv(boost::asio::buffer(buff), 0.050); +        if (nbytes == 0) +            break; //No more responses + +        const i2c_transaction_t *reply = reinterpret_cast<const i2c_transaction_t*>(buff); +        if (req.addr == reply->addr) +           addrs.push_back(udp_bcast_xport->get_recv_addr()); +    } + +    return addrs; +} + +static bool is_loopback(const if_addrs_t &if_addrs) +{ +       return if_addrs.inet == asio::ip::address_v4::loopback().to_string(); +} + +static device_addrs_t e300_find(const device_addr_t &multi_dev_hint) +{ +    // handle multi device discovery +    device_addrs_t hints = separate_device_addr(multi_dev_hint); + +    if (hints.size() > 1) { +        device_addrs_t found_devices; +        std::string err_msg; +        BOOST_FOREACH(const device_addr_t &hint_i, hints) +        { +            device_addrs_t found_devices_i = e300_find(hint_i); +            if(found_devices_i.size() != 1) +                err_msg += str(boost::format( +                    "Could not resolve device hint \"%s\" to a single device.") +                    % hint_i.to_string()); +            else +                found_devices.push_back(found_devices_i[0]); +            if (found_devices.empty()) +                return device_addrs_t(); + +            if (not err_msg.empty()) +                throw uhd::value_error(err_msg); +        } +        return device_addrs_t(1, combine_device_addrs(found_devices)); +    } + +    // initialize the hint for a single device case +    UHD_ASSERT_THROW(hints.size() <= 1); +    hints.resize(1); // in case it was empty +    device_addr_t hint = hints[0]; +    device_addrs_t e300_addrs; + +    // return an empty list of addresses when type is set to non-e300 +    if (hint.has_key("type") and hint["type"] != "e3x0") +        return e300_addrs; + +    const bool loopback_only = +        get_if_addrs().size() == 1 and is_loopback(get_if_addrs().at(0)); + +    // if we don't have connectivity, we might as well skip the network part +    if (not loopback_only) { +        // if no address or node has been specified, send a broadcast +        if ((not hint.has_key("addr")) and (not hint.has_key("node"))) { +            BOOST_FOREACH(const if_addrs_t &if_addrs, get_if_addrs()) +            { +                // avoid the loopback device +                if (is_loopback(if_addrs)) +                    continue; + +                // create a new hint with this broadcast address +                device_addr_t new_hint = hint; +                new_hint["addr"] = if_addrs.bcast; + +                // call discover with the new hint ad append results +                device_addrs_t new_e300_addrs = e300_find(new_hint); +                e300_addrs.insert(e300_addrs.begin(), +                    new_e300_addrs.begin(), new_e300_addrs.end()); + +            } +            return e300_addrs; +        } + +        std::vector<std::string> ip_addrs = discover_ip_addrs( +            hint["addr"], E300_SERVER_I2C_PORT); + +        BOOST_FOREACH(const std::string &ip_addr, ip_addrs) +        { +            device_addr_t new_addr; +            new_addr["type"] = "e3x0"; +            new_addr["addr"] = ip_addr; + +            // see if we can read the eeprom +            try { +                e300_eeprom_manager eeprom_manager( +                    i2c::make_simple_udp(new_addr["addr"], E300_SERVER_I2C_PORT)); +                const mboard_eeprom_t eeprom = eeprom_manager.get_mb_eeprom(); +                new_addr["name"] = eeprom["name"]; +                new_addr["serial"] = eeprom["serial"]; +                new_addr["product"] = eeprom["product"]; +            } catch (...) { +                // set these values as empty string, so the device may still be found +                // and the filters below can still operate on the discovered device +                new_addr["name"] = ""; +                new_addr["serial"] = ""; +            } +            // filter the discovered device below by matching optional keys +            if ((not hint.has_key("name")   or hint["name"]   == new_addr["name"]) and +                (not hint.has_key("serial") or hint["serial"] == new_addr["serial"])) +            { +                e300_addrs.push_back(new_addr); +            } +        } +    } + +    // finally search locally +    // if device node is not provided, +    // use the default one +    if (not hint.has_key("node")) { +        device_addr_t new_addr = hint; +        new_addr["node"] = "/dev/axi_fpga"; +        return e300_find(new_addr); +    } + +    // use the given node +    if (fs::exists(hint["node"])) { +        device_addr_t new_addr; +        new_addr["type"] = "e3x0"; +        new_addr["node"] = fs::system_complete(fs::path(hint["node"])).string(); + +        try { +            e300_eeprom_manager eeprom_manager(i2c::make_i2cdev(E300_I2CDEV_DEVICE)); +            const mboard_eeprom_t eeprom = eeprom_manager.get_mb_eeprom(); +            new_addr["name"] = eeprom["name"]; +            new_addr["serial"] = eeprom["serial"]; +            new_addr["product"] = eeprom["product"]; +        } catch (...) { +            // set these values as empty string, so the device may still be found +            // and the filters below can still operate on the discovered device +            new_addr["name"] = ""; +            new_addr["serial"] = ""; +        } +        // filter the discovered device below by matching optional keys +        if ((not hint.has_key("name")   or hint["name"]   == new_addr["name"]) and +            (not hint.has_key("serial") or hint["serial"] == new_addr["serial"])) +        { +            e300_addrs.push_back(new_addr); +        } +    } + +    return e300_addrs; +} + + +/*********************************************************************** + * Make + **********************************************************************/ +static device::sptr e300_make(const device_addr_t &device_addr) +{ +    UHD_LOG << "e300_make with args " << device_addr.to_pp_string() << std::endl; +    if(device_addr.has_key("server")) +        throw uhd::runtime_error( +            str(boost::format("Please run the server executable \"%s\"") +                % "usrp_e3x0_network_mode")); +    else +        return device::sptr(new e300_impl(device_addr)); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +e300_impl::e300_impl(const uhd::device_addr_t &device_addr) +    : _device_addr(device_addr) +    , _xport_path(device_addr.has_key("addr") ? ETH : AXI) +    , _sid_framer(0) +{ +    _type = uhd::device::USRP; + +    _async_md.reset(new async_md_type(1000/*messages deep*/)); + +    //////////////////////////////////////////////////////////////////// +    // load the fpga image +    //////////////////////////////////////////////////////////////////// +    if (_xport_path == AXI) { +        if (not device_addr.has_key("no_reload_fpga")) { +            // Load FPGA image if provided via args +            if (device_addr.has_key("fpga")) { +                common::load_fpga_image(device_addr["fpga"]); +            // Else load the FPGA image based on the product ID +            } else { +                //extract the FPGA path for the e300 +                const boost::uint16_t pid = boost::lexical_cast<boost::uint16_t>( +                    device_addr["product"]); +                std::string fpga_image; +                switch(e300_eeprom_manager::get_mb_type(pid)) { +                case e300_eeprom_manager::USRP_E310_MB: +                    fpga_image = find_image_path(E310_FPGA_FILE_NAME); +                    break; +                case e300_eeprom_manager::USRP_E300_MB: +                    fpga_image = find_image_path(E300_FPGA_FILE_NAME); +                    break; +                case e300_eeprom_manager::UNKNOWN: +                default: +                    UHD_MSG(warning) << "Unknown motherboard type, loading e300 image." +                                     << std::endl; +                    fpga_image = find_image_path(E300_FPGA_FILE_NAME); +                    break; +                } +                common::load_fpga_image(fpga_image); +            } +        } +    } + +    //////////////////////////////////////////////////////////////////// +    // setup fifo xports +    //////////////////////////////////////////////////////////////////// +    _ctrl_xport_params.recv_frame_size = e300::DEFAULT_CTRL_FRAME_SIZE; +    _ctrl_xport_params.num_recv_frames = e300::DEFAULT_CTRL_NUM_FRAMES; +    _ctrl_xport_params.send_frame_size = e300::DEFAULT_CTRL_FRAME_SIZE; +    _ctrl_xport_params.num_send_frames = e300::DEFAULT_CTRL_NUM_FRAMES; + +    _data_xport_params.recv_frame_size = e300::DEFAULT_RX_DATA_FRAME_SIZE; +    _data_xport_params.num_recv_frames = e300::DEFAULT_RX_DATA_NUM_FRAMES; +    _data_xport_params.send_frame_size = e300::DEFAULT_TX_DATA_FRAME_SIZE; +    _data_xport_params.num_send_frames = e300::DEFAULT_TX_DATA_NUM_FRAMES; + +    // until we figure out why this goes wrong we'll keep this hack around +    if (_xport_path == ETH) { +        _data_xport_params.recv_frame_size = +            std::min(e300::MAX_NET_RX_DATA_FRAME_SIZE, _data_xport_params.recv_frame_size); +        _data_xport_params.send_frame_size = +            std::min(e300::MAX_NET_TX_DATA_FRAME_SIZE, _data_xport_params.send_frame_size); +    } +    udp_zero_copy::buff_params dummy_buff_params_out; + +    if (_xport_path == ETH) { +        zero_copy_if::sptr codec_xport = +            udp_zero_copy::make(device_addr["addr"], E300_SERVER_CODEC_PORT, _ctrl_xport_params, dummy_buff_params_out, device_addr); +        _codec_ctrl = e300_remote_codec_ctrl::make(codec_xport); +        zero_copy_if::sptr gregs_xport = +            udp_zero_copy::make(device_addr["addr"], E300_SERVER_GREGS_PORT, _ctrl_xport_params, dummy_buff_params_out, device_addr); +        _global_regs = global_regs::make(gregs_xport); + +        zero_copy_if::sptr i2c_xport; +        i2c_xport = udp_zero_copy::make(device_addr["addr"], E300_SERVER_I2C_PORT, _ctrl_xport_params, dummy_buff_params_out, device_addr); +        _eeprom_manager = boost::make_shared<e300_eeprom_manager>(i2c::make_zc(i2c_xport)); + +        uhd::transport::zero_copy_xport_params sensor_xport_params; +        sensor_xport_params.recv_frame_size = 128; +        sensor_xport_params.num_recv_frames = 10; +        sensor_xport_params.send_frame_size = 128; +        sensor_xport_params.num_send_frames = 10; + +        zero_copy_if::sptr sensors_xport; +        sensors_xport = udp_zero_copy::make(device_addr["addr"], E300_SERVER_SENSOR_PORT, sensor_xport_params, dummy_buff_params_out, device_addr); +        _sensor_manager = e300_sensor_manager::make_proxy(sensors_xport); + +    } else { +        e300_fifo_config_t fifo_cfg; +        try { +            fifo_cfg = e300_read_sysfs(); +        } catch (uhd::lookup_error &e) { +            throw uhd::runtime_error("Failed to get driver parameters from sysfs."); +        } +        _fifo_iface = e300_fifo_interface::make(fifo_cfg); +        _global_regs = global_regs::make(_fifo_iface->get_global_regs_base()); + +        ad9361_params::sptr client_settings = boost::make_shared<e300_ad9361_client_t>(); +        _codec_ctrl = ad9361_ctrl::make_spi(client_settings, spi::make(E300_SPIDEV_DEVICE), 1); +        // This is horrible ... why do I have to sleep here? +        boost::this_thread::sleep(boost::posix_time::milliseconds(100)); +        _eeprom_manager = boost::make_shared<e300_eeprom_manager>(i2c::make_i2cdev(E300_I2CDEV_DEVICE)); +    } + +    UHD_MSG(status) << "Detecting internal GPSDO.... " << std::flush; +    if (_xport_path == AXI) { +        try { +            _gps = gps::ublox::ubx::control::make("/dev/ttyPS1", 9600); +        } catch (std::exception &e) { +            UHD_MSG(error) << "An error occured making GPSDO control: " << e.what() << std::endl; +        } +        _sensor_manager = e300_sensor_manager::make_local(_gps); +    } +    UHD_MSG(status) << (_sensor_manager->get_gps_found() ? "found" : "not found")  << std::endl; + +    // Verify we can talk to the e300 core control registers ... +    UHD_MSG(status) << "Initializing core control..." << std::endl; +    this->_register_loopback_self_test(_global_regs); + +    // Verify fpga compatibility version matches at least for the major +    if (_get_version(FPGA_MAJOR) != fpga::COMPAT_MAJOR) { +        throw uhd::runtime_error(str(boost::format( +            "Expected FPGA compatibility number %lu.x, but got %lu.%lu:\n" +            "The FPGA build is not compatible with the host code build.\n" +            "%s" +        ) % fpga::COMPAT_MAJOR +          % _get_version(FPGA_MAJOR) % _get_version(FPGA_MINOR) +          % print_images_error())); +    } + +    //////////////////////////////////////////////////////////////////// +    // Initialize the properties tree +    //////////////////////////////////////////////////////////////////// +    _tree = property_tree::make(); +    _tree->create<std::string>("/name").set("E-Series Device"); +    const fs_path mb_path = "/mboards/0"; +    _tree->create<std::string>(mb_path / "name") +        .set(_eeprom_manager->get_mb_type_string()); + +    _tree->create<std::string>(mb_path / "codename").set("Troll"); + +    _tree->create<std::string>(mb_path / "fpga_version").set( +        str(boost::format("%u.%u") +            % _get_version(FPGA_MAJOR) +            % _get_version(FPGA_MINOR))); + +    _tree->create<std::string>(mb_path / "fpga_version_hash").set( +        _get_version_hash()); + +    //////////////////////////////////////////////////////////////////// +    // and do the misc mboard sensors +    //////////////////////////////////////////////////////////////////// +    _tree->create<int>(mb_path / "sensors"); +    BOOST_FOREACH(const std::string &name, _sensor_manager->get_sensors()) +    { +        _tree->create<sensor_value_t>(mb_path / "sensors" / name) +            .publish(boost::bind(&e300_sensor_manager::get_sensor, _sensor_manager, name)); +    } + +    //////////////////////////////////////////////////////////////////// +    // setup the mboard eeprom +    //////////////////////////////////////////////////////////////////// +    _tree->create<mboard_eeprom_t>(mb_path / "eeprom") +        .set(_eeprom_manager->get_mb_eeprom())  // set first... +        .subscribe(boost::bind( +            &e300_eeprom_manager::write_mb_eeprom, +            _eeprom_manager, _1)); + +    //////////////////////////////////////////////////////////////////// +    // clocking +    //////////////////////////////////////////////////////////////////// +    _tree->create<double>(mb_path / "tick_rate") +        .coerce(boost::bind(&e300_impl::_set_tick_rate, this, _1)) +        .publish(boost::bind(&e300_impl::_get_tick_rate, this)) +        .subscribe(boost::bind(&e300_impl::_update_tick_rate, this, _1)); + +    //default some chains on -- needed for setup purposes +    _codec_ctrl->set_active_chains(true, false, true, false); +    _codec_ctrl->set_clock_rate(50e6); + +    //////////////////////////////////////////////////////////////////// +    // setup radios +    //////////////////////////////////////////////////////////////////// +    for(size_t instance = 0; instance < fpga::NUM_RADIOS; instance++) +        this->_setup_radio(instance); + +    _codec_ctrl->data_port_loopback(true); + +    // Radio 0 loopback through AD9361 +    this->_codec_loopback_self_test(_radio_perifs[0].ctrl); +    // Radio 1 loopback through AD9361 +    this->_codec_loopback_self_test(_radio_perifs[1].ctrl); + +    _codec_ctrl->data_port_loopback(false); + +    //////////////////////////////////////////////////////////////////// +    // internal gpios +    //////////////////////////////////////////////////////////////////// +    gpio_core_200::sptr fp_gpio = gpio_core_200::make(_radio_perifs[0].ctrl, TOREG(SR_FP_GPIO), RB32_FP_GPIO); +    const std::vector<std::string> gpio_attrs = boost::assign::list_of("CTRL")("DDR")("OUT")("ATR_0X")("ATR_RX")("ATR_TX")("ATR_XX"); +    BOOST_FOREACH(const std::string &attr, gpio_attrs) +    { +        _tree->create<boost::uint32_t>(mb_path / "gpio" / "INT0" / attr) +            .subscribe(boost::bind(&e300_impl::_set_internal_gpio, this, fp_gpio, attr, _1)) +            .set(0); +    } +    _tree->create<boost::uint8_t>(mb_path / "gpio" / "INT0" / "READBACK") +        .publish(boost::bind(&e300_impl::_get_internal_gpio, this, fp_gpio, "READBACK")); + + +    //////////////////////////////////////////////////////////////////// +    // register the time keepers - only one can be the highlander +    //////////////////////////////////////////////////////////////////// +    _tree->create<time_spec_t>(mb_path / "time" / "now") +        .publish(boost::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64)) +        .subscribe(boost::bind(&time_core_3000::set_time_now, _radio_perifs[0].time64, _1)) +        .subscribe(boost::bind(&time_core_3000::set_time_now, _radio_perifs[1].time64, _1)); +    _tree->create<time_spec_t>(mb_path / "time" / "pps") +        .publish(boost::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64)) +        .subscribe(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[0].time64, _1)) +        .subscribe(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[1].time64, _1)); +    //setup time source props +    _tree->create<std::string>(mb_path / "time_source" / "value") +        .subscribe(boost::bind(&e300_impl::_update_time_source, this, _1)); +    static const std::vector<std::string> time_sources = boost::assign::list_of("none")("external")("gpsdo"); +    _tree->create<std::vector<std::string> >(mb_path / "time_source" / "options").set(time_sources); +    //setup reference source props +    _tree->create<std::string>(mb_path / "clock_source" / "value") +        .subscribe(boost::bind(&e300_impl::_update_clock_source, this, _1)); +    static const std::vector<std::string> clock_sources = boost::assign::list_of("internal"); +    // not implemented ("external")("gpsdo"); +    _tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options").set(clock_sources); + +    //////////////////////////////////////////////////////////////////// +    // dboard eeproms but not really +    //////////////////////////////////////////////////////////////////// +    dboard_eeprom_t db_eeprom; +    _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "rx_eeprom") +        .set(_eeprom_manager->get_db_eeprom()) +        .subscribe(boost::bind( +            &e300_eeprom_manager::write_db_eeprom, +            _eeprom_manager, _1)); + +    _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "tx_eeprom") +        .set(_eeprom_manager->get_db_eeprom()) +        .subscribe(boost::bind( +            &e300_eeprom_manager::write_db_eeprom, +            _eeprom_manager, _1)); + +    _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "gdb_eeprom").set(db_eeprom); + +    //////////////////////////////////////////////////////////////////// +    // create RF frontend interfacing +    //////////////////////////////////////////////////////////////////// +    { +        const fs_path codec_path = mb_path / ("rx_codecs") / "A"; +        _tree->create<std::string>(codec_path / "name").set("E3x0 RX dual ADC"); +        _tree->create<int>(codec_path / "gains"); //empty cuz gains are in frontend +    } +    { +        const fs_path codec_path = mb_path / ("tx_codecs") / "A"; +        _tree->create<std::string>(codec_path / "name").set("E3x0 TX dual DAC"); +        _tree->create<int>(codec_path / "gains"); //empty cuz gains are in frontend +    } + +    //////////////////////////////////////////////////////////////////// +    // create frontend mapping +    //////////////////////////////////////////////////////////////////// + +     std::vector<size_t> default_map(2, 0); +     default_map[0] = 0; // set A->0 +     default_map[1] = 1; // set B->1, even if there's only A + +    _tree->create<std::vector<size_t> >(mb_path / "rx_chan_dsp_mapping").set(default_map); +    _tree->create<std::vector<size_t> >(mb_path / "tx_chan_dsp_mapping").set(default_map); + +    _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") +        .set(subdev_spec_t()) +        .subscribe(boost::bind(&e300_impl::_update_subdev_spec, this, "rx", _1)); +    _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") +        .set(subdev_spec_t()) +        .subscribe(boost::bind(&e300_impl::_update_subdev_spec, this, "tx", _1)); + +    //////////////////////////////////////////////////////////////////// +    // do some post-init tasks +    //////////////////////////////////////////////////////////////////// + +    // init the clock rate to something reasonable +    _tree->access<double>(mb_path / "tick_rate").set( +        device_addr.cast<double>("master_clock_rate", e300::DEFAULT_TICK_RATE)); + +    // subdev spec contains full width of selections +    subdev_spec_t rx_spec, tx_spec; +    BOOST_FOREACH(const std::string &fe, _tree->list(mb_path / "dboards" / "A" / "rx_frontends")) +    { +        rx_spec.push_back(subdev_spec_pair_t("A", fe)); +    } +    BOOST_FOREACH(const std::string &fe, _tree->list(mb_path / "dboards" / "A" / "tx_frontends")) +    { +        tx_spec.push_back(subdev_spec_pair_t("A", fe)); +    } +    _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(rx_spec); +    _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(tx_spec); + +    if (_sensor_manager->get_gps_found()) { +        _tree->access<std::string>(mb_path / "clock_source" / "value").set("gpsdo"); +        _tree->access<std::string>(mb_path / "time_source" / "value").set("gpsdo"); +        UHD_MSG(status) << "References initialized to GPSDO sources" << std::endl; +        const time_t tp = time_t(_sensor_manager->get_gps_time().to_int()); +        _tree->access<time_spec_t>(mb_path / "time" / "pps").set(time_spec_t(tp)); +        //wait for time to be set (timeout after 1 second) +        for (int i = 0; i < 10; i++) +        { +            boost::this_thread::sleep(boost::posix_time::milliseconds(100)); +            if(tp == (_tree->access<time_spec_t>(mb_path / "time" / "pps").get()).get_full_secs()) +                break; +        } +    } else { +        // init to default time and clock source +        _tree->access<std::string>(mb_path / "clock_source" / "value").set( +            e300::DEFAULT_CLOCK_SRC); +        _tree->access<std::string>(mb_path / "time_source" / "value").set( +            e300::DEFAULT_TIME_SRC); + +            UHD_MSG(status) << "References initialized to internal sources" << std::endl; +    } +} + +boost::uint8_t e300_impl::_get_internal_gpio( +    gpio_core_200::sptr gpio, +    const std::string &) +{ +    return boost::uint32_t(gpio->read_gpio(dboard_iface::UNIT_RX)); +} + +void e300_impl::_set_internal_gpio( +    gpio_core_200::sptr gpio, +    const std::string &attr, +    const boost::uint32_t value) +{ +    if (attr == "CTRL") +        return gpio->set_pin_ctrl(dboard_iface::UNIT_RX, value); +    else if (attr == "DDR") +        return gpio->set_gpio_ddr(dboard_iface::UNIT_RX, value); +    else if (attr == "OUT") +        return gpio->set_gpio_out(dboard_iface::UNIT_RX, value); +    else if (attr == "ATR_0X") +        return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, value); +    else if (attr == "ATR_RX") +        return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, value); +    else if (attr == "ATR_TX") +        return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, value); +    else if (attr == "ATR_XX") +        return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, value); +} + +uhd::sensor_value_t e300_impl::_get_fe_pll_lock(const bool is_tx) +{ +    const boost::uint32_t st = +        _global_regs->peek32(global_regs::RB32_CORE_PLL); +    const bool locked = is_tx ? st & 0x1 : st & 0x2; +    return sensor_value_t("LO", locked, "locked", "unlocked"); +} + +e300_impl::~e300_impl(void) +{ +    /* NOP */ +} + +void e300_impl::_enforce_tick_rate_limits( +        const size_t chan_count, +        const double tick_rate, +        const std::string &direction) +{ +    const size_t max_chans = 2; +    if (chan_count > max_chans) { +        throw uhd::value_error(boost::str( +            boost::format("cannot not setup %d %s channels (maximum is %d)") +                % chan_count +                % direction +                % max_chans +        )); +    } else { +        const double max_tick_rate = ad9361_device_t::AD9361_MAX_CLOCK_RATE / ((chan_count <= 1) ? 1 : 2); +        if (tick_rate - max_tick_rate >= 1.0) +        { +            throw uhd::value_error(boost::str( +                boost::format("current master clock rate (%.6f MHz) exceeds maximum possible master clock rate (%.6f MHz) when using %d %s channels") +                    % (tick_rate/1e6) +                    % (max_tick_rate/1e6) +                    % chan_count +                    % direction +            )); +        } +    } +} + +double e300_impl::_set_tick_rate(const double rate) +{ +    UHD_MSG(status) << "Asking for clock rate " << rate/1e6 << " MHz\n"; +    _tick_rate = _codec_ctrl->set_clock_rate(rate); +    UHD_MSG(status) << "Actually got clock rate " << _tick_rate/1e6 << " MHz\n"; + +    BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) +    { +        perif.time64->set_tick_rate(_tick_rate); +        perif.time64->self_test(); +    } +    return _tick_rate; +} + +void e300_impl::_load_fpga_image(const std::string &path) +{ +    if (not fs::exists("/dev/xdevcfg")) +    { +        ::system("mknod /dev/xdevcfg c 259 0"); +        //throw uhd::runtime_error("no xdevcfg, please run: mknod /dev/xdevcfg c 259 0"); +    } + +    UHD_MSG(status) << "Loading FPGA image: " << path << "..." << std::flush; + +    std::ifstream fpga_file(path.c_str(), std::ios_base::binary); +    UHD_ASSERT_THROW(fpga_file.good()); + +    std::FILE *wfile; +    wfile = std::fopen("/dev/xdevcfg", "wb"); +    UHD_ASSERT_THROW(!(wfile == NULL)); + +    char buff[16384]; // devcfg driver can't handle huge writes +    do { +        fpga_file.read(buff, sizeof(buff)); +        std::fwrite(buff, 1, fpga_file.gcount(), wfile); +    } while (fpga_file); + +    fpga_file.close(); +    std::fclose(wfile); + +    UHD_MSG(status) << " done" << std::endl; +} + +void e300_impl::_register_loopback_self_test(wb_iface::sptr iface) +{ +    bool test_fail = false; +    UHD_MSG(status) << "Performing register loopback test... " << std::flush; +    size_t hash = time(NULL); +    for (size_t i = 0; i < 100; i++) +    { +        boost::hash_combine(hash, i); +        iface->poke32(TOREG(SR_TEST), boost::uint32_t(hash)); +        test_fail = iface->peek32(RB32_TEST) != boost::uint32_t(hash); +        if (test_fail) break; //exit loop on any failure +    } +    UHD_MSG(status) << ((test_fail)? " fail" : "pass") << std::endl; +} + +boost::uint32_t e300_impl::_get_version(compat_t which) +{ +    const boost::uint16_t compat_num +        = _global_regs->peek32(global_regs::RB32_CORE_COMPAT); + +    switch(which) { +    case FPGA_MINOR: +        return compat_num & 0xff; +    case FPGA_MAJOR: +        return (compat_num & 0xff00) >> 8; +    default: +        throw uhd::value_error("Requested unknown version."); +    }; +} + +std::string e300_impl::_get_version_hash(void) +{ +    const boost::uint32_t git_hash +        = _global_regs->peek32(global_regs::RB32_CORE_GITHASH); +    return str(boost::format("%7x%s") +        % (git_hash & 0x0FFFFFFF) +        % ((git_hash & 0xF000000) ? "-dirty" : "")); +} + +void e300_impl::_codec_loopback_self_test(wb_iface::sptr iface) +{ +    bool test_fail = false; +    UHD_ASSERT_THROW(bool(iface)); +    UHD_MSG(status) << "Performing CODEC loopback test... " << std::flush; +    size_t hash = time(NULL); +    for (size_t i = 0; i < 100; i++) +    { +        boost::hash_combine(hash, i); +        const boost::uint32_t word32 = boost::uint32_t(hash) & 0xfff0fff0; +        iface->poke32(TOREG(SR_CODEC_IDLE), word32); +        iface->peek64(RB64_CODEC_READBACK); //enough idleness for loopback to propagate +        const boost::uint64_t rb_word64 = iface->peek64(RB64_CODEC_READBACK); +        const boost::uint32_t rb_tx = boost::uint32_t(rb_word64 >> 32); +        const boost::uint32_t rb_rx = boost::uint32_t(rb_word64 & 0xffffffff); +        test_fail = word32 != rb_tx or word32 != rb_rx; +        if (test_fail) break; //exit loop on any failure +    } +    UHD_MSG(status) << ((test_fail)? " fail" : "pass") << std::endl; + +    /* Zero out the idle data. */ +    iface->poke32(TOREG(SR_CODEC_IDLE), 0); +} + +boost::uint32_t e300_impl::_allocate_sid(const sid_config_t &config) +{ +    const boost::uint32_t stream = (config.dst_prefix | (config.router_dst_there << 2)) & 0xff; + +    const boost::uint32_t sid = 0 +        | (E300_DEVICE_HERE << 24) +        | (_sid_framer << 16) +        | (config.router_addr_there << 8) +        | (stream << 0) +    ; +    UHD_LOG << std::hex +        << " sid 0x" << sid +        << " framer 0x" << _sid_framer +        << " stream 0x" << stream +        << " router_dst_there 0x" << int(config.router_dst_there) +        << " router_addr_there 0x" << int(config.router_addr_there) +        << std::dec << std::endl; + +    // Program the E300 to recognize it's own local address. +    _global_regs->poke32(global_regs::SR_CORE_XB_LOCAL, config.router_addr_there); + +    // Program CAM entry for outgoing packets matching a E300 resource (e.g. Radio). +    // This type of packet matches the XB_LOCAL address and is looked up in the upper +    // half of the CAM +    _global_regs->poke32(XB_ADDR(256 + stream), +                         config.router_dst_there); + +    // Program CAM entry for returning packets to us (for example GR host via zynq_fifo) +    // This type of packet does not match the XB_LOCAL address and is looked up in the lower half of the CAM +    _global_regs->poke32(XB_ADDR(E300_DEVICE_HERE), +                         config.router_dst_here); + +    UHD_LOG << std::hex +        << "done router config for sid 0x" << sid +        << std::dec << std::endl; + +    //increment for next setup +    _sid_framer++; + +    return sid; +} + +void e300_impl::_setup_dest_mapping(const boost::uint32_t sid, const size_t which_stream) +{ +    UHD_LOG << boost::format("Setting up dest map for 0x%lx to be stream %d") +                                     % (sid & 0xff) % which_stream << std::endl; +    _global_regs->poke32(DST_ADDR(sid & 0xff), which_stream); +} + +void e300_impl::_update_time_source(const std::string &source) +{ +    UHD_MSG(status) << boost::format("Setting time source to %s") % source << std::endl; +    if (source == "none" or source == "internal") { +        _misc.pps_sel = global_regs::PPS_INT; +    } else if (source == "gpsdo") { +        _misc.pps_sel = global_regs::PPS_GPS; +    } else if (source == "external") { +        _misc.pps_sel = global_regs::PPS_EXT; +    } else { +        throw uhd::key_error("update_time_source: unknown source: " + source); +    } +    _update_gpio_state(); +} + +size_t e300_impl::_get_axi_dma_channel( +    boost::uint8_t destination, +    boost::uint8_t prefix) +{ +    static const boost::uint32_t RADIO_GRP_SIZE = 4; +    static const boost::uint32_t RADIO0_GRP     = 0; +    static const boost::uint32_t RADIO1_GRP     = 1; + +    boost::uint32_t radio_grp = (destination == E300_XB_DST_R0) ? RADIO0_GRP : RADIO1_GRP; +    return ((radio_grp * RADIO_GRP_SIZE) + prefix); +} + +boost::uint16_t e300_impl::_get_udp_port( +        boost::uint8_t destination, +        boost::uint8_t prefix) +{ +    if (destination == E300_XB_DST_R0) { +        if (prefix == E300_RADIO_DEST_PREFIX_CTRL) +            return boost::lexical_cast<boost::uint16_t>(E300_SERVER_CTRL_PORT0); +        else if (prefix == E300_RADIO_DEST_PREFIX_TX) +            return boost::lexical_cast<boost::uint16_t>(E300_SERVER_TX_PORT0); +        else if (prefix == E300_RADIO_DEST_PREFIX_RX) +            return boost::lexical_cast<boost::uint16_t>(E300_SERVER_RX_PORT0); +    } else if (destination == E300_XB_DST_R1) { +        if (prefix == E300_RADIO_DEST_PREFIX_CTRL) +            return boost::lexical_cast<boost::uint16_t>(E300_SERVER_CTRL_PORT1); +        else if (prefix == E300_RADIO_DEST_PREFIX_TX) +            return boost::lexical_cast<boost::uint16_t>(E300_SERVER_TX_PORT1); +        else if (prefix == E300_RADIO_DEST_PREFIX_RX) +            return boost::lexical_cast<boost::uint16_t>(E300_SERVER_RX_PORT1); +    } +    throw uhd::value_error(str(boost::format("No UDP port defined for combination: %u %u") % destination % prefix)); +} + +e300_impl::both_xports_t e300_impl::_make_transport( +    const boost::uint8_t &destination, +    const boost::uint8_t &prefix, +    const uhd::transport::zero_copy_xport_params ¶ms, +    boost::uint32_t &sid) +{ +    both_xports_t xports; + +    sid_config_t config; +    config.router_addr_there    = E300_DEVICE_THERE; +    config.dst_prefix           = prefix; +    config.router_dst_there     = destination; +    config.router_dst_here      = E300_XB_DST_AXI; +    sid = this->_allocate_sid(config); + +    // in local mode +    if (_xport_path == AXI) { +        // lookup which dma channel we need +        // to use to create our transport +        const size_t stream = _get_axi_dma_channel( +            destination, +            prefix); + +        xports.send = +            _fifo_iface->make_send_xport(stream, params); +        xports.recv = +            _fifo_iface->make_recv_xport(stream, params); + +    // in network mode +    } else if (_xport_path == ETH) { +        // lookup which udp port we need +        // to use to create our transport +        const boost::uint16_t port = _get_udp_port( +            destination, +            prefix); + +        udp_zero_copy::buff_params dummy_buff_params_out; +        xports.send = udp_zero_copy::make( +            _device_addr["addr"], +            str(boost::format("%u") % port), params, +            dummy_buff_params_out, +            _device_addr); + +        // use the same xport in both directions +        xports.recv = xports.send; +    } + +    // configure the return path +    _setup_dest_mapping(sid, _get_axi_dma_channel(destination, prefix)); + +    return xports; +} + +void e300_impl::_update_clock_source(const std::string &) +{ +} + +void e300_impl::_update_antenna_sel(const size_t &which, const std::string &ant) +{ +    if (ant != "TX/RX" and ant != "RX2") +        throw uhd::value_error("e300: unknown RX antenna option: " + ant); +    _radio_perifs[which].ant_rx2 = (ant == "RX2"); +    this->_update_atrs(); +} + +void e300_impl::_update_fe_lo_freq(const std::string &fe, const double freq) +{ +    if (fe[0] == 'R') +        _settings.rx_freq = freq; +    if (fe[0] == 'T') +        _settings.tx_freq = freq; +    this->_update_atrs(); +    _update_bandsel(fe, freq); +} + +void e300_impl::_setup_radio(const size_t dspno) +{ +    radio_perifs_t &perif = _radio_perifs[dspno]; +    const fs_path mb_path = "/mboards/0"; + +    //////////////////////////////////////////////////////////////////// +    // crossbar config for ctrl xports +    //////////////////////////////////////////////////////////////////// + +    // make a transport, grab a sid +    boost::uint32_t ctrl_sid; +    both_xports_t ctrl_xports = _make_transport( +       dspno ? E300_XB_DST_R1 : E300_XB_DST_R0, +       E300_RADIO_DEST_PREFIX_CTRL, +       _ctrl_xport_params, +       ctrl_sid); + +    this->_setup_dest_mapping( +        ctrl_sid, +        dspno ? E300_R1_CTRL_STREAM +              : E300_R0_CTRL_STREAM); + +    //////////////////////////////////////////////////////////////////// +    // radio control +    //////////////////////////////////////////////////////////////////// +    perif.ctrl = radio_ctrl_core_3000::make( +        false/*lilE*/, +        ctrl_xports.send, +        ctrl_xports.recv, +        ctrl_sid, +        dspno ? "1" : "0"); +    this->_register_loopback_self_test(perif.ctrl); +    perif.atr = gpio_core_200_32wo::make(perif.ctrl, TOREG(SR_GPIO)); + +    //////////////////////////////////////////////////////////////////// +    // front end corrections +    //////////////////////////////////////////////////////////////////// +    std::string slot_name = (dspno == 0) ? "A" : "B"; +    perif.rx_fe = rx_frontend_core_200::make(perif.ctrl, TOREG(SR_RX_FRONT)); +    const fs_path rx_fe_path = mb_path / "rx_frontends" / slot_name; +    _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") +        .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, perif.rx_fe, _1)) +        .set(std::complex<double>(0.0, 0.0)); +    _tree->create<bool>(rx_fe_path / "dc_offset" / "enable") +        .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, perif.rx_fe, _1)) +        .set(true); +    _tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value") +        .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, perif.rx_fe, _1)) +        .set(std::complex<double>(0.0, 0.0)); + +    perif.tx_fe = tx_frontend_core_200::make(perif.ctrl, TOREG(SR_TX_FRONT)); +    const fs_path tx_fe_path = mb_path / "tx_frontends" / slot_name; +    _tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value") +        .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, perif.tx_fe, _1)) +        .set(std::complex<double>(0.0, 0.0)); +    _tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value") +        .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, perif.tx_fe, _1)) +        .set(std::complex<double>(0.0, 0.0)); + +    //////////////////////////////////////////////////////////////////// +    // create rx dsp control objects +    //////////////////////////////////////////////////////////////////// +    perif.framer = rx_vita_core_3000::make(perif.ctrl, TOREG(SR_RX_CTRL)); +    perif.ddc = rx_dsp_core_3000::make(perif.ctrl, TOREG(SR_RX_DSP)); +    perif.ddc->set_link_rate(10e9/8); //whatever +    _tree->access<double>(mb_path / "tick_rate") +        .subscribe(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1)) +        .subscribe(boost::bind(&rx_dsp_core_3000::set_tick_rate, perif.ddc, _1)); +    const fs_path rx_dsp_path = mb_path / "rx_dsps" / str(boost::format("%u") % dspno); +    _tree->create<meta_range_t>(rx_dsp_path / "rate" / "range") +        .publish(boost::bind(&rx_dsp_core_3000::get_host_rates, perif.ddc)); +    _tree->create<double>(rx_dsp_path / "rate" / "value") +        .coerce(boost::bind(&rx_dsp_core_3000::set_host_rate, perif.ddc, _1)) +        .subscribe(boost::bind(&e300_impl::_update_rx_samp_rate, this, dspno, _1)) +        .set(e300::DEFAULT_RX_SAMP_RATE); +    _tree->create<double>(rx_dsp_path / "freq" / "value") +        .coerce(boost::bind(&rx_dsp_core_3000::set_freq, perif.ddc, _1)) +        .set(e300::DEFAULT_DDC_FREQ); +    _tree->create<meta_range_t>(rx_dsp_path / "freq" / "range") +        .publish(boost::bind(&rx_dsp_core_3000::get_freq_range, perif.ddc)); +    _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") +        .subscribe(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); + +    //////////////////////////////////////////////////////////////////// +    // create tx dsp control objects +    //////////////////////////////////////////////////////////////////// +    perif.deframer = tx_vita_core_3000::make(perif.ctrl, TOREG(SR_TX_CTRL)); +    perif.duc = tx_dsp_core_3000::make(perif.ctrl, TOREG(SR_TX_DSP)); +    perif.duc->set_link_rate(10e9/8); //whatever +    _tree->access<double>(mb_path / "tick_rate") +        .subscribe(boost::bind(&tx_vita_core_3000::set_tick_rate, perif.deframer, _1)) +        .subscribe(boost::bind(&tx_dsp_core_3000::set_tick_rate, perif.duc, _1)); +    const fs_path tx_dsp_path = mb_path / "tx_dsps" / str(boost::format("%u") % dspno); +    _tree->create<meta_range_t>(tx_dsp_path / "rate" / "range") +        .publish(boost::bind(&tx_dsp_core_3000::get_host_rates, perif.duc)); +    _tree->create<double>(tx_dsp_path / "rate" / "value") +        .coerce(boost::bind(&tx_dsp_core_3000::set_host_rate, perif.duc, _1)) +        .subscribe(boost::bind(&e300_impl::_update_tx_samp_rate, this, dspno, _1)) +        .set(e300::DEFAULT_TX_SAMP_RATE); +    _tree->create<double>(tx_dsp_path / "freq" / "value") +        .coerce(boost::bind(&tx_dsp_core_3000::set_freq, perif.duc, _1)) +        .set(e300::DEFAULT_DUC_FREQ); +    _tree->create<meta_range_t>(tx_dsp_path / "freq" / "range") +        .publish(boost::bind(&tx_dsp_core_3000::get_freq_range, perif.duc)); + +    //////////////////////////////////////////////////////////////////// +    // create time control objects +    //////////////////////////////////////////////////////////////////// +    time_core_3000::readback_bases_type time64_rb_bases; +    time64_rb_bases.rb_now = RB64_TIME_NOW; +    time64_rb_bases.rb_pps = RB64_TIME_PPS; +    perif.time64 = time_core_3000::make(perif.ctrl, TOREG(SR_TIME), time64_rb_bases); + +    //////////////////////////////////////////////////////////////////// +    // create RF frontend interfacing +    //////////////////////////////////////////////////////////////////// +    static const std::vector<std::string> data_directions = boost::assign::list_of("rx")("tx"); +    BOOST_FOREACH(const std::string& direction, data_directions) +    { +        const std::string key = boost::to_upper_copy(direction) + std::string(((dspno == FE0)? "1" : "2")); +        const fs_path rf_fe_path +            = mb_path / "dboards" / "A" / (direction + "_frontends") / ((dspno == 0) ? "A" : "B"); + +        _tree->create<std::string>(rf_fe_path / "name").set("FE-"+key); +        _tree->create<int>(rf_fe_path / "sensors"); //empty TODO +        _tree->create<sensor_value_t>(rf_fe_path / "sensors" / "lo_locked") +            .publish(boost::bind(&e300_impl::_get_fe_pll_lock, this, direction == "tx")); +        BOOST_FOREACH(const std::string &name, ad9361_ctrl::get_gain_names(key)) +        { +            _tree->create<meta_range_t>(rf_fe_path / "gains" / name / "range") +                .set(ad9361_ctrl::get_gain_range(key)); + +            _tree->create<double>(rf_fe_path / "gains" / name / "value") +                .coerce(boost::bind(&ad9361_ctrl::set_gain, _codec_ctrl, key, _1)) +                .set(e300::DEFAULT_FE_GAIN); +        } +        _tree->create<std::string>(rf_fe_path / "connection").set("IQ"); +        _tree->create<bool>(rf_fe_path / "enabled").set(true); +        _tree->create<bool>(rf_fe_path / "use_lo_offset").set(false); +        _tree->create<double>(rf_fe_path / "bandwidth" / "value") +            .coerce(boost::bind(&ad9361_ctrl::set_bw_filter, _codec_ctrl, key, _1)) +            .set(e300::DEFAULT_FE_BW); +        _tree->create<meta_range_t>(rf_fe_path / "bandwidth" / "range") +            .publish(boost::bind(&ad9361_ctrl::get_bw_filter_range, key)); +        _tree->create<double>(rf_fe_path / "freq" / "value") +            .coerce(boost::bind(&ad9361_ctrl::tune, _codec_ctrl, key, _1)) +            .subscribe(boost::bind(&e300_impl::_update_fe_lo_freq, this, key, _1)) +            .set(e300::DEFAULT_FE_FREQ); +        _tree->create<meta_range_t>(rf_fe_path / "freq" / "range") +            .publish(boost::bind(&ad9361_ctrl::get_rf_freq_range)); + +        //setup antenna stuff +        if (key[0] == 'R') { +            static const std::vector<std::string> ants = boost::assign::list_of("TX/RX")("RX2"); +            _tree->create<std::vector<std::string> >(rf_fe_path / "antenna" / "options").set(ants); +            _tree->create<std::string>(rf_fe_path / "antenna" / "value") +                .subscribe(boost::bind(&e300_impl::_update_antenna_sel, this, dspno, _1)) +                .set("RX2"); + +        } +        if (key[0] == 'T') { +            static const std::vector<std::string> ants(1, "TX/RX"); +            _tree->create<std::vector<std::string> >(rf_fe_path / "antenna" / "options").set(ants); +            _tree->create<std::string>(rf_fe_path / "antenna" / "value").set("TX/RX"); +        } +    } +} + +void e300_impl::_update_enables(void) +{ +    //extract settings from state variables +    const bool enb_tx1 = bool(_radio_perifs[FE0].tx_streamer.lock()); +    const bool enb_rx1 = bool(_radio_perifs[FE0].rx_streamer.lock()); +    const bool enb_tx2 = bool(_radio_perifs[FE1].tx_streamer.lock()); +    const bool enb_rx2 = bool(_radio_perifs[FE1].rx_streamer.lock()); +    const size_t num_rx = (enb_rx1 ? 1 : 0) + (enb_rx2 ? 1:0); +    const size_t num_tx = (enb_tx1 ? 1 : 0) + (enb_tx2 ? 1:0); +    const bool mimo = num_rx == 2 or num_tx == 2; + +    //setup the active chains in the codec +    _codec_ctrl->set_active_chains(enb_tx1, enb_tx2, enb_rx1, enb_rx2); +    if ((num_rx + num_tx) == 0) +        _codec_ctrl->set_active_chains( +            true, false, true, false); // enable something + +    //set_active_chains could cause a clock rate change - reset dcm +    _reset_codec_mmcm(); + +    //figure out if mimo is enabled based on new state +    _misc.mimo = (mimo)? 1 : 0; +    _update_gpio_state(); + +    //atrs change based on enables +    _update_atrs(); +} + +void e300_impl::_update_gpio_state(void) +{ +    boost::uint32_t misc_reg = 0 +        | (_misc.pps_sel      << gpio_t::PPS_SEL) +        | (_misc.mimo         << gpio_t::MIMO) +        | (_misc.codec_arst   << gpio_t::CODEC_ARST) +        | (_misc.tx_bandsels  << gpio_t::TX_BANDSEL) +        | (_misc.rx_bandsel_a << gpio_t::RX_BANDSELA) +        | (_misc.rx_bandsel_b << gpio_t::RX_BANDSELB) +        | (_misc.rx_bandsel_c << gpio_t::RX_BANDSELC); +    _global_regs->poke32(global_regs::SR_CORE_MISC, misc_reg); +} + +void e300_impl::_reset_codec_mmcm(void) +{ +    _misc.codec_arst = 1; +    _update_gpio_state(); +    boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +    _misc.codec_arst = 0; +    _update_gpio_state(); +} + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +//////////////// ATR SETUP FOR FRONTEND CONTROL VIA GPIO /////////////// +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// + +void e300_impl::_update_bandsel(const std::string& which, double freq) +{ +    if(which[0] == 'R') { +        if (freq < 450e6) { +            _misc.rx_bandsel_a  = 44; // 4 | (5 << 3) +            _misc.rx_bandsel_b  = 0;  // 0 | (0 << 2) +            _misc.rx_bandsel_c  = 6;  // 2 | (1 << 2) +        } else if (freq < 700e6) { +            _misc.rx_bandsel_a  = 26; // 2 | (3 << 3) +            _misc.rx_bandsel_b  = 0;  // 0 | (0 << 2) +            _misc.rx_bandsel_c  = 15; // 3 | (3 << 2) +        } else if (freq < 1200e6) { +            _misc.rx_bandsel_a  = 8; // 0 | (1 << 3) +            _misc.rx_bandsel_b  = 0; // 0 | (0 << 2) +            _misc.rx_bandsel_c  = 9; // 1 | (2 << 2) +        } else if (freq < 1800e6) { +            _misc.rx_bandsel_a  = 1; // 1 | (0 << 3) +            _misc.rx_bandsel_b  = 6; // 2 | (1 << 2) +            _misc.rx_bandsel_c  = 0; // 0 | (0 << 2) +        } else if (freq < 2350e6){ +            _misc.rx_bandsel_a  = 19; // 3 | (2 << 3) +            _misc.rx_bandsel_b  = 15; // 3 | (3 << 2) +            _misc.rx_bandsel_c  = 0;  // 0 | (0 << 2) +        } else if (freq < 2600e6){ +            _misc.rx_bandsel_a  = 37; // 5 | (4 << 3) +            _misc.rx_bandsel_b  = 9;  // 1 | (2 << 2) +            _misc.rx_bandsel_c  = 0;  // 0 | (0 << 2) +        } else { +            _misc.rx_bandsel_a  = 0; +            _misc.rx_bandsel_b  = 0; +            _misc.rx_bandsel_c  = 0; +        } +        _update_gpio_state(); +    } else if(which[0] == 'T') { +        if (freq < 117.7e6) +            _misc.tx_bandsels = 7; +        else if (freq < 178.2e6) +            _misc.tx_bandsels = 6; +        else if (freq < 284.3e6) +            _misc.tx_bandsels = 5; +        else if (freq < 453.7e6) +            _misc.tx_bandsels = 4; +        else if (freq < 723.8e6) +            _misc.tx_bandsels = 3; +        else if (freq < 1154.9e6) +            _misc.tx_bandsels = 2; +        else if (freq < 1842.6e6) +            _misc.tx_bandsels = 1; +        else if (freq < 2940.0e6) +            _misc.tx_bandsels = 0; +        else +            _misc.tx_bandsels = 7; +        _update_gpio_state(); +    } else { +        UHD_THROW_INVALID_CODE_PATH(); +    } +} + + +void e300_impl::_update_atrs(void) +{ +    for (size_t instance = 0; instance < fpga::NUM_RADIOS; instance++) +    { +        // if we're not ready, no point ... +        if (not _radio_perifs[instance].atr) +            return; + +        radio_perifs_t &perif = _radio_perifs[instance]; +        const bool enb_rx = bool(perif.rx_streamer.lock()); +        const bool enb_tx = bool(perif.tx_streamer.lock()); +        const bool rx_ant_rx2  = perif.ant_rx2; + +        const bool rx_low_band = _settings.rx_freq < 2.6e9; +        const bool tx_low_band = _settings.tx_freq < 2940.0e6; + +        // VCRX +        int vcrx_v1_rxing = 1; +        int vcrx_v2_rxing = 0; +        int vcrx_v1_txing = 1; +        int vcrx_v2_txing = 0; + +        if (rx_low_band) { +            vcrx_v1_rxing = rx_ant_rx2 ? 0 : 1; +            vcrx_v2_rxing = rx_ant_rx2 ? 1 : 0; +            vcrx_v1_txing = 0; +            vcrx_v2_txing = 1; +        } else { +            vcrx_v1_rxing = rx_ant_rx2 ? 1 : 0; +            vcrx_v2_rxing = rx_ant_rx2 ? 0 : 1; +            vcrx_v1_txing = 1; +            vcrx_v2_txing = 0; +        } + +        // VCTX +        int vctxrx_v1_rxing = 0; +        int vctxrx_v2_rxing = 1; +        int vctxrx_v1_txing = 0; +        int vctxrx_v2_txing = 1; + +        if (tx_low_band) { +            vctxrx_v1_rxing = rx_ant_rx2 ? 0 : 0; +            vctxrx_v2_rxing = rx_ant_rx2 ? 0 : 1; +            vctxrx_v1_txing = 1; +            vctxrx_v2_txing = 0; +        } else { +            vctxrx_v1_rxing = rx_ant_rx2 ? 0 : 0; +            vctxrx_v2_rxing = rx_ant_rx2 ? 0 : 1; +            vctxrx_v1_txing = 1; +            vctxrx_v2_txing = 1; +        } +        //swapped for routing reasons, reswap it here +        if (instance == 1) { +            std::swap(vctxrx_v1_rxing, vctxrx_v2_rxing); +            std::swap(vctxrx_v1_txing, vctxrx_v2_txing); +        } + +        int tx_enable_a = (!tx_low_band and enb_tx) ? 1 : 0; +        int tx_enable_b = (tx_low_band and  enb_tx) ? 1 : 0; + +        //----------------- LEDS ----------------------------// +        const int led_rx2  = rx_ant_rx2  ? 1 : 0; +        const int led_txrx = !rx_ant_rx2 ? 1 : 0; +        const int led_tx   = 1; + +        const int rx_leds = (led_rx2 << LED_RX_RX) | (led_txrx << LED_TXRX_RX); +        const int tx_leds = (led_tx << LED_TXRX_TX); +        const int xx_leds = tx_leds | (1 << LED_RX_RX); //forced to rx2 + +        const int rx_selects = 0 +            | (vcrx_v1_rxing << VCRX_V1) +            | (vcrx_v2_rxing << VCRX_V2) +            | (vctxrx_v1_rxing << VCTXRX_V1) +            | (vctxrx_v2_rxing << VCTXRX_V2) +        ; +        const int tx_selects = 0 +            | (vcrx_v1_txing << VCRX_V1) +            | (vcrx_v2_txing << VCRX_V2) +            | (vctxrx_v1_txing << VCTXRX_V1) +            | (vctxrx_v2_txing << VCTXRX_V2) +        ; +        const int tx_enables = 0 +            | (tx_enable_a << TX_ENABLEA) +            | (tx_enable_b << TX_ENABLEB) +        ; + +        //default selects +        int oo_reg = rx_selects; +        int rx_reg = rx_selects; +        int tx_reg = tx_selects; +        int fd_reg = tx_selects; //tx selects dominate in fd mode + +        //add in leds and tx enables based on fe enable +        if (enb_rx) +            rx_reg |= rx_leds; +        if (enb_rx) +            fd_reg |= xx_leds; +        if (enb_tx) +            tx_reg |= tx_enables | tx_leds; +        if (enb_tx) +            fd_reg |= tx_enables | xx_leds; + +        gpio_core_200_32wo::sptr atr = _radio_perifs[instance].atr; +        atr->set_atr_reg(dboard_iface::ATR_REG_IDLE, oo_reg); +        atr->set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, rx_reg); +        atr->set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, tx_reg); +        atr->set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, fd_reg); +    } +} + +}}} // namespace + +UHD_STATIC_BLOCK(register_e300_device) +{ +    device::register_device(&uhd::usrp::e300::e300_find, &uhd::usrp::e300::e300_make, uhd::device::USRP); +} diff --git a/host/lib/usrp/e300/e300_impl.hpp b/host/lib/usrp/e300/e300_impl.hpp new file mode 100644 index 000000000..9cfd80afd --- /dev/null +++ b/host/lib/usrp/e300/e300_impl.hpp @@ -0,0 +1,296 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_E300_IMPL_HPP +#define INCLUDED_E300_IMPL_HPP + +#include <uhd/device.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/types/serial.hpp> +#include <uhd/types/sensors.hpp> +#include <boost/weak_ptr.hpp> +#include <boost/thread/mutex.hpp> +#include "e300_fifo_config.hpp" +#include "radio_ctrl_core_3000.hpp" +#include "rx_frontend_core_200.hpp" +#include "tx_frontend_core_200.hpp" +#include "rx_vita_core_3000.hpp" +#include "tx_vita_core_3000.hpp" +#include "time_core_3000.hpp" +#include "rx_dsp_core_3000.hpp" +#include "tx_dsp_core_3000.hpp" +#include "ad9361_ctrl.hpp" +#include "gpio_core_200.hpp" + +#include "e300_global_regs.hpp" +#include "e300_i2c.hpp" +#include "e300_eeprom_manager.hpp" +#include "e300_sensor_manager.hpp" +#include "e300_ublox_control.hpp" + +namespace uhd { namespace usrp { namespace e300 { + +static const std::string E300_FPGA_FILE_NAME = "usrp_e300_fpga.bit"; +static const std::string E310_FPGA_FILE_NAME = "usrp_e310_fpga.bit"; + +static const std::string E300_TEMP_SYSFS = "iio:device0"; +static const std::string E300_SPIDEV_DEVICE  = "/dev/spidev0.1"; +static const std::string E300_I2CDEV_DEVICE  = "/dev/i2c-0"; + +static std::string E300_SERVER_RX_PORT0    = "21756"; +static std::string E300_SERVER_TX_PORT0    = "21757"; +static std::string E300_SERVER_CTRL_PORT0  = "21758"; + +static std::string E300_SERVER_RX_PORT1    = "21856"; +static std::string E300_SERVER_TX_PORT1    = "21857"; +static std::string E300_SERVER_CTRL_PORT1  = "21858"; + + +static std::string E300_SERVER_CODEC_PORT  = "21759"; +static std::string E300_SERVER_GREGS_PORT  = "21760"; +static std::string E300_SERVER_I2C_PORT    = "21761"; +static std::string E300_SERVER_SENSOR_PORT = "21762"; + +static const double E300_RX_SW_BUFF_FULLNESS = 0.9;        //Buffer should be half full + +// crossbar settings +static const boost::uint8_t E300_RADIO_DEST_PREFIX_TX   = 0; +static const boost::uint8_t E300_RADIO_DEST_PREFIX_CTRL = 1; +static const boost::uint8_t E300_RADIO_DEST_PREFIX_RX   = 2; + +static const boost::uint8_t E300_XB_DST_AXI = 0; +static const boost::uint8_t E300_XB_DST_R0  = 1; +static const boost::uint8_t E300_XB_DST_R1  = 2; +static const boost::uint8_t E300_XB_DST_CE0 = 3; +static const boost::uint8_t E300_XB_DST_CE1 = 4; + +static const boost::uint8_t E300_DEVICE_THERE = 2; +static const boost::uint8_t E300_DEVICE_HERE  = 0; + +static const size_t E300_R0_CTRL_STREAM    = (0 << 2) | E300_RADIO_DEST_PREFIX_CTRL; +static const size_t E300_R0_TX_DATA_STREAM = (0 << 2) | E300_RADIO_DEST_PREFIX_TX; +static const size_t E300_R0_RX_DATA_STREAM = (0 << 2) | E300_RADIO_DEST_PREFIX_RX; + +static const size_t E300_R1_CTRL_STREAM    = (1 << 2) | E300_RADIO_DEST_PREFIX_CTRL; +static const size_t E300_R1_TX_DATA_STREAM = (1 << 2) | E300_RADIO_DEST_PREFIX_TX; +static const size_t E300_R1_RX_DATA_STREAM = (1 << 2) | E300_RADIO_DEST_PREFIX_RX; + + +/*! + * USRP-E300 implementation guts: + * The implementation details are encapsulated here. + * Handles properties on the mboard, dboard, dsps... + */ +class e300_impl : public uhd::device +{ +public: +    //structors +    e300_impl(const uhd::device_addr_t &); +    virtual ~e300_impl(void); + +    //the io interface +    boost::mutex _stream_spawn_mutex; +    uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &); +    uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &); + +    typedef uhd::transport::bounded_buffer<uhd::async_metadata_t> async_md_type; +    boost::shared_ptr<async_md_type> _async_md; + +    bool recv_async_msg(uhd::async_metadata_t &, double); + +private: // types +    // sid convenience struct +    struct sid_config_t +    { +        boost::uint8_t router_addr_there; +        boost::uint8_t dst_prefix; //2bits +        boost::uint8_t router_dst_there; +        boost::uint8_t router_dst_here; +    }; + +    // perifs in the radio core +    struct radio_perifs_t +    { +        radio_ctrl_core_3000::sptr ctrl; +        gpio_core_200_32wo::sptr atr; +        time_core_3000::sptr time64; +        rx_vita_core_3000::sptr framer; +        rx_dsp_core_3000::sptr ddc; +        tx_vita_core_3000::sptr deframer; +        tx_dsp_core_3000::sptr duc; +        rx_frontend_core_200::sptr rx_fe; +        tx_frontend_core_200::sptr tx_fe; + +        boost::weak_ptr<uhd::rx_streamer> rx_streamer; +        boost::weak_ptr<uhd::tx_streamer> tx_streamer; + +        bool ant_rx2; +    }; + +    //frontend cache so we can update gpios +    struct fe_control_settings_t +    { +        fe_control_settings_t(void) +        { +            rx_freq = 1e9; +            tx_freq = 1e9; +        } +        double rx_freq; +        double tx_freq; +    }; + +    // convenience struct +    struct both_xports_t +    { +        uhd::transport::zero_copy_if::sptr recv; +        uhd::transport::zero_copy_if::sptr send; +    }; + +    enum xport_t {AXI, ETH}; + +    enum compat_t {FPGA_MAJOR, FPGA_MINOR}; + +    struct gpio_t +    { +        gpio_t() : pps_sel(global_regs::PPS_INT), +            mimo(0), codec_arst(0), tx_bandsels(0), +            rx_bandsel_a(0), rx_bandsel_b(0), rx_bandsel_c(0) +        {} + +        boost::uint32_t pps_sel; +        boost::uint32_t mimo; +        boost::uint32_t codec_arst; + +        boost::uint32_t tx_bandsels; +        boost::uint32_t rx_bandsel_a; +        boost::uint32_t rx_bandsel_b; +        boost::uint32_t rx_bandsel_c; + +        static const size_t PPS_SEL     = 0; +        static const size_t MIMO        = 2; +        static const size_t CODEC_ARST  = 3; +        static const size_t TX_BANDSEL  = 4; +        static const size_t RX_BANDSELA = 7; +        static const size_t RX_BANDSELB = 13; +        static const size_t RX_BANDSELC = 17; +    }; + +private: // methods +    void _load_fpga_image(const std::string &path); + +    void _register_loopback_self_test(uhd::wb_iface::sptr iface); + +    boost::uint32_t _get_version(compat_t which); +    std::string _get_version_hash(void); + +    void _setup_radio(const size_t which_radio); + +    boost::uint32_t _allocate_sid(const sid_config_t &config); + +    void _setup_dest_mapping( +        const boost::uint32_t sid, +        const size_t which_stream); + +    size_t _get_axi_dma_channel( +        boost::uint8_t destination, +        boost::uint8_t prefix); + +    boost::uint16_t _get_udp_port( +        boost::uint8_t destination, +        boost::uint8_t prefix); + +    both_xports_t _make_transport( +        const boost::uint8_t &destination, +        const boost::uint8_t &prefix, +        const uhd::transport::zero_copy_xport_params ¶ms, +        boost::uint32_t &sid); + +    double _get_tick_rate(void){return _tick_rate;} +    double _set_tick_rate(const double rate); + +    void _update_gpio_state(void); +    void _update_enables(void); +    void _reset_codec_mmcm(void); +    void _update_bandsel(const std::string& which, double freq); + +    void _check_tick_rate_with_current_streamers(const double rate); +    void _enforce_tick_rate_limits( +        const size_t change, +        const double tick_rate, +        const std::string &direction); + +    void _update_tick_rate(const double); +    void _update_rx_samp_rate(const size_t, const double); +    void _update_tx_samp_rate(const size_t, const double); + +    void _update_time_source(const std::string &source); +    void _update_clock_source(const std::string &); + +    void _update_subdev_spec( +        const std::string &txrx, +        const uhd::usrp::subdev_spec_t &spec); + +    void _codec_loopback_self_test(uhd::wb_iface::sptr iface); + +    void _update_atrs(void); +    void _update_antenna_sel(const size_t &fe, const std::string &ant); +    void _update_fe_lo_freq(const std::string &fe, const double freq); + +    // overflow handling is special for MIMO case +    void _handle_overflow( +        radio_perifs_t &perif, +        boost::weak_ptr<uhd::rx_streamer> streamer); + + +    // get frontend lock sensor +    uhd::sensor_value_t _get_fe_pll_lock(const bool is_tx); + +    // internal gpios +    boost::uint8_t _get_internal_gpio( +        gpio_core_200::sptr, +        const std::string &); + +    void _set_internal_gpio( +        gpio_core_200::sptr gpio, +        const std::string &attr, +        const boost::uint32_t value); + +private: // members +    uhd::device_addr_t                     _device_addr; +    xport_t                                _xport_path; +    e300_fifo_interface::sptr              _fifo_iface; +    size_t                                 _sid_framer; +    radio_perifs_t                         _radio_perifs[2]; +    double                                 _tick_rate; +    ad9361_ctrl::sptr                      _codec_ctrl; +    fe_control_settings_t                  _settings; +    global_regs::sptr                      _global_regs; +    e300_sensor_manager::sptr              _sensor_manager; +    e300_eeprom_manager::sptr              _eeprom_manager; +    uhd::transport::zero_copy_xport_params _data_xport_params; +    uhd::transport::zero_copy_xport_params _ctrl_xport_params; +    gpio_t                                 _misc; +    gps::ublox::ubx::control::sptr _gps; +}; + +}}} // namespace + +#endif /* INCLUDED_E300_IMPL_HPP */ diff --git a/host/lib/usrp/e300/e300_io_impl.cpp b/host/lib/usrp/e300/e300_io_impl.cpp new file mode 100644 index 000000000..dcb6f2afe --- /dev/null +++ b/host/lib/usrp/e300/e300_io_impl.cpp @@ -0,0 +1,599 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "e300_regs.hpp" +#include "e300_impl.hpp" +#include "e300_fpga_defs.hpp" +#include "validate_subdev_spec.hpp" +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include "async_packet_handler.hpp" +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/bind.hpp> +#include <uhd/utils/tasks.hpp> +#include <boost/foreach.hpp> +#include <boost/make_shared.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +namespace uhd { namespace usrp { namespace e300 { + +static const boost::uint32_t HW_SEQ_NUM_MASK = 0xfff; + +/*********************************************************************** + * update streamer rates + **********************************************************************/ +void e300_impl::_check_tick_rate_with_current_streamers(const double rate) +{ +    size_t max_tx_chan_count = 0, max_rx_chan_count = 0; +    BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) +    { +        { +            boost::shared_ptr<sph::recv_packet_streamer> rx_streamer = +                boost::dynamic_pointer_cast<sph::recv_packet_streamer>( +                    perif.rx_streamer.lock()); +            if (rx_streamer) +                max_rx_chan_count = std::max( +                    max_rx_chan_count, +                    rx_streamer->get_num_channels()); +        } + +        { +            boost::shared_ptr<sph::send_packet_streamer> tx_streamer = +                boost::dynamic_pointer_cast<sph::send_packet_streamer>( +                    perif.tx_streamer.lock()); +            if (tx_streamer) +                max_tx_chan_count = std::max( +                    max_tx_chan_count, +                    tx_streamer->get_num_channels()); +        } +    } +    _enforce_tick_rate_limits(max_rx_chan_count, rate, "RX"); +    _enforce_tick_rate_limits(max_tx_chan_count, rate, "TX"); +} + +void e300_impl::_update_tick_rate(const double rate) +{ +    _check_tick_rate_with_current_streamers(rate); + +    BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) +    { +        boost::shared_ptr<sph::recv_packet_streamer> my_streamer = +            boost::dynamic_pointer_cast<sph::recv_packet_streamer>(perif.rx_streamer.lock()); +        if (my_streamer) +            my_streamer->set_tick_rate(rate); +        perif.framer->set_tick_rate(_tick_rate); +    } +    BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) +    { +        boost::shared_ptr<sph::send_packet_streamer> my_streamer = +            boost::dynamic_pointer_cast<sph::send_packet_streamer>(perif.tx_streamer.lock()); +        if (my_streamer) +            my_streamer->set_tick_rate(rate); +        perif.deframer->set_tick_rate(_tick_rate); +    } +} + +void e300_impl::_update_rx_samp_rate(const size_t dspno, const double rate) +{ +    boost::shared_ptr<sph::recv_packet_streamer> my_streamer = +        boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_radio_perifs[dspno].rx_streamer.lock()); +    if (my_streamer) +        my_streamer->set_samp_rate(rate); +} + +void e300_impl::_update_tx_samp_rate(const size_t dspno, const double rate) +{ +    boost::shared_ptr<sph::send_packet_streamer> my_streamer = +        boost::dynamic_pointer_cast<sph::send_packet_streamer>(_radio_perifs[dspno].tx_streamer.lock()); +    if (my_streamer) +        my_streamer->set_samp_rate(rate); +} + +/*********************************************************************** + * frontend selection + **********************************************************************/ +void e300_impl::_update_subdev_spec( +        const std::string &txrx, +        const uhd::usrp::subdev_spec_t &spec) +{ +    //sanity checking +    if (spec.size()) +        validate_subdev_spec(_tree, spec, "rx"); + +    UHD_ASSERT_THROW(spec.size() <= fpga::NUM_RADIOS); + +    if (spec.size() >= 1) +    { +        UHD_ASSERT_THROW(spec[0].db_name == "A"); +        UHD_ASSERT_THROW(spec[0].sd_name == "A" or spec[0].sd_name == "B"); +    } +    if (spec.size() == 2) +    { +        UHD_ASSERT_THROW(spec[1].db_name == "A"); +        UHD_ASSERT_THROW( +            (spec[0].sd_name == "A" and spec[1].sd_name == "B") or +            (spec[0].sd_name == "B" and spec[1].sd_name == "A") +        ); +    } + +    std::vector<size_t> chan_to_dsp_map(spec.size(), 0); +    for (size_t i = 0; i < spec.size(); i++) +        chan_to_dsp_map[i] = (spec[i].sd_name == "A") ? 0 : 1; +    _tree->access<std::vector<size_t> >("/mboards/0" / (txrx + "_chan_dsp_mapping")).set(chan_to_dsp_map); + +    const fs_path mb_path = "/mboards/0"; + +    if (txrx == "tx") { +        for (size_t i = 0; i < spec.size(); i++) +        { +            const std::string conn = _tree->access<std::string>( +                mb_path / "dboards" / spec[i].db_name / +                ("tx_frontends") / spec[i].sd_name / "connection").get(); +            _radio_perifs[i].tx_fe->set_mux(conn); +        } + +    } else { +        for (size_t i = 0; i < spec.size(); i++) +        { +            const std::string conn = _tree->access<std::string>( +                mb_path / "dboards" / spec[i].db_name / +                ("rx_frontends") / spec[i].sd_name / "connection").get(); + +            const bool fe_swapped = (conn == "QI" or conn == "Q"); +            _radio_perifs[i].ddc->set_mux(conn, fe_swapped); +            _radio_perifs[i].rx_fe->set_mux(fe_swapped); +        } +    } + +    this->_update_enables(); +} + +/*********************************************************************** + * VITA stuff + **********************************************************************/ +static void e300_if_hdr_unpack_le( +    const boost::uint32_t *packet_buff, +    vrt::if_packet_info_t &if_packet_info +){ +    if_packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; +    return vrt::if_hdr_unpack_le(packet_buff, if_packet_info); +} + +static void e300_if_hdr_pack_le( +    boost::uint32_t *packet_buff, +    vrt::if_packet_info_t &if_packet_info +){ +    if_packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; +    return vrt::if_hdr_pack_le(packet_buff, if_packet_info); +} + +/*********************************************************************** + * RX flow control handler + **********************************************************************/ +struct e300_rx_fc_cache_t +{ +    e300_rx_fc_cache_t(): +        last_seq_in(0){} +    size_t last_seq_in; +}; + +void e300_impl::_handle_overflow( +    radio_perifs_t &perif, +    boost::weak_ptr<uhd::rx_streamer> streamer) +{ +    boost::shared_ptr<sph::recv_packet_streamer> my_streamer = +            boost::dynamic_pointer_cast<sph::recv_packet_streamer>(streamer.lock()); + +    //If the rx_streamer has expired then overflow handling makes no sense. +    if (not my_streamer) +        return; + +    if (my_streamer->get_num_channels() == 1) { +        perif.framer->handle_overflow(); +        return; +    } + +    // MIMO overflow recovery time +    // find out if we were in continuous mode before stopping +    const bool in_continuous_streaming_mode = perif.framer->in_continuous_streaming_mode(); +    // stop streaming +    my_streamer->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); +    // flush transports +    my_streamer->flush_all(0.001); +    // restart streaming +    if (in_continuous_streaming_mode) { +        stream_cmd_t stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); +        stream_cmd.stream_now = false; +        stream_cmd.time_spec = perif.time64->get_time_now() + time_spec_t(0.01); +        my_streamer->issue_stream_cmd(stream_cmd); +    } +} + + +static void handle_rx_flowctrl( +    const boost::uint32_t sid, +    zero_copy_if::sptr xport, +    boost::shared_ptr<e300_rx_fc_cache_t> fc_cache, +    const size_t last_seq) +{ +    static const size_t RXFC_PACKET_LEN_IN_WORDS    = 2; +    static const size_t RXFC_CMD_CODE_OFFSET        = 0; +    static const size_t RXFC_SEQ_NUM_OFFSET         = 1; + +    managed_send_buffer::sptr buff = xport->get_send_buff(1.0); +    if (not buff) +    { +        throw uhd::runtime_error("handle_rx_flowctrl timed out getting a send buffer"); +    } +    boost::uint32_t *pkt = buff->cast<boost::uint32_t *>(); + +    //recover seq32 +    size_t& seq_sw = fc_cache->last_seq_in; +    const size_t seq_hw = seq_sw & HW_SEQ_NUM_MASK; +    if (last_seq < seq_hw) +        seq_sw += (HW_SEQ_NUM_MASK + 1); +    seq_sw &= ~HW_SEQ_NUM_MASK; +    seq_sw |= last_seq; + +    //load packet info +    vrt::if_packet_info_t packet_info; +    packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_CONTEXT; +    packet_info.num_payload_words32 = RXFC_PACKET_LEN_IN_WORDS; +    packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); +    packet_info.packet_count = seq_sw; +    packet_info.sob = false; +    packet_info.eob = false; +    packet_info.sid = sid; +    packet_info.has_sid = true; +    packet_info.has_cid = false; +    packet_info.has_tsi = false; +    packet_info.has_tsf = false; +    packet_info.has_tlr = false; + +    //load header +    e300_if_hdr_pack_le(pkt, packet_info); + +    //load payload +    pkt[packet_info.num_header_words32+RXFC_CMD_CODE_OFFSET] = uhd::htowx<boost::uint32_t>(0); +    pkt[packet_info.num_header_words32+RXFC_SEQ_NUM_OFFSET] = uhd::htowx<boost::uint32_t>(seq_sw); + +    //send the buffer over the interface +    buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32)); +} + + +/*********************************************************************** + * TX flow control handler + **********************************************************************/ +struct e300_tx_fc_cache_t +{ +    e300_tx_fc_cache_t(void): +        stream_channel(0), +        device_channel(0), +        last_seq_out(0), +        last_seq_ack(0), +        seq_queue(1){} +    size_t stream_channel; +    size_t device_channel; +    size_t last_seq_out; +    size_t last_seq_ack; +    bounded_buffer<size_t> seq_queue; +    boost::shared_ptr<e300_impl::async_md_type> async_queue; +    boost::shared_ptr<e300_impl::async_md_type> old_async_queue; +}; + +#define E300_ASYNC_EVENT_CODE_FLOW_CTRL 0 + +typedef boost::function<double(void)> tick_rate_retriever_t; + + +static void handle_tx_async_msgs(boost::shared_ptr<e300_tx_fc_cache_t> fc_cache, +                                 zero_copy_if::sptr xport, +                                 boost::function<double(void)> get_tick_rate) +{ +    managed_recv_buffer::sptr buff = xport->get_recv_buff(); +    if (not buff) +        return; + +    //extract packet info +    vrt::if_packet_info_t if_packet_info; +    if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); +    const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); + +    //unpacking can fail +    try +    { +        e300_if_hdr_unpack_le(packet_buff, if_packet_info); +    } +    catch(const std::exception &ex) +    { +        UHD_MSG(error) << "Error parsing async message packet: " << ex.what() << std::endl; +        return; +    } + +    //catch the flow control packets and react +    if (uhd::wtohx(packet_buff[if_packet_info.num_header_words32+0]) == 0) +    { +        const size_t seq = uhd::wtohx(packet_buff[if_packet_info.num_header_words32+1]); +        fc_cache->seq_queue.push_with_haste(seq); +        return; +    } + +    //fill in the async metadata +    async_metadata_t metadata; +    load_metadata_from_buff(uhd::wtohx<boost::uint32_t>, +                            metadata, if_packet_info, packet_buff, +                            get_tick_rate(), fc_cache->stream_channel); + +    //The FC response and the burst ack are two indicators that the radio +    //consumed packets. Use them to update the FC metadata +    if (metadata.event_code == E300_ASYNC_EVENT_CODE_FLOW_CTRL or +        metadata.event_code == async_metadata_t::EVENT_CODE_BURST_ACK +    ) { +        const size_t seq = metadata.user_payload[0]; +        fc_cache->seq_queue.push_with_pop_on_full(seq); +    } + +    //FC responses don't propagate up to the user so filter them here +    if (metadata.event_code != E300_ASYNC_EVENT_CODE_FLOW_CTRL) { +        fc_cache->async_queue->push_with_pop_on_full(metadata); +        metadata.channel = fc_cache->device_channel; +        fc_cache->old_async_queue->push_with_pop_on_full(metadata); +        standard_async_msg_prints(metadata); +    } +} + +static managed_send_buffer::sptr get_tx_buff_with_flowctrl( +    task::sptr /*holds ref*/, +    boost::shared_ptr<e300_tx_fc_cache_t> fc_cache, +    zero_copy_if::sptr xport, +    const size_t fc_window, +    const double timeout +){ +    while (true) +    { +        const size_t delta = (fc_cache->last_seq_out & HW_SEQ_NUM_MASK) - (fc_cache->last_seq_ack & HW_SEQ_NUM_MASK); +        if ((delta & HW_SEQ_NUM_MASK) <= fc_window) +            break; + +        const bool ok = fc_cache->seq_queue.pop_with_timed_wait(fc_cache->last_seq_ack, timeout); +        if (not ok) +            return managed_send_buffer::sptr(); //timeout waiting for flow control +    } + +    managed_send_buffer::sptr buff = xport->get_send_buff(timeout); +    if (buff) { +        fc_cache->last_seq_out++; //update seq, this will actually be a send +    } + +    return buff; +} + +/*********************************************************************** + * Async Data + **********************************************************************/ +bool e300_impl::recv_async_msg( +    async_metadata_t &async_metadata, double timeout +) +{ +    return _async_md->pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +rx_streamer::sptr e300_impl::get_rx_stream(const uhd::stream_args_t &args_) +{ +    boost::mutex::scoped_lock lock(_stream_spawn_mutex); +    stream_args_t args = args_; + +    //setup defaults for unspecified values +    if (not args.otw_format.empty() and args.otw_format != "sc16") +    { +        throw uhd::value_error("e300_impl::get_rx_stream only supports otw_format sc16"); +    } +    args.otw_format = "sc16"; +    args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + +    boost::shared_ptr<sph::recv_packet_streamer> my_streamer; +    for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) +    { + +        const size_t radio_index = _tree->access<std::vector<size_t> >("/mboards/0/rx_chan_dsp_mapping") +                                       .get().at(args.channels[stream_i]); + +        radio_perifs_t &perif = _radio_perifs[radio_index]; + +        // make a transport, grab a sid +        boost::uint32_t data_sid; +        both_xports_t data_xports = _make_transport( +           radio_index ? E300_XB_DST_R1 : E300_XB_DST_R0, +           E300_RADIO_DEST_PREFIX_RX, +           _data_xport_params, +           data_sid); + +        //calculate packet size +        static const size_t hdr_size = 0 +            + vrt::num_vrl_words32*sizeof(boost::uint32_t) +            + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) +            + sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer +            - sizeof(vrt::if_packet_info_t().cid) //no class id ever used +            - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used +        ; +        const size_t bpp = data_xports.recv->get_recv_frame_size() - hdr_size; +        const size_t bpi = convert::get_bytes_per_item(args.otw_format); +        const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); + +        //make the new streamer given the samples per packet +        if (not my_streamer) +            my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp); +        my_streamer->resize(args.channels.size()); + +        //init some streamer stuff +        my_streamer->set_vrt_unpacker(&e300_if_hdr_unpack_le); + +        //set the converter +        uhd::convert::id_type id; +        id.input_format = args.otw_format + "_item32_le"; +        id.num_inputs = 1; +        id.output_format = args.cpu_format; +        id.num_outputs = 1; +        my_streamer->set_converter(id); + +        perif.framer->set_nsamps_per_packet(spp); //seems to be a good place to set this +        perif.framer->set_sid((data_sid << 16) | (data_sid >> 16)); +        perif.framer->setup(args); +        perif.ddc->setup(args); +        my_streamer->set_xport_chan_get_buff(stream_i, boost::bind( +            &zero_copy_if::get_recv_buff, data_xports.recv, _1 +        ), true /*flush*/); +        my_streamer->set_overflow_handler(stream_i, +            boost::bind(&rx_vita_core_3000::handle_overflow, perif.framer) +        ); + +        //setup flow control +        const size_t fc_window = data_xports.recv->get_num_recv_frames(); +        perif.framer->configure_flow_control(fc_window); +        boost::shared_ptr<e300_rx_fc_cache_t> fc_cache(new e300_rx_fc_cache_t()); +        my_streamer->set_xport_handle_flowctrl(stream_i, +            boost::bind(&handle_rx_flowctrl, data_sid, data_xports.send, fc_cache, _1), +            static_cast<size_t>(static_cast<double>(fc_window) * E300_RX_SW_BUFF_FULLNESS), +            true/*init*/); + +        my_streamer->set_issue_stream_cmd(stream_i, +            boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1) +        ); +        perif.rx_streamer = my_streamer; //store weak pointer + +        //sets all tick and samp rates on this streamer +        this->_update_tick_rate(this->_get_tick_rate()); +        _tree->access<double>(str(boost::format("/mboards/0/rx_dsps/%u/rate/value") % radio_index)).update(); + +    } +    _update_enables(); +    return my_streamer; +} + +/*********************************************************************** + * Transmit streamer + **********************************************************************/ +tx_streamer::sptr e300_impl::get_tx_stream(const uhd::stream_args_t &args_) +{ +    boost::mutex::scoped_lock lock(_stream_spawn_mutex); +    stream_args_t args = args_; + +    //setup defaults for unspecified values +    if (not args.otw_format.empty() and args.otw_format != "sc16") +    { +        throw uhd::value_error("e300_impl::get_tx_stream only supports otw_format sc16"); +    } +    args.otw_format = "sc16"; +    args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + + +    //shared async queue for all channels in streamer +    boost::shared_ptr<async_md_type> async_md(new async_md_type(1000/*messages deep*/)); + +    boost::shared_ptr<sph::send_packet_streamer> my_streamer; + +    for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) +    { +        const size_t radio_index = _tree->access<std::vector<size_t> >("/mboards/0/tx_chan_dsp_mapping") +                                       .get().at(args.channels[stream_i]); + + +        radio_perifs_t &perif = _radio_perifs[radio_index]; + + +        // make a transport, grab a sid +        boost::uint32_t data_sid; +        both_xports_t data_xports = _make_transport( +           radio_index ? E300_XB_DST_R1 : E300_XB_DST_R0, +           E300_RADIO_DEST_PREFIX_TX, +           _data_xport_params, +           data_sid); + +        //calculate packet size +        static const size_t hdr_size = 0 +            + vrt::num_vrl_words32*sizeof(boost::uint32_t) +            + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) +            + sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer +            - sizeof(vrt::if_packet_info_t().cid) //no class id ever used +            - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used +        ; +        const size_t bpp = data_xports.send->get_send_frame_size() - hdr_size; +        const size_t bpi = convert::get_bytes_per_item(args.otw_format); +        const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); + +        //make the new streamer given the samples per packet +        if (not my_streamer) +            my_streamer = boost::make_shared<sph::send_packet_streamer>(spp); +        my_streamer->resize(args.channels.size()); + +        //init some streamer stuff +        my_streamer->set_vrt_packer(&e300_if_hdr_pack_le); + +        //set the converter +        uhd::convert::id_type id; +        id.input_format = args.cpu_format; +        id.num_inputs = 1; +        id.output_format = args.otw_format + "_item32_le"; +        id.num_outputs = 1; +        my_streamer->set_converter(id); + +        perif.deframer->clear(); +        perif.deframer->setup(args); +        perif.duc->setup(args); + +        //flow control setup +        const size_t fc_window = data_xports.send->get_num_send_frames(); +        perif.deframer->configure_flow_control(0/*cycs off*/, fc_window/8/*pkts*/); +        boost::shared_ptr<e300_tx_fc_cache_t> fc_cache(new e300_tx_fc_cache_t()); +        fc_cache->stream_channel = stream_i; +        fc_cache->device_channel = args.channels[stream_i]; +        fc_cache->async_queue = async_md; +        fc_cache->old_async_queue = _async_md; + +        tick_rate_retriever_t get_tick_rate_fn = boost::bind(&e300_impl::_get_tick_rate, this); + +        task::sptr task = task::make(boost::bind(&handle_tx_async_msgs, +                                                 fc_cache, data_xports.recv, +                                                 get_tick_rate_fn)); + +        my_streamer->set_xport_chan_get_buff( +            stream_i, +            boost::bind(&get_tx_buff_with_flowctrl, task, fc_cache, data_xports.send, fc_window, _1) +        ); + +        my_streamer->set_async_receiver( +            boost::bind(&async_md_type::pop_with_timed_wait, async_md, _1, _2) +        ); +        my_streamer->set_xport_chan_sid(stream_i, true, data_sid); +        my_streamer->set_enable_trailer(false); //TODO not implemented trailer support yet +        perif.tx_streamer = my_streamer; //store weak pointer + +        //sets all tick and samp rates on this streamer +        this->_update_tick_rate(this->_get_tick_rate()); +        _tree->access<double>(str(boost::format("/mboards/0/tx_dsps/%u/rate/value") % radio_index)).update(); +    } +    _update_enables(); +    return my_streamer; +} +}}} // namespace diff --git a/host/lib/usrp/e300/e300_network.cpp b/host/lib/usrp/e300/e300_network.cpp new file mode 100644 index 000000000..883ff0c4f --- /dev/null +++ b/host/lib/usrp/e300/e300_network.cpp @@ -0,0 +1,642 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "e300_network.hpp" + +#ifdef E300_NATIVE + +#include "e300_impl.hpp" + +#include "ad9361_ctrl.hpp" + +#include "e300_sensor_manager.hpp" +#include "e300_fifo_config.hpp" +#include "e300_spi.hpp" +#include "e300_i2c.hpp" +#include "e300_defaults.hpp" +#include "e300_common.hpp" +#include "e300_remote_codec_ctrl.hpp" + +#include <uhd/utils/msg.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/images.hpp> + +#include <boost/asio.hpp> +#include <boost/thread.hpp> +#include <boost/filesystem.hpp> +#include <boost/make_shared.hpp> + +#include <fstream> + +using namespace uhd; +using namespace uhd::transport; +namespace asio = boost::asio; +namespace fs = boost::filesystem; + +namespace uhd { namespace usrp { namespace e300 { + +static const size_t E300_NETWORK_DEBUG = false; + +static inline bool wait_for_recv_ready(int sock_fd, const size_t timeout_ms) +{ +    //setup timeval for timeout +    timeval tv; +    tv.tv_sec = 0; +    tv.tv_usec = timeout_ms*1000; + +    //setup rset for timeout +    fd_set rset; +    FD_ZERO(&rset); +    FD_SET(sock_fd, &rset); + +    //call select with timeout on receive socket +    return ::select(sock_fd+1, &rset, NULL, NULL, &tv) > 0; +} + +static boost::mutex endpoint_mutex; + +/*********************************************************************** + * Receive tunnel - forwards recv interface to send socket + **********************************************************************/ +static void e300_recv_tunnel( +    const std::string &name, +    uhd::transport::zero_copy_if::sptr recver, +    boost::shared_ptr<asio::ip::udp::socket> sender, +    asio::ip::udp::endpoint *endpoint, +    bool *running +) +{ +    asio::ip::udp::endpoint _tx_endpoint; +    try +    { +        while (*running) +        { +            //step 1 - get the buffer +            managed_recv_buffer::sptr buff = recver->get_recv_buff(); +            if (not buff) continue; +            if (E300_NETWORK_DEBUG) UHD_MSG(status) << name << " got " << buff->size() << std::endl; + +            //step 1.5 -- update endpoint +            { +                boost::mutex::scoped_lock l(endpoint_mutex); +                _tx_endpoint = *endpoint; +            } + +            //step 2 - send to the socket +            sender->send_to(asio::buffer(buff->cast<const void *>(), buff->size()), _tx_endpoint); +        } +    } +    catch(const std::exception &ex) +    { +        UHD_MSG(error) << "e300_recv_tunnel exit " << name << " " << ex.what() << std::endl; +    } +    catch(...) +    { +        UHD_MSG(error) << "e300_recv_tunnel exit " << name << std::endl; +    } +    UHD_MSG(status) << "e300_recv_tunnel exit " << name << std::endl; +    *running = false; +} + +/*********************************************************************** + * Send tunnel - forwards recv socket to send interface + **********************************************************************/ +static void e300_send_tunnel( +    const std::string &name, +    boost::shared_ptr<asio::ip::udp::socket> recver, +    uhd::transport::zero_copy_if::sptr sender, +    asio::ip::udp::endpoint *endpoint, +    bool *running +) +{ +    asio::ip::udp::endpoint _rx_endpoint; +    try +    { +        while (*running) +        { +            //step 1 - get the buffer +            managed_send_buffer::sptr buff = sender->get_send_buff(); +            if (not buff) continue; + +            //step 2 - recv from socket +            while (not wait_for_recv_ready(recver->native(), 100) and *running){} +            if (not *running) break; +            const size_t num_bytes = recver->receive_from(asio::buffer(buff->cast<void *>(), buff->size()), _rx_endpoint); +            if (E300_NETWORK_DEBUG) UHD_MSG(status) << name << " got " << num_bytes << std::endl; + +            //step 2.5 -- update endpoint +            { +                boost::mutex::scoped_lock l(endpoint_mutex); +                *endpoint = _rx_endpoint; +            } + +            //step 3 - commit the buffer +            buff->commit(num_bytes); +        } +    } +    catch(const std::exception &ex) +    { +        UHD_MSG(error) << "e300_send_tunnel exit " << name << " " << ex.what() << std::endl; +    } +    catch(...) +    { +        UHD_MSG(error) << "e300_send_tunnel exit " << name << std::endl; +    } +    UHD_MSG(status) << "e300_send_tunnel exit " << name << std::endl; +    *running = false; +} + +static void e300_codec_ctrl_tunnel( +    const std::string &name, +    boost::shared_ptr<asio::ip::udp::socket> socket, +    ad9361_ctrl::sptr _codec_ctrl, +    asio::ip::udp::endpoint *endpoint, +    bool *running +) +{ +    asio::ip::udp::endpoint _endpoint; +    try +    { +        while (*running) +        { +            uint8_t in_buff[64] = {}; +            uint8_t out_buff[64] = {}; + +            const size_t num_bytes = socket->receive_from(asio::buffer(in_buff), *endpoint); + +            typedef e300_remote_codec_ctrl::transaction_t codec_xact_t; + +            if (num_bytes < sizeof(codec_xact_t)) { +                std::cout << "Received short packet of " << num_bytes  << std::endl; +                continue; +            } + +            codec_xact_t *in = reinterpret_cast<codec_xact_t*>(in_buff); +            codec_xact_t *out = reinterpret_cast<codec_xact_t*>(out_buff); +            std::memcpy(out, in, sizeof(codec_xact_t)); + +            std::string which_str; +            switch (uhd::ntohx<boost::uint32_t>(in->which)) { +            case codec_xact_t::CHAIN_TX1: +                which_str = "TX1"; break; +            case codec_xact_t::CHAIN_TX2: +                which_str = "TX2"; break; +            case codec_xact_t::CHAIN_RX1: +                which_str = "RX1"; break; +            case codec_xact_t::CHAIN_RX2: +                which_str = "RX2"; break; +            default: +                which_str = ""; break; +            } + +            switch (uhd::ntohx<boost::uint32_t>(in->action)) { +            case codec_xact_t::ACTION_SET_GAIN: +                out->gain = _codec_ctrl->set_gain(which_str, in->gain); +                break; +            case codec_xact_t::ACTION_SET_CLOCK_RATE: +                out->rate = _codec_ctrl->set_clock_rate(in->rate); +                break; +            case codec_xact_t::ACTION_SET_ACTIVE_CHANS: +                _codec_ctrl->set_active_chains( +                    uhd::ntohx<boost::uint32_t>(in->bits) & (1<<0), +                    uhd::ntohx<boost::uint32_t>(in->bits) & (1<<1), +                    uhd::ntohx<boost::uint32_t>(in->bits) & (1<<2), +                    uhd::ntohx<boost::uint32_t>(in->bits) & (1<<3)); +                break; +            case codec_xact_t::ACTION_TUNE: +                out->freq = _codec_ctrl->tune(which_str, in->freq); +                break; +            case codec_xact_t::ACTION_SET_LOOPBACK: +                _codec_ctrl->data_port_loopback( +                    uhd::ntohx<boost::uint32_t>(in->bits) & 1); +                break; +            default: +                UHD_MSG(status) << "Got unknown request?!" << std::endl; +                //Zero out actions to fail this request on client +                out->action = uhd::htonx<boost::uint32_t>(0); +            } + +            socket->send_to(asio::buffer(out_buff, 64), *endpoint); +        } +    } +    catch(const std::exception &ex) +    { +        UHD_MSG(error) << "e300_ctrl_tunnel exit " << name << " " << ex.what() << std::endl; +    } +    catch(...) +    { +        UHD_MSG(error) << "e300_ctrl_tunnel exit " << name << std::endl; +    } +    UHD_MSG(status) << "e300_ctrl_tunnel exit " << name << std::endl; +    *running = false; +} + +static void e300_global_regs_tunnel( +    const std::string &name, +    boost::shared_ptr<asio::ip::udp::socket> socket, +    global_regs::sptr regs, +    asio::ip::udp::endpoint *endpoint, +    bool *running +) +{ +    UHD_ASSERT_THROW(regs); +    asio::ip::udp::endpoint _endpoint; +    try +    { +        while (*running) +        { +            uint8_t in_buff[16] = {}; + +            const size_t num_bytes = socket->receive_from(asio::buffer(in_buff), *endpoint); + +            if (num_bytes < 16) { +                std::cout << "Received short packet: " << num_bytes << std::endl; +                continue; +            } + +            global_regs_transaction_t *in = +                reinterpret_cast<global_regs_transaction_t *>(in_buff); + +            if(uhd::ntohx<boost::uint32_t>(in->is_poke)) { +                regs->poke32(uhd::ntohx<boost::uint32_t>(in->addr), uhd::ntohx<boost::uint32_t>(in->data)); +            } +            else { +                in->data = uhd::htonx<boost::uint32_t>(regs->peek32(uhd::ntohx<boost::uint32_t>(in->addr))); +                socket->send_to(asio::buffer(in_buff, 16), *endpoint); +            } +        } +    } +    catch(const std::exception &ex) +    { +        UHD_MSG(error) << "e300_gregs_tunnel exit " << name << " " << ex.what() << std::endl; +    } +    catch(...) +    { +        UHD_MSG(error) << "e300_gregs_tunnel exit " << name << std::endl; +    } +    UHD_MSG(status) << "e300_gregs_tunnel exit " << name << std::endl; +    *running = false; +} + +static void e300_sensor_tunnel( +    const std::string &name, +    boost::shared_ptr<asio::ip::udp::socket> socket, +    e300_sensor_manager::sptr sensor_manager, +    asio::ip::udp::endpoint *endpoint, +    bool *running +) +{ +    asio::ip::udp::endpoint _endpoint; +    try +    { +        while (*running) +        { +            uint8_t in_buff[128] = {}; + +            const size_t num_bytes = socket->receive_from(asio::buffer(in_buff), *endpoint); + +            if (num_bytes < sizeof(sensor_transaction_t)) { +                std::cout << "Received short packet: " << num_bytes << std::endl; +                continue; +            } + +            uhd::usrp::e300::sensor_transaction_t *in = +                reinterpret_cast<uhd::usrp::e300::sensor_transaction_t *>(in_buff); + +            if (uhd::ntohx(in->which) == ZYNQ_TEMP) { +                sensor_value_t temp = sensor_manager->get_mb_temp(); +                // TODO: This is ugly ... use proper serialization +                in->value = uhd::htonx<boost::uint32_t>( +                    e300_sensor_manager::pack_float_in_uint32_t(temp.to_real())); +            } else if (uhd::ntohx(in->which) == GPS_FOUND) { +                in->value = uhd::htonx<boost::uint32_t>( +                    sensor_manager->get_gps_found() ? 1 : 0); + +            } else if (uhd::ntohx(in->which) == GPS_LOCK) { +                in->value = uhd::htonx<boost::uint32_t>( +                    sensor_manager->get_gps_lock().to_bool() ? 1 : 0); +            } else if (uhd::ntohx(in->which) == GPS_TIME) { +                in->value = uhd::htonx<boost::uint32_t>( +                    sensor_manager->get_gps_time().to_int()); +            } else +                UHD_MSG(status) << "Got unknown request?!" << std::endl; + +            socket->send_to(asio::buffer(in_buff, sizeof(sensor_transaction_t)), *endpoint); +        } +    } +    catch(const std::exception &ex) +    { +        UHD_MSG(error) << "e300_sensor_tunnel exit " << name << " " << ex.what() << std::endl; +    } +    catch(...) +    { +        UHD_MSG(error) << "e300_sensor_tunnel exit " << name << std::endl; +    } +    UHD_MSG(status) << "e300_sensor_tunnel exit " << name << std::endl; +    *running = false; +} + +static void e300_i2c_tunnel( +    const std::string &name, +    boost::shared_ptr<asio::ip::udp::socket> socket, +    uhd::usrp::e300::i2c::sptr i2c, +    asio::ip::udp::endpoint *endpoint, +    bool *running +) +{ +    UHD_ASSERT_THROW(i2c); +    asio::ip::udp::endpoint _endpoint; +    try +    { +        while (*running) +        { +            uint8_t in_buff[sizeof(uhd::usrp::e300::i2c_transaction_t)]; + +            const size_t num_bytes = socket->receive_from(asio::buffer(in_buff), *endpoint); + +            if (num_bytes < sizeof(uhd::usrp::e300::i2c_transaction_t)) { +                std::cout << "Received short packet: " << num_bytes << std::endl; +                continue; +            } + +            uhd::usrp::e300::i2c_transaction_t *in = +                reinterpret_cast<uhd::usrp::e300::i2c_transaction_t *>(in_buff); + +            // byte addressed accesses go through here +            if(in->type & i2c::ONEBYTE) { +                if(in->type & i2c::WRITE) { +                    i2c->set_i2c_reg8( +                        in->addr, +                        uhd::ntohx<boost::uint16_t>(in->reg), in->data); +                } else { +                    in->data = i2c->get_i2c_reg8(in->addr, uhd::ntohx<boost::uint16_t>(in->reg)); +                    socket->send_to(asio::buffer(in_buff, sizeof(in_buff)), *endpoint); +                } + +            // 2 byte addressed accesses go through here +            } else if (in->type & i2c::TWOBYTE) { +                if(in->type & i2c::WRITE) { +                    i2c->set_i2c_reg16( +                        in->addr, +                        uhd::ntohx<boost::uint16_t>(in->reg), in->data); +                } else { +                    in->data = i2c->get_i2c_reg16(in->addr, uhd::ntohx<boost::uint16_t>(in->reg)); +                    socket->send_to(asio::buffer(in_buff, sizeof(in_buff)), *endpoint); +                } + +            } else { +                UHD_MSG(error) << "e300_i2c_tunnel could not handle message." << std::endl; +            } +        } +    } +    catch(const std::exception &ex) +    { +        UHD_MSG(error) << "e300_i2c_tunnel exit " << name << " " << ex.what() << std::endl; +    } +    catch(...) +    { +        UHD_MSG(error) << "e300_i2c_tunnel exit " << name << std::endl; +    } +    UHD_MSG(status) << "e300_i2c_tunnel exit " << name << std::endl; +    *running = false; +} + + + + +class network_server_impl : public network_server +{ +public: +    network_server_impl(const uhd::device_addr_t &device_addr); +    virtual ~network_server_impl(void); +    void run(void); + +private: +    struct xports_t +    { +        uhd::transport::zero_copy_if::sptr send_ctrl_xport; +        uhd::transport::zero_copy_if::sptr recv_ctrl_xport; +        uhd::transport::zero_copy_if::sptr tx_data_xport; +        uhd::transport::zero_copy_if::sptr tx_flow_xport; +        uhd::transport::zero_copy_if::sptr rx_data_xport; +        uhd::transport::zero_copy_if::sptr rx_flow_xport; +    }; + +private: +    void _run_server( +        const std::string &port, +        const std::string &what, +        const size_t fe); + +private: +    boost::shared_ptr<e300_fifo_interface>   _fifo_iface; +    xports_t                                 _xports[2]; +    boost::shared_ptr<ad9361_ctrl>           _codec_ctrl; +    boost::shared_ptr<global_regs>           _global_regs; +    boost::shared_ptr<e300_sensor_manager>   _sensor_manager; +    boost::shared_ptr<e300_eeprom_manager>   _eeprom_manager; +}; + +network_server_impl::~network_server_impl(void) +{ +} + +/*********************************************************************** + * The UDP server itself + **********************************************************************/ +void network_server_impl::_run_server( +    const std::string &port, +    const std::string &what, +    const size_t fe) +{ +    asio::io_service io_service; +    asio::ip::udp::resolver resolver(io_service); +    asio::ip::udp::resolver::query query(asio::ip::udp::v4(), "0.0.0.0", port); +    asio::ip::udp::endpoint endpoint = *resolver.resolve(query); + +    //boost::shared_ptr<asio::ip::udp::acceptor> acceptor(new asio::ip::udp::acceptor(io_service, endpoint)); +    while (not boost::this_thread::interruption_requested()) +    { +        UHD_MSG(status) << "e300 run server on port " << port << " for " << what << std::endl; +        try +        { +            //while (not wait_for_recv_ready(acceptor->native(), 100)) +            //{ +            //    if (boost::this_thread::interruption_requested()) return; +            //} +            boost::shared_ptr<asio::ip::udp::socket> socket; +            socket.reset(new asio::ip::udp::socket(io_service, endpoint)); +            //acceptor->accept(*socket); +            UHD_MSG(status) << "e300 socket accept on port " << port << " for " << what << std::endl; +            //asio::ip::udp::no_delay option(true); +            //socket->set_option(option); +            boost::thread_group tg; +            bool running = true; +            xports_t &perif = _xports[fe]; +            if (what == "RX") { +                tg.create_thread(boost::bind(&e300_recv_tunnel, "RX data tunnel", perif.rx_data_xport, socket, &endpoint, &running)); +                tg.create_thread(boost::bind(&e300_send_tunnel, "RX flow tunnel", socket, perif.rx_flow_xport, &endpoint, &running)); +            } +            if (what == "TX") { +                tg.create_thread(boost::bind(&e300_recv_tunnel, "TX flow tunnel", perif.tx_flow_xport, socket, &endpoint, &running)); +                tg.create_thread(boost::bind(&e300_send_tunnel, "TX data tunnel", socket, perif.tx_data_xport, &endpoint, &running)); +            } +            if (what == "CTRL") { +                tg.create_thread(boost::bind(&e300_recv_tunnel, "response tunnel", perif.recv_ctrl_xport, socket, &endpoint, &running)); +                tg.create_thread(boost::bind(&e300_send_tunnel, "control tunnel", socket, perif.send_ctrl_xport, &endpoint, &running)); +            } +            if (what == "CODEC") { +                tg.create_thread(boost::bind(&e300_codec_ctrl_tunnel, "CODEC tunnel", socket, _codec_ctrl, &endpoint, &running)); +            } +            if (what == "I2C") { +                tg.create_thread(boost::bind(&e300_i2c_tunnel, "I2C tunnel", socket, _eeprom_manager->get_i2c_sptr(), &endpoint, &running)); +            } +            if (what == "GREGS") { +                tg.create_thread(boost::bind(&e300_global_regs_tunnel, "GREGS tunnel", socket, _global_regs, &endpoint, &running)); +            } +            if (what == "SENSOR") { +                tg.create_thread(boost::bind(&e300_sensor_tunnel, "SENSOR tunnel", socket, _sensor_manager, &endpoint, &running)); +            } + +            tg.join_all(); +            socket->close(); +            socket.reset(); +        } +        catch(...){} +    } +} + +void network_server_impl::run() +{ +    for(;;) +    { +        boost::thread_group tg; +        tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_RX_PORT0, "RX",0)); +        tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_TX_PORT0, "TX",0)); +        tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_CTRL_PORT0, "CTRL",0)); + +        tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_RX_PORT1, "RX",1)); +        tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_TX_PORT1, "TX",1)); +        tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_CTRL_PORT1, "CTRL",1)); + +        tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_SENSOR_PORT, "SENSOR", 0 /*don't care */)); + +        tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_CODEC_PORT, "CODEC", 0 /*don't care */)); +        tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_GREGS_PORT, "GREGS", 0 /*don't care */)); +        tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_I2C_PORT, "I2C", 0 /*don't care */)); +        tg.join_all(); +    } +} +network_server_impl::network_server_impl(const uhd::device_addr_t &device_addr) +{ +    _eeprom_manager = boost::make_shared<e300_eeprom_manager>(i2c::make_i2cdev(E300_I2CDEV_DEVICE)); +    if (not device_addr.has_key("no_reload_fpga")) { +        // Load FPGA image if provided via args +        if (device_addr.has_key("fpga")) { +            common::load_fpga_image(device_addr["fpga"]); +        // Else load the FPGA image based on the product ID +        } else { +            //extract the FPGA path for the e300 +            const boost::uint16_t pid = boost::lexical_cast<boost::uint16_t>( +                _eeprom_manager->get_mb_eeprom()["product"]); +            std::string fpga_image; +            switch(e300_eeprom_manager::get_mb_type(pid)) { +            case e300_eeprom_manager::USRP_E310_MB: +                fpga_image = find_image_path(E310_FPGA_FILE_NAME); +                break; +            case e300_eeprom_manager::USRP_E300_MB: +                fpga_image = find_image_path(E300_FPGA_FILE_NAME); +                break; +            case e300_eeprom_manager::UNKNOWN: +            default: +                UHD_MSG(warning) << "Unknown motherboard type, loading e300 image." +                                 << std::endl; +                fpga_image = find_image_path(E300_FPGA_FILE_NAME); +                break; +            } +            common::load_fpga_image(fpga_image); +        } +    } + +    uhd::transport::zero_copy_xport_params ctrl_xport_params; +    ctrl_xport_params.recv_frame_size = e300::DEFAULT_CTRL_FRAME_SIZE; +    ctrl_xport_params.num_recv_frames = e300::DEFAULT_CTRL_NUM_FRAMES; +    ctrl_xport_params.send_frame_size = e300::DEFAULT_CTRL_FRAME_SIZE; +    ctrl_xport_params.num_send_frames = e300::DEFAULT_CTRL_NUM_FRAMES; + +    uhd::transport::zero_copy_xport_params data_xport_params; +    data_xport_params.recv_frame_size = device_addr.cast<size_t>("recv_frame_size", e300::DEFAULT_RX_DATA_FRAME_SIZE); +    data_xport_params.num_recv_frames = device_addr.cast<size_t>("num_recv_frames", e300::DEFAULT_RX_DATA_NUM_FRAMES); +    data_xport_params.send_frame_size = device_addr.cast<size_t>("send_frame_size", e300::DEFAULT_TX_DATA_FRAME_SIZE); +    data_xport_params.num_send_frames = device_addr.cast<size_t>("num_send_frames", e300::DEFAULT_TX_DATA_NUM_FRAMES); +    // until we figure out why this goes wrong we'll keep this hack around +    data_xport_params.recv_frame_size = +        std::min(e300::MAX_NET_RX_DATA_FRAME_SIZE, data_xport_params.recv_frame_size); +    data_xport_params.send_frame_size = +        std::min(e300::MAX_NET_TX_DATA_FRAME_SIZE, data_xport_params.send_frame_size); + + +    e300_fifo_config_t fifo_cfg; +    try { +        fifo_cfg = e300_read_sysfs(); +    } catch (uhd::lookup_error &e) { +        throw uhd::runtime_error("Failed to get driver parameters from sysfs."); +    } +    _fifo_iface = e300_fifo_interface::make(fifo_cfg); +    _global_regs = global_regs::make(_fifo_iface->get_global_regs_base()); + +    // static mapping, boooohhhhhh +    _xports[0].send_ctrl_xport = _fifo_iface->make_send_xport(E300_R0_CTRL_STREAM, ctrl_xport_params); +    _xports[0].recv_ctrl_xport = _fifo_iface->make_recv_xport(E300_R0_CTRL_STREAM, ctrl_xport_params); +    _xports[0].tx_data_xport   = _fifo_iface->make_send_xport(E300_R0_TX_DATA_STREAM, data_xport_params); +    _xports[0].tx_flow_xport   = _fifo_iface->make_recv_xport(E300_R0_TX_DATA_STREAM, ctrl_xport_params); +    _xports[0].rx_data_xport   = _fifo_iface->make_recv_xport(E300_R0_RX_DATA_STREAM, data_xport_params); +    _xports[0].rx_flow_xport   = _fifo_iface->make_send_xport(E300_R0_RX_DATA_STREAM, ctrl_xport_params); + +    _xports[1].send_ctrl_xport = _fifo_iface->make_send_xport(E300_R1_CTRL_STREAM, ctrl_xport_params); +    _xports[1].recv_ctrl_xport = _fifo_iface->make_recv_xport(E300_R1_CTRL_STREAM, ctrl_xport_params); +    _xports[1].tx_data_xport   = _fifo_iface->make_send_xport(E300_R1_TX_DATA_STREAM, data_xport_params); +    _xports[1].tx_flow_xport   = _fifo_iface->make_recv_xport(E300_R1_TX_DATA_STREAM, ctrl_xport_params); +    _xports[1].rx_data_xport   = _fifo_iface->make_recv_xport(E300_R1_RX_DATA_STREAM, data_xport_params); +    _xports[1].rx_flow_xport   = _fifo_iface->make_send_xport(E300_R1_RX_DATA_STREAM, ctrl_xport_params); + +    ad9361_params::sptr client_settings = boost::make_shared<e300_ad9361_client_t>(); +    _codec_ctrl = ad9361_ctrl::make_spi(client_settings, spi::make(E300_SPIDEV_DEVICE), 1); +    // This is horrible ... why do I have to sleep here? +    boost::this_thread::sleep(boost::posix_time::milliseconds(100)); +    _sensor_manager = e300_sensor_manager::make_local( +        gps::ublox::ubx::control::make("/dev/ttyPS1", 9600)); +} + +}}} // namespace + +using namespace uhd::usrp::e300; + +network_server::sptr network_server::make(const uhd::device_addr_t &device_addr) +{ +    return sptr(new network_server_impl(device_addr)); +} + +#else + +using namespace uhd::usrp::e300; + +network_server::sptr network_server::make(const uhd::device_addr_t &) +{ +    throw uhd::assertion_error("network_server::make() !E300_NATIVE"); +} +#endif diff --git a/host/lib/usrp/e300/e300_network.hpp b/host/lib/usrp/e300/e300_network.hpp new file mode 100644 index 000000000..5a9df0ca7 --- /dev/null +++ b/host/lib/usrp/e300/e300_network.hpp @@ -0,0 +1,43 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_E300_NETWORK_HPP +#define INCLUDED_E300_NETWORK_HPP + +#include <string> +#include <boost/noncopyable.hpp> + +#include <uhd/device.hpp> + + +static const std::string E310_FPGA_FILE_NAME = "usrp_e310_fpga.bit"; +static const std::string E300_FPGA_FILE_NAME = "usrp_e300_fpga.bit"; + +namespace uhd { namespace usrp { namespace e300 { + +class UHD_API network_server : boost::noncopyable +{ +public: +    typedef boost::shared_ptr<network_server> sptr; +    virtual void run(void) = 0; + +    static sptr make(const uhd::device_addr_t &device_addr); +}; + + +}}} +#endif // INCLUDED_E300_NETWORK_HPP diff --git a/host/lib/usrp/e300/e300_regs.hpp b/host/lib/usrp/e300/e300_regs.hpp new file mode 100644 index 000000000..f99a19b8e --- /dev/null +++ b/host/lib/usrp/e300/e300_regs.hpp @@ -0,0 +1,69 @@ +// +// Copyright 2012-2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_E300_REGS_HPP +#define INCLUDED_E300_REGS_HPP + +#include <boost/cstdint.hpp> + +#define TOREG(x) ((x)*4) + +#define localparam static const int + +localparam SR_TEST       = 7; +localparam SR_SPI        = 8; +localparam SR_GPIO       = 16; +localparam SR_MISC_OUTS  = 24; +localparam SR_READBACK   = 32; +localparam SR_TX_CTRL    = 64; +localparam SR_RX_CTRL    = 96; +localparam SR_TIME       = 128; +localparam SR_RX_DSP     = 144; +localparam SR_TX_DSP     = 184; +localparam SR_LEDS       = 196; +localparam SR_FP_GPIO    = 200; +localparam SR_RX_FRONT   = 208; +localparam SR_TX_FRONT   = 216; +localparam SR_CODEC_IDLE = 250; + + +localparam RB32_SPI             = 4; +localparam RB64_TIME_NOW        = 8; +localparam RB64_TIME_PPS        = 16; +localparam RB32_TEST            = 24; +localparam RB32_FP_GPIO         = 32; +localparam RB64_CODEC_READBACK  = 40; +localparam RB32_RADIO_NUM       = 48; + +localparam ST_RX_ENABLE = 20; +localparam ST_TX_ENABLE = 19; + +localparam LED_TXRX_TX = 18; +localparam LED_TXRX_RX = 17; +localparam LED_RX_RX = 16; +localparam VCRX_V2 = 15; +localparam VCRX_V1 = 14; +localparam VCTXRX_V2 = 13; +localparam VCTXRX_V1 = 12; +localparam TX_ENABLEB = 11; +localparam TX_ENABLEA = 10; +localparam RXC_BANDSEL = 8; +localparam RXB_BANDSEL = 6; +localparam RX_BANDSEL = 3; +localparam TX_BANDSEL = 0; + +#endif /* INCLUDED_E300_REGS_HPP */ diff --git a/host/lib/usrp/e300/e300_remote_codec_ctrl.cpp b/host/lib/usrp/e300/e300_remote_codec_ctrl.cpp new file mode 100644 index 000000000..bcc8ee4cf --- /dev/null +++ b/host/lib/usrp/e300/e300_remote_codec_ctrl.cpp @@ -0,0 +1,148 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "e300_remote_codec_ctrl.hpp" + +#include <boost/cstdint.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/byteswap.hpp> +#include <cstring> +#include <iostream> + +namespace uhd { namespace usrp { namespace e300 { + +class e300_remote_codec_ctrl_impl : public e300_remote_codec_ctrl +{ +public: +    e300_remote_codec_ctrl_impl(uhd::transport::zero_copy_if::sptr xport) : _xport(xport) +    { +    } + +    virtual ~e300_remote_codec_ctrl_impl(void) +    { +    } + +    double set_gain(const std::string &which, const double value) +    { +        _clear(); +        _args.action = uhd::htonx<boost::uint32_t>(transaction_t::ACTION_SET_GAIN); +        if (which == "TX1")      _args.which = uhd::htonx<boost::uint32_t>(transaction_t::CHAIN_TX1); +        else if (which == "TX2") _args.which = uhd::htonx<boost::uint32_t>(transaction_t::CHAIN_TX2); +        else if (which == "RX1") _args.which = uhd::htonx<boost::uint32_t>(transaction_t::CHAIN_RX1); +        else if (which == "RX2") _args.which = uhd::htonx<boost::uint32_t>(transaction_t::CHAIN_RX2); +        else throw std::runtime_error("e300_remote_codec_ctrl_impl incorrect chain string."); +        _args.gain = value; + +        _transact(); +        return _retval.gain; +    } + +    double set_clock_rate(const double rate) +    { +        _clear(); +        _args.action = uhd::htonx<boost::uint32_t>( +            transaction_t::ACTION_SET_CLOCK_RATE); +        _args.which = uhd::htonx<boost::uint32_t>( +            transaction_t::CHAIN_NONE);  /*Unused*/ +        _args.rate = rate; + +        _transact(); +        return _retval.gain; +    } + +    void set_active_chains(bool tx1, bool tx2, bool rx1, bool rx2) +    { +        _clear(); +        _args.action = uhd::htonx<boost::uint32_t>( +            transaction_t::ACTION_SET_ACTIVE_CHANS); +        /*Unused*/ +        _args.which = uhd::htonx<boost::uint32_t>( +            transaction_t::CHAIN_NONE); +        _args.bits = uhd::htonx<boost::uint32_t>( +                     (tx1 ? (1<<0) : 0) | +                     (tx2 ? (1<<1) : 0) | +                     (rx1 ? (1<<2) : 0) | +                     (rx2 ? (1<<3) : 0)); + +        _transact(); +    } + +    double tune(const std::string &which, const double value) +    { +        _clear(); +        _args.action = uhd::htonx<boost::uint32_t>(transaction_t::ACTION_TUNE); +        if (which == "TX1")      _args.which = uhd::htonx<boost::uint32_t>(transaction_t::CHAIN_TX1); +        else if (which == "TX2") _args.which = uhd::htonx<boost::uint32_t>(transaction_t::CHAIN_TX2); +        else if (which == "RX1") _args.which = uhd::htonx<boost::uint32_t>(transaction_t::CHAIN_RX1); +        else if (which == "RX2") _args.which = uhd::htonx<boost::uint32_t>(transaction_t::CHAIN_RX2); +        else throw std::runtime_error("e300_remote_codec_ctrl_impl incorrect chain string."); +        _args.freq = value; + +        _transact(); +        return _retval.freq; +    } + +    void data_port_loopback(const bool on) +    { +        _clear(); +        _args.action = uhd::htonx<boost::uint32_t>(transaction_t::ACTION_SET_LOOPBACK); +        _args.which  = uhd::htonx<boost::uint32_t>(transaction_t::CHAIN_NONE);  /*Unused*/ +        _args.bits = uhd::htonx<boost::uint32_t>(on ? 1 : 0); + +        _transact(); +    } + +private: +    void _transact() { +        { +            uhd::transport::managed_send_buffer::sptr buff = _xport->get_send_buff(10.0); +            if (not buff or buff->size() < sizeof(_args)) +                throw std::runtime_error("e300_remote_codec_ctrl_impl send timeout"); +            std::memcpy(buff->cast<void *>(), &_args, sizeof(_args)); +            buff->commit(sizeof(_args)); +        } +        { +            uhd::transport::managed_recv_buffer::sptr buff = _xport->get_recv_buff(10.0); +            if (not buff or buff->size() < sizeof(_retval)) +                throw std::runtime_error("e300_remote_codec_ctrl_impl recv timeout"); +            std::memcpy(&_retval, buff->cast<const void *>(), sizeof(_retval)); +        } + +        if (_args.action != _retval.action) +            throw std::runtime_error("e300_remote_codec_ctrl_impl trancation failed."); +    } + +    void _clear() { +        _args.action = 0; +        _args.which = 0; +        _args.bits = 0; +        _retval.action = 0; +        _retval.which = 0; +        _retval.bits = 0; +    } + +    uhd::transport::zero_copy_if::sptr _xport; +    transaction_t                      _args; +    transaction_t                      _retval; +}; + +ad9361_ctrl::sptr e300_remote_codec_ctrl::make(uhd::transport::zero_copy_if::sptr xport) +{ +    return sptr(new e300_remote_codec_ctrl_impl(xport)); +} + +}}}; diff --git a/host/lib/usrp/e300/e300_remote_codec_ctrl.hpp b/host/lib/usrp/e300/e300_remote_codec_ctrl.hpp new file mode 100644 index 000000000..015ad8323 --- /dev/null +++ b/host/lib/usrp/e300/e300_remote_codec_ctrl.hpp @@ -0,0 +1,59 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_E300_REMOTE_CODEC_CTRL_HPP +#define INCLUDED_E300_REMOTE_CODEC_CTRL_HPP + +#include "ad9361_ctrl.hpp" +#include <uhd/transport/zero_copy.hpp> + +namespace uhd { namespace usrp { namespace e300 { + +class e300_remote_codec_ctrl : public uhd::usrp::ad9361_ctrl +{ +public: +    struct transaction_t { +        boost::uint32_t     action; +        boost::uint32_t     which; +        union { +            double          rate; +            double          gain; +            double          freq; +            boost::uint64_t bits; +        }; + +        //Actions +        static const boost::uint32_t ACTION_SET_GAIN            = 10; +        static const boost::uint32_t ACTION_SET_CLOCK_RATE      = 11; +        static const boost::uint32_t ACTION_SET_ACTIVE_CHANS    = 12; +        static const boost::uint32_t ACTION_TUNE                = 13; +        static const boost::uint32_t ACTION_SET_LOOPBACK        = 14; + +        //Values for "which" +        static const boost::uint32_t CHAIN_NONE = 0; +        static const boost::uint32_t CHAIN_TX1  = 1; +        static const boost::uint32_t CHAIN_TX2  = 2; +        static const boost::uint32_t CHAIN_RX1  = 3; +        static const boost::uint32_t CHAIN_RX2  = 4; +    }; + +    static sptr make(uhd::transport::zero_copy_if::sptr xport); +}; + +}}}; + +#endif /* INCLUDED_E300_REMOTE_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/e300/e300_sensor_manager.cpp b/host/lib/usrp/e300/e300_sensor_manager.cpp new file mode 100644 index 000000000..5e65b8fd3 --- /dev/null +++ b/host/lib/usrp/e300/e300_sensor_manager.cpp @@ -0,0 +1,289 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "e300_sensor_manager.hpp" + +#include <boost/thread.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> + +#include <cstring> +#include <uhd/exception.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/usrp/gps_ctrl.hpp> + +namespace uhd { namespace usrp { namespace e300 { + +class e300_sensor_proxy : public e300_sensor_manager +{ +public: +    e300_sensor_proxy( +        uhd::transport::zero_copy_if::sptr xport) : _xport(xport) +    { +    } + +    std::vector<std::string> get_sensors() +    { +        return boost::assign::list_of("temp")("gps_locked")("gps_time"); +    } + +    uhd::sensor_value_t get_sensor(const std::string &key) +    { +        if (key == "temp") +            return get_mb_temp(); +        else if (key == "gps_locked") +            return get_gps_lock(); +        else if (key == "gps_time") +            return get_gps_time(); +        else +            throw uhd::lookup_error( +                str(boost::format("Invalid sensor %s requested.") % key)); +    } + +    uhd::sensor_value_t get_mb_temp(void) +    { +        boost::mutex::scoped_lock(_mutex); +        sensor_transaction_t transaction; +        transaction.which = uhd::htonx<boost::uint32_t>(ZYNQ_TEMP); +        { +            uhd::transport::managed_send_buffer::sptr buff +                = _xport->get_send_buff(1.0); +            if (not buff or buff->size() < sizeof(transaction)) { +                throw uhd::runtime_error("sensor proxy send timeout"); +            } +            std::memcpy( +                buff->cast<void *>(), +                &transaction, +                sizeof(transaction)); +            buff->commit(sizeof(transaction)); +        } +        { +            uhd::transport::managed_recv_buffer::sptr buff +                = _xport->get_recv_buff(1.0); + +            if (not buff or buff->size() < sizeof(transaction)) +                throw uhd::runtime_error("sensor proxy recv timeout"); + +            std::memcpy( +                &transaction, +                buff->cast<const void *>(), +                sizeof(transaction)); +        } +        UHD_ASSERT_THROW(uhd::ntohx<boost::uint32_t>(transaction.which) == ZYNQ_TEMP); +        // TODO: Use proper serialization here ... +        return sensor_value_t( +            "temp", +            e300_sensor_manager::unpack_float_from_uint32_t( +                uhd::ntohx(transaction.value)), +            "C"); +    } + +    uhd::sensor_value_t get_gps_time(void) +    { +        boost::mutex::scoped_lock(_mutex); +        sensor_transaction_t transaction; +        transaction.which = uhd::htonx<boost::uint32_t>(GPS_TIME); +        { +            uhd::transport::managed_send_buffer::sptr buff +                = _xport->get_send_buff(1.0); +            if (not buff or buff->size() < sizeof(transaction)) { +                throw uhd::runtime_error("sensor proxy send timeout"); +            } +            std::memcpy( +                buff->cast<void *>(), +                &transaction, +                sizeof(transaction)); +            buff->commit(sizeof(transaction)); +        } +        { +            uhd::transport::managed_recv_buffer::sptr buff +                = _xport->get_recv_buff(1.0); + +            if (not buff or buff->size() < sizeof(transaction)) +                throw uhd::runtime_error("sensor proxy recv timeout"); + +            std::memcpy( +                &transaction, +                buff->cast<const void *>(), +                sizeof(transaction)); +        } +        UHD_ASSERT_THROW(uhd::ntohx<boost::uint32_t>(transaction.which) == GPS_TIME); +        // TODO: Use proper serialization here ... +        return sensor_value_t("GPS epoch time", int(uhd::ntohx<boost::uint32_t>(transaction.value)), "seconds"); +    } + +    bool get_gps_found(void) +    { +        boost::mutex::scoped_lock(_mutex); +        sensor_transaction_t transaction; +        transaction.which = uhd::htonx<boost::uint32_t>(GPS_FOUND); +        { +            uhd::transport::managed_send_buffer::sptr buff +                = _xport->get_send_buff(1.0); +            if (not buff or buff->size() < sizeof(transaction)) { +                throw uhd::runtime_error("sensor proxy send timeout"); +            } +            std::memcpy( +                buff->cast<void *>(), +                &transaction, +                sizeof(transaction)); +            buff->commit(sizeof(transaction)); +        } +        { +            uhd::transport::managed_recv_buffer::sptr buff +                = _xport->get_recv_buff(1.0); + +            if (not buff or buff->size() < sizeof(transaction)) +                throw uhd::runtime_error("sensor proxy recv timeout"); + +            std::memcpy( +                &transaction, +                buff->cast<const void *>(), +                sizeof(transaction)); +        } +        UHD_ASSERT_THROW(uhd::ntohx<boost::uint32_t>(transaction.which) == GPS_FOUND); +        // TODO: Use proper serialization here ... +        return static_cast<bool>(uhd::ntohx(transaction.value)); +    } + +    uhd::sensor_value_t get_gps_lock(void) +    { +        boost::mutex::scoped_lock(_mutex); +        sensor_transaction_t transaction; +        transaction.which = uhd::htonx<boost::uint32_t>(GPS_LOCK); +        { +            uhd::transport::managed_send_buffer::sptr buff +                = _xport->get_send_buff(1.0); +            if (not buff or buff->size() < sizeof(transaction)) { +                throw uhd::runtime_error("sensor proxy send timeout"); +            } +            std::memcpy( +                buff->cast<void *>(), +                &transaction, +                sizeof(transaction)); +            buff->commit(sizeof(transaction)); +        } +        { +            uhd::transport::managed_recv_buffer::sptr buff +                = _xport->get_recv_buff(1.0); + +            if (not buff or buff->size() < sizeof(transaction)) +                throw uhd::runtime_error("sensor proxy recv timeout"); + +            std::memcpy( +                &transaction, +                buff->cast<const void *>(), +                sizeof(transaction)); +        } +        UHD_ASSERT_THROW(uhd::ntohx<boost::uint32_t>(transaction.which) == GPS_LOCK); +        // TODO: Use proper serialization here ... +        return sensor_value_t("GPS lock status", static_cast<bool>(uhd::ntohx(transaction.value)), "locked", "unlocked"); +    } + +private: +    uhd::transport::zero_copy_if::sptr _xport; +    boost::mutex                       _mutex; +}; + +}}} // namespace + +using namespace uhd::usrp::e300; + +e300_sensor_manager::sptr e300_sensor_manager::make_proxy( +    uhd::transport::zero_copy_if::sptr xport) +{ +    return sptr(new e300_sensor_proxy(xport)); +} + +#ifdef E300_NATIVE +#include "e300_fifo_config.hpp" + +namespace uhd { namespace usrp { namespace e300 { + +static const std::string E300_TEMP_SYSFS = "iio:device0"; + +class e300_sensor_local : public e300_sensor_manager +{ +public: +    e300_sensor_local(uhd::gps_ctrl::sptr gps_ctrl) : _gps_ctrl(gps_ctrl) +    { +    } + +    std::vector<std::string> get_sensors() +    { +        return boost::assign::list_of("temp")("gps_locked")("gps_time"); +    } + +    uhd::sensor_value_t get_sensor(const std::string &key) +    { +        if (key == "temp") +            return get_mb_temp(); +        else if (key == "gps_locked") +            return get_gps_lock(); +        else if (key == "gps_time") +            return get_gps_time(); +        else +            throw uhd::lookup_error( +                str(boost::format("Invalid sensor %s requested.") % key)); +    } + +    uhd::sensor_value_t get_mb_temp(void) +    { +        double scale = boost::lexical_cast<double>( +            e300_get_sysfs_attr(E300_TEMP_SYSFS, "in_temp0_scale")); +        unsigned long raw = boost::lexical_cast<unsigned long>( +            e300_get_sysfs_attr(E300_TEMP_SYSFS, "in_temp0_raw")); +        unsigned long offset = boost::lexical_cast<unsigned long>( +            e300_get_sysfs_attr(E300_TEMP_SYSFS, "in_temp0_offset")); +        return sensor_value_t("temp", (raw + offset) * scale / 1000, "C"); +    } + +    bool get_gps_found(void) +    { +        return _gps_ctrl->gps_detected(); +    } + +    uhd::sensor_value_t get_gps_lock(void) +    { +        return _gps_ctrl->get_sensor("gps_locked"); +    } + +    uhd::sensor_value_t get_gps_time(void) +    { +        return _gps_ctrl->get_sensor("gps_time"); +    } + +private: +    gps_ctrl::sptr _gps_ctrl; +}; +}}} + +using namespace uhd::usrp::e300; +e300_sensor_manager::sptr e300_sensor_manager::make_local( +    uhd::gps_ctrl::sptr gps_ctrl) +{ +    return sptr(new e300_sensor_local(gps_ctrl)); +} + +#else +using namespace uhd::usrp::e300; +e300_sensor_manager::sptr e300_sensor_manager::make_local( +    uhd::gps_ctrl::sptr gps_ctrl) +{ +    throw uhd::assertion_error("e300_sensor_manager::make_local() !E300_NATIVE"); +} +#endif diff --git a/host/lib/usrp/e300/e300_sensor_manager.hpp b/host/lib/usrp/e300/e300_sensor_manager.hpp new file mode 100644 index 000000000..503a7bb63 --- /dev/null +++ b/host/lib/usrp/e300/e300_sensor_manager.hpp @@ -0,0 +1,77 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/noncopyable.hpp> +#include <boost/cstdint.hpp> + +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/usrp/gps_ctrl.hpp> + +#ifndef INCLUDED_E300_SENSOR_MANAGER_HPP +#define INCLUDED_E300_SENSOR_MANAGER_HPP + +namespace uhd { namespace usrp { namespace e300 { + +struct sensor_transaction_t { +    boost::uint32_t which; +    union { +        boost::uint32_t value; +        boost::uint32_t value64; +    }; +}; + + + +enum sensor {ZYNQ_TEMP=0, GPS_FOUND=1, GPS_TIME=2, +             GPS_LOCK=3}; + +class e300_sensor_manager : boost::noncopyable +{ +public: +    typedef boost::shared_ptr<e300_sensor_manager> sptr; +    virtual bool get_gps_found(void) = 0; + +    virtual uhd::sensor_value_t get_sensor(const std::string &key) = 0; +    virtual std::vector<std::string> get_sensors(void) = 0; + +    virtual uhd::sensor_value_t get_mb_temp(void) = 0; +    virtual uhd::sensor_value_t get_gps_lock(void) = 0; +    virtual uhd::sensor_value_t get_gps_time(void) = 0; + +    static sptr make_proxy(uhd::transport::zero_copy_if::sptr xport); +    static sptr make_local(uhd::gps_ctrl::sptr gps_ctrl); + +    // Note: This is a hack +    static boost::uint32_t pack_float_in_uint32_t(const float &v) +    { +        const boost::uint32_t *cast = reinterpret_cast<const uint32_t*>(&v); +        return *cast; +    } + +    static float unpack_float_from_uint32_t(const boost::uint32_t &v) +    { +        const float *cast = reinterpret_cast<const float*>(&v); +        return *cast; +    } +}; + + +}}} // namespace + +#endif // INCLUDED_E300_SENSOR_MANAGER_HPP diff --git a/host/lib/usrp/e300/e300_spi.cpp b/host/lib/usrp/e300/e300_spi.cpp new file mode 100644 index 000000000..9a2daf4a7 --- /dev/null +++ b/host/lib/usrp/e300/e300_spi.cpp @@ -0,0 +1,127 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/config.hpp> +#include <uhd/exception.hpp> +#include "e300_spi.hpp" + +#ifdef E300_NATIVE +#include <boost/thread.hpp> +#include <boost/format.hpp> + +#include <fcntl.h> +#include <sys/ioctl.h> +#include <linux/types.h> +#include <linux/spi/spidev.h> + +namespace uhd { namespace usrp { namespace e300 { + +class spidev_impl : public spi +{ +public: + +    spidev_impl(const std::string &device) +                          : _mode(SPI_CPHA), +                            _speed(2000000), +                            _bits(8), +                            _delay(0) +    { +        int ret; +        _fd = open(device.c_str(), O_RDWR); +        if (_fd < 0) +            throw uhd::runtime_error(str(boost::format("Could not open spidev device %s") % device)); + +        ret = ioctl(_fd, SPI_IOC_WR_MODE, &_mode); +        if (ret == -1) +            throw uhd::runtime_error("Could not set spidev mode"); + +        ret = ioctl(_fd, SPI_IOC_RD_MODE, &_mode); +        if (ret == -1) +            throw uhd::runtime_error("Could not get spidev mode"); + +        ret = ioctl(_fd, SPI_IOC_WR_BITS_PER_WORD, &_bits); +        if (ret == -1) +            throw uhd::runtime_error("Could not set spidev bits per word"); + +        ret = ioctl(_fd, SPI_IOC_RD_BITS_PER_WORD, &_bits); +        if (ret == -1) +            throw uhd::runtime_error("Could not get spidev bits per word"); + +        ret = ioctl(_fd, SPI_IOC_WR_MAX_SPEED_HZ, &_speed); +        if (ret == -1) +            throw uhd::runtime_error("Could not set spidev max speed"); + +        ret = ioctl(_fd, SPI_IOC_RD_MAX_SPEED_HZ, &_speed); +        if (ret == -1) +            throw uhd::runtime_error("Could not get spidev max speed"); +    } + +    virtual ~spidev_impl() +    { +        close(_fd); +    } + +    boost::uint32_t transact_spi(int, const uhd::spi_config_t &, +                                 boost::uint32_t data, size_t num_bits, +                                 bool) +    { +        int ret(0); +        struct spi_ioc_transfer tr; + +        uint8_t *tx_data = reinterpret_cast<uint8_t *>(&data); + + +        UHD_ASSERT_THROW(num_bits == 24); +        uint8_t tx[] = {tx_data[2], tx_data[1], tx_data[0]}; + +        uint8_t rx[3]; +        tr.tx_buf = (unsigned long) &tx[0]; +        tr.rx_buf = (unsigned long) &rx[0]; +        tr.len = num_bits >> 3; +        tr.bits_per_word = _bits; +        tr.speed_hz = _speed; +        tr.delay_usecs = _delay; + +        ret = ioctl(_fd, SPI_IOC_MESSAGE(1), &tr); +        if (ret < 1) +            throw uhd::runtime_error("Could not send spidev message"); + +        return rx[2]; +    } + +private: +    int _fd; +    boost::uint8_t _mode; +    boost::uint32_t _speed; +    boost::uint8_t _bits; +    boost::uint16_t _delay; +}; + +spi::sptr spi::make(const std::string &device) +{ +    return spi::sptr(new spidev_impl(device)); +} +}}}; +#else +namespace uhd { namespace usrp { namespace e300 { + +spi::sptr spi::make(const std::string &) +{ +    throw uhd::assertion_error("spi::make() !E300_NATIVE"); +} +}}}; +#endif //E300_NATIVE diff --git a/host/lib/usrp/e300/e300_spi.hpp b/host/lib/usrp/e300/e300_spi.hpp new file mode 100644 index 000000000..67e990aaa --- /dev/null +++ b/host/lib/usrp/e300/e300_spi.hpp @@ -0,0 +1,34 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_E300_SPI_HPP +#define INCLUDED_E300_SPI_HPP + +#include <uhd/types/serial.hpp> + +namespace uhd { namespace usrp { namespace e300 { + +class spi : public virtual uhd::spi_iface +{ +public: +    typedef boost::shared_ptr<spi> sptr; +    static sptr make(const std::string &device); +}; + +}}}; + +#endif /* INCLUDED_E300_SPI_HPP */ diff --git a/host/lib/usrp/e300/e300_sysfs_hooks.cpp b/host/lib/usrp/e300/e300_sysfs_hooks.cpp new file mode 100644 index 000000000..fdeaf0858 --- /dev/null +++ b/host/lib/usrp/e300/e300_sysfs_hooks.cpp @@ -0,0 +1,121 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifdef E300_NATIVE + +#include <cstdio> +#include <cstdlib> +#include <string> +#include <fcntl.h> +#include <unistd.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> + +#include <libudev.h> + +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> + +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> + +static const std::string E300_AXI_FPGA_SYSFS = "40000000.axi-fpga"; +static const std::string E300_XDEV_SYSFS = "f8007000.ps7-dev-cfg"; + +std::string e300_get_sysfs_attr(const std::string &node, const std::string &attr) +{ +    udev *udev; +    udev_enumerate *enumerate; +    udev_list_entry *devices, *dev_list_entry; +    udev_device *dev; +    std::string retstring; + +    udev = udev_new(); + +    if (!udev) { +        throw uhd::lookup_error("Failed to get udev handle."); +    } + +    enumerate = udev_enumerate_new(udev); +    udev_enumerate_add_match_sysname(enumerate, node.c_str()); +    udev_enumerate_scan_devices(enumerate); +    devices = udev_enumerate_get_list_entry(enumerate); + +    udev_list_entry_foreach(dev_list_entry, devices) +    { +        const char *path; + +        path = udev_list_entry_get_name(dev_list_entry); +        dev = udev_device_new_from_syspath(udev, path); + +       retstring = udev_device_get_sysattr_value(dev, attr.c_str()); +       if (retstring.size()) +           break; +    } + +    udev_enumerate_unref(enumerate); +    udev_unref(udev); + +    return retstring; +} + +static bool e300_fpga_loaded_successfully(void) +{ +    return boost::lexical_cast<bool>(e300_get_sysfs_attr(E300_XDEV_SYSFS, "prog_done")); +} + +#include "e300_fifo_config.hpp" +#include <uhd/exception.hpp> + +e300_fifo_config_t e300_read_sysfs(void) +{ + +    if (not e300_fpga_loaded_successfully()) +    { +        throw uhd::runtime_error("E300 FPGA load failed!"); +    } + +    e300_fifo_config_t config; + +    config.buff_length  = boost::lexical_cast<unsigned long>( +        e300_get_sysfs_attr(E300_AXI_FPGA_SYSFS, "buffer_length")); +    config.ctrl_length = boost::lexical_cast<unsigned long>( +        e300_get_sysfs_attr(E300_AXI_FPGA_SYSFS, "control_length")); +    config.phys_addr = boost::lexical_cast<unsigned long>( +        e300_get_sysfs_attr(E300_AXI_FPGA_SYSFS, "phys_addr")); + +    return config; +} + +#else //E300_NATIVE + +#include "e300_fifo_config.hpp" +#include <uhd/exception.hpp> + +e300_fifo_config_t e300_read_sysfs(void) +{ +    throw uhd::assertion_error("e300_read_sysfs() !E300_NATIVE"); +} + +std::string e300_get_sysfs_attr(const std::string &, const std::string &) +{ +    throw uhd::assertion_error("e300_sysfs_attr() !E300_NATIVE"); +} + +#endif //E300_NATIVE diff --git a/host/lib/usrp/e300/e300_ublox_control.hpp b/host/lib/usrp/e300/e300_ublox_control.hpp new file mode 100644 index 000000000..8705d6c52 --- /dev/null +++ b/host/lib/usrp/e300/e300_ublox_control.hpp @@ -0,0 +1,50 @@ +#ifndef INCLUDED_UHD_USRP_UBLOX_CONTROL_HPP +#define INCLUDED_UHD_USRP_UBLOX_CONTROL_HPP + +#include <boost/cstdint.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/asio.hpp> +#include <uhd/config.hpp> +#include <uhd/usrp/gps_ctrl.hpp> +#include <uhd/types/sensors.hpp> + +#include "e300_async_serial.hpp" + +namespace uhd { namespace usrp { namespace gps { + +namespace ublox { namespace ubx { + +class control : public virtual uhd::gps_ctrl +{ +public: +    typedef boost::shared_ptr<control> sptr; + +    static sptr make(const std::string &node, const size_t baud_rate); + +    virtual void configure_message_rate( +        const boost::uint16_t msg, +        const boost::uint8_t rate) = 0; + +    virtual void configure_antenna( +        const boost::uint16_t flags, +        const boost::uint16_t pins) = 0; + +    virtual void configure_pps( +        const boost::uint32_t interval, +        const boost::uint32_t length, +        const boost::int8_t status, +        const boost::uint8_t time_ref, +        const boost::uint8_t flags, +        const boost::int16_t antenna_delay, +        const boost::int16_t rf_group_delay, +        const boost::int32_t user_delay) = 0; + +    virtual void configure_rates( +        boost::uint16_t meas_rate, +        boost::uint16_t nav_rate, +        boost::uint16_t time_ref) = 0; +}; +}} // namespace ublox::ubx + +}}} // namespace +#endif // INCLUDED_UHD_USRP_UBLOX_CONTROL_HPP diff --git a/host/lib/usrp/e300/e300_ublox_control_impl.cpp b/host/lib/usrp/e300/e300_ublox_control_impl.cpp new file mode 100644 index 000000000..a0ec10271 --- /dev/null +++ b/host/lib/usrp/e300/e300_ublox_control_impl.cpp @@ -0,0 +1,505 @@ +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> +#include <boost/make_shared.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/assign/list_of.hpp> +#include "boost/date_time/posix_time/posix_time.hpp" + +#include <iostream> + + +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> + +#include "e300_ublox_control.hpp" + +#ifdef E300_NATIVE +#include "e300_ublox_control_impl.hpp" + + +namespace uhd { namespace usrp { namespace gps { + +namespace ublox { namespace ubx { + +control_impl::control_impl(const std::string &node, const size_t baud_rate) +{ +    _decode_init(); +    _serial = boost::make_shared<async_serial>(node, baud_rate); +    _serial->set_read_callback(boost::bind(&control_impl::_rx_callback, this, _1, _2)); + +    _detect(); + +    configure_message_rate(MSG_GLL, 0); +    configure_message_rate(MSG_GSV, 0); +    configure_message_rate(MSG_GGA, 0); +    configure_message_rate(MSG_GSA, 0); +    configure_message_rate(MSG_RMC, 0); +    configure_message_rate(MSG_VTG, 0); +    configure_message_rate(MSG_NAV_TIMEUTC, 1); +    configure_message_rate(MSG_NAV_SOL, 1); + +    configure_antenna(0x001b, 0x8251); + +    configure_pps(0xf4240, 0x3d090, 1, 0 /* utc */, 1, 0, 0, 0); + +    _sensors = boost::assign::list_of("gps_locked")("gps_time"); +} + +bool control_impl::gps_detected(void) +{ +    return _detected; +} + +void control_impl::_detect(void) +{ +    _send_message(MSG_MON_VER, NULL, 0); +} + +std::vector<std::string> control_impl::get_sensors(void) +{ +    return _sensors; +} + +uhd::sensor_value_t control_impl::get_sensor(std::string key) +{ +    if (key == "gps_time") { +        return sensor_value_t("GPS epoch time", int(_get_epoch_time()), "seconds"); +    } else if (key == "gps_locked") { +        bool lock; +        _locked.wait_and_see(lock); +        return sensor_value_t("GPS lock status", lock, "locked", "unlocked"); +    } else +        throw uhd::key_error(str(boost::format("sensor %s unknown.") % key)); +} + +std::time_t control_impl::_get_epoch_time(void) +{ +    boost::posix_time::ptime ptime; +    _ptime.wait_and_see(ptime); +    return (ptime - boost::posix_time::from_time_t(0)).total_seconds(); +} + +control_impl::~control_impl(void) +{ +    // turn it all off again +    configure_antenna(0x001a, 0x8251); +    configure_pps(0xf4240, 0x3d090, 1, 1, 0, 0, 0, 0); +} + +void control_impl::_decode_init(void) +{ +    _decode_state = DECODE_SYNC1; +    _rx_ck_a = 0; +    _rx_ck_b = 0; +    _rx_payload_length = 0; +    _rx_payload_index  = 0; +} + +void control_impl::_add_byte_to_checksum(const boost::uint8_t b) +{ +    _rx_ck_a = _rx_ck_a + b; +    _rx_ck_b = _rx_ck_b + _rx_ck_a; +} + +void control_impl::_calc_checksum( +    const boost::uint8_t *buffer, +    const boost::uint16_t length, +    checksum_t &checksum) +{ +    for (size_t i = 0; i < length; i++) +    { +        checksum.ck_a = checksum.ck_a + buffer[i]; +        checksum.ck_b = checksum.ck_b + checksum.ck_a; +    } +} + +void control_impl::configure_rates( +    boost::uint16_t meas_rate, +    boost::uint16_t nav_rate, +    boost::uint16_t time_ref) +{ +    payload_tx_cfg_rate_t cfg_rate; +    cfg_rate.meas_rate = uhd::htowx<boost::uint16_t>(meas_rate); +    cfg_rate.nav_rate = uhd::htowx<boost::uint16_t>(nav_rate); +    cfg_rate.time_ref = uhd::htowx<boost::uint16_t>(time_ref); + +    _send_message( +        MSG_CFG_RATE, +        reinterpret_cast<const uint8_t*>(&cfg_rate), +        sizeof(cfg_rate)); + +    _wait_for_ack(MSG_CFG_RATE, 1.0); +} + +void control_impl::configure_message_rate( +    const boost::uint16_t msg, +    const uint8_t rate) +{ +    payload_tx_cfg_msg_t cfg_msg; +    cfg_msg.msg  = uhd::htowx<boost::uint16_t>(msg); +    cfg_msg.rate[0] = 0;//rate; +    cfg_msg.rate[1] = rate; +    cfg_msg.rate[2] = 0;//rate; +    cfg_msg.rate[3] = 0;//rate; +    cfg_msg.rate[4] = 0;//rate; +    cfg_msg.rate[5] = 0;//rate; +    _send_message( +        MSG_CFG_MSG, +        reinterpret_cast<const uint8_t*>(&cfg_msg), +        sizeof(cfg_msg)); + +    _wait_for_ack(MSG_CFG_MSG, 1.0); +} + +void control_impl::configure_antenna( +    const boost::uint16_t flags, +    const boost::uint16_t pins) +{ +    payload_tx_cfg_ant_t cfg_ant; +    cfg_ant.pins = uhd::htowx<boost::uint16_t>(pins); +    cfg_ant.flags = uhd::htowx<boost::uint16_t>(flags); +    _send_message( +        MSG_CFG_ANT, +        reinterpret_cast<const uint8_t*>(&cfg_ant), +        sizeof(cfg_ant)); +    if (_wait_for_ack(MSG_CFG_ANT, 1.0) < 0) { +        throw uhd::runtime_error("Didn't get an ACK for antenna configuration."); +    } + +} + +void control_impl::configure_pps( +    const boost::uint32_t interval, +    const boost::uint32_t length, +    const boost::int8_t status, +    const boost::uint8_t time_ref, +    const boost::uint8_t flags, +    const boost::int16_t antenna_delay, +    const boost::int16_t rf_group_delay, +    const boost::int32_t user_delay) +{ +    payload_tx_cfg_tp_t cfg_tp; +    cfg_tp.interval = uhd::htowx<boost::uint32_t>(interval); +    cfg_tp.length = uhd::htowx<boost::uint32_t>(length); +    cfg_tp.status = status; +    cfg_tp.time_ref = time_ref; +    cfg_tp.flags = flags; +    cfg_tp.antenna_delay = uhd::htowx<boost::int16_t>(antenna_delay); +    cfg_tp.rf_group_delay = uhd::htowx<boost::int16_t>(rf_group_delay); +    cfg_tp.user_delay = uhd::htowx<boost::int32_t>(user_delay); +    _send_message( +        MSG_CFG_TP, +        reinterpret_cast<const uint8_t*>(&cfg_tp), +        sizeof(cfg_tp)); +    if (_wait_for_ack(MSG_CFG_TP, 1.0) < 0) { +        throw uhd::runtime_error("Didn't get an ACK for PPS configuration."); +    } +} + + +void control_impl::_rx_callback(const char *data, unsigned int len) +{ +    //std::cout << "IN RX CALLBACK" << std::flush << std::endl; +    std::vector<char> v(data, data+len); +    BOOST_FOREACH(const char &c, v) +    { +        _parse_char(c); +    } +} + +void control_impl::_parse_char(const boost::uint8_t b) +{ +    int ret = 0; + +    switch (_decode_state) { + +    // we're expecting the first sync byte +    case DECODE_SYNC1: +        if (b == SYNC1) { // sync1 found goto next step +            _decode_state = DECODE_SYNC2; +        } // else stay around +        break; + +    // we're expecting the second sync byte +    case DECODE_SYNC2: +        if (b == SYNC2) { // sync2 found goto next step +            _decode_state = DECODE_CLASS; +        } else { +            // failed, reset +            _decode_init(); +        } +        break; + +    // we're expecting the class byte +    case DECODE_CLASS: +        _add_byte_to_checksum(b); +        _rx_msg = b; +        _decode_state = DECODE_ID; +        break; + +    // we're expecting the id byte +    case DECODE_ID: +        _add_byte_to_checksum(b); +        _rx_msg |= (b << 8); +        _decode_state = DECODE_LENGTH1; +        break; + +    // we're expecting the first length byte +    case DECODE_LENGTH1: +        _add_byte_to_checksum(b); +        _rx_payload_length = b; +        _decode_state = DECODE_LENGTH2; +        break; + +    // we're expecting the second length byte +    case DECODE_LENGTH2: +        _add_byte_to_checksum(b); +        _rx_payload_length |= (b << 8); +        if(_payload_rx_init()) { +            _decode_init(); // we failed, give up for this one +        } else { +            _decode_state = _rx_payload_length ? +                DECODE_PAYLOAD : DECODE_CHKSUM1; +        } +        break; + +    // we're expecting payload +    case DECODE_PAYLOAD: +        _add_byte_to_checksum(b); +        switch(_rx_msg) { +        default: +            ret = _payload_rx_add(b); +            break; +        }; +        if (ret < 0) { +            // we couldn't deal with the payload, discard the whole thing +            _decode_init(); +        } else if (ret > 0) { +            // payload was complete, let's check the checksum; +            _decode_state = DECODE_CHKSUM1; +        } else { +            // more payload expected, don't move +        } +        ret = 0; +        break; + +    case DECODE_CHKSUM1: +        if (_rx_ck_a != b) { +            // checksum didn't match, barf +            std::cout << boost::format("Failed checksum byte1 %lx != %lx") +                % int(_rx_ck_a) % int(b) << std::endl; +            _decode_init(); +        } else { +            _decode_state = DECODE_CHKSUM2; +        } +        break; + +    case DECODE_CHKSUM2: +        if (_rx_ck_b != b) { +            // checksum didn't match, barf +            std::cout << boost::format("Failed checksum byte2 %lx != %lx") +                % int(_rx_ck_b) % int(b) << std::endl; + +        } else { +            ret = _payload_rx_done(); // payload done +        } +        _decode_init(); +        break; + +    default: +        break; +    }; +} + +int control_impl::_payload_rx_init(void) +{ +    int ret = 0; + +    _rx_state = RXMSG_HANDLE; // by default handle +    switch(_rx_msg) { + +    case MSG_NAV_SOL: +        if (not (_rx_payload_length == sizeof(payload_rx_nav_sol_t))) +            _rx_state = RXMSG_ERROR_LENGTH; +        break; + +    case MSG_NAV_TIMEUTC: +        if (not (_rx_payload_length == sizeof(payload_rx_nav_timeutc_t))) +            _rx_state = RXMSG_ERROR_LENGTH; +        break; + +    case MSG_MON_VER: +        break; // always take this one + +    case MSG_ACK_ACK: +        if (not (_rx_payload_length == sizeof(payload_rx_ack_ack_t))) +            _rx_state = RXMSG_ERROR_LENGTH; +        break; + +    case MSG_ACK_NAK: +        if (not (_rx_payload_length == sizeof(payload_rx_ack_nak_t))) +            _rx_state = RXMSG_ERROR_LENGTH; +        break; + +    default: +        _rx_state = RXMSG_DISABLE; +        break; +    }; + +    switch (_rx_state) { +    case RXMSG_HANDLE: // handle message +    case RXMSG_IGNORE: // ignore message but don't report error +        ret = 0; +        break; +    case RXMSG_DISABLE: // ignore message but don't report error +    case RXMSG_ERROR_LENGTH: // the length doesn't match +        ret = -1; +        break; +    default: // invalid, error +        ret = -1; +        break; +    }; + +    return ret; +} + +int control_impl::_payload_rx_add(const boost::uint8_t b) +{ +    int ret = 0; +    _buf.raw[_rx_payload_index] = b; +    if (++_rx_payload_index >= _rx_payload_length) +        ret = 1; +    return ret; +} + +int control_impl::_payload_rx_done(void) +{ +    int ret = 0; +    if (_rx_state != RXMSG_HANDLE) { +        return 0; +    } + +    switch (_rx_msg) { +    case MSG_MON_VER: +        _detected = true; +        break; + +    case MSG_MON_HW: +        std::cout << "MON-HW" << std::endl; +        break; + +    case MSG_ACK_ACK: +        if ((_ack_state == ACK_WAITING) and (_buf.payload_rx_ack_ack.msg == _ack_waiting_msg)) +            _ack_state = ACK_GOT_ACK; +        break; + +    case MSG_ACK_NAK: +        if ((_ack_state == ACK_WAITING) and (_buf.payload_rx_ack_nak.msg == _ack_waiting_msg)) +            _ack_state = ACK_GOT_NAK; + +        break; + +    case MSG_CFG_ANT: +        break; + +    case MSG_NAV_TIMEUTC: +        _ptime.update(boost::posix_time::ptime( +            boost::gregorian::date( +                boost::gregorian::greg_year(uhd::wtohx<boost::uint16_t>( +                    _buf.payload_rx_nav_timeutc.year)), +                boost::gregorian::greg_month(_buf.payload_rx_nav_timeutc.month), +                boost::gregorian::greg_day(_buf.payload_rx_nav_timeutc.day)), +            (boost::posix_time::hours(_buf.payload_rx_nav_timeutc.hour) +            + boost::posix_time::minutes(_buf.payload_rx_nav_timeutc.min) +            + boost::posix_time::seconds(_buf.payload_rx_nav_timeutc.sec)))); +        break; + +    case MSG_NAV_SOL: +        _locked.update(_buf.payload_rx_nav_sol.gps_fix > 0); +        break; + +    default: +        std::cout << boost::format("Got unknown message %lx , with good checksum [") % int(_rx_msg); +        for(size_t i = 0; i < _rx_payload_length; i++) +            std::cout << boost::format("%lx, ") % int(_buf.raw[i]); +        std::cout << "]"<< std::endl; +        break; +    }; +    return ret; +} + +void control_impl::_send_message( +    const boost::uint16_t msg, +    const boost::uint8_t *payload, +    const boost::uint16_t len) +{ +    header_t header = {SYNC1, SYNC2, msg, len}; +    checksum_t checksum = {0, 0}; + +    // calculate checksums, first header without sync +    // then payload +    _calc_checksum( +        reinterpret_cast<boost::uint8_t*>(&header) + 2, +        sizeof(header) - 2, checksum); +    if (payload) +        _calc_checksum(payload, len, checksum); + +    _serial->write( +        reinterpret_cast<const char*>(&header), +        sizeof(header)); + +    if (payload) +        _serial->write((const char *) payload, len); + +    _serial->write( +        reinterpret_cast<const char*>(&checksum), +        sizeof(checksum)); +} + +int control_impl::_wait_for_ack( +    const boost::uint16_t msg, +    const double timeout) +{ +    int ret = -1; + +    _ack_state = ACK_WAITING; +    _ack_waiting_msg = msg; + +    boost::system_time timeout_time = +        boost::get_system_time() + +        boost::posix_time::milliseconds(timeout * 1000.0); + +    do { +        if(_ack_state == ACK_GOT_ACK) +            return 0; +        else if (_ack_state == ACK_GOT_NAK) { +            return -1; +        } +        boost::this_thread::sleep(boost::posix_time::milliseconds(20)); +    } while (boost::get_system_time() < timeout_time); + +    // we get here ... it's a timeout +    _ack_state = ACK_IDLE; +    return ret; +} + + +}} // namespace ublox::ubx +}}} // namespace + +using namespace uhd::usrp::gps::ublox::ubx; + +control::sptr control::make(const std::string &node, const size_t baud_rate) +{ +    return control::sptr(new control_impl(node, baud_rate)); +} +#else +using namespace uhd::usrp::gps::ublox::ubx; + +control::sptr control::make(const std::string &node, const size_t baud_rate) +{ +    throw uhd::assertion_error("control::sptr::make: !E300_NATIVE"); +} +#endif // E300_NATIVE diff --git a/host/lib/usrp/e300/e300_ublox_control_impl.hpp b/host/lib/usrp/e300/e300_ublox_control_impl.hpp new file mode 100644 index 000000000..a1dcbfe6c --- /dev/null +++ b/host/lib/usrp/e300/e300_ublox_control_impl.hpp @@ -0,0 +1,457 @@ +#ifndef INCLUDED_UHD_USRP_UBLOX_CONTROL_IMPL_HPP +#define INCLUDED_UHD_USRP_UBLOX_CONTROL_IMPL_HPP + +#include <boost/cstdint.hpp> +#include <boost/noncopyable.hpp> +#include <boost/asio.hpp> +#include <uhd/config.hpp> +#include <uhd/usrp/gps_ctrl.hpp> +#include <uhd/types/sensors.hpp> + +#include "e300_async_serial.hpp" + +namespace uhd { namespace usrp { namespace gps { + +namespace ublox { namespace ubx { +// ublox binary sync words +static const boost::uint8_t SYNC1 = 0xB5; +static const boost::uint8_t SYNC2 = 0x62; + +// message classes +static const boost::uint8_t CLASS_NAV  = 0x01; +static const boost::uint8_t CLASS_ACK  = 0x05; +static const boost::uint8_t CLASS_CFG  = 0x06; +static const boost::uint8_t CLASS_MON  = 0x0a; +static const boost::uint8_t CLASS_NMEA = 0xf0; + +// Message IDs +static const boost::uint8_t ID_NAV_POSLLH  = 0x02; +static const boost::uint8_t ID_NAV_SOL     = 0x06; +static const boost::uint8_t ID_NAV_PVT     = 0x07; +static const boost::uint8_t ID_NAV_VELNED  = 0x12; +static const boost::uint8_t ID_NAV_TIMEUTC = 0x21; +static const boost::uint8_t ID_NAV_SVINFO  = 0x30; +static const boost::uint8_t ID_ACK_NAK     = 0x00; +static const boost::uint8_t ID_ACK_ACK     = 0x01; +static const boost::uint8_t ID_CFG_PRT     = 0x00; +static const boost::uint8_t ID_CFG_ANT     = 0x13; +static const boost::uint8_t ID_CFG_TP      = 0x07; +static const boost::uint8_t ID_CFG_MSG     = 0x01; +static const boost::uint8_t ID_CFG_RATE    = 0x08; +static const boost::uint8_t ID_CFG_NAV5    = 0x24; +static const boost::uint8_t ID_MON_VER     = 0x04; +static const boost::uint8_t ID_MON_HW      = 0x09; +static const boost::uint8_t ID_GGA         = 0x00; +static const boost::uint8_t ID_GLL         = 0x01; +static const boost::uint8_t ID_GSA         = 0x02; +static const boost::uint8_t ID_GSV         = 0x03; +static const boost::uint8_t ID_RMC         = 0x04; +static const boost::uint8_t ID_VTG         = 0x05; +static const boost::uint8_t ID_GST         = 0x07; + +// Message Classes & IDs // +static const boost::uint16_t MSG_NAV_POSLLH +    = CLASS_NAV | (ID_NAV_POSLLH << 8); +static const boost::uint16_t MSG_NAV_SOL +    = CLASS_NAV | (ID_NAV_SOL << 8); +static const boost::uint16_t MSG_NAV_PVT +    = CLASS_NAV | (ID_NAV_PVT << 8); +static const boost::uint16_t MSG_NAV_VELNED +    = CLASS_NAV | (ID_NAV_VELNED << 8); +static const boost::uint16_t MSG_NAV_TIMEUTC +    = CLASS_NAV | (ID_NAV_TIMEUTC << 8); +static const boost::uint16_t MSG_NAV_SVINFO +    = CLASS_NAV | (ID_NAV_SVINFO << 8); +static const boost::uint16_t MSG_ACK_NAK +    = CLASS_ACK | (ID_ACK_NAK << 8); +static const boost::uint16_t MSG_ACK_ACK +    = CLASS_ACK | (ID_ACK_ACK << 8); +static const boost::uint16_t MSG_CFG_PRT +    = CLASS_CFG | (ID_CFG_PRT << 8); +static const boost::uint16_t MSG_CFG_ANT +    = CLASS_CFG | (ID_CFG_ANT << 8); +static const boost::uint16_t MSG_CFG_TP +    = CLASS_CFG | (ID_CFG_TP << 8); +static const boost::uint16_t MSG_CFG_MSG +    = CLASS_CFG | (ID_CFG_MSG << 8); +static const boost::uint16_t MSG_CFG_RATE +    = CLASS_CFG | (ID_CFG_RATE << 8); +static const boost::uint16_t MSG_CFG_NAV5 +    = CLASS_CFG | (ID_CFG_NAV5 << 8); +static const boost::uint16_t MSG_MON_HW +    = CLASS_MON | (ID_MON_HW << 8); +static const boost::uint16_t MSG_MON_VER +    = CLASS_MON | (ID_MON_VER << 8); + +// NMEA ones +static const boost::uint16_t MSG_GGA +    = CLASS_NMEA | (ID_GGA << 8); +static const boost::uint16_t MSG_GLL +    = CLASS_NMEA | (ID_GLL << 8); +static const boost::uint16_t MSG_GSA +    = CLASS_NMEA | (ID_GSA << 8); +static const boost::uint16_t MSG_GSV +    = CLASS_NMEA | (ID_GSV << 8); +static const boost::uint16_t MSG_RMC +    = CLASS_NMEA | (ID_RMC << 8); +static const boost::uint16_t MSG_VTG +    = CLASS_NMEA | (ID_VTG << 8); + +// header +struct header_t +{ +    boost::uint8_t sync1; +    boost::uint8_t sync2; +    boost::uint16_t msg; +    boost::uint16_t length; +}; + +// checksum +struct checksum_t +{ +    boost::uint8_t ck_a; +    boost::uint8_t ck_b; +}; + +// rx rx mon-hw (ubx6) +struct payload_rx_mon_hw_t +{ +    boost::uint32_t pin_sel; +    boost::uint32_t pin_bank; +    boost::uint32_t pin_dir; +    boost::uint32_t pin_val; +    boost::uint16_t noise_per_ms; +    boost::uint16_t agc_cnt; +    boost::uint8_t  a_status; +    boost::uint8_t  a_power; +    boost::uint8_t  flags; +    boost::uint8_t  reserved1; +    boost::uint32_t used_mask; +    boost::uint8_t  vp[25]; +    boost::uint8_t  jam_ind; +    boost::uint16_t reserved3; +    boost::uint32_t pin_irq; +    boost::uint32_t pullh; +    boost::uint32_t pulll; +}; + +// rx mon-ver +struct payload_rx_mon_ver_part1_t +{ +    char sw_version[30]; +    char hw_version[10]; +}; + +struct payload_rx_mon_ver_part2_t +{ +    boost::uint8_t extension[30]; +}; + +// rx ack-ack +typedef union { +    boost::uint16_t msg; +    struct { +        boost::uint8_t cls_id; +        boost::uint8_t msg_id; +    }; +} payload_rx_ack_ack_t; + +// rx ack-nak +typedef union { +    boost::uint16_t msg; +    struct { +        boost::uint8_t cls_id; +        boost::uint8_t msg_id; +    }; +} payload_rx_ack_nak_t; + +// tx cfg-prt (uart) +struct payload_tx_cfg_prt_t +{ +    boost::uint8_t  port_id; +    boost::uint8_t  reserved0; +    boost::uint16_t tx_ready; +    boost::uint32_t mode; +    boost::uint32_t baud_rate; +    boost::uint16_t in_proto_mask; +    boost::uint16_t out_proto_mask; +    boost::uint16_t flags; +    boost::uint16_t reserved5; +}; + +// tx cfg-rate +struct payload_tx_cfg_rate_t +{ +    boost::uint16_t meas_rate; +    boost::uint16_t nav_rate; +    boost::uint16_t time_ref; +}; + +// tx cfg-msg +struct payload_tx_cfg_msg_t +{ +    boost::uint16_t msg; +    boost::uint8_t rate[6]; +}; + + +// tx cfg-ant +struct payload_tx_cfg_ant_t +{ +    boost::uint16_t flags; +    boost::uint16_t pins; +}; + +// tx cfg-tp +struct payload_tx_cfg_tp_t +{ +    boost::uint32_t interval; +    boost::uint32_t length; +    boost::int8_t status; +    boost::uint8_t time_ref; +    boost::uint8_t flags; +    boost::uint8_t reserved1; +    boost::int16_t antenna_delay; +    boost::int16_t rf_group_delay; +    boost::int32_t user_delay; +}; + +struct payload_rx_nav_sol_t +{ +    boost::uint32_t i_tow; +    boost::int32_t f_tow; +    boost::int16_t week; +    boost::uint8_t gps_fix; +    boost::uint8_t flags; +    boost::int32_t ecef_x; +    boost::int32_t ecef_y; +    boost::int32_t ecef_z; +    boost::uint32_t p_acc; +    boost::int32_t ecef_vx; +    boost::int32_t ecef_vy; +    boost::int32_t ecef_vz; +    boost::uint32_t s_acc; +    boost::uint16_t p_dop; +    boost::uint8_t reserved1; +    boost::uint8_t num_sv; +    boost::uint32_t reserved2; +}; + +struct payload_rx_nav_timeutc_t +{ +    boost::uint32_t i_tow; +    boost::uint32_t t_acc; +    boost::int32_t nano; +    boost::uint16_t year; +    boost::uint8_t month; +    boost::uint8_t day; +    boost::uint8_t hour; +    boost::uint8_t min; +    boost::uint8_t sec; +    boost::uint8_t valid; +}; + +typedef union { +    payload_rx_mon_hw_t        payload_rx_mon_hw; + +    payload_rx_mon_ver_part1_t payload_rx_mon_ver_part1; +    payload_rx_mon_ver_part2_t payload_rx_mon_ver_part2; + +    payload_rx_ack_ack_t       payload_rx_ack_ack; +    payload_rx_ack_nak_t       payload_rx_ack_nak; + +    payload_tx_cfg_prt_t       payload_tx_cfg_prt; +    payload_tx_cfg_ant_t       payload_tx_cfg_ant; +    payload_tx_cfg_rate_t      payload_tx_cfg_rate; + +    payload_tx_cfg_msg_t       payload_tx_cfg_msg; + +    payload_rx_nav_timeutc_t   payload_rx_nav_timeutc; +    payload_rx_nav_sol_t   payload_rx_nav_sol; +    boost::uint8_t             raw[]; +} buf_t; + + +template <typename T> +class sensor_entry +{ +public: +    sensor_entry() : _seen(false) +    { +    } + +    void update(const T &val) +    { +        boost::mutex::scoped_lock l(_mutex); +        _value = val; +        _seen = false; +        l.unlock(); +        _cond.notify_one(); +    } + +    bool seen() const +    { +        boost::mutex::scoped_lock l(_mutex); +        return _seen; +    } + +    bool try_and_see(T &val) +    { +        boost::mutex::scoped_lock l(_mutex); +        if (_seen) +            return false; + +        val = _value; +        _seen = true; +        return true; +    } + +    void wait_and_see(T &val) +    { +        boost::mutex::scoped_lock l(_mutex); +        while(_seen) +        { +            _cond.wait(l); +            //std::cout << "Already seen ... " << std::endl; +        } +        val = _value; +        _seen = true; +    } + +private: // members +    T                         _value; +    boost::mutex              _mutex; +    boost::condition_variable _cond; +    bool                _seen; +}; + +class control_impl : public control +{ +public: +    control_impl(const std::string &node, const size_t baud_rate); + +    virtual ~control_impl(void); + +    void configure_message_rate( +        const boost::uint16_t msg, +        const boost::uint8_t rate); + +    void configure_antenna( +        const boost::uint16_t flags, +        const boost::uint16_t pins); + +    void configure_pps( +        const boost::uint32_t interval, +        const boost::uint32_t length, +        const boost::int8_t status, +        const boost::uint8_t time_ref, +        const boost::uint8_t flags, +        const boost::int16_t antenna_delay, +        const boost::int16_t rf_group_delay, +        const boost::int32_t user_delay); + +    void configure_rates( +        boost::uint16_t meas_rate, +        boost::uint16_t nav_rate, +        boost::uint16_t time_ref); + +    // gps_ctrl interface +    bool gps_detected(void); +    std::vector<std::string> get_sensors(void); +    uhd::sensor_value_t get_sensor(std::string key); + +private: // types +    enum decoder_state_t { +        DECODE_SYNC1 = 0, +        DECODE_SYNC2, +        DECODE_CLASS, +        DECODE_ID, +        DECODE_LENGTH1, +        DECODE_LENGTH2, +        DECODE_PAYLOAD, +        DECODE_CHKSUM1, +        DECODE_CHKSUM2, +    }; + +    enum rxmsg_state_t { +        RXMSG_IGNORE = 0, +        RXMSG_HANDLE, +        RXMSG_DISABLE, +        RXMSG_ERROR_LENGTH +    }; + +    enum ack_state_t { +        ACK_IDLE = 0, +        ACK_WAITING, +        ACK_GOT_ACK, +        ACK_GOT_NAK +    }; + +private: // methods +    std::time_t _get_epoch_time(void); + +    void _decode_init(void); + +    void _add_byte_to_checksum(const boost::uint8_t b); + +    void _detect(void); + +    void _send_message( +        const boost::uint16_t msg, +        const boost::uint8_t *payload, +        const boost::uint16_t len); + +    int _wait_for_ack( +        const boost::uint16_t msg, +        const double timeout); + +    void _calc_checksum( +        const boost::uint8_t *buffer, +        const boost::uint16_t length, +        checksum_t &checksum); + +    void _rx_callback(const char *data, unsigned len); + +    void _parse_char(const boost::uint8_t b); + +    int _payload_rx_init(void); + +    int _payload_rx_add(const boost::uint8_t b); + +    int _payload_rx_done(void); + +private: // members +    // gps_ctrl stuff +    bool                                   _detected; +    std::vector<std::string>               _sensors; + +    sensor_entry<bool>                     _locked; +    sensor_entry<boost::posix_time::ptime> _ptime; + +    // decoder state +    decoder_state_t                        _decode_state; +    rxmsg_state_t                          _rxmsg_state; + +    ack_state_t                            _ack_state; +    boost::uint16_t                        _ack_waiting_msg; + +    boost::uint8_t                         _rx_ck_a; +    boost::uint8_t                         _rx_ck_b; + +    boost::uint16_t                        _rx_payload_length; +    size_t                                 _rx_payload_index; +    boost::uint16_t                        _rx_msg; + +    rxmsg_state_t                          _rx_state; + +    boost::shared_ptr<async_serial>        _serial; + +    // this has to be at the end of the +    // class to be valid C++ +    buf_t                                  _buf; +}; + +}} // namespace ublox::ubx + +}}} // namespace +#endif // INCLUDED_UHD_USRP_UBLOX_CONTROL_IMPL_HPP diff --git a/host/utils/CMakeLists.txt b/host/utils/CMakeLists.txt index 11bb2488a..f693ee7a6 100644 --- a/host/utils/CMakeLists.txt +++ b/host/utils/CMakeLists.txt @@ -35,6 +35,13 @@ SET(x3xx_burner_sources      cdecode.c  ) +find_package(UDev) +IF(ENABLE_E300) +    IF(UDEV_FOUND) +        LIST(APPEND util_runtime_sources usrp_e3x0_network_mode.cpp) +    ENDIF(UDEV_FOUND) +ENDIF(ENABLE_E300) +  #for each source: build an executable and install  FOREACH(util_source ${util_runtime_sources})      GET_FILENAME_COMPONENT(util_name ${util_source} NAME_WE) diff --git a/host/utils/query_gpsdo_sensors.cpp b/host/utils/query_gpsdo_sensors.cpp index 9a1556650..05f918eb4 100644 --- a/host/utils/query_gpsdo_sensors.cpp +++ b/host/utils/query_gpsdo_sensors.cpp @@ -107,15 +107,20 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){    const time_t pc_clock_time = time(NULL);    const uhd::time_spec_t last_pps_time = usrp->get_time_last_pps();    if (last_pps_time.to_ticks(1.0) == gps_time.to_int()) { -    std::cout << boost::format("GPS and UHD Device time are aligned.\n"); -  } else -    std::cout << boost::format("\nGPS and UHD Device time are NOT aligned. Try re-running the program. Double check 1 PPS connection from GPSDO.\n\n"); -     +      std::cout << boost::format("GPS and UHD Device time are aligned.\n"); +  } else { +      std::cout << boost::format("\nGPS and UHD Device time are NOT aligned last_pps: %ld vs gps: %ld. Try re-running the program. Double check 1 PPS connection from GPSDO.\n\n") % last_pps_time.to_ticks(1.0) % gps_time.to_int() << std::endl; +  } +    //print NMEA strings -  std::cout << boost::format("Printing available NMEA strings:\n"); -  uhd::sensor_value_t gga_string = usrp->get_mboard_sensor("gps_gpgga"); -  uhd::sensor_value_t rmc_string = usrp->get_mboard_sensor("gps_gprmc"); -  std::cout << boost::format("%s\n%s\n%s\n") % gga_string.to_pp_string() % rmc_string.to_pp_string() % gps_time.to_pp_string(); +  try { +      uhd::sensor_value_t gga_string = usrp->get_mboard_sensor("gps_gpgga"); +      uhd::sensor_value_t rmc_string = usrp->get_mboard_sensor("gps_gprmc"); +      std::cout << boost::format("Printing available NMEA strings:\n"); +      std::cout << boost::format("%s\n%s\n%s\n") % gga_string.to_pp_string() % rmc_string.to_pp_string() % gps_time.to_pp_string(); +  } catch (std::exception &e) { +      std::cout << "NMEA strings not implemented for this device." << std::endl; +  }    std::cout << boost::format("UHD Device time: %.0f seconds\n") % (last_pps_time.get_real_secs());    std::cout << boost::format("PC Clock time: %.0f seconds\n") % pc_clock_time; diff --git a/host/utils/usrp_e3x0_network_mode.cpp b/host/utils/usrp_e3x0_network_mode.cpp new file mode 100644 index 000000000..dae4b6ff7 --- /dev/null +++ b/host/utils/usrp_e3x0_network_mode.cpp @@ -0,0 +1,80 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program 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 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "../lib/usrp/e300/e300_network.hpp" +#include <uhd/device.hpp> +#include <uhd/exception.hpp> + +#include <uhd/utils/msg.hpp> +#include <uhd/transport/if_addrs.hpp> + +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/asio.hpp> + +#include <iostream> + +namespace po = boost::program_options; + +static void check_network_ok(void) +{ +    using namespace uhd::transport; +    using namespace boost::asio::ip; +    std::vector<if_addrs_t> addrs = get_if_addrs(); + +    if(addrs.size() == 1 and addrs.at(0).inet == address_v4::loopback().to_string()) +        throw uhd::runtime_error( +            "No network address except for loopback found.\n" +            "Make sure your DHCP server is working or configure a static IP"); +} + +int main(int argc, char *argv[]) +{ +    po::options_description desc("Allowed options"); +    desc.add_options() +        ("help", "help message") +        ("fpga", po::value<std::string>(), "fpga image to load") +    ; + +    po::variables_map vm; +    po::store(po::parse_command_line(argc, argv, desc), vm); +    po::notify(vm); + +    //print the help message +    if (vm.count("help")){ +        std::cout << boost::format("UHD E3x0 Network Mode %s") % desc << std::endl; +        return EXIT_FAILURE; +    } +    uhd::device_addr_t args; +    if(vm.count("fpga")) { +        args["fpga"] = vm["fpga"].as<std::string>(); +    } + +    try { +        check_network_ok(); +        uhd::usrp::e300::network_server::sptr server = uhd::usrp::e300::network_server::make(args); +        server->run(); +    } catch (uhd::assertion_error &e) { +        UHD_MSG(error) << "This executable is supposed to run on the device, not on the host." << std::endl +                       << "Please refer to the manual section on operating your e3x0 device in network mode." << std::endl; +        return EXIT_FAILURE; +    } catch (uhd::runtime_error &e) { +        UHD_MSG(error) << e.what() << std::endl; +        return EXIT_FAILURE; +    } +    return EXIT_SUCCESS; +}  | 
