From 1b149f561370687ad65e3aa644a402f00dbd16ea Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Tue, 7 Oct 2014 11:32:14 +0200 Subject: Initial commit E300 support. --- firmware/e300/rev_b/Makefile | 76 ++ firmware/e300/rev_b/PMC.atsln | 29 + firmware/e300/rev_b/PMC.cproj | 288 +++++ firmware/e300/rev_b/bq24190.c | 335 ++++++ firmware/e300/rev_b/bq24190.h | 26 + firmware/e300/rev_b/config.h | 2 + firmware/e300/rev_b/debug.c | 297 ++++++ firmware/e300/rev_b/debug.h | 90 ++ firmware/e300/rev_b/error.h | 31 + firmware/e300/rev_b/global.h | 49 + firmware/e300/rev_b/i2c.c | 518 +++++++++ firmware/e300/rev_b/i2c.h | 17 + firmware/e300/rev_b/io.c | 75 ++ firmware/e300/rev_b/io.h | 31 + firmware/e300/rev_b/ltc3675.c | 525 +++++++++ firmware/e300/rev_b/ltc3675.h | 37 + firmware/e300/rev_b/ltc4155.c | 402 +++++++ firmware/e300/rev_b/ltc4155.h | 25 + firmware/e300/rev_b/main.c | 385 +++++++ firmware/e300/rev_b/power.c | 909 ++++++++++++++++ firmware/e300/rev_b/power.h | 58 + firmware/e300/rev_c/Makefile | 76 ++ firmware/e300/rev_c/PMC.atsln | 29 + firmware/e300/rev_c/PMC.cproj | 288 +++++ firmware/e300/rev_c/bq24190.c | 335 ++++++ firmware/e300/rev_c/bq24190.h | 26 + firmware/e300/rev_c/config.h | 2 + firmware/e300/rev_c/debug.c | 297 ++++++ firmware/e300/rev_c/debug.h | 90 ++ firmware/e300/rev_c/error.h | 31 + firmware/e300/rev_c/global.h | 49 + firmware/e300/rev_c/i2c.c | 518 +++++++++ firmware/e300/rev_c/i2c.h | 17 + firmware/e300/rev_c/io.c | 75 ++ firmware/e300/rev_c/io.h | 31 + firmware/e300/rev_c/ltc3675.c | 525 +++++++++ firmware/e300/rev_c/ltc3675.h | 37 + firmware/e300/rev_c/ltc4155.c | 402 +++++++ firmware/e300/rev_c/ltc4155.h | 25 + firmware/e300/rev_c/main.c | 385 +++++++ firmware/e300/rev_c/power.c | 900 ++++++++++++++++ firmware/e300/rev_c/power.h | 58 + host/docs/mainpage.dox | 1 + host/docs/usrp_e3x0.dox | 302 ++++++ host/lib/convert/CMakeLists.txt | 3 + host/lib/convert/convert_neon.S | 37 + host/lib/convert/convert_with_neon.cpp | 36 +- host/lib/usrp/CMakeLists.txt | 1 + host/lib/usrp/dboard/CMakeLists.txt | 1 + host/lib/usrp/dboard/db_e3x0.cpp | 64 ++ host/lib/usrp/e300/CMakeLists.txt | 55 + host/lib/usrp/e300/e300_async_serial.cpp | 245 +++++ host/lib/usrp/e300/e300_async_serial.hpp | 113 ++ host/lib/usrp/e300/e300_common.cpp | 59 ++ host/lib/usrp/e300/e300_common.hpp | 31 + host/lib/usrp/e300/e300_defaults.hpp | 82 ++ host/lib/usrp/e300/e300_eeprom_manager.cpp | 236 +++++ host/lib/usrp/e300/e300_eeprom_manager.hpp | 125 +++ host/lib/usrp/e300/e300_fifo_config.cpp | 429 ++++++++ host/lib/usrp/e300/e300_fifo_config.hpp | 52 + host/lib/usrp/e300/e300_fpga_defs.hpp | 29 + host/lib/usrp/e300/e300_global_regs.cpp | 131 +++ host/lib/usrp/e300/e300_global_regs.hpp | 78 ++ host/lib/usrp/e300/e300_i2c.cpp | 409 +++++++ host/lib/usrp/e300/e300_i2c.hpp | 77 ++ host/lib/usrp/e300/e300_impl.cpp | 1347 ++++++++++++++++++++++++ host/lib/usrp/e300/e300_impl.hpp | 296 ++++++ host/lib/usrp/e300/e300_io_impl.cpp | 599 +++++++++++ host/lib/usrp/e300/e300_network.cpp | 642 +++++++++++ host/lib/usrp/e300/e300_network.hpp | 43 + host/lib/usrp/e300/e300_regs.hpp | 69 ++ host/lib/usrp/e300/e300_remote_codec_ctrl.cpp | 148 +++ host/lib/usrp/e300/e300_remote_codec_ctrl.hpp | 59 ++ host/lib/usrp/e300/e300_sensor_manager.cpp | 289 +++++ host/lib/usrp/e300/e300_sensor_manager.hpp | 77 ++ host/lib/usrp/e300/e300_spi.cpp | 127 +++ host/lib/usrp/e300/e300_spi.hpp | 34 + host/lib/usrp/e300/e300_sysfs_hooks.cpp | 121 +++ host/lib/usrp/e300/e300_ublox_control.hpp | 50 + host/lib/usrp/e300/e300_ublox_control_impl.cpp | 505 +++++++++ host/lib/usrp/e300/e300_ublox_control_impl.hpp | 457 ++++++++ host/utils/CMakeLists.txt | 7 + host/utils/query_gpsdo_sensors.cpp | 21 +- host/utils/usrp_e3x0_network_mode.cpp | 80 ++ 84 files changed, 15959 insertions(+), 9 deletions(-) create mode 100644 firmware/e300/rev_b/Makefile create mode 100644 firmware/e300/rev_b/PMC.atsln create mode 100644 firmware/e300/rev_b/PMC.cproj create mode 100644 firmware/e300/rev_b/bq24190.c create mode 100644 firmware/e300/rev_b/bq24190.h create mode 100644 firmware/e300/rev_b/config.h create mode 100644 firmware/e300/rev_b/debug.c create mode 100644 firmware/e300/rev_b/debug.h create mode 100644 firmware/e300/rev_b/error.h create mode 100644 firmware/e300/rev_b/global.h create mode 100644 firmware/e300/rev_b/i2c.c create mode 100644 firmware/e300/rev_b/i2c.h create mode 100644 firmware/e300/rev_b/io.c create mode 100644 firmware/e300/rev_b/io.h create mode 100644 firmware/e300/rev_b/ltc3675.c create mode 100644 firmware/e300/rev_b/ltc3675.h create mode 100644 firmware/e300/rev_b/ltc4155.c create mode 100644 firmware/e300/rev_b/ltc4155.h create mode 100644 firmware/e300/rev_b/main.c create mode 100644 firmware/e300/rev_b/power.c create mode 100644 firmware/e300/rev_b/power.h create mode 100644 firmware/e300/rev_c/Makefile create mode 100644 firmware/e300/rev_c/PMC.atsln create mode 100644 firmware/e300/rev_c/PMC.cproj create mode 100644 firmware/e300/rev_c/bq24190.c create mode 100644 firmware/e300/rev_c/bq24190.h create mode 100644 firmware/e300/rev_c/config.h create mode 100644 firmware/e300/rev_c/debug.c create mode 100644 firmware/e300/rev_c/debug.h create mode 100644 firmware/e300/rev_c/error.h create mode 100644 firmware/e300/rev_c/global.h create mode 100644 firmware/e300/rev_c/i2c.c create mode 100644 firmware/e300/rev_c/i2c.h create mode 100644 firmware/e300/rev_c/io.c create mode 100644 firmware/e300/rev_c/io.h create mode 100644 firmware/e300/rev_c/ltc3675.c create mode 100644 firmware/e300/rev_c/ltc3675.h create mode 100644 firmware/e300/rev_c/ltc4155.c create mode 100644 firmware/e300/rev_c/ltc4155.h create mode 100644 firmware/e300/rev_c/main.c create mode 100644 firmware/e300/rev_c/power.c create mode 100644 firmware/e300/rev_c/power.h create mode 100644 host/docs/usrp_e3x0.dox create mode 100644 host/lib/convert/convert_neon.S create mode 100644 host/lib/usrp/dboard/db_e3x0.cpp create mode 100644 host/lib/usrp/e300/CMakeLists.txt create mode 100644 host/lib/usrp/e300/e300_async_serial.cpp create mode 100644 host/lib/usrp/e300/e300_async_serial.hpp create mode 100644 host/lib/usrp/e300/e300_common.cpp create mode 100644 host/lib/usrp/e300/e300_common.hpp create mode 100644 host/lib/usrp/e300/e300_defaults.hpp create mode 100644 host/lib/usrp/e300/e300_eeprom_manager.cpp create mode 100644 host/lib/usrp/e300/e300_eeprom_manager.hpp create mode 100644 host/lib/usrp/e300/e300_fifo_config.cpp create mode 100644 host/lib/usrp/e300/e300_fifo_config.hpp create mode 100644 host/lib/usrp/e300/e300_fpga_defs.hpp create mode 100644 host/lib/usrp/e300/e300_global_regs.cpp create mode 100644 host/lib/usrp/e300/e300_global_regs.hpp create mode 100644 host/lib/usrp/e300/e300_i2c.cpp create mode 100644 host/lib/usrp/e300/e300_i2c.hpp create mode 100644 host/lib/usrp/e300/e300_impl.cpp create mode 100644 host/lib/usrp/e300/e300_impl.hpp create mode 100644 host/lib/usrp/e300/e300_io_impl.cpp create mode 100644 host/lib/usrp/e300/e300_network.cpp create mode 100644 host/lib/usrp/e300/e300_network.hpp create mode 100644 host/lib/usrp/e300/e300_regs.hpp create mode 100644 host/lib/usrp/e300/e300_remote_codec_ctrl.cpp create mode 100644 host/lib/usrp/e300/e300_remote_codec_ctrl.hpp create mode 100644 host/lib/usrp/e300/e300_sensor_manager.cpp create mode 100644 host/lib/usrp/e300/e300_sensor_manager.hpp create mode 100644 host/lib/usrp/e300/e300_spi.cpp create mode 100644 host/lib/usrp/e300/e300_spi.hpp create mode 100644 host/lib/usrp/e300/e300_sysfs_hooks.cpp create mode 100644 host/lib/usrp/e300/e300_ublox_control.hpp create mode 100644 host/lib/usrp/e300/e300_ublox_control_impl.cpp create mode 100644 host/lib/usrp/e300/e300_ublox_control_impl.hpp create mode 100644 host/utils/usrp_e3x0_network_mode.cpp 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 @@ + + + + 2.0 + 6.0 + com.Atmel.AVRGCC8 + {a379d421-4236-44af-a711-52b7bda29919} + ATtiny88 + none + Executable + C + $(MSBuildProjectName) + .elf + $(MSBuildProjectDirectory)\$(Configuration) + PMC + PMC + PMC + Native + true + false + + 0 + 3.1.3 + com.atmel.avrdbg.tool.stk600 + ISP + + com.atmel.avrdbg.tool.stk600 + STK600 + 004A8D68669B + true + false + + + + 127.0.0.1 + 49172 + False + + + ISP + + 249000 + 1000000 + 25000 + false + false + 0 + 0 + 0 + 0 + + + + + com.atmel.avrdbg.tool.ispmk2 + AVRISP mkII + 000200136505 + true + false + + + + 127.0.0.1 + 49228 + False + + + ISP + + 249000 + 1000000 + 8000 + false + false + 0 + 0 + 0 + 0 + + + + + + + + True + True + True + True + True + + + I2C_REWORK + ENABLE_SERIAL + + + Optimize for size (-Os) + True + True + True + True + + + m + + + + + + + + + True + True + True + True + True + + + ATTINY88_DIP + DEBUG + I2C_REWORK + + + Optimize (-O1) + True + True + Default (-g2) + True + True + + + m + + + Default (-Wa,-g) + + + + + + + True + True + True + True + True + + + I2C_REWORK + ENABLE_SERIAL + DDR3L + + + Optimize for size (-Os) + True + True + True + True + + + m + + + + + bin\Release (DDR3)\ + + + + + True + True + True + True + True + + + I2C_REWORK + ENABLE_SERIAL + ATTINY88_DIP + + + Optimize for size (-Os) + True + True + True + True + True + + + m + + + True + + + bin\Release (Dev)\ + + + + + True + True + True + True + True + + + I2C_REWORK_disabled + DDR3L_disabled_revB_R_FB_network_changed + CHARGER_TI + ENABLE_SERIAL_disabled + LED_POLARITY + + + Optimize for size (-Os) + True + True + True + True + + + m + + + + + bin\Release (DDR3L, Ti)\ + + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + + \ 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 + +#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 +#include + +#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 +#include +#include + +#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 +#include +#include + +#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 +#include +#include + +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 + +#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/ 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 + +#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 +#include + +#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 +#include +#include + +#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: | + + 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 +#include + +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 + +#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 +#include + +#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 +#include +#include +#include +#include +#include +#include + +#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 _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 +#include +#include +#include +#include + +#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 +#include + +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 @@ + + + + 2.0 + 6.0 + com.Atmel.AVRGCC8 + {a379d421-4236-44af-a711-52b7bda29919} + ATtiny88 + none + Executable + C + $(MSBuildProjectName) + .elf + $(MSBuildProjectDirectory)\$(Configuration) + PMC + PMC + PMC + Native + true + false + + 0 + 3.1.3 + com.atmel.avrdbg.tool.stk600 + ISP + + com.atmel.avrdbg.tool.stk600 + STK600 + 004A8D68669B + true + false + + + + 127.0.0.1 + 49172 + False + + + ISP + + 249000 + 1000000 + 25000 + false + false + 0 + 0 + 0 + 0 + + + + + com.atmel.avrdbg.tool.ispmk2 + AVRISP mkII + 000200136505 + true + false + + + + 127.0.0.1 + 49228 + False + + + ISP + + 249000 + 1000000 + 8000 + false + false + 0 + 0 + 0 + 0 + + + + + + + + True + True + True + True + True + + + I2C_REWORK + ENABLE_SERIAL + + + Optimize for size (-Os) + True + True + True + True + + + m + + + + + + + + + True + True + True + True + True + + + ATTINY88_DIP + DEBUG + I2C_REWORK + + + Optimize (-O1) + True + True + Default (-g2) + True + True + + + m + + + Default (-Wa,-g) + + + + + + + True + True + True + True + True + + + I2C_REWORK + ENABLE_SERIAL + DDR3L + + + Optimize for size (-Os) + True + True + True + True + + + m + + + + + bin\Release (DDR3)\ + + + + + True + True + True + True + True + + + I2C_REWORK + ENABLE_SERIAL + ATTINY88_DIP + + + Optimize for size (-Os) + True + True + True + True + True + + + m + + + True + + + bin\Release (Dev)\ + + + + + True + True + True + True + True + + + I2C_REWORK_disabled + DDR3L_disabled_revB_R_FB_network_changed + CHARGER_TI + ENABLE_SERIAL_disabled + LED_POLARITY + + + Optimize for size (-Os) + True + True + True + True + + + m + + + + + bin\Release (DDR3L, Ti)\ + + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + + \ 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 + +#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 +#include + +#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 +#include +#include + +#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 +#include +#include + +#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 +#include +#include + +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 + +#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/ 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 + +#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 +#include + +#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 +#include +#include + +#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: | + + 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 +#include + +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 + +#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 +#include + +#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 +#include +#include +#include +#include +#include +#include + +#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 _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 +#include +#include +#include +#include + +#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 +#include + +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 Xilinx Programming Tools (ISE, iMPACT) 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: + + /impact_jtag_programmer.sh --fpga-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 \\. +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: + +- `` are device address arguments (optional if only one USRP device is on your machine) + + cd /lib/uhd/examples + ./test_pps_input --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 . +// + + .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 #include +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(input+i, output+i, nsamps-i, scale_factor); } + +DECLARE_CONVERTER(sc16, 1, sc16_item32_le, 1, PRIORITY_SIMD){ + const sc16_t *input = reinterpret_cast(inputs[0]); + item32_t *output = reinterpret_cast(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(input+i, output+i, nsamps-i, scale_factor); +} + +DECLARE_CONVERTER(sc16_item32_le, 1, sc16, 1, PRIORITY_SIMD){ + const item32_t *input = reinterpret_cast(inputs[0]); + sc16_t *output = reinterpret_cast(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(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 . +// + +#include +#include +#include + +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 . +# + +######################################################################## +# 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 . +// + +#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 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 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 &data) +{ + { + boost::lock_guard 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 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 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 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 l(_error_mutex); + _error = e; +} + + +void async_serial::set_read_callback( + const boost::function &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 . +// + +#ifndef INCLUDED_ASYNC_SERIAL_HPP +#define INCLUDED_ASYNC_SERIAL_HPP + +#include +#include +#include +#include +#include +#include + +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 &data); + + void write_string(const std::string &s); + + static const size_t READ_BUFFER_SIZE=512; + + void set_read_callback( + const boost::function& 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 _write_queue; + boost::shared_array _write_buffer; + size_t _write_buffer_size; + boost::mutex _write_queue_mutex; + char _read_buffer[READ_BUFFER_SIZE]; + + boost::function _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 . +// +#include + +#include "e300_fifo_config.hpp" +#include "e300_fifo_config.hpp" + +#include "e300_common.hpp" + +#include +#include + +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 . +// + +#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 . +// + +#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 . +// + +#include "e300_eeprom_manager.hpp" +#include +#include + +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 bytes; + bytes.resize(sizeof(mb_eeprom_map_t)); + mb_eeprom_map_t *map_ptr = reinterpret_cast(&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( + uhd::ntohx(map.hw_product)); + _mb_eeprom["revision"] = boost::lexical_cast( + uhd::ntohx(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 bytes; + bytes.resize(sizeof(db_eeprom_map_t)); + db_eeprom_map_t *map_ptr = reinterpret_cast(&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(map.hw_product)); + + _db_eeprom.revision = boost::lexical_cast( + uhd::ntohx(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 bytes; + bytes.resize(sizeof(db_eeprom_map_t)); + + + db_eeprom_map_t *map_ptr = reinterpret_cast(&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( + _db_eeprom.id.to_uint16()); + } + + if (not _db_eeprom.revision.empty()) { + map.hw_revision = uhd::htonx( + boost::lexical_cast(_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 bytes; + bytes.resize(sizeof(mb_eeprom_map_t)); + + + mb_eeprom_map_t *map_ptr = reinterpret_cast(&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::lexical_cast(_mb_eeprom["product"])); + } + if (_mb_eeprom.has_key("revision")) { + map.hw_revision = uhd::htonx( + boost::lexical_cast(_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( + _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( + _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 . +// + +#ifndef INCLUDED_E300_EEPROM_MANAGER_HPP +#define INCLUDED_E300_EEPROM_MANAGER_HPP + +#include +#include +#include +#include +#include + +#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 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 . +// + +#ifdef E300_NATIVE + +#include +#include + +// 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 //mmap +#include //open, close +#include //poll +#include +#include +#include +#include //sleep +#include //timeout +#include +#include + +//locking stuff for shared irq +#include +#include + +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(addr); + *p = data; +} + +UHD_INLINE boost::uint32_t zf_peek32(const boost::uint32_t addr) +{ + volatile const boost::uint32_t *p = reinterpret_cast(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 + UHD_INLINE typename T::sptr get_new(void) + { + return make(reinterpret_cast(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 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 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(); //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 + 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(); + } + _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(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(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 _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 > _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(S2H_NUM_STREAMS, 0)), + _send_entries_in_use(std::vector(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 _recv_entries_in_use; + std::vector _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 + +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 . +// + +#ifndef INCLUDED_E300_FIFO_CONFIG_HPP +#define INCLUDED_E300_FIFO_CONFIG_HPP + +#include +#include +#include +#include + +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 +{ + typedef boost::shared_ptr 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 . +// + +#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 . +// + +#include "e300_global_regs.hpp" + +#include +#include +#include +#include +#include + +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(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(addr); + *p = data; + } + + UHD_INLINE boost::uint32_t _peek32(const boost::uint32_t addr) + { + volatile const boost::uint32_t *p = reinterpret_cast(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(0); + transaction.addr = uhd::htonx( + static_cast(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(), &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(), sizeof(transaction)); + } + return uhd::ntohx(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(1); + transaction.addr = uhd::htonx( + static_cast(addr)); + transaction.data = uhd::htonx(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(), &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 . +// + +#ifndef INCLUDED_E300_GLOBAL_REGS_HPP +#define INCLUDED_E300_GLOBAL_REGS_HPP + +#include +#include + +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 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 . +// + +#include +#include +#include + + +#include "e300_i2c.hpp" +#include + +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(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(), &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(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(), &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(), 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(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(), &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(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(), &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(), 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(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(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(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(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(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(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 +#include +#include +#include +#include + +#include +#include + +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 . +// + +#ifndef INCLUDED_E300_I2C_HPP +#define INCLUDED_E300_I2C_HPP + +#include +#include +#include + +#include + +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 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 . +// + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include //sleep +#include +#include + +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 discover_ip_addrs( + const std::string& addr_hint, const std::string& port) +{ + std::vector 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(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 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( + 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(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(); + _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(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("/name").set("E-Series Device"); + const fs_path mb_path = "/mboards/0"; + _tree->create(mb_path / "name") + .set(_eeprom_manager->get_mb_type_string()); + + _tree->create(mb_path / "codename").set("Troll"); + + _tree->create(mb_path / "fpga_version").set( + str(boost::format("%u.%u") + % _get_version(FPGA_MAJOR) + % _get_version(FPGA_MINOR))); + + _tree->create(mb_path / "fpga_version_hash").set( + _get_version_hash()); + + //////////////////////////////////////////////////////////////////// + // and do the misc mboard sensors + //////////////////////////////////////////////////////////////////// + _tree->create(mb_path / "sensors"); + BOOST_FOREACH(const std::string &name, _sensor_manager->get_sensors()) + { + _tree->create(mb_path / "sensors" / name) + .publish(boost::bind(&e300_sensor_manager::get_sensor, _sensor_manager, name)); + } + + //////////////////////////////////////////////////////////////////// + // setup the mboard eeprom + //////////////////////////////////////////////////////////////////// + _tree->create(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(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 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(mb_path / "gpio" / "INT0" / attr) + .subscribe(boost::bind(&e300_impl::_set_internal_gpio, this, fp_gpio, attr, _1)) + .set(0); + } + _tree->create(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(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(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(mb_path / "time_source" / "value") + .subscribe(boost::bind(&e300_impl::_update_time_source, this, _1)); + static const std::vector time_sources = boost::assign::list_of("none")("external")("gpsdo"); + _tree->create >(mb_path / "time_source" / "options").set(time_sources); + //setup reference source props + _tree->create(mb_path / "clock_source" / "value") + .subscribe(boost::bind(&e300_impl::_update_clock_source, this, _1)); + static const std::vector clock_sources = boost::assign::list_of("internal"); + // not implemented ("external")("gpsdo"); + _tree->create >(mb_path / "clock_source" / "options").set(clock_sources); + + //////////////////////////////////////////////////////////////////// + // dboard eeproms but not really + //////////////////////////////////////////////////////////////////// + dboard_eeprom_t db_eeprom; + _tree->create(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(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(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(codec_path / "name").set("E3x0 RX dual ADC"); + _tree->create(codec_path / "gains"); //empty cuz gains are in frontend + } + { + const fs_path codec_path = mb_path / ("tx_codecs") / "A"; + _tree->create(codec_path / "name").set("E3x0 TX dual DAC"); + _tree->create(codec_path / "gains"); //empty cuz gains are in frontend + } + + //////////////////////////////////////////////////////////////////// + // create frontend mapping + //////////////////////////////////////////////////////////////////// + + std::vector 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 >(mb_path / "rx_chan_dsp_mapping").set(default_map); + _tree->create >(mb_path / "tx_chan_dsp_mapping").set(default_map); + + _tree->create(mb_path / "rx_subdev_spec") + .set(subdev_spec_t()) + .subscribe(boost::bind(&e300_impl::_update_subdev_spec, this, "rx", _1)); + _tree->create(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(mb_path / "tick_rate").set( + device_addr.cast("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(mb_path / "rx_subdev_spec").set(rx_spec); + _tree->access(mb_path / "tx_subdev_spec").set(tx_spec); + + if (_sensor_manager->get_gps_found()) { + _tree->access(mb_path / "clock_source" / "value").set("gpsdo"); + _tree->access(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(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(mb_path / "time" / "pps").get()).get_full_secs()) + break; + } + } else { + // init to default time and clock source + _tree->access(mb_path / "clock_source" / "value").set( + e300::DEFAULT_CLOCK_SRC); + _tree->access(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(E300_SERVER_CTRL_PORT0); + else if (prefix == E300_RADIO_DEST_PREFIX_TX) + return boost::lexical_cast(E300_SERVER_TX_PORT0); + else if (prefix == E300_RADIO_DEST_PREFIX_RX) + return boost::lexical_cast(E300_SERVER_RX_PORT0); + } else if (destination == E300_XB_DST_R1) { + if (prefix == E300_RADIO_DEST_PREFIX_CTRL) + return boost::lexical_cast(E300_SERVER_CTRL_PORT1); + else if (prefix == E300_RADIO_DEST_PREFIX_TX) + return boost::lexical_cast(E300_SERVER_TX_PORT1); + else if (prefix == E300_RADIO_DEST_PREFIX_RX) + return boost::lexical_cast(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 >(rx_fe_path / "dc_offset" / "value") + .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, perif.rx_fe, _1)) + .set(std::complex(0.0, 0.0)); + _tree->create(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 >(rx_fe_path / "iq_balance" / "value") + .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, perif.rx_fe, _1)) + .set(std::complex(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 >(tx_fe_path / "dc_offset" / "value") + .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, perif.tx_fe, _1)) + .set(std::complex(0.0, 0.0)); + _tree->create >(tx_fe_path / "iq_balance" / "value") + .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, perif.tx_fe, _1)) + .set(std::complex(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(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(rx_dsp_path / "rate" / "range") + .publish(boost::bind(&rx_dsp_core_3000::get_host_rates, perif.ddc)); + _tree->create(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(rx_dsp_path / "freq" / "value") + .coerce(boost::bind(&rx_dsp_core_3000::set_freq, perif.ddc, _1)) + .set(e300::DEFAULT_DDC_FREQ); + _tree->create(rx_dsp_path / "freq" / "range") + .publish(boost::bind(&rx_dsp_core_3000::get_freq_range, perif.ddc)); + _tree->create(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(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(tx_dsp_path / "rate" / "range") + .publish(boost::bind(&tx_dsp_core_3000::get_host_rates, perif.duc)); + _tree->create(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(tx_dsp_path / "freq" / "value") + .coerce(boost::bind(&tx_dsp_core_3000::set_freq, perif.duc, _1)) + .set(e300::DEFAULT_DUC_FREQ); + _tree->create(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 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(rf_fe_path / "name").set("FE-"+key); + _tree->create(rf_fe_path / "sensors"); //empty TODO + _tree->create(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(rf_fe_path / "gains" / name / "range") + .set(ad9361_ctrl::get_gain_range(key)); + + _tree->create(rf_fe_path / "gains" / name / "value") + .coerce(boost::bind(&ad9361_ctrl::set_gain, _codec_ctrl, key, _1)) + .set(e300::DEFAULT_FE_GAIN); + } + _tree->create(rf_fe_path / "connection").set("IQ"); + _tree->create(rf_fe_path / "enabled").set(true); + _tree->create(rf_fe_path / "use_lo_offset").set(false); + _tree->create(rf_fe_path / "bandwidth" / "value") + .coerce(boost::bind(&ad9361_ctrl::set_bw_filter, _codec_ctrl, key, _1)) + .set(e300::DEFAULT_FE_BW); + _tree->create(rf_fe_path / "bandwidth" / "range") + .publish(boost::bind(&ad9361_ctrl::get_bw_filter_range, key)); + _tree->create(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(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 ants = boost::assign::list_of("TX/RX")("RX2"); + _tree->create >(rf_fe_path / "antenna" / "options").set(ants); + _tree->create(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 ants(1, "TX/RX"); + _tree->create >(rf_fe_path / "antenna" / "options").set(ants); + _tree->create(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 . +// + +#ifndef INCLUDED_E300_IMPL_HPP +#define INCLUDED_E300_IMPL_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 async_md_type; + boost::shared_ptr _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 rx_streamer; + boost::weak_ptr 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 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 . +// + +#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 +#include +#include +#include +#include + +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 rx_streamer = + boost::dynamic_pointer_cast( + 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 tx_streamer = + boost::dynamic_pointer_cast( + 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 my_streamer = + boost::dynamic_pointer_cast(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 my_streamer = + boost::dynamic_pointer_cast(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 my_streamer = + boost::dynamic_pointer_cast(_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 my_streamer = + boost::dynamic_pointer_cast(_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 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 >("/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( + 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( + 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 streamer) +{ + boost::shared_ptr my_streamer = + boost::dynamic_pointer_cast(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 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(); + + //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(0); + pkt[packet_info.num_header_words32+RXFC_SEQ_NUM_OFFSET] = uhd::htowx(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 seq_queue; + boost::shared_ptr async_queue; + boost::shared_ptr old_async_queue; +}; + +#define E300_ASYNC_EVENT_CODE_FLOW_CTRL 0 + +typedef boost::function tick_rate_retriever_t; + + +static void handle_tx_async_msgs(boost::shared_ptr fc_cache, + zero_copy_if::sptr xport, + boost::function 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(); + + //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, + 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 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(1, 0) : args.channels; + + boost::shared_ptr my_streamer; + for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) + { + + const size_t radio_index = _tree->access >("/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("spp", bpp/bpi)); + + //make the new streamer given the samples per packet + if (not my_streamer) + my_streamer = boost::make_shared(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 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(static_cast(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(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(1, 0) : args.channels; + + + //shared async queue for all channels in streamer + boost::shared_ptr async_md(new async_md_type(1000/*messages deep*/)); + + boost::shared_ptr my_streamer; + + for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) + { + const size_t radio_index = _tree->access >("/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("spp", bpp/bpi)); + + //make the new streamer given the samples per packet + if (not my_streamer) + my_streamer = boost::make_shared(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 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(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 . +// + +#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 +#include +#include + +#include +#include +#include +#include + +#include + +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 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(), 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 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(), 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 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(in_buff); + codec_xact_t *out = reinterpret_cast(out_buff); + std::memcpy(out, in, sizeof(codec_xact_t)); + + std::string which_str; + switch (uhd::ntohx(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(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(in->bits) & (1<<0), + uhd::ntohx(in->bits) & (1<<1), + uhd::ntohx(in->bits) & (1<<2), + uhd::ntohx(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(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(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 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(in_buff); + + if(uhd::ntohx(in->is_poke)) { + regs->poke32(uhd::ntohx(in->addr), uhd::ntohx(in->data)); + } + else { + in->data = uhd::htonx(regs->peek32(uhd::ntohx(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 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(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( + e300_sensor_manager::pack_float_in_uint32_t(temp.to_real())); + } else if (uhd::ntohx(in->which) == GPS_FOUND) { + in->value = uhd::htonx( + sensor_manager->get_gps_found() ? 1 : 0); + + } else if (uhd::ntohx(in->which) == GPS_LOCK) { + in->value = uhd::htonx( + sensor_manager->get_gps_lock().to_bool() ? 1 : 0); + } else if (uhd::ntohx(in->which) == GPS_TIME) { + in->value = uhd::htonx( + 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 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(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(in->reg), in->data); + } else { + in->data = i2c->get_i2c_reg8(in->addr, uhd::ntohx(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(in->reg), in->data); + } else { + in->data = i2c->get_i2c_reg16(in->addr, uhd::ntohx(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 _fifo_iface; + xports_t _xports[2]; + boost::shared_ptr _codec_ctrl; + boost::shared_ptr _global_regs; + boost::shared_ptr _sensor_manager; + boost::shared_ptr _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 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 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(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( + _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("recv_frame_size", e300::DEFAULT_RX_DATA_FRAME_SIZE); + data_xport_params.num_recv_frames = device_addr.cast("num_recv_frames", e300::DEFAULT_RX_DATA_NUM_FRAMES); + data_xport_params.send_frame_size = device_addr.cast("send_frame_size", e300::DEFAULT_TX_DATA_FRAME_SIZE); + data_xport_params.num_send_frames = device_addr.cast("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(); + _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 . +// + +#ifndef INCLUDED_E300_NETWORK_HPP +#define INCLUDED_E300_NETWORK_HPP + +#include +#include + +#include + + +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 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 . +// + +#ifndef INCLUDED_E300_REGS_HPP +#define INCLUDED_E300_REGS_HPP + +#include + +#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 . +// + +#include "e300_remote_codec_ctrl.hpp" + +#include +#include +#include +#include +#include + +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(transaction_t::ACTION_SET_GAIN); + if (which == "TX1") _args.which = uhd::htonx(transaction_t::CHAIN_TX1); + else if (which == "TX2") _args.which = uhd::htonx(transaction_t::CHAIN_TX2); + else if (which == "RX1") _args.which = uhd::htonx(transaction_t::CHAIN_RX1); + else if (which == "RX2") _args.which = uhd::htonx(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( + transaction_t::ACTION_SET_CLOCK_RATE); + _args.which = uhd::htonx( + 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( + transaction_t::ACTION_SET_ACTIVE_CHANS); + /*Unused*/ + _args.which = uhd::htonx( + transaction_t::CHAIN_NONE); + _args.bits = uhd::htonx( + (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(transaction_t::ACTION_TUNE); + if (which == "TX1") _args.which = uhd::htonx(transaction_t::CHAIN_TX1); + else if (which == "TX2") _args.which = uhd::htonx(transaction_t::CHAIN_TX2); + else if (which == "RX1") _args.which = uhd::htonx(transaction_t::CHAIN_RX1); + else if (which == "RX2") _args.which = uhd::htonx(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(transaction_t::ACTION_SET_LOOPBACK); + _args.which = uhd::htonx(transaction_t::CHAIN_NONE); /*Unused*/ + _args.bits = uhd::htonx(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(), &_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(), 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 . +// + +#ifndef INCLUDED_E300_REMOTE_CODEC_CTRL_HPP +#define INCLUDED_E300_REMOTE_CODEC_CTRL_HPP + +#include "ad9361_ctrl.hpp" +#include + +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 . +// + +#include "e300_sensor_manager.hpp" + +#include +#include +#include + +#include +#include +#include +#include + +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 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(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(), + &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(), + sizeof(transaction)); + } + UHD_ASSERT_THROW(uhd::ntohx(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(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(), + &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(), + sizeof(transaction)); + } + UHD_ASSERT_THROW(uhd::ntohx(transaction.which) == GPS_TIME); + // TODO: Use proper serialization here ... + return sensor_value_t("GPS epoch time", int(uhd::ntohx(transaction.value)), "seconds"); + } + + bool get_gps_found(void) + { + boost::mutex::scoped_lock(_mutex); + sensor_transaction_t transaction; + transaction.which = uhd::htonx(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(), + &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(), + sizeof(transaction)); + } + UHD_ASSERT_THROW(uhd::ntohx(transaction.which) == GPS_FOUND); + // TODO: Use proper serialization here ... + return static_cast(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(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(), + &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(), + sizeof(transaction)); + } + UHD_ASSERT_THROW(uhd::ntohx(transaction.which) == GPS_LOCK); + // TODO: Use proper serialization here ... + return sensor_value_t("GPS lock status", static_cast(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 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( + e300_get_sysfs_attr(E300_TEMP_SYSFS, "in_temp0_scale")); + unsigned long raw = boost::lexical_cast( + e300_get_sysfs_attr(E300_TEMP_SYSFS, "in_temp0_raw")); + unsigned long offset = boost::lexical_cast( + 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 . +// + +#include +#include + +#include +#include +#include +#include + +#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 sptr; + virtual bool get_gps_found(void) = 0; + + virtual uhd::sensor_value_t get_sensor(const std::string &key) = 0; + virtual std::vector 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(&v); + return *cast; + } + + static float unpack_float_from_uint32_t(const boost::uint32_t &v) + { + const float *cast = reinterpret_cast(&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 . +// + +#include +#include +#include "e300_spi.hpp" + +#ifdef E300_NATIVE +#include +#include + +#include +#include +#include +#include + +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(&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 . +// + +#ifndef INCLUDED_E300_SPI_HPP +#define INCLUDED_E300_SPI_HPP + +#include + +namespace uhd { namespace usrp { namespace e300 { + +class spi : public virtual uhd::spi_iface +{ +public: + typedef boost::shared_ptr 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 . +// + +#ifdef E300_NATIVE + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include +#include + +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(e300_get_sysfs_attr(E300_XDEV_SYSFS, "prog_done")); +} + +#include "e300_fifo_config.hpp" +#include + +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( + e300_get_sysfs_attr(E300_AXI_FPGA_SYSFS, "buffer_length")); + config.ctrl_length = boost::lexical_cast( + e300_get_sysfs_attr(E300_AXI_FPGA_SYSFS, "control_length")); + config.phys_addr = boost::lexical_cast( + e300_get_sysfs_attr(E300_AXI_FPGA_SYSFS, "phys_addr")); + + return config; +} + +#else //E300_NATIVE + +#include "e300_fifo_config.hpp" +#include + +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 +#include +#include +#include +#include +#include + +#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 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 +#include +#include +#include +#include +#include +#include "boost/date_time/posix_time/posix_time.hpp" + +#include + + +#include +#include +#include + +#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(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 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(meas_rate); + cfg_rate.nav_rate = uhd::htowx(nav_rate); + cfg_rate.time_ref = uhd::htowx(time_ref); + + _send_message( + MSG_CFG_RATE, + reinterpret_cast(&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(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(&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(pins); + cfg_ant.flags = uhd::htowx(flags); + _send_message( + MSG_CFG_ANT, + reinterpret_cast(&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(interval); + cfg_tp.length = uhd::htowx(length); + cfg_tp.status = status; + cfg_tp.time_ref = time_ref; + cfg_tp.flags = flags; + cfg_tp.antenna_delay = uhd::htowx(antenna_delay); + cfg_tp.rf_group_delay = uhd::htowx(rf_group_delay); + cfg_tp.user_delay = uhd::htowx(user_delay); + _send_message( + MSG_CFG_TP, + reinterpret_cast(&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 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( + _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(&header) + 2, + sizeof(header) - 2, checksum); + if (payload) + _calc_checksum(payload, len, checksum); + + _serial->write( + reinterpret_cast(&header), + sizeof(header)); + + if (payload) + _serial->write((const char *) payload, len); + + _serial->write( + reinterpret_cast(&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 +#include +#include +#include +#include +#include + +#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 +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 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 _sensors; + + sensor_entry _locked; + sensor_entry _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 _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 . +// + +#include "../lib/usrp/e300/e300_network.hpp" +#include +#include + +#include +#include + +#include +#include +#include + +#include + +namespace po = boost::program_options; + +static void check_network_ok(void) +{ + using namespace uhd::transport; + using namespace boost::asio::ip; + std::vector 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(), "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(); + } + + 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; +} -- cgit v1.2.3