/* Driver for wrapper around standard Xilinx Aurora (xilinx.com:ip:aurora_8b10b) * * Author: Niklas Eiling * SPDX-FileCopyrightText: 2023-2024 Niklas Eiling * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include using namespace villas::fpga::ip; Dino::Dino() : Node(), i2cdev(nullptr), i2c_channel(0), configDone(false) {} Dino::~Dino() {} bool Dino::init() { if (!configDone) { logger->error("Dino configuration not done yet"); throw RuntimeError("Dino configuration not done yet"); } if (i2cdev == nullptr) { i2cdev = std::dynamic_pointer_cast( card->lookupIp(fpga::Vlnv("xilinx.com:ip:axi_iic:"))); if (i2cdev == nullptr) { logger->error("No I2C found on FPGA"); throw RuntimeError( "Dino requires and I2C device but none was found on FPGA"); } else { logger->debug("Found I2C on FPGA"); } } configureHardware(); return true; } void Dino::setIoextDir(IoextPorts ports) { if (i2cdev == nullptr) { throw RuntimeError("I2C device not set"); } std::vector data = {I2C_IOEXT_REG_DIR, ports.raw}; i2cdev->write(I2C_IOEXT_ADDR, data); } void Dino::setIoextOut(IoextPorts ports) { if (i2cdev == nullptr) { throw RuntimeError("I2C device not set"); } std::vector data = {I2C_IOEXT_REG_OUT, ports.raw}; i2cdev->write(I2C_IOEXT_ADDR, data); } Dino::IoextPorts Dino::getIoextDir() { if (i2cdev == nullptr) { throw RuntimeError("I2C device not set"); } std::vector data; i2cdev->readRegister(I2C_IOEXT_ADDR, I2C_IOEXT_REG_DIR, data, 1); IoextPorts ports; ports.raw = data[0]; return ports; } Dino::IoextPorts Dino::getIoextDirectionRegister() { if (i2cdev == nullptr) { throw RuntimeError("I2C device not set"); } i2cdev->getSwitch().setAndLockChannel(i2c_channel); auto ret = getIoextDir(); i2cdev->getSwitch().unlockChannel(); return ret; } Dino::IoextPorts Dino::getIoextOut() { if (i2cdev == nullptr) { throw RuntimeError("I2C device not set"); } std::vector data; i2cdev->readRegister(I2C_IOEXT_ADDR, I2C_IOEXT_REG_OUT, data, 1); IoextPorts ports; ports.raw = data[0]; return ports; } Dino::IoextPorts Dino::getIoextOutputRegister() { if (i2cdev == nullptr) { throw RuntimeError("I2C device not set"); } i2cdev->getSwitch().setAndLockChannel(i2c_channel); auto ret = getIoextOut(); i2cdev->getSwitch().unlockChannel(); return ret; } DinoAdc::DinoAdc() : Dino() {} DinoAdc::~DinoAdc() {} void DinoAdc::configureHardware() { if (!configDone) { logger->error("ADC configuration not done yet"); throw RuntimeError("ADC configuration not done yet"); } if (i2cdev == nullptr) { throw RuntimeError("I2C device not set"); } i2cdev->getSwitch().setAndLockChannel(i2c_channel); IoextPorts ioext = {.raw = 0}; ioext.fields.sat_detect = true; setIoextDir(ioext); auto readback = getIoextDir(); if (readback.raw != ioext.raw) { logger->error("Ioext direction register readback incorrect: {:#x} != {:#x}", readback.raw, ioext.raw); throw RuntimeError("Failed to set IOEXT direction register"); } logger->debug("ADC Ioext: Direction register configured to {}", readback); ioext.raw = 0; ioext.fields.data_dir = true; ioext.fields.status_led = true; ioext.fields.n_we = true; ioext.fields.input_zero = true; setIoextOut(ioext); ioext.fields.n_we = false; setIoextOut(ioext); readback = getIoextOut(); if (readback.raw != ioext.raw) { logger->error("Ioext output register readback incorrect: {:#x} != {:#x}", readback.raw, ioext.raw); throw RuntimeError("Failed to set IOEXT output register"); } i2cdev->getSwitch().unlockChannel(); logger->debug("ADC Ioext: Output register configured to {}", readback); } void DinoAdc::setRegisterConfig(std::shared_ptr reg, double sampleRate) { constexpr double dinoClk = 25e6; // Dino is clocked with 25 Mhz constexpr size_t dinoRegisterTimer = 0; constexpr size_t dinoRegisterAdcScale = 1; constexpr size_t dinoRegisterAdcOffset = 2; constexpr size_t dinoRegisterDacExternalTrig = 4; constexpr size_t dinoRegisterStsActive = 5; constexpr size_t dinoRegisterDacScale = 6; constexpr size_t dinoRegisterDacOffset = 7; uint32_t dinoTimerVal = static_cast(dinoClk / sampleRate); double rateError = dinoClk / dinoTimerVal - sampleRate; reg->setRegister( dinoRegisterTimer, dinoTimerVal); // Timer value for generating ADC trigger signal // The following are calibration values for the ADC and DAC. Scale // sets an factor to be multiplied with the input value. This is the // raw 16 bit ADC value for the ADC and the float value from VILLAS for // the DAC. Offset is a value to be added to the result of the multiplication. // All values are IEE 754 single precision floating point values. reg->setRegister(dinoRegisterAdcScale, -0.001615254F); // Scale factor for ADC value reg->setRegister(dinoRegisterAdcOffset, 10.8061F); // Offset for ADC value reg->setRegister(dinoRegisterDacScale, 3448.53852516F); // Scale factor for DAC value reg->setRegister(dinoRegisterDacOffset, 32767.5F); // Offset for DAC value uint32_t rate = reg->getRegister(dinoRegisterTimer); float adcScale = reg->getRegisterFloat(dinoRegisterAdcScale); float adcOffset = reg->getRegisterFloat(dinoRegisterAdcOffset); float dacScale = reg->getRegisterFloat(dinoRegisterDacScale); float dacOffset = reg->getRegisterFloat(dinoRegisterDacOffset); uint32_t dacExternalTrig = reg->getRegister(dinoRegisterDacExternalTrig); uint32_t stsActive = reg->getRegister(dinoRegisterStsActive); Log::get("Dino")->info( "Check: Register configuration: TimerThresh: {}, Rate-Error: {} Hz, ADC " "Scale: {}, ADC Offset: {}, DAC Scale: {}, DAC Offset: {}, DAC External " "Trig: {:#x}, STS Active: {:#x}", rate, rateError, adcScale, adcOffset, dacScale, dacOffset, dacExternalTrig, stsActive); } DinoDac::DinoDac() : Dino() {} DinoDac::~DinoDac() {} void DinoDac::configureHardware() { if (!configDone) { logger->error("DAC configuration not done yet"); throw RuntimeError("DAC configuration not done yet"); } if (i2cdev == nullptr) { throw RuntimeError("I2C device not set"); } i2cdev->getSwitch().setAndLockChannel(i2c_channel); IoextPorts ioext = {.raw = 0}; setIoextDir(ioext); auto readback = getIoextDir(); if (readback.raw != ioext.raw) { logger->error("Ioext direction register readback incorrect: {:#x} != {:#x}", readback.raw, ioext.raw); throw RuntimeError("Failed to set IOEXT direction register"); } logger->debug("DAC Ioext: Direction register configured to {}", readback); ioext.fields.status_led = true; // Default gain is 1. Although not really necessary, let's be explicit here ioext.fields.gain_lsb = Gain::GAIN_1 & 0x1; ioext.fields.gain_msb = Gain::GAIN_1 & 0x2; setIoextOut(ioext); readback = getIoextOut(); if (readback.raw != ioext.raw) { logger->error("Ioext output register readback incorrect: {:#x} != {:#x}", readback.raw, ioext.raw); throw RuntimeError("Failed to set IOEXT output register"); } i2cdev->getSwitch().unlockChannel(); logger->debug("DAC Ioext: Output register configured to {}", readback); } void DinoDac::setGain(Gain gain) { if (i2cdev == nullptr) { throw RuntimeError("I2C device not set"); } i2cdev->getSwitch().setAndLockChannel(i2c_channel); IoextPorts ioext = getIoextOut(); ioext.fields.gain_lsb = gain & 0x1; ioext.fields.gain_msb = gain & 0x2; setIoextOut(ioext); i2cdev->getSwitch().unlockChannel(); } Dino::Gain DinoDac::getGain() { if (i2cdev == nullptr) { throw RuntimeError("I2C device not set"); } i2cdev->getSwitch().setAndLockChannel(i2c_channel); IoextPorts ioext = getIoextOut(); auto ret = static_cast((ioext.fields.gain_msb << 1) | ioext.fields.gain_lsb); i2cdev->getSwitch().unlockChannel(); return ret; } void DinoFactory::parse(Core &ip, json_t *cfg) { NodeFactory::parse(ip, cfg); auto &dino = dynamic_cast(ip); json_error_t err; int i2c_channel; int ret = json_unpack_ex(cfg, &err, 0, "{ s: i }", "i2c_channel", &i2c_channel); if (ret != 0) { throw ConfigError(cfg, err, "", "Failed to parse Dino configuration"); } if (i2c_channel < 0 || i2c_channel >= 8) { throw ConfigError(cfg, err, "", "Invalid I2C channel"); } dino.i2c_channel = static_cast(i2c_channel); dino.configDone = true; } static DinoAdcFactory fAdc; static DinoDacFactory fDac;